ObexCode Logo ObexCode Logo

SyncServer API Python interface

The Python OCSS library provides a pythonic interface with which third-party developers can easily interact with the SyncServer web service. It is distributed under the terms of the (Apache 2.0) license.

Installation

The latest version of the OCSS library is made available as a standard gzipped tarball of the source code. To install it, unpack and run setup.py as follows (you may need to be root):

$ tar xzf OCSS-latest.tar.gz
$ cd OCSS-<version>
$ python setup.py install

From release 1.1.0 of the OCSS library, only functionality provided by the Python standard library is required, so there are no dependencies beyond Python version 2.5 (or later).

Getting started

The quickest way to start working with the API is to instantiate ocss.SyncServerAPI with no arguments. This will connect via HTTPS to the default host (api.obexcode.com), and provide access to those parts of the service which do not require authorization; such as the phone model database:

>>> import ocss
>>> api = ocss.SyncServerAPI()
>>> for vendor in api.phones.vendors:
...     print vendor.name, len(vendor.models)
... 
HTC 26
Nokia 188
LG 5
Sony Ericsson 84
etc.
>>>

Authentication

Authentication makes use of SyncServer's OAuth functionality. Hence, before you can access protected parts of the API, you must have generated both a consumer and access token via the web portal's OAuth tokens page.

Once you have your authentication tokens, a SyncServerAuth object can be used to provide this information when you instantiate the API interface, thusly:

>>> import ocss
>>> auth = ocss.SyncServerAuth(
...         consumer_key,
...         consumer_secret,
...         access_key,
...         access_secret,
...         )
>>> api = ocss.SyncServerAPI(auth)

General API

The various parts of an instantiated API hierarchy are, for the most part, either Resources or Collections (of Resources). Most nodes share a similar access interface. The documentation in the library code itself, accessible in Python's interactive interpreter through the help() builtin, is quite thorough and should be useful to explore the hierarchy interactively (if book learnin' ain't your style).

Unless otherwise specified, all dates and times supplied to the API should be specified as UTC. Otherwise, confusion reigns.

Resources

Resources represent single items - a particular client or handset model, for example. Generally they are represented in the web API as an Atom entry.

Every resource has the following attributes and methods:

  • created The date-time of this resource's creation. While most kinds of resources provide this information, it is not guaranteed that all of them will.

  • id
    Individual resources always have an ID. Usually, but not always, this is a UUID-style string.

  • modified The date-time of this resource's last modification. Like created, this attribute is likely, but not guaranteed.

  • delete()
    Calling this method will send the server a request to delete this resource.

  • update(**kwargs)
    The basic functionality of this method is to update the server with any local changes made to the resource's properties. New values can also be specified by passing keyword arguments to the update() call. The properties available to be updated in this manner depend on the definition of the type of resource, and are documented below.

  • refresh()
    This method will clear out the local data cache for this resource, including any unsent changes made to its properties. This means that the resource data will be re-requested the next time it is required.

Two objects pointing to the same resource will equal (==) each other, but are not guaranteed to be the same object (using is).

Collections

Collections are containers for Resources; all the handset models from a particular vendor, for example. These are usually the equivalent of the web API's Atom feeds.

The general interface for collections mirrors that of Python's own dicts, using the collection's contents' IDs for the keys. Eg:

>>> 'some-vendor-id' in api.phones.vendors
True
>>> vendor = api.clients['some-vendor-id']
>>> vendor.id
'some-vendor-id'
>>> api.clients['invalid-vendor-id']
Traceback (most recent call last):
  ...
ocss.exceptions.NotFoundError: "No Vendor with ID 'invalid-vendor-id'"

Some collections (though currently only implemented for the contents of datastores) can additionally have filters applied to them by, calling the Collection itself with keyword arguments. The return value will be a new instance of that Collection, with the specified filters applied. Eg:

>>> store = api.clients.datastore['my-datastore-client-id']
>>> len(store.contents)
15
>>> len(store.contents(since=datetime.datetime(2009, 11, 1, 12, 30)))
10
>>> len(store.contents(since="2009-12-09T130500Z"))
5

Additionally, Collections have available the following methods:

  • new(...)
    Attempts to create a new resource in the collection. The interface for this method will vary depending on what kind of resource the collection contains. Each will be documented below.

  • delete(resource)
    Attempts to delete the given resource (instance or ID) from this collection.

  • update(resource, **kwargs)
    Will request that the given resource is updated, in the same manner that a call to resource.update(**kwargs) would.

  • refresh()
    As with generic Resources, calling this method will clear out the local data cache for this Collection, prompting a re-request when next required.

Exceptions

