15.1.2. DataReps

15.1.2.1. Using DataRep objects

DataRep objects are the primary means of interacting with a REST server. Each instance is associated with a URI defining the address of the resource on the server, and optionally a json-schema that describes the structure of the data.

There are a few ways to create DataRep instances:

  • Use Service.bind() method - this looks up a resource by name in the service definition.
  • Call follow() or execute() from another DataRep instance
  • Create a new object via create() from a DataRep instance that supports creation.

Once created, a local copy of the data for this instance is retrieved from the server via pull() and a the server is updated via push().

A common read-modify-write cycle is shown below:

# Staring with bookstore as a Service object
>>> book = bookstore.bind('book', id=1)
>>> book
<DataRep '/api/bookstore/1.0/book' type book>

# Retrieve a copy of the data from server
>>> book.pull()

# Examine the data retrieved
>>> book.data
{ 'id': 1, 'title': 'My first book',
  'author_ids': [1, 9], 'publisher_id': 3 }

# Change the data
>>> book.data['title'] = 'My First Book - Using Python'

# Push the changes back to the server
>>> book.push()

15.1.2.3. Relations

Relations provide the means to reach other resources that are related to this one. Each relation is essentially a pointer from one resource to another.

For example, the book resource above has a publisher_id data member. This identifies the publisher associated with this book. The schema defines a relation ‘publisher’ that provides the link to the full publisher resource:

book:
   relations:
      publisher:
         resource: publisher
         vars:
            id: '0/publisher_id'

This allows using the follow() method to get to a DataRep for the publisher:

>>> pub = book.follow('publisher')
>>> pub
<DataRep '/api/bookstore/1.0/publishers/3' type:publisher>

>>> pub.data
{'id': 3,
 'name': 'DigiPrinters',
 'billing_address': {'city': 'Boston', 'street': '123 Street'}
 }

The follow method use the vars property in the relation definition to map the book.data[‘publisher_id’] value to the id variable in the publisher representation, which is used to build the full URI.

15.1.2.4. Fragments

In some cases, it may be necessary to follow links on data nested within a single resource. Consider the book example from above:

>>> book
>>> book.data
{ 'id': 1, 'title': 'My first book',
  'author_ids': [1, 9], 'publisher_id': 3 }

>>> book.data['author_ids']
[1, 9]

Just like following the publisher link based on publisher_id, it’s possible to follow a link to reach an author resource. However, unlike publisher, there are multiple authors.

The schema defines the full relation to reach an author as follows:

book:
   description: A book object
   type: object
   properties:
      id: { type: number }
      title: { type: string }
      publisher_id: { type: number }
      author_ids:
         type: array
         items:
            id: author_id
            type: number

            relations:
               full:
                  resource: author
                  vars: { id: '0' }

The publisher relation was defined at the top-level at book.relations.publisher. The full relation is nested within the structure at book.properties.author_ids.items.relations.full. The best way to understand this is to look at the type at the same level as the relations keywork. In this case relations.full is aligned with type: number. This number is one author id in an array of authors associated with this book. That means that the full relation must be invoked relative to an item in the book.author_ids array. The full reference indicates that following this link will lead to a complete resource that is represented in part by the current data member (the id):

>>> first_author = book['author_ids'][0].follow('full')
>>> first_author
<DataRep '/api/bookstore/1.0/authors/1' type:author>

>>> second_author = book['author_ids'][1].follow('full')
>>> second_author
<DataRep '/api/bookstore/1.0/authors/9' type:author>

Breaking down that first line further shows DataRep fragment instances created:

>>> book.relations.keys()
['instances', 'publishers']
>>> book_author_ids = book['author_ids']
>>> book_author_ids
<DataRep '/api/bookstore/1.0/books/1#/author_ids' type:book.author_ids>
>>> book_author_ids.data
[1, 9]
>>> book_author_ids_0 = book_author_ids[0]
<DataRep '/api/bookstore/1.0/books/1#/author_ids/0'
    type:book.author_ids[author_id]>
>>> book_author_ids_0.relations.keys()
['full']
>>> first_author = book_author_ids_0.follow('full')

Each time a DataRep instance is indexed using [], a new DataRep fragment is created. This fragment is still associated with the same URI because it is merely a piece of the data at that URI based on the JSON pointer following the hash mark ‘#’.

15.1.2.5. class DataRep

class sleepwalker.datarep.DataRep(service=None, uri=None, jsonschema=None, fragment='', root=None, data=<_DataRepValue UNSET>, path_vars=None)

A concrete representation of a resource at a fully defined address.

The DataRep object manages a data representation of a particular REST resource at a defined address. If a jsonschema is attached, the jsonschema describes the structure of that data representation.

__init__(service=None, uri=None, jsonschema=None, fragment='', root=None, data=<_DataRepValue UNSET>, path_vars=None)

Creata a new DataRep object associated with the resource at uri.

