Quickstart¶
This guide will walk you through the basics of schema definition, data serialization, deserialization and validation.
Declaring Types¶
Let’s start with a your application-level model:
class Person(object):
def __init__(self, name, birthdate):
self.name = name
self.birthdate = birthdate
def __repr__(self):
return "<Person name={name} birthdate={birthdate}>".format(
name=repr(self.name), birthdate=repr(self.birthdate),
)
You want to create a JSON API to load and dump it. First you need to define a type for that data:
from lollipop.types import Object, String, Date
PersonType = Object({
'name': String(),
'birthdate': Date(),
})
Serializing data¶
To serialize your data, pass it to your type’s dump()
method:
from datetime import date
import json
john = Person(name='John', birthdate=date(1970, 02, 29))
john_data = PersonType.dump(john)
print json.dump(john_data, indent=2)
# {
# "name": "John",
# "birthdate": "1970-02-29"
# }
Deserializing data¶
To load data back, pass it to your type’s load()
method:
user_data = {
"name": "Bill",
"birthdate": "1994-08-12",
}
user = PersonType.load(user_data)
print user
# {"name": "Bill", "birthdate": date(1994, 08, 12)}
If you want to restore original data type, you can pass it’s constructor function when you define your type:
PersonType = Object({
'name': String(),
'birthdate': Date(),
}, constructor=Person)
print PersonType.load({
"name": "Bill",
"birthdate": "1994-08-12",
})
# <Person name="Bill" birthdate=date(1994, 08, 12)>
To deserialize a list of objects, you can create a List
instance with your object type as element type:
List(PersonType).load([
{"name": "Bob", "birthdate": "1980-12-12"},
{"name": "Jane", birthdate": "1991-08-04"},
])
# => [<Person name="Bob" birthdate=date(1980, 12, 12)>,
<Person name="Jane" birthdate=date(1991, 08, 04)>]
Validation¶
By default all fields are required to have values, so if you accidentally forget
to specify one, you will get a ValidationError
exception:
from lollipop.errors import ValidationError
try:
PersonType.load({"name": "Bob"})
except ValidationError as ve:
print ve.messages # => {"birthdate": "Value is required"}
The same applies to field types: if you specify value of incorrect type, you will get validation error.
If you want more control on your data, you can specify additional validators:
from lollipop.validators import Regexp
email_validator = Regexp(
'(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)',
error='Invalid email',
)
UserType = Object({
'email': String(validate=email_validator),
})
try:
UserType.load({"email": "wasa"})
except ValidationError as ve:
print ve.messages # => {"email": "Invalid email"}
If you just need to validate date and not interested in result, you can use
validate()
method:
print UserType.validate({"email": "wasa"})
# => {"email": "Invalid email"}
print UserType.validate({"email": "wasa@example.com"})
# => None
You can define your own validators:
def validate_person(person):
errors = ValidationErrorBuilder()
if person.name == 'Bob':
errors.add_error('name', 'Should not be called Bob')
if person.age < 18:
errors.add_error('age', 'Should be at least 18 years old')
errors.raise_errors()
PersonType = Object({
'name': String(),
'birthdate': Date(),
}, validate=validate_person)
PersonType.validate({'name': 'Bob', 'age': 15})
# => {'name': 'Should not be called Bob',
# 'age': 'Should be at least 18 years old'}
or use Predicate
validator and supply a True/False
function to it.
Validating cross-field dependencies is easy:
def validate_person(person):
if person.name == 'Bob' and person.age < 18:
raise ValidationError('All Bobs should be at least 18 years old')
Changing The Way Accessing Object Data¶
When you define an Object
type, by default it will retrieve
object data by accessing object’s attributes with the same name as name of the field
you define. Most often it is what you want. However sometimes you might want to
obtain data differently. To do that, you define object’s fields not with
Type
instances, but with Field
instances.
To access attribute with a different name, use AttributeField
:
MyObject = namedtuple('MyObject', ['other_field'])
MyObjectType = Object({
'field1': AttributeField(String(), attribute='other_field'),
})
To get data from a method instead of an attribute, use
MethodField
:
class Person:
def __init__(self, first_name, last_name):
self.first_name = first_name
self.last_name = last_name
def get_name(self):
return self.first_name + ' ' + self.last_name
PersonType = Object({
'name': MethodField(String(), method='get_name'),
})