There are several exceptions defined by the library; all located in the ocss.exceptions module:

  • OCSSAPIError
    The baseclass for all exceptions which will be raised by the library. If you want to catch all, and only, exceptions triggered due to use of this library, catching OCSSAPIError should be enough.

  • ConnectionError
    This error is raised when something goes wrong with the connection to the API server. Should this occur, it is most likely enough to simply try again later; the library will automatically attempt to reconnect to the server under most circumstances.

  • ServerError
    The server encountered an error in its processing of the request. It would be most helpful if such occurrences are reported to ObexCode, as it indicates a problem on the server.

  • ClientError
    The library tried to perform an action which the server didn't take kindly to. If you encounter this exception while using the library as prescribed, it indicates a problem in the library itself, and the incidence should be reported to the developers.

  • NotFoundError This exception will be raised when a resource is requested which does not exist on the server. This exception is a subclass of KeyError, as well as OCSSAPIError; instances of it are what you will encounter when accessing a non-existent member of a collection, for example.

  • NotAllowedError
    An attempt was made to access a resource, which was denied. This can be either a user-credential issue, or a resource which doesn't support the attempted operation. For example, the phones API is read-only, and attempts to update() or delete() will fail with this error.

Phones

>>> api.phones
<Phones collection at 0x123456>

For the most part, authentication credentials are not required to access the phones API. The exception being the model-detection methods; described below.

The phones object itself is only nominally a collection; it doesn't itself represent any functionality of the API, and is intended solely to group the vendors and models collections logically.

Vendors

>>> api.phones.vendors
<Vendors collection at 0x123456>
>>> api.phones.vendors['some-vendor-id']
<Vendor id='some-vendor-id'>

This collection provides the interface to access to all of the mobile device manufacturers/vendors known by the service.

In addition to the standard resource attributes and methods, Vendor instances also have the following:

  • name
    The name of the vendor.

  • models
    Each Vendor resource provides a collection of all that vendor's known handset/device models. See the below section for more information.

Note that the vendor API is read-only. Attempts to update or delete these resources will result in a NotAllowedError.

Models

>>> api.phones.models
<Models collection at 0x123456>
>>> api.phones.vendors['some-vendor-id'].models
<Models collection at 0x456789>
>>> api.phones.models['some-model-id']
<Model id='some-model-id'>

Models collections are available directly from SyncServerAPI.phones, for the entire collection of models, or from the models attribute of individual Vendor resources (see above).

All Models collections have the following methods. Note that these are not limited by vendor, even when called from a vendor's model collection. These detect_* methods also require that valid authentication credentials were supplied when instantiating the API interface.

  • detect_auto(number)
    For allowed users, this method will attempt to identify a handset using only the phone number. It provides a Model instance if successful, or None otherwise.

  • detect_manual(number)
    This method will send a message to the given phone number, inviting the owner to visit a specific handset-detection page. The immediate return value will be a unique code string.
    Once the visit has been made, the detect_poll(...) method (below) can be used to fetch the detected Model.

  • detect_poll(code)
    Pass this method a code acquired in a previous detect_manual(...) call, and it will query the server for the identified handset model.
    The return value will be either a Model instance, False; indicating that the model could not be identified, or None, indicating that the phone has not yet visited the detection page. Additionally, this method will raise a ClientError if an invalid code was used.

In addition to the standard resource attributes and methods, Model instances provide the following:

  • image_url
    The URL to an image of the phone handset.

  • name
    The name of the phone model.

  • vendor
    An instance of the Vendor resource associated with this handset.

Clients

>>> api.clients
<Clients collection at 0x123456>
>>> api.clients['some-client-id']
<SyncmlClient id='some-client-id'>
>>> api.clients['another-client-id']
<ActivesyncClient id='another-client-id'>

A client is one half of a sync relationship, and comes in several flavours: activesync, datastore, gdata, syncml. The difference between these are described later in this section.

At the current time, only read-access is possible through the generic client interface (api.clients); listing all the registered clients, and fetching individual client instances. Creating new clients must be done through the different sub-collections.

  • bind(source, target)
    Use this method to bind two clients into a sync relationship (in which the source will take precedence in case of conflicts).
    This method is actually a reference to api.clients.bindings.new, provided here for convenience.

Bindings

>>> api.clients.bindings
<Bindings collection at 0x123456>
>>> api.clients.bindings[123]
<Binding id=123>

A binding is used to connect two clients in a synchronisation relationship.

The bindings collection provides a mechanism to create new bindings:

  • new(source, target)
    Use this method to bind two clients into a sync relationship (in which the source will take precedence in case of conflicts). The arguments can be supplied as either client resource instances, or just their IDs.

