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

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