Audrey provides some base resource classes for a developer to subclass to model the objects specific to their application. The main classes are:
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.
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.
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.
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.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
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
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
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:
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
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
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
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
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
People classes defined here as examples.
You may also want to explore the API documentation to discover further functionality and details.