Welcome to nanomongo’s documentation!¶
nanomongo is a minimal MongoDB Object-Document Mapper for Python.
It does not attempt to be a feature-complete ODM but if you like
using pymongo
api with python dictionaries and often find yourself
writing validators and pymongo.Collection
wrappers, nanomongo
might suit your needs.
nanomongo has full test coverage.
Quick Links: Source (github) - Documentation (rtd) - Packages (PyPi)
Version 0.4: Utility methods dbref_field_getters, get_dbref()
and Bugfix Python23 text type compatibility
Version 0.3: nanomongo is now python2 compatible (with syntactic difference when defining your Document, see Defining Your Document below).
Installation¶
$ pip install nanomongo
Quick Tutorial¶
Defining Your Document¶
import pymongo
from nanomongo import Index, Field, BaseDocument
mclient = pymongo.MongoClient()
class Py23CompatibleDoc(BaseDocument):
client = mclient
db = 'dbname'
dot_notation = True
foo = Field(str)
bar = Field(int, required=False)
# Python3 only
class Py3Doc(BaseDocument, dot_notation=True, client=mclient, db='dbname'):
foo = Field(str)
bar = Field(int, required=False)
__indexes__ = [
Index('foo'),
Index([('bar', 1), ('foo', -1)], unique=True),
]
You don’t have to declare client
or db
as shown above, you can
register()
(and I definitely prefer it on
python2) your document later as such:
client = pymongo.MongoClient()
MyDoc.register(client=client, db='dbname', collection='mydoc')
If you omit collection
when defining/registering your document,
__name__.lower()
will be used by default
Creating, Inserting, Saving¶
doc = MyDoc(foo='42') # or MyDoc({'foo': '42'})
doc.bar = 42 # attribute style access because dot_notation=True
doc.insert()
insert()
is a wrapper around
pymongo.Collection().insert()
and has the same return value (_id
)
unless you explicitly set w=0
doc.foo = 'new foo' # this change is recorded
del doc.bar # this is recored as well
doc.save() # save only does partial updates
save()
uses pymongo.Collection().update()
with the changed data. The above will run
update({'_id': doc['_id']}, {'$set': {'foo': 'new foo'}, '$unset': {'bar': 1}})
Querying¶
Doc.find({'bar': 42})
Doc.find_one({'foo': 'new foo'})
find()
and find_one()
methods are essentially wrappers around respective methods of pymongo.Collection()
and they take the same arguments.
Extensive Example¶
Advanced Features¶
$addToSet¶
MongoDB $addToSet
update modifier is very useful. nanomongo implements it.
addToSet()
will do the add-to-field-if-doesnt-exist
on your document instance and record the change to be applied later when
save()
is called.
# lets expand our MyDoc
class NewDoc(MyDoc):
list_field = Field(list)
dict_field = Field(dict)
NewDoc.register(client=client, db='dbname')
doc_id = NewDoc(list_field=[42], dict_field={'foo':[]}).insert()
doc = NewDoc.find_one({'_id': doc_id})
doc.addToSet('list_field', 1337)
doc.addToSet('dict_field.foo', 'like a boss')
doc.save()
Both of the above addToSet
are applied to the NewDoc
instance like MongoDB does it eg.
- create list field with new value if it doesn’t exist
- add new value to list field if it’s missing (append)
- complain if it is not a list field
When save is called, query becomes:
update({'$addToSet': {'list_field': {'$each': [1337]}},
'dict_field.foo': {'$each': ['like a boss']}})
Undefined fields or field type mismatch raises ValidationError
.
QuerySpec check¶
find()
and find_one()
has a simple check against queries that can not match, logging warnings. This is
an experimental feature at the moment and only does type checks as such:
{'foo': 1}
will log warnings if
- Document has no field named
foo
(field existence) foo
field is not of typeint
(field data type)
or {'foo.bar': 1}
will log warnings if
foo
field is not of typedict
orlist
(dotted field type)
dbref_field_getters¶
Documents that define bson.DBRef
fields automatically generate getter methods
through nanomongo.document.ref_getter_maker()
where the generated methods
have names such as get_<field_name>_field
.
class MyDoc(BaseDocument):
# document_class with full path
source = Field(DBRef, document_class='some_module.Source')
# must be defined in same module as this will use
# mydoc_instance.__class__.__module__
destination = Field(DBRef, document_class='Destination')
# autodiscover
user = Field(DBRef)
nanomongo tries to guess the document_class
if it’s not provided by looking at
registered subclasses of BaseDocument
. If it matches two (for example
when two document classes use the same collection), it will raise
UnsupportedOperation
.
pymongo & motor¶
Throughout the documentation, pymongo
is referenced but all features work the
same when using motor transparently if you
register the document class with a motor.MotorClient
.
import motor
from nanomongo import Field, BaseDocument
class MyDoc(BaseDocument, dot_notation=True):
foo = Field(str)
bar = Field(list, required=False)
client = motor.MotorClient().open_sync()
MyDoc.register(client=client, db='dbname')
# and now some async motor queries (using @gen.engine)
doc_id = yield motor.Op(MyDoc(foo=42).insert)
doc = yield motor.Op(MyDoc.find_one, {'foo': 42})
doc.addToSet('bar', 1337)
yield motor.Op(doc.save)
Note however that pymongo vs motor behaviour is not necessarily identical.
Asynchronous methods require tornado.ioloop.IOLoop
. For example,
register()
runs ensure_index
but the query will not be sent
to MongoDB until IOLoop.start()
is called.
Contents¶
nanomongo.field
— Field
¶
Field¶
-
class
nanomongo.field.
Field
(*args, **kwargs)[source]¶ Instances of this class is used to define field types and automatically create validators. Note that a Field definition has no value added:
field_name = Field(str, default='cheeseburger') foo = Field(datetime, auto_update=True) bar = Field(list, required=False)
-
Field.
__init__
(*args, **kwargs)[source]¶ Field kwargs are checked for correctness and field validator is set, along with other attributes such as
required
andauto_update
Keyword Arguments: - default: default field value, must pass type check, can be a
callable
- required: if
True
field must exist and not beNone
(default:True
) - auto_update: set value to
datetime.utcnow()
before inserts/saves; only valid for datetime fields (default:False
)
- default: default field value, must pass type check, can be a
nanomongo.document
— BaseDocument
¶
BaseDocument¶
-
class
nanomongo.document.
BaseDocument
(*args, **kwargs)[source]¶ BaseDocument class. Subclasses should be used. See
__init__()
-
BaseDocument.
__init__
(*args, **kwargs)[source]¶ Inits the document with given data and validates the fields (field validation bad idea during init?). If you define
__init__
method for your document class, make sure to call thisclass MyDoc(BaseDocument, dot_notation=True): foo = Field(str) bar = Field(int, required=False) def __init__(self, *args, **kwargs): super(MyDoc, self).__init__(*args, **kwargs) # do other stuff
-
classmethod
BaseDocument.
register
(client=None, db=None, collection=None)[source]¶ Register this document. Sets client, database, collection information, builds (ensure) indexes and sets SON manipulator
-
classmethod
BaseDocument.
find
(*args, **kwargs)[source]¶ pymongo.Collection().find
wrapper for this document
-
classmethod
BaseDocument.
find_one
(*args, **kwargs)[source]¶ pymongo.Collection().find_one
wrapper for this document
-
BaseDocument.
validate
()[source]¶ Override this to add extra document validation, will be called at the end of
validate_all()
-
BaseDocument.
validate_all
()[source]¶ Check against extra fields, run field validators and user-defined
validate()
-
BaseDocument.
validate_diff
()[source]¶ Check correctness of diffs before partial update, also run user-defined
validate()
-
BaseDocument.
run_auto_updates
()[source]¶ Runs functions in
nanomongo.transforms
like auto_update stuff beforeinsert()
save()
-
BaseDocument.
insert
(**kwargs)[source]¶ Insert document into database, return _id. Runs
run_auto_updates()
andvalidate_all()
-
BaseDocument.
save
(**kwargs)[source]¶ Saves document. This method only does partial updates and no inserts. Runs
run_auto_updates()
andvalidate_all()
prior to save. ReturnsCollection.update()
response
-
BaseDocument.
addToSet
(field, value)[source]¶ MongoDB
Collection.update()
$addToSet functionality. This sets the value accordingly and records the change in__nanodiff__
to be saved withsave()
.# MongoDB style dot notation can be used to add to lists # in embedded documents doc = Doc(foo=[], bar={}) doc.addToSet('foo', new_value) doc.addToSet('bar.sub_field', new_value)
Contrary to how $set has no effect under __setitem__ (see
RecordingDict
.__setitem__) when the new value is equal to the current; $addToSet explicitly adds the call to__nanodiff__
so it will be sent to the database whensave()
is called.
-
BaseDocument.
get_dbref
()[source]¶ create a
bson.DBRef
instance for thisBaseDocument
instance
nanomongo.util
— RecordingDict
¶
RecordingDict¶
-
class
nanomongo.util.
RecordingDict
(*args, **kwargs)[source]¶ A dictionary subclass modifying
__setitem__()
and__delitem__()
methods to record changes in its__nanodiff__
attribute-
check_can_update
(modifier, field_name)[source]¶ Check if given modifier field_name combination can be added. MongoDB does not allow field duplication with update modifiers. This is to be used with methods addToSet …
-
clean_other_modifiers
(current_mod, field_name)[source]¶ Given current_mod, removes other field_name modifiers, eg. when called with $set, removes $unset and $addToSet etc. on field_name
-
get_sub_diff
()[source]¶ get __nanodiff__ from embedded documents. Find fields of
RecordingDict
type, iterate over their diff and build dotted keys for top level diff
-
-
class
nanomongo.util.
NanomongoSONManipulator
(as_class, transforms=None)[source]¶ A pymongo SON Manipulator used on data that comes from the database to transform data to the document class we want because as_class argument to pymongo find methods is called in a way that screws us.
- Recursively applied, we don’t want that
- __init__ is not properly used but rather __setitem__, fails us
JIRA: PYTHON-175 PYTHON-215
nanomongo.errors
¶
-
class
nanomongo.errors.
ConfigurationError
[source]¶ Raised when a required value found to be not set during operation, or a Document class is registered more than once
-
class
nanomongo.errors.
IndexMismatchError
[source]¶ Raised when a defined index does not match defined fields