Parameters:
  • service – the service of which this resource is a part. :type service: sleepwalker.service.Service
  • uri (string) – the URI of the resource, without any fragment attached. If fragment and root are passed, this must be None and the URI is inherited from the root.
  • jsonschema (reschema.jsonschema.Schema subclass) – a jsonschema.Schema derivative that describes the structure of the data at this uri. If fragment and root are passed, this must be None, as the schema is inherited from the root.
  • fragment (string) – an optional JSON pointer creating a DataRep for a portion of the data at the given URI. Requires root to be set.
  • root (DataRep) – must be set to the DataRep associated with the full data if fragment is set.
  • data (Whatever Python data type matches the schema.) – optional, may be set to initialize the data value for this representation. May not be used with fragment.

Param path_vars: optional, variables to resolve paths of links :type path_vars: dict

apply_params(**kwargs)

Discard existing params and return a new DataRep with given params.

The new DataRep has an UNSET value, whether or not this DataRep has a pulled value or has un-pushed modificatinos. The state of the data in this DataRep is unaffected.

Returns:a DataRep with the supplied parameters applied to the URI.
Raises:NotImplementedError – if this DataRep URI has a fragment.
create(obj)

Create a new instance of a resource in a collection.

This relies on the ‘create’ link in the json-schema.

On success, this returns a new DataRep instance associated with the newly created resource.

data

Return the data associated with this resource.

This property serves as the client-side holder of the data associated the resource at the given address. Calling pull() will refresh this propery with the latest data from the server. Calling push() will update the server with this data.

If data has not yet been retrieved from the server, the first call to access this proprerty will result in a call to pull() Subsequent accesses will not refresh the data automatically, the client must manually invoke pull() as needed to refresh.

If the last pull() resulted in a failure, an exception will be raised.

If this DataRep instance defines a fragment, the data returned will be the result of following the fragment (as a JSON pointer) from the full data representation as the full URI.

data_unset()

Return True if the data property has not yet been set.

If a failed attempt to fetch the data has beenmade, this returns false.

data_valid()

Return True if the data property has a valid data representation.

This method will return false if no data has yet been pulled, even if the resource on the server has valid data. It will not ever trigger a network operation.

delete()

Issue a delete for this resource.

This relies on the ‘delete’ link.

On success, this marks the data property as DELETED and returns self.

execute(_name, _data=None, **kwargs)

Execute a link by name.

Param:the link to follow and must exist in the jsonschema
Param:is used if the link defines a request object

additional keword arguments may be passed to resolve path variables.

follow(_name, **kwargs)

Follow a relation by name.

Param:the name of the relation to follow, and must exist in the jsonschema relations

Additional keyword arguments can be passed to resolve path variables.

classmethod from_schema(service=None, uri=None, jsonschema=None, root=None, fragment='', **kwargs)

Create the appropriate type of DataRep based on json-schema type

This factory method instantiates the right kind of DataRep depending on whether the schema supplied has a type of “object” (dict), “array” (list) or some other type.

Arguments are the same as for DataRep.__init__

full()

Return a DataRep representing the full item for this fragment.

pull()

Update the data representation from the server.

This relies on the schema ‘get’ link. This will always perform an interaction with the server to refresh the representation as per the ‘get’ link.

On success, the result is cached in self.data and self is returned.

push(obj=<_DataRepValue UNSET>)

Modify the data representation for this resource from the server.

This relies on the schema ‘set’ link. This will always perform an interaction with the server to attempt an update of the representation as per the ‘set’ link.

If obj is passed, self.data is modified. This is true even if the push to the server results in a failure.

Note that if this DataRep is associated with a fragment, the full data representation will be pulled if necessary, and the full modified data will then be pushed to the server.

Returns:

self

Raises:
  • DataNotSetError – if no data exists or has been supplied to push to the server.
  • DataPullError – if the data needed to be pulled in order to be modified and pushed, but the pull failed.
  • LinkError – if no set link is present to which to push.
  • ValidationError – if validation was requested and the value to be pushed fails validation.

15.1.2.6. class Schema

class sleepwalker.datarep.Schema(service, jsonschema)

A Schema object represents the jsonschema for a resource or type.

The Schema object is the generic form of a REST resource as defined by a json-schema. If the json-schema includes a ‘self’ link, the bind() method may be used to instantiate concrete resources at fully defined addresses. This class may also represent a type defined in the service definition.

Typcially, a Schema instance is created via the Service class. This allows inspection of the jsonschema as well as to bind and create DataRep instances:

>>> book_schema = bookstore.lookup_schema('book')
>>> book_schema
<Schema '/api/bookstore/1.0/books/{id}' type:book>
>>> book_schema.jsonschema.validate({'id': 1})
>>> book1 = book_schema.bind(id=1)
>>> book1
<DataRep '/api/bookstore/1.0/books/1' type:book>
__init__(service, jsonschema)

Create a Schema bound to service as defined by jsonschema.

bind(**kwargs)

Return a DataRep object by binding variables in the ‘self’ link.

This method is used to instantiate concreate DataRep objects with fully qualified URIs based on the ‘self’ link associated with the jsonschema for this object. The **kwargs must match the parameters defined in the self link, if any.

Example:

>>> book_schema = Schema(bookstore, book_jsonschema)
>>> book1 = book_schema.bind(id=1)