5. Types & Models¶
Models allow you to define rules for your API’s data. Prestans uses these rules to ensure the integrity of the data exchanged between the client and the server. If you’ve’ used the Django or Google AppEngine Prestans models will look very familiar. Prestans models are not persistent.
Prestans types are one of the following:
prestans.types.DataType
all Prestans types are a subclass ofDataType
, this is the most basic DataType in the Prestans world.prestans.types.DateStructure
are a subclass ofDataType
but represent complex types like Date Time.prestans.types.DateCollection
are a subclass ofDataType
and represent collections like Arrays or Dictionaries (refered to as Models in the Prestans world).
Each type has configurable properties that Prestans uses to validate data. It’s important to design your models with the strictest case in mind. Use request and response filters to relax the rules for specific cases, refer to our chapter on Validating Requests & Responses.
This chapter introduces you to writing Models and using it in various parts of your Prestans application. It is possible to write custom DataType
.
All Prestans types are wrappers on Pythonic data types, that you get a chance to define strict rules for each attribute. These rules ensure that the data you exchange with a client is sane, ensures the integrity of your business logic and minimizes issues when persisting data. All of this happens even before your handler is even called.
Most importantly it cuts out the need for writing trivial boilerplate code to validate incoming and outgoing data. If your handler is called you can trust the data is sane and safe to use.
Prestans types are divided into, Basic Types, and Collections, currently supported types are:
String
, wraps a Pythonstr
Integer
, wraps a Python numberFloat
, wraps a Python numberBoolean
, wraps a Pythonbool
DataURLFile
, supports uploading files via HTML5 FileReader APIDate
, wraps Pythondate
Time
, wraps Pythontime
DateTime
, wraps Pythondatetime
Array
, wraps Pythonlists
Model
, wraps Pythondict
The second half of this chapter has a detailed reference of configuration parameters for each Prestans DataType
.
5.1. Writing Models¶
Models
are defined by extending prestans.types.Model
. Models contain attributes which either be a basic Prestans type (a direct subclass of prestans.types.DataType
) or a reference to an instance of another Model, or an Array
of objects.
The REST standard talks about URLs refering to entities, this is often interpreted literally as REST API URLs refer to persistent models. Your REST API is the business logic layer of your Web client / server application. Providing direct access to persistently stored data through your REST API is simply replicating XML-RPC and not only is it bad design in the RESTful world but also extremely insecure.
RESTful APIs should serve back REST models. REST models are views of your data, that make sense as a response to the REST request. It’s important to understand this so you can define your REST models to be as strict as possible. Like all good business logic layers, a RESTful API should never accept a request it can’t comply with, this includes authority to perform the requested tasks on the data.
Consider a scenario where we are trying to model discographies, where a Band
has Albums
, has Tracks
.
Depending on the implementation of this applicaiton it might be easier to send down Tracks
when a client requests Albums
, but might only want to send down Albums
(without Tracks
) when a list of Bands
is requested.
Note
Read our section of Design Notes, to learn more about designing better REST APIs.
General convention for Prestans apps is to keep all your REST models in a single package. To start creating models, simply define a class that extends from prestans.types.Model
... amogst other things
import prestans.types
class Track(prestans.types.Model):
... next read about attributes here
5.1.1. Defining Attributes¶
All atributes of a Model
must be an instance of a prestans.type
, Attributes can also be relationships to instances or collections of Models.
Attributes are defined at a class level, these are the rules used by Prestans for each instance attributes of your Model
. By default Prestans is absolutely unforgiving and will ensure that each attribute satifies all it’s conditions. Failure results in aborting the creation of an instance.
At the class level define attributes by instantiating Prestans types with your rules, ensure they are as strict as possible, the more your define here the less you have to do in your handler. The objective is not to pass through data that your handler can’t work with.
class Track(prestans.types.Model):
id = prestans.types.Integer(required=False)
name = prestans.types.String(required=True, min_length=1)
duration = prestans.types.Float(required=True)
Our Type Configuration Reference guide documents in detail configuration validation options provided by each Prestans DataType
.
Note
Prestans Models do not provide back references when defining relationships between Models (like many ORM layers), defining cross references in Models can cause an infinite recursion. REST models are views on your persistent data, in most cases cross references might mean re-thinking your API design. You can also use DataAdapters to prevent an infinite recursion.
5.1.2. To One Relationship¶
One to One relationships are defined assigning an instance of an existing Model
to an attribute of another.
Validation rules accepted as instantiation values are for the attribute of the container Model
, they are evaluated the same way as basic Prestans DataTypes
.
class Band(prestans.types.Model):
... other attributes ...
created_by = UserProfile(required=True)
On success the attribute will refer to an instance of the child Model
. Failure to validate attributes of the children result in the failure of the parent Model
.
5.1.3. To Many Relationship (using Arrays)¶
Prestans provides prestans.types.Array
to provide lists of objects. Because REST end points refer to Entities, Collections in REST responses or requests must have elements of the same data type.
You must provide an instance Prestans DataType (e.g Array of Strings for tagging) or defined Model as the element_template
property of an Array
. Each instance in the Array
must comply with the rules defined by the template. Failure to validate any instance in the Array
, results as a failure to validate the entire Array
.
class Album(prestans.types.Model):
... other attributes ...
tracks = prestans.types.Array(element_template=Track(), min_length=1)
Arrays of Models are validated using the rules defined by each attribute. If you are creating an Array of a basic Prestans type, the validation rules are defined in the instance provided as the element_template
:
class Album(prestans.types.Model):
... other attributes ...
tags = prestans.types.Array(element_template=prestans.types.String(min_length=1, max_length=20))
5.1.4. Self References¶
Self references in Prestans Model definition are the same as self referencing Python objects.
... amogst other things
import prestans.types
# Define the Model first
class Genre(prestans.types.Model):
id = prestans.types.Integer(required=False)
name = prestans.types.String(required=True, min_length=1)
year_started = prestans.types.Float(required=True)
... and other attributes
# Once defined above you can self refer
Genre.parent = Genre(required=False)
Use arrays to make a list:
Genre.sub_genres = prestans.types.Array(element_template=Genre())
5.2. Special Types¶
Apart the usual suspects (String
, Integer
, Float
, Boolean
) Prestans also provides a few complex DataTypes
. These are wrappers on data types that have extensive libraries both on browsers and the Python runtime, but are serialized as strings or numbers.
5.2.1. DateTime¶
DateTime wraps around python datetime
, serialization formats like JSON serialize dates as strings, there are various standard formats for serializing dates as Strings, by default Prestans DateTime
uses RFC 822 expressed as %Y-%m-%d %H:%M:%S
format string in Python. This is because Google Closure’s Date API conveniently provides goog.date.fromIsoString
to parse these Strings.
To use another format string, override the format
parameter when defining DateTime
attributes.
class Album(prestans.types.Model):
last_updated = prestans.types.DateTime(default=prestans.types.DATETIME.CONSTANT.NOW)
Assigning python datetime
instances as the default value for Prestans DateTime
attributes works on the server, our problem lies in auto-generating client side stub code. The use of the constant prestans.types.CONSTANT.DATETIME_NOW
instruct Prestans to handle this properly.
5.2.2. DataURLFile¶
HTML5’s FileReader API is well supported by all modern browsers. Traditionally Web applications used multi part mime messages to upload files in a POST request. The FileReader
API allows JavaScript to get access to local files and makes for a much nicer solution for file uploads via a REST API.
The FileReader API provides FileReader.readAsDataURL
which reads the file using as Data URL Scheme, which essentially is a Base64 encoded file with meta information.
<!-- Use of data URL to embed an image -->
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA
AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO
9TXL0Y4OHwAAAABJRU5ErkJggg==" alt="Red dot"/>
<!-- Courtesy Wikipedia -->
prestans.types.DataURLFile
decodes the file Data URL Scheme encoded file and give access to the content and meta information. If you are using a traditional Web server like Apache, DataURLFile
provides a save
method to write the uploaded contents out, if you are on a Cloud infrastructure e.g Google AppEngine, you can use the file_contents
property to get the decoded file.
DataURLFile can restrict uploads based on mime types.
class Album(prestans.types.Model):
... other attributes
album_art = prestans.types.DataURLFile(allowed_mime_types=['image/jpeg', 'image/png', 'image/gif'])
5.3. Using Models to write Responses¶
REST APIs should validate any data being sent back down to clients. Your application’s persistent layer can’t always guarantee that stored data meets your business logic rules.
Models are a great way of constructing sound responses. They are also serializable by prestans. Your handlers can simply pass a collection (using Arrays) or instance of a Model and Prestans will serialize the results.
class AlbumEntityHandler(prestans.handlers.RESTRequestHandler):
def get(self, band_id, album_id):
... environment specific code to get an Album for the Band
album = pdemo.rest.models.Album()
album.name = persistent_album_object.name
... and so on until you copy all the values across
self.response.http_status = prestans.http.STATUS.OK
self.response.body = album
From the above example it’s clear that code to convert persistent objects into REST models becomes repetitive, and as a result error prone. Prestans provides DataAdapters
, that automate the conversion of persistent models to REST models. Read about it in the Data Adapters chapter.
If you use Google’s Closure Library for client side development, we provide a complete client side implementation of our types library to create and parse, requests and responses. Details available in the Google Closure Library Extensions section.
5.4. Type Configuration Reference¶
Basic Prestans types extend from prestans.types.DataType
, these are the building blocks of all data represented in systems, e.g Strings, Numbers, Booleans, Date and Times.
Collections contain a series of attributes of both Basic and Collection types.
5.4.1. String¶
Strings are wrappers on Pythonic strings, the rules allow pattern matching and validation.
Note
Extends prestans.types.DataType
required
flags if this is a mandatory field, acceptsTrue
orFalse
and is set toTrue
by defaultdefault
specifies the value to be assigned to the attribute if one isn’t provided on instantiation, this must be a String.min_length
the minimum acceptable length of the String, if using thedefault
parameter ensure it respects the length.max_length
the maximum acceptable length of the String, if using thedefault
parameter ensure it respects the length.format
a regular expression for custom validation of the String.choices
a list of Strings that are acceptable values for the attribute.utf_encoding
set toutf-8
by default is the confiurable UTF encoding setting for the String.
5.4.2. Integer¶
Integers are wrappers on Python numbers, limited to Integers. We distinguish between Integers and Floats because of formatting requirements.
Note
Extends prestans.types.DataType
required
flags if this is a mandatory field, acceptsTrue
orFalse
and is set toTrue
by defaultdefault
specifies the value to be assigned to the attribute if one isn’t provided on instantiation, this must be a Integer.minimum
the minimum acceptable value for the Integer, if using default ensure it’s greater or equal to than the minimum.maximum
the maximum acceptable value for the Integer, if using default ensure it’s less or equal to than the maximum.choices
a list of choices that the Integer value can be set to, if using default ensure the value is set to of the choices.
5.4.3. Float¶
Floats are wrappers on Python numbers, expanded to Floats.
Note
Extends prestans.types.DataType
required
flags if this is a mandatory field, acceptsTrue
orFalse
and is set toTrue
by defaultdefault
specifies the value to be assigned to the attribute if one isn’t provided on instantiation, this must be a Float.minimum
the minimum acceptable value for the Float, if using default ensure it’s greater or equal to than the minimum.maximum
the maximum acceptable value for the Float, if using default ensure it’s less or equal to than the maximum.choices
a list of choices that the Float value can be set to, if using default ensure the value is set to of the choices.
5.4.4. Boolean¶
Booleans are wrappers on Python bools
.
Note
Extends prestans.types.DataType
required
flags if this is a mandatory field, acceptsTrue
orFalse
and is set toTrue
by defaultdefault
specifies the value to be assigned to the attribute if one isn’t provided on instantiation, this must be a Boolean.
5.4.5. DataURLFile¶
Supports uploading files using the HTML5 FileReader API.
Note
Extends prestans.types.DataType
required
flags if this is a mandatory field, acceptsTrue
orFalse
and is set toTrue
by defaultallowed_mime_types
5.4.6. Date¶
Date Time is a complex structure that parses strings to Python datetime
and vice versa. Default string format is %Y-%m-%d
to assist with parsing on the client side using Google Closure Library provided DateTime.
Note
Extends prestans.types.DataStructure
required
flags if this is a mandatory field, acceptsTrue
orFalse
and is set toTrue
by defaultdefault
specifies the value to be assigned to the attribute if one isn’t provided on instantiation, this must be a date. Prestans provides a constansprestans.types.Date.CONSTANT.TODAY
if you want to use the date / time of execusion.format
default format%Y-%m-%d
5.4.7. Time¶
Date Time is a complex structure that parses strings to Python datetime
and vice versa. Default string format is %H:%M:%S
to assist with parsing on the client side using Google Closure Library provided DateTime.
Note
Extends prestans.types.DataStructure
required
flags if this is a mandatory field, acceptsTrue
orFalse
and is set toTrue
by defaultdefault
specifies the value to be assigned to the attribute if one isn’t provided on instantiation, this must be a date. Prestans provides a constansprestans.types.Time.CONSTANT.NOW
if you want to use the date / time of execusion.format
default format%H:%M:%S
5.4.8. DateTime¶
Date Time is a complex structure that parses strings to Python datetime
and vice versa. Default string format is %Y-%m-%d %H:%M:%S
to assist with parsing on the client side using Google Closure Library provided DateTime.
Note
Extends prestans.types.DataStructure
required
flags if this is a mandatory field, acceptsTrue
orFalse
and is set toTrue
by defaultdefault
specifies the value to be assigned to the attribute if one isn’t provided on instantiation, this must be a date. Prestans provides a constansprestans.types.DateTime.CONSTANT.NOW
if you want to use the date / time of execusion.format
default format%Y-%m-%d %H:%M:%S
5.5. Collections¶
Collections are formalised representations to complex itterable data structures. Prestans provides two Collections, Arrays and Models (dictionaries).
5.5.1. Array¶
Arrays are collections of any Prestans type. To ensure the integrity of RESTful responses, Array
elements must always be of the same kind, this is defined by specifying an element_template
. Prestans Arrays are itterable.
Note
Extends prestans.types.DataCollection
required
flags if this is a mandatory field, acceptsTrue
orFalse
and is set toTrue
by defaultdefault
a default object of typeprestans.types.Array
to be used if a value is not providedelement_template
a instance of aprestans.types
subclass that’s use to validate each element. Prestans does not allow arrays of mixed types because it does not form valid URL responses.min_length
minimum length of an array, if using default it must conform to this constraintmax_length
maximum length of an array,
5.5.2. Model¶
Models are wrapper on dictionaries, it provides a list of key, value pairs formalised as a Python class
made up of any number of Prestans DataType
attributes. Models can have instances of other models or Arrays of Basic or Complex Prestans types.
Note
Extends prestans.types.DataCollection
required
flags if this is a mandatory field, acceptsTrue
orFalse
and is set toTrue
by defaultdefault
a default model instance, this is useful when defining relationships
The following is a parallel argument:
**kwargs
a set of key value arguments, each one of these must be an acceptable value for instance variables, all defined validation rules apply.