[Checkins] SVN: mongopersist/trunk/ - Feature: The new ``IdNamesMongoContainer`` class uses the natural Mongo
Stephen Richter
cvs-admin at zope.org
Mon Apr 2 15:10:18 UTC 2012
Log message for revision 124891:
- Feature: The new ``IdNamesMongoContainer`` class uses the natural Mongo
ObjectId as the name/key for the items in the container. No more messing
around with coming up or generating a name. Of course, if you specified
``None`` as a key in the past, it already used the object id, but it was
sotred again in the mapping key field. Now the object id is used directly
everywhere.
Changed:
U mongopersist/trunk/CHANGES.txt
U mongopersist/trunk/src/mongopersist/testing.py
U mongopersist/trunk/src/mongopersist/tests/test_datamanager.py
U mongopersist/trunk/src/mongopersist/zope/container.py
U mongopersist/trunk/src/mongopersist/zope/tests/test_container.py
-=-
Modified: mongopersist/trunk/CHANGES.txt
===================================================================
--- mongopersist/trunk/CHANGES.txt 2012-04-02 14:59:09 UTC (rev 124890)
+++ mongopersist/trunk/CHANGES.txt 2012-04-02 15:10:15 UTC (rev 124891)
@@ -27,6 +27,13 @@
``conflict_handler_factory`` to the data manager constructor. The factory
needs to expect on argument, the data manager.
+- Feature: The new ``IdNamesMongoContainer`` class uses the natural Mongo
+ ObjectId as the name/key for the items in the container. No more messing
+ around with coming up or generating a name. Of course, if you specified
+ ``None`` as a key in the past, it already used the object id, but it was
+ sotred again in the mapping key field. Now the object id is used directly
+ everywhere.
+
- Feature: Whenever ``setattr()`` is called on a persistent object, it is
marked as changed even if the new value equals the old one. To minimize
writes to MongoDB, the latest database state is compared to the new state
@@ -54,7 +61,7 @@
accomplished by removing unnecessary database accesses, better caching and
more efficient algorithms. This results in speedups between 4-25 times.
- - When resolving the path to a klass, the result is now cached. More
+ - When resolving the path to a class, the result is now cached. More
importantly, lookup failures are also cached mapping path ->
``None``. This is important, since an optimization the ``resolve()``
method causes a lot of failing lookups.
Modified: mongopersist/trunk/src/mongopersist/testing.py
===================================================================
--- mongopersist/trunk/src/mongopersist/testing.py 2012-04-02 14:59:09 UTC (rev 124890)
+++ mongopersist/trunk/src/mongopersist/testing.py 2012-04-02 15:10:15 UTC (rev 124891)
@@ -24,8 +24,10 @@
checker = renormalizing.RENormalizing([
(re.compile(r'datetime.datetime(.*)'),
'datetime.datetime(2011, 10, 1, 9, 45)'),
- (re.compile(r"ObjectId\('[0-9a-f]*'\)"),
+ (re.compile(r"ObjectId\('[0-9a-f]{24}'\)"),
"ObjectId('4e7ddf12e138237403000000')"),
+ (re.compile(r"u'[0-9a-f]{24}'"),
+ "u'4e7ddf12e138237403000000'"),
(re.compile(r"object at 0x[0-9a-f]*>"),
"object at 0x001122>"),
])
Modified: mongopersist/trunk/src/mongopersist/tests/test_datamanager.py
===================================================================
--- mongopersist/trunk/src/mongopersist/tests/test_datamanager.py 2012-04-02 14:59:09 UTC (rev 124890)
+++ mongopersist/trunk/src/mongopersist/tests/test_datamanager.py 2012-04-02 15:10:15 UTC (rev 124891)
@@ -671,7 +671,8 @@
>>> foo_A = dm.load(foo_ref)
>>> foo_A.name = u'1'
>>> coll.find_one({})
- {u'_id': ObjectId('4e7dd'), u'_py_serial': 1, u'name': u'one'}
+ {u'_id': ObjectId('4e7ddf12e138237403000000'),
+ u'_py_serial': 1, u'name': u'one'}
2. Transaction B comes along and modifies the object as well and commits:
@@ -684,14 +685,16 @@
>>> foo_B.name = 'Eins'
>>> dm_B.tpc_finish(None)
>>> coll.find_one({})
- {u'_id': ObjectId('4e7dd'), u'_py_serial': 2, u'name': u'Eins'}
+ {u'_id': ObjectId('4e7ddf12e138237403000000'), u'_py_serial': 2,
+ u'name': u'Eins'}
3. If transcation A is later aborted, it does not reset the state, since
it changed:
>>> dm.abort(None)
>>> coll.find_one({})
- {u'_id': ObjectId('4e7dd'), u'_py_serial': 2, u'name': u'Eins'}
+ {u'_id': ObjectId('4e7ddf12e138237403000000'), u'_py_serial': 2,
+ u'name': u'Eins'}
"""
Modified: mongopersist/trunk/src/mongopersist/zope/container.py
===================================================================
--- mongopersist/trunk/src/mongopersist/zope/container.py 2012-04-02 14:59:09 UTC (rev 124890)
+++ mongopersist/trunk/src/mongopersist/zope/container.py 2012-04-02 15:10:15 UTC (rev 124891)
@@ -15,7 +15,9 @@
import UserDict
import persistent
import pymongo.dbref
+import pymongo.objectid
import zope.component
+from bson.errors import InvalidId
from rwproperty import getproperty, setproperty
from zope.container import contained, sample
from zope.container.interfaces import IContainer
@@ -154,6 +156,10 @@
if key not in filter:
filter[key] = value
+ def _locate(self, obj, doc):
+ obj._v_key = doc[self._m_mapping_key]
+ obj._v_parent = self
+
def _load_one(self, doc):
# Create a DBRef object and then load the full state of the object.
dbref = pymongo.dbref.DBRef(
@@ -162,22 +168,21 @@
# Stick the doc into the _latest_states:
self._m_jar._latest_states[dbref] = doc
obj = self._m_jar.load(dbref)
- obj._v_key = doc[self._m_mapping_key]
- obj._v_parent = self
+ self._locate(obj, doc)
return obj
def __getitem__(self, key):
filter = self._m_get_items_filter()
filter[self._m_mapping_key] = key
- coll = self.get_collection()
- doc = coll.find_one(filter)
- if doc is None:
+ obj = self.find_one(filter)
+ if obj is None:
raise KeyError(key)
- return self._load_one(doc)
+ return obj
def _real_setitem(self, key, value):
# This call by iteself caues the state to change _p_changed to True.
- setattr(value, self._m_mapping_key, key)
+ if self._m_mapping_key is not None:
+ setattr(value, self._m_mapping_key, key)
if self._m_parent_key is not None:
setattr(value, self._m_parent_key, self._m_get_parent_key_value())
@@ -259,6 +264,53 @@
return None
return self._load_one(doc)
+
+class IdNamesMongoContainer(MongoContainer):
+ """A container that uses the Mongo ObjectId as the name/key."""
+ _m_mapping_key = None
+
+
+ @property
+ def _m_remove_documents(self):
+ # Objects must be removed, since removing the _id of a document is not
+ # allowed.
+ return True
+
+ def _locate(self, obj, doc):
+ obj._v_key = unicode(doc['_id'])
+ obj._v_parent = self
+
+ def __getitem__(self, key):
+ try:
+ id = pymongo.objectid.ObjectId(key)
+ except InvalidId:
+ raise KeyError(key)
+ filter = self._m_get_items_filter()
+ filter['_id'] = id
+ obj = self.find_one(filter)
+ if obj is None:
+ raise KeyError(key)
+ return obj
+
+ def __contains__(self, key):
+ try:
+ id = pymongo.objectid.ObjectId(key)
+ except InvalidId:
+ return False
+ return self.raw_find_one({'_id': id}, fields=()) is not None
+
+ def __iter__(self):
+ result = self.raw_find(fields=None)
+ for doc in result:
+ yield unicode(doc['_id'])
+
+ def iteritems(self):
+ result = self.raw_find()
+ for doc in result:
+ obj = self._load_one(doc)
+ yield unicode(doc['_id']), obj
+
+
class AllItemsMongoContainer(MongoContainer):
_m_parent_key = None
Modified: mongopersist/trunk/src/mongopersist/zope/tests/test_container.py
===================================================================
--- mongopersist/trunk/src/mongopersist/zope/tests/test_container.py 2012-04-02 14:59:09 UTC (rev 124890)
+++ mongopersist/trunk/src/mongopersist/zope/tests/test_container.py 2012-04-02 15:10:15 UTC (rev 124891)
@@ -447,6 +447,89 @@
<Person Stephan>
"""
+def doctest_IdNamesMongoContainer_basic():
+ """IdNamesMongoContainer: basic
+
+ This container uses the Mongo ObjectId as the name for each object. Since
+ ObjectIds are required to be unique within a collection, this is actually
+ a nice and cheap scenario.
+
+ Let's add a container to the root:
+
+ >>> transaction.commit()
+ >>> dm.root['c'] = container.IdNamesMongoContainer('person')
+
+ Let's now add a new person:
+
+ >>> dm.root['c'].add(Person(u'Stephan'))
+ >>> keys = dm.root['c'].keys()
+ >>> keys
+ [u'4e7ddf12e138237403000003']
+ >>> name = keys[0]
+ >>> dm.root['c'][name]
+ <Person Stephan>
+
+ >>> dm.root['c'].values()
+ [<Person Stephan>]
+
+ >>> dm.root['c'][name].__parent__
+ <mongopersist.zope.container.IdNamesMongoContainer object at 0x7fec50f86500>
+ >>> dm.root['c'][name].__name__
+ u'4e7ddf12e138237403000003'
+
+ It is a feature of the container that the item is immediately available
+ after assignment, but before the data is stored in the database. Let's
+ commit and access the data again:
+
+ >>> transaction.commit()
+
+ >>> db = dm._conn[DBNAME]
+ >>> pprint(list(db['person'].find()))
+ [{u'_id': ObjectId('4e7e9d3ae138232d7b000003'),
+ u'name': u'Stephan',
+ u'parent': DBRef(u'mongopersist.zope.container.IdNamesMongoContainer',
+ ObjectId('4e7e9d3ae138232d7b000000'),
+ u'mongopersist_container_test')}]
+
+ Notice how there is no "key" entry in the document. We get a usual key
+ error, if an object does not exist:
+
+ >>> dm.root['c']['4e7e9d3ae138232d7b000fff']
+ Traceback (most recent call last):
+ ...
+ KeyError: '4e7e9d3ae138232d7b000fff'
+
+ >>> '4e7e9d3ae138232d7b000fff' in dm.root['c']
+ False
+
+ >>> dm.root['c']['roy']
+ Traceback (most recent call last):
+ ...
+ KeyError: 'roy'
+
+ >>> 'roy' in dm.root['c']
+ False
+
+ Now remove the item:
+
+ >>> del dm.root['c'][name]
+
+ The changes are immediately visible.
+
+ >>> dm.root['c'].keys()
+ []
+ >>> dm.root['c'][name]
+ Traceback (most recent call last):
+ ...
+ KeyError: u'4e7e9d3ae138232d7b000003'
+
+ Make sure it is really gone after committing:
+
+ >>> transaction.commit()
+ >>> dm.root['c'].keys()
+ []
+ """
+
def doctest_AllItemsMongoContainer_basic():
"""AllItemsMongoContainer: basic
@@ -601,8 +684,10 @@
checker = renormalizing.RENormalizing([
(re.compile(r'datetime.datetime(.*)'),
'datetime.datetime(2011, 10, 1, 9, 45)'),
- (re.compile(r"ObjectId\('[0-9a-f]*'\)"),
+ (re.compile(r"ObjectId\('[0-9a-f]{24}'\)"),
"ObjectId('4e7ddf12e138237403000000')"),
+ (re.compile(r"u'[0-9a-f]{24}'"),
+ "u'4e7ddf12e138237403000000'"),
(re.compile(r"object at 0x[0-9a-f]*>"),
"object at 0x001122>"),
(re.compile(r"zodb-[0-9a-f].*"),
More information about the checkins
mailing list