Annotated Resources

A resource is a collection of fields and associated meta data that is used to validate data within the fields.

The basics:
  • Each annotated resource is a Python class that subclasses odin.AnnotatedResource.

  • Each field attribute of the resource is defined type attributes.

  • Additional field options can be provided using odin.Options.

Quick example

This example resource defines a Book, which has a title, genre and num_pages:

import odin

class Book(odin.AnnotatedResource):
    title: str
    genre: str
    num_pages: int
    rrp: float = 24.5

title, genre, num_pages and rrp are fields. Each field is specified as a class attribute, the rrp field has a default value of 24.5.

The above Book resource would create a JSON object like this:

{
    "$": "resources.Book",
    "title": "Consider Phlebas",
    "genre": "Space Opera",
    "num_pages": 471,
    "rrp": 24.5
}
Some technical notes:
  • The $ field is a special field that defines the type of AnnotatedResource.

  • 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 attributes. Be careful not to choose field names that conflict with the resources API like clean.

Example:

class Author(odin.AnnotatedResource):
    name: str

class Book(odin.AnnotatedResource):
    title: str
    authors: List[Author]
    genre: str
    num_pages: int
    rrp: float = 24.5

Field types

Each field in your annotated resource must include a type annotation that odin uses to determine the appropriate field type.

Supported Annotations

All basic types of field are supported by Odin, str, bool, int, float, dict, list, datetime.datetime, datatime.date, datetime.time, uuid.UUID and pathlib.Path. These all map to the appropriate odin field along with any options.

Odin also includes a number of builtin type aliases for commonly used field types via the odin.types alias odin.types.Email, odin.types.IPv4, odin.types.IPv6, odin.types.IPv46 and odin.types.Url.

Enum types are supported by simply specifying an enum type.

Composite fields are supported using generic type hints, the List/Sequence and Mapping/Dict type specifiers will map to the appropriate fields based on the arguments provided.

Finally use of the Optional typing alias is used to set the null field option.

Field options

Options are provided to the appropriate field using the odin.Options class. This will be identified by Odin with the options passed to the resolved field type.

An example:

class Book(odin.AnnotatedResource):
    title: str = odin.Options(min_length=1)

The first value of the Options class is the default value, the Options object cna be left out if only a default value needs to be provided.

To defined a completely custom field type use the field_type option, to pass a field instance that will be used for the field.

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.AnnotatedResource, abstract=True):
    title: str


class PictureBook(CommonBook):
    photographer: str

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.