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.2. Links¶
A schema may define one or more links the describe operations than can be performed relative to an instance. Each link is associated with a specific HTTP method as described in the service definition.
For standard CRUD style resources, a resource will define get/set/create/delete links. Additional links that perform non-standard actions specific to that resource may also be defined.
For example, consider the ‘book’ resource defined below:
book:
type: object
properties:
id: { type: number }
title: { type: string }
author_ids:
type: array
items: { type: number }
publisher_id: { type: number }
links:
purchase:
path: '$/books/{id}/purchase'
method: POST
request:
type: object
properties:
num_copies: { type: number }
shipping_address: { $ref: address }
response:
type: object
properties:
delivery_date: { type: string }
final_cost: { type: number }
The purchase link describes how to purchase one or more copies of this book. In order to purchase 100 copies of book id=1, the client must perform a POST to the server at the address ‘/api/bookstore/1.0/books/1/purchase’ with a body include the requested number of copies and shipping address.
Using a DataRep instance for this book, this is accomplished via the execute() method:
>>> book = bookstore.bind('book', id=1)
>>> request = {'num_copies': 100, 'shipping_address': '123 Street, Boston' }
>>> response = book.execute('purchase', request)
>>> response
<DataRep '/api/bookstore/1.0/books/1/purchase'
type:book.links.purchase.response>
>>> response.data
{ 'delivery_date': 'Oct 1', 'final_cost': 129.90 }
Calling execute() always returns a new DataRep instance representing the response.
The list of links for a given DataRep is available by inspecting the links property:
>>> book.links.keys()
['self', 'get', 'set', 'delete', 'purchase', 'new_chapter']
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)
-