Resource ModellingΒΆ

Audrey provides some base resource classes for a developer to subclass to model the objects specific to their application. The main classes are:

1. audrey.resources.object.Object - This is the fundamental building block of an Audrey application. Objects have methods to save/load/remove themselves in MongoDB and index/reindex/unindex themselves in ElasticSearch.

2. audrey.resources.collection.Collection - Collections are sets of Objects. They correspond to MongoDB collections and have various methods to access and manipulate their child objects.

3. audrey.resources.root.Root - Root is the container of Collections and represents the root of your app. It also provides various “global” services (such as search, cross-collection references and file uploads/downloads).

As the application developer, you define your Object classes (using colander to define the schema for each class), your Collection classes (specifying which Object classes can be created in each Collection), and a Root class (specifying the list of Collections). Audrey then provides what I hope is a comfortable and Pythonic interface that handles the boring, repetitve yet error-prone details of interacting with MongoDB and ElasticSearch, validating your schemas, etc.

Note

The base Object and Collection classes don’t allow explicit control of the __name__ attribute used for traversal. For cases where you need such control, use the audrey.resources.object.NamedObject and audrey.resources.collection.NamingCollection classes instead.

After you create a new project using the audrey scaffold (as described in Creating a new project), you’ll have a couple of example Objects and Collections defined in the file resources.py inside your package directory (which will be the same name as your project name, but in lowercase). You’ll of course want to replace these examples with your own Objects and Collections, and may even want to split the single file up into a resources sub-package.

Let’s take a close look at the example resources.py file and see how it subclasses the base Audrey classes. The file should have content similar to the following:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import audrey
import colander

# The following are just some example resource classes to get
# you started using Audrey.

class Person(audrey.resources.object.Object):
    _object_type = "person"

    _schema = colander.SchemaNode(colander.Mapping())
    _schema.add(colander.SchemaNode(colander.String(), name='firstname'))
    _schema.add(colander.SchemaNode(colander.String(), name='lastname'))
    _schema.add(colander.SchemaNode(audrey.types.File(), name='photo',
                default=None, missing=None))

    def get_title(self):
        parts = []
        for att in ('firstname', 'lastname'):
            val = getattr(self, att, '')
            if val:
                parts.append(val)
        if parts:
            return " ".join(parts)
        else:
            return "Untitled"

class People(audrey.resources.collection.Collection):
    _collection_name = 'people'
    _object_classes = (Person,)

# A deferred schema binding.  Used to populate the missing attribute
# of Post.dateline at runtime.
@colander.deferred
def deferred_datetime_now(node, kw):
    return audrey.dateutil.utcnow(zero_seconds=True)

class Post(audrey.resources.object.Object):
    _object_type = "post"

    _schema = colander.SchemaNode(colander.Mapping())
    _schema.add(colander.SchemaNode(colander.String(), name='title'))
    _schema.add(colander.SchemaNode(colander.DateTime(), name='dateline',
                missing=deferred_datetime_now))
    _schema.add(colander.SchemaNode(colander.String(), name='body',
                is_html=True))
    _schema.add(colander.SchemaNode(
                audrey.types.Reference(collection='people'),
                name='author', default=None, missing=None))

    @classmethod
    def get_class_schema(cls, request=None):
        return cls._schema.bind()

    def get_title(self):
        return getattr(self, 'title', None) or 'Untitled'

class Posts(audrey.resources.collection.Collection):
    _collection_name = 'posts'
    _object_classes = (Post,)

class Root(audrey.resources.root.Root):
    _collection_classes = (People, Posts, )

def root_factory(request): # pragma: no cover
    return Root(request)

Starting at line 7, a Person class is defined that subclasses audrey.resources.object.Object.

At line 8, the class attribute _object_type is overridden. The value of this attribute should be a string that uniquely identifies the Object type (within the context of your project). It’s used in many places as a key to lookup a given Object class. There are no restrictions on the characters it may contain, so feel free to make it human-friendly (using spaces instead of underscores to separate words, for example).

In lines 10-14, the class attribute _schema is overridden. The value of this attribute should be a colander schema representing the user-editable attributes for the Object type (the sort of attributes that might be shown as fields in an edit form). This is standard colander stuff, and you can use all the colander types (including Mapping and Sequence). Additionally Audrey defines a couple of its own colander types:

1. audrey.types.File - This type represents an uploaded file which will be stored in the MongoDB GridFS. As an example, see line 15 where a File attribute with the name photo is defined for the person type.

2. audrey.types.Reference - This type represents a reference to another Object (possibly in another collection). As an example, see lines 46-48 where a Reference attribute with the name author is defined to allow a reference from the post type to the person type.

In lines 16-25, the method get_title() is overridden. This method should return a string suitable for use as a human-friendly title of an Object instance (as might be shown as the text in a link to the object). If you don’t override this method, it will return the object’s __name__ by default. The implementation of Person.get_title() is a little long since it tries to be flexible and handle cases where the “firstname” and “lastname” attributes may be missing. The implementation of Post.get_title() at line 45 is a one-liner suitable for types that have a single attribute that’s a natural fit for a title.

For a lot of object types, that’s all you’ll need to override. It should go without saying that since these are just Python classes, you’re free to override other methods and add your own to suit your specific needs.

Moving on, lines 27-29 define a People class that subclasses audrey.resources.collection.Collection. This is pretty short and sweet.

Line 28 overrides the _collection_name class attribute. The value of this attribute is a string that uniquely identifies the Collection within the content of your project. It’s used as a key/name to traverse from the root of the app to a singleton instance of the Collection.

Line 29 overrides the _object_classes class attribute. The value of this attribute is a sequence of Object classes representing the types of Objects that may exist in the Collection. In this case, the People Collection is homogenous and only contains Person Objects. You can, however, define Collections that may contain multiple Object types (presumably with some common sub-schema). When creating Object types that will be in a non-homogenous Collection, be sure to set the audrey.resources.object.Object._save_object_type_to_mongo class attribute to True; otherwise the Collection will raise an exception while deserializing from MongoDB since it won’t be able to determine the correct Object class to construct.

Lines 31-61 define another Object type and another homogenous Collection. The Post class demonstrates overriding the audrey.resources.object.Object.get_class_schema() class method to do deferred schema binding at runtime.

Lines 63-64 define a Root class that subclasses audrey.resources.root.Root and overrides the _collection_classes class attribute. The value of this attribute is a sequence of Collection classes representing all the Collections in use in the app.

Lines 66-67 define a root_factory() function which returns an instance of Root for a request. This function is used by Audrey to configure the Pyramid application to find the traversal root.

If you haven’t read the Introduction section yet, you may want to now. It demonstrates some of the functionality Audrey provides using the Person and People classes defined here as examples.

You may also want to explore the API documentation to discover further functionality and details.