Guides¶
Getting Started¶
Before we start, make sure that a copy of MongoDB is running in an accessible location. If you haven’t installed pymongoext, simply use pip to install it like so:
$ pip install pymongoext
Every Model subclass need implement the pymongoext.model.Model.db()
method which returns a
valid pymongo database instance.
To achieve this, we advice creating a base model class that implements the db()
method.
from pymongo import MongoClient
from pymongoext import Model
class BaseModel(Model):
@classmethod
def db(cls):
return MongoClient()['my_database_name']
Now all concrete models would extend this class.
Defining our documents¶
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. This makes life a lot easier in many regards, especially when there is a change to the data model. However, defining schemas for our documents can help to iron out bugs involving incorrect types or missing fields, and also allow us to define utility methods on our documents in the same way that traditional ORMs do.
Users Model¶
Just as if we were using a relational database with an ORM, we need to define which fields a User may have (schema), and what types of data they might store.
from pymongoext import DictField, StringField, IntField
class User(BaseModel):
__schema__ = DictField(dict(
email=StringField(required=True),
first_name=StringField(required=True),
last_name=StringField(required=True),
yob=IntField(minimum=1900, maximum=2019)
))
Indexes¶
MongoDB supports secondary indexes.
With pymongoext, we define these indexes as a list within our Model on the __indexes__
variable.
Both Single Field and compound indexes are supported.
from pymongo import IndexModel
class User(BaseModel):
.....
__indexes__ = [IndexModel('email', unique=True), 'first_name']
This example creates a unique index on email
and an index on first_name
.
Single Field descending index¶
Suppose we wanted the index on first_name
to be sorted in a descending manner.
That could be achieved as follows
>>> ('first_name', pymongo.DESCENDING)
Alternatively, we could also create a descending index by prefixing the field name with a -
sign
>>> '-first_name'
Compound indexes¶
A compound index is simply a list of single field indexes.
Therefore, to create a compound index on first_name
and last_name
sorted in opposite directions
from pymongo import IndexModel
class User(BaseModel):
.....
__indexes__ = [
IndexModel('email', unique=True),
['-first_name', '+last_name'] # compound index
]
Note the -
and +
signs which specify the index on first_name and last_name should be sorted in
descending and ascending order respectively.
Manipulators¶
Manipulators are useful for manipulating (adding, removing, modifying) document properties
before being persisted to MongoDB and after retrieval.
A manipulator has two methods transform_incoming
and transform_outgoing
.
Suppose you want to print out the person’s full name. You could do it yourself:
user = User.find_one()
print("{} {}".format(user['first_name'], user['last_name']))
But concatenating the first and last name every time can get cumbersome.
And what if you want to do some extra processing on the name, like removing diacritics?
A Manipulator lets you define a virtual property full_name
that won’t get persisted to MongoDB.
from pymongoext.manipulators import Manipulator
class User(BaseModel):
.....
class FullNameManipulator(Manipulator):
def transform_outgoing(self, doc, model):
doc['full_name'] = "{} {}".format(user['first_name'], user['last_name'])
return doc
def transform_incoming(self, doc, model, action):
if 'full_name' in doc:
del doc['full_name'] # Don't persist full name
return doc
Now, every document you retrieve will have a full_name property.
user = User.find_one()
print(user['full_name'])
Parametrized Manipulator¶
A manipulator is bind to a Model as either a manipulator class or object. The later is necessary when using manipulators that are initialized with parameters.
Suppose you had a manipulator that adds a dynamic value to a dynamic field:
class AddManipulator(Manipulator):
def __init__(self, field, value):
self.field = field
self.value = value
def transform_outgoing(self, doc, model):
doc[self.field] = doc[self.field] + self.value
return doc
Then we would bind this manipulator to the model as an object
class User(BaseModel):
.....
addOneToFamilySize = AddManipulator('family_size', 1)
class Book(BaseModel):
......
subtractFirstAndLastPages = AddManipulator('page_count', -2)