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.
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).
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 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)
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 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 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.
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.
>>> 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.
>>> 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.
>>> 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.
>>> 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)source will take precedence in case of conflicts).api.clients.bindings.new,
provided here for convenience.>>> 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)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.
>>> 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.
>>> 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.
>>> 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.
>>> 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.
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.
>>> 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)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.