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.

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 of Resource.

  • The name of the resource, resources.Book, is automatically derived from some resource metadata but can be overridden.

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 is False.

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"
        }
    ]
}

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