from .data_proxy import DataProxy
from .exceptions import NotCreatedError, AbstractDocumentError
from .meta import MetaDocument, DocumentOpts
from .data_objects import Reference
from .registerer import retrieve_document
from bson import DBRef
def _base_opts():
opts = DocumentOpts('Document', {}, ())
opts.abstract = True
opts.allow_inheritance = True
opts.register_document = False
return opts
[docs]class Document(metaclass=MetaDocument):
__slots__ = ('created', '_data', 'parent')
# opts is generated by MetaDocument from Meta class except for root Document
opts = _base_opts()
def __init__(self, **kwargs):
if self.opts.abstract:
raise AbstractDocumentError("Cannot instantiate an abstract Document")
super().__init__()
self.created = False
self._data = DataProxy(self.schema, data=kwargs if kwargs else None)
def __repr__(self):
return '<object Document %s.%s(%s)>' % (
self.__module__, self.__class__.__name__, self._data._data)
def __eq__(self, other):
if self.pk is None:
return self is other
elif isinstance(other, self.__class__) and other.pk is not None:
return self.pk == other.pk
elif isinstance(other, DBRef):
return other.collection == self.collection.name and other.id == self.pk
elif isinstance(other, Reference):
return isinstance(self, other.document_cls) and self.pk == other.pk
return NotImplemented
@property
def pk(self):
"""
Return the document's primary key (i.e. `_id` in mongo notation) or
None if not available yet
"""
return self._data.get_by_mongo_name('_id')
@property
def dbref(self):
"""
Return a pymongo DBRef instance related to the document
"""
if not self.created:
raise NotCreatedError('Must create the document before'
' having access to DBRef')
return DBRef(collection=self.collection.name, id=self.pk)
@classmethod
[docs] def build_from_mongo(cls, data, partial=False, use_cls=False):
"""
Create a document instance from MongoDB data
:param data: data as retrieved from MongoDB
:param use_cls: if the data contains a ``_cls`` field,
use it determine the Document class to instanciate
"""
# If a _cls is specified, we have to use this document class
if use_cls and '_cls' in data:
cls = retrieve_document(data['_cls'])
doc = cls()
doc.from_mongo(data, partial=partial)
return doc
[docs] def from_mongo(self, data, partial=False):
"""
Update the document with the MongoDB data
:param data: data as retrieved from MongoDB
"""
# TODO: handle partial
self._data.from_mongo(data, partial=partial)
self.created = True
[docs] def to_mongo(self, update=False):
"""
"""
if update and not self.created:
raise NotCreatedError('Must create the document before'
' using update')
return self._data.to_mongo(update=update)
[docs] def update(self, data, schema=None):
"""
Update the document with the given data
:param schema: use this schema for the load instead of the default one
"""
return self._data.update(data, schema=schema)
[docs] def dump(self, schema=None):
"""
Dump the document: return a ``dict``
:param schema: use this schema for the dump instead of the default one
"""
return self._data.dump(schema=schema)
[docs] def clear_modified(self):
"""
Reset the list of document's modified items
"""
self._data.clear_modified()
@property
def collection(self):
"""
Return the collection used by this Document class
"""
# Cannot implicitly access to the class's property
return type(self).collection
# Data-proxy accessor shortcuts
def __getitem__(self, name):
return self._data.get(name)
def __delitem__(self, name):
self._data.delete(name)
def __setitem__(self, name, value):
self._data.set(name, value)
def __setattr__(self, name, value):
if name in Document.__dict__:
Document.__dict__[name].__set__(self, value)
else:
self._data.set(name, value, to_raise=AttributeError)
def __getattr__(self, name):
return self._data.get(name, to_raise=AttributeError)
def __delattr__(self, name):
self._data.delete(name, to_raise=AttributeError)