Added to the docs, mostly the user guide

This commit is contained in:
Harry Marr 2009-12-22 03:42:35 +00:00
parent 78d8cc7df8
commit 69eaf4b3f6
7 changed files with 303 additions and 44 deletions

View File

@ -1,8 +1,18 @@
===========
MongoEngine MongoEngine
=========== ===========
MongoEngine is an ORM-like layer on top of PyMongo. :Info: MongoEngine is an ORM-like layer on top of PyMongo.
:Author: Harry Marr (http://github.com/hmarr)
Tutorial available at http://hmarr.com/mongoengine/ About
=====
MongoEngine is a Python Object-Document Mapper for working with MongoDB.
Documentation available at http://hmarr.com/mongoengine/ -- there is currently
a `tutorial <http://hmarr.com/mongoengine/tutorial.html>`_, a `user guide
<http://hmarr.com/mongoengine/userguide.html>`_ and an `API reference
<http://hmarr.com/mongoengine/apireference.html>`_.
**Warning:** this software is still in development and should *not* be used Dependencies
in production. ============
pymongo 1.1+
sphinx (optional -- for documentation generation)

View File

@ -1,13 +1,14 @@
=============
API Reference API Reference
============= =============
Connecting Connecting
---------- ==========
.. autofunction:: mongoengine.connect .. autofunction:: mongoengine.connect
Documents Documents
--------- =========
.. autoclass:: mongoengine.Document .. autoclass:: mongoengine.Document
:members: :members:
@ -21,13 +22,13 @@ Documents
:members: :members:
Querying Querying
-------- ========
.. autoclass:: mongoengine.queryset.QuerySet .. autoclass:: mongoengine.queryset.QuerySet
:members: :members:
Fields Fields
------ ======
.. autoclass:: mongoengine.StringField .. autoclass:: mongoengine.StringField

View File

@ -44,10 +44,11 @@ copyright = u'2009, Harry Marr'
# |version| and |release|, also used in various other places throughout the # |version| and |release|, also used in various other places throughout the
# built documents. # built documents.
# #
import mongoengine
# The short X.Y version. # The short X.Y version.
version = '0.1' version = mongoengine.get_version()
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
release = '0.1' release = mongoengine.get_release()
# The language for content autogenerated by Sphinx. Refer to documentation # The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages. # for a list of supported languages.
@ -128,7 +129,7 @@ html_static_path = ['_static']
# If true, SmartyPants will be used to convert quotes and dashes to # If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities. # typographically correct entities.
#html_use_smartypants = True html_use_smartypants = True
# Custom sidebar templates, maps document names to template names. # Custom sidebar templates, maps document names to template names.
#html_sidebars = {} #html_sidebars = {}

View File

@ -13,9 +13,9 @@ MongoDB. The source is available on
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2
tutorial.rst tutorial
userguide.rst userguide
apireference.rst apireference
Indices and tables Indices and tables
================== ==================

View File

@ -1,3 +1,4 @@
========
Tutorial Tutorial
======== ========
This tutorial introduces **MongoEngine** by means of example --- we will walk This tutorial introduces **MongoEngine** by means of example --- we will walk
@ -10,7 +11,7 @@ focus on the data-modelling side of the application, leaving out a user
interface. interface.
Getting started Getting started
--------------- ===============
Before we start, make sure that a copy of MongoDB is running in an accessible Before we start, make sure that a copy of MongoDB is running in an accessible
location --- running it locally will be easier, but if that is not an option location --- running it locally will be easier, but if that is not an option
then it may be run on a remote server. then it may be run on a remote server.
@ -27,7 +28,7 @@ database to use::
For more information about connecting to MongoDB see :ref:`guide-connecting`. For more information about connecting to MongoDB see :ref:`guide-connecting`.
Defining our documents Defining our documents
---------------------- ======================
MongoDB is *schemaless*, which means that no schema is enforced by the database MongoDB is *schemaless*, which means that no schema is enforced by the database
--- we may add and remove fields however we want and MongoDB won't complain. --- we may add and remove fields however we want and MongoDB won't complain.
This makes life a lot easier in many regards, especially when there is a change This makes life a lot easier in many regards, especially when there is a change
@ -46,7 +47,7 @@ specified tag. Finally, it would be nice if **comments** could be added to
posts. We'll start with **users**, as the others are slightly more involved. posts. We'll start with **users**, as the others are slightly more involved.
Users Users
^^^^^ -----
Just as if we were using a relational database with an ORM, we need to define Just as if we were using a relational database with an ORM, we need to define
which fields a :class:`User` may have, and what their types will be:: which fields a :class:`User` may have, and what their types will be::
@ -61,7 +62,7 @@ MongoDB --- this will only be enforced at the application level. Also, the User
documents will be stored in a MongoDB *collection* rather than a table. documents will be stored in a MongoDB *collection* rather than a table.
Posts, Comments and Tags Posts, Comments and Tags
^^^^^^^^^^^^^^^^^^^^^^^^ ------------------------
Now we'll think about how to store the rest of the information. If we were Now we'll think about how to store the rest of the information. If we were
using a relational database, we would most likely have a table of **posts**, a using a relational database, we would most likely have a table of **posts**, a
table of **comments** and a table of **tags**. To associate the comments with table of **comments** and a table of **tags**. To associate the comments with
@ -73,7 +74,7 @@ several ways we can achieve this, but each of them have their problems --- none
of them stand out as particularly intuitive solutions. of them stand out as particularly intuitive solutions.
Posts Posts
""""" ^^^^^
But MongoDB *isn't* a relational database, so we're not going to do it that But MongoDB *isn't* a relational database, so we're not going to do it that
way. As it turns out, we can use MongoDB's schemaless nature to provide us with way. As it turns out, we can use MongoDB's schemaless nature to provide us with
a much nicer solution. We will store all of the posts in *one collection* --- a much nicer solution. We will store all of the posts in *one collection* ---
@ -99,12 +100,12 @@ this kind of modelling out of the box::
link_url = StringField() link_url = StringField()
We are storing a reference to the author of the posts using a We are storing a reference to the author of the posts using a
:class:`mongoengine.ReferenceField` object. These are similar to foreign key :class:`~mongoengine.ReferenceField` object. These are similar to foreign key
fields in traditional ORMs, and are automatically translated into references fields in traditional ORMs, and are automatically translated into references
when they are saved, and dereferenced when they are loaded. when they are saved, and dereferenced when they are loaded.
Tags Tags
"""" ^^^^
Now that we have our Post models figured out, how will we attach tags to them? Now that we have our Post models figured out, how will we attach tags to them?
MongoDB allows us to store lists of items natively, so rather than having a MongoDB allows us to store lists of items natively, so rather than having a
link table, we can just store a list of tags in each post. So, for both link table, we can just store a list of tags in each post. So, for both
@ -120,13 +121,13 @@ size of our database. So let's take a look that the code our modified
author = ReferenceField(User) author = ReferenceField(User)
tags = ListField(StringField(max_length=30)) tags = ListField(StringField(max_length=30))
The :class:`mongoengine.ListField` object that is used to define a Post's tags The :class:`~mongoengine.ListField` object that is used to define a Post's tags
takes a field object as its first argument --- this means that you can have takes a field object as its first argument --- this means that you can have
lists of any type of field (including lists). Note that we don't need to lists of any type of field (including lists). Note that we don't need to
modify the specialised post types as they all inherit from :class:`Post`. modify the specialised post types as they all inherit from :class:`Post`.
Comments Comments
"""""""" ^^^^^^^^
A comment is typically associated with *one* post. In a relational database, to A comment is typically associated with *one* post. In a relational database, to
display a post with its comments, we would have to retrieve the post from the display a post with its comments, we would have to retrieve the post from the
database, then query the database again for the comments associated with the database, then query the database again for the comments associated with the
@ -152,7 +153,7 @@ We can then store a list of comment documents in our post document::
comments = ListField(EmbeddedDocumentField(Comment)) comments = ListField(EmbeddedDocumentField(Comment))
Adding data to our Tumblelog Adding data to our Tumblelog
---------------------------- ============================
Now that we've defined how our documents will be structured, let's start adding Now that we've defined how our documents will be structured, let's start adding
some documents to the database. Firstly, we'll need to create a :class:`User` some documents to the database. Firstly, we'll need to create a :class:`User`
object:: object::
@ -184,10 +185,10 @@ Note that if you change a field on a object that has already been saved, then
call :meth:`save` again, the document will be updated. call :meth:`save` again, the document will be updated.
Accessing our data Accessing our data
------------------ ==================
So now we've got a couple of posts in our database, how do we display them? So now we've got a couple of posts in our database, how do we display them?
Each document class (i.e. any class that inherits either directly or indirectly Each document class (i.e. any class that inherits either directly or indirectly
from :class:`mongoengine.Document`) has an :attr:`objects` attribute, which is from :class:`~mongoengine.Document`) has an :attr:`objects` attribute, which is
used to access the documents in the database collection associated with that used to access the documents in the database collection associated with that
class. So let's see how we can get our posts' titles:: class. So let's see how we can get our posts' titles::
@ -195,7 +196,7 @@ class. So let's see how we can get our posts' titles::
print post.title print post.title
Retrieving type-specific information Retrieving type-specific information
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ------------------------------------
This will print the titles of our posts, one on each line. But What if we want This will print the titles of our posts, one on each line. But What if we want
to access the type-specific data (link_url, content, etc.)? One way is simply to access the type-specific data (link_url, content, etc.)? One way is simply
to use the :attr:`objects` attribute of a subclass of :class:`Post`:: to use the :attr:`objects` attribute of a subclass of :class:`Post`::
@ -205,7 +206,7 @@ to use the :attr:`objects` attribute of a subclass of :class:`Post`::
Using TextPost's :attr:`objects` attribute only returns documents that were Using TextPost's :attr:`objects` attribute only returns documents that were
created using :class:`TextPost`. Actually, there is a more general rule here: created using :class:`TextPost`. Actually, there is a more general rule here:
the :attr:`objects` attribute of any subclass of :class:`mongoengine.Document` the :attr:`objects` attribute of any subclass of :class:`~mongoengine.Document`
only looks for documents that were created using that subclass or one of its only looks for documents that were created using that subclass or one of its
subclasses. subclasses.
@ -233,20 +234,21 @@ This would print the title of each post, followed by the content if it was a
text post, and "Link: <url>" if it was a link post. text post, and "Link: <url>" if it was a link post.
Searching our posts by tag Searching our posts by tag
^^^^^^^^^^^^^^^^^^^^^^^^^^ --------------------------
The :attr:`objects` attribute of a :class:`mongoengine.Document` is actually a The :attr:`objects` attribute of a :class:`~mongoengine.Document` is actually a
:class:`mongoengine.QuerySet` object. This lazily queries the database only :class:`~mongoengine.queryset.QuerySet` object. This lazily queries the
when you need the data. It may also be filtered to narrow down your query. database only when you need the data. It may also be filtered to narrow down
Let's adjust our query so that only posts with the tag "mongodb" are returned:: your query. Let's adjust our query so that only posts with the tag "mongodb"
are returned::
for post in Post.objects(tags='mongodb'): for post in Post.objects(tags='mongodb'):
print post.title print post.title
There are also methods available on :class:`mongoengine.QuerySet` objects that There are also methods available on :class:`~mongoengine.queryset.QuerySet`
allow different results to be returned, for example, calling :meth:`first` on objects that allow different results to be returned, for example, calling
the :attr:`objects` attribute will return a single document, the first matched :meth:`first` on the :attr:`objects` attribute will return a single document,
by the query you provide. Aggregation functions may also be used on the first matched by the query you provide. Aggregation functions may also be
:class:`mongoengine.QuerySet` objects:: used on :class:`~mongoengine.queryset.QuerySet` objects::
num_posts = Post.objects(tags='mongodb').count() num_posts = Post.objects(tags='mongodb').count()
print 'Found % posts with tag "mongodb"' % num_posts print 'Found % posts with tag "mongodb"' % num_posts

View File

@ -1,10 +1,11 @@
==========
User Guide User Guide
========== ==========
.. _guide-connecting: .. _guide-connecting:
Connecting to MongoDB Connecting to MongoDB
--------------------- =====================
To connect to a running instance of :program:`mongod`, use the To connect to a running instance of :program:`mongod`, use the
:func:`~mongoengine.connect` function. The first argument is the name of the :func:`~mongoengine.connect` function. The first argument is the name of the
database to connect to. If the database does not exist, it will be created. If database to connect to. If the database does not exist, it will be created. If
@ -22,7 +23,7 @@ provide :attr:`host` and :attr:`port` arguments to
connect('project1', host='192.168.1.35', port=12345) connect('project1', host='192.168.1.35', port=12345)
Defining documents Defining documents
------------------ ==================
In MongoDB, a **document** is roughly equivalent to a **row** in an RDBMS. When In MongoDB, a **document** is roughly equivalent to a **row** in an RDBMS. When
working with relational databases, rows are stored in **tables**, which have a working with relational databases, rows are stored in **tables**, which have a
strict **schema** that the rows follow. MongoDB stores documents in strict **schema** that the rows follow. MongoDB stores documents in
@ -30,7 +31,7 @@ strict **schema** that the rows follow. MongoDB stores documents in
is enforced at a database level. is enforced at a database level.
Defining a document's schema Defining a document's schema
^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ----------------------------
MongoEngine allows you to define schemata for documents as this helps to reduce MongoEngine allows you to define schemata for documents as this helps to reduce
coding errors, and allows for utility methods to be defined on fields which may coding errors, and allows for utility methods to be defined on fields which may
be present. be present.
@ -47,7 +48,7 @@ objects** as class attributes to the document class::
date_modified = DateTimeField(default=datetime.now) date_modified = DateTimeField(default=datetime.now)
Fields Fields
^^^^^^ ------
By default, fields are not required. To make a field mandatory, set the By default, fields are not required. To make a field mandatory, set the
:attr:`required` keyword argument of a field to ``True``. Fields also may have :attr:`required` keyword argument of a field to ``True``. Fields also may have
validation constraints available (such as :attr:`max_length` in the example validation constraints available (such as :attr:`max_length` in the example
@ -60,12 +61,69 @@ are as follows:
* :class:`~mongoengine.IntField` * :class:`~mongoengine.IntField`
* :class:`~mongoengine.FloatField` * :class:`~mongoengine.FloatField`
* :class:`~mongoengine.DateTimeField` * :class:`~mongoengine.DateTimeField`
* :class:`~mongoengine.ListField`
* :class:`~mongoengine.ObjectIdField` * :class:`~mongoengine.ObjectIdField`
* :class:`~mongoengine.EmbeddedDocumentField` * :class:`~mongoengine.EmbeddedDocumentField`
* :class:`~mongoengine.ReferenceField` * :class:`~mongoengine.ReferenceField`
List fields
^^^^^^^^^^^
MongoDB allows the storage of lists of items. To add a list of items to a
:class:`~mongoengine.Document`, use the :class:`~mongoengine.ListField` field
type. :class:`~mongoengine.ListField` takes another field object as its first
argument, which specifies which type elements may be stored within the list::
class Page(Document):
tags = ListField(StringField(max_length=50))
Embedded documents
^^^^^^^^^^^^^^^^^^
MongoDB has the ability to embed documents within other documents. Schemata may
be defined for these embedded documents, just as they may be for regular
documents. To create an embedded document, just define a document as usual, but
inherit from :class:`~mongoengine.EmbeddedDocument` rather than
:class:`~mongoengine.Document`::
class Comment(EmbeddedDocument):
content = StringField()
To embed the document within another document, use the
:class:`~mongoengine.EmbeddedDocumentField` field type, providing the embedded
document class as the first argument::
class Page(Document):
comments = ListField(EmbeddedDocumentField(Comment))
comment1 = Comment('Good work!')
comment2 = Comment('Nice article!')
page = Page(comments=[comment1, comment2])
Reference fields
^^^^^^^^^^^^^^^^
References may be stored to other documents in the database using the
:class:`~mongoengine.ReferenceField`. Pass in another document class as the
first argument to the constructor, then simply assign document objects to the
field::
class User(Document):
name = StringField()
class Page(Document):
content = StringField()
author = ReferenceField(User)
john = User(name="John Smith")
john.save()
post = Page(content="Test Page")
post.author = john
post.save()
The :class:`User` object is automatically turned into a reference behind the
scenes, and dereferenced when the :class:`Page` object is retrieved.
Document collections Document collections
^^^^^^^^^^^^^^^^^^^^ --------------------
Document classes that inherit **directly** from :class:`~mongoengine.Document` Document classes that inherit **directly** from :class:`~mongoengine.Document`
will have their own **collection** in the database. The name of the collection will have their own **collection** in the database. The name of the collection
is by default the name of the class, coverted to lowercase (so in the example is by default the name of the class, coverted to lowercase (so in the example
@ -78,3 +136,175 @@ document class to use::
class Page(Document): class Page(Document):
title = StringField(max_length=200, required=True) title = StringField(max_length=200, required=True)
meta = {'collection': 'cmsPage'} meta = {'collection': 'cmsPage'}
Document inheritance
--------------------
To create a specialised type of a :class:`~mongoengine.Document` you have
defined, you may subclass it and add any extra fields or methods you may need.
As this is new class is not a direct subclass of
:class:`~mongoengine.Document`, it will not be stored in its own collection; it
will use the same collection as its superclass uses. This allows for more
convenient and efficient retrieval of related documents::
# Stored in a collection named 'page'
class Page(Document):
title = StringField(max_length=200, required=True)
# Also stored in the collection named 'page'
class DatedPage(Page):
date = DateTimeField()
Working with existing data
^^^^^^^^^^^^^^^^^^^^^^^^^^
To enable correct retrieval of documents involved in this kind of heirarchy,
two extra attributes are stored on each document in the database: :attr:`_cls`
and :attr:`_types`. These are hidden from the user through the MongoEngine
interface, but may not be present if you are trying to use MongoEngine with
an existing database. For this reason, you may disable this inheritance
mechansim, removing the dependency of :attr:`_cls` and :attr:`_types`, enabling
you to work with existing databases. To disable inheritance on a document
class, set :attr:`allow_inheritance` to ``False`` in the :attr:`meta`
dictionary::
# Will work with data in an existing collection named 'cmsPage'
class Page(Document):
title = StringField(max_length=200, required=True)
meta = {
'collection': 'cmsPage',
'allow_inheritance': False,
}
Documents instances
===================
To create a new document object, create an instance of the relevant document
class, providing values for its fields as its constructor keyword arguments.
You may provide values for any of the fields on the document, but only
**required** fields are necessary at this stage::
>>> page = Page(title="Test Page")
>>> page.title
'Test Page'
You may also assign values to the document's fields using standard object
attribute syntax::
>>> page.title = "Example Page"
>>> page.title
'Example Page'
Saving and deleting documents
-----------------------------
To save the document to the database, call the
:meth:`~mongoengine.Document.save` method. If the document does not exist in
the database, it will be created. If it does already exist, it will be
updated.
To delete a document, call the :meth:`~mongoengine.Document.delete` method.
Note that this will only work if the document exists in the database and has a
valide :attr:`id`.
Document IDs
------------
Each document in the database has a unique id. This may be accessed through the
:attr:`id` attribute on :class:`~mongoengine.Document` objects. Usually, the id
will be generated automatically by the database server when the object is save,
meaning that you may only access the :attr:`id` field once a document has been
saved::
>>> page = Page(title="Test Page")
>>> page.id
...
AttributeError('_id')
>>> page.save()
>>> page.id
ObjectId('123456789abcdef000000000')
Alternatively, you may explicitly set the :attr:`id` before you save the
document, but the id must be a valid PyMongo :class:`ObjectId`.
Querying the database
=====================
:class:`~mongoengine.Document` classes have an :attr:`objects` attribute, which
is used for accessing the objects in the database associated with the class.
The :attr:`objects` attribute is actually a
:class:`~mongoengine.queryset.QuerySetManager`, which creates and returns a new
a new :class:`~mongoengine.queryset.QuerySet` object on access. The
:class:`~mongoengine.queryset.QuerySet` object may may be iterated over to
fetch documents from the database::
# Prints out the names of all the users in the database
for user in User.objects:
print user.name
Filtering queries
-----------------
The query may be filtered by calling the
:class:`~mongoengine.queryset.QuerySet` object with field lookup keyword
arguments. The keys in the keyword arguments correspond to fields on the
:class:`~mongoengine.Document` you are querying::
# This will return a QuerySet that will only iterate over users whose
# 'country' field is set to 'uk'
uk_users = User.objects(country='uk')
Fields on embedded documents may also be referred to using field lookup syntax
by using a double-underscore in place of the dot in object attribute access
syntax::
# This will return a QuerySet that will only iterate over pages that have
# been written by a user whose 'country' field is set to 'uk'
uk_pages = Page.objects(author__country='uk')
Querying lists
^^^^^^^^^^^^^^
On most fields, this syntax will look up documents where the field specified
matches the given value exactly, but when the field refers to a
:class:`~mongoengine.ListField`, a single item may be provided, in which case
lists that contain that item will be matched::
class Page(Document):
tags = ListField(StringField())
# This will match all pages that have the word 'coding' as an item in the
# 'tags' list
Page.objects(tags='coding')
Query operators
---------------
Operators other than equality may also be used in queries; just attach the
operator name to a key with a double-underscore::
# Only find users whose age is 18 or less
young_users = Users.objects(age__lte=18)
Available operators are as follows:
* ``neq`` -- not equal to
* ``lt`` -- less than
* ``lte`` -- less than or equal to
* ``gt`` -- greater than
* ``gte`` -- greater than or equal to
* ``in`` -- value is in list (a list of values should be provided)
* ``nin`` -- value is not in list (a list of values should be provided)
* ``mod`` -- ``value % x == y``, where ``x`` and ``y`` are two provided values
* ``all`` -- every item in array is in list of values provided
* ``size`` -- the size of the array is
* ``exists`` -- value for field exists
Limiting and skipping results
-----------------------------
Just as with traditional ORMs, you may limit the number of results returned, or
skip a number or results in you query.
:meth:`mongoengine.queryset.QuerySet.limit` and
:meth:`mongoengine.queryset.QuerySet.skip` and methods are available on
:meth:`mongoengine.queryset.QuerySet` objects, but the prefered syntax for
achieving this is using array-slicing syntax::
# Only the first 5 people
users = User.objects[:5]
# All except for the first 5 people
users = User.objects[5:]
# 5 users, starting from the 10th user found
users = User.objects[10:15]

View File

@ -8,5 +8,20 @@ from connection import *
__all__ = document.__all__ + fields.__all__ + connection.__all__ __all__ = document.__all__ + fields.__all__ + connection.__all__
__author__ = 'Harry Marr' __author__ = 'Harry Marr'
__version__ = '0.1'
VERSION = (0, 1, 0, 'alpha')
def get_version():
version = '%s.%s' % (VERSION[0], VERSION[1])
if VERSION[2]:
version = '%s.%s' % (version, VERSION[2])
return version
def get_release():
version = get_version()
if VERSION[3] != 'final':
version = '%s-%s' % (version, VERSION[3])
return version
__version__ = get_release()