7. Extensions¶
prestans extensions are purpose built extensions that act as bridges between prestans elements and for argument sake persistent backends. Etensions build on the core prestans framework and are heavily dependent on environment specific packages.
7.1. Data Adapters¶
Our Models chapter discusses in detail, the use of Models to validate and build responses returned by handlers. Models can use AttributeFilters to make exceptions to the validation rules set out by your Model’s original definition.
We identified the scenario and data validation benefits of converting persistently stored data to REST models, and in turn identified that it’s a code laborious process.
DataAdapters fills that gap in prestans, it automates the process of converting persistent models into REST models by providing:
- A static registry
prestans.ext.data.adapters.registry
, that maps persistent models to REST models - QueryResultsIterator, that iterates through collections of persistent results and turns them into REST models. QueryResultsIterator is specific to backends and uses the registry to determine relationships between persistent and REST models.
Note
You can map multiple REST models to the same persistent model.
For our sample code assume that rest models live in the pdemo.rest.models
and the persistent models live in pdemo.models
, and is written for AppEngine.
prestans supports SQLAlchemy
and AppEngine’s ndb
and datastore
. You can write your DataAdapter to support custom backends.
7.1.1. Pairing REST models to persistent models¶
The registry allows you to provide a map acceptable translations between persistent and REST models. If a persistent model maps to more than one REST model, DataAdapters try and make the sensible choice unless you explicitly provide the REST model you wish to adapt the data to.
General practice is to register the persistent models along side their definition. An excerpt from pdemo.models
.
Registering the persistent model is as easy as calling the register_adapter
method on prestans.ext.data.adapters.registry
, and providing it an instance of the appropriate ModelAdapter
.
Consider a REST model defined prestans.rest.models
:
import prestans.types
class Band(prestans.types.Model):
id = prestans.types.Integer(required=False)
name = prestans.types.String(required=True, max_length=30)
albums = prestans.types.Array(element_template=Album(), required=False)
And then in your persistent model package, use prestans.ext.data.adapters.registry
to join the dots. Ensure that all children models are present in the registry (e.g Album):
from google.appengine.ext import ndb
from google.appengine.api import users
import prestans.ext.data.adapters
import prestans.ext.data.adapters.ndb
class Band(ndb.Model):
name = ndb.StringProperty()
created = ndb.DateTimeProperty(auto_now_add=True)
last_updated = ndb.DateTimeProperty(auto_now=True)
@property
def albums(self):
return Album.query(ancestor=self.key).order(Album.year)
@property
def id(self):
return self.key.id()
# Register the persistent model to adapt to the Band rest model, also
# ensure that Album is registered for the children models to adapt
prestans.ext.data.adapters.registry.register_adapter(
prestans.ext.data.adapters.ndb.ModelAdapter(
rest_model_class=pdemo.rest.models.Band,
persistent_model_class=Band
)
)
7.1.2. Adapting Models¶
Once your models have been declared in the adapter registry, your REST handler:
- Query the data that your handler is expected to return
- Set the HTTP status code
- Use the appropriate QueryResultIterator to construct your REST adapted models
- Assign the returned collection to
self.response.body
from google.appengine.ext import ndb
import pdemo.models
import pdemo.rest.handlers
import pdemo.rest.models
import prestans.ext.data.adapters.ndb
import prestans.handlers
import prestans.parsers
import prestans.rest
class CollectionRequestParser(prestans.parsers.RequestParser):
GET = prestans.parsers.ParserRuleSet(
response_attribute_filter_template=prestans.parsers.AttributeFilter.from_model(pdemo.rest.models.Band())
)
class BandCollection(pdemo.rest.handlers.Base):
request_parser = CollectionRequestParser()
def get(self):
bands = pdemo.models.Band().query()
self.response.http_status = prestans.rest.STATUS.OK
self.response.body = prestans.ext.data.adapters.ndb.QueryResultIterator(
collection=bands,
target_rest_instance=pdemo.rest.models.Band
)
If you are using AttributeFilters (read our chapter on Validating Requests to learn how you can make exceptions to Model validation rules) you can pass them onto the QueryResultsIterator which results in the QueryResultsIterator skipping accessing that property all together significantly reducing the load on the Data Layer:
class BandCollection(pdemo.rest.handlers.Base):
request_parser = CollectionRequestParser()
def get(self):
bands = pdemo.models.Band().query()
self.response.http_status = prestans.rest.STATUS.OK
self.response.body = prestans.ext.data.adapters.ndb.QueryResultIterator(
collection=bands,
target_rest_instance=pdemo.rest.models.Band,
attribute_filter = self.response.attribute_filter
)