To install, run:
pip install teleport
Teleport’s primary object is the serializer. A serializer corresponds to a certain data type and lets the user convert values of this type between their two representations: the JSON form and the native form. In order to do this, a serializer must provide a to_json() and a from_json() method.
The JSON form represents a valid JSON string, however, for convenience, our implementation uses an intermediate format, namely the format expected by json.dumps() from the Python standard library. It is limited to dictionaries, lists, numbers, booleans, strings and None.
Internally, your application will use the native form. It can be as rich as you want, however, for the most basic types, it happens to be the same as the JSON form. Even when there is nothing to convert, the serializer is useful as a way of validating input.
Here is a simple serializer:
>>> from teleport import *
>>> Integer.from_json(1)
1
Integer is a basic type, it is represented by the Integer class, which does not need to be instantiated because the type takes no parameters. Array is a parametrized type, it is represented by the Array class, which is instantiated with a parameter:
>>> Array(Integer).from_json([1, 2, 3, 4, 5])
[1, 2, 3, 4, 5]
>>> Array(Integer).from_json([1, 2, 3, 4, 5.1])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "teleport.py", line 349, in from_json
ret.append(self.param.from_json(item))
File "teleport.py", line 208, in from_json
raise ValidationError("Invalid Integer", datum)
teleport.ValidationError: Item at [4] Invalid Integer: 5.1
Array(Integer) is an interesting object. It may be useful to send it over the wire, and with Teleport it is actually very easy to do. Teleport provides a special serializer Schema for serializing other serializers:
>>> Schema.to_json(Array(Integer))
{'type': 'Array', 'param': {'type': 'Integer'}}
The client that receives this JSON object can then deserialize it:
>>> Schema.from_json({'type': 'Array', 'param': {'type': 'Integer'}})
<teleport.Array object at 0xb7189d6c>
Pretty cool, huh? But actually, Schema isn’t all that special. You can use it just like any other serializer. For instance, here is an array of schemas:
>>> Array(Schema).to_json([Integer, Boolean, Float])
[{'type': 'Integer'}, {'type': 'Boolean'}, {'type': 'Float'}]
Another parametrized type is the Struct. It is used for dicts with non-arbitrary keys:
>>> from teleport import Struct, required, optional
>>> s = Struct([
... required("name", String),
... optional("scores", Array(Integer))
... ])
>>> s.from_json({"name": "Bob"})
{"name": u"Bob"}
>>> s.from_json({"name": "Bob", "scores": [1, 2, 3.1]})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "teleport.py", line 406, in from_json
ret[field] = schema.from_json(datum[field])
File "teleport.py", line 349, in from_json
ret.append(self.param.from_json(item))
File "teleport.py", line 208, in from_json
raise ValidationError("Invalid Integer", datum)
teleport.ValidationError: Item at ['scores'][2] Invalid Integer: 3.1
To create a custom type, define a serializer class:
class YesNoMaybe(object):
@staticmethod
def from_json(datum):
if datum not in [True, False, None]:
raise ValidationError("Invalid YesNoMaybe", datum)
return datum
@staticmethod
def to_json(datum):
return datum
YesNoMaybe is a primitive serializer as it defines functions to convert data directly to and from JSON. Another option is a wrapper serializer, which relies on an internal serializer for dealing with JSON and builds on top of it by defining assemble() and disassemble() methods.
The assemble() method may be used to perform additional validation that the internal serializer doesn’t take care of:
class Suit(BasicWrapper):
schema = String
@staticmethod
def assemble(datum):
if datum not in ["hearts", "spades", "clubs", "diamonds"]:
raise ValidationError("Invalid Suit", datum)
return datum
Note that the BasicWrapper mixin defines the to_json() and from_json() functions for you:
>>> Suit.from_json("hearts")
"hearts"
>>> Suit.from_json("heart")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "teleport.py", line 406, in from_json
ret[field] = schema.from_json(datum[field])
File "teleport.py", line 349, in from_json
ret.append(self.param.from_json(item))
teleport.ValidationError: Invalid Suit: "heart"
When the native form of the data type is a class instance, BasicWrapper can be used to teach the class to serialize itself:
class Player(BasicWrapper):
schema = Struct([
required("name", String),
# Note how struct fields can accept an optional doc parameter
required("level", Integer, "0-100")
])
@staticmethod
def assemble(datum):
return Player(**datum)
@staticmethod
def disassemble(player):
return {
"name": player.name,
"level": player.level
}
Both primitive and wrapper types can also be parametrized, which means that their serializers will have to be instantiated with parameters and that their JSON form will have an additional attribute param.
Take a look at the source code of Array for an example of a primitive parametrized type, and OrderedMap for an example of a wrapper parametrized type.
The types we created above will mostly work, however, if you expect to deserialize their schema from JSON, you will be faced with an error:
>>> Schema.from_json({"type": "Player"})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "teleport.py", line 178, in from_json
raise UnknownTypeValidationError("Unknown type", t)
teleport.UnknownTypeValidationError: Unknown type: 'Player'
The Schema class that you imported from the teleport module will never be aware of the custom types you’ve created. In order to extend Teleport, you need to recreate all the built-in types (including Schema) in a separate module. This sounds like a complicated task, but standard_types() makes it very easy.
Parameters: |
|
---|---|
Returns: | A dict, mapping class names to classes. |
Create an empty module in your Python application, say myapp.types.
from teleport import standard_types
def getter(name):
if name == "Suit":
return Suit
raise KeyError()
class Suit(BasicWrapper):
schema = String
@staticmethod
def assemble(datum):
if datum not in ["hearts", "spades", "clubs", "diamonds"]:
raise ValidationError("Invalid Suit", datum)
return datum
globals().update(standard_types(getter))
standard_types() will inject your custom models into Teleport via the getter parameter. The return value is a dict of freshly recreated Teleport serializers. Now, instead of doing:
from teleport import *
you do:
from myapp.types import *
Note that by default, Teleport will use the class name as the name of the serializer. If your new type’s class name is different from its actual type name, let Teleport know by setting the type_name property on the class.
If datum is an integer, return it; if it is a float with a 0 for its fractional part, return the integer part as an int. Otherwise, raise a ValidationError.
If datum is a float, return it; if it is an integer, cast it to a float and return it. Otherwise, raise a ValidationError.
If datum is a boolean, return it. Otherwise, raise a ValidationError.
If datum is of unicode type, return it. If it is a string, decode it as UTF-8 and return the result. Otherwise, raise a ValidationError. Unicode errors are dealt with strictly by raising UnicodeDecodeValidationError, a subclass of the above.
This type may be useful when you wish to send a binary file. The byte string will be base64-encoded for safety.
>>> b = open('pixel.png', 'rb').read()
>>> Binary.to_json(b)
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAACXBIWXMAAAsTAAALE
wEAmpwYAAAAB3RJTUUH3QoSBggTmj6VgAAAABl0RVh0Q29tbWVudABDcmVhdGVkIHd
pdGggR0lNUFeBDhcAAAAMSURBVAjXY/j//z8ABf4C/tzMWecAAAAASUVORK5CYII='
If datum is a base64-encoded string, decode and return it. If not a string, or encoding is wrong, raise ValidationError.
Encode datum using base64.
Bases: teleport.legacy.BasicWrapper
Wraps the String type.
>>> DateTime.to_json(datetime.now())
u'2013-10-18T01:58:24.904349'
Parse datum as an ISO 8601-encoded time and return a datetime object. If the string is invalid, raise a ValidationError.
Given a datetime object, return an ISO 8601-encoded string.
This type may be used as a kind of wildcard that will accept any JSON value and return it untouched. Presumably you still want to interpret the meaning of this arbitrary JSON data, you just don’t want to do it through Teleport.
Used as a wrapper around JSON data to disambiguate None as a JSON value (null) from None as an absence of value. Its datum attribute will hold the actual JSON value.
For example, an HTTP request body may be empty in which case your function may return None or it may be “null”, in which case the function can return a Box instance with None inside.
The argument param is a serializer that defines the type of each item in the array.
If datum is a list, construct a new list by putting each element of datum through a serializer provided as param. This serializer may raise a ValidationError. If datum is not a list, ValidationError will also be raised.
Serialize each item in the datum iterable using param. Return the resulting values in a list.
The argument param is a serializer that defines the type of each item in the map.
If datum is a dict, deserialize it, otherwise raise a ValidationError. The keys of the dict must be unicode, and the values will be deserialized using param.
The argument param is a serializer that defines the type of each item in the map.
Internal schema:
Struct([
required(u"map", Map(param)),
required(u"order", Array(String))
])
The order of the items in map is not preserved by JSON, hence the existence of order, an array of keys in map.
ValidationError is raised if order does not correspond to the keys in map. The native form is Python’s OrderedDict.
param must be an OrderedDict, where the keys are field names, and values are dicts with two items: schema (serializer) and required (Boolean). For each pair, schema is used to serialize and deserialize a dict value matched by the corresponding key.
For convenience, Struct can be instantiated with a list of tuples like the constructor of OrderedDict.
If datum is a dict, deserialize it against param and return the resulting dict. Otherwise raise a ValidationError.
A ValidationError will be raised if:
Expects a JSON object with a type attribute and an optional param attribute. Uses type to find the serializer. If the type is simple, returns the serializer, if parametrized, deserializes param and uses it to instatiate the serializer class before returning it.
After looking in the built-in types, this method will attempt to find the serializer via type_getter, an argument of standard_types(). See Extending Teleport. If no serializer is found, UnknownTypeValidationError will be raised.
If given a serializer representing a simple type, return a JSON object with a single attribute type, if a parametrized one, also include an attribute param.
By default type is the class name of the serializer, but it can be overridden by the serializer’s type_name property.
Raised during deserialization. Stores the location of the error in the JSON document relative to its root.
First argument is the error message, second optional argument is the object that failed validation.
Bases: teleport.legacy.ValidationError
Bases: teleport.legacy.ValidationError