Interacting with Syncano

This tutorial will walk you through our ORM syntax and how to use it to make proper API calls.

Creating a Connection

In each example I’ll be assuming that you have configured connection to syncano:

>>> import syncano
>>> connection = syncano.connect(email='YOUR_EMAIL', password='YOUR_PASSWORD')

Accessing models

All models are defined in syncano.models.base but syncano simplifies access to them by attaching all of them directly to connection. Thus:

from syncano.models.base import Instance

and:

Instance = connection.Instance

are equivalent.

Creating objects

A model class represents a single Syncano API endpoint, and an instance of that class represents a particular record in this endpoint.

To create an object, instantiate it using keyword arguments to the model class, then call save() to save it to the Syncano API.

Here’s an example:

>>> instance = Instance(name='test-one', description='')
>>> instance.save()

This performs a POST request to Syncano API behind the scenes. Syncano doesn’t hit the API until you explicitly call save().

Note

To create and save an object in a single step, use the create() method. To create and save multiple objects in a single step, use the bulk_create() method.

Saving changes to objects

To save changes to an object that’s already in the Syncano API, use save(). Regarding our instance from previous example, this example changes its description and updates its record in the Syncano API:

>>> instance.description = 'new description'
>>> instance.save()

This performs a PUT request to Syncano API behind the scenes. Syncano doesn’t hit the API until you explicitly call save().

Note

To change and save an object in a single step, use the update() method.

Retrieving objects

To retrieve objects from Syncano API, construct a query via a Manager on your model class.

Each model has only one Manager, and it’s called please by default. Access it directly via the model class, like so:

>>> Instance.please
[<Instance: test>, <Instance: test-two>, '...(remaining elements truncated)...']
>>> i = Instance(name='Foo', description='Bar')
>>> i.please
Traceback:
...
AttributeError: Manager isn't accessible via Instance instances.

Note

Managers are accessible only via model classes, rather than from model instances, to enforce a separation between “table-level” operations and “record-level” operations.

Retrieving all objects

The simplest way to retrieve objects from a Syncano API is to get all of them. To do this, use the all() or list() method on a Manager:

>>> Instance.please
>>> Instance.please.all()
>>> Instance.please.list()

This performs a GET request to Syncano API list endpoint behind the scenes.

Note

all() removes any limits from query and loads all possible objects from API, while the list() method just executes current query.

Manager is lazy

Manager is lazy – the act of creating a Manager doesn’t involve any API activity. You can stack Manager methods all day long, and Syncano won’t actually run the API call until the Manager is evaluated. Take a look at this example:

>>> query = Class.please.list('test-instance')
>>> query = query.limit(10)
>>> print(query)

Though this looks like two API calls, in fact it hits API only once, at the last line (print(query)). In general, the results of a Manager aren’t fetched from API until you “ask” for them.

Retrieving a single object

If you know there is only one object that matches your API call, you can use the get() method on a Manager which returns the object directly:

>>> instance = Instance.please.get('instance-name')

This performs a GET request to Syncano API details endpoint behind the scenes.

If there are no results that match the API call, get() will raise a SyncanoDoesNotExist exception. This exception is an attribute of the model class that the API call is being performed on - so in the code above, if there is no Instance object with a name equal “instance-name”, Syncano will raise Instance.DoesNotExist.

Note

To have more RESTful like method names there is detail() alias for get() method.

Removing a single object

The delete method, conveniently, is named delete(). This method immediately deletes the object and has no return value. Example:

>>> instance = Instance.please.get('test-one')
>>> instance.delete()

This performs a DELETE request to Syncano API details endpoint behind the scenes.

Limiting returned objects

Use a subset of Python’s array-slicing syntax to limit your Manager to a certain number of results.

For example, this returns the first 5 objects:

>>> Instance.please[:5]

This returns the sixth through tenth objects:

>>> Instance.please[5:10]

Negative indexing (i.e. Instance.please.all()[-1]) is not supported.

Note

If you don’t want to use array-slicing syntax there is a special manager method called limit().

Warning

Python’s array-slicing syntax is a expensive operation in context of API calls so using limit() is a recommended way.

Lookups that span relationships

Syncano API has nested architecture so in some cases there will be a need to provide a few additional arguments to resolve endpoint URL.

For example ApiKey is related to Instance and its URL patter looks like this:

/v1/instances/{instance_name}/api_keys/{id}

This example will not work:

>>> ApiKey.please.list()
Traceback:
...
SyncanoValueError: Request property "instance_name" is required.

So how to fix that? We need to provide instance_name as an argument to list() method:

>>> ApiKey.please.list(instance_name='test-one')
[<ApiKey 1>...]

This performs a GET request to /v1/instances/test-one/api_keys/.

Note

Additional request properties are resolved in order as they occurred in URL pattern. So if you have pattern like this /v1/{a}/{b}/{c}/ list() method can be invoked like any other Python function i.e list('a', 'b', 'c') or list('a', c='c', b='b').

Backward relations

For example Instance has related ApiKey model so all Instance objects will have backward relation to list of ApiKey‘s:

>>> instance = Instance.please.get('test-one')
>>> instance.api_keys.list()
[<ApiKey 1>...]
>>> instance.api_keys.get(id=1)
<ApiKey 1>

Note

Related objects do not require additional request properties passed to list() method.

Falling back to raw JSON

If you find yourself needing to work on raw JSON data instead of Python objects just use raw() method:

>>> Instance.please.list()
[<Instance: test>, <Instance: test-two>, '...(remaining elements truncated)...']

>>> Instance.please.list().raw()
[{u'name': u'test-one'...} ...]

>>> Instance.please.list().limit(1).raw()
[{u'name': u'test-one'...}]

>>> Instance.please.raw().get('test-one')
{u'name': u'test-one'...}

Environmental variables

Some settings can be overwritten via environmental variables e.g:

$ export SYNCANO_LOGLEVEL=DEBUG
$ export SYNCANO_APIROOT='https://127.0.0.1/'
$ export SYNCANO_EMAIL=admin@syncano.com
$ export SYNCANO_PASSWORD=dummy
$ export SYNCANO_APIKEY=dummy123
$ export SYNCANO_INSTANCE=test

Warning

DEBUG loglevel will disable SSL cert check.