Resources Instances¶
A resource is a collection of fields and associated meta data that is used to validate data within the fields.
- The basics:
- Each resource is a Python class that subclasses
odin.Resource
. - Each field attribute of the resource is defined by a
odin.Field
subclass.
- Each resource is a Python class that subclasses
Quick example¶
This example resource defines a Book
, which has a title
, genre
and num_pages
:
import odin
class Book(odin.Resource):
title = odin.StringField()
genre = odin.StringField()
num_pages = odin.IntegerField()
title
, genre
and num_pages
are fields. Each field is specified as a class attribute.
The above Book
resource would create a JSON object like this:
{
"$": "resources.Book",
"title": "Consider Phlebas",
"genre": "Space Opera",
"num_pages": 471
}
- Some technical notes:
- The
$
field is a special field that defines the type ofResource
. - The name of the resource,
resources.Book
, is automatically derived from some resource metadata but can be overridden.
- The
Fields¶
The most important part of a resource – and the only required part of a resource – is the list of fields it defines.
Fields are specified by class attributes. Be careful not to choose field names that conflict with the resources API like
clean
.
Example:
class Author(odin.Resource):
name = odin.StringField()
class Book(odin.Resource):
title = odin.StringField()
authors = odin.ArrayOf(Author)
genre = odin.StringField()
num_pages = odin.IntegerField()
Field types¶
Each field in your resource should be an instance of the appropriate Field class. ODIN uses the field class types to determine a few things:
- The data type (e.g.
Integer
,String
). - Validation requirements.
Field options¶
Each field takes a certain set of field-specific arguments (documented in the resource field reference).
There’s also a set of common arguments available to all field types. All are optional. They’re fully explained in the reference, but here’s a quick summary of the most often-used ones:
null
- If
True
, the field is allowed to be null. Default isFalse
. default
- The default value for the field. This can be a value or a callable object. If callable it will be called every time a new object is created.
choices
An iterable (e.g., a list or tuple) of 2-tuples to use as choices for this field.
A choices list looks like this:
GENRE_CHOICES = ( ('sci-fi', 'Science Fiction'), ('fantasy', 'Fantasy'), ('others', 'Others'), )
The first element in each tuple is the value that will be stored field or serialised document, the second element is a display value.
Again, these are just short descriptions of the most common field options.
Verbose field names¶
Each field type, except for DictAs
and ArrayOf
, takes an optional first positional argument – a verbose name.
If the verbose name isn’t given, Odin will automatically create it using the field’s attribute name, converting
underscores to spaces.
In this example, the verbose name is “person’s first name”:
first_name = odin.StringField("person's first name")
In this example, the verbose name is “first name”:
first_name = odin.StringField()
DictAs
and ArrayOf
require the first argument to be a resource class, so use the verbose_name
keyword
argument:
publisher = odin.DictAs(Publisher, verbose_name="the publisher")
authors = odin.ArrayOf(Author, verbose_name="list of authors")
Resource level validation¶
Field validation can be customised at the resource level. This is useful as it allows validation of data using a value from another field for example ensuring a maximum value is greater than a minimum value, or checking that a password and a check password matches.
This is achieved using by defining a method called clean_FIELDNAME
which accepts a single value argument, Odin will
then use this method during the cleaning process to validate the field. Odin will then use the value that is returned
from the clean method, allowing you to apply any customised formatting. If an issue is found with a value then raise a
odin.exceptions.ValidationError
and the error returned will be applied to validation results.
Example:
class Timing(odin.Resource):
minimum_delay = odin.IntegerField(min_value=0)
maximum_delay = odin.IntegerField()
def clean_maximum_delay(self, value):
if value < self.minimum_delay:
raise ValidationError('Maximum delay must be greater than the minimum delay value')
return value
Important
Ensure that a return value is provided, if no return value is specified the Python default is
None
and this is the value that Odin will use.
Relationships¶
To really model more complex documents objects and lists need to be able to be combined, Odin offers ways to define
these structures, DictAs
and ArrayOf
fields handle these structures.
DictAs relationships¶
To define a object-as relationship, use odin.DictAs
. You use it just like any other Field type by including
it as a class attribute of your resource.
DictAs
requires a positional argument: the class to which the resource is related.
For example, if a Book
resource has a Publisher
– that is, a single Publisher
publishes a book:
class Publisher(odin.Resource):
# ...
class Book(odin.Resource):
publisher = odin.DictAs(Publisher)
# ...
This would produce a JSON document of:
{
"$": "resources.Book",
"title": "Consider Phlebas",
"publisher": {
"$": "resources.Publisher",
"name": "Macmillan"
}
}
ArrayOf relationships¶
To define a array-of relationship, use odin.ArrayOf
. You use it just like any other Field type by including it as a
class attribute of your resource.
ArrayOf
requires a positional argument: the class to which the resource is related.
For example, if a Book
resource has a several Authors
– that is, a multiple authors can publish a book:
class Author(odin.Resource):
# ...
class Book(odin.Resource):
authors = odin.ArrayOf(Author)
# ...
This would produce a JSON document of:
{
"$": "resources.Book",
"title": "Consider Phlebas",
"authors": [
{
"$": "resources.Author",
"name": "Iain M. Banks"
}
]
}
Meta options¶
Give your resource metadata by using an inner class Meta
, like so:
class Book(odin.Resource):
title = odin.StringField()
class Meta:
name_space = "library"
verbose_name_plural = "Books"
Resource metadata is “anything that’s not a field”, module_name and human-readable plural names (verbose_name and verbose_name_plural). None are required, and adding class Meta to a resource is completely optional.
name
- Override the name of a resource. This is the name used to represent the resource in a JSON document. The default name is the name of the class used to define the resource.
name_space
- The name space is an optional string value that is used to group a set of common resources. Typically a namespace should be in the form of dot-atoms eg: university.library or org.poweredbypenguins. The default is no namespace.
verbose_name
- A long version of the name for used when displaying a resource or in generated documentation. The default
verbose_name is a name attribute that has been converted to lower case and spaces put before each upper case
character eg:
LibraryBook
-> “library book“ verbose_name_plural
- A pluralised version of the verbose_name. The default is to use the verbose name and append an ‘s’ character. In the case of many words this does not work correctly so this attribute allows for the default behaviour to be overridden.
abstract
- Marks the current resource as an abstract resource. See the section Abstract base classes for more detail of
the abstract attribute. The default value for abstract is
False
. doc_group
- A grouping for documentation purposes. This is purely optional but is useful for grouping common elements together.
The default value for doc_group is
None
.
Resource inheritance¶
Resource inheritance in Odin works almost identically to the way normal class inheritance works in Python. The only decision you have to make is whether you want the parent resources to be resources in their own right, or if the parents are just holders of common information that will only be visible through the child resources.
Abstract base classes¶
Abstract base classes are useful when you want to put some common information into a number of other resources. You write your base class and put abstract=True in the Meta class. This resource will then not be able to created from a JSON document. Instead, when it is used as a base class for other resources, its fields will be added to those of the child class.
An example:
class CommonBook(odin.Resources):
title = odin.StringField()
class Meta:
abstract = True
class PictureBook(CommonBook):
photographer = odin.StringField()
The PictureBook resource will have two fields: title and photographer. The CommonBook resource cannot be used as a normal resource, since it is an abstract base class.
todo: | Add details of how to support multiple object types in a list using Abstract resources |
---|