Skip to content
This repository has been archived by the owner on May 13, 2020. It is now read-only.

Commit

Permalink
- Feature: Added find_objects() and find_one_object()to the c…
Browse files Browse the repository at this point in the history
…ollection

  wrapper, so that whenever you get a collection from the data manager, you
  can load objects directly through the find API.

- Feature: Added the ability for MongoContained objects to fully reference and
  load their parents. This allows one to query mongo directly and create the
  object from the doc without going through the right container, which you
  might not know easily
  • Loading branch information
strichter committed Feb 9, 2013
1 parent 2a1e56e commit 0a211f5
Show file tree
Hide file tree
Showing 6 changed files with 263 additions and 14 deletions.
11 changes: 9 additions & 2 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,17 @@
CHANGES
=======

0.7.8 (unreleased)
0.8.0 (unreleased)
------------------

- Nothing changed yet.
- Feature: Added ``find_objects()`` and ``find_one_object()``to the collection
wrapper, so that whenever you get a collection from the data manager, you
can load objects directly through the find API.

- Feature: Added the ability for MongoContained objects to fully reference and
load their parents. This allows one to query mongo directly and create the
object from the doc without going through the right container, which you
might not know easily.


0.7.7 (2013-02-08)
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ def read(*rnames):

setup (
name='mongopersist',
version='0.7.8.dev0',
version='0.8.0.dev0',
author = "Stephan Richter",
author_email = "stephan.richter@gmail.com",
description = "Mongo Persistence Backend",
Expand Down
18 changes: 18 additions & 0 deletions src/mongopersist/datamanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"""Mongo Persistent Data Manager"""
from __future__ import absolute_import
import UserDict
import bson
import logging
import transaction
import sys
Expand Down Expand Up @@ -111,6 +112,23 @@ def __init__(self, collection, datamanager):
self.__dict__['collection'] = collection
self.__dict__['_datamanager'] = datamanager

def find_objects(self, *args, **kw):
docs = self.find(*args, **kw)
coll = self.collection.name
dbname = self.collection.database.name
for doc in docs:
dbref = bson.dbref.DBRef(coll, doc['_id'], dbname)
self._datamanager._latest_states[dbref] = doc
yield self._datamanager.load(dbref)

def find_one_object(self, *args, **kw):
doc = self.find_one(*args, **kw)
coll = self.collection.name
dbname = self.collection.database.name
dbref = bson.dbref.DBRef(coll, doc['_id'], dbname)
self._datamanager._latest_states[dbref] = doc
return self._datamanager.load(dbref)

def __getattr__(self, name):
attr = getattr(self.collection, name)
if MONGO_ACCESS_LOGGING and name in self.LOGGED_METHODS:
Expand Down
3 changes: 3 additions & 0 deletions src/mongopersist/serialize.py
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,9 @@ def set_ghost_state(self, obj, doc=None):
coll = self._jar.get_collection(
obj._p_oid.database, obj._p_oid.collection)
doc = coll.find_one({'_id': obj._p_oid.id})
# Check that we really have a state doc now.
if doc is None:
raise ImportError(obj._p_oid)
# Create a copy of the doc, so that we can modify it.
state_doc = doc.copy()
# Remove unwanted attributes.
Expand Down
50 changes: 39 additions & 11 deletions src/mongopersist/zope/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,43 @@

class MongoContained(contained.Contained):

_v_name = None
_m_name_attr = None
_m_name_getter = None
_m_name_setter = None

_m_parent_attr = None
_m_parent_getter = None
_m_parent_setter = None
_v_parent = None

@getproperty
def __name__(self):
return getattr(self, '_v_key', None)
if self._v_name is None:
if self._m_name_attr is not None:
self._v_name = getattr(self, self._m_name_attr, None)
elif self._m_name_getter is not None:
self._v_name = self._m_name_getter()
return self._v_name
@setproperty
def __name__(self, value):
setattr(self, '_v_key', value)
if self._m_name_setter is not None:
self._m_name_setter(value)
self._v_name = value

@getproperty
def __parent__(self):
return getattr(self, '_v_parent', None)
if self._v_parent is None:
if self._m_parent_attr is not None:
self._v_parent = getattr(self, self._m_parent_attr, None)
elif self._m_parent_getter is not None:
self._v_parent = self._m_parent_getter()
return self._v_parent
@setproperty
def __parent__(self, value):
setattr(self, '_v_parent', value)
if self._m_parent_setter is not None:
self._m_parent_setter(value)
self._v_parent = value


class SimpleMongoContainer(sample.SampleContainer, persistent.Persistent):
Expand All @@ -62,22 +86,22 @@ def __setstate__(self, state):

def __getitem__(self, key):
obj = super(SimpleMongoContainer, self).__getitem__(key)
obj._v_key = key
obj._v_name = key
obj._v_parent = self
return obj

def get(self, key, default=None):
'''See interface `IReadContainer`'''
obj = super(SimpleMongoContainer, self).get(key, default)
if obj is not default:
obj._v_key = key
obj._v_name = key
obj._v_parent = self
return obj

def items(self):
items = super(SimpleMongoContainer, self).items()
for key, obj in items:
obj._v_key = key
obj._v_name = key
obj._v_parent = self
return items

Expand Down Expand Up @@ -161,8 +185,12 @@ def _m_add_items_filter(self, filter):
filter[key] = value

def _locate(self, obj, doc):
obj._v_key = doc[self._m_mapping_key]
obj._v_parent = self
# Helper method that is only used when locating items that are already
# in the container and are simply loaded from Mongo.
if obj.__name__ is None:
obj.__name__ = doc[self._m_mapping_key]
if obj.__parent__ is None:
obj._v_parent = self

def _load_one(self, doc):
# Create a DBRef object and then load the full state of the object.
Expand Down Expand Up @@ -190,7 +218,7 @@ def __getitem__(self, key):
return obj

def _real_setitem(self, key, value):
# This call by iteself caues the state to change _p_changed to True.
# This call by iteself causes the state to change _p_changed to True.
if self._m_mapping_key is not None:
setattr(value, self._m_mapping_key, key)
if self._m_parent_key is not None:
Expand Down Expand Up @@ -295,7 +323,7 @@ def _m_remove_documents(self):
return True

def _locate(self, obj, doc):
obj._v_key = unicode(doc['_id'])
obj._v_name = unicode(doc['_id'])
obj._v_parent = self

def __getitem__(self, key):
Expand Down
193 changes: 193 additions & 0 deletions src/mongopersist/zope/tests/test_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,199 @@ class Person(container.MongoContained, SimplePerson):
pass


def doctest_MongoContained_simple():
"""MongoContained: simple use
The simplest way to use MongoContained is to use it without any special
modification. In this case it is required that the container always sets
the name and parent after loading the item. It can do so directly by
setting ``_v_name`` and ``_v_parent`` so that the persistence mechanism
does not kick in.
>>> class Simples(container.MongoContainer):
... def __init__(self, name):
... super(Simples, self).__init__()
... self.name = name
... def __repr__(self):
... return '<Simples %s>' %self.name
>>> class Simple(container.MongoContained, persistent.Persistent):
... pass
Let's create a simple component and activate the persistence machinery:
>>> s = Simple()
>>> s._p_jar = dm
As you can see, the changed flag is not changed:
>>> s._p_changed
False
>>> s._v_name = 'simple'
>>> s._v_parent = Simples('one')
>>> s._p_changed
False
And accessing the name and parent works:
>>> s.__name__
'simple'
>>> s.__parent__
<Simples one>
But assignment works as well.
>>> s.__name__ = 'simple2'
>>> s.__name__
'simple2'
>>> s.__parent__ = Simples('two')
>>> s.__parent__
<Simples two>
>>> s._p_changed
True
"""

def doctest_MongoContained_proxy_attr():
"""MongoContained: proxy attributes
It is also possible to use proxy attributes to reference the name and
parent. This allows you to have nice attribute names for storage in Mongo.
The main benefit, though is the ability of the object to load its
location, so that you can load the object without going through the
container and get full location path.
>>> class Proxies(container.MongoContainer):
... def __init__(self, name):
... super(Proxies, self).__init__()
... self.name = name
... def __repr__(self):
... return '<Proxies %s>' %self.name
>>> class Proxy(container.MongoContained, persistent.Persistent):
... _m_name_attr = 'name'
... _m_parent_attr = 'parent'
... def __init__(self, name, parent):
... self.name = name
... self.parent = parent
Let's create a proxy component and activate the persistence machinery:
>>> p = Proxy('proxy', Proxies('one'))
>>> p._p_jar = dm
So accessing the name and parent works:
>>> p.__name__
'proxy'
>>> p.__parent__
<Proxies one>
But assignment is only stored into the volatile variables and the proxy
attribute values are not touched.
>>> p.__name__ = 'proxy2'
>>> p.__name__
'proxy2'
>>> p.name
'proxy'
>>> p.__parent__ = Proxies('two')
>>> p.__parent__
<Proxies two>
>>> p.parent
<Proxies one>
This behavior is intentional, so that containment machinery cannot mess
with the real attributes. Note that in practice, only MongoContainer sets
the ``__name__`` and ``__parent__`` and it should be always consistent
with the referenced attributes.
"""

def doctest_MongoContained_setter_getter():
"""MongoContained: setter/getter functions
If you need ultimate flexibility of where to get and store the name and
parent, then you can define setters and getters.
>>> class Funcs(container.MongoContainer):
... def __init__(self, name):
... super(Funcs, self).__init__()
... self.name = name
... def __repr__(self):
... return '<Funcs %s>' %self.name
>>> class Func(container.MongoContained, persistent.Persistent):
... _m_name_getter = lambda s: s.name
... _m_name_setter = lambda s, v: setattr(s, 'name', v)
... _m_parent_getter = lambda s: s.parent
... _m_parent_setter = lambda s, v: setattr(s, 'parent', v)
... def __init__(self, name, parent):
... self.name = name
... self.parent = parent
Let's create a func component and activate the persistence machinery:
>>> f = Func('func', Funcs('one'))
>>> f._p_jar = dm
So accessing the name and parent works:
>>> f.__name__
'func'
>>> f.__parent__
<Funcs one>
In this case, the setters are used, if the name and parent are changed:
>>> f.__name__ = 'func2'
>>> f.__name__
'func2'
>>> f.name
'func2'
>>> f.__parent__ = Funcs('two')
>>> f.__parent__
<Funcs two>
>>> f.parent
<Funcs two>
"""


def doctest_MongoContained_mixed():
"""MongoContained: mixed usage
When the container is stored in the ZODB or another persistence mechanism,
a mixed usage of proxy attributes and getter/setter functions is the best
appraoch.
>>> class Mixers(btree.BTreeContainer):
... def __init__(self, name):
... super(Mixers, self).__init__()
... self.name = name
... def __repr__(self):
... return '<Mixers %s>' %self.name
>>> mixers = Mixers('one')
>>> class Mixer(container.MongoContained, persistent.Persistent):
... _m_name_attr = 'name'
... _m_parent_getter = lambda s: mixers
... def __init__(self, name):
... self.name = name
Let's create a mixer component and activate the persistence machinery:
>>> m = Mixer('mixer')
>>> m._p_jar = dm
So accessing the name and parent works:
>>> m.__name__
'mixer'
>>> m.__parent__
<Mixers one>
"""


def doctest_SimpleMongoContainer_basic():
"""SimpleMongoContainer: basic
Expand Down

0 comments on commit 0a211f5

Please sign in to comment.