Each individual binding has the following attributes, in addition to those supplied by the generic resource interface:

  • dominant
    A string value; either 'source' or 'target', indicating which client will take precedence when there are conflicting changes in sync data.

  • source
    The 'source' client instance.

  • target
    The 'target' client instance.

SyncML

>>> api.clients.syncml
<Syncml collection at 0x12456>
>>> api.clients.syncml['some-client-id']
<SyncmlClient id='some-client-id'>

SyncML client resources expose the following additional attributes and methods:

  • imei
    The IMEI for the client device, if any.

  • model
    The device Model for this client.

  • phone_number
    The phone number of the client device.

  • timezone
    The timezone registered against the device.

  • send_setup(pincode)
    Sends a setup SMS containing server information to the client device. The given pincode will be required to access the message on the phone.

  • sync()
    Initiates a sync with the device from the server end, if possible.

ActiveSync

>>> api.clients.activesync
<Activesync collection at 0x12456>
>>> api.clients.activesync['some-client-id']
<ActivesyncClient id='some-client-id'>

ActiveSync clients provide the following attributes:

  • username
    This is the authentication username for the client.

  • old_pass and password
    These attributes are not automatically populated with values. They are only potentially used during an .update() call, when a change in the client password is desired.

Datastore

>>> api.clients.datastore
<Datastore collection at 0x12456>
>>> api.clients.datastore['some-client-id']
<DatastoreClient id='some-client-id'>

The following additional attribute is provided by Datastore client instances.

  • name
    The custom name assigned to this datastore.

  • contents
    The collection of the contents of this datastore. See below.

Datastore contents

>>> store = api.clients.datastore['my-datastore-client-id']
>>> store.contents
<ContentCollection collection at 0x123456>
>>> store.contents['my-content-id']
<Content id='my-content-id'>

The contents of a datastore are somewhat unrestricted. The library currently makes no effort to wrap or parse the raw data string itself, neither when storing nor retrieving.

There are two filters which can be applied to the contents collection; 'since' and 'dataclass'.

>>> store = api.clients.datastore['my-datastore-client-id']
>>> len(store.contents)
15
>>> len(store.contents(since=datetime.datetime(2009, 11, 1, 12, 30)))
10
>>> len(store.contents(dataclass='contacts'))
8
>>> len(store.contents(dataclass='contacts', since='2009-11-01T123000Z')
5

Specifying the since parameter will return a collection holding all of the changes to the datastore's contents since the given timestamp. Each entry will provide an additional operation attribute (see below) in this circumstance.

Filtering on dataclass will limit the collection's contents to only those associated with that dataclass. A dataclass is a category of similar content; implicitly understood values include "contacts", "tasks", "events", "notes", though any string value is valid.

The content collection also allows the creation of new content items:

  • new(data, ctype, [dataclass])
    Call this method to create a new item of content in the datastore.

    • The data argument is the raw string of data to be put into storage.
    • ctype is the content-type (eg. "text/plain") of the given data.
    • dataclass can optionally be supplied, to categorise the supplied data. Dataclasses which are implicitly understood by the API are: "contacts", "tasks", "events" and "notes"; but any arbitrary string value can be used. If no dataclass is supplied one will be assigned based on the ctype, if known, or falling back to using "Misc".

Meanwhile, each Content resource provides the following additional attributes:

  • data
    The raw string data representation of the content.

  • dataclass
    The category under which this content item is stored.

  • operation
    When a since filter has been applied to a collection, this attribute of its contents will indicate what has happened to the item. Possible values are 'update', 'insert' and 'delete'.

  • title
    The title or display name of the content item.

  • type
    The (MIME) content-type of the item data.

GData

>>> api.clients.gdata
<GData collection at 0x12456>
>>> api.clients.gdata['some-client-id']
<GDataClient id='some-client-id'>

Any third-party data store which supports the gdata interface, such as GMail, can also be synced in to the system using these clients and their datastores.

  • new(user_id)
    Create and return a new GData client with the given user_id. A GMail user_id, for example, is the email address itself.

Each GData client resource also provides the following:

  • datastores
    A list of information about all the datastores registered against this client. Each item in the list is a dictionary of: dataclass, url and name.

  • user_id
    The user_id for this GData client.

  • add_datastore(dataclass, url, key, secret, scopes, [name], [token_type]))
    Register a new datastore against this client.

    • dataclass should be one of either "Contacts" or "Calendar".
    • url is the intended location of the datastore's GData interface.
    • name is an optional custom name for the datastore. The name of the dataclass will be used if no name is supplied.
    • key and secret should be the values from the access token used to authenticate with the datastore.
    • scopes should be a list of the base URLs for which the token is valid.
    • token_type should be either 1 or 2, indicating a request or access token respectively. The default is 2; an access token.