[Checkins] SVN: mongopersist/trunk/ Initial import of mongopersist code. Here is an except fromt he README:

Stephan Richter srichter at gmail.com
Fri Nov 4 17:21:00 UTC 2011


Log message for revision 123285:
  Initial import of mongopersist code. Here is an except fromt he README:
  
  ``mongopersist`` is a Mongo storage implementation for persistent
  Python objects. It is *not* a storage for the ZODB.
  
  The goal of ``mongopersist`` is to provide a data manager that serializes
  objects to Mongo at transaction boundaries. The mongo data manager is a
  persistent data manager, which handles events at transaction boundaries (see
  ``transaction.interfaces.IDataManager``) as well as events from the
  persistency framework (see ``persistent.interfaces.IPersistentDataManager``).
  
  

Changed:
  A   mongopersist/trunk/
  A   mongopersist/trunk/.installed.cfg
  A   mongopersist/trunk/bin/
  A   mongopersist/trunk/bin/buildout
  A   mongopersist/trunk/bin/coverage-report
  A   mongopersist/trunk/bin/coverage-test
  A   mongopersist/trunk/bin/python
  A   mongopersist/trunk/bin/test
  A   mongopersist/trunk/bootstrap.py
  A   mongopersist/trunk/buildout.cfg
  A   mongopersist/trunk/coverage/
  A   mongopersist/trunk/coverage/mongopersist.__init__.cover
  A   mongopersist/trunk/coverage/mongopersist.datamanager.cover
  A   mongopersist/trunk/coverage/mongopersist.interfaces.cover
  A   mongopersist/trunk/coverage/mongopersist.mapping.cover
  A   mongopersist/trunk/coverage/mongopersist.serialize.cover
  A   mongopersist/trunk/coverage/mongopersist.testing.cover
  A   mongopersist/trunk/coverage/mongopersist.tests.__init__.cover
  A   mongopersist/trunk/coverage/mongopersist.tests.test_datamanager.cover
  A   mongopersist/trunk/coverage/mongopersist.tests.test_doc.cover
  A   mongopersist/trunk/coverage/mongopersist.tests.test_mapping.cover
  A   mongopersist/trunk/coverage/mongopersist.tests.test_serialize.cover
  A   mongopersist/trunk/coverage/mongopersist.zope.__init__.cover
  A   mongopersist/trunk/coverage/mongopersist.zope.container.cover
  A   mongopersist/trunk/coverage/mongopersist.zope.tests.__init__.cover
  A   mongopersist/trunk/coverage/mongopersist.zope.tests.test_container.cover
  A   mongopersist/trunk/coverage/report/
  A   mongopersist/trunk/coverage/report/all.html
  A   mongopersist/trunk/coverage/report/mongopersist.__init__.html
  A   mongopersist/trunk/coverage/report/mongopersist.datamanager.html
  A   mongopersist/trunk/coverage/report/mongopersist.html
  A   mongopersist/trunk/coverage/report/mongopersist.interfaces.html
  A   mongopersist/trunk/coverage/report/mongopersist.mapping.html
  A   mongopersist/trunk/coverage/report/mongopersist.serialize.html
  A   mongopersist/trunk/coverage/report/mongopersist.testing.html
  A   mongopersist/trunk/coverage/report/mongopersist.zope.__init__.html
  A   mongopersist/trunk/coverage/report/mongopersist.zope.container.html
  A   mongopersist/trunk/coverage/report/mongopersist.zope.html
  A   mongopersist/trunk/develop-eggs/
  A   mongopersist/trunk/develop-eggs/mongopersist.egg-link
  A   mongopersist/trunk/dist/
  A   mongopersist/trunk/dist/mongopersist-0.1dev.tar.gz
  A   mongopersist/trunk/parts/
  A   mongopersist/trunk/parts/buildout/
  A   mongopersist/trunk/parts/buildout/site.py
  A   mongopersist/trunk/parts/buildout/site.pyo
  A   mongopersist/trunk/parts/buildout/sitecustomize.py
  A   mongopersist/trunk/parts/buildout/sitecustomize.pyo
  A   mongopersist/trunk/parts/coverage-report/
  A   mongopersist/trunk/parts/coverage-report/site.py
  A   mongopersist/trunk/parts/coverage-report/sitecustomize.py
  A   mongopersist/trunk/parts/coverage-test/
  A   mongopersist/trunk/parts/coverage-test/site-packages/
  A   mongopersist/trunk/parts/coverage-test/site-packages/site.py
  A   mongopersist/trunk/parts/coverage-test/site-packages/sitecustomize.py
  A   mongopersist/trunk/parts/coverage-test/working-directory/
  A   mongopersist/trunk/parts/python/
  A   mongopersist/trunk/parts/python/site.py
  A   mongopersist/trunk/parts/python/sitecustomize.py
  A   mongopersist/trunk/parts/test/
  A   mongopersist/trunk/parts/test/site-packages/
  A   mongopersist/trunk/parts/test/site-packages/site.py
  A   mongopersist/trunk/parts/test/site-packages/sitecustomize.py
  A   mongopersist/trunk/parts/test/working-directory/
  A   mongopersist/trunk/setup.py
  A   mongopersist/trunk/src/
  A   mongopersist/trunk/src/mongopersist/
  A   mongopersist/trunk/src/mongopersist/README.txt
  A   mongopersist/trunk/src/mongopersist/__init__.py
  A   mongopersist/trunk/src/mongopersist/datamanager.py
  A   mongopersist/trunk/src/mongopersist/interfaces.py
  A   mongopersist/trunk/src/mongopersist/mapping.py
  A   mongopersist/trunk/src/mongopersist/pool.py
  A   mongopersist/trunk/src/mongopersist/serialize.py
  A   mongopersist/trunk/src/mongopersist/serializers.py
  A   mongopersist/trunk/src/mongopersist/testing.py
  A   mongopersist/trunk/src/mongopersist/tests/
  A   mongopersist/trunk/src/mongopersist/tests/__init__.py
  A   mongopersist/trunk/src/mongopersist/tests/test_datamanager.py
  A   mongopersist/trunk/src/mongopersist/tests/test_doc.py
  A   mongopersist/trunk/src/mongopersist/tests/test_mapping.py
  A   mongopersist/trunk/src/mongopersist/tests/test_serialize.py
  A   mongopersist/trunk/src/mongopersist/zope/
  A   mongopersist/trunk/src/mongopersist/zope/__init__.py
  A   mongopersist/trunk/src/mongopersist/zope/container.py
  A   mongopersist/trunk/src/mongopersist/zope/schema.py
  A   mongopersist/trunk/src/mongopersist/zope/tests/
  A   mongopersist/trunk/src/mongopersist/zope/tests/__init__.py
  A   mongopersist/trunk/src/mongopersist/zope/tests/test_container.py
  A   mongopersist/trunk/src/mongopersist.egg-info/
  A   mongopersist/trunk/src/mongopersist.egg-info/PKG-INFO
  A   mongopersist/trunk/src/mongopersist.egg-info/SOURCES.txt
  A   mongopersist/trunk/src/mongopersist.egg-info/dependency_links.txt
  A   mongopersist/trunk/src/mongopersist.egg-info/not-zip-safe
  A   mongopersist/trunk/src/mongopersist.egg-info/requires.txt
  A   mongopersist/trunk/src/mongopersist.egg-info/top_level.txt
  A   mongopersist/trunk/versions.cfg

-=-
Added: mongopersist/trunk/.installed.cfg
===================================================================
--- mongopersist/trunk/.installed.cfg	                        (rev 0)
+++ mongopersist/trunk/.installed.cfg	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,101 @@
+[buildout]
+installed_develop_eggs = 
+parts = test python coverage-test coverage-report
+
+[test]
+__buildout_installed__ = /opt/zope/cipherhealth/packages/mongopersist/parts/test/site-packages/sitecustomize.py
+	/opt/zope/cipherhealth/packages/mongopersist/parts/test/site-packages/site.py
+	/opt/zope/cipherhealth/packages/mongopersist/bin/test
+__buildout_signature__ = zc.recipe.testrunner-1.4.0-py2.6.egg z3c.recipe.scripts-1.0.1-py2.6.egg setuptools-0.6c12dev_r88846-py2.6.egg zope.testrunner-4.0.3-py2.6.egg zc.buildout-1.5.2-py2.6.egg zc.recipe.egg-1.3.2-py2.6.egg zope.interface-3.6.3-py2.6-linux-x86_64.egg zope.exceptions-3.6.1-py2.6.egg
+_b = /opt/zope/cipherhealth/packages/mongopersist/bin
+_d = /opt/zope/cipherhealth/packages/mongopersist/develop-eggs
+_e = /opt/zope/packages/eggs
+allowed-eggs-from-site-packages = *
+bin-directory = /opt/zope/cipherhealth/packages/mongopersist/bin
+defaults = ['--tests-pattern', '^f?tests$$', '-v']
+develop-eggs-directory = /opt/zope/cipherhealth/packages/mongopersist/develop-eggs
+eggs = mongopersist [test, zope]
+eggs-directory = /opt/zope/packages/eggs
+exec-sitecustomize = true
+executable = /usr/bin/py26
+include-site-packages = false
+index = http://pypi.python.org/simple
+location = /opt/zope/cipherhealth/packages/mongopersist/parts/test/working-directory
+parts-directory = /opt/zope/cipherhealth/packages/mongopersist/parts/test
+python = buildout
+recipe = zc.recipe.testrunner
+script = /opt/zope/cipherhealth/packages/mongopersist/bin/test
+
+[python]
+__buildout_installed__ = /opt/zope/cipherhealth/packages/mongopersist/parts/python/sitecustomize.py
+	/opt/zope/cipherhealth/packages/mongopersist/parts/python/site.py
+	/opt/zope/cipherhealth/packages/mongopersist/bin/python
+__buildout_signature__ = z3c.recipe.scripts-1.0.1-py2.6.egg setuptools-0.6c12dev_r88846-py2.6.egg zc.recipe.egg-1.3.2-py2.6.egg zc.buildout-1.5.2-py2.6.egg
+_b = /opt/zope/cipherhealth/packages/mongopersist/bin
+_d = /opt/zope/cipherhealth/packages/mongopersist/develop-eggs
+_e = /opt/zope/packages/eggs
+allowed-eggs-from-site-packages = *
+bin-directory = /opt/zope/cipherhealth/packages/mongopersist/bin
+develop-eggs-directory = /opt/zope/cipherhealth/packages/mongopersist/develop-eggs
+eggs = mongopersist [test, zope]
+eggs-directory = /opt/zope/packages/eggs
+exec-sitecustomize = true
+executable = /usr/bin/py26
+include-site-packages = false
+index = http://pypi.python.org/simple
+interpreter = python
+parts-directory = /opt/zope/cipherhealth/packages/mongopersist/parts/python
+python = buildout
+recipe = z3c.recipe.scripts
+
+[coverage-test]
+__buildout_installed__ = /opt/zope/cipherhealth/packages/mongopersist/parts/coverage-test
+	/opt/zope/cipherhealth/packages/mongopersist/parts/coverage-test/site-packages
+	/opt/zope/cipherhealth/packages/mongopersist/parts/coverage-test/working-directory
+	/opt/zope/cipherhealth/packages/mongopersist/parts/coverage-test/site-packages/sitecustomize.py
+	/opt/zope/cipherhealth/packages/mongopersist/parts/coverage-test/site-packages/site.py
+	/opt/zope/cipherhealth/packages/mongopersist/bin/coverage-test
+__buildout_signature__ = zc.recipe.testrunner-1.4.0-py2.6.egg z3c.recipe.scripts-1.0.1-py2.6.egg setuptools-0.6c12dev_r88846-py2.6.egg zope.testrunner-4.0.3-py2.6.egg zc.buildout-1.5.2-py2.6.egg zc.recipe.egg-1.3.2-py2.6.egg zope.interface-3.6.3-py2.6-linux-x86_64.egg zope.exceptions-3.6.1-py2.6.egg
+_b = /opt/zope/cipherhealth/packages/mongopersist/bin
+_d = /opt/zope/cipherhealth/packages/mongopersist/develop-eggs
+_e = /opt/zope/packages/eggs
+allowed-eggs-from-site-packages = *
+bin-directory = /opt/zope/cipherhealth/packages/mongopersist/bin
+defaults = ['--coverage', '/opt/zope/cipherhealth/packages/mongopersist/coverage']
+develop-eggs-directory = /opt/zope/cipherhealth/packages/mongopersist/develop-eggs
+eggs = mongopersist [test, zope]
+eggs-directory = /opt/zope/packages/eggs
+exec-sitecustomize = true
+executable = /usr/bin/py26
+include-site-packages = false
+index = http://pypi.python.org/simple
+location = /opt/zope/cipherhealth/packages/mongopersist/parts/coverage-test/working-directory
+parts-directory = /opt/zope/cipherhealth/packages/mongopersist/parts/coverage-test
+python = buildout
+recipe = zc.recipe.testrunner
+script = /opt/zope/cipherhealth/packages/mongopersist/bin/coverage-test
+
+[coverage-report]
+__buildout_installed__ = /opt/zope/cipherhealth/packages/mongopersist/parts/coverage-report
+	/opt/zope/cipherhealth/packages/mongopersist/parts/coverage-report/sitecustomize.py
+	/opt/zope/cipherhealth/packages/mongopersist/parts/coverage-report/site.py
+	/opt/zope/cipherhealth/packages/mongopersist/bin/coverage-report
+__buildout_signature__ = z3c.recipe.scripts-1.0.1-py2.6.egg setuptools-0.6c12dev_r88846-py2.6.egg zc.recipe.egg-1.3.2-py2.6.egg zc.buildout-1.5.2-py2.6.egg
+_b = /opt/zope/cipherhealth/packages/mongopersist/bin
+_d = /opt/zope/cipherhealth/packages/mongopersist/develop-eggs
+_e = /opt/zope/packages/eggs
+allowed-eggs-from-site-packages = *
+arguments = ('/opt/zope/cipherhealth/packages/mongopersist/coverage',
+	'/opt/zope/cipherhealth/packages/mongopersist/coverage/report')
+bin-directory = /opt/zope/cipherhealth/packages/mongopersist/bin
+develop-eggs-directory = /opt/zope/cipherhealth/packages/mongopersist/develop-eggs
+eggs = z3c.coverage
+eggs-directory = /opt/zope/packages/eggs
+exec-sitecustomize = true
+executable = /usr/bin/py26
+include-site-packages = false
+index = http://pypi.python.org/simple
+parts-directory = /opt/zope/cipherhealth/packages/mongopersist/parts/coverage-report
+python = buildout
+recipe = z3c.recipe.scripts
+scripts = coveragereport=coverage-report

Added: mongopersist/trunk/bin/buildout
===================================================================
--- mongopersist/trunk/bin/buildout	                        (rev 0)
+++ mongopersist/trunk/bin/buildout	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,20 @@
+#!/usr/bin/py26 -S
+
+import sys
+sys.path[0:0] = [
+    '/opt/zope/cipherhealth/packages/mongopersist/parts/buildout',
+    ]
+
+
+import os
+path = sys.path[0]
+if os.environ.get('PYTHONPATH'):
+    path = os.pathsep.join([path, os.environ['PYTHONPATH']])
+os.environ['BUILDOUT_ORIGINAL_PYTHONPATH'] = os.environ.get('PYTHONPATH', '')
+os.environ['PYTHONPATH'] = path
+import site # imports custom buildout-generated site.py
+
+import zc.buildout.buildout
+
+if __name__ == '__main__':
+    zc.buildout.buildout.main()


Property changes on: mongopersist/trunk/bin/buildout
___________________________________________________________________
Added: svn:executable
   + 

Added: mongopersist/trunk/bin/coverage-report
===================================================================
--- mongopersist/trunk/bin/coverage-report	                        (rev 0)
+++ mongopersist/trunk/bin/coverage-report	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,21 @@
+#!/usr/bin/py26 -S
+
+import sys
+sys.path[0:0] = [
+    '/opt/zope/cipherhealth/packages/mongopersist/parts/coverage-report',
+    ]
+
+
+import os
+path = sys.path[0]
+if os.environ.get('PYTHONPATH'):
+    path = os.pathsep.join([path, os.environ['PYTHONPATH']])
+os.environ['BUILDOUT_ORIGINAL_PYTHONPATH'] = os.environ.get('PYTHONPATH', '')
+os.environ['PYTHONPATH'] = path
+import site # imports custom buildout-generated site.py
+
+import z3c.coverage.coveragereport
+
+if __name__ == '__main__':
+    z3c.coverage.coveragereport.main(('/opt/zope/cipherhealth/packages/mongopersist/coverage',
+'/opt/zope/cipherhealth/packages/mongopersist/coverage/report'))


Property changes on: mongopersist/trunk/bin/coverage-report
___________________________________________________________________
Added: svn:executable
   + 

Added: mongopersist/trunk/bin/coverage-test
===================================================================
--- mongopersist/trunk/bin/coverage-test	                        (rev 0)
+++ mongopersist/trunk/bin/coverage-test	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,25 @@
+#!/usr/bin/py26 -S
+
+import sys
+sys.path[0:0] = [
+    '/opt/zope/cipherhealth/packages/mongopersist/parts/coverage-test/site-packages',
+    ]
+
+
+import os
+path = sys.path[0]
+if os.environ.get('PYTHONPATH'):
+    path = os.pathsep.join([path, os.environ['PYTHONPATH']])
+os.environ['BUILDOUT_ORIGINAL_PYTHONPATH'] = os.environ.get('PYTHONPATH', '')
+os.environ['PYTHONPATH'] = path
+import site # imports custom buildout-generated site.py
+import os
+sys.argv[0] = os.path.abspath(sys.argv[0])
+os.chdir('/opt/zope/cipherhealth/packages/mongopersist/parts/coverage-test/working-directory')
+
+import zope.testrunner
+
+if __name__ == '__main__':
+    zope.testrunner.run((['--coverage', '/opt/zope/cipherhealth/packages/mongopersist/coverage']) + [
+        '--test-path', '/opt/zope/cipherhealth/packages/mongopersist/src',
+        ])


Property changes on: mongopersist/trunk/bin/coverage-test
___________________________________________________________________
Added: svn:executable
   + 

Added: mongopersist/trunk/bin/python
===================================================================
--- mongopersist/trunk/bin/python	                        (rev 0)
+++ mongopersist/trunk/bin/python	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,11 @@
+#!/usr/bin/py26 -S
+import os
+import sys
+
+argv = [sys.executable] + sys.argv[1:]
+environ = os.environ.copy()
+path = '/opt/zope/cipherhealth/packages/mongopersist/parts/python'
+if environ.get('PYTHONPATH'):
+    path = os.pathsep.join([path, environ['PYTHONPATH']])
+environ['PYTHONPATH'] = path
+os.execve(sys.executable, argv, environ)


Property changes on: mongopersist/trunk/bin/python
___________________________________________________________________
Added: svn:executable
   + 

Added: mongopersist/trunk/bin/test
===================================================================
--- mongopersist/trunk/bin/test	                        (rev 0)
+++ mongopersist/trunk/bin/test	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,25 @@
+#!/usr/bin/py26 -S
+
+import sys
+sys.path[0:0] = [
+    '/opt/zope/cipherhealth/packages/mongopersist/parts/test/site-packages',
+    ]
+
+
+import os
+path = sys.path[0]
+if os.environ.get('PYTHONPATH'):
+    path = os.pathsep.join([path, os.environ['PYTHONPATH']])
+os.environ['BUILDOUT_ORIGINAL_PYTHONPATH'] = os.environ.get('PYTHONPATH', '')
+os.environ['PYTHONPATH'] = path
+import site # imports custom buildout-generated site.py
+import os
+sys.argv[0] = os.path.abspath(sys.argv[0])
+os.chdir('/opt/zope/cipherhealth/packages/mongopersist/parts/test/working-directory')
+
+import zope.testrunner
+
+if __name__ == '__main__':
+    zope.testrunner.run((['--tests-pattern', '^f?tests$$', '-v']) + [
+        '--test-path', '/opt/zope/cipherhealth/packages/mongopersist/src',
+        ])


Property changes on: mongopersist/trunk/bin/test
___________________________________________________________________
Added: svn:executable
   + 

Added: mongopersist/trunk/bootstrap.py
===================================================================
--- mongopersist/trunk/bootstrap.py	                        (rev 0)
+++ mongopersist/trunk/bootstrap.py	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,259 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Bootstrap a buildout-based project
+
+Simply run this script in a directory containing a buildout.cfg.
+The script accepts buildout command-line options, so you can
+use the -c option to specify an alternate configuration file.
+"""
+SETUPTOOLS_VERSION = '0.6c11'
+
+import os, shutil, sys, tempfile, textwrap, urllib, urllib2, subprocess
+from optparse import OptionParser
+
+if sys.platform == 'win32':
+    def quote(c):
+        if ' ' in c:
+            return '"%s"' % c # work around spawn lamosity on windows
+        else:
+            return c
+else:
+    quote = str
+
+# See zc.buildout.easy_install._has_broken_dash_S for motivation and comments.
+stdout, stderr = subprocess.Popen(
+    [sys.executable, '-Sc',
+     'try:\n'
+     '    import ConfigParser\n'
+     'except ImportError:\n'
+     '    print 1\n'
+     'else:\n'
+     '    print 0\n'],
+    stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
+has_broken_dash_S = bool(int(stdout.strip()))
+
+# In order to be more robust in the face of system Pythons, we want to
+# run without site-packages loaded.  This is somewhat tricky, in
+# particular because Python 2.6's distutils imports site, so starting
+# with the -S flag is not sufficient.  However, we'll start with that:
+if not has_broken_dash_S and 'site' in sys.modules:
+    # We will restart with python -S.
+    args = sys.argv[:]
+    args[0:0] = [sys.executable, '-S']
+    args = map(quote, args)
+    os.execv(sys.executable, args)
+# Now we are running with -S.  We'll get the clean sys.path, import site
+# because distutils will do it later, and then reset the path and clean
+# out any namespace packages from site-packages that might have been
+# loaded by .pth files.
+clean_path = sys.path[:]
+import site
+sys.path[:] = clean_path
+for k, v in sys.modules.items():
+    if (hasattr(v, '__path__') and
+        len(v.__path__)==1 and
+        not os.path.exists(os.path.join(v.__path__[0],'__init__.py'))):
+        # This is a namespace package.  Remove it.
+        sys.modules.pop(k)
+
+is_jython = sys.platform.startswith('java')
+
+setuptools_source = 'http://peak.telecommunity.com/dist/ez_setup.py'
+distribute_source = 'http://python-distribute.org/distribute_setup.py'
+
+# parsing arguments
+def normalize_to_url(option, opt_str, value, parser):
+    if value:
+        if '://' not in value: # It doesn't smell like a URL.
+            value = 'file://%s' % (
+                urllib.pathname2url(
+                    os.path.abspath(os.path.expanduser(value))),)
+        if opt_str == '--download-base' and not value.endswith('/'):
+            # Download base needs a trailing slash to make the world happy.
+            value += '/'
+    else:
+        value = None
+    name = opt_str[2:].replace('-', '_')
+    setattr(parser.values, name, value)
+
+usage = '''\
+[DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options]
+
+Bootstraps a buildout-based project.
+
+Simply run this script in a directory containing a buildout.cfg, using the
+Python that you want bin/buildout to use.
+
+Note that by using --setup-source and --download-base to point to
+local resources, you can keep this script from going over the network.
+'''
+
+parser = OptionParser(usage=usage)
+parser.add_option("-v", "--version", dest="version",
+                          help="use a specific zc.buildout version")
+parser.add_option("-d", "--distribute",
+                   action="store_true", dest="use_distribute", default=False,
+                   help="Use Distribute rather than Setuptools.")
+parser.add_option("--setup-source", action="callback", dest="setup_source",
+                  callback=normalize_to_url, nargs=1, type="string",
+                  help=("Specify a URL or file location for the setup file. "
+                        "If you use Setuptools, this will default to " +
+                        setuptools_source + "; if you use Distribute, this "
+                        "will default to " + distribute_source +"."))
+parser.add_option("--download-base", action="callback", dest="download_base",
+                  callback=normalize_to_url, nargs=1, type="string",
+                  help=("Specify a URL or directory for downloading "
+                        "zc.buildout and either Setuptools or Distribute. "
+                        "Defaults to PyPI."))
+parser.add_option("--eggs",
+                  help=("Specify a directory for storing eggs.  Defaults to "
+                        "a temporary directory that is deleted when the "
+                        "bootstrap script completes."))
+parser.add_option("-t", "--accept-buildout-test-releases",
+                  dest='accept_buildout_test_releases',
+                  action="store_true", default=False,
+                  help=("Normally, if you do not specify a --version, the "
+                        "bootstrap script and buildout gets the newest "
+                        "*final* versions of zc.buildout and its recipes and "
+                        "extensions for you.  If you use this flag, "
+                        "bootstrap and buildout will get the newest releases "
+                        "even if they are alphas or betas."))
+parser.add_option("-c", None, action="store", dest="config_file",
+                   help=("Specify the path to the buildout configuration "
+                         "file to be used."))
+
+options, args = parser.parse_args()
+
+# if -c was provided, we push it back into args for buildout's main function
+if options.config_file is not None:
+    args += ['-c', options.config_file]
+
+if options.eggs:
+    eggs_dir = os.path.abspath(os.path.expanduser(options.eggs))
+else:
+    eggs_dir = tempfile.mkdtemp()
+
+if options.setup_source is None:
+    if options.use_distribute:
+        options.setup_source = distribute_source
+    else:
+        options.setup_source = setuptools_source
+
+if options.accept_buildout_test_releases:
+    args.append('buildout:accept-buildout-test-releases=true')
+args.append('bootstrap')
+
+try:
+    import pkg_resources
+    import setuptools # A flag.  Sometimes pkg_resources is installed alone.
+    if not hasattr(pkg_resources, '_distribute'):
+        raise ImportError
+except ImportError:
+    ez_code = urllib2.urlopen(
+        options.setup_source).read().replace('\r\n', '\n')
+    ez = {}
+    exec ez_code in ez
+    setup_args = dict(version=SETUPTOOLS_VERSION, to_dir=eggs_dir, download_delay=0)
+    if options.download_base:
+        setup_args['download_base'] = options.download_base
+    if options.use_distribute:
+        setup_args['no_fake'] = True
+    ez['use_setuptools'](**setup_args)
+    reload(sys.modules['pkg_resources'])
+    import pkg_resources
+    # This does not (always?) update the default working set.  We will
+    # do it.
+    for path in sys.path:
+        if path not in pkg_resources.working_set.entries:
+            pkg_resources.working_set.add_entry(path)
+
+cmd = [quote(sys.executable),
+       '-c',
+       quote('from setuptools.command.easy_install import main; main()'),
+       '-mqNxd',
+       quote(eggs_dir)]
+
+if not has_broken_dash_S:
+    cmd.insert(1, '-S')
+
+find_links = options.download_base
+if not find_links:
+    find_links = os.environ.get('bootstrap-testing-find-links')
+if find_links:
+    cmd.extend(['-f', quote(find_links)])
+
+if options.use_distribute:
+    setup_requirement = 'distribute'
+else:
+    setup_requirement = 'setuptools'
+ws = pkg_resources.working_set
+setup_requirement_path = ws.find(
+    pkg_resources.Requirement.parse(setup_requirement)).location
+env = dict(
+    os.environ,
+    PYTHONPATH=setup_requirement_path)
+
+requirement = 'zc.buildout'
+version = options.version
+if version is None and not options.accept_buildout_test_releases:
+    # Figure out the most recent final version of zc.buildout.
+    import setuptools.package_index
+    _final_parts = '*final-', '*final'
+    def _final_version(parsed_version):
+        for part in parsed_version:
+            if (part[:1] == '*') and (part not in _final_parts):
+                return False
+        return True
+    index = setuptools.package_index.PackageIndex(
+        search_path=[setup_requirement_path])
+    if find_links:
+        index.add_find_links((find_links,))
+    req = pkg_resources.Requirement.parse(requirement)
+    if index.obtain(req) is not None:
+        best = []
+        bestv = None
+        for dist in index[req.project_name]:
+            distv = dist.parsed_version
+            if _final_version(distv):
+                if bestv is None or distv > bestv:
+                    best = [dist]
+                    bestv = distv
+                elif distv == bestv:
+                    best.append(dist)
+        if best:
+            best.sort()
+            version = best[-1].version
+if version:
+    requirement = '=='.join((requirement, version))
+cmd.append(requirement)
+
+if is_jython:
+    import subprocess
+    exitcode = subprocess.Popen(cmd, env=env).wait()
+else: # Windows prefers this, apparently; otherwise we would prefer subprocess
+    exitcode = os.spawnle(*([os.P_WAIT, sys.executable] + cmd + [env]))
+if exitcode != 0:
+    sys.stdout.flush()
+    sys.stderr.flush()
+    print ("An error occurred when trying to install zc.buildout. "
+           "Look above this message for any errors that "
+           "were output by easy_install.")
+    sys.exit(exitcode)
+
+ws.add_entry(eggs_dir)
+ws.require(requirement)
+import zc.buildout.buildout
+zc.buildout.buildout.main(args)
+if not options.eggs: # clean up temporary egg directory
+    shutil.rmtree(eggs_dir)


Property changes on: mongopersist/trunk/bootstrap.py
___________________________________________________________________
Added: svn:keywords
   + Id

Added: mongopersist/trunk/buildout.cfg
===================================================================
--- mongopersist/trunk/buildout.cfg	                        (rev 0)
+++ mongopersist/trunk/buildout.cfg	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,35 @@
+[buildout]
+extends = versions.cfg
+develop = .
+parts =
+  test python coverage-test coverage-report
+versions = versions
+extensions = buildout-versions
+buildout_versions_file = ./versions.cfg
+newest = false
+include-site-packages = false
+unzip = true
+
+[test]
+recipe = zc.recipe.testrunner
+eggs = mongopersist [test, zope]
+defaults = ['--tests-pattern', '^f?tests$$', '-v']
+
+[coverage-test]
+recipe = zc.recipe.testrunner
+eggs = mongopersist [test, zope]
+defaults = ['--coverage', '${buildout:directory}/coverage']
+
+[coverage-report]
+recipe = z3c.recipe.scripts
+eggs = z3c.coverage
+scripts = coveragereport=coverage-report
+arguments = ('${buildout:directory}/coverage',
+             '${buildout:directory}/coverage/report')
+
+[python]
+recipe = z3c.recipe.scripts
+eggs = mongopersist [test, zope]
+interpreter = python
+
+[versions]

Added: mongopersist/trunk/coverage/mongopersist.__init__.cover
===================================================================
--- mongopersist/trunk/coverage/mongopersist.__init__.cover	                        (rev 0)
+++ mongopersist/trunk/coverage/mongopersist.__init__.cover	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1 @@
+    1: # Make a package.

Added: mongopersist/trunk/coverage/mongopersist.datamanager.cover
===================================================================
--- mongopersist/trunk/coverage/mongopersist.datamanager.cover	                        (rev 0)
+++ mongopersist/trunk/coverage/mongopersist.datamanager.cover	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,174 @@
+       ##############################################################################
+       #
+       # Copyright (c) 2011 Zope Foundation and Contributors.
+       # All Rights Reserved.
+       #
+       # This software is subject to the provisions of the Zope Public License,
+       # Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+       # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+       # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+       # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+       # FOR A PARTICULAR PURPOSE.
+       #
+       ##############################################################################
+    1: """Mongo Persistent Data Manager"""
+    1: from __future__ import absolute_import
+    1: import UserDict
+    1: import persistent
+    1: import pymongo
+    1: import pymongo.dbref
+    1: import transaction
+    1: import zope.interface
+       
+    1: from mongopersist import interfaces, serialize
+       
+    1: def create_conflict_error(obj, new_doc):
+    3:     return interfaces.ConflictError(
+    3:         None, obj,
+    3:         (new_doc.get('_py_serial', 0), serialize.u64(obj._p_serial)))
+       
+       
+    2: class Root(UserDict.DictMixin):
+       
+    1:     database='mongopersist'
+    1:     collection = 'persistence_root'
+       
+    1:     def __init__(self, jar, database=None, collection=None):
+  123:         self._jar = jar
+  123:         if database is not None:
+   58:             self.database = database
+  123:         if collection is not None:
+    2:             self.collection = collection
+  123:         db = self._jar._conn[self.database]
+  123:         self._collection_inst = db[self.collection]
+       
+    1:     def __getitem__(self, key):
+  119:         doc = self._collection_inst.find_one({'name': key})
+  119:         if doc is None:
+   14:             raise KeyError(key)
+  105:         return self._jar.load(doc['ref'])
+       
+    1:     def __setitem__(self, key, value):
+   16:         dbref = self._jar.dump(value)
+   15:         if self.get(key) is not None:
+    1:             del self[key]
+   15:         doc = {'ref': dbref, 'name': key}
+   15:         self._collection_inst.insert(doc)
+       
+    1:     def __delitem__(self, key):
+    2:         doc = self._collection_inst.find_one({'name': key})
+    2:         coll = self._jar._conn[doc['ref'].database][doc['ref'].collection]
+    2:         coll.remove(doc['ref'].id)
+    2:         self._collection_inst.remove({'name': key})
+       
+    1:     def keys(self):
+    6:         return [doc['name'] for doc in self._collection_inst.find()]
+       
+       
+    2: class MongoDataManager(object):
+    1:     zope.interface.implements(interfaces.IMongoDataManager)
+       
+    1:     detect_conflicts = False
+    1:     default_database = 'mongopersist'
+    1:     name_map_collection = 'persistence_name_map'
+    1:     conflict_error_factory = staticmethod(create_conflict_error)
+       
+    1:     def __init__(self, conn, detect_conflicts=None, default_database=None,
+    1:                  root_database=None, root_collection=None,
+    1:                  name_map_collection=None, conflict_error_factory=None):
+  122:         self._conn = conn
+  122:         self._reader = serialize.ObjectReader(self)
+  122:         self._writer = serialize.ObjectWriter(self)
+  122:         self._registered_objects = []
+  122:         self._loaded_objects = []
+  122:         self._needs_to_join = True
+  122:         self._object_cache = {}
+  122:         self.annotations = {}
+  122:         if detect_conflicts is not None:
+    2:             self.detect_conflicts = detect_conflicts
+  122:         if default_database is not None:
+   57:             self.default_database = default_database
+  122:         if name_map_collection is not None:
+    1:             self.name_map_collection = name_map_collection
+  122:         if conflict_error_factory is not None:
+    1:             self.conflict_error_factory = conflict_error_factory
+  122:         self.transaction_manager = transaction.manager
+  122:         self.root = Root(self, root_database, root_collection)
+       
+    1:     def dump(self, obj):
+   23:         return self._writer.store(obj)
+       
+    1:     def load(self, dbref):
+  109:         return self._reader.get_ghost(dbref)
+       
+    1:     def reset(self):
+   65:         root = self.root
+   65:         self.__init__(self._conn)
+   65:         self.root = root
+       
+    1:     def setstate(self, obj):
+               # When reading a state from Mongo, we also need to join the
+               # transaction, because we keep an active object cache that gets stale
+               # after the transaction is complete and must be cleaned.
+   46:         if self._needs_to_join:
+   29:             self.transaction_manager.get().join(self)
+   29:             self._needs_to_join = False
+   46:         self._reader.set_ghost_state(obj)
+   46:         self._loaded_objects.append(obj)
+       
+    1:     def oldstate(self, obj, tid):
+               # I cannot find any code using this method. Also, since we do not keep
+               # version history, we always raise an error.
+    1:         raise KeyError(tid)
+       
+    1:     def register(self, obj):
+   58:         if self._needs_to_join:
+   16:             self.transaction_manager.get().join(self)
+   16:             self._needs_to_join = False
+       
+   58:         if obj is not None and obj not in self._registered_objects:
+   56:             self._registered_objects.append(obj)
+       
+    1:     def abort(self, transaction):
+   15:         self.reset()
+       
+    1:     def commit(self, transaction):
+   39:         if not self.detect_conflicts:
+   32:             return
+               # Check each modified object to see whether Mongo has a new version of
+               # the object.
+   12:         for obj in self._registered_objects:
+                   # This object is not even added to the database yet, so there
+                   # cannot be a conflict.
+    7:             if obj._p_oid is None:
+    1:                 continue
+    6:             db_name, coll_name = self._writer.get_collection_name(obj)
+    6:             coll = self._conn[db_name][coll_name]
+    6:             new_doc = coll.find_one(obj._p_oid.id, fields=('_py_serial',))
+    6:             if new_doc is None:
+    1:                 continue
+    5:             if new_doc.get('_py_serial', 0) != serialize.u64(obj._p_serial):
+    2:                 raise self.conflict_error_factory(obj, new_doc)
+       
+    1:     def tpc_begin(self, transaction):
+   35:         pass
+       
+    1:     def tpc_vote(self, transaction):
+   34:         pass
+       
+    1:     def tpc_finish(self, transaction):
+   37:         written = []
+   92:         for obj in self._registered_objects:
+   55:             if getattr(obj, '_p_mongo_sub_object', False):
+    5:                 obj = obj._p_mongo_doc_object
+   55:             if obj in written:
+    3:                 continue
+   52:             self._writer.store(obj)
+   52:             written.append(obj)
+   37:         self.reset()
+       
+    1:     def tpc_abort(self, transaction):
+    2:         self.abort(transaction)
+       
+    1:     def sortKey(self):
+    6:         return ('MongoDataManager', 0)

Added: mongopersist/trunk/coverage/mongopersist.interfaces.cover
===================================================================
--- mongopersist/trunk/coverage/mongopersist.interfaces.cover	                        (rev 0)
+++ mongopersist/trunk/coverage/mongopersist.interfaces.cover	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,168 @@
+       ##############################################################################
+       #
+       # Copyright (c) 2011 Zope Foundation and Contributors.
+       # All Rights Reserved.
+       #
+       # This software is subject to the provisions of the Zope Public License,
+       # Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+       # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+       # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+       # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+       # FOR A PARTICULAR PURPOSE.
+       #
+       ##############################################################################
+    1: """Mongo Persistence Interfaces"""
+    1: from __future__ import absolute_import
+    1: import datetime
+    1: import persistent.interfaces
+    1: import transaction.interfaces
+    1: import types
+    1: import zope.interface
+    1: import zope.schema
+    1: from pymongo import objectid, dbref
+       
+       MONGO_NATIVE_TYPES = (
+    1:     int, float, unicode, datetime.datetime, types.NoneType,
+    1:     objectid.ObjectId, dbref.DBRef)
+       
+    2: class ConflictError(transaction.interfaces.TransientError):
+       
+    1:     def __init__(self, message=None, object=None, serials=None):
+    3:         self.message = message or "database conflict error"
+    3:         self.object = object
+    3:         self.serials = serials
+       
+    1:     @property
+           def new_serial(self):
+    6:         return self.serials[0]
+       
+    1:     @property
+           def old_serial(self):
+    6:         return self.serials[1]
+       
+    1:     def __str__(self):
+               extras = [
+    6:             'oid %s' %self.object._p_oid,
+    6:             'class %s' %self.object.__class__.__name__,
+    6:             'start serial %s' %self.old_serial,
+    6:             'current serial %s' %self.new_serial]
+    6:         return "%s (%s)" % (self.message, ", ".join(extras))
+       
+    1:     def __repr__(self):
+    1:         return '%s: %s' %(self.__class__.__name__, self)
+       
+       
+    2: class CircularReferenceError(Exception):
+    1:     pass
+       
+    2: class IObjectSerializer(zope.interface.Interface):
+           """An object serializer allows for custom serialization output for
+    1:     objects."""
+       
+    1:     def can_read(state):
+               """Returns a boolean indicating whether this serializer can deserialize
+               this state."""
+       
+    1:     def get_object(state):
+               """Convert the state to an object."""
+       
+    1:     def can_write(obj):
+               """Returns a boolean indicating whether this serializer can serialize
+               this object."""
+       
+    1:     def get_state(obj):
+               """Convert the object to a state/document."""
+       
+       
+    2: class IObjectWriter(zope.interface.Interface):
+    1:     """The object writer stores an object in the database."""
+       
+    1:     def get_non_persistent_state(obj, seen):
+               """Convert a non-persistent object to a Mongo state/document."""
+       
+    1:     def get_persistent_state(obj, seen):
+               """Convert a persistent object to a Mongo state/document."""
+       
+    1:     def get_state(obj, seen=None):
+               """Convert an arbitrary object to a Mongo state/document.
+       
+               A ``CircularReferenceError`` is raised, if a non-persistent loop is
+               detected.
+               """
+       
+    1:     def store(obj):
+               """Store an object in the database."""
+       
+       
+    2: class IObjectReader(zope.interface.Interface):
+    1:     """The object reader reads an object from the database."""
+       
+    1:     def resolve(path):
+               """Resolve a path to a class.
+       
+               The path can be any string. It is the responsibility of the resolver
+               to maintain the mapping from path to class.
+               """
+       
+    1:     def get_object(state, obj):
+               """Get an object from the given state.
+       
+               The ``obj`` is the Mongo document of which the created object is part
+               of.
+               """
+       
+    1:     def set_ghost_state(obj):
+               """Convert a ghosted object to an active object by loading its state.
+               """
+       
+    1:     def get_ghost(coll_name, oid):
+               """Get the ghosted version of the object.
+               """
+       
+       
+    2: class IMongoDataManager(persistent.interfaces.IPersistentDataManager):
+    1:     """A persistent data manager that stores data in Mongo."""
+       
+    1:     root = zope.interface.Attribute(
+    1:         """Get the root object, which is a mapping.""")
+       
+    1:     def reset():
+               """Reset the datamanager for the next transaction."""
+       
+    1:     def dump(obj):
+               """Store the object to Mongo and return its DBRef."""
+       
+    1:     def load(dbref):
+               """Load the object from Mongo by using its DBRef.
+       
+               Note: The returned object is in the ghost state.
+               """
+       
+       
+    2: class IMongoConnectionPool(zope.interface.Interface):
+    1:     """MongoDB connection pool"""
+       
+    1:     connection = zope.interface.Attribute('MongoDBConnection instance')
+       
+    1:     host = zope.schema.TextLine(
+    1:         title=u'MongoDB Server Hostname (without protocol)',
+    1:         description=u'MongoDB Server Hostname or IPv4 address',
+    1:         default=u'localhost',
+    1:         required=True)
+       
+    1:     port = zope.schema.Int(
+    1:         title=u'MongoDB Server Port',
+    1:         description=u'MongoDB Server Port',
+    1:         default=27017,
+    1:         required=True)
+       
+       
+    2: class IMongoDataManagerProvider(zope.interface.Interface):
+           """Utility to get a mongo data manager.
+       
+           Implementations of this utility ususally maintain connection information
+           and ensure that there is one consistent datamanager per thread.
+    1:     """
+       
+    1:     def get():
+               """Return a mongo data manager."""

Added: mongopersist/trunk/coverage/mongopersist.mapping.cover
===================================================================
--- mongopersist/trunk/coverage/mongopersist.mapping.cover	                        (rev 0)
+++ mongopersist/trunk/coverage/mongopersist.mapping.cover	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,64 @@
+       ##############################################################################
+       #
+       # Copyright (c) 2011 Zope Foundation and Contributors.
+       # All Rights Reserved.
+       #
+       # This software is subject to the provisions of the Zope Public License,
+       # Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+       # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+       # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+       # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+       # FOR A PARTICULAR PURPOSE.
+       #
+       ##############################################################################
+    1: """Mongo Mapping Implementations"""
+    1: import UserDict
+    1: import pymongo
+       
+    1: from mongopersist import interfaces
+       
+    2: class MongoCollectionMapping(UserDict.DictMixin, object):
+    1:     __mongo_database__ = None
+    1:     __mongo_collection__ = None
+    1:     __mongo_mapping_key__ = 'key'
+       
+    1:     def __init__(self, jar):
+    8:         self._m_jar = jar
+       
+    1:     def __mongo_filter__(self):
+    9:         return {}
+       
+    1:     def get_mongo_collection(self):
+   14:         db_name = self.__mongo_database__ or self._m_jar.default_database
+   14:         return self._m_jar._conn[db_name][self.__mongo_collection__]
+       
+    1:     def __getitem__(self, key):
+    6:         filter = self.__mongo_filter__()
+    6:         filter[self.__mongo_mapping_key__] = key
+    6:         doc = self.get_mongo_collection().find_one(filter)
+    6:         if doc is None:
+    2:             raise KeyError(key)
+    4:         db_name = self.__mongo_database__ or self._m_jar.default_database
+    4:         dbref = pymongo.dbref.DBRef(
+    4:             self.__mongo_collection__, doc['_id'], db_name)
+    4:         return self._m_jar._reader.get_ghost(dbref)
+       
+    1:     def __setitem__(self, key, value):
+               # Even though setting the attribute should register the object with
+               # the data manager, the value might not be in the DB at all at this
+               # point, so registering it manually ensures that new objects get added.
+    2:         self._m_jar.register(value)
+    2:         setattr(value, self.__mongo_mapping_key__, key)
+       
+    1:     def __delitem__(self, key):
+               # Deleting the object from the database is not our job. We simply
+               # remove it from the dictionary.
+    1:         value = self[key]
+    1:         setattr(value, self.__mongo_mapping_key__, None)
+       
+    1:     def keys(self):
+    7:         filter = self.__mongo_filter__()
+    7:         filter[self.__mongo_mapping_key__] = {'$ne': None}
+               return [
+    7:             doc[self.__mongo_mapping_key__]
+   15:             for doc in self.get_mongo_collection().find(filter)]

Added: mongopersist/trunk/coverage/mongopersist.serialize.cover
===================================================================
--- mongopersist/trunk/coverage/mongopersist.serialize.cover	                        (rev 0)
+++ mongopersist/trunk/coverage/mongopersist.serialize.cover	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,407 @@
+       ##############################################################################
+       #
+       # Copyright (c) 2011 Zope Foundation and Contributors.
+       # All Rights Reserved.
+       #
+       # This software is subject to the provisions of the Zope Public License,
+       # Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+       # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+       # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+       # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+       # FOR A PARTICULAR PURPOSE.
+       #
+       ##############################################################################
+    1: """Object Serialization for Mongo/BSON"""
+    1: from __future__ import absolute_import
+    1: import copy_reg
+    1: import struct
+       
+    1: import lru
+    1: import persistent.interfaces
+    1: import persistent.dict
+    1: import persistent.list
+    1: import pymongo.binary
+    1: import pymongo.dbref
+    1: import pymongo.objectid
+    1: import types
+    1: import zope.interface
+    1: from zope.dottedname.resolve import resolve
+       
+    1: from mongopersist import interfaces
+       
+    1: SERIALIZERS = []
+    1: OID_CLASS_LRU = lru.LRUCache(20000)
+       
+    1: def p64(v):
+           """Pack an integer or long into a 8-byte string"""
+   16:     return struct.pack(">Q", v)
+       
+    1: def u64(v):
+           """Unpack an 8-byte string into a 64-bit long integer."""
+   19:     return struct.unpack(">Q", v)[0]
+       
+    1: def get_dotted_name(obj):
+  207:     return obj.__module__+'.'+obj.__name__
+       
+    2: class PersistentDict(persistent.dict.PersistentDict):
+    1:     _p_mongo_sub_object = True
+       
+    2: class PersistentList(persistent.list.PersistentList):
+    1:     _p_mongo_sub_object = True
+       
+       
+    2: class ObjectSerializer(object):
+    1:     zope.interface.implements(interfaces.IObjectSerializer)
+       
+    1:     def can_read(self, state):
+    1:         raise NotImplementedError
+       
+    1:     def read(self, state):
+    1:         raise NotImplementedError
+       
+    1:     def can_write(self, obj):
+    1:         raise NotImplementedError
+       
+    1:     def write(self, obj):
+    1:         raise NotImplementedError
+       
+       
+    2: class ObjectWriter(object):
+    1:     zope.interface.implements(interfaces.IObjectWriter)
+       
+    1:     def __init__(self, jar):
+  139:         self._jar = jar
+       
+    1:     def get_collection_name(self, obj):
+  168:         db_name = getattr(obj, '_p_mongo_database', self._jar.default_database)
+  168:         try:
+  168:             coll_name = obj._p_mongo_collection
+   99:         except AttributeError:
+   99:             return db_name, get_dotted_name(obj.__class__)
+               # Make sure that the coll_name to class path mapping is available.
+   69:         db = self._jar._conn[self._jar.default_database]
+   69:         coll = db[self._jar.name_map_collection]
+   69:         map = {'collection': coll_name,
+   69:                'database': db_name,
+   69:                'path': get_dotted_name(obj.__class__)}
+   69:         result = coll.find_one(map)
+   69:         if result is None:
+                   # If there is already a map for this collection, the next map must
+                   # force the object to store the type.
+   28:             result = coll.find({'collection': coll_name,
+   28:                                 'database': db_name})
+   28:             if result.count() > 0:
+    3:                 setattr(obj, '_p_mongo_store_type', True)
+   28:             map['doc_has_type'] = getattr(obj, '_p_mongo_store_type', False)
+   28:             coll.save(map)
+   69:         return db_name, coll_name
+       
+    1:     def get_non_persistent_state(self, obj, seen):
+   34:         __traceback_info__ = obj
+               # XXX: Look at the pickle library how to properly handle all types and
+               # old-style classes with all of the possible pickle extensions.
+       
+               # Only non-persistent, custom objects can produce unresolvable
+               # circular references.
+   34:         if obj in seen:
+    2:             raise interfaces.CircularReferenceError(obj)
+               # Add the current object to the list of seen objects.
+   32:         seen.append(obj)
+               # Get the state of the object. Only pickable objects can be reduced.
+   32:         reduced = obj.__reduce__()
+               # The full object state (item 3) seems to be optional, so let's make
+               # sure we handle that case gracefully.
+   32:         if len(reduced) == 2:
+    2:             factory, args = obj.__reduce__()
+    2:             obj_state = {}
+               else:
+   30:             factory, args, obj_state = reduced
+               # We are trying very hard to create a clean Mongo (sub-)document. But
+               # we need a little bit of meta-data to help us out later.
+   32:         if factory == copy_reg._reconstructor and \
+   16:                args == (obj.__class__, object, None):
+                   # This is the simple case, which means we can produce a nicer
+                   # Mongo output.
+   16:             state = {'_py_type': get_dotted_name(args[0])}
+   16:         elif factory == copy_reg.__newobj__ and args == (obj.__class__,):
+                   # Another simple case for persistent objects that do not want
+                   # their own document.
+   14:             state = {'_py_persistent_type': get_dotted_name(args[0])}
+               else:
+    2:             state = {'_py_factory': get_dotted_name(factory),
+    2:                      '_py_factory_args': self.get_state(args, seen)}
+   86:         for name, value in obj_state.items():
+   56:             state[name] = self.get_state(value, seen)
+   30:         return state
+       
+    1:     def get_persistent_state(self, obj, seen):
+   73:         __traceback_info__ = obj
+               # Persistent sub-objects are stored by reference, the key being
+               # (collection name, oid).
+               # Getting the collection name is easy, but if we have an unsaved
+               # persistent object, we do not yet have an OID. This must be solved by
+               # storing the persistent object.
+   73:         if obj._p_oid is None:
+   11:             dbref = self.store(obj, ref_only=True)
+               else:
+   62:             db_name, coll_name = self.get_collection_name(obj)
+   62:             dbref = obj._p_oid
+               # Create the reference sub-document. The _p_type value helps with the
+               # deserialization later.
+   73:         return dbref
+       
+    1:     def get_state(self, obj, seen=None):
+  478:         seen = seen or []
+  478:         if isinstance(obj, interfaces.MONGO_NATIVE_TYPES):
+                   # If we have a native type, we'll just use it as the state.
+  189:             return obj
+  289:         if isinstance(obj, str):
+                   # In Python 2, strings can be ASCII, encoded unicode or binary
+                   # data. Unfortunately, BSON cannot handle that. So, if we have a
+                   # string that cannot be UTF-8 decoded (luckily ASCII is a valid
+                   # subset of UTF-8), then we use the BSON binary type.
+   50:             try:
+   50:                 obj.decode('utf-8')
+   48:                 return obj
+    2:             except UnicodeError:
+    2:                 return pymongo.binary.Binary(obj)
+       
+               # Some objects might not naturally serialize well and create a very
+               # ugly Mongo entry. Thus, we allow custom serializers to be
+               # registered, which can encode/decode different types of objects.
+  317:         for serializer in SERIALIZERS:
+   85:             if serializer.can_write(obj):
+    7:                 return serializer.write(obj)
+       
+  232:         if isinstance(obj, (type, types.ClassType)):
+                   # We frequently store class and function paths as meta-data, so we
+                   # need to be able to properly encode those.
+    2:             return {'_py_type': 'type',
+    2:                     'path': get_dotted_name(obj)}
+  230:         if isinstance(obj, (tuple, list, PersistentList)):
+                   # Make sure that all values within a list are serialized
+                   # correctly. Also convert any sequence-type to a simple list.
+   47:             return [self.get_state(value, seen) for value in obj]
+  209:         if isinstance(obj, (dict, PersistentDict)):
+                   # Same as for sequences, make sure that the contained values are
+                   # properly serialized.
+                   # Note: A big constraint in Mongo is that keys must be strings!
+  109:             has_non_string_key = False
+  109:             data = []
+  373:             for key, value in obj.items():
+  265:                 data.append((key, self.get_state(value, seen)))
+  264:                 has_non_string_key |= not isinstance(key, basestring)
+  108:             if not has_non_string_key:
+                       # The easy case: all keys are strings:
+  107:                 return dict(data)
+                   else:
+                       # We first need to reduce the keys and then produce a data
+                       # structure.
+    4:                 data = [(self.get_state(key), value) for key, value in data]
+    1:                 return {'dict_data': data}
+       
+  100:         if isinstance(obj, persistent.Persistent):
+                   # Only create a persistent reference, if the object does not want
+                   # to be a sub-document.
+   84:             if not getattr(obj, '_p_mongo_sub_object', False):
+   71:                 return self.get_persistent_state(obj, seen)
+                   # This persistent object is a sub-document, so it is treated like
+                   # a non-persistent object.
+       
+   29:         return self.get_non_persistent_state(obj, seen)
+       
+    1:     def store(self, obj, ref_only=False):
+   96:         db_name, coll_name = self.get_collection_name(obj)
+   96:         coll = self._jar._conn[db_name][coll_name]
+   96:         if ref_only:
+                   # We only want to get OID quickly. Trying to reduce the full state
+                   # might cause infinite recusrion loop. (Example: 2 new objects
+                   # reference each other.)
+   11:             doc = {}
+                   # Make sure that the object gets saved fully later.
+   11:             self._jar.register(obj)
+               else:
+                   # XXX: Handle newargs; see ZODB.serialize.ObjectWriter.serialize
+                   # Go through each attribute and search for persistent references.
+   85:             doc = self.get_state(obj.__getstate__())
+   95:         if getattr(obj, '_p_mongo_store_type', False):
+    4:             doc['_py_persistent_type'] = get_dotted_name(obj.__class__)
+               # If conflict detection is turned on, store a serial number for the
+               # document.
+   95:         if self._jar.detect_conflicts:
+   11:             doc['_py_serial'] = u64(getattr(obj, '_p_serial', 0)) + 1
+   11:             obj._p_serial = p64(doc['_py_serial'])
+       
+   95:         if obj._p_oid is None:
+   63:             doc_id = coll.insert(doc)
+   63:             obj._p_jar = self._jar
+   63:             obj._p_oid = pymongo.dbref.DBRef(coll_name, doc_id, db_name)
+                   # Make sure that any other code accessing this object in this
+                   # session, gets the same instance.
+   63:             self._jar._object_cache[doc_id] = obj
+               else:
+   32:             doc['_id'] = obj._p_oid.id
+   32:             coll.save(doc)
+   95:         return obj._p_oid
+       
+       
+    2: class ObjectReader(object):
+    1:     zope.interface.implements(interfaces.IObjectReader)
+       
+    1:     def __init__(self, jar):
+  138:         self._jar = jar
+       
+    1:     def simple_resolve(self, path):
+  118:         return resolve(path)
+       
+    1:     def resolve(self, dbref):
+   72:         try:
+   72:             return OID_CLASS_LRU[dbref.id]
+   66:         except KeyError:
+   66:             pass
+               # First we try to resolve the path directly.
+   66:         try:
+   66:             return self.simple_resolve(dbref.collection)
+   28:         except ImportError:
+   28:             pass
+               # Let's now try to look up the path from the collection to path
+               # mapping
+   28:         db = self._jar._conn[self._jar.default_database]
+   28:         coll = db[self._jar.name_map_collection]
+   28:         result = coll.find(
+   28:             {'collection': dbref.collection, 'database': dbref.database})
+   28:         if result.count() == 0:
+    1:             raise ImportError(dbref)
+   27:         elif result.count() == 1:
+                   # Do not add these results to the LRU cache, since the count might
+                   # change later.
+   22:             return self.simple_resolve(result.next()['path'])
+               else:
+    5:             if dbref.id is None:
+    1:                 raise ImportError(dbref)
+                   # Multiple object types are stored in the collection. We have to
+                   # look at the object to find out the type.
+    4:             obj_doc = self._jar._conn[dbref.database][dbref.collection].find_one(
+    4:                 dbref.id, fields=('_py_persistent_type',))
+    4:             if '_py_persistent_type' in obj_doc:
+    2:                 klass = self.simple_resolve(obj_doc['_py_persistent_type'])
+                   else:
+                       # Find the name-map entry where "doc_has_type" is False.
+    2:                 for name_map_item in result:
+    2:                     if not name_map_item['doc_has_type']:
+    2:                         klass = self.simple_resolve(name_map_item['path'])
+    2:                         break
+                       else:
+>>>>>>                     raise ImportError(path)
+    4:             OID_CLASS_LRU[dbref.id] = klass
+    4:             return klass
+       
+    1:     def get_non_persistent_object(self, state, obj):
+   24:         if '_py_type' in state:
+                   # Handle the simplified case.
+   12:             klass = self.simple_resolve(state.pop('_py_type'))
+   12:             sub_obj = copy_reg._reconstructor(klass, object, None)
+   12:         elif '_py_persistent_type' in state:
+                   # Another simple case for persistent objects that do not want
+                   # their own document.
+   10:             klass = self.simple_resolve(state.pop('_py_persistent_type'))
+   10:             sub_obj = copy_reg.__newobj__(klass)
+               else:
+    2:             factory = self.simple_resolve(state.pop('_py_factory'))
+    2:             factory_args = self.get_object(state.pop('_py_factory_args'), obj)
+    2:             sub_obj = factory(*factory_args)
+   24:         if len(state):
+   20:             sub_obj_state = self.get_object(state, obj)
+   20:             if isinstance(sub_obj, persistent.Persistent):
+    9:                 sub_obj.__setstate__(sub_obj_state)
+                   else:
+   11:                 sub_obj.__dict__.update(sub_obj_state)
+   24:         if getattr(sub_obj, '_p_mongo_sub_object', False):
+   10:             sub_obj._p_mongo_doc_object = obj
+   10:             sub_obj._p_jar = self._jar
+   24:         return sub_obj
+       
+    1:     def get_object(self, state, obj):
+  584:         if isinstance(state, pymongo.objectid.ObjectId):
+                   # The object id is special. Preserve it.
+    1:             return state
+  583:         if isinstance(state, pymongo.binary.Binary):
+                   # Binary data in Python 2 is presented as a string. We will
+                   # convert back to binary when serializing again.
+    2:             return str(state)
+  581:         if isinstance(state, pymongo.dbref.DBRef):
+                   # Load a persistent object. Using the get_ghost() method, so that
+                   # caching is properly applied.
+   38:             return self.get_ghost(state)
+  543:         if isinstance(state, dict) and state.get('_py_type') == 'type':
+                   # Convert a simple object reference, mostly classes.
+    1:             return self.simple_resolve(state['path'])
+       
+               # Give the custom serializers a chance to weigh in.
+  810:         for serializer in SERIALIZERS:
+  275:             if serializer.can_read(state):
+    7:                 return serializer.read(state)
+       
+  535:         if isinstance(state, dict) and ('_py_factory' in state or \
+  106:                '_py_type' in state or '_py_persistent_type' in state):
+                   # Load a non-persistent object.
+   20:             return self.get_non_persistent_object(state, obj)
+  515:         if isinstance(state, (tuple, list)):
+                   # All lists are converted to persistent lists, so that their state
+                   # changes are noticed. Also make sure that all value states are
+                   # converted to objects.
+   17:             sub_obj = PersistentList(
+   40:                 [self.get_object(value, obj) for value in state])
+   17:             sub_obj._p_mongo_doc_object = obj
+   17:             sub_obj._p_jar = self._jar
+   17:             return sub_obj
+  498:         if isinstance(state, dict):
+                   # All dictionaries are converted to persistent dictionaries, so
+                   # that state changes are detected. Also convert all value states
+                   # to objects.
+                   # Handle non-string key dicts.
+   87:             if 'dict_data' in state:
+    1:                 items = state['dict_data']
+                   else:
+   86:                 items = state.items()
+   87:             sub_obj = PersistentDict(
+   87:                 [(self.get_object(name, obj), self.get_object(value, obj))
+  329:                  for name, value in items])
+   87:             sub_obj._p_mongo_doc_object = obj
+   87:             sub_obj._p_jar = self._jar
+   87:             return sub_obj
+  411:         return state
+       
+    1:     def set_ghost_state(self, obj):
+               # Look up the object state by coll_name and oid.
+   47:         coll = self._jar._conn[obj._p_oid.database][obj._p_oid.collection]
+   47:         doc = coll.find_one({'_id': obj._p_oid.id})
+   47:         doc.pop('_id')
+   47:         doc.pop('_py_persistent_type', None)
+               # Store the serial, if conflict detection is enabled.
+   47:         if self._jar.detect_conflicts:
+    5:             obj._p_serial = p64(doc.pop('_py_serial', 0))
+               # Now convert the document to a proper Python state dict.
+   47:         state = self.get_object(doc, obj)
+               # Set the state.
+   47:         obj.__setstate__(dict(state))
+       
+    1:     def get_ghost(self, dbref):
+               # If we can, we return the object from cache.
+  171:         try:
+  171:             return self._jar._object_cache[dbref.id]
+   66:         except KeyError:
+   66:             pass
+   66:         klass = self.resolve(dbref)
+   66:         obj = klass.__new__(klass)
+   66:         obj._p_jar = self._jar
+   66:         obj._p_oid = dbref
+   66:         del obj._p_changed
+               # Assign the collection after deleting _p_changed, since the attribute
+               # is otherwise deleted.
+   66:         obj._p_mongo_database = dbref.database
+   66:         obj._p_mongo_collection = dbref.collection
+               # Adding the object to the cache is very important, so that we get the
+               # same object reference throughout the transaction.
+   66:         self._jar._object_cache[dbref.id] = obj
+   66:         return obj

Added: mongopersist/trunk/coverage/mongopersist.testing.cover
===================================================================
--- mongopersist/trunk/coverage/mongopersist.testing.cover	                        (rev 0)
+++ mongopersist/trunk/coverage/mongopersist.testing.cover	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,55 @@
+       ##############################################################################
+       #
+       # Copyright (c) 2011 Zope Foundation and Contributors.
+       # All Rights Reserved.
+       #
+       # This software is subject to the provisions of the Zope Public License,
+       # Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+       # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+       # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+       # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+       # FOR A PARTICULAR PURPOSE.
+       #
+       ##############################################################################
+    1: """Mongo Persistence Testing Support"""
+    1: from __future__ import absolute_import
+    1: import doctest
+    1: import pymongo
+    1: import re
+    1: import transaction
+    1: from zope.testing import module, renormalizing
+       
+    1: from mongopersist import datamanager, serialize
+       
+    1: checker = renormalizing.RENormalizing([
+    1:     (re.compile(r'datetime.datetime(.*)'),
+    1:      'datetime.datetime(2011, 10, 1, 9, 45)'),
+    1:     (re.compile(r"ObjectId\('[0-9a-f]*'\)"),
+    1:      "ObjectId('4e7ddf12e138237403000000')"),
+    1:     (re.compile(r"object at 0x[0-9a-f]*>"),
+    1:      "object at 0x001122>"),
+           ])
+       
+       OPTIONFLAGS = (doctest.NORMALIZE_WHITESPACE|
+    1:                doctest.ELLIPSIS|
+    1:                doctest.REPORT_ONLY_FIRST_FAILURE
+                      #|doctest.REPORT_NDIFF
+                      )
+       
+    1: def setUp(test):
+   46:     module.setUp(test)
+   46:     test.globs['conn'] = pymongo.Connection('localhost', 27017, tz_aware=False)
+   46:     test.globs['DBNAME'] = 'mongopersist_test'
+   46:     test.globs['conn'].drop_database(test.globs['DBNAME'])
+   46:     test.globs['commit'] = transaction.commit
+   46:     test.globs['dm'] = datamanager.MongoDataManager(
+   46:         test.globs['conn'],
+   46:         default_database=test.globs['DBNAME'],
+   46:         root_database=test.globs['DBNAME'])
+       
+    1: def tearDown(test):
+   46:     module.tearDown(test)
+   46:     transaction.abort()
+   46:     test.globs['conn'].drop_database(test.globs['DBNAME'])
+   46:     test.globs['conn'].disconnect()
+   46:     serialize.SERIALIZERS.__init__()

Added: mongopersist/trunk/coverage/mongopersist.tests.__init__.cover
===================================================================
--- mongopersist/trunk/coverage/mongopersist.tests.__init__.cover	                        (rev 0)
+++ mongopersist/trunk/coverage/mongopersist.tests.__init__.cover	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1 @@
+    1: # Make a package.

Added: mongopersist/trunk/coverage/mongopersist.tests.test_datamanager.cover
===================================================================
--- mongopersist/trunk/coverage/mongopersist.tests.test_datamanager.cover	                        (rev 0)
+++ mongopersist/trunk/coverage/mongopersist.tests.test_datamanager.cover	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,353 @@
+       ##############################################################################
+       #
+       # Copyright (c) 2011 Zope Foundation and Contributors.
+       # All Rights Reserved.
+       #
+       # This software is subject to the provisions of the Zope Public License,
+       # Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+       # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+       # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+       # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+       # FOR A PARTICULAR PURPOSE.
+       #
+       ##############################################################################
+    1: """Mongo  Tests"""
+    1: import doctest
+    1: import persistent
+    1: import pprint
+    1: import transaction
+    1: from pymongo import dbref, objectid
+       
+    1: from mongopersist import testing, datamanager
+       
+    2: class Foo(persistent.Persistent):
+    1:     def __init__(self, name=None):
+   14:         self.name = name
+       
+    2: class Bar(persistent.Persistent):
+    1:     _p_mongo_sub_object = True
+       
+    1: def doctest_create_conflict_error():
+           r"""create_conflict_error(): General Test
+       
+           Simple helper function to create a conflict error.
+       
+            >>> foo = Foo()
+            >>> foo._p_serial = '\x00\x00\x00\x00\x00\x00\x00\x01'
+       
+            >>> datamanager.create_conflict_error(foo, {'_py_serial': 3})
+            ConflictError: database conflict error
+                           (oid None, class Foo, start serial 1, current serial 3)
+           """
+       
+    1: def doctest_Root():
+           r"""Root: General Test
+       
+           This class represents the root(s) of the object tree. All roots are stored
+           in a specified collection. Since the rooted object needs to immediately
+           provide a data manager (jar), the operations on the DB root are not art of
+           the transaction mechanism.
+       
+             >>> root = datamanager.Root(dm, DBNAME, 'proot')
+       
+           Initially the root is empty:
+       
+             >>> root.keys()
+             []
+       
+           Let's now add an item:
+       
+             >>> foo = Foo()
+             >>> root['foo'] = foo
+             >>> root.keys()
+             [u'foo']
+             >>> root['foo'] == foo
+             True
+       
+           Root objects can be overridden:
+       
+             >>> foo2 = Foo()
+             >>> root['foo'] = foo2
+             >>> root.keys()
+             [u'foo']
+             >>> root['foo'] == foo
+             False
+       
+           And of course we can delete an item:
+       
+             >>> del root['foo']
+             >>> root.keys()
+             []
+           """
+       
+    1: def doctest_MongoDataManager_object_dump_load_reset():
+           r"""MongoDataManager: dump(), load(), reset()
+       
+           The Mongo Data Manager is a persistent data manager that manages object
+           states in a Mongo database accross Python transactions.
+       
+           There are several arguments to create the data manager, but only the
+           pymongo connection is required:
+       
+             >>> dm = datamanager.MongoDataManager(
+             ...     conn,
+             ...     detect_conflicts=True,
+             ...     default_database = DBNAME,
+             ...     root_database = DBNAME,
+             ...     root_collection = 'proot',
+             ...     name_map_collection = 'coll_pypath_map',
+             ...     conflict_error_factory = datamanager.create_conflict_error)
+       
+           There are two convenience methods that let you serialize and de-serialize
+           objects explicitly:
+       
+             >>> foo = Foo()
+             >>> dm.dump(foo)
+             DBRef('mongopersist.tests.test_datamanager.Foo',
+                   ObjectId('4eb2eb7437a08e0156000000'),
+                   'mongopersist_test')
+       
+           Let's now reset the data manager, so we do not hit a cache while loading
+           the object again:
+       
+             >>> dm.reset()
+       
+           We can now load the object:
+       
+             >>> foo2 = dm.load(foo._p_oid)
+             >>> foo == foo2
+             False
+             >>> foo._p_oid = foo2._p_oid
+           """
+       
+    1: def doctest_MongoDataManager_set_state():
+           r"""MongoDataManager: set_state()
+       
+           This method loads and sets the state of an object and joins the
+           transaction.
+       
+             >>> foo = Foo(u'foo')
+             >>> ref = dm.dump(foo)
+       
+             >>> dm.reset()
+             >>> dm._needs_to_join
+             True
+       
+             >>> foo2 = Foo()
+             >>> foo2._p_oid = ref
+             >>> dm.setstate(foo2)
+             >>> foo2.name
+             u'foo'
+       
+             >>> dm._needs_to_join
+             False
+           """
+       
+    1: def doctest_MongoDataManager_oldstate():
+           r"""MongoDataManager: oldstate()
+       
+           Loads the state of an object for a given transaction. Since we are not
+           supporting history, this always raises a key error as documented.
+       
+             >>> foo = Foo(u'foo')
+             >>> dm.oldstate(foo, '0')
+             Traceback (most recent call last):
+             ...
+             KeyError: '0'
+           """
+       
+    1: def doctest_MongoDataManager_register():
+           r"""MongoDataManager: register()
+       
+           Registers an object to be stored.
+       
+             >>> dm._needs_to_join
+             True
+             >>> len(dm._registered_objects)
+             0
+       
+             >>> foo = Foo(u'foo')
+             >>> dm.register(foo)
+       
+             >>> dm._needs_to_join
+             False
+             >>> len(dm._registered_objects)
+             1
+       
+          But there are no duplicates:
+       
+             >>> dm.register(foo)
+             >>> len(dm._registered_objects)
+             1
+           """
+       
+    1: def doctest_MongoDataManager_abort():
+           r"""MongoDataManager: abort()
+       
+           Aborts a transaction, which clears all object and transaction registrations:
+       
+             >>> dm._registered_objects = [Foo()]
+             >>> dm._needs_to_join = False
+       
+             >>> dm.abort(transaction.get())
+       
+             >>> dm._needs_to_join
+             True
+             >>> len(dm._registered_objects)
+             0
+           """
+       
+    1: def doctest_MongoDataManager_commit():
+           r"""MongoDataManager: commit()
+       
+           Contrary to what the name suggests, this is the commit called during the
+           first phase of a two-phase commit. Thus, for all practically purposes,
+           this method merely checks whether the commit would potentially fail.
+       
+           This means, if conflict detection is disabled, this method does nothing.
+       
+             >>> dm.detect_conflicts
+             False
+             >>> dm.commit(transaction.get())
+       
+           Let's now turn on conflict detection:
+       
+             >>> dm.detect_conflicts = True
+       
+           For new objects (not having an oid), it always passes:
+       
+             >>> dm.reset()
+             >>> dm._registered_objects = [Foo()]
+             >>> dm.commit(transaction.get())
+       
+           If the object has an oid, but is not found in the DB, we also just pass,
+           because the object will be inserted.
+       
+             >>> foo = Foo()
+             >>> foo._p_oid =  dbref.DBRef(
+             ...     'mongopersist.tests.test_datamanager.Foo',
+             ...     objectid.ObjectId('4eb2eb7437a08e0156000000'),
+             ...     'mongopersist_test')
+       
+             >>> dm.reset()
+             >>> dm._registered_objects = [foo]
+             >>> dm.commit(transaction.get())
+       
+           Let's now store an object and make sure it does not conflict:
+       
+             >>> foo = Foo()
+             >>> ref = dm.dump(foo)
+             >>> ref
+             DBRef('mongopersist.tests.test_datamanager.Foo',
+                   ObjectId('4eb3468037a08e1b74000000'),
+                   'mongopersist_test')
+       
+             >>> dm.reset()
+             >>> dm._registered_objects = [foo]
+             >>> dm.commit(transaction.get())
+       
+           Next, let's cause a conflict byt simulating a conflicting transaction:
+       
+             >>> dm.reset()
+             >>> foo2 = dm.load(ref)
+             >>> foo2.name = 'foo2'
+             >>> transaction.commit()
+       
+             >>> dm.reset()
+             >>> dm._registered_objects = [foo]
+             >>> dm.commit(transaction.get())
+             Traceback (most recent call last):
+             ...
+             ConflictError: database conflict error
+                 (oid DBRef('mongopersist.tests.test_datamanager.Foo',
+                            ObjectId('4eb3499637a08e1c5a000000'),
+                            'mongopersist_test'),
+                  class Foo, start serial 1, current serial 2)
+           """
+       
+    1: def doctest_MongoDataManager_tpc_begin():
+           r"""MongoDataManager: tpc_begin()
+       
+           This is a non-op for the mongo data manager.
+       
+             >>> dm.tpc_begin(transaction.get())
+           """
+       
+    1: def doctest_MongoDataManager_tpc_vote():
+           r"""MongoDataManager: tpc_vote()
+       
+           This is a non-op for the mongo data manager.
+       
+             >>> dm.tpc_vote(transaction.get())
+           """
+       
+    1: def doctest_MongoDataManager_tpc_finish():
+           r"""MongoDataManager: tpc_finish()
+       
+           This method finishes the two-phase commit. So let's store a simple object:
+       
+             >>> foo = Foo()
+             >>> dm.detect_conflicts = True
+             >>> dm._registered_objects = [foo]
+             >>> dm.tpc_finish(transaction.get())
+             >>> foo._p_serial
+             '\x00\x00\x00\x00\x00\x00\x00\x01'
+       
+           Note that objects cannot be stored twice in the same transation:
+       
+             >>> dm.reset()
+             >>> dm._registered_objects = [foo, foo]
+             >>> dm.tpc_finish(transaction.get())
+             >>> foo._p_serial
+             '\x00\x00\x00\x00\x00\x00\x00\x02'
+       
+           Also, when a persistent sub-object is stored that does not want its own
+           document, then its parent is stored instead, still avoiding dual storage.
+       
+             >>> dm.reset()
+             >>> foo2 = dm.load(foo._p_oid)
+             >>> foo2.bar = Bar()
+       
+             >>> dm.tpc_finish(transaction.get())
+             >>> foo2._p_serial
+             '\x00\x00\x00\x00\x00\x00\x00\x03'
+       
+             >>> dm.reset()
+             >>> foo3 = dm.load(foo._p_oid)
+             >>> dm._registered_objects = [foo3.bar, foo3]
+             >>> dm.tpc_finish(transaction.get())
+             >>> foo3._p_serial
+             '\x00\x00\x00\x00\x00\x00\x00\x04'
+       
+           """
+       
+    1: def doctest_MongoDataManager_tpc_abort():
+           r"""MongoDataManager: tpc_abort()
+       
+           Aborts a two-phase commit. This is simply the same as the regular abort.
+       
+             >>> dm._registered_objects = [Foo()]
+             >>> dm._needs_to_join = False
+       
+             >>> dm.tpc_abort(transaction.get())
+       
+             >>> dm._needs_to_join
+             True
+             >>> len(dm._registered_objects)
+             0
+           """
+       
+    1: def doctest_MongoDataManager_sortKey():
+           r"""MongoDataManager: sortKey()
+       
+           The data manager's sort key is trivial.
+       
+             >>> dm.sortKey()
+             ('MongoDataManager', 0)
+           """
+       
+    1: def test_suite():
+    1:     return doctest.DocTestSuite(
+    1:         setUp=testing.setUp, tearDown=testing.tearDown,
+    1:         checker=testing.checker,
+    1:         optionflags=testing.OPTIONFLAGS)

Added: mongopersist/trunk/coverage/mongopersist.tests.test_doc.cover
===================================================================
--- mongopersist/trunk/coverage/mongopersist.tests.test_doc.cover	                        (rev 0)
+++ mongopersist/trunk/coverage/mongopersist.tests.test_doc.cover	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,25 @@
+       ##############################################################################
+       #
+       # Copyright (c) 2011 Zope Foundation and Contributors.
+       # All Rights Reserved.
+       #
+       # This software is subject to the provisions of the Zope Public License,
+       # Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+       # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+       # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+       # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+       # FOR A PARTICULAR PURPOSE.
+       #
+       ##############################################################################
+    1: """Mongo Persistence Doc Tests"""
+    1: import doctest
+       
+    1: from mongopersist import testing
+       
+    1: def test_suite():
+    1:     return doctest.DocFileSuite(
+    1:         '../README.txt',
+    1:         setUp=testing.setUp, tearDown=testing.tearDown,
+    1:         checker=testing.checker,
+    1:         optionflags=testing.OPTIONFLAGS
+               )

Added: mongopersist/trunk/coverage/mongopersist.tests.test_mapping.cover
===================================================================
--- mongopersist/trunk/coverage/mongopersist.tests.test_mapping.cover	                        (rev 0)
+++ mongopersist/trunk/coverage/mongopersist.tests.test_mapping.cover	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,128 @@
+       ##############################################################################
+       #
+       # Copyright (c) 2011 Zope Foundation and Contributors.
+       # All Rights Reserved.
+       #
+       # This software is subject to the provisions of the Zope Public License,
+       # Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+       # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+       # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+       # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+       # FOR A PARTICULAR PURPOSE.
+       #
+       ##############################################################################
+    1: """Mongo  Tests"""
+    1: import doctest
+    1: import persistent
+    1: import pprint
+    1: import transaction
+    1: from pymongo import dbref, objectid
+       
+    1: from mongopersist import testing, mapping
+       
+    2: class Item(persistent.Persistent):
+    1:     def __init__(self, name=None, site=None):
+    5:         self.name = name
+    5:         self.site = site
+       
+    1: def doctest_MongoCollectionMapping_simple():
+           r"""MongoCollectionMapping: simple
+       
+           The Mongo Collection Mapping provides a Python dict interface for a mongo
+           collection. Here is a simple example for our Item class/collection:
+       
+             >>> class SimpleContainer(mapping.MongoCollectionMapping):
+             ...     __mongo_collection__ = 'mongopersist.tests.test_mapping.Item'
+             ...     __mongo_mapping_key__ = 'name'
+       
+           To initialize the mapping, we need a data manager:
+       
+             >>> container = SimpleContainer(dm)
+       
+           Let's do some obvious initial manipulations:
+       
+             >>> container['one'] = one = Item()
+             >>> one.name
+             'one'
+             >>> transaction.commit()
+       
+           After the transaction is committed, we can access the item:
+       
+             >>> container.keys()
+             [u'one']
+             >>> container['one'].name
+             u'one'
+       
+             >>> container['two']
+             Traceback (most recent call last):
+             ...
+             KeyError: 'two'
+       
+           Of course we can delete an item, but note that it only removes the name,
+           but does not delete the document by default:
+       
+             >>> del container['one']
+             >>> transaction.commit()
+             >>> container.keys()
+             []
+       
+           Finally, you can always get to the collection that the mapping is
+           managing:
+       
+             >>> container.get_mongo_collection()
+             Collection(Database(Connection('localhost', 27017),
+                                 u'mongopersist_test'),
+                                 u'mongopersist.tests.test_mapping.Item')
+           """
+       
+    1: def doctest_MongoCollectionMapping_filter():
+           r"""MongoCollectionMapping: filter
+       
+           It is often desirable to manage multiple mappings for the same type of
+           object and thus same collection. The mongo mapping thus supports filtering
+           for all its functions.
+       
+             >>> class SiteContainer(mapping.MongoCollectionMapping):
+             ...     __mongo_collection__ = 'mongopersist.tests.test_mapping.Item'
+             ...     __mongo_mapping_key__ = 'name'
+             ...     def __init__(self, jar, site):
+             ...         super(SiteContainer, self).__init__(jar)
+             ...         self.site = site
+             ...     def __mongo_filter__(self):
+             ...         return {'site': self.site}
+       
+             >>> container1 = SiteContainer(dm, 'site1')
+             >>> container2 = SiteContainer(dm, 'site2')
+       
+           Let's now add some items:
+       
+             >>> ref11 = dm.dump(Item('1-1', 'site1'))
+             >>> ref12 = dm.dump(Item('1-2', 'site1'))
+             >>> ref13 = dm.dump(Item('1-3', 'site1'))
+             >>> ref21 = dm.dump(Item('2-1', 'site2'))
+       
+           And accessing the items works as expected:
+       
+             >>> dm.reset()
+             >>> container1.keys()
+             [u'1-1', u'1-2', u'1-3']
+             >>> container1['1-1'].name
+             u'1-1'
+             >>> container1['2-1']
+             Traceback (most recent call last):
+             ...
+             KeyError: '2-1'
+       
+             >>> container2.keys()
+             [u'2-1']
+       
+           Note: The mutator methods (``__setitem__`` and ``__delitem__``) do nto
+           take the filter into account by default. They need to be extended to
+           properly setup and tear down the filter criteria.
+           """
+       
+    1: def test_suite():
+    1:     return doctest.DocTestSuite(
+    1:         setUp=testing.setUp, tearDown=testing.tearDown,
+    1:         checker=testing.checker,
+    1:         optionflags=testing.OPTIONFLAGS)

Added: mongopersist/trunk/coverage/mongopersist.tests.test_serialize.cover
===================================================================
--- mongopersist/trunk/coverage/mongopersist.tests.test_serialize.cover	                        (rev 0)
+++ mongopersist/trunk/coverage/mongopersist.tests.test_serialize.cover	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,720 @@
+       ##############################################################################
+       #
+       # Copyright (c) 2011 Zope Foundation and Contributors.
+       # All Rights Reserved.
+       #
+       # This software is subject to the provisions of the Zope Public License,
+       # Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+       # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+       # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+       # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+       # FOR A PARTICULAR PURPOSE.
+       #
+       ##############################################################################
+    1: """Mongo Persistence Serializeation Tests"""
+    1: import datetime
+    1: import doctest
+    1: import persistent
+    1: import pprint
+       
+    1: from pymongo import binary, dbref, objectid
+       
+    1: from mongopersist import testing, serialize
+       
+    2: class Top(persistent.Persistent):
+    1:     _p_mongo_collection = 'Top'
+       
+    1: def create_top(name):
+    1:     top = Top()
+    1:     top.name = name
+    1:     return top
+       
+    2: class Top2(Top):
+    1:     pass
+       
+    2: class Tier2(persistent.Persistent):
+    1:     _p_mongo_sub_object = True
+       
+    2: class Foo(persistent.Persistent):
+    1:     _p_mongo_collection = 'Foo'
+       
+    2: class Anything(persistent.Persistent):
+    1:     pass
+       
+    2: class Simple(object):
+    1:     pass
+       
+    1: def doctest_ObjectSerializer():
+           """Test the abstract ObjectSerializer class.
+       
+           Object serializers are hooks into the serialization process to allow
+           better serialization for particular objects. For example, the result of
+           reducing a datetime.date object is a short, optimized binary string. This
+           representation might be optimal for pickles, but is really aweful for
+           Mongo, since it does not allow querying for dates. An object serializer
+           can be used to use a better representation, such as the date ordinal
+           number.
+       
+             >>> os = serialize.ObjectSerializer()
+       
+           So here are the methods that must be implemented by an object serializer:
+       
+             >>> os.can_read({})
+             Traceback (most recent call last):
+             ...
+             NotImplementedError
+       
+             >>> os.read({})
+             Traceback (most recent call last):
+             ...
+             NotImplementedError
+       
+             >>> os.can_write(object())
+             Traceback (most recent call last):
+             ...
+             NotImplementedError
+       
+             >>> os.write(object())
+             Traceback (most recent call last):
+             ...
+             NotImplementedError
+           """
+       
+    1: def doctest_ObjectWriter_get_collection_name():
+           """ObjectWriter: get_collection_name()
+       
+           This method determines the collection name and database for a given
+           object. It can either be specified via '_p_mongo_collection' or is
+           determined from the class path. When the collection name is specified, the
+           mapping from collection name to class path is stored.
+       
+             >>> print tuple(conn[DBNAME][dm.name_map_collection].find())
+             ()
+       
+             >>> writer = serialize.ObjectWriter(dm)
+             >>> writer.get_collection_name(Anything())
+             ('mongopersist_test', 'mongopersist.tests.test_serialize.Anything')
+       
+             >>> top = Top()
+             >>> writer.get_collection_name(top)
+             ('mongopersist_test', 'Top')
+       
+             >>> print tuple(conn[DBNAME][dm.name_map_collection].find())
+             ({u'path': u'mongopersist.tests.test_serialize.Top',
+               u'doc_has_type': False,
+               u'_id': ObjectId('4eb19f9937a08e27b7000000'),
+               u'collection': u'Top',
+               u'database': u'mongopersist_test'},)
+       
+             >>> getattr(top, '_p_mongo_store_type', None)
+       
+           When classes use inheritance, it often happens that all sub-objects share
+           the same collection. However, only one can have an entry in our mapping
+           table to avoid non-unique answers. Thus we require all sub-types after the
+           first one to store their typing providing a hint for deseriealization:
+       
+             >>> top2 = Top2()
+             >>> writer.get_collection_name(top2)
+             ('mongopersist_test', 'Top')
+       
+             >>> pprint.pprint(tuple(conn[DBNAME][dm.name_map_collection].find()))
+             ({u'_id': ObjectId('4eb1b5ab37a08e2f06000000'),
+               u'collection': u'Top',
+               u'database': u'mongopersist_test',
+               u'doc_has_type': False,
+               u'path': u'mongopersist.tests.test_serialize.Top'},
+              {u'_id': ObjectId('4eb1b5ab37a08e2f06000001'),
+               u'collection': u'Top',
+               u'database': u'mongopersist_test',
+               u'doc_has_type': True,
+               u'path': u'mongopersist.tests.test_serialize.Top2'})
+       
+             >>> getattr(top2, '_p_mongo_store_type', None)
+             True
+           """
+       
+    1: def doctest_ObjectWriter_get_non_persistent_state():
+           r"""ObjectWriter: get_non_persistent_state()
+       
+           This method produces a proper reduced state for custom, non-persistent
+           objects.
+       
+             >>> writer = serialize.ObjectWriter(dm)
+       
+           A simple new-style class:
+       
+             >>> class This(object):
+             ...     def __init__(self, num):
+             ...         self.num = num
+       
+             >>> this = This(1)
+             >>> writer.get_non_persistent_state(this, [])
+             {'num': 1, '_py_type': '__main__.This'}
+       
+           A simple old-style class:
+       
+             >>> class That(object):
+             ...     def __init__(self, num):
+             ...         self.num = num
+       
+             >>> that = That(1)
+             >>> writer.get_non_persistent_state(that, [])
+             {'num': 1, '_py_type': '__main__.That'}
+       
+           The method also handles persistent classes that do not want their own
+           document:
+       
+             >>> top = Top()
+             >>> writer.get_non_persistent_state(top, [])
+             {'_py_persistent_type': 'mongopersist.tests.test_serialize.Top'}
+       
+           And then there are the really weird cases:
+       
+             >>> writer.get_non_persistent_state(datetime.date(2011, 11, 1), [])
+             {'_py_factory': 'datetime.date',
+              '_py_factory_args': [Binary('\x07\xdb\x0b\x01', 0)]}
+       
+           Circular object references cause an error:
+       
+             >>> writer.get_non_persistent_state(this, [this])
+             Traceback (most recent call last):
+             ...
+             CircularReferenceError: <__main__.This object at 0x3051550>
+           """
+       
+    1: def doctest_ObjectWriter_get_persistent_state():
+           r"""ObjectWriter: get_persistent_state()
+       
+           This method produces a proper reduced state for a persistent object, which
+           is basically a Mongo DBRef.
+       
+             >>> writer = serialize.ObjectWriter(dm)
+       
+             >>> foo = Foo()
+             >>> foo._p_oid
+             >>> list(conn[DBNAME]['Foo'].find())
+             []
+       
+             >>> writer.get_persistent_state(foo, [])
+             DBRef('Foo', ObjectId('4eb1a87f37a08e29ff000002'), 'mongopersist_test')
+       
+             >>> foo._p_oid
+             DBRef('Foo', ObjectId('4eb1a87f37a08e29ff000002'), 'mongopersist_test')
+             >>> pprint.pprint(list(conn[DBNAME]['Foo'].find()))
+             [{u'_id': ObjectId('4eb1a96c37a08e2a7b000002')}]
+       
+           The next time the object simply returns its reference:
+       
+             >>> writer.get_persistent_state(foo, [])
+             DBRef('Foo', ObjectId('4eb1a87f37a08e29ff000002'), 'mongopersist_test')
+             >>> pprint.pprint(list(conn[DBNAME]['Foo'].find()))
+             [{u'_id': ObjectId('4eb1a96c37a08e2a7b000002')}]
+           """
+       
+       
+    1: def doctest_ObjectWriter_get_state_MONGO_NATIVE_TYPES():
+           """ObjectWriter: get_state(): Mongo-native Types
+       
+             >>> writer = serialize.ObjectWriter(None)
+             >>> writer.get_state(1)
+             1
+             >>> writer.get_state(1.0)
+             1.0
+             >>> writer.get_state(u'Test')
+             u'Test'
+             >>> writer.get_state(datetime.datetime(2011, 11, 1, 12, 0, 0))
+             datetime.datetime(2011, 11, 1, 12, 0, 0)
+             >>> print writer.get_state(None)
+             None
+             >>> writer.get_state(objectid.ObjectId('4e7ddf12e138237403000000'))
+             ObjectId('4e7ddf12e138237403000000')
+             >>> writer.get_state(dbref.DBRef('4e7ddf12e138237403000000', 'test'))
+             DBRef('4e7ddf12e138237403000000', 'test')
+           """
+       
+    1: def doctest_ObjectWriter_get_state_types():
+           """ObjectWriter: get_state(): types (type, class)
+       
+             >>> writer = serialize.ObjectWriter(None)
+             >>> writer.get_state(Top)
+             {'path': 'mongopersist.tests.test_serialize.Top', '_py_type': 'type'}
+             >>> writer.get_state(str)
+             {'path': '__builtin__.str', '_py_type': 'type'}
+           """
+       
+    1: def doctest_ObjectWriter_get_state_sequences():
+           """ObjectWriter: get_state(): sequences (tuple, list, PersistentList)
+       
+           We convert any sequence into a simple list, since Mongo supports that
+           type natively. But also reduce any sub-objects.
+       
+             >>> class Number(object):
+             ...     def __init__(self, num):
+             ...         self.num = num
+       
+             >>> writer = serialize.ObjectWriter(None)
+             >>> writer.get_state((1, '2', Number(3)))
+             [1, '2', {'num': 3, '_py_type': '__main__.Number'}]
+             >>> writer.get_state([1, '2', Number(3)])
+             [1, '2', {'num': 3, '_py_type': '__main__.Number'}]
+           """
+       
+    1: def doctest_ObjectWriter_get_state_mappings():
+           """ObjectWriter: get_state(): mappings (dict, PersistentDict)
+       
+           We convert any mapping into a simple dict, since Mongo supports that
+           type natively. But also reduce any sub-objects.
+       
+             >>> class Number(object):
+             ...     def __init__(self, num):
+             ...         self.num = num
+       
+             >>> writer = serialize.ObjectWriter(None)
+             >>> writer.get_state({'1': 1, '2': '2', '3': Number(3)})
+             {'1': 1, '3': {'num': 3, '_py_type': '__main__.Number'}, '2': '2'}
+       
+           Unfortunately, Mongo only supports text keys. So whenever we have non-text
+           keys, we need to create a less natural, but consistent structure:
+       
+             >>> writer.get_state({1: 'one', 2: 'two', 3: 'three'})
+             {'dict_data': [(1, 'one'), (2, 'two'), (3, 'three')]}
+           """
+       
+    1: def doctest_ObjectWriter_get_state_Persistent():
+           """ObjectWriter: get_state(): Persistent objects
+       
+             >>> writer = serialize.ObjectWriter(dm)
+       
+             >>> top = Top()
+             >>> writer.get_state(top)
+             DBRef('Top', ObjectId('4eb1aede37a08e2c8d000004'), 'mongopersist_test')
+       
+           But a persistent object can declare that it does not want a separate
+           document:
+       
+             >>> top2 = Top()
+             >>> top2._p_mongo_sub_object = True
+             >>> writer.get_state(top2)
+             {'_py_persistent_type': 'mongopersist.tests.test_serialize.Top'}
+           """
+       
+    1: def doctest_ObjectWriter_store():
+           """ObjectWriter: store()
+       
+             >>> writer = serialize.ObjectWriter(dm)
+       
+           Simply store an object:
+       
+             >>> pprint.pprint(list(conn[DBNAME]['Top'].find()))
+             []
+       
+             >>> top = Top()
+             >>> writer.store(top)
+             DBRef('Top', ObjectId('4eb1b16537a08e2d1a000001'), 'mongopersist_test')
+             >>> pprint.pprint(list(conn[DBNAME]['Top'].find()))
+             [{u'_id': ObjectId('4eb1b17937a08e2d29000001')}]
+       
+           Now that we have an object, storing an object simply means updating the
+           existing document:
+       
+             >>> top.name = 'top'
+             >>> writer.store(top)
+             DBRef('Top', ObjectId('4eb1b16537a08e2d1a000001'), 'mongopersist_test')
+             >>> pprint.pprint(list(conn[DBNAME]['Top'].find()))
+             [{u'_id': ObjectId('4eb1b17937a08e2d29000001'), u'name': u'top'}]
+       
+           """
+       
+    1: def doctest_ObjectWriter_store_with_mongo_store_type():
+           """ObjectWriter: store(): _p_mongo_store_type = True
+       
+             >>> writer = serialize.ObjectWriter(dm)
+       
+             >>> top = Top()
+             >>> top._p_mongo_store_type = True
+             >>> writer.store(top)
+             DBRef('Top', ObjectId('4eb1b16537a08e2d1a000001'), 'mongopersist_test')
+             >>> pprint.pprint(list(conn[DBNAME]['Top'].find()))
+             [{u'_id': ObjectId('4eb1b27437a08e2d7d000003'),
+               u'_py_persistent_type': u'mongopersist.tests.test_serialize.Top'}]
+           """
+       
+    1: def doctest_ObjectWriter_store_with_conflict_detection():
+           """ObjectWriter: store(): conflict detection
+       
+           The writer supports the data manager's conflict detection by storing a
+           serial number, which is effectively the version of the object. The data
+           manager can then use the serial to detect whether a competing transaction
+           has written to the document.
+       
+             >>> dm.detect_conflicts = True
+             >>> writer = serialize.ObjectWriter(dm)
+       
+             >>> top = Top()
+             >>> writer.store(top)
+             DBRef('Top', ObjectId('4eb1b16537a08e2d1a000001'), 'mongopersist_test')
+             >>> pprint.pprint(list(conn[DBNAME]['Top'].find()))
+             [{u'_id': ObjectId('4eb1b31137a08e2d9d000003'), u'_py_serial': 1}]
+           """
+       
+    1: def doctest_ObjectWriter_store_with_new_object_references():
+           """ObjectWriter: store(): new object references
+       
+           When two new objects reference each other, extracting the full state would
+           cause infinite recursion errors. The code protects against that by
+           optionally only creating an initial empty reference document.
+       
+             >>> writer = serialize.ObjectWriter(dm)
+       
+             >>> top = Top()
+             >>> top.foo = Foo()
+             >>> top.foo.top = top
+             >>> writer.store(top)
+             DBRef('Top', ObjectId('4eb1b16537a08e2d1a000001'), 'mongopersist_test')
+             >>> pprint.pprint(list(conn[DBNAME]['Top'].find()))
+             [{u'_id': ObjectId('4eb1b3d337a08e2de7000009'),
+               u'foo': DBRef(u'Foo', ObjectId('4eb1b3d337a08e2de7000008'),
+                             u'mongopersist_test')}]
+           """
+       
+    1: def doctest_ObjectReader_simple_resolve():
+           """ObjectReader: simple_resolve()
+       
+           This methods simply resolves a Python path to the represented object.
+       
+             >>> reader = serialize.ObjectReader(dm)
+             >>> reader.simple_resolve('mongopersist.tests.test_serialize.Top')
+             <class 'mongopersist.tests.test_serialize.Top'>
+           """
+       
+    1: def doctest_ObjectReader_resolve_simple():
+           """ObjectReader: resolve(): simple
+       
+           This methods resolves a collection name to its class. The collection name
+           can be either any arbitrary string or a Python path.
+       
+             >>> reader = serialize.ObjectReader(dm)
+             >>> ref = dbref.DBRef('mongopersist.tests.test_serialize.Top',
+             ...                   '4eb1b3d337a08e2de7000100')
+             >>> reader.resolve(ref)
+             <class 'mongopersist.tests.test_serialize.Top'>
+           """
+       
+    1: def doctest_ObjectReader_resolve_lookup():
+           """ObjectReader: resolve(): lookup
+       
+           If Python path resolution fails, we try to lookup the path from the
+           collection mapping collection names to Python paths.
+       
+             >>> reader = serialize.ObjectReader(dm)
+             >>> ref = dbref.DBRef('Top', '4eb1b3d337a08e2de7000100', DBNAME)
+             >>> reader.resolve(ref)
+             Traceback (most recent call last):
+             ...
+             ImportError: DBRef('Top', '4eb1b3d337a08e2de7000100', 'mongopersist_test')
+       
+           The lookup failed, because there is no map entry yet for the 'Top'
+           collection. The easiest way to create one is with the object writer:
+       
+             >>> top = Top()
+             >>> writer = serialize.ObjectWriter(dm)
+             >>> writer.get_collection_name(top)
+             ('mongopersist_test', 'Top')
+       
+             >>> reader.resolve(ref)
+             <class 'mongopersist.tests.test_serialize.Top'>
+           """
+       
+    1: def doctest_ObjectReader_resolve_lookup_with_multiple_maps():
+           """ObjectReader: resolve(): lookup with multiple maps entries
+       
+           When the collection name to Python path map has multiple entries, things
+           are more interesting. In this case, we need to lookup the object, if it
+           stores its persistent type otherwise we use the first map entry.
+       
+             >>> writer = serialize.ObjectWriter(dm)
+             >>> top = Top()
+             >>> writer.store(top)
+             DBRef('Top', ObjectId('4eb1e0f237a08e38dd000002'), 'mongopersist_test')
+             >>> top2 = Top2()
+             >>> writer.store(top2)
+             DBRef('Top', ObjectId('4eb1e10437a08e38e8000004'), 'mongopersist_test')
+       
+             >>> reader = serialize.ObjectReader(dm)
+             >>> reader.resolve(top._p_oid)
+             <class 'mongopersist.tests.test_serialize.Top'>
+             >>> reader.resolve(top2._p_oid)
+             <class 'mongopersist.tests.test_serialize.Top2'>
+       
+             >>> pprint.pprint(list(conn[DBNAME]['Top'].find()))
+             [{u'_id': ObjectId('4eb1e13337a08e392d000002')},
+              {u'_id': ObjectId('4eb1e13337a08e392d000004'),
+               u'_py_persistent_type': u'mongopersist.tests.test_serialize.Top2'}]
+       
+           If the DBRef does not have an object id, then an import error is raised:
+       
+             >>> reader.resolve(dbref.DBRef('Top', None, 'mongopersist_test'))
+             Traceback (most recent call last):
+             ...
+             ImportError: DBRef('Top', None, 'mongopersist_test')
+           """
+       
+    1: def doctest_ObjectReader_get_non_persistent_object_py_type():
+           """ObjectReader: get_non_persistent_object(): _py_type
+       
+           The simplest case is a document with a _py_type:
+       
+             >>> reader = serialize.ObjectReader(dm)
+             >>> reader.get_non_persistent_object(
+             ...    {'_py_type': 'mongopersist.tests.test_serialize.Simple'}, None)
+             <mongopersist.tests.test_serialize.Simple object at 0x306f410>
+       
+           It is a little bit more interesting when there is some additional state:
+       
+             >>> simple = reader.get_non_persistent_object(
+             ...    {u'_py_type': 'mongopersist.tests.test_serialize.Simple',
+             ...     u'name': u'Here'},
+             ...    None)
+             >>> simple.name
+             u'Here'
+           """
+       
+    1: def doctest_ObjectReader_get_non_persistent_object_py_persistent_type():
+           """ObjectReader: get_non_persistent_object(): _py_persistent_type
+       
+           In this case the document has a _py_persistent_type attribute, which
+           signals a persistent object living in its parent's document:
+       
+             >>> top = Top()
+       
+             >>> reader = serialize.ObjectReader(dm)
+             >>> tier2 = reader.get_non_persistent_object(
+             ...    {'_py_persistent_type': 'mongopersist.tests.test_serialize.Tier2',
+             ...     'name': 'Number 2'},
+             ...    top)
+             >>> tier2
+             <mongopersist.tests.test_serialize.Tier2 object at 0x306f410>
+       
+           We keep track of the containing object, so we can set _p_changed when this
+           object changes.
+       
+             >>> tier2._p_mongo_doc_object
+             <mongopersist.tests.test_serialize.Top object at 0x7fa30b534050>
+             >>> tier2._p_jar
+             <mongopersist.datamanager.MongoDataManager object at 0x7fc3cab375d0>
+           """
+       
+    1: def doctest_ObjectReader_get_non_persistent_object_py_factory():
+           """ObjectReader: get_non_persistent_object(): _py_factory
+       
+           This is the case of last resort. Specify a factory and its arguments:
+       
+             >>> reader = serialize.ObjectReader(dm)
+             >>> top = reader.get_non_persistent_object(
+             ...    {'_py_factory': 'mongopersist.tests.test_serialize.create_top',
+             ...     '_py_factory_args': ('TOP',)},
+             ...    None)
+             >>> top
+             <mongopersist.tests.test_serialize.Top object at 0x306f410>
+             >>> top.name
+             'TOP'
+           """
+       
+    1: def doctest_ObjectReader_get_object_ObjectId():
+           """ObjectReader: get_object(): ObjectId
+       
+           The object id is special and we simply conserve it:
+       
+             >>> reader = serialize.ObjectReader(dm)
+             >>> reader.get_object(
+             ...     objectid.ObjectId('4e827608e13823598d000003'), None)
+             ObjectId('4e827608e13823598d000003')
+           """
+       
+    1: def doctest_ObjectReader_get_object_binary():
+           """ObjectReader: get_object(): binary data
+       
+           Binary data is just converted to a string:
+       
+             >>> reader = serialize.ObjectReader(dm)
+             >>> reader.get_object(binary.Binary('hello'), None)
+             'hello'
+           """
+       
+    1: def doctest_ObjectReader_get_object_dbref():
+           """ObjectReader: get_object(): DBRef
+       
+             >>> writer = serialize.ObjectWriter(dm)
+             >>> top = Top()
+             >>> writer.store(top)
+             DBRef('Top', ObjectId('4eb1e0f237a08e38dd000002'), 'mongopersist_test')
+       
+           Database references load the ghost state of the obejct they represent:
+       
+             >>> reader = serialize.ObjectReader(dm)
+             >>> reader.get_object(top._p_oid, None)
+             <mongopersist.tests.test_serialize.Top object at 0x2801938>
+           """
+       
+    1: def doctest_ObjectReader_get_object_type_ref():
+           """ObjectReader: get_object(): type reference
+       
+           Type references are resolved.
+       
+             >>> reader = serialize.ObjectReader(dm)
+             >>> reader.get_object(
+             ...     {'_py_type': 'type',
+             ...      'path': 'mongopersist.tests.test_serialize.Simple'},
+             ...     None)
+             <class 'mongopersist.tests.test_serialize.Simple'>
+           """
+       
+    1: def doctest_ObjectReader_get_object_instance():
+           """ObjectReader: get_object(): instance
+       
+           Instances are completely loaded:
+       
+             >>> reader = serialize.ObjectReader(dm)
+             >>> simple = reader.get_object(
+             ...     {u'_py_type': 'mongopersist.tests.test_serialize.Simple',
+             ...      u'name': u'easy'},
+             ...     None)
+             >>> simple
+             <mongopersist.tests.test_serialize.Simple object at 0x2bcc950>
+             >>> simple.name
+             u'easy'
+           """
+       
+    1: def doctest_ObjectReader_get_object_sequence():
+           """ObjectReader: get_object(): sequence
+       
+           Sequences become persistent lists with all obejcts deserialized.
+       
+             >>> reader = serialize.ObjectReader(dm)
+             >>> reader.get_object([1, '2', 3.0], None)
+             [1, '2', 3.0]
+           """
+       
+    1: def doctest_ObjectReader_get_object_mapping():
+           """ObjectReader: get_object(): mapping
+       
+           Mappings become persistent dicts with all obejcts deserialized.
+       
+             >>> reader = serialize.ObjectReader(dm)
+             >>> pprint.pprint(reader.get_object({'1': 1, '2': 2, '3': 3}, None))
+             {'1': 1, '3': 3, '2': 2}
+       
+           Since Mongo does not allow for non-string keys, the state for a dict with
+           non-string keys looks different:
+       
+             >>> pprint.pprint(reader.get_object(
+             ...     {'dict_data': [(1, '1'), (2, '2'), (3, '3')]},
+             ...     None))
+             {1: '1', 2: '2', 3: '3'}
+           """
+       
+    1: def doctest_ObjectReader_get_ghost():
+           """ObjectReader: get_ghost()
+       
+             >>> writer = serialize.ObjectWriter(dm)
+             >>> top = Top()
+             >>> writer.store(top)
+             DBRef('Top', ObjectId('4eb1e0f237a08e38dd000002'), 'mongopersist_test')
+       
+           The ghost object is a shell without any loaded object state:
+       
+             >>> reader = serialize.ObjectReader(dm)
+             >>> gobj = reader.get_ghost(top._p_oid)
+             >>> gobj._p_jar
+             <mongopersist.datamanager.MongoDataManager object at 0x2720e50>
+             >>> gobj._p_state
+             0
+       
+           The second time we look up the object, it comes from cache:
+       
+             >>> gobj = reader.get_ghost(top._p_oid)
+             >>> gobj._p_state
+             0
+           """
+       
+    1: def doctest_ObjectReader_set_ghost_state():
+           r"""ObjectReader: set_ghost_state()
+       
+             >>> dm.detect_conflicts = True
+       
+             >>> writer = serialize.ObjectWriter(dm)
+             >>> top = Top()
+             >>> top.name = 'top'
+             >>> writer.store(top)
+             DBRef('Top', ObjectId('4eb1e0f237a08e38dd000002'), 'mongopersist_test')
+       
+           The ghost object is a shell without any loaded object state:
+       
+             >>> reader = serialize.ObjectReader(dm)
+             >>> gobj = reader.get_ghost(top._p_oid)
+             >>> gobj._p_jar
+             <mongopersist.datamanager.MongoDataManager object at 0x2720e50>
+             >>> gobj._p_state
+             0
+       
+           Now load the state:
+       
+             >>> reader.set_ghost_state(gobj)
+             >>> gobj.name
+             u'top'
+             >>> gobj._p_serial
+             '\x00\x00\x00\x00\x00\x00\x00\x01'
+           """
+       
+       
+       
+    1: def doctest_deserialize_persistent_references():
+           """Deserialization o persistent references.
+       
+           The purpose of this test is to demonstrate the proper deserialization of
+           persistent object references.
+       
+           Let's create a simple object hierarchy:
+       
+             >>> top = Top()
+             >>> top.name = 'top'
+             >>> top.foo = Foo()
+             >>> top.foo.name = 'foo'
+       
+             >>> dm.root['top'] = top
+             >>> commit()
+       
+           Let's check that the objects were properly serialized.
+       
+             >>> pprint.pprint(list(conn[DBNAME]['Top'].find()))
+             [{u'_id': ObjectId('4e827608e13823598d000003'),
+               u'foo': DBRef(u'Foo',
+                             ObjectId('4e827608e13823598d000002'),
+                             u'mongopersist_test'),
+               u'name': u'top'}]
+             >>> pprint.pprint(list(conn[DBNAME]['Foo'].find()))
+             [{u'_id': ObjectId('4e8276c3e138235a2e000002'), u'name': u'foo'}]
+       
+           Now we access the objects objects again to see whether they got properly
+           deserialized.
+       
+             >>> top2 = dm.root['top']
+             >>> id(top2) == id(top)
+             False
+             >>> top2.name
+             u'top'
+       
+             >>> id(top2.foo) == id(top.foo)
+             False
+             >>> top2.foo
+             <mongopersist.tests.test_serialize.Foo object at 0x7fb1a0c0b668>
+             >>> top2.foo.name
+             u'foo'
+           """
+       
+       
+    1: def test_suite():
+    1:     return doctest.DocTestSuite(
+    1:         setUp=testing.setUp, tearDown=testing.tearDown,
+    1:         checker=testing.checker,
+    1:         optionflags=testing.OPTIONFLAGS)

Added: mongopersist/trunk/coverage/mongopersist.zope.__init__.cover
===================================================================
--- mongopersist/trunk/coverage/mongopersist.zope.__init__.cover	                        (rev 0)
+++ mongopersist/trunk/coverage/mongopersist.zope.__init__.cover	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1 @@
+    1: # Make a package.

Added: mongopersist/trunk/coverage/mongopersist.zope.container.cover
===================================================================
--- mongopersist/trunk/coverage/mongopersist.zope.container.cover	                        (rev 0)
+++ mongopersist/trunk/coverage/mongopersist.zope.container.cover	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,249 @@
+       ##############################################################################
+       #
+       # Copyright (c) 2011 Zope Foundation and Contributors.
+       # All Rights Reserved.
+       #
+       # This software is subject to the provisions of the Zope Public License,
+       # Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+       # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+       # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+       # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+       # FOR A PARTICULAR PURPOSE.
+       #
+       ##############################################################################
+    1: """Mongo Persistence Zope Containers"""
+    1: import UserDict
+    1: import persistent
+    1: import pymongo.dbref
+    1: import zope.component
+    1: from rwproperty import getproperty, setproperty
+    1: from zope.container import contained, sample
+       
+    1: from mongopersist import interfaces, serialize
+       
+       
+    2: class MongoContained(contained.Contained):
+       
+    1:     @getproperty
+           def __name__(self):
+    5:         return getattr(self, '_v_key', None)
+    1:     @setproperty
+           def __name__(self, value):
+    1:         setattr(self, '_v_key', value)
+       
+    1:     @getproperty
+           def __parent__(self):
+    5:         return getattr(self, '_v_parent', None)
+    1:     @setproperty
+           def __parent__(self, value):
+    1:         setattr(self, '_v_parent', value)
+       
+       
+    2: class SimpleMongoContainer(sample.SampleContainer, persistent.Persistent):
+       
+    1:     def __getstate__(self):
+    5:         state = super(SimpleMongoContainer, self).__getstate__()
+    5:         state['data'] = state.pop('_SampleContainer__data')
+    5:         return state
+       
+    1:     def __setstate__(self, state):
+    4:         state['_SampleContainer__data'] = state.pop('data', {})
+    4:         super(SimpleMongoContainer, self).__setstate__(state)
+       
+    1:     def __getitem__(self, key):
+   13:         obj = super(SimpleMongoContainer, self).__getitem__(key)
+   12:         obj._v_key = key
+   12:         obj._v_parent = self
+   12:         return obj
+       
+    1:     def get(self, key, default=None):
+               '''See interface `IReadContainer`'''
+    3:         obj = super(SimpleMongoContainer, self).get(key, default)
+    3:         if obj is not default:
+    1:             obj._v_key = key
+    1:             obj._v_parent = self
+    3:         return obj
+       
+    1:     def items(self):
+    2:         items = super(SimpleMongoContainer, self).items()
+    4:         for key, obj in items:
+    2:             obj._v_key = key
+    2:             obj._v_parent = self
+    2:         return items
+       
+    1:     def values(self):
+    2:         return [v for k, v in self.items()]
+       
+    1:     def __setitem__(self, key, object):
+    2:         super(SimpleMongoContainer, self).__setitem__(key, object)
+    2:         self._p_changed = True
+       
+    1:     def __delitem__(self, key):
+    1:         super(SimpleMongoContainer, self).__delitem__(key)
+    1:         self._p_changed = True
+       
+    2: class MongoContainer(contained.Contained,
+    1:                      persistent.Persistent,
+    1:                      UserDict.DictMixin):
+    1:     _m_database = None
+    1:     _m_collection = None
+    1:     _m_mapping_key = 'key'
+    1:     _m_parent_key = 'parent'
+       
+    1:     def __init__(self, collection=None, database=None,
+    1:                  mapping_key=None, parent_key=None):
+   10:         if collection:
+   10:             self._m_collection = collection
+   10:         if database:
+    1:             self._m_database = database
+   10:         if mapping_key is not None:
+    1:             self._m_mapping_key = mapping_key
+   10:         if parent_key is not None:
+    1:             self._m_parent_key = parent_key
+       
+    1:     @property
+           def _added(self):
+   48:         ann = self._m_jar.annotations.setdefault(self._p_oid or id(self), {})
+   48:         return ann.setdefault('added', {})
+       
+    1:     @property
+           def _deleted(self):
+   46:         ann = self._m_jar.annotations.setdefault(self._p_oid or id(self), {})
+   46:         return ann.setdefault('deleted', {})
+       
+    1:     @property
+           def _m_jar(self):
+               # If the container is in a Mongo storage hierarchy, then getting the
+               # datamanager is easy, otherwise we do an adapter lookup.
+  228:         if interfaces.IMongoDataManager.providedBy(self._p_jar):
+  208:             return self._p_jar
+               else:
+   20:             provider = zope.component.getUtility(
+   20:                 interfaces.IMongoDataManagerProvider)
+   19:             return provider.get()
+       
+    1:     def get_collection(self):
+   27:         db_name = self._m_database or self._m_jar.default_database
+   27:         return self._m_jar._conn[db_name][self._m_collection]
+       
+    1:     def _m_get_parent_key_value(self):
+   48:         if getattr(self, '_p_jar', None) is None:
+    1:             raise ValueError('_p_jar not found.')
+   47:         if interfaces.IMongoDataManager.providedBy(self._p_jar):
+   42:             return self
+               else:
+   50:             return 'zodb-'+''.join("%02x" % ord(x) for x in self._p_oid).strip()
+       
+    1:     def _m_get_items_filter(self):
+   28:         filter = {}
+               # Make sure that we only look through objects that have the mapping
+               # key. Objects not having the mapping key cannot be part of the
+               # collection.
+   28:         if self._m_mapping_key is not None:
+   28:             filter[self._m_mapping_key] = {'$exists': True}
+   28:         if self._m_parent_key is not None:
+   27:             gs = self._m_jar._writer.get_state
+   26:             filter[self._m_parent_key] = gs(self._m_get_parent_key_value())
+   27:         return filter
+       
+    1:     def __getitem__(self, key):
+   12:         if key in self._added:
+    4:             return self._added[key]
+    8:         if key in self._deleted:
+    1:             raise KeyError(key)
+    7:         filter = self._m_get_items_filter()
+    7:         filter[self._m_mapping_key] = key
+    7:         doc = self.get_collection().find_one(filter, fields=())
+    7:         if doc is None:
+    1:             raise KeyError(key)
+    6:         dbref = pymongo.dbref.DBRef(
+    6:             self._m_collection, doc['_id'],
+    6:             self._m_database or self._m_jar.default_database)
+    6:         obj = self._m_jar._reader.get_ghost(dbref)
+    6:         obj._v_key = key
+    6:         obj._v_parent = self
+    6:         return obj
+       
+    1:     def __setitem__(self, key, value):
+               # This call by iteself caues the state to change _p_changed to True.
+   19:         setattr(value, self._m_mapping_key, key)
+   19:         if self._m_parent_key is not None:
+   19:             setattr(value, self._m_parent_key, self._m_get_parent_key_value())
+   19:         self._m_jar.register(value)
+               # Temporarily store the added object, so it is immediately available
+               # via the API.
+   19:         value._v_key = key
+   19:         value._v_parent = self
+   19:         self._added[key] = value
+   19:         self._deleted.pop(key, None)
+       
+    1:     def __delitem__(self, key):
+               # Deleting the object from the database is not our job. We simply
+               # remove it from the dictionary.
+    1:         value = self[key]
+    1:         if self._m_mapping_key is not None:
+    1:             delattr(value, self._m_mapping_key)
+    1:         if self._m_parent_key is not None:
+    1:             delattr(value, self._m_parent_key)
+    1:         self._deleted[key] = value
+    1:         self._added.pop(key, None)
+       
+    1:     def keys(self):
+   13:         filter = self._m_get_items_filter()
+   12:         filter[self._m_mapping_key] = {'$ne': None}
+               keys = [
+   12:             doc[self._m_mapping_key]
+   12:             for doc in self.get_collection().find(filter)
+   18:             if not doc[self._m_mapping_key] in self._deleted]
+   12:         keys += self._added.keys()
+   12:         return keys
+       
+    1:     def raw_find(self, spec=None, *args, **kwargs):
+    3:         if spec is None:
+    1:             spec  = {}
+    3:         spec.update(self._m_get_items_filter())
+    3:         return self.get_collection().find(spec, *args, **kwargs)
+       
+    1:     def find(self, spec=None, fields=None, *args, **kwargs):
+               # If fields were not specified, we only request the oid and the key.
+    2:         fields = tuple(fields or ())
+    2:         fields += (self._m_mapping_key,)
+    2:         result = self.raw_find(spec, fields, *args, **kwargs)
+   10:         for doc in result:
+    8:             dbref = pymongo.dbref.DBRef(
+    8:                 self._m_collection, doc['_id'],
+    8:                 self._m_database or self._m_jar.default_database)
+    8:             obj = self._m_jar._reader.get_ghost(dbref)
+    8:             obj._v_key = doc[self._m_mapping_key]
+    8:             obj._v_parent = self
+    8:             yield obj
+       
+    1:     def raw_find_one(self, spec_or_id=None, *args, **kwargs):
+    5:         if spec_or_id is None:
+    1:             spec_or_id  = {}
+    5:         if not isinstance(spec_or_id, dict):
+    1:             spec_or_id = {'_id': spec_or_id}
+    5:         spec_or_id.update(self._m_get_items_filter())
+    5:         return self.get_collection().find_one(spec_or_id, *args, **kwargs)
+       
+    1:     def find_one(self, spec_or_id=None, fields=None, *args, **kwargs):
+               # If fields were not specified, we only request the oid and the key.
+    4:         fields = tuple(fields or ())
+    4:         fields += (self._m_mapping_key,)
+    4:         doc = self.raw_find_one(spec_or_id, fields, *args, **kwargs)
+    4:         if doc is None:
+    1:             return None
+    3:         dbref = pymongo.dbref.DBRef(
+    3:             self._m_collection, doc['_id'],
+    3:             self._m_database or self._m_jar.default_database)
+    3:         obj = self._m_jar._reader.get_ghost(dbref)
+    3:         obj._v_key = doc[self._m_mapping_key]
+    3:         obj._v_parent = self
+    3:         return obj
+       
+    2: class AllItemsMongoContainer(MongoContainer):
+    1:     _m_parent_key = None
+       
+       
+    2: class SubDocumentMongoContainer(MongoContained, MongoContainer):
+    1:     _p_mongo_sub_object = True

Added: mongopersist/trunk/coverage/mongopersist.zope.tests.__init__.cover
===================================================================
--- mongopersist/trunk/coverage/mongopersist.zope.tests.__init__.cover	                        (rev 0)
+++ mongopersist/trunk/coverage/mongopersist.zope.tests.__init__.cover	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1 @@
+    1: # Make a package.

Added: mongopersist/trunk/coverage/mongopersist.zope.tests.test_container.cover
===================================================================
--- mongopersist/trunk/coverage/mongopersist.zope.tests.test_container.cover	                        (rev 0)
+++ mongopersist/trunk/coverage/mongopersist.zope.tests.test_container.cover	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,566 @@
+       ##############################################################################
+       #
+       # Copyright (c) 2011 Zope Foundation and Contributors.
+       # All Rights Reserved.
+       #
+       # This software is subject to the provisions of the Zope Public License,
+       # Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+       # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+       # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+       # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+       # FOR A PARTICULAR PURPOSE.
+       #
+       ##############################################################################
+    1: """Mongo Persistence Doc Tests"""
+    1: import doctest
+       
+    1: import ZODB
+    1: import ZODB.DemoStorage
+    1: import persistent
+    1: import pymongo
+    1: import re
+    1: import transaction
+    1: import zope.component
+    1: import zope.interface
+    1: from pprint import pprint
+    1: from zope.app.testing import placelesssetup
+    1: from zope.container import contained, btree
+    1: from zope.testing import module, renormalizing
+       
+    1: from mongopersist import datamanager, interfaces, serialize
+    1: from mongopersist.zope import container
+       
+    2: class ApplicationRoot(container.SimpleMongoContainer):
+    1:     _p_mongo_collection = 'root'
+       
+    2: class SimplePerson(contained.Contained, persistent.Persistent):
+    1:     _p_mongo_collection = 'person'
+       
+    1:     def __init__(self, name):
+   20:         self.name = name
+       
+    1:     def __str__(self):
+   52:         return self.name
+       
+    1:     def __repr__(self):
+   26:         return '<%s %s>' %(self.__class__.__name__, self)
+       
+    2: class Person(container.MongoContained, SimplePerson):
+    1:     pass
+       
+       
+    1: def doctest_SimpleMongoContainer_basic():
+           """SimpleMongoContainer: basic
+       
+             >>> cn = 'mongopersist.zope.container.SimpleMongoContainer'
+       
+           Let's add a container to the root:
+       
+             >>> dm.reset()
+             >>> dm.root['c'] = container.SimpleMongoContainer()
+       
+             >>> db = dm._conn[DBNAME]
+             >>> pprint(list(db[cn].find()))
+             [{u'_id': ObjectId('4e7ea146e13823316f000000'), u'data': {}}]
+       
+           As you can see, the serialization is very clean. Next we add a person.
+       
+             >>> dm.root['c'][u'stephan'] = SimplePerson(u'Stephan')
+             >>> dm.root['c'].keys()
+             [u'stephan']
+             >>> dm.root['c'][u'stephan']
+             <SimplePerson Stephan>
+       
+             >>> dm.root['c']['stephan'].__parent__
+             <mongopersist.zope.container.SimpleMongoContainer object at 0x7fec50f86500>
+             >>> dm.root['c']['stephan'].__name__
+             u'stephan'
+       
+           You can also access objects using the ``get()`` method of course:
+       
+             >>> stephan = dm.root['c'].get(u'stephan')
+             >>> stephan.__parent__
+             <mongopersist.zope.container.SimpleMongoContainer object at 0x7fec50f86500>
+             >>> stephan.__name__
+             u'stephan'
+       
+           Let's commit and access the data again:
+       
+             >>> transaction.commit()
+       
+             >>> pprint(list(db['person'].find()))
+             [{u'__name__': u'stephan',
+               u'__parent__':
+                   DBRef(u'mongopersist.zope.container.SimpleMongoContainer',
+                         ObjectId('4e7ddf12e138237403000000'),
+                         u'mongopersist_container_test'),
+               u'_id': ObjectId('4e7ddf12e138237403000000'),
+               u'name': u'Stephan'}]
+       
+             >>> dm.root['c'].keys()
+             [u'stephan']
+             >>> dm.root['c']['stephan'].__parent__
+             <mongopersist.zope.container.SimpleMongoContainer object at 0x7fec50f86500>
+             >>> dm.root['c']['stephan'].__name__
+             u'stephan'
+       
+             >>> dm.root['c'].items()
+             [(u'stephan', <SimplePerson Stephan>)]
+       
+             >>> dm.root['c'].values()
+             [<SimplePerson Stephan>]
+       
+           Now remove the item:
+       
+             >>> del dm.root['c']['stephan']
+       
+           The changes are immediately visible.
+       
+             >>> dm.root['c'].keys()
+             []
+             >>> dm.root['c']['stephan']
+             Traceback (most recent call last):
+             ...
+             KeyError: 'stephan'
+       
+           Make sure it is really gone after committing:
+       
+             >>> transaction.commit()
+             >>> dm.root['c'].keys()
+             []
+           """
+       
+       
+    1: def doctest_MongoContainer_basic():
+           """MongoContainer: basic
+       
+           Let's add a container to the root:
+       
+             >>> transaction.commit()
+             >>> dm.root['c'] = container.MongoContainer('person')
+       
+             >>> db = dm._conn[DBNAME]
+             >>> pprint(list(db['mongopersist.zope.container.MongoContainer'].find()))
+             [{u'_id': ObjectId('4e7ddf12e138237403000000'),
+               u'_m_collection': u'person'}]
+       
+           It is unfortunate that the '_m_collection' attribute is set. This is
+           avoidable using a sub-class.
+       
+             >>> dm.root['c'][u'stephan'] = Person(u'Stephan')
+             >>> dm.root['c'].keys()
+             [u'stephan']
+             >>> dm.root['c'][u'stephan']
+             <Person Stephan>
+       
+             >>> dm.root['c']['stephan'].__parent__
+             <mongopersist.zope.container.MongoContainer object at 0x7fec50f86500>
+             >>> dm.root['c']['stephan'].__name__
+             u'stephan'
+       
+           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()
+       
+             >>> pprint(list(db['person'].find()))
+             [{u'_id': ObjectId('4e7e9d3ae138232d7b000003'),
+               u'key': u'stephan',
+               u'name': u'Stephan',
+               u'parent': DBRef(u'mongopersist.zope.container.MongoContainer',
+                                ObjectId('4e7e9d3ae138232d7b000000'),
+                                u'mongopersist_container_test')}]
+       
+             >>> dm.root['c'].keys()
+             [u'stephan']
+             >>> dm.root['c']['stephan'].__parent__
+             <mongopersist.zope.container.MongoContainer object at 0x7fec50f86500>
+             >>> dm.root['c']['stephan'].__name__
+             'stephan'
+       
+           We get a usual key error, if an object does not exist:
+       
+             >>> dm.root['c']['roy']
+             Traceback (most recent call last):
+             ...
+             KeyError: 'roy'
+       
+           Now remove the item:
+       
+             >>> del dm.root['c']['stephan']
+       
+           The changes are immediately visible.
+       
+             >>> dm.root['c'].keys()
+             []
+             >>> dm.root['c']['stephan']
+             Traceback (most recent call last):
+             ...
+             KeyError: 'stephan'
+       
+           Make sure it is really gone after committing:
+       
+             >>> transaction.commit()
+             >>> dm.root['c'].keys()
+             []
+           """
+       
+    1: def doctest_MongoContainer_constructor():
+           """MongoContainer: constructor
+       
+           The constructor of the MongoContainer class has several advanced arguments
+           that allow customizing the storage options.
+       
+             >>> transaction.commit()
+             >>> c = container.MongoContainer(
+             ...     'person',
+             ...     database = 'testdb',
+             ...     mapping_key = 'name',
+             ...     parent_key = 'site')
+       
+           The database allows you to specify a custom database in which the items
+           are located. Otherwise the datamanager's default database is used.
+       
+             >>> c._m_database
+             'testdb'
+       
+           The mapping key is the key/attribute of the contained items in which their
+           name/key within the mapping is stored.
+       
+             >>> c._m_mapping_key
+             'name'
+       
+           The parent key is the key/attribute in which the parent reference is
+           stored. This is used to suport multiple containers per Mongo collection.
+       
+             >>> c._m_parent_key
+             'site'
+           """
+    1: def doctest_MongoContainer_m_parent_key_value():
+           r"""MongoContainer: _m_parent_key_value()
+       
+           This method is used to extract the parent refernce for the item.
+       
+             >>> c = container.MongoContainer('person')
+       
+           The default implementation requires the container to be in some sort of
+           persistent store, though it does not care whether this store is Mongo or a
+           classic ZODB. This feature allows one to mix and match ZODB and Mongo
+           storage.
+       
+             >>> c._m_get_parent_key_value()
+             Traceback (most recent call last):
+             ...
+             ValueError: _p_jar not found.
+       
+           Now the ZODB case:
+       
+             >>> c._p_jar = object()
+             >>> c._p_oid = '\x00\x00\x00\x00\x00\x00\x00\x01'
+             >>> c._m_get_parent_key_value()
+             'zodb-0000000000000001'
+       
+           And finally the Mongo case:
+       
+             >>> c._p_jar = c._p_oid = None
+             >>> dm.root['people'] = c
+             >>> c._m_get_parent_key_value()
+             <mongopersist.zope.container.MongoContainer object at 0x32deed8>
+       
+           In that final case, the container itself is returned, because upon
+           serialization, we simply look up the dbref.
+           """
+       
+    1: def doctest_MongoContainer_many_items():
+           """MongoContainer: many items
+       
+           Let's create an interesting set of data:
+       
+             >>> transaction.commit()
+             >>> dm.root['people'] = container.MongoContainer('person')
+             >>> dm.root['people'][u'stephan'] = Person(u'Stephan')
+             >>> dm.root['people'][u'roy'] = Person(u'Roy')
+             >>> dm.root['people'][u'roger'] = Person(u'Roger')
+             >>> dm.root['people'][u'adam'] = Person(u'Adam')
+             >>> dm.root['people'][u'albertas'] = Person(u'Albertas')
+             >>> dm.root['people'][u'russ'] = Person(u'Russ')
+       
+           In order for find to work, the data has to be committed:
+       
+             >>> transaction.commit()
+       
+           Let's now search and receive documents as result:
+       
+             >>> sorted(dm.root['people'].keys())
+             [u'adam', u'albertas', u'roger', u'roy', u'russ', u'stephan']
+             >>> dm.root['people'][u'stephan']
+             <Person Stephan>
+             >>> dm.root['people'][u'adam']
+             <Person Adam>
+       """
+       
+    1: def doctest_MongoContainer_find():
+           """MongoContainer: find
+       
+           The Mongo Container supports direct Mongo queries. It does, however,
+           insert the additional container filter arguments and can optionally
+           convert the documents to objects.
+       
+           Let's create an interesting set of data:
+       
+             >>> transaction.commit()
+             >>> dm.root['people'] = container.MongoContainer('person')
+             >>> dm.root['people'][u'stephan'] = Person(u'Stephan')
+             >>> dm.root['people'][u'roy'] = Person(u'Roy')
+             >>> dm.root['people'][u'roger'] = Person(u'Roger')
+             >>> dm.root['people'][u'adam'] = Person(u'Adam')
+             >>> dm.root['people'][u'albertas'] = Person(u'Albertas')
+             >>> dm.root['people'][u'russ'] = Person(u'Russ')
+       
+           In order for find to work, the data has to be committed:
+       
+             >>> transaction.commit()
+       
+           Let's now search and receive documents as result:
+       
+             >>> res = dm.root['people'].raw_find({'name': {'$regex': '^Ro.*'}})
+             >>> pprint(list(res))
+             [{u'_id': ObjectId('4e7eb152e138234158000004'),
+               u'key': u'roy',
+               u'name': u'Roy',
+               u'parent': DBRef(u'mongopersist.zope.container.MongoContainer',
+                                ObjectId('4e7eb152e138234158000000'),
+                                u'mongopersist_container_test')},
+              {u'_id': ObjectId('4e7eb152e138234158000005'),
+               u'key': u'roger',
+               u'name': u'Roger',
+               u'parent': DBRef(u'mongopersist.zope.container.MongoContainer',
+                                ObjectId('4e7eb152e138234158000000'),
+                                u'mongopersist_container_test')}]
+       
+           And now the same query, but this time with object results:
+       
+             >>> res = dm.root['people'].find({'name': {'$regex': '^Ro.*'}})
+             >>> pprint(list(res))
+             [<Person Roy>, <Person Roger>]
+       
+           When no spec is specified, all items are returned:
+       
+             >>> res = dm.root['people'].find()
+             >>> pprint(list(res))
+             [<Person Stephan>, <Person Roy>, <Person Roger>, <Person Adam>,
+              <Person Albertas>, <Person Russ>]
+       
+           You can also search for a single result:
+       
+             >>> res = dm.root['people'].raw_find_one({'name': {'$regex': '^St.*'}})
+             >>> pprint(res)
+             {u'_id': ObjectId('4e7eb259e138234289000003'),
+              u'key': u'stephan',
+              u'name': u'Stephan',
+              u'parent': DBRef(u'mongopersist.zope.container.MongoContainer',
+                               ObjectId('4e7eb259e138234289000000'),
+                               u'mongopersist_container_test')}
+       
+             >>> stephan = dm.root['people'].find_one({'name': {'$regex': '^St.*'}})
+             >>> pprint(stephan)
+             <Person Stephan>
+       
+           If no result is found, ``None`` is returned:
+       
+             >>> dm.root['people'].find_one({'name': {'$regex': '^XXX.*'}})
+       
+           If there is no spec, then simply the first item is returned:
+       
+             >>> dm.root['people'].find_one()
+             <Person Stephan>
+       
+           On the other hand, if the spec is an id, we look for it instead:
+       
+             >>> dm.root['people'].find_one(stephan._p_oid.id)
+             <Person Stephan>
+           """
+       
+    1: def doctest_AllItemsMongoContainer_basic():
+           """AllItemsMongoContainer: basic
+       
+           This type of container returns all items of the collection without regard
+           of a parenting hierarchy.
+       
+           Let's start by creating two person containers that service different
+           purposes:
+       
+             >>> transaction.commit()
+       
+             >>> dm.root['friends'] = container.MongoContainer('person')
+             >>> dm.root['friends'][u'roy'] = Person(u'Roy')
+             >>> dm.root['friends'][u'roger'] = Person(u'Roger')
+       
+             >>> dm.root['family'] = container.MongoContainer('person')
+             >>> dm.root['family'][u'anton'] = Person(u'Anton')
+             >>> dm.root['family'][u'konrad'] = Person(u'Konrad')
+       
+             >>> transaction.commit()
+             >>> sorted(dm.root['friends'].keys())
+             [u'roger', u'roy']
+             >>> sorted(dm.root['family'].keys())
+             [u'anton', u'konrad']
+       
+           Now we can create an all-items-container that allows us to view all
+           people.
+       
+             >>> dm.root['all-people'] = container.AllItemsMongoContainer('person')
+             >>> sorted(dm.root['all-people'].keys())
+             [u'anton', u'konrad', u'roger', u'roy']
+           """
+       
+    1: def doctest_SubDocumentMongoContainer_basic():
+           r"""SubDocumentMongoContainer: basic
+       
+           Sub_document Mongo containers are useful, since they avoid the creation of
+           a commonly trivial collections holding meta-data for the collection
+           object. But they require a root document:
+       
+             >>> dm.reset()
+             >>> dm.root['app_root'] = ApplicationRoot()
+       
+           Let's add a container to the app root:
+       
+             >>> dm.root['app_root']['people'] = \
+             ...     container.SubDocumentMongoContainer('person')
+       
+             >>> transaction.commit()
+             >>> db = dm._conn[DBNAME]
+             >>> pprint(list(db['root'].find()))
+             [{u'_id': ObjectId('4e7ea67be138233711000001'),
+               u'data':
+                {u'people':
+                 {u'_m_collection': u'person',
+                  u'_py_persistent_type':
+                      u'mongopersist.zope.container.SubDocumentMongoContainer'}}}]
+       
+           It is unfortunate that the '_m_collection' attribute is set. This is
+           avoidable using a sub-class. Let's make sure the container can be loaded
+           correctly:
+       
+             >>> dm.root['app_root']['people']
+             <mongopersist.zope.container.SubDocumentMongoContainer ...>
+             >>> dm.root['app_root']['people'].__parent__
+             <mongopersist.zope.tests.test_container.ApplicationRoot object at 0x7f>
+             >>> dm.root['app_root']['people'].__name__
+             'people'
+       
+           Let's add an item to the container:
+       
+             >>> dm.root['app_root']['people'][u'stephan'] = Person(u'Stephan')
+             >>> dm.root['app_root']['people'].keys()
+             [u'stephan']
+             >>> dm.root['app_root']['people'][u'stephan']
+             <Person Stephan>
+       
+             >>> transaction.commit()
+             >>> dm.root['app_root']['people'].keys()
+             [u'stephan']
+           """
+       
+    1: def doctest_MongoContainer_with_ZODB():
+           r"""MongoContainer: with ZODB
+       
+           This test demonstrates how a Mongo Container lives inside a ZODB tree:
+       
+             >>> zodb = ZODB.DB(ZODB.DemoStorage.DemoStorage())
+             >>> root = zodb.open().root()
+             >>> root['app'] = btree.BTreeContainer()
+             >>> root['app']['people'] = container.MongoContainer('person')
+       
+           Let's now commit the transaction and make sure everything is cool.
+       
+             >>> transaction.commit()
+             >>> root = zodb.open().root()
+             >>> root['app']
+             <zope.container.btree.BTreeContainer object at 0x7fbb5842f578>
+             >>> root['app']['people']
+             <mongopersist.zope.container.MongoContainer object at 0x7fd6e23555f0>
+       
+           Trying accessing people fails:
+       
+             >>> root['app']['people'].keys()
+             Traceback (most recent call last):
+             ...
+             ComponentLookupError:
+              (<InterfaceClass mongopersist.interfaces.IMongoDataManagerProvider>, '')
+       
+           This is because we have not told the system how to get a datamanager:
+       
+             >>> class Provider(object):
+             ...     zope.interface.implements(interfaces.IMongoDataManagerProvider)
+             ...     def get(self):
+             ...         return dm
+             >>> zope.component.provideUtility(Provider())
+       
+           So let's try again:
+       
+             >>> root['app']['people'].keys()
+             []
+       
+           Next we create a person object and make sure it gets properly persisted.
+       
+             >>> root['app']['people']['stephan'] = Person(u'Stephan')
+             >>> transaction.commit()
+             >>> root = zodb.open().root()
+             >>> root['app']['people'].keys()
+             [u'stephan']
+       
+             >>> stephan = root['app']['people']['stephan']
+             >>> stephan.__name__
+             'stephan'
+             >>> stephan.__parent__
+             <mongopersist.zope.container.MongoContainer object at 0x7f6b6273b7d0>
+       
+             >>> pprint(list(dm._conn[DBNAME]['person'].find()))
+             [{u'_id': ObjectId('4e7ed795e1382366a0000001'),
+               u'key': u'stephan',
+               u'name': u'Stephan',
+               u'parent': u'zodb-1058e89d27d8afd9'}]
+       
+           Note that we produced a nice hex-presentation of the ZODB's OID.
+           """
+       
+    1: checker = renormalizing.RENormalizing([
+    1:     (re.compile(r'datetime.datetime(.*)'),
+    1:      'datetime.datetime(2011, 10, 1, 9, 45)'),
+    1:     (re.compile(r"ObjectId\('[0-9a-f]*'\)"),
+    1:      "ObjectId('4e7ddf12e138237403000000')"),
+    1:     (re.compile(r"object at 0x[0-9a-f]*>"),
+    1:      "object at 0x001122>"),
+    1:     (re.compile(r"zodb-[0-9a-f].*"),
+    1:      "zodb-01af3b00c5"),
+           ])
+       
+    1: def setUp(test):
+    9:     placelesssetup.setUp(test)
+    9:     module.setUp(test)
+    9:     test.globs['conn'] = pymongo.Connection('localhost', 27017, tz_aware=False)
+    9:     test.globs['DBNAME'] = 'mongopersist_container_test'
+    9:     test.globs['conn'].drop_database(test.globs['DBNAME'])
+    9:     test.globs['dm'] = datamanager.MongoDataManager(
+    9:         test.globs['conn'],
+    9:         default_database=test.globs['DBNAME'],
+    9:         root_database=test.globs['DBNAME'])
+       
+    1: def tearDown(test):
+    9:     placelesssetup.tearDown(test)
+    9:     module.tearDown(test)
+    9:     test.globs['conn'].disconnect()
+    9:     serialize.SERIALIZERS.__init__()
+       
+    1: def test_suite():
+    1:     return doctest.DocTestSuite(
+    1:         setUp=setUp, tearDown=tearDown, checker=checker,
+               optionflags=(doctest.NORMALIZE_WHITESPACE|
+    1:                      doctest.ELLIPSIS|
+    1:                      doctest.REPORT_ONLY_FIRST_FAILURE
+                            #|doctest.REPORT_NDIFF
+                            )
+               )

Added: mongopersist/trunk/coverage/report/all.html
===================================================================
--- mongopersist/trunk/coverage/report/all.html	                        (rev 0)
+++ mongopersist/trunk/coverage/report/all.html	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,31 @@
+
+    <html>
+      <head><title>Test coverage for mongopersist</title>
+      <style type="text/css">
+        a {text-decoration: none; display: block; padding-right: 1em;}
+        a:hover {background: #EFA;}
+        hr {height: 1px; border: none; border-top: 1px solid gray;}
+        .notcovered {background: #FCC;}
+        .footer {margin: 2em; font-size: small; color: gray;}
+      </style>
+      </head>
+      <body><h1>Test coverage for mongopersist</h1>
+      <table>
+    
+<tr><td><a href="mongopersist.html">mongopersist/</a></td> <td style="background: yellow">&nbsp;&nbsp;&nbsp;&nbsp;</td> <td>covered 99% (1 of 695 uncovered)</td></tr>
+<tr><td><a href="mongopersist.testing.html">&nbsp;&nbsp;&nbsp;&nbsp;testing.py</a></td> <td style="background: green">&nbsp;&nbsp;&nbsp;&nbsp;</td> <td>covered 100% (0 of 33 uncovered)</td></tr>
+<tr><td><a href="mongopersist.interfaces.html">&nbsp;&nbsp;&nbsp;&nbsp;interfaces.py</a></td> <td style="background: green">&nbsp;&nbsp;&nbsp;&nbsp;</td> <td>covered 100% (0 of 71 uncovered)</td></tr>
+<tr><td><a href="mongopersist.serialize.html">&nbsp;&nbsp;&nbsp;&nbsp;serialize.py</a></td> <td style="background: yellow">&nbsp;&nbsp;&nbsp;&nbsp;</td> <td>covered 99% (1 of 250 uncovered)</td></tr>
+<tr><td><a href="mongopersist.mapping.html">&nbsp;&nbsp;&nbsp;&nbsp;mapping.py</a></td> <td style="background: green">&nbsp;&nbsp;&nbsp;&nbsp;</td> <td>covered 100% (0 of 36 uncovered)</td></tr>
+<tr><td><a href="mongopersist.datamanager.html">&nbsp;&nbsp;&nbsp;&nbsp;datamanager.py</a></td> <td style="background: green">&nbsp;&nbsp;&nbsp;&nbsp;</td> <td>covered 100% (0 of 124 uncovered)</td></tr>
+<tr><td><a href="mongopersist.zope.html">&nbsp;&nbsp;&nbsp;&nbsp;zope/</a></td> <td style="background: green">&nbsp;&nbsp;&nbsp;&nbsp;</td> <td>covered 100% (0 of 180 uncovered)</td></tr>
+<tr><td><a href="mongopersist.zope.container.html">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;container.py</a></td> <td style="background: green">&nbsp;&nbsp;&nbsp;&nbsp;</td> <td>covered 100% (0 of 179 uncovered)</td></tr>
+<tr><td><a href="mongopersist.zope.__init__.html">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;__init__.py</a></td> <td style="background: green">&nbsp;&nbsp;&nbsp;&nbsp;</td> <td>covered 100% (0 of 1 uncovered)</td></tr>
+<tr><td><a href="mongopersist.__init__.html">&nbsp;&nbsp;&nbsp;&nbsp;__init__.py</a></td> <td style="background: green">&nbsp;&nbsp;&nbsp;&nbsp;</td> <td>covered 100% (0 of 1 uncovered)</td></tr>
+</table><hr/>
+
+      <div class="footer">
+      Generated for revision 5108:5114M on 2011-11-04 17:02:17.574731Z
+      </div>
+    </body>
+    </html>

Added: mongopersist/trunk/coverage/report/mongopersist.__init__.html
===================================================================
--- mongopersist/trunk/coverage/report/mongopersist.__init__.html	                        (rev 0)
+++ mongopersist/trunk/coverage/report/mongopersist.__init__.html	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,26 @@
+
+    <html>
+      <head><title>Test coverage for mongopersist.__init__</title>
+      <style type="text/css">
+        a {text-decoration: none; display: block; padding-right: 1em;}
+        a:hover {background: #EFA;}
+        hr {height: 1px; border: none; border-top: 1px solid gray;}
+        .notcovered {background: #FCC;}
+        .footer {margin: 2em; font-size: small; color: gray;}
+      </style>
+      </head>
+      <body><h1>Test coverage for mongopersist.__init__</h1>
+      <table>
+    
+<tr><td><a href="mongopersist.html">mongopersist/</a></td> <td style="background: yellow">&nbsp;&nbsp;&nbsp;&nbsp;</td> <td>covered 99% (1 of 695 uncovered)</td></tr>
+<tr><td><a href="mongopersist.__init__.html">&nbsp;&nbsp;&nbsp;&nbsp;__init__.py</a></td> <td style="background: green">&nbsp;&nbsp;&nbsp;&nbsp;</td> <td>covered 100% (0 of 1 uncovered)</td></tr>
+</table><hr/>
+<pre>
+    1: <I><FONT COLOR="#B22222"># Make a package.
+</FONT></I></pre>
+
+      <div class="footer">
+      Generated for revision 5108:5114M on 2011-11-04 17:02:17.574731Z
+      </div>
+    </body>
+    </html>

Added: mongopersist/trunk/coverage/report/mongopersist.datamanager.html
===================================================================
--- mongopersist/trunk/coverage/report/mongopersist.datamanager.html	                        (rev 0)
+++ mongopersist/trunk/coverage/report/mongopersist.datamanager.html	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,199 @@
+
+    <html>
+      <head><title>Test coverage for mongopersist.datamanager</title>
+      <style type="text/css">
+        a {text-decoration: none; display: block; padding-right: 1em;}
+        a:hover {background: #EFA;}
+        hr {height: 1px; border: none; border-top: 1px solid gray;}
+        .notcovered {background: #FCC;}
+        .footer {margin: 2em; font-size: small; color: gray;}
+      </style>
+      </head>
+      <body><h1>Test coverage for mongopersist.datamanager</h1>
+      <table>
+    
+<tr><td><a href="mongopersist.html">mongopersist/</a></td> <td style="background: yellow">&nbsp;&nbsp;&nbsp;&nbsp;</td> <td>covered 99% (1 of 695 uncovered)</td></tr>
+<tr><td><a href="mongopersist.datamanager.html">&nbsp;&nbsp;&nbsp;&nbsp;datamanager.py</a></td> <td style="background: green">&nbsp;&nbsp;&nbsp;&nbsp;</td> <td>covered 100% (0 of 124 uncovered)</td></tr>
+</table><hr/>
+<pre>
+       <I><FONT COLOR="#B22222">##############################################################################
+</FONT></I>       <I><FONT COLOR="#B22222">#
+</FONT></I>       <I><FONT COLOR="#B22222"># Copyright (c) 2011 Zope Foundation and Contributors.
+</FONT></I>       <I><FONT COLOR="#B22222"># All Rights Reserved.
+</FONT></I>       <I><FONT COLOR="#B22222">#
+</FONT></I>       <I><FONT COLOR="#B22222"># This software is subject to the provisions of the Zope Public License,
+</FONT></I>       <I><FONT COLOR="#B22222"># Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+</FONT></I>       <I><FONT COLOR="#B22222"># THIS SOFTWARE IS PROVIDED &quot;AS IS&quot; AND ANY AND ALL EXPRESS OR IMPLIED
+</FONT></I>       <I><FONT COLOR="#B22222"># WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+</FONT></I>       <I><FONT COLOR="#B22222"># WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+</FONT></I>       <I><FONT COLOR="#B22222"># FOR A PARTICULAR PURPOSE.
+</FONT></I>       <I><FONT COLOR="#B22222">#
+</FONT></I>       <I><FONT COLOR="#B22222">##############################################################################
+</FONT></I>    1: <B><FONT COLOR="#BC8F8F">&quot;&quot;&quot;Mongo Persistent Data Manager&quot;&quot;&quot;</FONT></B>
+    1: <B><FONT COLOR="#A020F0">from</FONT></B> __future__ <B><FONT COLOR="#A020F0">import</FONT></B> absolute_import
+    1: <B><FONT COLOR="#A020F0">import</FONT></B> UserDict
+    1: <B><FONT COLOR="#A020F0">import</FONT></B> persistent
+    1: <B><FONT COLOR="#A020F0">import</FONT></B> pymongo
+    1: <B><FONT COLOR="#A020F0">import</FONT></B> pymongo.dbref
+    1: <B><FONT COLOR="#A020F0">import</FONT></B> transaction
+    1: <B><FONT COLOR="#A020F0">import</FONT></B> zope.interface
+       
+    1: <B><FONT COLOR="#A020F0">from</FONT></B> mongopersist <B><FONT COLOR="#A020F0">import</FONT></B> interfaces, serialize
+       
+    1: <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">create_conflict_error</FONT></B>(obj, new_doc):
+    3:     <B><FONT COLOR="#A020F0">return</FONT></B> interfaces.ConflictError(
+    3:         None, obj,
+    3:         (new_doc.get(<B><FONT COLOR="#BC8F8F">'_py_serial'</FONT></B>, 0), serialize.u64(obj._p_serial)))
+       
+       
+    2: <B><FONT COLOR="#A020F0">class</FONT></B> Root(UserDict.DictMixin):
+       
+    1:     database=<B><FONT COLOR="#BC8F8F">'mongopersist'</FONT></B>
+    1:     collection = <B><FONT COLOR="#BC8F8F">'persistence_root'</FONT></B>
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">__init__</FONT></B>(self, jar, database=None, collection=None):
+  123:         self._jar = jar
+  123:         <B><FONT COLOR="#A020F0">if</FONT></B> database <B><FONT COLOR="#A020F0">is</FONT></B> <B><FONT COLOR="#A020F0">not</FONT></B> None:
+   58:             self.database = database
+  123:         <B><FONT COLOR="#A020F0">if</FONT></B> collection <B><FONT COLOR="#A020F0">is</FONT></B> <B><FONT COLOR="#A020F0">not</FONT></B> None:
+    2:             self.collection = collection
+  123:         db = self._jar._conn[self.database]
+  123:         self._collection_inst = db[self.collection]
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">__getitem__</FONT></B>(self, key):
+  119:         doc = self._collection_inst.find_one({<B><FONT COLOR="#BC8F8F">'name'</FONT></B>: key})
+  119:         <B><FONT COLOR="#A020F0">if</FONT></B> doc <B><FONT COLOR="#A020F0">is</FONT></B> None:
+   14:             <B><FONT COLOR="#A020F0">raise</FONT></B> KeyError(key)
+  105:         <B><FONT COLOR="#A020F0">return</FONT></B> self._jar.load(doc[<B><FONT COLOR="#BC8F8F">'ref'</FONT></B>])
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">__setitem__</FONT></B>(self, key, value):
+   16:         dbref = self._jar.dump(value)
+   15:         <B><FONT COLOR="#A020F0">if</FONT></B> self.get(key) <B><FONT COLOR="#A020F0">is</FONT></B> <B><FONT COLOR="#A020F0">not</FONT></B> None:
+    1:             <B><FONT COLOR="#A020F0">del</FONT></B> self[key]
+   15:         doc = {<B><FONT COLOR="#BC8F8F">'ref'</FONT></B>: dbref, <B><FONT COLOR="#BC8F8F">'name'</FONT></B>: key}
+   15:         self._collection_inst.insert(doc)
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">__delitem__</FONT></B>(self, key):
+    2:         doc = self._collection_inst.find_one({<B><FONT COLOR="#BC8F8F">'name'</FONT></B>: key})
+    2:         coll = self._jar._conn[doc[<B><FONT COLOR="#BC8F8F">'ref'</FONT></B>].database][doc[<B><FONT COLOR="#BC8F8F">'ref'</FONT></B>].collection]
+    2:         coll.remove(doc[<B><FONT COLOR="#BC8F8F">'ref'</FONT></B>].id)
+    2:         self._collection_inst.remove({<B><FONT COLOR="#BC8F8F">'name'</FONT></B>: key})
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">keys</FONT></B>(self):
+    6:         <B><FONT COLOR="#A020F0">return</FONT></B> [doc[<B><FONT COLOR="#BC8F8F">'name'</FONT></B>] <B><FONT COLOR="#A020F0">for</FONT></B> doc <B><FONT COLOR="#A020F0">in</FONT></B> self._collection_inst.find()]
+       
+       
+    2: <B><FONT COLOR="#A020F0">class</FONT></B> MongoDataManager(object):
+    1:     zope.interface.implements(interfaces.IMongoDataManager)
+       
+    1:     detect_conflicts = False
+    1:     default_database = <B><FONT COLOR="#BC8F8F">'mongopersist'</FONT></B>
+    1:     name_map_collection = <B><FONT COLOR="#BC8F8F">'persistence_name_map'</FONT></B>
+    1:     conflict_error_factory = staticmethod(create_conflict_error)
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">__init__</FONT></B>(self, conn, detect_conflicts=None, default_database=None,
+    1:                  root_database=None, root_collection=None,
+    1:                  name_map_collection=None, conflict_error_factory=None):
+  122:         self._conn = conn
+  122:         self._reader = serialize.ObjectReader(self)
+  122:         self._writer = serialize.ObjectWriter(self)
+  122:         self._registered_objects = []
+  122:         self._loaded_objects = []
+  122:         self._needs_to_join = True
+  122:         self._object_cache = {}
+  122:         self.annotations = {}
+  122:         <B><FONT COLOR="#A020F0">if</FONT></B> detect_conflicts <B><FONT COLOR="#A020F0">is</FONT></B> <B><FONT COLOR="#A020F0">not</FONT></B> None:
+    2:             self.detect_conflicts = detect_conflicts
+  122:         <B><FONT COLOR="#A020F0">if</FONT></B> default_database <B><FONT COLOR="#A020F0">is</FONT></B> <B><FONT COLOR="#A020F0">not</FONT></B> None:
+   57:             self.default_database = default_database
+  122:         <B><FONT COLOR="#A020F0">if</FONT></B> name_map_collection <B><FONT COLOR="#A020F0">is</FONT></B> <B><FONT COLOR="#A020F0">not</FONT></B> None:
+    1:             self.name_map_collection = name_map_collection
+  122:         <B><FONT COLOR="#A020F0">if</FONT></B> conflict_error_factory <B><FONT COLOR="#A020F0">is</FONT></B> <B><FONT COLOR="#A020F0">not</FONT></B> None:
+    1:             self.conflict_error_factory = conflict_error_factory
+  122:         self.transaction_manager = transaction.manager
+  122:         self.root = Root(self, root_database, root_collection)
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">dump</FONT></B>(self, obj):
+   23:         <B><FONT COLOR="#A020F0">return</FONT></B> self._writer.store(obj)
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">load</FONT></B>(self, dbref):
+  109:         <B><FONT COLOR="#A020F0">return</FONT></B> self._reader.get_ghost(dbref)
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">reset</FONT></B>(self):
+   65:         root = self.root
+   65:         self.__init__(self._conn)
+   65:         self.root = root
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">setstate</FONT></B>(self, obj):
+               <I><FONT COLOR="#B22222"># When reading a state from Mongo, we also need to join the
+</FONT></I>               <I><FONT COLOR="#B22222"># transaction, because we keep an active object cache that gets stale
+</FONT></I>               <I><FONT COLOR="#B22222"># after the transaction is complete and must be cleaned.
+</FONT></I>   46:         <B><FONT COLOR="#A020F0">if</FONT></B> self._needs_to_join:
+   29:             self.transaction_manager.get().join(self)
+   29:             self._needs_to_join = False
+   46:         self._reader.set_ghost_state(obj)
+   46:         self._loaded_objects.append(obj)
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">oldstate</FONT></B>(self, obj, tid):
+               <I><FONT COLOR="#B22222"># I cannot find any code using this method. Also, since we do not keep
+</FONT></I>               <I><FONT COLOR="#B22222"># version history, we always raise an error.
+</FONT></I>    1:         <B><FONT COLOR="#A020F0">raise</FONT></B> KeyError(tid)
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">register</FONT></B>(self, obj):
+   58:         <B><FONT COLOR="#A020F0">if</FONT></B> self._needs_to_join:
+   16:             self.transaction_manager.get().join(self)
+   16:             self._needs_to_join = False
+       
+   58:         <B><FONT COLOR="#A020F0">if</FONT></B> obj <B><FONT COLOR="#A020F0">is</FONT></B> <B><FONT COLOR="#A020F0">not</FONT></B> None <B><FONT COLOR="#A020F0">and</FONT></B> obj <B><FONT COLOR="#A020F0">not</FONT></B> <B><FONT COLOR="#A020F0">in</FONT></B> self._registered_objects:
+   56:             self._registered_objects.append(obj)
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">abort</FONT></B>(self, transaction):
+   15:         self.reset()
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">commit</FONT></B>(self, transaction):
+   39:         <B><FONT COLOR="#A020F0">if</FONT></B> <B><FONT COLOR="#A020F0">not</FONT></B> self.detect_conflicts:
+   32:             <B><FONT COLOR="#A020F0">return</FONT></B>
+               <I><FONT COLOR="#B22222"># Check each modified object to see whether Mongo has a new version of
+</FONT></I>               <I><FONT COLOR="#B22222"># the object.
+</FONT></I>   12:         <B><FONT COLOR="#A020F0">for</FONT></B> obj <B><FONT COLOR="#A020F0">in</FONT></B> self._registered_objects:
+                   <I><FONT COLOR="#B22222"># This object is not even added to the database yet, so there
+</FONT></I>                   <I><FONT COLOR="#B22222"># cannot be a conflict.
+</FONT></I>    7:             <B><FONT COLOR="#A020F0">if</FONT></B> obj._p_oid <B><FONT COLOR="#A020F0">is</FONT></B> None:
+    1:                 <B><FONT COLOR="#A020F0">continue</FONT></B>
+    6:             db_name, coll_name = self._writer.get_collection_name(obj)
+    6:             coll = self._conn[db_name][coll_name]
+    6:             new_doc = coll.find_one(obj._p_oid.id, fields=(<B><FONT COLOR="#BC8F8F">'_py_serial'</FONT></B>,))
+    6:             <B><FONT COLOR="#A020F0">if</FONT></B> new_doc <B><FONT COLOR="#A020F0">is</FONT></B> None:
+    1:                 <B><FONT COLOR="#A020F0">continue</FONT></B>
+    5:             <B><FONT COLOR="#A020F0">if</FONT></B> new_doc.get(<B><FONT COLOR="#BC8F8F">'_py_serial'</FONT></B>, 0) != serialize.u64(obj._p_serial):
+    2:                 <B><FONT COLOR="#A020F0">raise</FONT></B> self.conflict_error_factory(obj, new_doc)
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">tpc_begin</FONT></B>(self, transaction):
+   35:         <B><FONT COLOR="#A020F0">pass</FONT></B>
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">tpc_vote</FONT></B>(self, transaction):
+   34:         <B><FONT COLOR="#A020F0">pass</FONT></B>
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">tpc_finish</FONT></B>(self, transaction):
+   37:         written = []
+   92:         <B><FONT COLOR="#A020F0">for</FONT></B> obj <B><FONT COLOR="#A020F0">in</FONT></B> self._registered_objects:
+   55:             <B><FONT COLOR="#A020F0">if</FONT></B> getattr(obj, <B><FONT COLOR="#BC8F8F">'_p_mongo_sub_object'</FONT></B>, False):
+    5:                 obj = obj._p_mongo_doc_object
+   55:             <B><FONT COLOR="#A020F0">if</FONT></B> obj <B><FONT COLOR="#A020F0">in</FONT></B> written:
+    3:                 <B><FONT COLOR="#A020F0">continue</FONT></B>
+   52:             self._writer.store(obj)
+   52:             written.append(obj)
+   37:         self.reset()
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">tpc_abort</FONT></B>(self, transaction):
+    2:         self.abort(transaction)
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">sortKey</FONT></B>(self):
+    6:         <B><FONT COLOR="#A020F0">return</FONT></B> (<B><FONT COLOR="#BC8F8F">'MongoDataManager'</FONT></B>, 0)
+</pre>
+
+      <div class="footer">
+      Generated for revision 5108:5114M on 2011-11-04 17:02:17.574731Z
+      </div>
+    </body>
+    </html>

Added: mongopersist/trunk/coverage/report/mongopersist.html
===================================================================
--- mongopersist/trunk/coverage/report/mongopersist.html	                        (rev 0)
+++ mongopersist/trunk/coverage/report/mongopersist.html	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,29 @@
+
+    <html>
+      <head><title>Test coverage for mongopersist</title>
+      <style type="text/css">
+        a {text-decoration: none; display: block; padding-right: 1em;}
+        a:hover {background: #EFA;}
+        hr {height: 1px; border: none; border-top: 1px solid gray;}
+        .notcovered {background: #FCC;}
+        .footer {margin: 2em; font-size: small; color: gray;}
+      </style>
+      </head>
+      <body><h1>Test coverage for mongopersist</h1>
+      <table>
+    
+<tr><td><a href="mongopersist.html">mongopersist/</a></td> <td style="background: yellow">&nbsp;&nbsp;&nbsp;&nbsp;</td> <td>covered 99% (1 of 695 uncovered)</td></tr>
+<tr><td><a href="mongopersist.serialize.html">&nbsp;&nbsp;&nbsp;&nbsp;serialize.py</a></td> <td style="background: yellow">&nbsp;&nbsp;&nbsp;&nbsp;</td> <td>covered 99% (1 of 250 uncovered)</td></tr>
+<tr><td><a href="mongopersist.__init__.html">&nbsp;&nbsp;&nbsp;&nbsp;__init__.py</a></td> <td style="background: green">&nbsp;&nbsp;&nbsp;&nbsp;</td> <td>covered 100% (0 of 1 uncovered)</td></tr>
+<tr><td><a href="mongopersist.datamanager.html">&nbsp;&nbsp;&nbsp;&nbsp;datamanager.py</a></td> <td style="background: green">&nbsp;&nbsp;&nbsp;&nbsp;</td> <td>covered 100% (0 of 124 uncovered)</td></tr>
+<tr><td><a href="mongopersist.interfaces.html">&nbsp;&nbsp;&nbsp;&nbsp;interfaces.py</a></td> <td style="background: green">&nbsp;&nbsp;&nbsp;&nbsp;</td> <td>covered 100% (0 of 71 uncovered)</td></tr>
+<tr><td><a href="mongopersist.mapping.html">&nbsp;&nbsp;&nbsp;&nbsp;mapping.py</a></td> <td style="background: green">&nbsp;&nbsp;&nbsp;&nbsp;</td> <td>covered 100% (0 of 36 uncovered)</td></tr>
+<tr><td><a href="mongopersist.testing.html">&nbsp;&nbsp;&nbsp;&nbsp;testing.py</a></td> <td style="background: green">&nbsp;&nbsp;&nbsp;&nbsp;</td> <td>covered 100% (0 of 33 uncovered)</td></tr>
+<tr><td><a href="mongopersist.zope.html">&nbsp;&nbsp;&nbsp;&nbsp;zope/</a></td> <td style="background: green">&nbsp;&nbsp;&nbsp;&nbsp;</td> <td>covered 100% (0 of 180 uncovered)</td></tr>
+</table><hr/>
+
+      <div class="footer">
+      Generated for revision 5108:5114M on 2011-11-04 17:02:17.574731Z
+      </div>
+    </body>
+    </html>

Added: mongopersist/trunk/coverage/report/mongopersist.interfaces.html
===================================================================
--- mongopersist/trunk/coverage/report/mongopersist.interfaces.html	                        (rev 0)
+++ mongopersist/trunk/coverage/report/mongopersist.interfaces.html	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,193 @@
+
+    <html>
+      <head><title>Test coverage for mongopersist.interfaces</title>
+      <style type="text/css">
+        a {text-decoration: none; display: block; padding-right: 1em;}
+        a:hover {background: #EFA;}
+        hr {height: 1px; border: none; border-top: 1px solid gray;}
+        .notcovered {background: #FCC;}
+        .footer {margin: 2em; font-size: small; color: gray;}
+      </style>
+      </head>
+      <body><h1>Test coverage for mongopersist.interfaces</h1>
+      <table>
+    
+<tr><td><a href="mongopersist.html">mongopersist/</a></td> <td style="background: yellow">&nbsp;&nbsp;&nbsp;&nbsp;</td> <td>covered 99% (1 of 695 uncovered)</td></tr>
+<tr><td><a href="mongopersist.interfaces.html">&nbsp;&nbsp;&nbsp;&nbsp;interfaces.py</a></td> <td style="background: green">&nbsp;&nbsp;&nbsp;&nbsp;</td> <td>covered 100% (0 of 71 uncovered)</td></tr>
+</table><hr/>
+<pre>
+       <I><FONT COLOR="#B22222">##############################################################################
+</FONT></I>       <I><FONT COLOR="#B22222">#
+</FONT></I>       <I><FONT COLOR="#B22222"># Copyright (c) 2011 Zope Foundation and Contributors.
+</FONT></I>       <I><FONT COLOR="#B22222"># All Rights Reserved.
+</FONT></I>       <I><FONT COLOR="#B22222">#
+</FONT></I>       <I><FONT COLOR="#B22222"># This software is subject to the provisions of the Zope Public License,
+</FONT></I>       <I><FONT COLOR="#B22222"># Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+</FONT></I>       <I><FONT COLOR="#B22222"># THIS SOFTWARE IS PROVIDED &quot;AS IS&quot; AND ANY AND ALL EXPRESS OR IMPLIED
+</FONT></I>       <I><FONT COLOR="#B22222"># WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+</FONT></I>       <I><FONT COLOR="#B22222"># WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+</FONT></I>       <I><FONT COLOR="#B22222"># FOR A PARTICULAR PURPOSE.
+</FONT></I>       <I><FONT COLOR="#B22222">#
+</FONT></I>       <I><FONT COLOR="#B22222">##############################################################################
+</FONT></I>    1: <B><FONT COLOR="#BC8F8F">&quot;&quot;&quot;Mongo Persistence Interfaces&quot;&quot;&quot;</FONT></B>
+    1: <B><FONT COLOR="#A020F0">from</FONT></B> __future__ <B><FONT COLOR="#A020F0">import</FONT></B> absolute_import
+    1: <B><FONT COLOR="#A020F0">import</FONT></B> datetime
+    1: <B><FONT COLOR="#A020F0">import</FONT></B> persistent.interfaces
+    1: <B><FONT COLOR="#A020F0">import</FONT></B> transaction.interfaces
+    1: <B><FONT COLOR="#A020F0">import</FONT></B> types
+    1: <B><FONT COLOR="#A020F0">import</FONT></B> zope.interface
+    1: <B><FONT COLOR="#A020F0">import</FONT></B> zope.schema
+    1: <B><FONT COLOR="#A020F0">from</FONT></B> pymongo <B><FONT COLOR="#A020F0">import</FONT></B> objectid, dbref
+       
+       MONGO_NATIVE_TYPES = (
+    1:     int, float, unicode, datetime.datetime, types.NoneType,
+    1:     objectid.ObjectId, dbref.DBRef)
+       
+    2: <B><FONT COLOR="#A020F0">class</FONT></B> ConflictError(transaction.interfaces.TransientError):
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">__init__</FONT></B>(self, message=None, object=None, serials=None):
+    3:         self.message = message <B><FONT COLOR="#A020F0">or</FONT></B> <B><FONT COLOR="#BC8F8F">&quot;database conflict error&quot;</FONT></B>
+    3:         self.object = object
+    3:         self.serials = serials
+       
+    1:     @property
+           <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">new_serial</FONT></B>(self):
+    6:         <B><FONT COLOR="#A020F0">return</FONT></B> self.serials[0]
+       
+    1:     @property
+           <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">old_serial</FONT></B>(self):
+    6:         <B><FONT COLOR="#A020F0">return</FONT></B> self.serials[1]
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">__str__</FONT></B>(self):
+               extras = [
+    6:             <B><FONT COLOR="#BC8F8F">'oid %s'</FONT></B> %self.object._p_oid,
+    6:             <B><FONT COLOR="#BC8F8F">'class %s'</FONT></B> %self.object.__class__.__name__,
+    6:             <B><FONT COLOR="#BC8F8F">'start serial %s'</FONT></B> %self.old_serial,
+    6:             <B><FONT COLOR="#BC8F8F">'current serial %s'</FONT></B> %self.new_serial]
+    6:         <B><FONT COLOR="#A020F0">return</FONT></B> <B><FONT COLOR="#BC8F8F">&quot;%s (%s)&quot;</FONT></B> % (self.message, <B><FONT COLOR="#BC8F8F">&quot;, &quot;</FONT></B>.join(extras))
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">__repr__</FONT></B>(self):
+    1:         <B><FONT COLOR="#A020F0">return</FONT></B> <B><FONT COLOR="#BC8F8F">'%s: %s'</FONT></B> %(self.__class__.__name__, self)
+       
+       
+    2: <B><FONT COLOR="#A020F0">class</FONT></B> CircularReferenceError(Exception):
+    1:     <B><FONT COLOR="#A020F0">pass</FONT></B>
+       
+    2: <B><FONT COLOR="#A020F0">class</FONT></B> IObjectSerializer(zope.interface.Interface):
+           <B><FONT COLOR="#BC8F8F">&quot;&quot;&quot;An object serializer allows for custom serialization output for
+    1:     objects.&quot;&quot;&quot;</FONT></B>
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">can_read</FONT></B>(state):
+               <B><FONT COLOR="#BC8F8F">&quot;&quot;&quot;Returns a boolean indicating whether this serializer can deserialize
+               this state.&quot;&quot;&quot;</FONT></B>
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">get_object</FONT></B>(state):
+               <B><FONT COLOR="#BC8F8F">&quot;&quot;&quot;Convert the state to an object.&quot;&quot;&quot;</FONT></B>
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">can_write</FONT></B>(obj):
+               <B><FONT COLOR="#BC8F8F">&quot;&quot;&quot;Returns a boolean indicating whether this serializer can serialize
+               this object.&quot;&quot;&quot;</FONT></B>
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">get_state</FONT></B>(obj):
+               <B><FONT COLOR="#BC8F8F">&quot;&quot;&quot;Convert the object to a state/document.&quot;&quot;&quot;</FONT></B>
+       
+       
+    2: <B><FONT COLOR="#A020F0">class</FONT></B> IObjectWriter(zope.interface.Interface):
+    1:     <B><FONT COLOR="#BC8F8F">&quot;&quot;&quot;The object writer stores an object in the database.&quot;&quot;&quot;</FONT></B>
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">get_non_persistent_state</FONT></B>(obj, seen):
+               <B><FONT COLOR="#BC8F8F">&quot;&quot;&quot;Convert a non-persistent object to a Mongo state/document.&quot;&quot;&quot;</FONT></B>
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">get_persistent_state</FONT></B>(obj, seen):
+               <B><FONT COLOR="#BC8F8F">&quot;&quot;&quot;Convert a persistent object to a Mongo state/document.&quot;&quot;&quot;</FONT></B>
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">get_state</FONT></B>(obj, seen=None):
+               <B><FONT COLOR="#BC8F8F">&quot;&quot;&quot;Convert an arbitrary object to a Mongo state/document.
+       
+               A ``CircularReferenceError`` is raised, if a non-persistent loop is
+               detected.
+               &quot;&quot;&quot;</FONT></B>
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">store</FONT></B>(obj):
+               <B><FONT COLOR="#BC8F8F">&quot;&quot;&quot;Store an object in the database.&quot;&quot;&quot;</FONT></B>
+       
+       
+    2: <B><FONT COLOR="#A020F0">class</FONT></B> IObjectReader(zope.interface.Interface):
+    1:     <B><FONT COLOR="#BC8F8F">&quot;&quot;&quot;The object reader reads an object from the database.&quot;&quot;&quot;</FONT></B>
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">resolve</FONT></B>(path):
+               <B><FONT COLOR="#BC8F8F">&quot;&quot;&quot;Resolve a path to a class.
+       
+               The path can be any string. It is the responsibility of the resolver
+               to maintain the mapping from path to class.
+               &quot;&quot;&quot;</FONT></B>
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">get_object</FONT></B>(state, obj):
+               <B><FONT COLOR="#BC8F8F">&quot;&quot;&quot;Get an object from the given state.
+       
+               The ``obj`` is the Mongo document of which the created object is part
+               of.
+               &quot;&quot;&quot;</FONT></B>
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">set_ghost_state</FONT></B>(obj):
+               <B><FONT COLOR="#BC8F8F">&quot;&quot;&quot;Convert a ghosted object to an active object by loading its state.
+               &quot;&quot;&quot;</FONT></B>
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">get_ghost</FONT></B>(coll_name, oid):
+               <B><FONT COLOR="#BC8F8F">&quot;&quot;&quot;Get the ghosted version of the object.
+               &quot;&quot;&quot;</FONT></B>
+       
+       
+    2: <B><FONT COLOR="#A020F0">class</FONT></B> IMongoDataManager(persistent.interfaces.IPersistentDataManager):
+    1:     <B><FONT COLOR="#BC8F8F">&quot;&quot;&quot;A persistent data manager that stores data in Mongo.&quot;&quot;&quot;</FONT></B>
+       
+    1:     root = zope.interface.Attribute(
+    1:         <B><FONT COLOR="#BC8F8F">&quot;&quot;&quot;Get the root object, which is a mapping.&quot;&quot;&quot;</FONT></B>)
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">reset</FONT></B>():
+               <B><FONT COLOR="#BC8F8F">&quot;&quot;&quot;Reset the datamanager for the next transaction.&quot;&quot;&quot;</FONT></B>
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">dump</FONT></B>(obj):
+               <B><FONT COLOR="#BC8F8F">&quot;&quot;&quot;Store the object to Mongo and return its DBRef.&quot;&quot;&quot;</FONT></B>
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">load</FONT></B>(dbref):
+               <B><FONT COLOR="#BC8F8F">&quot;&quot;&quot;Load the object from Mongo by using its DBRef.
+       
+               Note: The returned object is in the ghost state.
+               &quot;&quot;&quot;</FONT></B>
+       
+       
+    2: <B><FONT COLOR="#A020F0">class</FONT></B> IMongoConnectionPool(zope.interface.Interface):
+    1:     <B><FONT COLOR="#BC8F8F">&quot;&quot;&quot;MongoDB connection pool&quot;&quot;&quot;</FONT></B>
+       
+    1:     connection = zope.interface.Attribute(<B><FONT COLOR="#BC8F8F">'MongoDBConnection instance'</FONT></B>)
+       
+    1:     host = zope.schema.TextLine(
+    1:         title=u<B><FONT COLOR="#BC8F8F">'MongoDB Server Hostname (without protocol)'</FONT></B>,
+    1:         description=u<B><FONT COLOR="#BC8F8F">'MongoDB Server Hostname or IPv4 address'</FONT></B>,
+    1:         default=u<B><FONT COLOR="#BC8F8F">'localhost'</FONT></B>,
+    1:         required=True)
+       
+    1:     port = zope.schema.Int(
+    1:         title=u<B><FONT COLOR="#BC8F8F">'MongoDB Server Port'</FONT></B>,
+    1:         description=u<B><FONT COLOR="#BC8F8F">'MongoDB Server Port'</FONT></B>,
+    1:         default=27017,
+    1:         required=True)
+       
+       
+    2: <B><FONT COLOR="#A020F0">class</FONT></B> IMongoDataManagerProvider(zope.interface.Interface):
+           <B><FONT COLOR="#BC8F8F">&quot;&quot;&quot;Utility to get a mongo data manager.
+       
+           Implementations of this utility ususally maintain connection information
+           and ensure that there is one consistent datamanager per thread.
+    1:     &quot;&quot;&quot;</FONT></B>
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">get</FONT></B>():
+               <B><FONT COLOR="#BC8F8F">&quot;&quot;&quot;Return a mongo data manager.&quot;&quot;&quot;</FONT></B>
+</pre>
+
+      <div class="footer">
+      Generated for revision 5108:5114M on 2011-11-04 17:02:17.574731Z
+      </div>
+    </body>
+    </html>

Added: mongopersist/trunk/coverage/report/mongopersist.mapping.html
===================================================================
--- mongopersist/trunk/coverage/report/mongopersist.mapping.html	                        (rev 0)
+++ mongopersist/trunk/coverage/report/mongopersist.mapping.html	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,89 @@
+
+    <html>
+      <head><title>Test coverage for mongopersist.mapping</title>
+      <style type="text/css">
+        a {text-decoration: none; display: block; padding-right: 1em;}
+        a:hover {background: #EFA;}
+        hr {height: 1px; border: none; border-top: 1px solid gray;}
+        .notcovered {background: #FCC;}
+        .footer {margin: 2em; font-size: small; color: gray;}
+      </style>
+      </head>
+      <body><h1>Test coverage for mongopersist.mapping</h1>
+      <table>
+    
+<tr><td><a href="mongopersist.html">mongopersist/</a></td> <td style="background: yellow">&nbsp;&nbsp;&nbsp;&nbsp;</td> <td>covered 99% (1 of 695 uncovered)</td></tr>
+<tr><td><a href="mongopersist.mapping.html">&nbsp;&nbsp;&nbsp;&nbsp;mapping.py</a></td> <td style="background: green">&nbsp;&nbsp;&nbsp;&nbsp;</td> <td>covered 100% (0 of 36 uncovered)</td></tr>
+</table><hr/>
+<pre>
+       <I><FONT COLOR="#B22222">##############################################################################
+</FONT></I>       <I><FONT COLOR="#B22222">#
+</FONT></I>       <I><FONT COLOR="#B22222"># Copyright (c) 2011 Zope Foundation and Contributors.
+</FONT></I>       <I><FONT COLOR="#B22222"># All Rights Reserved.
+</FONT></I>       <I><FONT COLOR="#B22222">#
+</FONT></I>       <I><FONT COLOR="#B22222"># This software is subject to the provisions of the Zope Public License,
+</FONT></I>       <I><FONT COLOR="#B22222"># Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+</FONT></I>       <I><FONT COLOR="#B22222"># THIS SOFTWARE IS PROVIDED &quot;AS IS&quot; AND ANY AND ALL EXPRESS OR IMPLIED
+</FONT></I>       <I><FONT COLOR="#B22222"># WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+</FONT></I>       <I><FONT COLOR="#B22222"># WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+</FONT></I>       <I><FONT COLOR="#B22222"># FOR A PARTICULAR PURPOSE.
+</FONT></I>       <I><FONT COLOR="#B22222">#
+</FONT></I>       <I><FONT COLOR="#B22222">##############################################################################
+</FONT></I>    1: <B><FONT COLOR="#BC8F8F">&quot;&quot;&quot;Mongo Mapping Implementations&quot;&quot;&quot;</FONT></B>
+    1: <B><FONT COLOR="#A020F0">import</FONT></B> UserDict
+    1: <B><FONT COLOR="#A020F0">import</FONT></B> pymongo
+       
+    1: <B><FONT COLOR="#A020F0">from</FONT></B> mongopersist <B><FONT COLOR="#A020F0">import</FONT></B> interfaces
+       
+    2: <B><FONT COLOR="#A020F0">class</FONT></B> MongoCollectionMapping(UserDict.DictMixin, object):
+    1:     __mongo_database__ = None
+    1:     __mongo_collection__ = None
+    1:     __mongo_mapping_key__ = <B><FONT COLOR="#BC8F8F">'key'</FONT></B>
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">__init__</FONT></B>(self, jar):
+    8:         self._m_jar = jar
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">__mongo_filter__</FONT></B>(self):
+    9:         <B><FONT COLOR="#A020F0">return</FONT></B> {}
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">get_mongo_collection</FONT></B>(self):
+   14:         db_name = self.__mongo_database__ <B><FONT COLOR="#A020F0">or</FONT></B> self._m_jar.default_database
+   14:         <B><FONT COLOR="#A020F0">return</FONT></B> self._m_jar._conn[db_name][self.__mongo_collection__]
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">__getitem__</FONT></B>(self, key):
+    6:         filter = self.__mongo_filter__()
+    6:         filter[self.__mongo_mapping_key__] = key
+    6:         doc = self.get_mongo_collection().find_one(filter)
+    6:         <B><FONT COLOR="#A020F0">if</FONT></B> doc <B><FONT COLOR="#A020F0">is</FONT></B> None:
+    2:             <B><FONT COLOR="#A020F0">raise</FONT></B> KeyError(key)
+    4:         db_name = self.__mongo_database__ <B><FONT COLOR="#A020F0">or</FONT></B> self._m_jar.default_database
+    4:         dbref = pymongo.dbref.DBRef(
+    4:             self.__mongo_collection__, doc[<B><FONT COLOR="#BC8F8F">'_id'</FONT></B>], db_name)
+    4:         <B><FONT COLOR="#A020F0">return</FONT></B> self._m_jar._reader.get_ghost(dbref)
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">__setitem__</FONT></B>(self, key, value):
+               <I><FONT COLOR="#B22222"># Even though setting the attribute should register the object with
+</FONT></I>               <I><FONT COLOR="#B22222"># the data manager, the value might not be in the DB at all at this
+</FONT></I>               <I><FONT COLOR="#B22222"># point, so registering it manually ensures that new objects get added.
+</FONT></I>    2:         self._m_jar.register(value)
+    2:         setattr(value, self.__mongo_mapping_key__, key)
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">__delitem__</FONT></B>(self, key):
+               <I><FONT COLOR="#B22222"># Deleting the object from the database is not our job. We simply
+</FONT></I>               <I><FONT COLOR="#B22222"># remove it from the dictionary.
+</FONT></I>    1:         value = self[key]
+    1:         setattr(value, self.__mongo_mapping_key__, None)
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">keys</FONT></B>(self):
+    7:         filter = self.__mongo_filter__()
+    7:         filter[self.__mongo_mapping_key__] = {<B><FONT COLOR="#BC8F8F">'$ne'</FONT></B>: None}
+               <B><FONT COLOR="#A020F0">return</FONT></B> [
+    7:             doc[self.__mongo_mapping_key__]
+   15:             <B><FONT COLOR="#A020F0">for</FONT></B> doc <B><FONT COLOR="#A020F0">in</FONT></B> self.get_mongo_collection().find(filter)]
+</pre>
+
+      <div class="footer">
+      Generated for revision 5108:5114M on 2011-11-04 17:02:17.574731Z
+      </div>
+    </body>
+    </html>

Added: mongopersist/trunk/coverage/report/mongopersist.serialize.html
===================================================================
--- mongopersist/trunk/coverage/report/mongopersist.serialize.html	                        (rev 0)
+++ mongopersist/trunk/coverage/report/mongopersist.serialize.html	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,431 @@
+
+    <html>
+      <head><title>Test coverage for mongopersist.serialize</title>
+      <style type="text/css">
+        a {text-decoration: none; display: block; padding-right: 1em;}
+        a:hover {background: #EFA;}
+        hr {height: 1px; border: none; border-top: 1px solid gray;}
+        .notcovered {background: #FCC;}
+        .footer {margin: 2em; font-size: small; color: gray;}
+      </style>
+      </head>
+      <body><h1>Test coverage for mongopersist.serialize</h1>
+      <table>
+    
+<tr><td><a href="mongopersist.html">mongopersist/</a></td> <td style="background: yellow">&nbsp;&nbsp;&nbsp;&nbsp;</td> <td>covered 99% (1 of 695 uncovered)</td></tr>
+<tr><td><a href="mongopersist.serialize.html">&nbsp;&nbsp;&nbsp;&nbsp;serialize.py</a></td> <td style="background: yellow">&nbsp;&nbsp;&nbsp;&nbsp;</td> <td>covered 99% (1 of 250 uncovered)</td></tr>
+</table><hr/>
+<pre>
+       <I><FONT COLOR="#B22222">##############################################################################
+</FONT></I>       <I><FONT COLOR="#B22222">#
+</FONT></I>       <I><FONT COLOR="#B22222"># Copyright (c) 2011 Zope Foundation and Contributors.
+</FONT></I>       <I><FONT COLOR="#B22222"># All Rights Reserved.
+</FONT></I>       <I><FONT COLOR="#B22222">#
+</FONT></I>       <I><FONT COLOR="#B22222"># This software is subject to the provisions of the Zope Public License,
+</FONT></I>       <I><FONT COLOR="#B22222"># Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+</FONT></I>       <I><FONT COLOR="#B22222"># THIS SOFTWARE IS PROVIDED &quot;AS IS&quot; AND ANY AND ALL EXPRESS OR IMPLIED
+</FONT></I>       <I><FONT COLOR="#B22222"># WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+</FONT></I>       <I><FONT COLOR="#B22222"># WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+</FONT></I>       <I><FONT COLOR="#B22222"># FOR A PARTICULAR PURPOSE.
+</FONT></I>       <I><FONT COLOR="#B22222">#
+</FONT></I>       <I><FONT COLOR="#B22222">##############################################################################
+</FONT></I>    1: <B><FONT COLOR="#BC8F8F">&quot;&quot;&quot;Object Serialization for Mongo/BSON&quot;&quot;&quot;</FONT></B>
+    1: <B><FONT COLOR="#A020F0">from</FONT></B> __future__ <B><FONT COLOR="#A020F0">import</FONT></B> absolute_import
+    1: <B><FONT COLOR="#A020F0">import</FONT></B> copy_reg
+    1: <B><FONT COLOR="#A020F0">import</FONT></B> struct
+       
+    1: <B><FONT COLOR="#A020F0">import</FONT></B> lru
+    1: <B><FONT COLOR="#A020F0">import</FONT></B> persistent.interfaces
+    1: <B><FONT COLOR="#A020F0">import</FONT></B> persistent.dict
+    1: <B><FONT COLOR="#A020F0">import</FONT></B> persistent.list
+    1: <B><FONT COLOR="#A020F0">import</FONT></B> pymongo.binary
+    1: <B><FONT COLOR="#A020F0">import</FONT></B> pymongo.dbref
+    1: <B><FONT COLOR="#A020F0">import</FONT></B> pymongo.objectid
+    1: <B><FONT COLOR="#A020F0">import</FONT></B> types
+    1: <B><FONT COLOR="#A020F0">import</FONT></B> zope.interface
+    1: <B><FONT COLOR="#A020F0">from</FONT></B> zope.dottedname.resolve <B><FONT COLOR="#A020F0">import</FONT></B> resolve
+       
+    1: <B><FONT COLOR="#A020F0">from</FONT></B> mongopersist <B><FONT COLOR="#A020F0">import</FONT></B> interfaces
+       
+    1: SERIALIZERS = []
+    1: OID_CLASS_LRU = lru.LRUCache(20000)
+       
+    1: <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">p64</FONT></B>(v):
+           <B><FONT COLOR="#BC8F8F">&quot;&quot;&quot;Pack an integer or long into a 8-byte string&quot;&quot;&quot;</FONT></B>
+   16:     <B><FONT COLOR="#A020F0">return</FONT></B> struct.pack(<B><FONT COLOR="#BC8F8F">&quot;&gt;Q&quot;</FONT></B>, v)
+       
+    1: <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">u64</FONT></B>(v):
+           <B><FONT COLOR="#BC8F8F">&quot;&quot;&quot;Unpack an 8-byte string into a 64-bit long integer.&quot;&quot;&quot;</FONT></B>
+   19:     <B><FONT COLOR="#A020F0">return</FONT></B> struct.unpack(<B><FONT COLOR="#BC8F8F">&quot;&gt;Q&quot;</FONT></B>, v)[0]
+       
+    1: <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">get_dotted_name</FONT></B>(obj):
+  207:     <B><FONT COLOR="#A020F0">return</FONT></B> obj.__module__+<B><FONT COLOR="#BC8F8F">'.'</FONT></B>+obj.__name__
+       
+    2: <B><FONT COLOR="#A020F0">class</FONT></B> PersistentDict(persistent.dict.PersistentDict):
+    1:     _p_mongo_sub_object = True
+       
+    2: <B><FONT COLOR="#A020F0">class</FONT></B> PersistentList(persistent.list.PersistentList):
+    1:     _p_mongo_sub_object = True
+       
+       
+    2: <B><FONT COLOR="#A020F0">class</FONT></B> ObjectSerializer(object):
+    1:     zope.interface.implements(interfaces.IObjectSerializer)
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">can_read</FONT></B>(self, state):
+    1:         <B><FONT COLOR="#A020F0">raise</FONT></B> NotImplementedError
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">read</FONT></B>(self, state):
+    1:         <B><FONT COLOR="#A020F0">raise</FONT></B> NotImplementedError
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">can_write</FONT></B>(self, obj):
+    1:         <B><FONT COLOR="#A020F0">raise</FONT></B> NotImplementedError
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">write</FONT></B>(self, obj):
+    1:         <B><FONT COLOR="#A020F0">raise</FONT></B> NotImplementedError
+       
+       
+    2: <B><FONT COLOR="#A020F0">class</FONT></B> ObjectWriter(object):
+    1:     zope.interface.implements(interfaces.IObjectWriter)
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">__init__</FONT></B>(self, jar):
+  139:         self._jar = jar
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">get_collection_name</FONT></B>(self, obj):
+  168:         db_name = getattr(obj, <B><FONT COLOR="#BC8F8F">'_p_mongo_database'</FONT></B>, self._jar.default_database)
+  168:         <B><FONT COLOR="#A020F0">try</FONT></B>:
+  168:             coll_name = obj._p_mongo_collection
+   99:         <B><FONT COLOR="#A020F0">except</FONT></B> AttributeError:
+   99:             <B><FONT COLOR="#A020F0">return</FONT></B> db_name, get_dotted_name(obj.__class__)
+               <I><FONT COLOR="#B22222"># Make sure that the coll_name to class path mapping is available.
+</FONT></I>   69:         db = self._jar._conn[self._jar.default_database]
+   69:         coll = db[self._jar.name_map_collection]
+   69:         map = {<B><FONT COLOR="#BC8F8F">'collection'</FONT></B>: coll_name,
+   69:                <B><FONT COLOR="#BC8F8F">'database'</FONT></B>: db_name,
+   69:                <B><FONT COLOR="#BC8F8F">'path'</FONT></B>: get_dotted_name(obj.__class__)}
+   69:         result = coll.find_one(map)
+   69:         <B><FONT COLOR="#A020F0">if</FONT></B> result <B><FONT COLOR="#A020F0">is</FONT></B> None:
+                   <I><FONT COLOR="#B22222"># If there is already a map for this collection, the next map must
+</FONT></I>                   <I><FONT COLOR="#B22222"># force the object to store the type.
+</FONT></I>   28:             result = coll.find({<B><FONT COLOR="#BC8F8F">'collection'</FONT></B>: coll_name,
+   28:                                 <B><FONT COLOR="#BC8F8F">'database'</FONT></B>: db_name})
+   28:             <B><FONT COLOR="#A020F0">if</FONT></B> result.count() &gt; 0:
+    3:                 setattr(obj, <B><FONT COLOR="#BC8F8F">'_p_mongo_store_type'</FONT></B>, True)
+   28:             map[<B><FONT COLOR="#BC8F8F">'doc_has_type'</FONT></B>] = getattr(obj, <B><FONT COLOR="#BC8F8F">'_p_mongo_store_type'</FONT></B>, False)
+   28:             coll.save(map)
+   69:         <B><FONT COLOR="#A020F0">return</FONT></B> db_name, coll_name
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">get_non_persistent_state</FONT></B>(self, obj, seen):
+   34:         __traceback_info__ = obj
+               <I><FONT COLOR="#B22222"># XXX: Look at the pickle library how to properly handle all types and
+</FONT></I>               <I><FONT COLOR="#B22222"># old-style classes with all of the possible pickle extensions.
+</FONT></I>       
+               <I><FONT COLOR="#B22222"># Only non-persistent, custom objects can produce unresolvable
+</FONT></I>               <I><FONT COLOR="#B22222"># circular references.
+</FONT></I>   34:         <B><FONT COLOR="#A020F0">if</FONT></B> obj <B><FONT COLOR="#A020F0">in</FONT></B> seen:
+    2:             <B><FONT COLOR="#A020F0">raise</FONT></B> interfaces.CircularReferenceError(obj)
+               <I><FONT COLOR="#B22222"># Add the current object to the list of seen objects.
+</FONT></I>   32:         seen.append(obj)
+               <I><FONT COLOR="#B22222"># Get the state of the object. Only pickable objects can be reduced.
+</FONT></I>   32:         reduced = obj.__reduce__()
+               <I><FONT COLOR="#B22222"># The full object state (item 3) seems to be optional, so let's make
+</FONT></I>               <I><FONT COLOR="#B22222"># sure we handle that case gracefully.
+</FONT></I>   32:         <B><FONT COLOR="#A020F0">if</FONT></B> len(reduced) == 2:
+    2:             factory, args = obj.__reduce__()
+    2:             obj_state = {}
+               <B><FONT COLOR="#A020F0">else</FONT></B>:
+   30:             factory, args, obj_state = reduced
+               <I><FONT COLOR="#B22222"># We are trying very hard to create a clean Mongo (sub-)document. But
+</FONT></I>               <I><FONT COLOR="#B22222"># we need a little bit of meta-data to help us out later.
+</FONT></I>   32:         <B><FONT COLOR="#A020F0">if</FONT></B> factory == copy_reg._reconstructor <B><FONT COLOR="#A020F0">and</FONT></B> \
+   16:                args == (obj.__class__, object, None):
+                   <I><FONT COLOR="#B22222"># This is the simple case, which means we can produce a nicer
+</FONT></I>                   <I><FONT COLOR="#B22222"># Mongo output.
+</FONT></I>   16:             state = {<B><FONT COLOR="#BC8F8F">'_py_type'</FONT></B>: get_dotted_name(args[0])}
+   16:         <B><FONT COLOR="#A020F0">elif</FONT></B> factory == copy_reg.__newobj__ <B><FONT COLOR="#A020F0">and</FONT></B> args == (obj.__class__,):
+                   <I><FONT COLOR="#B22222"># Another simple case for persistent objects that do not want
+</FONT></I>                   <I><FONT COLOR="#B22222"># their own document.
+</FONT></I>   14:             state = {<B><FONT COLOR="#BC8F8F">'_py_persistent_type'</FONT></B>: get_dotted_name(args[0])}
+               <B><FONT COLOR="#A020F0">else</FONT></B>:
+    2:             state = {<B><FONT COLOR="#BC8F8F">'_py_factory'</FONT></B>: get_dotted_name(factory),
+    2:                      <B><FONT COLOR="#BC8F8F">'_py_factory_args'</FONT></B>: self.get_state(args, seen)}
+   86:         <B><FONT COLOR="#A020F0">for</FONT></B> name, value <B><FONT COLOR="#A020F0">in</FONT></B> obj_state.items():
+   56:             state[name] = self.get_state(value, seen)
+   30:         <B><FONT COLOR="#A020F0">return</FONT></B> state
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">get_persistent_state</FONT></B>(self, obj, seen):
+   73:         __traceback_info__ = obj
+               <I><FONT COLOR="#B22222"># Persistent sub-objects are stored by reference, the key being
+</FONT></I>               <I><FONT COLOR="#B22222"># (collection name, oid).
+</FONT></I>               <I><FONT COLOR="#B22222"># Getting the collection name is easy, but if we have an unsaved
+</FONT></I>               <I><FONT COLOR="#B22222"># persistent object, we do not yet have an OID. This must be solved by
+</FONT></I>               <I><FONT COLOR="#B22222"># storing the persistent object.
+</FONT></I>   73:         <B><FONT COLOR="#A020F0">if</FONT></B> obj._p_oid <B><FONT COLOR="#A020F0">is</FONT></B> None:
+   11:             dbref = self.store(obj, ref_only=True)
+               <B><FONT COLOR="#A020F0">else</FONT></B>:
+   62:             db_name, coll_name = self.get_collection_name(obj)
+   62:             dbref = obj._p_oid
+               <I><FONT COLOR="#B22222"># Create the reference sub-document. The _p_type value helps with the
+</FONT></I>               <I><FONT COLOR="#B22222"># deserialization later.
+</FONT></I>   73:         <B><FONT COLOR="#A020F0">return</FONT></B> dbref
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">get_state</FONT></B>(self, obj, seen=None):
+  478:         seen = seen <B><FONT COLOR="#A020F0">or</FONT></B> []
+  478:         <B><FONT COLOR="#A020F0">if</FONT></B> isinstance(obj, interfaces.MONGO_NATIVE_TYPES):
+                   <I><FONT COLOR="#B22222"># If we have a native type, we'll just use it as the state.
+</FONT></I>  189:             <B><FONT COLOR="#A020F0">return</FONT></B> obj
+  289:         <B><FONT COLOR="#A020F0">if</FONT></B> isinstance(obj, str):
+                   <I><FONT COLOR="#B22222"># In Python 2, strings can be ASCII, encoded unicode or binary
+</FONT></I>                   <I><FONT COLOR="#B22222"># data. Unfortunately, BSON cannot handle that. So, if we have a
+</FONT></I>                   <I><FONT COLOR="#B22222"># string that cannot be UTF-8 decoded (luckily ASCII is a valid
+</FONT></I>                   <I><FONT COLOR="#B22222"># subset of UTF-8), then we use the BSON binary type.
+</FONT></I>   50:             <B><FONT COLOR="#A020F0">try</FONT></B>:
+   50:                 obj.decode(<B><FONT COLOR="#BC8F8F">'utf-8'</FONT></B>)
+   48:                 <B><FONT COLOR="#A020F0">return</FONT></B> obj
+    2:             <B><FONT COLOR="#A020F0">except</FONT></B> UnicodeError:
+    2:                 <B><FONT COLOR="#A020F0">return</FONT></B> pymongo.binary.Binary(obj)
+       
+               <I><FONT COLOR="#B22222"># Some objects might not naturally serialize well and create a very
+</FONT></I>               <I><FONT COLOR="#B22222"># ugly Mongo entry. Thus, we allow custom serializers to be
+</FONT></I>               <I><FONT COLOR="#B22222"># registered, which can encode/decode different types of objects.
+</FONT></I>  317:         <B><FONT COLOR="#A020F0">for</FONT></B> serializer <B><FONT COLOR="#A020F0">in</FONT></B> SERIALIZERS:
+   85:             <B><FONT COLOR="#A020F0">if</FONT></B> serializer.can_write(obj):
+    7:                 <B><FONT COLOR="#A020F0">return</FONT></B> serializer.write(obj)
+       
+  232:         <B><FONT COLOR="#A020F0">if</FONT></B> isinstance(obj, (type, types.ClassType)):
+                   <I><FONT COLOR="#B22222"># We frequently store class and function paths as meta-data, so we
+</FONT></I>                   <I><FONT COLOR="#B22222"># need to be able to properly encode those.
+</FONT></I>    2:             <B><FONT COLOR="#A020F0">return</FONT></B> {<B><FONT COLOR="#BC8F8F">'_py_type'</FONT></B>: <B><FONT COLOR="#BC8F8F">'type'</FONT></B>,
+    2:                     <B><FONT COLOR="#BC8F8F">'path'</FONT></B>: get_dotted_name(obj)}
+  230:         <B><FONT COLOR="#A020F0">if</FONT></B> isinstance(obj, (tuple, list, PersistentList)):
+                   <I><FONT COLOR="#B22222"># Make sure that all values within a list are serialized
+</FONT></I>                   <I><FONT COLOR="#B22222"># correctly. Also convert any sequence-type to a simple list.
+</FONT></I>   47:             <B><FONT COLOR="#A020F0">return</FONT></B> [self.get_state(value, seen) <B><FONT COLOR="#A020F0">for</FONT></B> value <B><FONT COLOR="#A020F0">in</FONT></B> obj]
+  209:         <B><FONT COLOR="#A020F0">if</FONT></B> isinstance(obj, (dict, PersistentDict)):
+                   <I><FONT COLOR="#B22222"># Same as for sequences, make sure that the contained values are
+</FONT></I>                   <I><FONT COLOR="#B22222"># properly serialized.
+</FONT></I>                   <I><FONT COLOR="#B22222"># Note: A big constraint in Mongo is that keys must be strings!
+</FONT></I>  109:             has_non_string_key = False
+  109:             data = []
+  373:             <B><FONT COLOR="#A020F0">for</FONT></B> key, value <B><FONT COLOR="#A020F0">in</FONT></B> obj.items():
+  265:                 data.append((key, self.get_state(value, seen)))
+  264:                 has_non_string_key |= <B><FONT COLOR="#A020F0">not</FONT></B> isinstance(key, basestring)
+  108:             <B><FONT COLOR="#A020F0">if</FONT></B> <B><FONT COLOR="#A020F0">not</FONT></B> has_non_string_key:
+                       <I><FONT COLOR="#B22222"># The easy case: all keys are strings:
+</FONT></I>  107:                 <B><FONT COLOR="#A020F0">return</FONT></B> dict(data)
+                   <B><FONT COLOR="#A020F0">else</FONT></B>:
+                       <I><FONT COLOR="#B22222"># We first need to reduce the keys and then produce a data
+</FONT></I>                       <I><FONT COLOR="#B22222"># structure.
+</FONT></I>    4:                 data = [(self.get_state(key), value) <B><FONT COLOR="#A020F0">for</FONT></B> key, value <B><FONT COLOR="#A020F0">in</FONT></B> data]
+    1:                 <B><FONT COLOR="#A020F0">return</FONT></B> {<B><FONT COLOR="#BC8F8F">'dict_data'</FONT></B>: data}
+       
+  100:         <B><FONT COLOR="#A020F0">if</FONT></B> isinstance(obj, persistent.Persistent):
+                   <I><FONT COLOR="#B22222"># Only create a persistent reference, if the object does not want
+</FONT></I>                   <I><FONT COLOR="#B22222"># to be a sub-document.
+</FONT></I>   84:             <B><FONT COLOR="#A020F0">if</FONT></B> <B><FONT COLOR="#A020F0">not</FONT></B> getattr(obj, <B><FONT COLOR="#BC8F8F">'_p_mongo_sub_object'</FONT></B>, False):
+   71:                 <B><FONT COLOR="#A020F0">return</FONT></B> self.get_persistent_state(obj, seen)
+                   <I><FONT COLOR="#B22222"># This persistent object is a sub-document, so it is treated like
+</FONT></I>                   <I><FONT COLOR="#B22222"># a non-persistent object.
+</FONT></I>       
+   29:         <B><FONT COLOR="#A020F0">return</FONT></B> self.get_non_persistent_state(obj, seen)
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">store</FONT></B>(self, obj, ref_only=False):
+   96:         db_name, coll_name = self.get_collection_name(obj)
+   96:         coll = self._jar._conn[db_name][coll_name]
+   96:         <B><FONT COLOR="#A020F0">if</FONT></B> ref_only:
+                   <I><FONT COLOR="#B22222"># We only want to get OID quickly. Trying to reduce the full state
+</FONT></I>                   <I><FONT COLOR="#B22222"># might cause infinite recusrion loop. (Example: 2 new objects
+</FONT></I>                   <I><FONT COLOR="#B22222"># reference each other.)
+</FONT></I>   11:             doc = {}
+                   <I><FONT COLOR="#B22222"># Make sure that the object gets saved fully later.
+</FONT></I>   11:             self._jar.register(obj)
+               <B><FONT COLOR="#A020F0">else</FONT></B>:
+                   <I><FONT COLOR="#B22222"># XXX: Handle newargs; see ZODB.serialize.ObjectWriter.serialize
+</FONT></I>                   <I><FONT COLOR="#B22222"># Go through each attribute and search for persistent references.
+</FONT></I>   85:             doc = self.get_state(obj.__getstate__())
+   95:         <B><FONT COLOR="#A020F0">if</FONT></B> getattr(obj, <B><FONT COLOR="#BC8F8F">'_p_mongo_store_type'</FONT></B>, False):
+    4:             doc[<B><FONT COLOR="#BC8F8F">'_py_persistent_type'</FONT></B>] = get_dotted_name(obj.__class__)
+               <I><FONT COLOR="#B22222"># If conflict detection is turned on, store a serial number for the
+</FONT></I>               <I><FONT COLOR="#B22222"># document.
+</FONT></I>   95:         <B><FONT COLOR="#A020F0">if</FONT></B> self._jar.detect_conflicts:
+   11:             doc[<B><FONT COLOR="#BC8F8F">'_py_serial'</FONT></B>] = u64(getattr(obj, <B><FONT COLOR="#BC8F8F">'_p_serial'</FONT></B>, 0)) + 1
+   11:             obj._p_serial = p64(doc[<B><FONT COLOR="#BC8F8F">'_py_serial'</FONT></B>])
+       
+   95:         <B><FONT COLOR="#A020F0">if</FONT></B> obj._p_oid <B><FONT COLOR="#A020F0">is</FONT></B> None:
+   63:             doc_id = coll.insert(doc)
+   63:             obj._p_jar = self._jar
+   63:             obj._p_oid = pymongo.dbref.DBRef(coll_name, doc_id, db_name)
+                   <I><FONT COLOR="#B22222"># Make sure that any other code accessing this object in this
+</FONT></I>                   <I><FONT COLOR="#B22222"># session, gets the same instance.
+</FONT></I>   63:             self._jar._object_cache[doc_id] = obj
+               <B><FONT COLOR="#A020F0">else</FONT></B>:
+   32:             doc[<B><FONT COLOR="#BC8F8F">'_id'</FONT></B>] = obj._p_oid.id
+   32:             coll.save(doc)
+   95:         <B><FONT COLOR="#A020F0">return</FONT></B> obj._p_oid
+       
+       
+    2: <B><FONT COLOR="#A020F0">class</FONT></B> ObjectReader(object):
+    1:     zope.interface.implements(interfaces.IObjectReader)
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">__init__</FONT></B>(self, jar):
+  138:         self._jar = jar
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">simple_resolve</FONT></B>(self, path):
+  118:         <B><FONT COLOR="#A020F0">return</FONT></B> resolve(path)
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">resolve</FONT></B>(self, dbref):
+   72:         <B><FONT COLOR="#A020F0">try</FONT></B>:
+   72:             <B><FONT COLOR="#A020F0">return</FONT></B> OID_CLASS_LRU[dbref.id]
+   66:         <B><FONT COLOR="#A020F0">except</FONT></B> KeyError:
+   66:             <B><FONT COLOR="#A020F0">pass</FONT></B>
+               <I><FONT COLOR="#B22222"># First we try to resolve the path directly.
+</FONT></I>   66:         <B><FONT COLOR="#A020F0">try</FONT></B>:
+   66:             <B><FONT COLOR="#A020F0">return</FONT></B> self.simple_resolve(dbref.collection)
+   28:         <B><FONT COLOR="#A020F0">except</FONT></B> ImportError:
+   28:             <B><FONT COLOR="#A020F0">pass</FONT></B>
+               <I><FONT COLOR="#B22222"># Let's now try to look up the path from the collection to path
+</FONT></I>               <I><FONT COLOR="#B22222"># mapping
+</FONT></I>   28:         db = self._jar._conn[self._jar.default_database]
+   28:         coll = db[self._jar.name_map_collection]
+   28:         result = coll.find(
+   28:             {<B><FONT COLOR="#BC8F8F">'collection'</FONT></B>: dbref.collection, <B><FONT COLOR="#BC8F8F">'database'</FONT></B>: dbref.database})
+   28:         <B><FONT COLOR="#A020F0">if</FONT></B> result.count() == 0:
+    1:             <B><FONT COLOR="#A020F0">raise</FONT></B> ImportError(dbref)
+   27:         <B><FONT COLOR="#A020F0">elif</FONT></B> result.count() == 1:
+                   <I><FONT COLOR="#B22222"># Do not add these results to the LRU cache, since the count might
+</FONT></I>                   <I><FONT COLOR="#B22222"># change later.
+</FONT></I>   22:             <B><FONT COLOR="#A020F0">return</FONT></B> self.simple_resolve(result.next()[<B><FONT COLOR="#BC8F8F">'path'</FONT></B>])
+               <B><FONT COLOR="#A020F0">else</FONT></B>:
+    5:             <B><FONT COLOR="#A020F0">if</FONT></B> dbref.id <B><FONT COLOR="#A020F0">is</FONT></B> None:
+    1:                 <B><FONT COLOR="#A020F0">raise</FONT></B> ImportError(dbref)
+                   <I><FONT COLOR="#B22222"># Multiple object types are stored in the collection. We have to
+</FONT></I>                   <I><FONT COLOR="#B22222"># look at the object to find out the type.
+</FONT></I>    4:             obj_doc = self._jar._conn[dbref.database][dbref.collection].find_one(
+    4:                 dbref.id, fields=(<B><FONT COLOR="#BC8F8F">'_py_persistent_type'</FONT></B>,))
+    4:             <B><FONT COLOR="#A020F0">if</FONT></B> <B><FONT COLOR="#BC8F8F">'_py_persistent_type'</FONT></B> <B><FONT COLOR="#A020F0">in</FONT></B> obj_doc:
+    2:                 klass = self.simple_resolve(obj_doc[<B><FONT COLOR="#BC8F8F">'_py_persistent_type'</FONT></B>])
+                   <B><FONT COLOR="#A020F0">else</FONT></B>:
+                       <I><FONT COLOR="#B22222"># Find the name-map entry where &quot;doc_has_type&quot; is False.
+</FONT></I>    2:                 <B><FONT COLOR="#A020F0">for</FONT></B> name_map_item <B><FONT COLOR="#A020F0">in</FONT></B> result:
+    2:                     <B><FONT COLOR="#A020F0">if</FONT></B> <B><FONT COLOR="#A020F0">not</FONT></B> name_map_item[<B><FONT COLOR="#BC8F8F">'doc_has_type'</FONT></B>]:
+    2:                         klass = self.simple_resolve(name_map_item[<B><FONT COLOR="#BC8F8F">'path'</FONT></B>])
+    2:                         <B><FONT COLOR="#A020F0">break</FONT></B>
+                       <B><FONT COLOR="#A020F0">else</FONT></B>:
+<div class="notcovered">&gt;&gt;&gt;&gt;&gt;&gt;                     <B><FONT COLOR="#A020F0">raise</FONT></B> ImportError(path)</div>    4:             OID_CLASS_LRU[dbref.id] = klass
+    4:             <B><FONT COLOR="#A020F0">return</FONT></B> klass
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">get_non_persistent_object</FONT></B>(self, state, obj):
+   24:         <B><FONT COLOR="#A020F0">if</FONT></B> <B><FONT COLOR="#BC8F8F">'_py_type'</FONT></B> <B><FONT COLOR="#A020F0">in</FONT></B> state:
+                   <I><FONT COLOR="#B22222"># Handle the simplified case.
+</FONT></I>   12:             klass = self.simple_resolve(state.pop(<B><FONT COLOR="#BC8F8F">'_py_type'</FONT></B>))
+   12:             sub_obj = copy_reg._reconstructor(klass, object, None)
+   12:         <B><FONT COLOR="#A020F0">elif</FONT></B> <B><FONT COLOR="#BC8F8F">'_py_persistent_type'</FONT></B> <B><FONT COLOR="#A020F0">in</FONT></B> state:
+                   <I><FONT COLOR="#B22222"># Another simple case for persistent objects that do not want
+</FONT></I>                   <I><FONT COLOR="#B22222"># their own document.
+</FONT></I>   10:             klass = self.simple_resolve(state.pop(<B><FONT COLOR="#BC8F8F">'_py_persistent_type'</FONT></B>))
+   10:             sub_obj = copy_reg.__newobj__(klass)
+               <B><FONT COLOR="#A020F0">else</FONT></B>:
+    2:             factory = self.simple_resolve(state.pop(<B><FONT COLOR="#BC8F8F">'_py_factory'</FONT></B>))
+    2:             factory_args = self.get_object(state.pop(<B><FONT COLOR="#BC8F8F">'_py_factory_args'</FONT></B>), obj)
+    2:             sub_obj = factory(*factory_args)
+   24:         <B><FONT COLOR="#A020F0">if</FONT></B> len(state):
+   20:             sub_obj_state = self.get_object(state, obj)
+   20:             <B><FONT COLOR="#A020F0">if</FONT></B> isinstance(sub_obj, persistent.Persistent):
+    9:                 sub_obj.__setstate__(sub_obj_state)
+                   <B><FONT COLOR="#A020F0">else</FONT></B>:
+   11:                 sub_obj.__dict__.update(sub_obj_state)
+   24:         <B><FONT COLOR="#A020F0">if</FONT></B> getattr(sub_obj, <B><FONT COLOR="#BC8F8F">'_p_mongo_sub_object'</FONT></B>, False):
+   10:             sub_obj._p_mongo_doc_object = obj
+   10:             sub_obj._p_jar = self._jar
+   24:         <B><FONT COLOR="#A020F0">return</FONT></B> sub_obj
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">get_object</FONT></B>(self, state, obj):
+  584:         <B><FONT COLOR="#A020F0">if</FONT></B> isinstance(state, pymongo.objectid.ObjectId):
+                   <I><FONT COLOR="#B22222"># The object id is special. Preserve it.
+</FONT></I>    1:             <B><FONT COLOR="#A020F0">return</FONT></B> state
+  583:         <B><FONT COLOR="#A020F0">if</FONT></B> isinstance(state, pymongo.binary.Binary):
+                   <I><FONT COLOR="#B22222"># Binary data in Python 2 is presented as a string. We will
+</FONT></I>                   <I><FONT COLOR="#B22222"># convert back to binary when serializing again.
+</FONT></I>    2:             <B><FONT COLOR="#A020F0">return</FONT></B> str(state)
+  581:         <B><FONT COLOR="#A020F0">if</FONT></B> isinstance(state, pymongo.dbref.DBRef):
+                   <I><FONT COLOR="#B22222"># Load a persistent object. Using the get_ghost() method, so that
+</FONT></I>                   <I><FONT COLOR="#B22222"># caching is properly applied.
+</FONT></I>   38:             <B><FONT COLOR="#A020F0">return</FONT></B> self.get_ghost(state)
+  543:         <B><FONT COLOR="#A020F0">if</FONT></B> isinstance(state, dict) <B><FONT COLOR="#A020F0">and</FONT></B> state.get(<B><FONT COLOR="#BC8F8F">'_py_type'</FONT></B>) == <B><FONT COLOR="#BC8F8F">'type'</FONT></B>:
+                   <I><FONT COLOR="#B22222"># Convert a simple object reference, mostly classes.
+</FONT></I>    1:             <B><FONT COLOR="#A020F0">return</FONT></B> self.simple_resolve(state[<B><FONT COLOR="#BC8F8F">'path'</FONT></B>])
+       
+               <I><FONT COLOR="#B22222"># Give the custom serializers a chance to weigh in.
+</FONT></I>  810:         <B><FONT COLOR="#A020F0">for</FONT></B> serializer <B><FONT COLOR="#A020F0">in</FONT></B> SERIALIZERS:
+  275:             <B><FONT COLOR="#A020F0">if</FONT></B> serializer.can_read(state):
+    7:                 <B><FONT COLOR="#A020F0">return</FONT></B> serializer.read(state)
+       
+  535:         <B><FONT COLOR="#A020F0">if</FONT></B> isinstance(state, dict) <B><FONT COLOR="#A020F0">and</FONT></B> (<B><FONT COLOR="#BC8F8F">'_py_factory'</FONT></B> <B><FONT COLOR="#A020F0">in</FONT></B> state <B><FONT COLOR="#A020F0">or</FONT></B> \
+  106:                <B><FONT COLOR="#BC8F8F">'_py_type'</FONT></B> <B><FONT COLOR="#A020F0">in</FONT></B> state <B><FONT COLOR="#A020F0">or</FONT></B> <B><FONT COLOR="#BC8F8F">'_py_persistent_type'</FONT></B> <B><FONT COLOR="#A020F0">in</FONT></B> state):
+                   <I><FONT COLOR="#B22222"># Load a non-persistent object.
+</FONT></I>   20:             <B><FONT COLOR="#A020F0">return</FONT></B> self.get_non_persistent_object(state, obj)
+  515:         <B><FONT COLOR="#A020F0">if</FONT></B> isinstance(state, (tuple, list)):
+                   <I><FONT COLOR="#B22222"># All lists are converted to persistent lists, so that their state
+</FONT></I>                   <I><FONT COLOR="#B22222"># changes are noticed. Also make sure that all value states are
+</FONT></I>                   <I><FONT COLOR="#B22222"># converted to objects.
+</FONT></I>   17:             sub_obj = PersistentList(
+   40:                 [self.get_object(value, obj) <B><FONT COLOR="#A020F0">for</FONT></B> value <B><FONT COLOR="#A020F0">in</FONT></B> state])
+   17:             sub_obj._p_mongo_doc_object = obj
+   17:             sub_obj._p_jar = self._jar
+   17:             <B><FONT COLOR="#A020F0">return</FONT></B> sub_obj
+  498:         <B><FONT COLOR="#A020F0">if</FONT></B> isinstance(state, dict):
+                   <I><FONT COLOR="#B22222"># All dictionaries are converted to persistent dictionaries, so
+</FONT></I>                   <I><FONT COLOR="#B22222"># that state changes are detected. Also convert all value states
+</FONT></I>                   <I><FONT COLOR="#B22222"># to objects.
+</FONT></I>                   <I><FONT COLOR="#B22222"># Handle non-string key dicts.
+</FONT></I>   87:             <B><FONT COLOR="#A020F0">if</FONT></B> <B><FONT COLOR="#BC8F8F">'dict_data'</FONT></B> <B><FONT COLOR="#A020F0">in</FONT></B> state:
+    1:                 items = state[<B><FONT COLOR="#BC8F8F">'dict_data'</FONT></B>]
+                   <B><FONT COLOR="#A020F0">else</FONT></B>:
+   86:                 items = state.items()
+   87:             sub_obj = PersistentDict(
+   87:                 [(self.get_object(name, obj), self.get_object(value, obj))
+  329:                  <B><FONT COLOR="#A020F0">for</FONT></B> name, value <B><FONT COLOR="#A020F0">in</FONT></B> items])
+   87:             sub_obj._p_mongo_doc_object = obj
+   87:             sub_obj._p_jar = self._jar
+   87:             <B><FONT COLOR="#A020F0">return</FONT></B> sub_obj
+  411:         <B><FONT COLOR="#A020F0">return</FONT></B> state
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">set_ghost_state</FONT></B>(self, obj):
+               <I><FONT COLOR="#B22222"># Look up the object state by coll_name and oid.
+</FONT></I>   47:         coll = self._jar._conn[obj._p_oid.database][obj._p_oid.collection]
+   47:         doc = coll.find_one({<B><FONT COLOR="#BC8F8F">'_id'</FONT></B>: obj._p_oid.id})
+   47:         doc.pop(<B><FONT COLOR="#BC8F8F">'_id'</FONT></B>)
+   47:         doc.pop(<B><FONT COLOR="#BC8F8F">'_py_persistent_type'</FONT></B>, None)
+               <I><FONT COLOR="#B22222"># Store the serial, if conflict detection is enabled.
+</FONT></I>   47:         <B><FONT COLOR="#A020F0">if</FONT></B> self._jar.detect_conflicts:
+    5:             obj._p_serial = p64(doc.pop(<B><FONT COLOR="#BC8F8F">'_py_serial'</FONT></B>, 0))
+               <I><FONT COLOR="#B22222"># Now convert the document to a proper Python state dict.
+</FONT></I>   47:         state = self.get_object(doc, obj)
+               <I><FONT COLOR="#B22222"># Set the state.
+</FONT></I>   47:         obj.__setstate__(dict(state))
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">get_ghost</FONT></B>(self, dbref):
+               <I><FONT COLOR="#B22222"># If we can, we return the object from cache.
+</FONT></I>  171:         <B><FONT COLOR="#A020F0">try</FONT></B>:
+  171:             <B><FONT COLOR="#A020F0">return</FONT></B> self._jar._object_cache[dbref.id]
+   66:         <B><FONT COLOR="#A020F0">except</FONT></B> KeyError:
+   66:             <B><FONT COLOR="#A020F0">pass</FONT></B>
+   66:         klass = self.resolve(dbref)
+   66:         obj = klass.__new__(klass)
+   66:         obj._p_jar = self._jar
+   66:         obj._p_oid = dbref
+   66:         <B><FONT COLOR="#A020F0">del</FONT></B> obj._p_changed
+               <I><FONT COLOR="#B22222"># Assign the collection after deleting _p_changed, since the attribute
+</FONT></I>               <I><FONT COLOR="#B22222"># is otherwise deleted.
+</FONT></I>   66:         obj._p_mongo_database = dbref.database
+   66:         obj._p_mongo_collection = dbref.collection
+               <I><FONT COLOR="#B22222"># Adding the object to the cache is very important, so that we get the
+</FONT></I>               <I><FONT COLOR="#B22222"># same object reference throughout the transaction.
+</FONT></I>   66:         self._jar._object_cache[dbref.id] = obj
+   66:         <B><FONT COLOR="#A020F0">return</FONT></B> obj
+</pre>
+
+      <div class="footer">
+      Generated for revision 5108:5114M on 2011-11-04 17:02:17.574731Z
+      </div>
+    </body>
+    </html>

Added: mongopersist/trunk/coverage/report/mongopersist.testing.html
===================================================================
--- mongopersist/trunk/coverage/report/mongopersist.testing.html	                        (rev 0)
+++ mongopersist/trunk/coverage/report/mongopersist.testing.html	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,80 @@
+
+    <html>
+      <head><title>Test coverage for mongopersist.testing</title>
+      <style type="text/css">
+        a {text-decoration: none; display: block; padding-right: 1em;}
+        a:hover {background: #EFA;}
+        hr {height: 1px; border: none; border-top: 1px solid gray;}
+        .notcovered {background: #FCC;}
+        .footer {margin: 2em; font-size: small; color: gray;}
+      </style>
+      </head>
+      <body><h1>Test coverage for mongopersist.testing</h1>
+      <table>
+    
+<tr><td><a href="mongopersist.html">mongopersist/</a></td> <td style="background: yellow">&nbsp;&nbsp;&nbsp;&nbsp;</td> <td>covered 99% (1 of 695 uncovered)</td></tr>
+<tr><td><a href="mongopersist.testing.html">&nbsp;&nbsp;&nbsp;&nbsp;testing.py</a></td> <td style="background: green">&nbsp;&nbsp;&nbsp;&nbsp;</td> <td>covered 100% (0 of 33 uncovered)</td></tr>
+</table><hr/>
+<pre>
+       <I><FONT COLOR="#B22222">##############################################################################
+</FONT></I>       <I><FONT COLOR="#B22222">#
+</FONT></I>       <I><FONT COLOR="#B22222"># Copyright (c) 2011 Zope Foundation and Contributors.
+</FONT></I>       <I><FONT COLOR="#B22222"># All Rights Reserved.
+</FONT></I>       <I><FONT COLOR="#B22222">#
+</FONT></I>       <I><FONT COLOR="#B22222"># This software is subject to the provisions of the Zope Public License,
+</FONT></I>       <I><FONT COLOR="#B22222"># Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+</FONT></I>       <I><FONT COLOR="#B22222"># THIS SOFTWARE IS PROVIDED &quot;AS IS&quot; AND ANY AND ALL EXPRESS OR IMPLIED
+</FONT></I>       <I><FONT COLOR="#B22222"># WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+</FONT></I>       <I><FONT COLOR="#B22222"># WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+</FONT></I>       <I><FONT COLOR="#B22222"># FOR A PARTICULAR PURPOSE.
+</FONT></I>       <I><FONT COLOR="#B22222">#
+</FONT></I>       <I><FONT COLOR="#B22222">##############################################################################
+</FONT></I>    1: <B><FONT COLOR="#BC8F8F">&quot;&quot;&quot;Mongo Persistence Testing Support&quot;&quot;&quot;</FONT></B>
+    1: <B><FONT COLOR="#A020F0">from</FONT></B> __future__ <B><FONT COLOR="#A020F0">import</FONT></B> absolute_import
+    1: <B><FONT COLOR="#A020F0">import</FONT></B> doctest
+    1: <B><FONT COLOR="#A020F0">import</FONT></B> pymongo
+    1: <B><FONT COLOR="#A020F0">import</FONT></B> re
+    1: <B><FONT COLOR="#A020F0">import</FONT></B> transaction
+    1: <B><FONT COLOR="#A020F0">from</FONT></B> zope.testing <B><FONT COLOR="#A020F0">import</FONT></B> module, renormalizing
+       
+    1: <B><FONT COLOR="#A020F0">from</FONT></B> mongopersist <B><FONT COLOR="#A020F0">import</FONT></B> datamanager, serialize
+       
+    1: checker = renormalizing.RENormalizing([
+    1:     (re.compile(r<B><FONT COLOR="#BC8F8F">'datetime.datetime(.*)'</FONT></B>),
+    1:      <B><FONT COLOR="#BC8F8F">'datetime.datetime(2011, 10, 1, 9, 45)'</FONT></B>),
+    1:     (re.compile(r<B><FONT COLOR="#BC8F8F">&quot;ObjectId\('[0-9a-f]*'\)&quot;</FONT></B>),
+    1:      <B><FONT COLOR="#BC8F8F">&quot;ObjectId('4e7ddf12e138237403000000')&quot;</FONT></B>),
+    1:     (re.compile(r<B><FONT COLOR="#BC8F8F">&quot;object at 0x[0-9a-f]*&gt;&quot;</FONT></B>),
+    1:      <B><FONT COLOR="#BC8F8F">&quot;object at 0x001122&gt;&quot;</FONT></B>),
+           ])
+       
+       OPTIONFLAGS = (doctest.NORMALIZE_WHITESPACE|
+    1:                doctest.ELLIPSIS|
+    1:                doctest.REPORT_ONLY_FIRST_FAILURE
+                      <I><FONT COLOR="#B22222">#|doctest.REPORT_NDIFF
+</FONT></I>                      )
+       
+    1: <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">setUp</FONT></B>(test):
+   46:     module.setUp(test)
+   46:     test.globs[<B><FONT COLOR="#BC8F8F">'conn'</FONT></B>] = pymongo.Connection(<B><FONT COLOR="#BC8F8F">'localhost'</FONT></B>, 27017, tz_aware=False)
+   46:     test.globs[<B><FONT COLOR="#BC8F8F">'DBNAME'</FONT></B>] = <B><FONT COLOR="#BC8F8F">'mongopersist_test'</FONT></B>
+   46:     test.globs[<B><FONT COLOR="#BC8F8F">'conn'</FONT></B>].drop_database(test.globs[<B><FONT COLOR="#BC8F8F">'DBNAME'</FONT></B>])
+   46:     test.globs[<B><FONT COLOR="#BC8F8F">'commit'</FONT></B>] = transaction.commit
+   46:     test.globs[<B><FONT COLOR="#BC8F8F">'dm'</FONT></B>] = datamanager.MongoDataManager(
+   46:         test.globs[<B><FONT COLOR="#BC8F8F">'conn'</FONT></B>],
+   46:         default_database=test.globs[<B><FONT COLOR="#BC8F8F">'DBNAME'</FONT></B>],
+   46:         root_database=test.globs[<B><FONT COLOR="#BC8F8F">'DBNAME'</FONT></B>])
+       
+    1: <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">tearDown</FONT></B>(test):
+   46:     module.tearDown(test)
+   46:     transaction.abort()
+   46:     test.globs[<B><FONT COLOR="#BC8F8F">'conn'</FONT></B>].drop_database(test.globs[<B><FONT COLOR="#BC8F8F">'DBNAME'</FONT></B>])
+   46:     test.globs[<B><FONT COLOR="#BC8F8F">'conn'</FONT></B>].disconnect()
+   46:     serialize.SERIALIZERS.__init__()
+</pre>
+
+      <div class="footer">
+      Generated for revision 5108:5114M on 2011-11-04 17:02:17.574731Z
+      </div>
+    </body>
+    </html>

Added: mongopersist/trunk/coverage/report/mongopersist.zope.__init__.html
===================================================================
--- mongopersist/trunk/coverage/report/mongopersist.zope.__init__.html	                        (rev 0)
+++ mongopersist/trunk/coverage/report/mongopersist.zope.__init__.html	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,27 @@
+
+    <html>
+      <head><title>Test coverage for mongopersist.zope.__init__</title>
+      <style type="text/css">
+        a {text-decoration: none; display: block; padding-right: 1em;}
+        a:hover {background: #EFA;}
+        hr {height: 1px; border: none; border-top: 1px solid gray;}
+        .notcovered {background: #FCC;}
+        .footer {margin: 2em; font-size: small; color: gray;}
+      </style>
+      </head>
+      <body><h1>Test coverage for mongopersist.zope.__init__</h1>
+      <table>
+    
+<tr><td><a href="mongopersist.html">mongopersist/</a></td> <td style="background: yellow">&nbsp;&nbsp;&nbsp;&nbsp;</td> <td>covered 99% (1 of 695 uncovered)</td></tr>
+<tr><td><a href="mongopersist.zope.html">&nbsp;&nbsp;&nbsp;&nbsp;zope/</a></td> <td style="background: green">&nbsp;&nbsp;&nbsp;&nbsp;</td> <td>covered 100% (0 of 180 uncovered)</td></tr>
+<tr><td><a href="mongopersist.zope.__init__.html">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;__init__.py</a></td> <td style="background: green">&nbsp;&nbsp;&nbsp;&nbsp;</td> <td>covered 100% (0 of 1 uncovered)</td></tr>
+</table><hr/>
+<pre>
+    1: <I><FONT COLOR="#B22222"># Make a package.
+</FONT></I></pre>
+
+      <div class="footer">
+      Generated for revision 5108:5114M on 2011-11-04 17:02:17.574731Z
+      </div>
+    </body>
+    </html>

Added: mongopersist/trunk/coverage/report/mongopersist.zope.container.html
===================================================================
--- mongopersist/trunk/coverage/report/mongopersist.zope.container.html	                        (rev 0)
+++ mongopersist/trunk/coverage/report/mongopersist.zope.container.html	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,275 @@
+
+    <html>
+      <head><title>Test coverage for mongopersist.zope.container</title>
+      <style type="text/css">
+        a {text-decoration: none; display: block; padding-right: 1em;}
+        a:hover {background: #EFA;}
+        hr {height: 1px; border: none; border-top: 1px solid gray;}
+        .notcovered {background: #FCC;}
+        .footer {margin: 2em; font-size: small; color: gray;}
+      </style>
+      </head>
+      <body><h1>Test coverage for mongopersist.zope.container</h1>
+      <table>
+    
+<tr><td><a href="mongopersist.html">mongopersist/</a></td> <td style="background: yellow">&nbsp;&nbsp;&nbsp;&nbsp;</td> <td>covered 99% (1 of 695 uncovered)</td></tr>
+<tr><td><a href="mongopersist.zope.html">&nbsp;&nbsp;&nbsp;&nbsp;zope/</a></td> <td style="background: green">&nbsp;&nbsp;&nbsp;&nbsp;</td> <td>covered 100% (0 of 180 uncovered)</td></tr>
+<tr><td><a href="mongopersist.zope.container.html">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;container.py</a></td> <td style="background: green">&nbsp;&nbsp;&nbsp;&nbsp;</td> <td>covered 100% (0 of 179 uncovered)</td></tr>
+</table><hr/>
+<pre>
+       <I><FONT COLOR="#B22222">##############################################################################
+</FONT></I>       <I><FONT COLOR="#B22222">#
+</FONT></I>       <I><FONT COLOR="#B22222"># Copyright (c) 2011 Zope Foundation and Contributors.
+</FONT></I>       <I><FONT COLOR="#B22222"># All Rights Reserved.
+</FONT></I>       <I><FONT COLOR="#B22222">#
+</FONT></I>       <I><FONT COLOR="#B22222"># This software is subject to the provisions of the Zope Public License,
+</FONT></I>       <I><FONT COLOR="#B22222"># Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+</FONT></I>       <I><FONT COLOR="#B22222"># THIS SOFTWARE IS PROVIDED &quot;AS IS&quot; AND ANY AND ALL EXPRESS OR IMPLIED
+</FONT></I>       <I><FONT COLOR="#B22222"># WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+</FONT></I>       <I><FONT COLOR="#B22222"># WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+</FONT></I>       <I><FONT COLOR="#B22222"># FOR A PARTICULAR PURPOSE.
+</FONT></I>       <I><FONT COLOR="#B22222">#
+</FONT></I>       <I><FONT COLOR="#B22222">##############################################################################
+</FONT></I>    1: <B><FONT COLOR="#BC8F8F">&quot;&quot;&quot;Mongo Persistence Zope Containers&quot;&quot;&quot;</FONT></B>
+    1: <B><FONT COLOR="#A020F0">import</FONT></B> UserDict
+    1: <B><FONT COLOR="#A020F0">import</FONT></B> persistent
+    1: <B><FONT COLOR="#A020F0">import</FONT></B> pymongo.dbref
+    1: <B><FONT COLOR="#A020F0">import</FONT></B> zope.component
+    1: <B><FONT COLOR="#A020F0">from</FONT></B> rwproperty <B><FONT COLOR="#A020F0">import</FONT></B> getproperty, setproperty
+    1: <B><FONT COLOR="#A020F0">from</FONT></B> zope.container <B><FONT COLOR="#A020F0">import</FONT></B> contained, sample
+       
+    1: <B><FONT COLOR="#A020F0">from</FONT></B> mongopersist <B><FONT COLOR="#A020F0">import</FONT></B> interfaces, serialize
+       
+       
+    2: <B><FONT COLOR="#A020F0">class</FONT></B> MongoContained(contained.Contained):
+       
+    1:     @getproperty
+           <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">__name__</FONT></B>(self):
+    5:         <B><FONT COLOR="#A020F0">return</FONT></B> getattr(self, <B><FONT COLOR="#BC8F8F">'_v_key'</FONT></B>, None)
+    1:     @setproperty
+           <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">__name__</FONT></B>(self, value):
+    1:         setattr(self, <B><FONT COLOR="#BC8F8F">'_v_key'</FONT></B>, value)
+       
+    1:     @getproperty
+           <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">__parent__</FONT></B>(self):
+    5:         <B><FONT COLOR="#A020F0">return</FONT></B> getattr(self, <B><FONT COLOR="#BC8F8F">'_v_parent'</FONT></B>, None)
+    1:     @setproperty
+           <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">__parent__</FONT></B>(self, value):
+    1:         setattr(self, <B><FONT COLOR="#BC8F8F">'_v_parent'</FONT></B>, value)
+       
+       
+    2: <B><FONT COLOR="#A020F0">class</FONT></B> SimpleMongoContainer(sample.SampleContainer, persistent.Persistent):
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">__getstate__</FONT></B>(self):
+    5:         state = super(SimpleMongoContainer, self).__getstate__()
+    5:         state[<B><FONT COLOR="#BC8F8F">'data'</FONT></B>] = state.pop(<B><FONT COLOR="#BC8F8F">'_SampleContainer__data'</FONT></B>)
+    5:         <B><FONT COLOR="#A020F0">return</FONT></B> state
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">__setstate__</FONT></B>(self, state):
+    4:         state[<B><FONT COLOR="#BC8F8F">'_SampleContainer__data'</FONT></B>] = state.pop(<B><FONT COLOR="#BC8F8F">'data'</FONT></B>, {})
+    4:         super(SimpleMongoContainer, self).__setstate__(state)
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">__getitem__</FONT></B>(self, key):
+   13:         obj = super(SimpleMongoContainer, self).__getitem__(key)
+   12:         obj._v_key = key
+   12:         obj._v_parent = self
+   12:         <B><FONT COLOR="#A020F0">return</FONT></B> obj
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">get</FONT></B>(self, key, default=None):
+               <B><FONT COLOR="#BC8F8F">'''See interface `IReadContainer`'''</FONT></B>
+    3:         obj = super(SimpleMongoContainer, self).get(key, default)
+    3:         <B><FONT COLOR="#A020F0">if</FONT></B> obj <B><FONT COLOR="#A020F0">is</FONT></B> <B><FONT COLOR="#A020F0">not</FONT></B> default:
+    1:             obj._v_key = key
+    1:             obj._v_parent = self
+    3:         <B><FONT COLOR="#A020F0">return</FONT></B> obj
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">items</FONT></B>(self):
+    2:         items = super(SimpleMongoContainer, self).items()
+    4:         <B><FONT COLOR="#A020F0">for</FONT></B> key, obj <B><FONT COLOR="#A020F0">in</FONT></B> items:
+    2:             obj._v_key = key
+    2:             obj._v_parent = self
+    2:         <B><FONT COLOR="#A020F0">return</FONT></B> items
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">values</FONT></B>(self):
+    2:         <B><FONT COLOR="#A020F0">return</FONT></B> [v <B><FONT COLOR="#A020F0">for</FONT></B> k, v <B><FONT COLOR="#A020F0">in</FONT></B> self.items()]
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">__setitem__</FONT></B>(self, key, object):
+    2:         super(SimpleMongoContainer, self).__setitem__(key, object)
+    2:         self._p_changed = True
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">__delitem__</FONT></B>(self, key):
+    1:         super(SimpleMongoContainer, self).__delitem__(key)
+    1:         self._p_changed = True
+       
+    2: <B><FONT COLOR="#A020F0">class</FONT></B> MongoContainer(contained.Contained,
+    1:                      persistent.Persistent,
+    1:                      UserDict.DictMixin):
+    1:     _m_database = None
+    1:     _m_collection = None
+    1:     _m_mapping_key = <B><FONT COLOR="#BC8F8F">'key'</FONT></B>
+    1:     _m_parent_key = <B><FONT COLOR="#BC8F8F">'parent'</FONT></B>
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">__init__</FONT></B>(self, collection=None, database=None,
+    1:                  mapping_key=None, parent_key=None):
+   10:         <B><FONT COLOR="#A020F0">if</FONT></B> collection:
+   10:             self._m_collection = collection
+   10:         <B><FONT COLOR="#A020F0">if</FONT></B> database:
+    1:             self._m_database = database
+   10:         <B><FONT COLOR="#A020F0">if</FONT></B> mapping_key <B><FONT COLOR="#A020F0">is</FONT></B> <B><FONT COLOR="#A020F0">not</FONT></B> None:
+    1:             self._m_mapping_key = mapping_key
+   10:         <B><FONT COLOR="#A020F0">if</FONT></B> parent_key <B><FONT COLOR="#A020F0">is</FONT></B> <B><FONT COLOR="#A020F0">not</FONT></B> None:
+    1:             self._m_parent_key = parent_key
+       
+    1:     @property
+           <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">_added</FONT></B>(self):
+   48:         ann = self._m_jar.annotations.setdefault(self._p_oid <B><FONT COLOR="#A020F0">or</FONT></B> id(self), {})
+   48:         <B><FONT COLOR="#A020F0">return</FONT></B> ann.setdefault(<B><FONT COLOR="#BC8F8F">'added'</FONT></B>, {})
+       
+    1:     @property
+           <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">_deleted</FONT></B>(self):
+   46:         ann = self._m_jar.annotations.setdefault(self._p_oid <B><FONT COLOR="#A020F0">or</FONT></B> id(self), {})
+   46:         <B><FONT COLOR="#A020F0">return</FONT></B> ann.setdefault(<B><FONT COLOR="#BC8F8F">'deleted'</FONT></B>, {})
+       
+    1:     @property
+           <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">_m_jar</FONT></B>(self):
+               <I><FONT COLOR="#B22222"># If the container is in a Mongo storage hierarchy, then getting the
+</FONT></I>               <I><FONT COLOR="#B22222"># datamanager is easy, otherwise we do an adapter lookup.
+</FONT></I>  228:         <B><FONT COLOR="#A020F0">if</FONT></B> interfaces.IMongoDataManager.providedBy(self._p_jar):
+  208:             <B><FONT COLOR="#A020F0">return</FONT></B> self._p_jar
+               <B><FONT COLOR="#A020F0">else</FONT></B>:
+   20:             provider = zope.component.getUtility(
+   20:                 interfaces.IMongoDataManagerProvider)
+   19:             <B><FONT COLOR="#A020F0">return</FONT></B> provider.get()
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">get_collection</FONT></B>(self):
+   27:         db_name = self._m_database <B><FONT COLOR="#A020F0">or</FONT></B> self._m_jar.default_database
+   27:         <B><FONT COLOR="#A020F0">return</FONT></B> self._m_jar._conn[db_name][self._m_collection]
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">_m_get_parent_key_value</FONT></B>(self):
+   48:         <B><FONT COLOR="#A020F0">if</FONT></B> getattr(self, <B><FONT COLOR="#BC8F8F">'_p_jar'</FONT></B>, None) <B><FONT COLOR="#A020F0">is</FONT></B> None:
+    1:             <B><FONT COLOR="#A020F0">raise</FONT></B> ValueError(<B><FONT COLOR="#BC8F8F">'_p_jar not found.'</FONT></B>)
+   47:         <B><FONT COLOR="#A020F0">if</FONT></B> interfaces.IMongoDataManager.providedBy(self._p_jar):
+   42:             <B><FONT COLOR="#A020F0">return</FONT></B> self
+               <B><FONT COLOR="#A020F0">else</FONT></B>:
+   50:             <B><FONT COLOR="#A020F0">return</FONT></B> <B><FONT COLOR="#BC8F8F">'zodb-'</FONT></B>+<B><FONT COLOR="#BC8F8F">''</FONT></B>.join(<B><FONT COLOR="#BC8F8F">&quot;%02x&quot;</FONT></B> % ord(x) <B><FONT COLOR="#A020F0">for</FONT></B> x <B><FONT COLOR="#A020F0">in</FONT></B> self._p_oid).strip()
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">_m_get_items_filter</FONT></B>(self):
+   28:         filter = {}
+               <I><FONT COLOR="#B22222"># Make sure that we only look through objects that have the mapping
+</FONT></I>               <I><FONT COLOR="#B22222"># key. Objects not having the mapping key cannot be part of the
+</FONT></I>               <I><FONT COLOR="#B22222"># collection.
+</FONT></I>   28:         <B><FONT COLOR="#A020F0">if</FONT></B> self._m_mapping_key <B><FONT COLOR="#A020F0">is</FONT></B> <B><FONT COLOR="#A020F0">not</FONT></B> None:
+   28:             filter[self._m_mapping_key] = {<B><FONT COLOR="#BC8F8F">'$exists'</FONT></B>: True}
+   28:         <B><FONT COLOR="#A020F0">if</FONT></B> self._m_parent_key <B><FONT COLOR="#A020F0">is</FONT></B> <B><FONT COLOR="#A020F0">not</FONT></B> None:
+   27:             gs = self._m_jar._writer.get_state
+   26:             filter[self._m_parent_key] = gs(self._m_get_parent_key_value())
+   27:         <B><FONT COLOR="#A020F0">return</FONT></B> filter
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">__getitem__</FONT></B>(self, key):
+   12:         <B><FONT COLOR="#A020F0">if</FONT></B> key <B><FONT COLOR="#A020F0">in</FONT></B> self._added:
+    4:             <B><FONT COLOR="#A020F0">return</FONT></B> self._added[key]
+    8:         <B><FONT COLOR="#A020F0">if</FONT></B> key <B><FONT COLOR="#A020F0">in</FONT></B> self._deleted:
+    1:             <B><FONT COLOR="#A020F0">raise</FONT></B> KeyError(key)
+    7:         filter = self._m_get_items_filter()
+    7:         filter[self._m_mapping_key] = key
+    7:         doc = self.get_collection().find_one(filter, fields=())
+    7:         <B><FONT COLOR="#A020F0">if</FONT></B> doc <B><FONT COLOR="#A020F0">is</FONT></B> None:
+    1:             <B><FONT COLOR="#A020F0">raise</FONT></B> KeyError(key)
+    6:         dbref = pymongo.dbref.DBRef(
+    6:             self._m_collection, doc[<B><FONT COLOR="#BC8F8F">'_id'</FONT></B>],
+    6:             self._m_database <B><FONT COLOR="#A020F0">or</FONT></B> self._m_jar.default_database)
+    6:         obj = self._m_jar._reader.get_ghost(dbref)
+    6:         obj._v_key = key
+    6:         obj._v_parent = self
+    6:         <B><FONT COLOR="#A020F0">return</FONT></B> obj
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">__setitem__</FONT></B>(self, key, value):
+               <I><FONT COLOR="#B22222"># This call by iteself caues the state to change _p_changed to True.
+</FONT></I>   19:         setattr(value, self._m_mapping_key, key)
+   19:         <B><FONT COLOR="#A020F0">if</FONT></B> self._m_parent_key <B><FONT COLOR="#A020F0">is</FONT></B> <B><FONT COLOR="#A020F0">not</FONT></B> None:
+   19:             setattr(value, self._m_parent_key, self._m_get_parent_key_value())
+   19:         self._m_jar.register(value)
+               <I><FONT COLOR="#B22222"># Temporarily store the added object, so it is immediately available
+</FONT></I>               <I><FONT COLOR="#B22222"># via the API.
+</FONT></I>   19:         value._v_key = key
+   19:         value._v_parent = self
+   19:         self._added[key] = value
+   19:         self._deleted.pop(key, None)
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">__delitem__</FONT></B>(self, key):
+               <I><FONT COLOR="#B22222"># Deleting the object from the database is not our job. We simply
+</FONT></I>               <I><FONT COLOR="#B22222"># remove it from the dictionary.
+</FONT></I>    1:         value = self[key]
+    1:         <B><FONT COLOR="#A020F0">if</FONT></B> self._m_mapping_key <B><FONT COLOR="#A020F0">is</FONT></B> <B><FONT COLOR="#A020F0">not</FONT></B> None:
+    1:             delattr(value, self._m_mapping_key)
+    1:         <B><FONT COLOR="#A020F0">if</FONT></B> self._m_parent_key <B><FONT COLOR="#A020F0">is</FONT></B> <B><FONT COLOR="#A020F0">not</FONT></B> None:
+    1:             delattr(value, self._m_parent_key)
+    1:         self._deleted[key] = value
+    1:         self._added.pop(key, None)
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">keys</FONT></B>(self):
+   13:         filter = self._m_get_items_filter()
+   12:         filter[self._m_mapping_key] = {<B><FONT COLOR="#BC8F8F">'$ne'</FONT></B>: None}
+               keys = [
+   12:             doc[self._m_mapping_key]
+   12:             <B><FONT COLOR="#A020F0">for</FONT></B> doc <B><FONT COLOR="#A020F0">in</FONT></B> self.get_collection().find(filter)
+   18:             <B><FONT COLOR="#A020F0">if</FONT></B> <B><FONT COLOR="#A020F0">not</FONT></B> doc[self._m_mapping_key] <B><FONT COLOR="#A020F0">in</FONT></B> self._deleted]
+   12:         keys += self._added.keys()
+   12:         <B><FONT COLOR="#A020F0">return</FONT></B> keys
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">raw_find</FONT></B>(self, spec=None, *args, **kwargs):
+    3:         <B><FONT COLOR="#A020F0">if</FONT></B> spec <B><FONT COLOR="#A020F0">is</FONT></B> None:
+    1:             spec  = {}
+    3:         spec.update(self._m_get_items_filter())
+    3:         <B><FONT COLOR="#A020F0">return</FONT></B> self.get_collection().find(spec, *args, **kwargs)
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">find</FONT></B>(self, spec=None, fields=None, *args, **kwargs):
+               <I><FONT COLOR="#B22222"># If fields were not specified, we only request the oid and the key.
+</FONT></I>    2:         fields = tuple(fields <B><FONT COLOR="#A020F0">or</FONT></B> ())
+    2:         fields += (self._m_mapping_key,)
+    2:         result = self.raw_find(spec, fields, *args, **kwargs)
+   10:         <B><FONT COLOR="#A020F0">for</FONT></B> doc <B><FONT COLOR="#A020F0">in</FONT></B> result:
+    8:             dbref = pymongo.dbref.DBRef(
+    8:                 self._m_collection, doc[<B><FONT COLOR="#BC8F8F">'_id'</FONT></B>],
+    8:                 self._m_database <B><FONT COLOR="#A020F0">or</FONT></B> self._m_jar.default_database)
+    8:             obj = self._m_jar._reader.get_ghost(dbref)
+    8:             obj._v_key = doc[self._m_mapping_key]
+    8:             obj._v_parent = self
+    8:             <B><FONT COLOR="#A020F0">yield</FONT></B> obj
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">raw_find_one</FONT></B>(self, spec_or_id=None, *args, **kwargs):
+    5:         <B><FONT COLOR="#A020F0">if</FONT></B> spec_or_id <B><FONT COLOR="#A020F0">is</FONT></B> None:
+    1:             spec_or_id  = {}
+    5:         <B><FONT COLOR="#A020F0">if</FONT></B> <B><FONT COLOR="#A020F0">not</FONT></B> isinstance(spec_or_id, dict):
+    1:             spec_or_id = {<B><FONT COLOR="#BC8F8F">'_id'</FONT></B>: spec_or_id}
+    5:         spec_or_id.update(self._m_get_items_filter())
+    5:         <B><FONT COLOR="#A020F0">return</FONT></B> self.get_collection().find_one(spec_or_id, *args, **kwargs)
+       
+    1:     <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">find_one</FONT></B>(self, spec_or_id=None, fields=None, *args, **kwargs):
+               <I><FONT COLOR="#B22222"># If fields were not specified, we only request the oid and the key.
+</FONT></I>    4:         fields = tuple(fields <B><FONT COLOR="#A020F0">or</FONT></B> ())
+    4:         fields += (self._m_mapping_key,)
+    4:         doc = self.raw_find_one(spec_or_id, fields, *args, **kwargs)
+    4:         <B><FONT COLOR="#A020F0">if</FONT></B> doc <B><FONT COLOR="#A020F0">is</FONT></B> None:
+    1:             <B><FONT COLOR="#A020F0">return</FONT></B> None
+    3:         dbref = pymongo.dbref.DBRef(
+    3:             self._m_collection, doc[<B><FONT COLOR="#BC8F8F">'_id'</FONT></B>],
+    3:             self._m_database <B><FONT COLOR="#A020F0">or</FONT></B> self._m_jar.default_database)
+    3:         obj = self._m_jar._reader.get_ghost(dbref)
+    3:         obj._v_key = doc[self._m_mapping_key]
+    3:         obj._v_parent = self
+    3:         <B><FONT COLOR="#A020F0">return</FONT></B> obj
+       
+    2: <B><FONT COLOR="#A020F0">class</FONT></B> AllItemsMongoContainer(MongoContainer):
+    1:     _m_parent_key = None
+       
+       
+    2: <B><FONT COLOR="#A020F0">class</FONT></B> SubDocumentMongoContainer(MongoContained, MongoContainer):
+    1:     _p_mongo_sub_object = True
+</pre>
+
+      <div class="footer">
+      Generated for revision 5108:5114M on 2011-11-04 17:02:17.574731Z
+      </div>
+    </body>
+    </html>

Added: mongopersist/trunk/coverage/report/mongopersist.zope.html
===================================================================
--- mongopersist/trunk/coverage/report/mongopersist.zope.html	                        (rev 0)
+++ mongopersist/trunk/coverage/report/mongopersist.zope.html	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,25 @@
+
+    <html>
+      <head><title>Test coverage for mongopersist.zope</title>
+      <style type="text/css">
+        a {text-decoration: none; display: block; padding-right: 1em;}
+        a:hover {background: #EFA;}
+        hr {height: 1px; border: none; border-top: 1px solid gray;}
+        .notcovered {background: #FCC;}
+        .footer {margin: 2em; font-size: small; color: gray;}
+      </style>
+      </head>
+      <body><h1>Test coverage for mongopersist.zope</h1>
+      <table>
+    
+<tr><td><a href="mongopersist.html">mongopersist/</a></td> <td style="background: yellow">&nbsp;&nbsp;&nbsp;&nbsp;</td> <td>covered 99% (1 of 695 uncovered)</td></tr>
+<tr><td><a href="mongopersist.zope.html">&nbsp;&nbsp;&nbsp;&nbsp;zope/</a></td> <td style="background: green">&nbsp;&nbsp;&nbsp;&nbsp;</td> <td>covered 100% (0 of 180 uncovered)</td></tr>
+<tr><td><a href="mongopersist.zope.__init__.html">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;__init__.py</a></td> <td style="background: green">&nbsp;&nbsp;&nbsp;&nbsp;</td> <td>covered 100% (0 of 1 uncovered)</td></tr>
+<tr><td><a href="mongopersist.zope.container.html">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;container.py</a></td> <td style="background: green">&nbsp;&nbsp;&nbsp;&nbsp;</td> <td>covered 100% (0 of 179 uncovered)</td></tr>
+</table><hr/>
+
+      <div class="footer">
+      Generated for revision 5108:5114M on 2011-11-04 17:02:17.574731Z
+      </div>
+    </body>
+    </html>

Added: mongopersist/trunk/develop-eggs/mongopersist.egg-link
===================================================================
--- mongopersist/trunk/develop-eggs/mongopersist.egg-link	                        (rev 0)
+++ mongopersist/trunk/develop-eggs/mongopersist.egg-link	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,2 @@
+/opt/zope/cipherhealth/packages/mongopersist/src
+../
\ No newline at end of file

Added: mongopersist/trunk/dist/mongopersist-0.1dev.tar.gz
===================================================================
(Binary files differ)


Property changes on: mongopersist/trunk/dist/mongopersist-0.1dev.tar.gz
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: mongopersist/trunk/parts/buildout/site.py
===================================================================
--- mongopersist/trunk/parts/buildout/site.py	                        (rev 0)
+++ mongopersist/trunk/parts/buildout/site.py	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,580 @@
+"""Append module search paths for third-party packages to sys.path.
+
+****************************************************************
+* This module is automatically imported during initialization. *
+****************************************************************
+
+In earlier versions of Python (up to 1.5a3), scripts or modules that
+needed to use site-specific modules would place ``import site''
+somewhere near the top of their code.  Because of the automatic
+import, this is no longer necessary (but code that does it still
+works).
+
+This will append site-specific paths to the module search path.  On
+Unix (including Mac OSX), it starts with sys.prefix and
+sys.exec_prefix (if different) and appends
+lib/python<version>/site-packages as well as lib/site-python.
+On other platforms (such as Windows), it tries each of the
+prefixes directly, as well as with lib/site-packages appended.  The
+resulting directories, if they exist, are appended to sys.path, and
+also inspected for path configuration files.
+
+A path configuration file is a file whose name has the form
+<package>.pth; its contents are additional directories (one per line)
+to be added to sys.path.  Non-existing directories (or
+non-directories) are never added to sys.path; no directory is added to
+sys.path more than once.  Blank lines and lines beginning with
+'#' are skipped. Lines starting with 'import' are executed.
+
+For example, suppose sys.prefix and sys.exec_prefix are set to
+/usr/local and there is a directory /usr/local/lib/python2.5/site-packages
+with three subdirectories, foo, bar and spam, and two path
+configuration files, foo.pth and bar.pth.  Assume foo.pth contains the
+following:
+
+  # foo package configuration
+  foo
+  bar
+  bletch
+
+and bar.pth contains:
+
+  # bar package configuration
+  bar
+
+Then the following directories are added to sys.path, in this order:
+
+  /usr/local/lib/python2.5/site-packages/bar
+  /usr/local/lib/python2.5/site-packages/foo
+
+Note that bletch is omitted because it doesn't exist; bar precedes foo
+because bar.pth comes alphabetically before foo.pth; and spam is
+omitted because it is not mentioned in either path configuration file.
+
+After these path manipulations, an attempt is made to import a module
+named sitecustomize, which can perform arbitrary additional
+site-specific customizations.  If this import fails with an
+ImportError exception, it is silently ignored.
+
+"""
+
+import sys
+import os
+import __builtin__
+
+# Prefixes for site-packages; add additional prefixes like /usr/local here
+PREFIXES = [sys.prefix, sys.exec_prefix]
+# Enable per user site-packages directory
+# set it to False to disable the feature or True to force the feature
+ENABLE_USER_SITE = False # buildout does not support user sites.
+# for distutils.commands.install
+USER_SITE = None
+USER_BASE = None
+
+
+def makepath(*paths):
+    dir = os.path.abspath(os.path.join(*paths))
+    return dir, os.path.normcase(dir)
+
+
+def abs__file__():
+    """Set all module' __file__ attribute to an absolute path"""
+    for m in sys.modules.values():
+        if hasattr(m, '__loader__'):
+            continue   # don't mess with a PEP 302-supplied __file__
+        try:
+            m.__file__ = os.path.abspath(m.__file__)
+        except AttributeError:
+            continue
+
+
+def removeduppaths():
+    """ Remove duplicate entries from sys.path along with making them
+    absolute"""
+    # This ensures that the initial path provided by the interpreter contains
+    # only absolute pathnames, even if we're running from the build directory.
+    L = []
+    known_paths = set()
+    for dir in sys.path:
+        # Filter out duplicate paths (on case-insensitive file systems also
+        # if they only differ in case); turn relative paths into absolute
+        # paths.
+        dir, dircase = makepath(dir)
+        if not dircase in known_paths:
+            L.append(dir)
+            known_paths.add(dircase)
+    sys.path[:] = L
+    return known_paths
+
+# XXX This should not be part of site.py, since it is needed even when
+# using the -S option for Python.  See http://www.python.org/sf/586680
+def addbuilddir():
+    """Append ./build/lib.<platform> in case we're running in the build dir
+    (especially for Guido :-)"""
+    from distutils.util import get_platform
+    s = "build/lib.%s-%.3s" % (get_platform(), sys.version)
+    if hasattr(sys, 'gettotalrefcount'):
+        s += '-pydebug'
+    s = os.path.join(os.path.dirname(sys.path[-1]), s)
+    sys.path.append(s)
+
+
+def _init_pathinfo():
+    """Return a set containing all existing directory entries from sys.path"""
+    d = set()
+    for dir in sys.path:
+        try:
+            if os.path.isdir(dir):
+                dir, dircase = makepath(dir)
+                d.add(dircase)
+        except TypeError:
+            continue
+    return d
+
+
+def addpackage(sitedir, name, known_paths):
+    """Process a .pth file within the site-packages directory:
+       For each line in the file, either combine it with sitedir to a path
+       and add that to known_paths, or execute it if it starts with 'import '.
+    """
+    if known_paths is None:
+        _init_pathinfo()
+        reset = 1
+    else:
+        reset = 0
+    fullname = os.path.join(sitedir, name)
+    try:
+        f = open(fullname, "rU")
+    except IOError:
+        return
+    with f:
+        for line in f:
+            if line.startswith("#"):
+                continue
+            if line.startswith(("import ", "import\t")):
+                exec line
+                continue
+            line = line.rstrip()
+            dir, dircase = makepath(sitedir, line)
+            if not dircase in known_paths and os.path.exists(dir):
+                sys.path.append(dir)
+                known_paths.add(dircase)
+    if reset:
+        known_paths = None
+    return known_paths
+
+
+def addsitedir(sitedir, known_paths=None):
+    """Add 'sitedir' argument to sys.path if missing and handle .pth files in
+    'sitedir'"""
+    if known_paths is None:
+        known_paths = _init_pathinfo()
+        reset = 1
+    else:
+        reset = 0
+    sitedir, sitedircase = makepath(sitedir)
+    if not sitedircase in known_paths:
+        sys.path.append(sitedir)        # Add path component
+    try:
+        names = os.listdir(sitedir)
+    except os.error:
+        return
+    dotpth = os.extsep + "pth"
+    names = [name for name in names if name.endswith(dotpth)]
+    for name in sorted(names):
+        addpackage(sitedir, name, known_paths)
+    if reset:
+        known_paths = None
+    return known_paths
+
+
+def check_enableusersite():
+    """Check if user site directory is safe for inclusion
+
+    The function tests for the command line flag (including environment var),
+    process uid/gid equal to effective uid/gid.
+
+    None: Disabled for security reasons
+    False: Disabled by user (command line option)
+    True: Safe and enabled
+    """
+    if sys.flags.no_user_site:
+        return False
+
+    if hasattr(os, "getuid") and hasattr(os, "geteuid"):
+        # check process uid == effective uid
+        if os.geteuid() != os.getuid():
+            return None
+    if hasattr(os, "getgid") and hasattr(os, "getegid"):
+        # check process gid == effective gid
+        if os.getegid() != os.getgid():
+            return None
+
+    return True
+
+
+def addusersitepackages(known_paths):
+    """Add a per user site-package to sys.path
+
+    Each user has its own python directory with site-packages in the
+    home directory.
+
+    USER_BASE is the root directory for all Python versions
+
+    USER_SITE is the user specific site-packages directory
+
+    USER_SITE/.. can be used for data.
+    """
+    global USER_BASE, USER_SITE, ENABLE_USER_SITE
+    env_base = os.environ.get("PYTHONUSERBASE", None)
+
+    def joinuser(*args):
+        return os.path.expanduser(os.path.join(*args))
+
+    #if sys.platform in ('os2emx', 'riscos'):
+    #    # Don't know what to put here
+    #    USER_BASE = ''
+    #    USER_SITE = ''
+    if os.name == "nt":
+        base = os.environ.get("APPDATA") or "~"
+        USER_BASE = env_base if env_base else joinuser(base, "Python")
+        USER_SITE = os.path.join(USER_BASE,
+                                 "Python" + sys.version[0] + sys.version[2],
+                                 "site-packages")
+    else:
+        USER_BASE = env_base if env_base else joinuser("~", ".local")
+        USER_SITE = os.path.join(USER_BASE, "lib",
+                                 "python" + sys.version[:3],
+                                 "site-packages")
+
+    if ENABLE_USER_SITE and os.path.isdir(USER_SITE):
+        addsitedir(USER_SITE, known_paths)
+    return known_paths
+
+
+def addsitepackages(known_paths):
+    """Add site packages, as determined by zc.buildout.
+
+    See original_addsitepackages, below, for the original version."""
+    buildout_paths = [
+        '/opt/zope/packages/eggs/setuptools-0.6c12dev_r88846-py2.6.egg',
+        '/opt/zope/packages/eggs/zc.buildout-1.5.2-py2.6.egg'
+        ]
+    for path in buildout_paths:
+        sitedir, sitedircase = makepath(path)
+        if not sitedircase in known_paths and os.path.exists(sitedir):
+            sys.path.append(sitedir)
+            known_paths.add(sitedircase)
+    return known_paths
+
+def original_addsitepackages(known_paths):
+    """Add site-packages (and possibly site-python) to sys.path"""
+    sitedirs = []
+    seen = []
+
+    for prefix in PREFIXES:
+        if not prefix or prefix in seen:
+            continue
+        seen.append(prefix)
+
+        if sys.platform in ('os2emx', 'riscos'):
+            sitedirs.append(os.path.join(prefix, "Lib", "site-packages"))
+        elif os.sep == '/':
+            sitedirs.append(os.path.join(prefix, "lib",
+                                        "python" + sys.version[:3],
+                                        "site-packages"))
+            sitedirs.append(os.path.join(prefix, "lib", "site-python"))
+        else:
+            sitedirs.append(prefix)
+            sitedirs.append(os.path.join(prefix, "lib", "site-packages"))
+
+        if sys.platform == "darwin":
+            # for framework builds *only* we add the standard Apple
+            # locations. Currently only per-user, but /Library and
+            # /Network/Library could be added too
+            if 'Python.framework' in prefix:
+                sitedirs.append(
+                    os.path.expanduser(
+                        os.path.join("~", "Library", "Python",
+                                     sys.version[:3], "site-packages")))
+
+    for sitedir in sitedirs:
+        if os.path.isdir(sitedir):
+            addsitedir(sitedir, known_paths)
+
+    return known_paths
+
+
+def setBEGINLIBPATH():
+    """The OS/2 EMX port has optional extension modules that do double duty
+    as DLLs (and must use the .DLL file extension) for other extensions.
+    The library search path needs to be amended so these will be found
+    during module import.  Use BEGINLIBPATH so that these are at the start
+    of the library search path.
+
+    """
+    dllpath = os.path.join(sys.prefix, "Lib", "lib-dynload")
+    libpath = os.environ['BEGINLIBPATH'].split(';')
+    if libpath[-1]:
+        libpath.append(dllpath)
+    else:
+        libpath[-1] = dllpath
+    os.environ['BEGINLIBPATH'] = ';'.join(libpath)
+
+
+def setquit():
+    """Define new built-ins 'quit' and 'exit'.
+    These are simply strings that display a hint on how to exit.
+
+    """
+    if os.sep == ':':
+        eof = 'Cmd-Q'
+    elif os.sep == '\\':
+        eof = 'Ctrl-Z plus Return'
+    else:
+        eof = 'Ctrl-D (i.e. EOF)'
+
+    class Quitter(object):
+        def __init__(self, name):
+            self.name = name
+        def __repr__(self):
+            return 'Use %s() or %s to exit' % (self.name, eof)
+        def __call__(self, code=None):
+            # Shells like IDLE catch the SystemExit, but listen when their
+            # stdin wrapper is closed.
+            try:
+                sys.stdin.close()
+            except:
+                pass
+            raise SystemExit(code)
+    __builtin__.quit = Quitter('quit')
+    __builtin__.exit = Quitter('exit')
+
+
+class _Printer(object):
+    """interactive prompt objects for printing the license text, a list of
+    contributors and the copyright notice."""
+
+    MAXLINES = 23
+
+    def __init__(self, name, data, files=(), dirs=()):
+        self.__name = name
+        self.__data = data
+        self.__files = files
+        self.__dirs = dirs
+        self.__lines = None
+
+    def __setup(self):
+        if self.__lines:
+            return
+        data = None
+        for dir in self.__dirs:
+            for filename in self.__files:
+                filename = os.path.join(dir, filename)
+                try:
+                    fp = file(filename, "rU")
+                    data = fp.read()
+                    fp.close()
+                    break
+                except IOError:
+                    pass
+            if data:
+                break
+        if not data:
+            data = self.__data
+        self.__lines = data.split('\n')
+        self.__linecnt = len(self.__lines)
+
+    def __repr__(self):
+        self.__setup()
+        if len(self.__lines) <= self.MAXLINES:
+            return "\n".join(self.__lines)
+        else:
+            return "Type %s() to see the full %s text" % ((self.__name,)*2)
+
+    def __call__(self):
+        self.__setup()
+        prompt = 'Hit Return for more, or q (and Return) to quit: '
+        lineno = 0
+        while 1:
+            try:
+                for i in range(lineno, lineno + self.MAXLINES):
+                    print self.__lines[i]
+            except IndexError:
+                break
+            else:
+                lineno += self.MAXLINES
+                key = None
+                while key is None:
+                    key = raw_input(prompt)
+                    if key not in ('', 'q'):
+                        key = None
+                if key == 'q':
+                    break
+
+def setcopyright():
+    """Set 'copyright' and 'credits' in __builtin__"""
+    __builtin__.copyright = _Printer("copyright", sys.copyright)
+    if sys.platform[:4] == 'java':
+        __builtin__.credits = _Printer(
+            "credits",
+            "Jython is maintained by the Jython developers (www.jython.org).")
+    else:
+        __builtin__.credits = _Printer("credits", """\
+    Thanks to CWI, CNRI, BeOpen.com, Zope Corporation and a cast of thousands
+    for supporting Python development.  See www.python.org for more information.""")
+    here = os.path.dirname(os.__file__)
+    __builtin__.license = _Printer(
+        "license", "See http://www.python.org/%.3s/license.html" % sys.version,
+        ["LICENSE.txt", "LICENSE"],
+        [os.path.join(here, os.pardir), here, os.curdir])
+
+
+class _Helper(object):
+    """Define the built-in 'help'.
+    This is a wrapper around pydoc.help (with a twist).
+
+    """
+
+    def __repr__(self):
+        return "Type help() for interactive help, " \
+               "or help(object) for help about object."
+    def __call__(self, *args, **kwds):
+        import pydoc
+        return pydoc.help(*args, **kwds)
+
+def sethelper():
+    __builtin__.help = _Helper()
+
+def aliasmbcs():
+    """On Windows, some default encodings are not provided by Python,
+    while they are always available as "mbcs" in each locale. Make
+    them usable by aliasing to "mbcs" in such a case."""
+    if sys.platform == 'win32':
+        import locale, codecs
+        enc = locale.getdefaultlocale()[1]
+        if enc.startswith('cp'):            # "cp***" ?
+            try:
+                codecs.lookup(enc)
+            except LookupError:
+                import encodings
+                encodings._cache[enc] = encodings._unknown
+                encodings.aliases.aliases[enc] = 'mbcs'
+
+def setencoding():
+    """Set the string encoding used by the Unicode implementation.  The
+    default is 'ascii', but if you're willing to experiment, you can
+    change this."""
+    encoding = "ascii" # Default value set by _PyUnicode_Init()
+    if 0:
+        # Enable to support locale aware default string encodings.
+        import locale
+        loc = locale.getdefaultlocale()
+        if loc[1]:
+            encoding = loc[1]
+    if 0:
+        # Enable to switch off string to Unicode coercion and implicit
+        # Unicode to string conversion.
+        encoding = "undefined"
+    if encoding != "ascii":
+        # On Non-Unicode builds this will raise an AttributeError...
+        sys.setdefaultencoding(encoding) # Needs Python Unicode build !
+
+
+def execsitecustomize():
+    """Run custom site specific code, if available."""
+    try:
+        import sitecustomize
+    except ImportError:
+        pass
+
+
+def execusercustomize():
+    """Run custom user specific code, if available."""
+    try:
+        import usercustomize
+    except ImportError:
+        pass
+
+
+def main():
+    global ENABLE_USER_SITE
+
+    abs__file__()
+    known_paths = removeduppaths()
+    if (os.name == "posix" and sys.path and
+        os.path.basename(sys.path[-1]) == "Modules"):
+        addbuilddir()
+    if ENABLE_USER_SITE is None:
+        ENABLE_USER_SITE = check_enableusersite()
+    known_paths = addusersitepackages(known_paths)
+    known_paths = addsitepackages(known_paths)
+    if sys.platform == 'os2emx':
+        setBEGINLIBPATH()
+    setquit()
+    setcopyright()
+    sethelper()
+    aliasmbcs()
+    setencoding()
+    execsitecustomize()
+    if ENABLE_USER_SITE:
+        execusercustomize()
+    # Remove sys.setdefaultencoding() so that users cannot change the
+    # encoding after initialization.  The test for presence is needed when
+    # this module is run as a script, because this code is executed twice.
+    if hasattr(sys, "setdefaultencoding"):
+        del sys.setdefaultencoding
+
+main()
+
+def _script():
+    help = """\
+    %s [--user-base] [--user-site]
+
+    Without arguments print some useful information
+    With arguments print the value of USER_BASE and/or USER_SITE separated
+    by '%s'.
+
+    Exit codes with --user-base or --user-site:
+      0 - user site directory is enabled
+      1 - user site directory is disabled by user
+      2 - uses site directory is disabled by super user
+          or for security reasons
+     >2 - unknown error
+    """
+    args = sys.argv[1:]
+    if not args:
+        print "sys.path = ["
+        for dir in sys.path:
+            print "    %r," % (dir,)
+        print "]"
+        print "USER_BASE: %r (%s)" % (USER_BASE,
+            "exists" if os.path.isdir(USER_BASE) else "doesn't exist")
+        print "USER_SITE: %r (%s)" % (USER_SITE,
+            "exists" if os.path.isdir(USER_SITE) else "doesn't exist")
+        print "ENABLE_USER_SITE: %r" %  ENABLE_USER_SITE
+        sys.exit(0)
+
+    buffer = []
+    if '--user-base' in args:
+        buffer.append(USER_BASE)
+    if '--user-site' in args:
+        buffer.append(USER_SITE)
+
+    if buffer:
+        print os.pathsep.join(buffer)
+        if ENABLE_USER_SITE:
+            sys.exit(0)
+        elif ENABLE_USER_SITE is False:
+            sys.exit(1)
+        elif ENABLE_USER_SITE is None:
+            sys.exit(2)
+        else:
+            sys.exit(3)
+    else:
+        import textwrap
+        print textwrap.dedent(help % (sys.argv[0], os.pathsep))
+        sys.exit(10)
+
+if __name__ == '__main__':
+    _script()


Property changes on: mongopersist/trunk/parts/buildout/site.py
___________________________________________________________________
Added: svn:keywords
   + Id

Added: mongopersist/trunk/parts/buildout/site.pyo
===================================================================
(Binary files differ)


Property changes on: mongopersist/trunk/parts/buildout/site.pyo
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream


Property changes on: mongopersist/trunk/parts/buildout/sitecustomize.py
___________________________________________________________________
Added: svn:keywords
   + Id

Added: mongopersist/trunk/parts/buildout/sitecustomize.pyo
===================================================================
(Binary files differ)


Property changes on: mongopersist/trunk/parts/buildout/sitecustomize.pyo
___________________________________________________________________
Added: svn:mime-type
   + application/octet-stream

Added: mongopersist/trunk/parts/coverage-report/site.py
===================================================================
--- mongopersist/trunk/parts/coverage-report/site.py	                        (rev 0)
+++ mongopersist/trunk/parts/coverage-report/site.py	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,580 @@
+"""Append module search paths for third-party packages to sys.path.
+
+****************************************************************
+* This module is automatically imported during initialization. *
+****************************************************************
+
+In earlier versions of Python (up to 1.5a3), scripts or modules that
+needed to use site-specific modules would place ``import site''
+somewhere near the top of their code.  Because of the automatic
+import, this is no longer necessary (but code that does it still
+works).
+
+This will append site-specific paths to the module search path.  On
+Unix (including Mac OSX), it starts with sys.prefix and
+sys.exec_prefix (if different) and appends
+lib/python<version>/site-packages as well as lib/site-python.
+On other platforms (such as Windows), it tries each of the
+prefixes directly, as well as with lib/site-packages appended.  The
+resulting directories, if they exist, are appended to sys.path, and
+also inspected for path configuration files.
+
+A path configuration file is a file whose name has the form
+<package>.pth; its contents are additional directories (one per line)
+to be added to sys.path.  Non-existing directories (or
+non-directories) are never added to sys.path; no directory is added to
+sys.path more than once.  Blank lines and lines beginning with
+'#' are skipped. Lines starting with 'import' are executed.
+
+For example, suppose sys.prefix and sys.exec_prefix are set to
+/usr/local and there is a directory /usr/local/lib/python2.5/site-packages
+with three subdirectories, foo, bar and spam, and two path
+configuration files, foo.pth and bar.pth.  Assume foo.pth contains the
+following:
+
+  # foo package configuration
+  foo
+  bar
+  bletch
+
+and bar.pth contains:
+
+  # bar package configuration
+  bar
+
+Then the following directories are added to sys.path, in this order:
+
+  /usr/local/lib/python2.5/site-packages/bar
+  /usr/local/lib/python2.5/site-packages/foo
+
+Note that bletch is omitted because it doesn't exist; bar precedes foo
+because bar.pth comes alphabetically before foo.pth; and spam is
+omitted because it is not mentioned in either path configuration file.
+
+After these path manipulations, an attempt is made to import a module
+named sitecustomize, which can perform arbitrary additional
+site-specific customizations.  If this import fails with an
+ImportError exception, it is silently ignored.
+
+"""
+
+import sys
+import os
+import __builtin__
+
+# Prefixes for site-packages; add additional prefixes like /usr/local here
+PREFIXES = [sys.prefix, sys.exec_prefix]
+# Enable per user site-packages directory
+# set it to False to disable the feature or True to force the feature
+ENABLE_USER_SITE = False # buildout does not support user sites.
+# for distutils.commands.install
+USER_SITE = None
+USER_BASE = None
+
+
+def makepath(*paths):
+    dir = os.path.abspath(os.path.join(*paths))
+    return dir, os.path.normcase(dir)
+
+
+def abs__file__():
+    """Set all module' __file__ attribute to an absolute path"""
+    for m in sys.modules.values():
+        if hasattr(m, '__loader__'):
+            continue   # don't mess with a PEP 302-supplied __file__
+        try:
+            m.__file__ = os.path.abspath(m.__file__)
+        except AttributeError:
+            continue
+
+
+def removeduppaths():
+    """ Remove duplicate entries from sys.path along with making them
+    absolute"""
+    # This ensures that the initial path provided by the interpreter contains
+    # only absolute pathnames, even if we're running from the build directory.
+    L = []
+    known_paths = set()
+    for dir in sys.path:
+        # Filter out duplicate paths (on case-insensitive file systems also
+        # if they only differ in case); turn relative paths into absolute
+        # paths.
+        dir, dircase = makepath(dir)
+        if not dircase in known_paths:
+            L.append(dir)
+            known_paths.add(dircase)
+    sys.path[:] = L
+    return known_paths
+
+# XXX This should not be part of site.py, since it is needed even when
+# using the -S option for Python.  See http://www.python.org/sf/586680
+def addbuilddir():
+    """Append ./build/lib.<platform> in case we're running in the build dir
+    (especially for Guido :-)"""
+    from distutils.util import get_platform
+    s = "build/lib.%s-%.3s" % (get_platform(), sys.version)
+    if hasattr(sys, 'gettotalrefcount'):
+        s += '-pydebug'
+    s = os.path.join(os.path.dirname(sys.path[-1]), s)
+    sys.path.append(s)
+
+
+def _init_pathinfo():
+    """Return a set containing all existing directory entries from sys.path"""
+    d = set()
+    for dir in sys.path:
+        try:
+            if os.path.isdir(dir):
+                dir, dircase = makepath(dir)
+                d.add(dircase)
+        except TypeError:
+            continue
+    return d
+
+
+def addpackage(sitedir, name, known_paths):
+    """Process a .pth file within the site-packages directory:
+       For each line in the file, either combine it with sitedir to a path
+       and add that to known_paths, or execute it if it starts with 'import '.
+    """
+    if known_paths is None:
+        _init_pathinfo()
+        reset = 1
+    else:
+        reset = 0
+    fullname = os.path.join(sitedir, name)
+    try:
+        f = open(fullname, "rU")
+    except IOError:
+        return
+    with f:
+        for line in f:
+            if line.startswith("#"):
+                continue
+            if line.startswith(("import ", "import\t")):
+                exec line
+                continue
+            line = line.rstrip()
+            dir, dircase = makepath(sitedir, line)
+            if not dircase in known_paths and os.path.exists(dir):
+                sys.path.append(dir)
+                known_paths.add(dircase)
+    if reset:
+        known_paths = None
+    return known_paths
+
+
+def addsitedir(sitedir, known_paths=None):
+    """Add 'sitedir' argument to sys.path if missing and handle .pth files in
+    'sitedir'"""
+    if known_paths is None:
+        known_paths = _init_pathinfo()
+        reset = 1
+    else:
+        reset = 0
+    sitedir, sitedircase = makepath(sitedir)
+    if not sitedircase in known_paths:
+        sys.path.append(sitedir)        # Add path component
+    try:
+        names = os.listdir(sitedir)
+    except os.error:
+        return
+    dotpth = os.extsep + "pth"
+    names = [name for name in names if name.endswith(dotpth)]
+    for name in sorted(names):
+        addpackage(sitedir, name, known_paths)
+    if reset:
+        known_paths = None
+    return known_paths
+
+
+def check_enableusersite():
+    """Check if user site directory is safe for inclusion
+
+    The function tests for the command line flag (including environment var),
+    process uid/gid equal to effective uid/gid.
+
+    None: Disabled for security reasons
+    False: Disabled by user (command line option)
+    True: Safe and enabled
+    """
+    if sys.flags.no_user_site:
+        return False
+
+    if hasattr(os, "getuid") and hasattr(os, "geteuid"):
+        # check process uid == effective uid
+        if os.geteuid() != os.getuid():
+            return None
+    if hasattr(os, "getgid") and hasattr(os, "getegid"):
+        # check process gid == effective gid
+        if os.getegid() != os.getgid():
+            return None
+
+    return True
+
+
+def addusersitepackages(known_paths):
+    """Add a per user site-package to sys.path
+
+    Each user has its own python directory with site-packages in the
+    home directory.
+
+    USER_BASE is the root directory for all Python versions
+
+    USER_SITE is the user specific site-packages directory
+
+    USER_SITE/.. can be used for data.
+    """
+    global USER_BASE, USER_SITE, ENABLE_USER_SITE
+    env_base = os.environ.get("PYTHONUSERBASE", None)
+
+    def joinuser(*args):
+        return os.path.expanduser(os.path.join(*args))
+
+    #if sys.platform in ('os2emx', 'riscos'):
+    #    # Don't know what to put here
+    #    USER_BASE = ''
+    #    USER_SITE = ''
+    if os.name == "nt":
+        base = os.environ.get("APPDATA") or "~"
+        USER_BASE = env_base if env_base else joinuser(base, "Python")
+        USER_SITE = os.path.join(USER_BASE,
+                                 "Python" + sys.version[0] + sys.version[2],
+                                 "site-packages")
+    else:
+        USER_BASE = env_base if env_base else joinuser("~", ".local")
+        USER_SITE = os.path.join(USER_BASE, "lib",
+                                 "python" + sys.version[:3],
+                                 "site-packages")
+
+    if ENABLE_USER_SITE and os.path.isdir(USER_SITE):
+        addsitedir(USER_SITE, known_paths)
+    return known_paths
+
+
+def addsitepackages(known_paths):
+    """Add site packages, as determined by zc.buildout.
+
+    See original_addsitepackages, below, for the original version."""
+    buildout_paths = [
+        '/opt/zope/packages/eggs/z3c.coverage-1.2.0-py2.6.egg',
+        '/opt/zope/packages/eggs/setuptools-0.6c12dev_r88846-py2.6.egg'
+        ]
+    for path in buildout_paths:
+        sitedir, sitedircase = makepath(path)
+        if not sitedircase in known_paths and os.path.exists(sitedir):
+            sys.path.append(sitedir)
+            known_paths.add(sitedircase)
+    return known_paths
+
+def original_addsitepackages(known_paths):
+    """Add site-packages (and possibly site-python) to sys.path"""
+    sitedirs = []
+    seen = []
+
+    for prefix in PREFIXES:
+        if not prefix or prefix in seen:
+            continue
+        seen.append(prefix)
+
+        if sys.platform in ('os2emx', 'riscos'):
+            sitedirs.append(os.path.join(prefix, "Lib", "site-packages"))
+        elif os.sep == '/':
+            sitedirs.append(os.path.join(prefix, "lib",
+                                        "python" + sys.version[:3],
+                                        "site-packages"))
+            sitedirs.append(os.path.join(prefix, "lib", "site-python"))
+        else:
+            sitedirs.append(prefix)
+            sitedirs.append(os.path.join(prefix, "lib", "site-packages"))
+
+        if sys.platform == "darwin":
+            # for framework builds *only* we add the standard Apple
+            # locations. Currently only per-user, but /Library and
+            # /Network/Library could be added too
+            if 'Python.framework' in prefix:
+                sitedirs.append(
+                    os.path.expanduser(
+                        os.path.join("~", "Library", "Python",
+                                     sys.version[:3], "site-packages")))
+
+    for sitedir in sitedirs:
+        if os.path.isdir(sitedir):
+            addsitedir(sitedir, known_paths)
+
+    return known_paths
+
+
+def setBEGINLIBPATH():
+    """The OS/2 EMX port has optional extension modules that do double duty
+    as DLLs (and must use the .DLL file extension) for other extensions.
+    The library search path needs to be amended so these will be found
+    during module import.  Use BEGINLIBPATH so that these are at the start
+    of the library search path.
+
+    """
+    dllpath = os.path.join(sys.prefix, "Lib", "lib-dynload")
+    libpath = os.environ['BEGINLIBPATH'].split(';')
+    if libpath[-1]:
+        libpath.append(dllpath)
+    else:
+        libpath[-1] = dllpath
+    os.environ['BEGINLIBPATH'] = ';'.join(libpath)
+
+
+def setquit():
+    """Define new built-ins 'quit' and 'exit'.
+    These are simply strings that display a hint on how to exit.
+
+    """
+    if os.sep == ':':
+        eof = 'Cmd-Q'
+    elif os.sep == '\\':
+        eof = 'Ctrl-Z plus Return'
+    else:
+        eof = 'Ctrl-D (i.e. EOF)'
+
+    class Quitter(object):
+        def __init__(self, name):
+            self.name = name
+        def __repr__(self):
+            return 'Use %s() or %s to exit' % (self.name, eof)
+        def __call__(self, code=None):
+            # Shells like IDLE catch the SystemExit, but listen when their
+            # stdin wrapper is closed.
+            try:
+                sys.stdin.close()
+            except:
+                pass
+            raise SystemExit(code)
+    __builtin__.quit = Quitter('quit')
+    __builtin__.exit = Quitter('exit')
+
+
+class _Printer(object):
+    """interactive prompt objects for printing the license text, a list of
+    contributors and the copyright notice."""
+
+    MAXLINES = 23
+
+    def __init__(self, name, data, files=(), dirs=()):
+        self.__name = name
+        self.__data = data
+        self.__files = files
+        self.__dirs = dirs
+        self.__lines = None
+
+    def __setup(self):
+        if self.__lines:
+            return
+        data = None
+        for dir in self.__dirs:
+            for filename in self.__files:
+                filename = os.path.join(dir, filename)
+                try:
+                    fp = file(filename, "rU")
+                    data = fp.read()
+                    fp.close()
+                    break
+                except IOError:
+                    pass
+            if data:
+                break
+        if not data:
+            data = self.__data
+        self.__lines = data.split('\n')
+        self.__linecnt = len(self.__lines)
+
+    def __repr__(self):
+        self.__setup()
+        if len(self.__lines) <= self.MAXLINES:
+            return "\n".join(self.__lines)
+        else:
+            return "Type %s() to see the full %s text" % ((self.__name,)*2)
+
+    def __call__(self):
+        self.__setup()
+        prompt = 'Hit Return for more, or q (and Return) to quit: '
+        lineno = 0
+        while 1:
+            try:
+                for i in range(lineno, lineno + self.MAXLINES):
+                    print self.__lines[i]
+            except IndexError:
+                break
+            else:
+                lineno += self.MAXLINES
+                key = None
+                while key is None:
+                    key = raw_input(prompt)
+                    if key not in ('', 'q'):
+                        key = None
+                if key == 'q':
+                    break
+
+def setcopyright():
+    """Set 'copyright' and 'credits' in __builtin__"""
+    __builtin__.copyright = _Printer("copyright", sys.copyright)
+    if sys.platform[:4] == 'java':
+        __builtin__.credits = _Printer(
+            "credits",
+            "Jython is maintained by the Jython developers (www.jython.org).")
+    else:
+        __builtin__.credits = _Printer("credits", """\
+    Thanks to CWI, CNRI, BeOpen.com, Zope Corporation and a cast of thousands
+    for supporting Python development.  See www.python.org for more information.""")
+    here = os.path.dirname(os.__file__)
+    __builtin__.license = _Printer(
+        "license", "See http://www.python.org/%.3s/license.html" % sys.version,
+        ["LICENSE.txt", "LICENSE"],
+        [os.path.join(here, os.pardir), here, os.curdir])
+
+
+class _Helper(object):
+    """Define the built-in 'help'.
+    This is a wrapper around pydoc.help (with a twist).
+
+    """
+
+    def __repr__(self):
+        return "Type help() for interactive help, " \
+               "or help(object) for help about object."
+    def __call__(self, *args, **kwds):
+        import pydoc
+        return pydoc.help(*args, **kwds)
+
+def sethelper():
+    __builtin__.help = _Helper()
+
+def aliasmbcs():
+    """On Windows, some default encodings are not provided by Python,
+    while they are always available as "mbcs" in each locale. Make
+    them usable by aliasing to "mbcs" in such a case."""
+    if sys.platform == 'win32':
+        import locale, codecs
+        enc = locale.getdefaultlocale()[1]
+        if enc.startswith('cp'):            # "cp***" ?
+            try:
+                codecs.lookup(enc)
+            except LookupError:
+                import encodings
+                encodings._cache[enc] = encodings._unknown
+                encodings.aliases.aliases[enc] = 'mbcs'
+
+def setencoding():
+    """Set the string encoding used by the Unicode implementation.  The
+    default is 'ascii', but if you're willing to experiment, you can
+    change this."""
+    encoding = "ascii" # Default value set by _PyUnicode_Init()
+    if 0:
+        # Enable to support locale aware default string encodings.
+        import locale
+        loc = locale.getdefaultlocale()
+        if loc[1]:
+            encoding = loc[1]
+    if 0:
+        # Enable to switch off string to Unicode coercion and implicit
+        # Unicode to string conversion.
+        encoding = "undefined"
+    if encoding != "ascii":
+        # On Non-Unicode builds this will raise an AttributeError...
+        sys.setdefaultencoding(encoding) # Needs Python Unicode build !
+
+
+def execsitecustomize():
+    """Run custom site specific code, if available."""
+    try:
+        import sitecustomize
+    except ImportError:
+        pass
+
+
+def execusercustomize():
+    """Run custom user specific code, if available."""
+    try:
+        import usercustomize
+    except ImportError:
+        pass
+
+
+def main():
+    global ENABLE_USER_SITE
+
+    abs__file__()
+    known_paths = removeduppaths()
+    if (os.name == "posix" and sys.path and
+        os.path.basename(sys.path[-1]) == "Modules"):
+        addbuilddir()
+    if ENABLE_USER_SITE is None:
+        ENABLE_USER_SITE = check_enableusersite()
+    known_paths = addusersitepackages(known_paths)
+    known_paths = addsitepackages(known_paths)
+    if sys.platform == 'os2emx':
+        setBEGINLIBPATH()
+    setquit()
+    setcopyright()
+    sethelper()
+    aliasmbcs()
+    setencoding()
+    execsitecustomize()
+    if ENABLE_USER_SITE:
+        execusercustomize()
+    # Remove sys.setdefaultencoding() so that users cannot change the
+    # encoding after initialization.  The test for presence is needed when
+    # this module is run as a script, because this code is executed twice.
+    if hasattr(sys, "setdefaultencoding"):
+        del sys.setdefaultencoding
+
+main()
+
+def _script():
+    help = """\
+    %s [--user-base] [--user-site]
+
+    Without arguments print some useful information
+    With arguments print the value of USER_BASE and/or USER_SITE separated
+    by '%s'.
+
+    Exit codes with --user-base or --user-site:
+      0 - user site directory is enabled
+      1 - user site directory is disabled by user
+      2 - uses site directory is disabled by super user
+          or for security reasons
+     >2 - unknown error
+    """
+    args = sys.argv[1:]
+    if not args:
+        print "sys.path = ["
+        for dir in sys.path:
+            print "    %r," % (dir,)
+        print "]"
+        print "USER_BASE: %r (%s)" % (USER_BASE,
+            "exists" if os.path.isdir(USER_BASE) else "doesn't exist")
+        print "USER_SITE: %r (%s)" % (USER_SITE,
+            "exists" if os.path.isdir(USER_SITE) else "doesn't exist")
+        print "ENABLE_USER_SITE: %r" %  ENABLE_USER_SITE
+        sys.exit(0)
+
+    buffer = []
+    if '--user-base' in args:
+        buffer.append(USER_BASE)
+    if '--user-site' in args:
+        buffer.append(USER_SITE)
+
+    if buffer:
+        print os.pathsep.join(buffer)
+        if ENABLE_USER_SITE:
+            sys.exit(0)
+        elif ENABLE_USER_SITE is False:
+            sys.exit(1)
+        elif ENABLE_USER_SITE is None:
+            sys.exit(2)
+        else:
+            sys.exit(3)
+    else:
+        import textwrap
+        print textwrap.dedent(help % (sys.argv[0], os.pathsep))
+        sys.exit(10)
+
+if __name__ == '__main__':
+    _script()


Property changes on: mongopersist/trunk/parts/coverage-report/site.py
___________________________________________________________________
Added: svn:keywords
   + Id


Property changes on: mongopersist/trunk/parts/coverage-report/sitecustomize.py
___________________________________________________________________
Added: svn:keywords
   + Id

Added: mongopersist/trunk/parts/coverage-test/site-packages/site.py
===================================================================
--- mongopersist/trunk/parts/coverage-test/site-packages/site.py	                        (rev 0)
+++ mongopersist/trunk/parts/coverage-test/site-packages/site.py	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,625 @@
+"""Append module search paths for third-party packages to sys.path.
+
+****************************************************************
+* This module is automatically imported during initialization. *
+****************************************************************
+
+In earlier versions of Python (up to 1.5a3), scripts or modules that
+needed to use site-specific modules would place ``import site''
+somewhere near the top of their code.  Because of the automatic
+import, this is no longer necessary (but code that does it still
+works).
+
+This will append site-specific paths to the module search path.  On
+Unix (including Mac OSX), it starts with sys.prefix and
+sys.exec_prefix (if different) and appends
+lib/python<version>/site-packages as well as lib/site-python.
+On other platforms (such as Windows), it tries each of the
+prefixes directly, as well as with lib/site-packages appended.  The
+resulting directories, if they exist, are appended to sys.path, and
+also inspected for path configuration files.
+
+A path configuration file is a file whose name has the form
+<package>.pth; its contents are additional directories (one per line)
+to be added to sys.path.  Non-existing directories (or
+non-directories) are never added to sys.path; no directory is added to
+sys.path more than once.  Blank lines and lines beginning with
+'#' are skipped. Lines starting with 'import' are executed.
+
+For example, suppose sys.prefix and sys.exec_prefix are set to
+/usr/local and there is a directory /usr/local/lib/python2.5/site-packages
+with three subdirectories, foo, bar and spam, and two path
+configuration files, foo.pth and bar.pth.  Assume foo.pth contains the
+following:
+
+  # foo package configuration
+  foo
+  bar
+  bletch
+
+and bar.pth contains:
+
+  # bar package configuration
+  bar
+
+Then the following directories are added to sys.path, in this order:
+
+  /usr/local/lib/python2.5/site-packages/bar
+  /usr/local/lib/python2.5/site-packages/foo
+
+Note that bletch is omitted because it doesn't exist; bar precedes foo
+because bar.pth comes alphabetically before foo.pth; and spam is
+omitted because it is not mentioned in either path configuration file.
+
+After these path manipulations, an attempt is made to import a module
+named sitecustomize, which can perform arbitrary additional
+site-specific customizations.  If this import fails with an
+ImportError exception, it is silently ignored.
+
+"""
+
+import sys
+import os
+import __builtin__
+
+# Prefixes for site-packages; add additional prefixes like /usr/local here
+PREFIXES = [sys.prefix, sys.exec_prefix]
+# Enable per user site-packages directory
+# set it to False to disable the feature or True to force the feature
+ENABLE_USER_SITE = False # buildout does not support user sites.
+# for distutils.commands.install
+USER_SITE = None
+USER_BASE = None
+
+
+def makepath(*paths):
+    dir = os.path.abspath(os.path.join(*paths))
+    return dir, os.path.normcase(dir)
+
+
+def abs__file__():
+    """Set all module' __file__ attribute to an absolute path"""
+    for m in sys.modules.values():
+        if hasattr(m, '__loader__'):
+            continue   # don't mess with a PEP 302-supplied __file__
+        try:
+            m.__file__ = os.path.abspath(m.__file__)
+        except AttributeError:
+            continue
+
+
+def removeduppaths():
+    """ Remove duplicate entries from sys.path along with making them
+    absolute"""
+    # This ensures that the initial path provided by the interpreter contains
+    # only absolute pathnames, even if we're running from the build directory.
+    L = []
+    known_paths = set()
+    for dir in sys.path:
+        # Filter out duplicate paths (on case-insensitive file systems also
+        # if they only differ in case); turn relative paths into absolute
+        # paths.
+        dir, dircase = makepath(dir)
+        if not dircase in known_paths:
+            L.append(dir)
+            known_paths.add(dircase)
+    sys.path[:] = L
+    return known_paths
+
+# XXX This should not be part of site.py, since it is needed even when
+# using the -S option for Python.  See http://www.python.org/sf/586680
+def addbuilddir():
+    """Append ./build/lib.<platform> in case we're running in the build dir
+    (especially for Guido :-)"""
+    from distutils.util import get_platform
+    s = "build/lib.%s-%.3s" % (get_platform(), sys.version)
+    if hasattr(sys, 'gettotalrefcount'):
+        s += '-pydebug'
+    s = os.path.join(os.path.dirname(sys.path[-1]), s)
+    sys.path.append(s)
+
+
+def _init_pathinfo():
+    """Return a set containing all existing directory entries from sys.path"""
+    d = set()
+    for dir in sys.path:
+        try:
+            if os.path.isdir(dir):
+                dir, dircase = makepath(dir)
+                d.add(dircase)
+        except TypeError:
+            continue
+    return d
+
+
+def addpackage(sitedir, name, known_paths):
+    """Process a .pth file within the site-packages directory:
+       For each line in the file, either combine it with sitedir to a path
+       and add that to known_paths, or execute it if it starts with 'import '.
+    """
+    if known_paths is None:
+        _init_pathinfo()
+        reset = 1
+    else:
+        reset = 0
+    fullname = os.path.join(sitedir, name)
+    try:
+        f = open(fullname, "rU")
+    except IOError:
+        return
+    with f:
+        for line in f:
+            if line.startswith("#"):
+                continue
+            if line.startswith(("import ", "import\t")):
+                exec line
+                continue
+            line = line.rstrip()
+            dir, dircase = makepath(sitedir, line)
+            if not dircase in known_paths and os.path.exists(dir):
+                sys.path.append(dir)
+                known_paths.add(dircase)
+    if reset:
+        known_paths = None
+    return known_paths
+
+
+def addsitedir(sitedir, known_paths=None):
+    """Add 'sitedir' argument to sys.path if missing and handle .pth files in
+    'sitedir'"""
+    if known_paths is None:
+        known_paths = _init_pathinfo()
+        reset = 1
+    else:
+        reset = 0
+    sitedir, sitedircase = makepath(sitedir)
+    if not sitedircase in known_paths:
+        sys.path.append(sitedir)        # Add path component
+    try:
+        names = os.listdir(sitedir)
+    except os.error:
+        return
+    dotpth = os.extsep + "pth"
+    names = [name for name in names if name.endswith(dotpth)]
+    for name in sorted(names):
+        addpackage(sitedir, name, known_paths)
+    if reset:
+        known_paths = None
+    return known_paths
+
+
+def check_enableusersite():
+    """Check if user site directory is safe for inclusion
+
+    The function tests for the command line flag (including environment var),
+    process uid/gid equal to effective uid/gid.
+
+    None: Disabled for security reasons
+    False: Disabled by user (command line option)
+    True: Safe and enabled
+    """
+    if sys.flags.no_user_site:
+        return False
+
+    if hasattr(os, "getuid") and hasattr(os, "geteuid"):
+        # check process uid == effective uid
+        if os.geteuid() != os.getuid():
+            return None
+    if hasattr(os, "getgid") and hasattr(os, "getegid"):
+        # check process gid == effective gid
+        if os.getegid() != os.getgid():
+            return None
+
+    return True
+
+
+def addusersitepackages(known_paths):
+    """Add a per user site-package to sys.path
+
+    Each user has its own python directory with site-packages in the
+    home directory.
+
+    USER_BASE is the root directory for all Python versions
+
+    USER_SITE is the user specific site-packages directory
+
+    USER_SITE/.. can be used for data.
+    """
+    global USER_BASE, USER_SITE, ENABLE_USER_SITE
+    env_base = os.environ.get("PYTHONUSERBASE", None)
+
+    def joinuser(*args):
+        return os.path.expanduser(os.path.join(*args))
+
+    #if sys.platform in ('os2emx', 'riscos'):
+    #    # Don't know what to put here
+    #    USER_BASE = ''
+    #    USER_SITE = ''
+    if os.name == "nt":
+        base = os.environ.get("APPDATA") or "~"
+        USER_BASE = env_base if env_base else joinuser(base, "Python")
+        USER_SITE = os.path.join(USER_BASE,
+                                 "Python" + sys.version[0] + sys.version[2],
+                                 "site-packages")
+    else:
+        USER_BASE = env_base if env_base else joinuser("~", ".local")
+        USER_SITE = os.path.join(USER_BASE, "lib",
+                                 "python" + sys.version[:3],
+                                 "site-packages")
+
+    if ENABLE_USER_SITE and os.path.isdir(USER_SITE):
+        addsitedir(USER_SITE, known_paths)
+    return known_paths
+
+
+def addsitepackages(known_paths):
+    """Add site packages, as determined by zc.buildout.
+
+    See original_addsitepackages, below, for the original version."""
+    buildout_paths = [
+        '/opt/zope/cipherhealth/packages/mongopersist/src',
+        '/opt/zope/packages/eggs/zope.testrunner-4.0.3-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.interface-3.6.3-py2.6-linux-x86_64.egg',
+        '/opt/zope/packages/eggs/zope.exceptions-3.6.1-py2.6.egg',
+        '/opt/zope/packages/eggs/setuptools-0.6c12dev_r88846-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.container-3.12.0-py2.6-linux-x86_64.egg',
+        '/opt/zope/packages/eggs/rwproperty-1.0-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.testing-3.10.2-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.app.testing-3.8.1-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.dottedname-3.4.6-py2.6.egg',
+        '/opt/zope/packages/eggs/pymongo-2.0.1-py2.6-linux-x86_64.egg',
+        '/opt/zope/packages/eggs/lru-0.1-py2.6.egg',
+        '/opt/zope/packages/eggs/ZODB3-3.10.3-py2.6-linux-x86_64.egg',
+        '/opt/zope/packages/eggs/zope.broken-3.6.0-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.publisher-3.12.6-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.traversing-3.14.0-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.size-3.4.1-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.filerepresentation-3.6.0-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.i18nmessageid-3.5.3-py2.6-linux-x86_64.egg',
+        '/opt/zope/packages/eggs/zope.lifecycleevent-3.6.2-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.security-3.8.2-py2.6-linux-x86_64.egg',
+        '/opt/zope/packages/eggs/zope.location-3.9.0-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.event-3.5.0_1-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.component-3.10.0-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.schema-3.8.0-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.site-3.9.2-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.password-3.6.1-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.i18n-3.7.4-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.app.publication-3.12.0-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.app.dependable-3.5.1-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.app.debug-3.4.1-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.processlifetime-1.0-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.app.appsetup-3.15.0-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.annotation-3.5.0-py2.6.egg',
+        '/opt/zope/packages/eggs/zdaemon-2.0.4-py2.6.egg',
+        '/opt/zope/packages/eggs/ZConfig-2.8.0-py2.6.egg',
+        '/opt/zope/packages/eggs/zc.lockfile-1.0.0-py2.6.egg',
+        '/opt/zope/packages/eggs/transaction-1.1.1-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.proxy-3.6.1-py2.6-linux-x86_64.egg',
+        '/opt/zope/packages/eggs/zope.contenttype-3.5.3-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.configuration-3.7.4-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.browser-1.3-py2.6.egg',
+        '/opt/zope/packages/eggs/pytz-2011g-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.error-3.7.2-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.authentication-3.7.1-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.session-3.9.4-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.minmax-1.1.2-py2.6.egg'
+        ]
+    for path in buildout_paths:
+        sitedir, sitedircase = makepath(path)
+        if not sitedircase in known_paths and os.path.exists(sitedir):
+            sys.path.append(sitedir)
+            known_paths.add(sitedircase)
+    return known_paths
+
+def original_addsitepackages(known_paths):
+    """Add site-packages (and possibly site-python) to sys.path"""
+    sitedirs = []
+    seen = []
+
+    for prefix in PREFIXES:
+        if not prefix or prefix in seen:
+            continue
+        seen.append(prefix)
+
+        if sys.platform in ('os2emx', 'riscos'):
+            sitedirs.append(os.path.join(prefix, "Lib", "site-packages"))
+        elif os.sep == '/':
+            sitedirs.append(os.path.join(prefix, "lib",
+                                        "python" + sys.version[:3],
+                                        "site-packages"))
+            sitedirs.append(os.path.join(prefix, "lib", "site-python"))
+        else:
+            sitedirs.append(prefix)
+            sitedirs.append(os.path.join(prefix, "lib", "site-packages"))
+
+        if sys.platform == "darwin":
+            # for framework builds *only* we add the standard Apple
+            # locations. Currently only per-user, but /Library and
+            # /Network/Library could be added too
+            if 'Python.framework' in prefix:
+                sitedirs.append(
+                    os.path.expanduser(
+                        os.path.join("~", "Library", "Python",
+                                     sys.version[:3], "site-packages")))
+
+    for sitedir in sitedirs:
+        if os.path.isdir(sitedir):
+            addsitedir(sitedir, known_paths)
+
+    return known_paths
+
+
+def setBEGINLIBPATH():
+    """The OS/2 EMX port has optional extension modules that do double duty
+    as DLLs (and must use the .DLL file extension) for other extensions.
+    The library search path needs to be amended so these will be found
+    during module import.  Use BEGINLIBPATH so that these are at the start
+    of the library search path.
+
+    """
+    dllpath = os.path.join(sys.prefix, "Lib", "lib-dynload")
+    libpath = os.environ['BEGINLIBPATH'].split(';')
+    if libpath[-1]:
+        libpath.append(dllpath)
+    else:
+        libpath[-1] = dllpath
+    os.environ['BEGINLIBPATH'] = ';'.join(libpath)
+
+
+def setquit():
+    """Define new built-ins 'quit' and 'exit'.
+    These are simply strings that display a hint on how to exit.
+
+    """
+    if os.sep == ':':
+        eof = 'Cmd-Q'
+    elif os.sep == '\\':
+        eof = 'Ctrl-Z plus Return'
+    else:
+        eof = 'Ctrl-D (i.e. EOF)'
+
+    class Quitter(object):
+        def __init__(self, name):
+            self.name = name
+        def __repr__(self):
+            return 'Use %s() or %s to exit' % (self.name, eof)
+        def __call__(self, code=None):
+            # Shells like IDLE catch the SystemExit, but listen when their
+            # stdin wrapper is closed.
+            try:
+                sys.stdin.close()
+            except:
+                pass
+            raise SystemExit(code)
+    __builtin__.quit = Quitter('quit')
+    __builtin__.exit = Quitter('exit')
+
+
+class _Printer(object):
+    """interactive prompt objects for printing the license text, a list of
+    contributors and the copyright notice."""
+
+    MAXLINES = 23
+
+    def __init__(self, name, data, files=(), dirs=()):
+        self.__name = name
+        self.__data = data
+        self.__files = files
+        self.__dirs = dirs
+        self.__lines = None
+
+    def __setup(self):
+        if self.__lines:
+            return
+        data = None
+        for dir in self.__dirs:
+            for filename in self.__files:
+                filename = os.path.join(dir, filename)
+                try:
+                    fp = file(filename, "rU")
+                    data = fp.read()
+                    fp.close()
+                    break
+                except IOError:
+                    pass
+            if data:
+                break
+        if not data:
+            data = self.__data
+        self.__lines = data.split('\n')
+        self.__linecnt = len(self.__lines)
+
+    def __repr__(self):
+        self.__setup()
+        if len(self.__lines) <= self.MAXLINES:
+            return "\n".join(self.__lines)
+        else:
+            return "Type %s() to see the full %s text" % ((self.__name,)*2)
+
+    def __call__(self):
+        self.__setup()
+        prompt = 'Hit Return for more, or q (and Return) to quit: '
+        lineno = 0
+        while 1:
+            try:
+                for i in range(lineno, lineno + self.MAXLINES):
+                    print self.__lines[i]
+            except IndexError:
+                break
+            else:
+                lineno += self.MAXLINES
+                key = None
+                while key is None:
+                    key = raw_input(prompt)
+                    if key not in ('', 'q'):
+                        key = None
+                if key == 'q':
+                    break
+
+def setcopyright():
+    """Set 'copyright' and 'credits' in __builtin__"""
+    __builtin__.copyright = _Printer("copyright", sys.copyright)
+    if sys.platform[:4] == 'java':
+        __builtin__.credits = _Printer(
+            "credits",
+            "Jython is maintained by the Jython developers (www.jython.org).")
+    else:
+        __builtin__.credits = _Printer("credits", """\
+    Thanks to CWI, CNRI, BeOpen.com, Zope Corporation and a cast of thousands
+    for supporting Python development.  See www.python.org for more information.""")
+    here = os.path.dirname(os.__file__)
+    __builtin__.license = _Printer(
+        "license", "See http://www.python.org/%.3s/license.html" % sys.version,
+        ["LICENSE.txt", "LICENSE"],
+        [os.path.join(here, os.pardir), here, os.curdir])
+
+
+class _Helper(object):
+    """Define the built-in 'help'.
+    This is a wrapper around pydoc.help (with a twist).
+
+    """
+
+    def __repr__(self):
+        return "Type help() for interactive help, " \
+               "or help(object) for help about object."
+    def __call__(self, *args, **kwds):
+        import pydoc
+        return pydoc.help(*args, **kwds)
+
+def sethelper():
+    __builtin__.help = _Helper()
+
+def aliasmbcs():
+    """On Windows, some default encodings are not provided by Python,
+    while they are always available as "mbcs" in each locale. Make
+    them usable by aliasing to "mbcs" in such a case."""
+    if sys.platform == 'win32':
+        import locale, codecs
+        enc = locale.getdefaultlocale()[1]
+        if enc.startswith('cp'):            # "cp***" ?
+            try:
+                codecs.lookup(enc)
+            except LookupError:
+                import encodings
+                encodings._cache[enc] = encodings._unknown
+                encodings.aliases.aliases[enc] = 'mbcs'
+
+def setencoding():
+    """Set the string encoding used by the Unicode implementation.  The
+    default is 'ascii', but if you're willing to experiment, you can
+    change this."""
+    encoding = "ascii" # Default value set by _PyUnicode_Init()
+    if 0:
+        # Enable to support locale aware default string encodings.
+        import locale
+        loc = locale.getdefaultlocale()
+        if loc[1]:
+            encoding = loc[1]
+    if 0:
+        # Enable to switch off string to Unicode coercion and implicit
+        # Unicode to string conversion.
+        encoding = "undefined"
+    if encoding != "ascii":
+        # On Non-Unicode builds this will raise an AttributeError...
+        sys.setdefaultencoding(encoding) # Needs Python Unicode build !
+
+
+def execsitecustomize():
+    """Run custom site specific code, if available."""
+    try:
+        import sitecustomize
+    except ImportError:
+        pass
+
+
+def execusercustomize():
+    """Run custom user specific code, if available."""
+    try:
+        import usercustomize
+    except ImportError:
+        pass
+
+
+def main():
+    global ENABLE_USER_SITE
+
+    abs__file__()
+    known_paths = removeduppaths()
+    if (os.name == "posix" and sys.path and
+        os.path.basename(sys.path[-1]) == "Modules"):
+        addbuilddir()
+    if ENABLE_USER_SITE is None:
+        ENABLE_USER_SITE = check_enableusersite()
+    known_paths = addusersitepackages(known_paths)
+    known_paths = addsitepackages(known_paths)
+    if sys.platform == 'os2emx':
+        setBEGINLIBPATH()
+    setquit()
+    setcopyright()
+    sethelper()
+    aliasmbcs()
+    setencoding()
+    execsitecustomize()
+    if ENABLE_USER_SITE:
+        execusercustomize()
+    # Remove sys.setdefaultencoding() so that users cannot change the
+    # encoding after initialization.  The test for presence is needed when
+    # this module is run as a script, because this code is executed twice.
+    if hasattr(sys, "setdefaultencoding"):
+        del sys.setdefaultencoding
+
+main()
+
+def _script():
+    help = """\
+    %s [--user-base] [--user-site]
+
+    Without arguments print some useful information
+    With arguments print the value of USER_BASE and/or USER_SITE separated
+    by '%s'.
+
+    Exit codes with --user-base or --user-site:
+      0 - user site directory is enabled
+      1 - user site directory is disabled by user
+      2 - uses site directory is disabled by super user
+          or for security reasons
+     >2 - unknown error
+    """
+    args = sys.argv[1:]
+    if not args:
+        print "sys.path = ["
+        for dir in sys.path:
+            print "    %r," % (dir,)
+        print "]"
+        print "USER_BASE: %r (%s)" % (USER_BASE,
+            "exists" if os.path.isdir(USER_BASE) else "doesn't exist")
+        print "USER_SITE: %r (%s)" % (USER_SITE,
+            "exists" if os.path.isdir(USER_SITE) else "doesn't exist")
+        print "ENABLE_USER_SITE: %r" %  ENABLE_USER_SITE
+        sys.exit(0)
+
+    buffer = []
+    if '--user-base' in args:
+        buffer.append(USER_BASE)
+    if '--user-site' in args:
+        buffer.append(USER_SITE)
+
+    if buffer:
+        print os.pathsep.join(buffer)
+        if ENABLE_USER_SITE:
+            sys.exit(0)
+        elif ENABLE_USER_SITE is False:
+            sys.exit(1)
+        elif ENABLE_USER_SITE is None:
+            sys.exit(2)
+        else:
+            sys.exit(3)
+    else:
+        import textwrap
+        print textwrap.dedent(help % (sys.argv[0], os.pathsep))
+        sys.exit(10)
+
+if __name__ == '__main__':
+    _script()


Property changes on: mongopersist/trunk/parts/coverage-test/site-packages/site.py
___________________________________________________________________
Added: svn:keywords
   + Id


Property changes on: mongopersist/trunk/parts/coverage-test/site-packages/sitecustomize.py
___________________________________________________________________
Added: svn:keywords
   + Id

Added: mongopersist/trunk/parts/python/site.py
===================================================================
--- mongopersist/trunk/parts/python/site.py	                        (rev 0)
+++ mongopersist/trunk/parts/python/site.py	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,624 @@
+"""Append module search paths for third-party packages to sys.path.
+
+****************************************************************
+* This module is automatically imported during initialization. *
+****************************************************************
+
+In earlier versions of Python (up to 1.5a3), scripts or modules that
+needed to use site-specific modules would place ``import site''
+somewhere near the top of their code.  Because of the automatic
+import, this is no longer necessary (but code that does it still
+works).
+
+This will append site-specific paths to the module search path.  On
+Unix (including Mac OSX), it starts with sys.prefix and
+sys.exec_prefix (if different) and appends
+lib/python<version>/site-packages as well as lib/site-python.
+On other platforms (such as Windows), it tries each of the
+prefixes directly, as well as with lib/site-packages appended.  The
+resulting directories, if they exist, are appended to sys.path, and
+also inspected for path configuration files.
+
+A path configuration file is a file whose name has the form
+<package>.pth; its contents are additional directories (one per line)
+to be added to sys.path.  Non-existing directories (or
+non-directories) are never added to sys.path; no directory is added to
+sys.path more than once.  Blank lines and lines beginning with
+'#' are skipped. Lines starting with 'import' are executed.
+
+For example, suppose sys.prefix and sys.exec_prefix are set to
+/usr/local and there is a directory /usr/local/lib/python2.5/site-packages
+with three subdirectories, foo, bar and spam, and two path
+configuration files, foo.pth and bar.pth.  Assume foo.pth contains the
+following:
+
+  # foo package configuration
+  foo
+  bar
+  bletch
+
+and bar.pth contains:
+
+  # bar package configuration
+  bar
+
+Then the following directories are added to sys.path, in this order:
+
+  /usr/local/lib/python2.5/site-packages/bar
+  /usr/local/lib/python2.5/site-packages/foo
+
+Note that bletch is omitted because it doesn't exist; bar precedes foo
+because bar.pth comes alphabetically before foo.pth; and spam is
+omitted because it is not mentioned in either path configuration file.
+
+After these path manipulations, an attempt is made to import a module
+named sitecustomize, which can perform arbitrary additional
+site-specific customizations.  If this import fails with an
+ImportError exception, it is silently ignored.
+
+"""
+
+import sys
+import os
+import __builtin__
+
+# Prefixes for site-packages; add additional prefixes like /usr/local here
+PREFIXES = [sys.prefix, sys.exec_prefix]
+# Enable per user site-packages directory
+# set it to False to disable the feature or True to force the feature
+ENABLE_USER_SITE = False # buildout does not support user sites.
+# for distutils.commands.install
+USER_SITE = None
+USER_BASE = None
+
+
+def makepath(*paths):
+    dir = os.path.abspath(os.path.join(*paths))
+    return dir, os.path.normcase(dir)
+
+
+def abs__file__():
+    """Set all module' __file__ attribute to an absolute path"""
+    for m in sys.modules.values():
+        if hasattr(m, '__loader__'):
+            continue   # don't mess with a PEP 302-supplied __file__
+        try:
+            m.__file__ = os.path.abspath(m.__file__)
+        except AttributeError:
+            continue
+
+
+def removeduppaths():
+    """ Remove duplicate entries from sys.path along with making them
+    absolute"""
+    # This ensures that the initial path provided by the interpreter contains
+    # only absolute pathnames, even if we're running from the build directory.
+    L = []
+    known_paths = set()
+    for dir in sys.path:
+        # Filter out duplicate paths (on case-insensitive file systems also
+        # if they only differ in case); turn relative paths into absolute
+        # paths.
+        dir, dircase = makepath(dir)
+        if not dircase in known_paths:
+            L.append(dir)
+            known_paths.add(dircase)
+    sys.path[:] = L
+    return known_paths
+
+# XXX This should not be part of site.py, since it is needed even when
+# using the -S option for Python.  See http://www.python.org/sf/586680
+def addbuilddir():
+    """Append ./build/lib.<platform> in case we're running in the build dir
+    (especially for Guido :-)"""
+    from distutils.util import get_platform
+    s = "build/lib.%s-%.3s" % (get_platform(), sys.version)
+    if hasattr(sys, 'gettotalrefcount'):
+        s += '-pydebug'
+    s = os.path.join(os.path.dirname(sys.path[-1]), s)
+    sys.path.append(s)
+
+
+def _init_pathinfo():
+    """Return a set containing all existing directory entries from sys.path"""
+    d = set()
+    for dir in sys.path:
+        try:
+            if os.path.isdir(dir):
+                dir, dircase = makepath(dir)
+                d.add(dircase)
+        except TypeError:
+            continue
+    return d
+
+
+def addpackage(sitedir, name, known_paths):
+    """Process a .pth file within the site-packages directory:
+       For each line in the file, either combine it with sitedir to a path
+       and add that to known_paths, or execute it if it starts with 'import '.
+    """
+    if known_paths is None:
+        _init_pathinfo()
+        reset = 1
+    else:
+        reset = 0
+    fullname = os.path.join(sitedir, name)
+    try:
+        f = open(fullname, "rU")
+    except IOError:
+        return
+    with f:
+        for line in f:
+            if line.startswith("#"):
+                continue
+            if line.startswith(("import ", "import\t")):
+                exec line
+                continue
+            line = line.rstrip()
+            dir, dircase = makepath(sitedir, line)
+            if not dircase in known_paths and os.path.exists(dir):
+                sys.path.append(dir)
+                known_paths.add(dircase)
+    if reset:
+        known_paths = None
+    return known_paths
+
+
+def addsitedir(sitedir, known_paths=None):
+    """Add 'sitedir' argument to sys.path if missing and handle .pth files in
+    'sitedir'"""
+    if known_paths is None:
+        known_paths = _init_pathinfo()
+        reset = 1
+    else:
+        reset = 0
+    sitedir, sitedircase = makepath(sitedir)
+    if not sitedircase in known_paths:
+        sys.path.append(sitedir)        # Add path component
+    try:
+        names = os.listdir(sitedir)
+    except os.error:
+        return
+    dotpth = os.extsep + "pth"
+    names = [name for name in names if name.endswith(dotpth)]
+    for name in sorted(names):
+        addpackage(sitedir, name, known_paths)
+    if reset:
+        known_paths = None
+    return known_paths
+
+
+def check_enableusersite():
+    """Check if user site directory is safe for inclusion
+
+    The function tests for the command line flag (including environment var),
+    process uid/gid equal to effective uid/gid.
+
+    None: Disabled for security reasons
+    False: Disabled by user (command line option)
+    True: Safe and enabled
+    """
+    if sys.flags.no_user_site:
+        return False
+
+    if hasattr(os, "getuid") and hasattr(os, "geteuid"):
+        # check process uid == effective uid
+        if os.geteuid() != os.getuid():
+            return None
+    if hasattr(os, "getgid") and hasattr(os, "getegid"):
+        # check process gid == effective gid
+        if os.getegid() != os.getgid():
+            return None
+
+    return True
+
+
+def addusersitepackages(known_paths):
+    """Add a per user site-package to sys.path
+
+    Each user has its own python directory with site-packages in the
+    home directory.
+
+    USER_BASE is the root directory for all Python versions
+
+    USER_SITE is the user specific site-packages directory
+
+    USER_SITE/.. can be used for data.
+    """
+    global USER_BASE, USER_SITE, ENABLE_USER_SITE
+    env_base = os.environ.get("PYTHONUSERBASE", None)
+
+    def joinuser(*args):
+        return os.path.expanduser(os.path.join(*args))
+
+    #if sys.platform in ('os2emx', 'riscos'):
+    #    # Don't know what to put here
+    #    USER_BASE = ''
+    #    USER_SITE = ''
+    if os.name == "nt":
+        base = os.environ.get("APPDATA") or "~"
+        USER_BASE = env_base if env_base else joinuser(base, "Python")
+        USER_SITE = os.path.join(USER_BASE,
+                                 "Python" + sys.version[0] + sys.version[2],
+                                 "site-packages")
+    else:
+        USER_BASE = env_base if env_base else joinuser("~", ".local")
+        USER_SITE = os.path.join(USER_BASE, "lib",
+                                 "python" + sys.version[:3],
+                                 "site-packages")
+
+    if ENABLE_USER_SITE and os.path.isdir(USER_SITE):
+        addsitedir(USER_SITE, known_paths)
+    return known_paths
+
+
+def addsitepackages(known_paths):
+    """Add site packages, as determined by zc.buildout.
+
+    See original_addsitepackages, below, for the original version."""
+    buildout_paths = [
+        '/opt/zope/cipherhealth/packages/mongopersist/src',
+        '/opt/zope/packages/eggs/zope.container-3.12.0-py2.6-linux-x86_64.egg',
+        '/opt/zope/packages/eggs/rwproperty-1.0-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.testing-3.10.2-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.app.testing-3.8.1-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.interface-3.6.3-py2.6-linux-x86_64.egg',
+        '/opt/zope/packages/eggs/zope.dottedname-3.4.6-py2.6.egg',
+        '/opt/zope/packages/eggs/setuptools-0.6c12dev_r88846-py2.6.egg',
+        '/opt/zope/packages/eggs/pymongo-2.0.1-py2.6-linux-x86_64.egg',
+        '/opt/zope/packages/eggs/lru-0.1-py2.6.egg',
+        '/opt/zope/packages/eggs/ZODB3-3.10.3-py2.6-linux-x86_64.egg',
+        '/opt/zope/packages/eggs/zope.broken-3.6.0-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.publisher-3.12.6-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.traversing-3.14.0-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.size-3.4.1-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.filerepresentation-3.6.0-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.i18nmessageid-3.5.3-py2.6-linux-x86_64.egg',
+        '/opt/zope/packages/eggs/zope.lifecycleevent-3.6.2-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.security-3.8.2-py2.6-linux-x86_64.egg',
+        '/opt/zope/packages/eggs/zope.location-3.9.0-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.event-3.5.0_1-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.component-3.10.0-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.schema-3.8.0-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.exceptions-3.6.1-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.site-3.9.2-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.password-3.6.1-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.i18n-3.7.4-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.app.publication-3.12.0-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.app.dependable-3.5.1-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.app.debug-3.4.1-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.processlifetime-1.0-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.app.appsetup-3.15.0-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.annotation-3.5.0-py2.6.egg',
+        '/opt/zope/packages/eggs/zdaemon-2.0.4-py2.6.egg',
+        '/opt/zope/packages/eggs/ZConfig-2.8.0-py2.6.egg',
+        '/opt/zope/packages/eggs/zc.lockfile-1.0.0-py2.6.egg',
+        '/opt/zope/packages/eggs/transaction-1.1.1-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.proxy-3.6.1-py2.6-linux-x86_64.egg',
+        '/opt/zope/packages/eggs/zope.contenttype-3.5.3-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.configuration-3.7.4-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.browser-1.3-py2.6.egg',
+        '/opt/zope/packages/eggs/pytz-2011g-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.error-3.7.2-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.authentication-3.7.1-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.session-3.9.4-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.minmax-1.1.2-py2.6.egg'
+        ]
+    for path in buildout_paths:
+        sitedir, sitedircase = makepath(path)
+        if not sitedircase in known_paths and os.path.exists(sitedir):
+            sys.path.append(sitedir)
+            known_paths.add(sitedircase)
+    return known_paths
+
+def original_addsitepackages(known_paths):
+    """Add site-packages (and possibly site-python) to sys.path"""
+    sitedirs = []
+    seen = []
+
+    for prefix in PREFIXES:
+        if not prefix or prefix in seen:
+            continue
+        seen.append(prefix)
+
+        if sys.platform in ('os2emx', 'riscos'):
+            sitedirs.append(os.path.join(prefix, "Lib", "site-packages"))
+        elif os.sep == '/':
+            sitedirs.append(os.path.join(prefix, "lib",
+                                        "python" + sys.version[:3],
+                                        "site-packages"))
+            sitedirs.append(os.path.join(prefix, "lib", "site-python"))
+        else:
+            sitedirs.append(prefix)
+            sitedirs.append(os.path.join(prefix, "lib", "site-packages"))
+
+        if sys.platform == "darwin":
+            # for framework builds *only* we add the standard Apple
+            # locations. Currently only per-user, but /Library and
+            # /Network/Library could be added too
+            if 'Python.framework' in prefix:
+                sitedirs.append(
+                    os.path.expanduser(
+                        os.path.join("~", "Library", "Python",
+                                     sys.version[:3], "site-packages")))
+
+    for sitedir in sitedirs:
+        if os.path.isdir(sitedir):
+            addsitedir(sitedir, known_paths)
+
+    return known_paths
+
+
+def setBEGINLIBPATH():
+    """The OS/2 EMX port has optional extension modules that do double duty
+    as DLLs (and must use the .DLL file extension) for other extensions.
+    The library search path needs to be amended so these will be found
+    during module import.  Use BEGINLIBPATH so that these are at the start
+    of the library search path.
+
+    """
+    dllpath = os.path.join(sys.prefix, "Lib", "lib-dynload")
+    libpath = os.environ['BEGINLIBPATH'].split(';')
+    if libpath[-1]:
+        libpath.append(dllpath)
+    else:
+        libpath[-1] = dllpath
+    os.environ['BEGINLIBPATH'] = ';'.join(libpath)
+
+
+def setquit():
+    """Define new built-ins 'quit' and 'exit'.
+    These are simply strings that display a hint on how to exit.
+
+    """
+    if os.sep == ':':
+        eof = 'Cmd-Q'
+    elif os.sep == '\\':
+        eof = 'Ctrl-Z plus Return'
+    else:
+        eof = 'Ctrl-D (i.e. EOF)'
+
+    class Quitter(object):
+        def __init__(self, name):
+            self.name = name
+        def __repr__(self):
+            return 'Use %s() or %s to exit' % (self.name, eof)
+        def __call__(self, code=None):
+            # Shells like IDLE catch the SystemExit, but listen when their
+            # stdin wrapper is closed.
+            try:
+                sys.stdin.close()
+            except:
+                pass
+            raise SystemExit(code)
+    __builtin__.quit = Quitter('quit')
+    __builtin__.exit = Quitter('exit')
+
+
+class _Printer(object):
+    """interactive prompt objects for printing the license text, a list of
+    contributors and the copyright notice."""
+
+    MAXLINES = 23
+
+    def __init__(self, name, data, files=(), dirs=()):
+        self.__name = name
+        self.__data = data
+        self.__files = files
+        self.__dirs = dirs
+        self.__lines = None
+
+    def __setup(self):
+        if self.__lines:
+            return
+        data = None
+        for dir in self.__dirs:
+            for filename in self.__files:
+                filename = os.path.join(dir, filename)
+                try:
+                    fp = file(filename, "rU")
+                    data = fp.read()
+                    fp.close()
+                    break
+                except IOError:
+                    pass
+            if data:
+                break
+        if not data:
+            data = self.__data
+        self.__lines = data.split('\n')
+        self.__linecnt = len(self.__lines)
+
+    def __repr__(self):
+        self.__setup()
+        if len(self.__lines) <= self.MAXLINES:
+            return "\n".join(self.__lines)
+        else:
+            return "Type %s() to see the full %s text" % ((self.__name,)*2)
+
+    def __call__(self):
+        self.__setup()
+        prompt = 'Hit Return for more, or q (and Return) to quit: '
+        lineno = 0
+        while 1:
+            try:
+                for i in range(lineno, lineno + self.MAXLINES):
+                    print self.__lines[i]
+            except IndexError:
+                break
+            else:
+                lineno += self.MAXLINES
+                key = None
+                while key is None:
+                    key = raw_input(prompt)
+                    if key not in ('', 'q'):
+                        key = None
+                if key == 'q':
+                    break
+
+def setcopyright():
+    """Set 'copyright' and 'credits' in __builtin__"""
+    __builtin__.copyright = _Printer("copyright", sys.copyright)
+    if sys.platform[:4] == 'java':
+        __builtin__.credits = _Printer(
+            "credits",
+            "Jython is maintained by the Jython developers (www.jython.org).")
+    else:
+        __builtin__.credits = _Printer("credits", """\
+    Thanks to CWI, CNRI, BeOpen.com, Zope Corporation and a cast of thousands
+    for supporting Python development.  See www.python.org for more information.""")
+    here = os.path.dirname(os.__file__)
+    __builtin__.license = _Printer(
+        "license", "See http://www.python.org/%.3s/license.html" % sys.version,
+        ["LICENSE.txt", "LICENSE"],
+        [os.path.join(here, os.pardir), here, os.curdir])
+
+
+class _Helper(object):
+    """Define the built-in 'help'.
+    This is a wrapper around pydoc.help (with a twist).
+
+    """
+
+    def __repr__(self):
+        return "Type help() for interactive help, " \
+               "or help(object) for help about object."
+    def __call__(self, *args, **kwds):
+        import pydoc
+        return pydoc.help(*args, **kwds)
+
+def sethelper():
+    __builtin__.help = _Helper()
+
+def aliasmbcs():
+    """On Windows, some default encodings are not provided by Python,
+    while they are always available as "mbcs" in each locale. Make
+    them usable by aliasing to "mbcs" in such a case."""
+    if sys.platform == 'win32':
+        import locale, codecs
+        enc = locale.getdefaultlocale()[1]
+        if enc.startswith('cp'):            # "cp***" ?
+            try:
+                codecs.lookup(enc)
+            except LookupError:
+                import encodings
+                encodings._cache[enc] = encodings._unknown
+                encodings.aliases.aliases[enc] = 'mbcs'
+
+def setencoding():
+    """Set the string encoding used by the Unicode implementation.  The
+    default is 'ascii', but if you're willing to experiment, you can
+    change this."""
+    encoding = "ascii" # Default value set by _PyUnicode_Init()
+    if 0:
+        # Enable to support locale aware default string encodings.
+        import locale
+        loc = locale.getdefaultlocale()
+        if loc[1]:
+            encoding = loc[1]
+    if 0:
+        # Enable to switch off string to Unicode coercion and implicit
+        # Unicode to string conversion.
+        encoding = "undefined"
+    if encoding != "ascii":
+        # On Non-Unicode builds this will raise an AttributeError...
+        sys.setdefaultencoding(encoding) # Needs Python Unicode build !
+
+
+def execsitecustomize():
+    """Run custom site specific code, if available."""
+    try:
+        import sitecustomize
+    except ImportError:
+        pass
+
+
+def execusercustomize():
+    """Run custom user specific code, if available."""
+    try:
+        import usercustomize
+    except ImportError:
+        pass
+
+
+def main():
+    global ENABLE_USER_SITE
+
+    abs__file__()
+    known_paths = removeduppaths()
+    if (os.name == "posix" and sys.path and
+        os.path.basename(sys.path[-1]) == "Modules"):
+        addbuilddir()
+    if ENABLE_USER_SITE is None:
+        ENABLE_USER_SITE = check_enableusersite()
+    known_paths = addusersitepackages(known_paths)
+    known_paths = addsitepackages(known_paths)
+    if sys.platform == 'os2emx':
+        setBEGINLIBPATH()
+    setquit()
+    setcopyright()
+    sethelper()
+    aliasmbcs()
+    setencoding()
+    execsitecustomize()
+    if ENABLE_USER_SITE:
+        execusercustomize()
+    # Remove sys.setdefaultencoding() so that users cannot change the
+    # encoding after initialization.  The test for presence is needed when
+    # this module is run as a script, because this code is executed twice.
+    if hasattr(sys, "setdefaultencoding"):
+        del sys.setdefaultencoding
+
+main()
+
+def _script():
+    help = """\
+    %s [--user-base] [--user-site]
+
+    Without arguments print some useful information
+    With arguments print the value of USER_BASE and/or USER_SITE separated
+    by '%s'.
+
+    Exit codes with --user-base or --user-site:
+      0 - user site directory is enabled
+      1 - user site directory is disabled by user
+      2 - uses site directory is disabled by super user
+          or for security reasons
+     >2 - unknown error
+    """
+    args = sys.argv[1:]
+    if not args:
+        print "sys.path = ["
+        for dir in sys.path:
+            print "    %r," % (dir,)
+        print "]"
+        print "USER_BASE: %r (%s)" % (USER_BASE,
+            "exists" if os.path.isdir(USER_BASE) else "doesn't exist")
+        print "USER_SITE: %r (%s)" % (USER_SITE,
+            "exists" if os.path.isdir(USER_SITE) else "doesn't exist")
+        print "ENABLE_USER_SITE: %r" %  ENABLE_USER_SITE
+        sys.exit(0)
+
+    buffer = []
+    if '--user-base' in args:
+        buffer.append(USER_BASE)
+    if '--user-site' in args:
+        buffer.append(USER_SITE)
+
+    if buffer:
+        print os.pathsep.join(buffer)
+        if ENABLE_USER_SITE:
+            sys.exit(0)
+        elif ENABLE_USER_SITE is False:
+            sys.exit(1)
+        elif ENABLE_USER_SITE is None:
+            sys.exit(2)
+        else:
+            sys.exit(3)
+    else:
+        import textwrap
+        print textwrap.dedent(help % (sys.argv[0], os.pathsep))
+        sys.exit(10)
+
+if __name__ == '__main__':
+    _script()


Property changes on: mongopersist/trunk/parts/python/site.py
___________________________________________________________________
Added: svn:keywords
   + Id


Property changes on: mongopersist/trunk/parts/python/sitecustomize.py
___________________________________________________________________
Added: svn:keywords
   + Id

Added: mongopersist/trunk/parts/test/site-packages/site.py
===================================================================
--- mongopersist/trunk/parts/test/site-packages/site.py	                        (rev 0)
+++ mongopersist/trunk/parts/test/site-packages/site.py	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,625 @@
+"""Append module search paths for third-party packages to sys.path.
+
+****************************************************************
+* This module is automatically imported during initialization. *
+****************************************************************
+
+In earlier versions of Python (up to 1.5a3), scripts or modules that
+needed to use site-specific modules would place ``import site''
+somewhere near the top of their code.  Because of the automatic
+import, this is no longer necessary (but code that does it still
+works).
+
+This will append site-specific paths to the module search path.  On
+Unix (including Mac OSX), it starts with sys.prefix and
+sys.exec_prefix (if different) and appends
+lib/python<version>/site-packages as well as lib/site-python.
+On other platforms (such as Windows), it tries each of the
+prefixes directly, as well as with lib/site-packages appended.  The
+resulting directories, if they exist, are appended to sys.path, and
+also inspected for path configuration files.
+
+A path configuration file is a file whose name has the form
+<package>.pth; its contents are additional directories (one per line)
+to be added to sys.path.  Non-existing directories (or
+non-directories) are never added to sys.path; no directory is added to
+sys.path more than once.  Blank lines and lines beginning with
+'#' are skipped. Lines starting with 'import' are executed.
+
+For example, suppose sys.prefix and sys.exec_prefix are set to
+/usr/local and there is a directory /usr/local/lib/python2.5/site-packages
+with three subdirectories, foo, bar and spam, and two path
+configuration files, foo.pth and bar.pth.  Assume foo.pth contains the
+following:
+
+  # foo package configuration
+  foo
+  bar
+  bletch
+
+and bar.pth contains:
+
+  # bar package configuration
+  bar
+
+Then the following directories are added to sys.path, in this order:
+
+  /usr/local/lib/python2.5/site-packages/bar
+  /usr/local/lib/python2.5/site-packages/foo
+
+Note that bletch is omitted because it doesn't exist; bar precedes foo
+because bar.pth comes alphabetically before foo.pth; and spam is
+omitted because it is not mentioned in either path configuration file.
+
+After these path manipulations, an attempt is made to import a module
+named sitecustomize, which can perform arbitrary additional
+site-specific customizations.  If this import fails with an
+ImportError exception, it is silently ignored.
+
+"""
+
+import sys
+import os
+import __builtin__
+
+# Prefixes for site-packages; add additional prefixes like /usr/local here
+PREFIXES = [sys.prefix, sys.exec_prefix]
+# Enable per user site-packages directory
+# set it to False to disable the feature or True to force the feature
+ENABLE_USER_SITE = False # buildout does not support user sites.
+# for distutils.commands.install
+USER_SITE = None
+USER_BASE = None
+
+
+def makepath(*paths):
+    dir = os.path.abspath(os.path.join(*paths))
+    return dir, os.path.normcase(dir)
+
+
+def abs__file__():
+    """Set all module' __file__ attribute to an absolute path"""
+    for m in sys.modules.values():
+        if hasattr(m, '__loader__'):
+            continue   # don't mess with a PEP 302-supplied __file__
+        try:
+            m.__file__ = os.path.abspath(m.__file__)
+        except AttributeError:
+            continue
+
+
+def removeduppaths():
+    """ Remove duplicate entries from sys.path along with making them
+    absolute"""
+    # This ensures that the initial path provided by the interpreter contains
+    # only absolute pathnames, even if we're running from the build directory.
+    L = []
+    known_paths = set()
+    for dir in sys.path:
+        # Filter out duplicate paths (on case-insensitive file systems also
+        # if they only differ in case); turn relative paths into absolute
+        # paths.
+        dir, dircase = makepath(dir)
+        if not dircase in known_paths:
+            L.append(dir)
+            known_paths.add(dircase)
+    sys.path[:] = L
+    return known_paths
+
+# XXX This should not be part of site.py, since it is needed even when
+# using the -S option for Python.  See http://www.python.org/sf/586680
+def addbuilddir():
+    """Append ./build/lib.<platform> in case we're running in the build dir
+    (especially for Guido :-)"""
+    from distutils.util import get_platform
+    s = "build/lib.%s-%.3s" % (get_platform(), sys.version)
+    if hasattr(sys, 'gettotalrefcount'):
+        s += '-pydebug'
+    s = os.path.join(os.path.dirname(sys.path[-1]), s)
+    sys.path.append(s)
+
+
+def _init_pathinfo():
+    """Return a set containing all existing directory entries from sys.path"""
+    d = set()
+    for dir in sys.path:
+        try:
+            if os.path.isdir(dir):
+                dir, dircase = makepath(dir)
+                d.add(dircase)
+        except TypeError:
+            continue
+    return d
+
+
+def addpackage(sitedir, name, known_paths):
+    """Process a .pth file within the site-packages directory:
+       For each line in the file, either combine it with sitedir to a path
+       and add that to known_paths, or execute it if it starts with 'import '.
+    """
+    if known_paths is None:
+        _init_pathinfo()
+        reset = 1
+    else:
+        reset = 0
+    fullname = os.path.join(sitedir, name)
+    try:
+        f = open(fullname, "rU")
+    except IOError:
+        return
+    with f:
+        for line in f:
+            if line.startswith("#"):
+                continue
+            if line.startswith(("import ", "import\t")):
+                exec line
+                continue
+            line = line.rstrip()
+            dir, dircase = makepath(sitedir, line)
+            if not dircase in known_paths and os.path.exists(dir):
+                sys.path.append(dir)
+                known_paths.add(dircase)
+    if reset:
+        known_paths = None
+    return known_paths
+
+
+def addsitedir(sitedir, known_paths=None):
+    """Add 'sitedir' argument to sys.path if missing and handle .pth files in
+    'sitedir'"""
+    if known_paths is None:
+        known_paths = _init_pathinfo()
+        reset = 1
+    else:
+        reset = 0
+    sitedir, sitedircase = makepath(sitedir)
+    if not sitedircase in known_paths:
+        sys.path.append(sitedir)        # Add path component
+    try:
+        names = os.listdir(sitedir)
+    except os.error:
+        return
+    dotpth = os.extsep + "pth"
+    names = [name for name in names if name.endswith(dotpth)]
+    for name in sorted(names):
+        addpackage(sitedir, name, known_paths)
+    if reset:
+        known_paths = None
+    return known_paths
+
+
+def check_enableusersite():
+    """Check if user site directory is safe for inclusion
+
+    The function tests for the command line flag (including environment var),
+    process uid/gid equal to effective uid/gid.
+
+    None: Disabled for security reasons
+    False: Disabled by user (command line option)
+    True: Safe and enabled
+    """
+    if sys.flags.no_user_site:
+        return False
+
+    if hasattr(os, "getuid") and hasattr(os, "geteuid"):
+        # check process uid == effective uid
+        if os.geteuid() != os.getuid():
+            return None
+    if hasattr(os, "getgid") and hasattr(os, "getegid"):
+        # check process gid == effective gid
+        if os.getegid() != os.getgid():
+            return None
+
+    return True
+
+
+def addusersitepackages(known_paths):
+    """Add a per user site-package to sys.path
+
+    Each user has its own python directory with site-packages in the
+    home directory.
+
+    USER_BASE is the root directory for all Python versions
+
+    USER_SITE is the user specific site-packages directory
+
+    USER_SITE/.. can be used for data.
+    """
+    global USER_BASE, USER_SITE, ENABLE_USER_SITE
+    env_base = os.environ.get("PYTHONUSERBASE", None)
+
+    def joinuser(*args):
+        return os.path.expanduser(os.path.join(*args))
+
+    #if sys.platform in ('os2emx', 'riscos'):
+    #    # Don't know what to put here
+    #    USER_BASE = ''
+    #    USER_SITE = ''
+    if os.name == "nt":
+        base = os.environ.get("APPDATA") or "~"
+        USER_BASE = env_base if env_base else joinuser(base, "Python")
+        USER_SITE = os.path.join(USER_BASE,
+                                 "Python" + sys.version[0] + sys.version[2],
+                                 "site-packages")
+    else:
+        USER_BASE = env_base if env_base else joinuser("~", ".local")
+        USER_SITE = os.path.join(USER_BASE, "lib",
+                                 "python" + sys.version[:3],
+                                 "site-packages")
+
+    if ENABLE_USER_SITE and os.path.isdir(USER_SITE):
+        addsitedir(USER_SITE, known_paths)
+    return known_paths
+
+
+def addsitepackages(known_paths):
+    """Add site packages, as determined by zc.buildout.
+
+    See original_addsitepackages, below, for the original version."""
+    buildout_paths = [
+        '/opt/zope/cipherhealth/packages/mongopersist/src',
+        '/opt/zope/packages/eggs/zope.testrunner-4.0.3-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.interface-3.6.3-py2.6-linux-x86_64.egg',
+        '/opt/zope/packages/eggs/zope.exceptions-3.6.1-py2.6.egg',
+        '/opt/zope/packages/eggs/setuptools-0.6c12dev_r88846-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.container-3.12.0-py2.6-linux-x86_64.egg',
+        '/opt/zope/packages/eggs/rwproperty-1.0-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.testing-3.10.2-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.app.testing-3.8.1-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.dottedname-3.4.6-py2.6.egg',
+        '/opt/zope/packages/eggs/pymongo-2.0.1-py2.6-linux-x86_64.egg',
+        '/opt/zope/packages/eggs/lru-0.1-py2.6.egg',
+        '/opt/zope/packages/eggs/ZODB3-3.10.3-py2.6-linux-x86_64.egg',
+        '/opt/zope/packages/eggs/zope.broken-3.6.0-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.publisher-3.12.6-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.traversing-3.14.0-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.size-3.4.1-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.filerepresentation-3.6.0-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.i18nmessageid-3.5.3-py2.6-linux-x86_64.egg',
+        '/opt/zope/packages/eggs/zope.lifecycleevent-3.6.2-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.security-3.8.2-py2.6-linux-x86_64.egg',
+        '/opt/zope/packages/eggs/zope.location-3.9.0-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.event-3.5.0_1-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.component-3.10.0-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.schema-3.8.0-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.site-3.9.2-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.password-3.6.1-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.i18n-3.7.4-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.app.publication-3.12.0-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.app.dependable-3.5.1-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.app.debug-3.4.1-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.processlifetime-1.0-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.app.appsetup-3.15.0-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.annotation-3.5.0-py2.6.egg',
+        '/opt/zope/packages/eggs/zdaemon-2.0.4-py2.6.egg',
+        '/opt/zope/packages/eggs/ZConfig-2.8.0-py2.6.egg',
+        '/opt/zope/packages/eggs/zc.lockfile-1.0.0-py2.6.egg',
+        '/opt/zope/packages/eggs/transaction-1.1.1-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.proxy-3.6.1-py2.6-linux-x86_64.egg',
+        '/opt/zope/packages/eggs/zope.contenttype-3.5.3-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.configuration-3.7.4-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.browser-1.3-py2.6.egg',
+        '/opt/zope/packages/eggs/pytz-2011g-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.error-3.7.2-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.authentication-3.7.1-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.session-3.9.4-py2.6.egg',
+        '/opt/zope/packages/eggs/zope.minmax-1.1.2-py2.6.egg'
+        ]
+    for path in buildout_paths:
+        sitedir, sitedircase = makepath(path)
+        if not sitedircase in known_paths and os.path.exists(sitedir):
+            sys.path.append(sitedir)
+            known_paths.add(sitedircase)
+    return known_paths
+
+def original_addsitepackages(known_paths):
+    """Add site-packages (and possibly site-python) to sys.path"""
+    sitedirs = []
+    seen = []
+
+    for prefix in PREFIXES:
+        if not prefix or prefix in seen:
+            continue
+        seen.append(prefix)
+
+        if sys.platform in ('os2emx', 'riscos'):
+            sitedirs.append(os.path.join(prefix, "Lib", "site-packages"))
+        elif os.sep == '/':
+            sitedirs.append(os.path.join(prefix, "lib",
+                                        "python" + sys.version[:3],
+                                        "site-packages"))
+            sitedirs.append(os.path.join(prefix, "lib", "site-python"))
+        else:
+            sitedirs.append(prefix)
+            sitedirs.append(os.path.join(prefix, "lib", "site-packages"))
+
+        if sys.platform == "darwin":
+            # for framework builds *only* we add the standard Apple
+            # locations. Currently only per-user, but /Library and
+            # /Network/Library could be added too
+            if 'Python.framework' in prefix:
+                sitedirs.append(
+                    os.path.expanduser(
+                        os.path.join("~", "Library", "Python",
+                                     sys.version[:3], "site-packages")))
+
+    for sitedir in sitedirs:
+        if os.path.isdir(sitedir):
+            addsitedir(sitedir, known_paths)
+
+    return known_paths
+
+
+def setBEGINLIBPATH():
+    """The OS/2 EMX port has optional extension modules that do double duty
+    as DLLs (and must use the .DLL file extension) for other extensions.
+    The library search path needs to be amended so these will be found
+    during module import.  Use BEGINLIBPATH so that these are at the start
+    of the library search path.
+
+    """
+    dllpath = os.path.join(sys.prefix, "Lib", "lib-dynload")
+    libpath = os.environ['BEGINLIBPATH'].split(';')
+    if libpath[-1]:
+        libpath.append(dllpath)
+    else:
+        libpath[-1] = dllpath
+    os.environ['BEGINLIBPATH'] = ';'.join(libpath)
+
+
+def setquit():
+    """Define new built-ins 'quit' and 'exit'.
+    These are simply strings that display a hint on how to exit.
+
+    """
+    if os.sep == ':':
+        eof = 'Cmd-Q'
+    elif os.sep == '\\':
+        eof = 'Ctrl-Z plus Return'
+    else:
+        eof = 'Ctrl-D (i.e. EOF)'
+
+    class Quitter(object):
+        def __init__(self, name):
+            self.name = name
+        def __repr__(self):
+            return 'Use %s() or %s to exit' % (self.name, eof)
+        def __call__(self, code=None):
+            # Shells like IDLE catch the SystemExit, but listen when their
+            # stdin wrapper is closed.
+            try:
+                sys.stdin.close()
+            except:
+                pass
+            raise SystemExit(code)
+    __builtin__.quit = Quitter('quit')
+    __builtin__.exit = Quitter('exit')
+
+
+class _Printer(object):
+    """interactive prompt objects for printing the license text, a list of
+    contributors and the copyright notice."""
+
+    MAXLINES = 23
+
+    def __init__(self, name, data, files=(), dirs=()):
+        self.__name = name
+        self.__data = data
+        self.__files = files
+        self.__dirs = dirs
+        self.__lines = None
+
+    def __setup(self):
+        if self.__lines:
+            return
+        data = None
+        for dir in self.__dirs:
+            for filename in self.__files:
+                filename = os.path.join(dir, filename)
+                try:
+                    fp = file(filename, "rU")
+                    data = fp.read()
+                    fp.close()
+                    break
+                except IOError:
+                    pass
+            if data:
+                break
+        if not data:
+            data = self.__data
+        self.__lines = data.split('\n')
+        self.__linecnt = len(self.__lines)
+
+    def __repr__(self):
+        self.__setup()
+        if len(self.__lines) <= self.MAXLINES:
+            return "\n".join(self.__lines)
+        else:
+            return "Type %s() to see the full %s text" % ((self.__name,)*2)
+
+    def __call__(self):
+        self.__setup()
+        prompt = 'Hit Return for more, or q (and Return) to quit: '
+        lineno = 0
+        while 1:
+            try:
+                for i in range(lineno, lineno + self.MAXLINES):
+                    print self.__lines[i]
+            except IndexError:
+                break
+            else:
+                lineno += self.MAXLINES
+                key = None
+                while key is None:
+                    key = raw_input(prompt)
+                    if key not in ('', 'q'):
+                        key = None
+                if key == 'q':
+                    break
+
+def setcopyright():
+    """Set 'copyright' and 'credits' in __builtin__"""
+    __builtin__.copyright = _Printer("copyright", sys.copyright)
+    if sys.platform[:4] == 'java':
+        __builtin__.credits = _Printer(
+            "credits",
+            "Jython is maintained by the Jython developers (www.jython.org).")
+    else:
+        __builtin__.credits = _Printer("credits", """\
+    Thanks to CWI, CNRI, BeOpen.com, Zope Corporation and a cast of thousands
+    for supporting Python development.  See www.python.org for more information.""")
+    here = os.path.dirname(os.__file__)
+    __builtin__.license = _Printer(
+        "license", "See http://www.python.org/%.3s/license.html" % sys.version,
+        ["LICENSE.txt", "LICENSE"],
+        [os.path.join(here, os.pardir), here, os.curdir])
+
+
+class _Helper(object):
+    """Define the built-in 'help'.
+    This is a wrapper around pydoc.help (with a twist).
+
+    """
+
+    def __repr__(self):
+        return "Type help() for interactive help, " \
+               "or help(object) for help about object."
+    def __call__(self, *args, **kwds):
+        import pydoc
+        return pydoc.help(*args, **kwds)
+
+def sethelper():
+    __builtin__.help = _Helper()
+
+def aliasmbcs():
+    """On Windows, some default encodings are not provided by Python,
+    while they are always available as "mbcs" in each locale. Make
+    them usable by aliasing to "mbcs" in such a case."""
+    if sys.platform == 'win32':
+        import locale, codecs
+        enc = locale.getdefaultlocale()[1]
+        if enc.startswith('cp'):            # "cp***" ?
+            try:
+                codecs.lookup(enc)
+            except LookupError:
+                import encodings
+                encodings._cache[enc] = encodings._unknown
+                encodings.aliases.aliases[enc] = 'mbcs'
+
+def setencoding():
+    """Set the string encoding used by the Unicode implementation.  The
+    default is 'ascii', but if you're willing to experiment, you can
+    change this."""
+    encoding = "ascii" # Default value set by _PyUnicode_Init()
+    if 0:
+        # Enable to support locale aware default string encodings.
+        import locale
+        loc = locale.getdefaultlocale()
+        if loc[1]:
+            encoding = loc[1]
+    if 0:
+        # Enable to switch off string to Unicode coercion and implicit
+        # Unicode to string conversion.
+        encoding = "undefined"
+    if encoding != "ascii":
+        # On Non-Unicode builds this will raise an AttributeError...
+        sys.setdefaultencoding(encoding) # Needs Python Unicode build !
+
+
+def execsitecustomize():
+    """Run custom site specific code, if available."""
+    try:
+        import sitecustomize
+    except ImportError:
+        pass
+
+
+def execusercustomize():
+    """Run custom user specific code, if available."""
+    try:
+        import usercustomize
+    except ImportError:
+        pass
+
+
+def main():
+    global ENABLE_USER_SITE
+
+    abs__file__()
+    known_paths = removeduppaths()
+    if (os.name == "posix" and sys.path and
+        os.path.basename(sys.path[-1]) == "Modules"):
+        addbuilddir()
+    if ENABLE_USER_SITE is None:
+        ENABLE_USER_SITE = check_enableusersite()
+    known_paths = addusersitepackages(known_paths)
+    known_paths = addsitepackages(known_paths)
+    if sys.platform == 'os2emx':
+        setBEGINLIBPATH()
+    setquit()
+    setcopyright()
+    sethelper()
+    aliasmbcs()
+    setencoding()
+    execsitecustomize()
+    if ENABLE_USER_SITE:
+        execusercustomize()
+    # Remove sys.setdefaultencoding() so that users cannot change the
+    # encoding after initialization.  The test for presence is needed when
+    # this module is run as a script, because this code is executed twice.
+    if hasattr(sys, "setdefaultencoding"):
+        del sys.setdefaultencoding
+
+main()
+
+def _script():
+    help = """\
+    %s [--user-base] [--user-site]
+
+    Without arguments print some useful information
+    With arguments print the value of USER_BASE and/or USER_SITE separated
+    by '%s'.
+
+    Exit codes with --user-base or --user-site:
+      0 - user site directory is enabled
+      1 - user site directory is disabled by user
+      2 - uses site directory is disabled by super user
+          or for security reasons
+     >2 - unknown error
+    """
+    args = sys.argv[1:]
+    if not args:
+        print "sys.path = ["
+        for dir in sys.path:
+            print "    %r," % (dir,)
+        print "]"
+        print "USER_BASE: %r (%s)" % (USER_BASE,
+            "exists" if os.path.isdir(USER_BASE) else "doesn't exist")
+        print "USER_SITE: %r (%s)" % (USER_SITE,
+            "exists" if os.path.isdir(USER_SITE) else "doesn't exist")
+        print "ENABLE_USER_SITE: %r" %  ENABLE_USER_SITE
+        sys.exit(0)
+
+    buffer = []
+    if '--user-base' in args:
+        buffer.append(USER_BASE)
+    if '--user-site' in args:
+        buffer.append(USER_SITE)
+
+    if buffer:
+        print os.pathsep.join(buffer)
+        if ENABLE_USER_SITE:
+            sys.exit(0)
+        elif ENABLE_USER_SITE is False:
+            sys.exit(1)
+        elif ENABLE_USER_SITE is None:
+            sys.exit(2)
+        else:
+            sys.exit(3)
+    else:
+        import textwrap
+        print textwrap.dedent(help % (sys.argv[0], os.pathsep))
+        sys.exit(10)
+
+if __name__ == '__main__':
+    _script()


Property changes on: mongopersist/trunk/parts/test/site-packages/site.py
___________________________________________________________________
Added: svn:keywords
   + Id


Property changes on: mongopersist/trunk/parts/test/site-packages/sitecustomize.py
___________________________________________________________________
Added: svn:keywords
   + Id

Added: mongopersist/trunk/setup.py
===================================================================
--- mongopersist/trunk/setup.py	                        (rev 0)
+++ mongopersist/trunk/setup.py	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,45 @@
+"""Setup
+"""
+import os
+from setuptools import setup, find_packages
+
+setup (
+    name='mongopersist',
+    version='0.5.0dev',
+    author = "Stephan Richter",
+    author_email = "stephan.richter at gmail.com",
+    description = "Mongo Persistence Backend",
+    license = "ZPL 2.1",
+    keywords = "mongo persistent ",
+    classifiers = [
+        'Development Status :: 4 - Beta',
+        'Intended Audience :: Developers',
+        'Programming Language :: Python',
+        'Programming Language :: Python :: 2',
+        'Framework :: ZODB',
+        'License :: OSI Approved :: Zope Public License',
+        'Natural Language :: English',
+        'Operating System :: OS Independent'],
+    packages = find_packages('src'),
+    package_dir = {'':'src'},
+    extras_require = dict(
+        test = (
+            'zope.app.testing',
+            'zope.testing',
+            ),
+        zope = (
+            'rwproperty',
+            'zope.container',
+            ),
+        ),
+    install_requires = [
+        'ZODB3',
+        'lru',
+        'pymongo',
+        'setuptools',
+        'zope.dottedname',
+        'zope.interface',
+    ],
+    include_package_data = True,
+    zip_safe = False,
+    )


Property changes on: mongopersist/trunk/setup.py
___________________________________________________________________
Added: svn:keywords
   + Id

Added: mongopersist/trunk/src/mongopersist/README.txt
===================================================================
--- mongopersist/trunk/src/mongopersist/README.txt	                        (rev 0)
+++ mongopersist/trunk/src/mongopersist/README.txt	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,596 @@
+======================
+Mongo Data Persistence
+======================
+
+This document outlines the general capabilities of the ``mongopersist``
+package. ``mongopersist`` is a Mongo storage implementation for persistent
+Python objects. It is *not* a storage for the ZODB.
+
+The goal of ``mongopersist`` is to provide a data manager that serializes
+objects to Mongo at transaction boundaries. The mongo data manager is a
+persistent data manager, which handles events at transaction boundaries (see
+``transaction.interfaces.IDataManager``) as well as events from the
+persistency framework (see ``persistent.interfaces.IPersistentDataManager``).
+
+An instance of a data manager is supposed to have the same life time as the
+transaction, meaning that it is assumed that you create a new data manager
+when creating a new transaction:
+
+  >>> import transaction
+
+Note: The ``conn`` object is a ``pymongo.connection.Connection`` instance. In
+this case our tests use the ``mongopersist_test`` database.
+
+Let's now define a simple persistent object:
+
+  >>> import datetime
+  >>> import persistent
+
+  >>> class Person(persistent.Persistent):
+  ...
+  ...     def __init__(self, name, phone=None, address=None, friends=None,
+  ...                  visited=(), birthday=None):
+  ...         self.name = name
+  ...         self.address = address
+  ...         self.friends = friends or {}
+  ...         self.visited = visited
+  ...         self.phone = phone
+  ...         self.birthday = birthday
+  ...         self.today = datetime.datetime.now()
+  ...
+  ...     def __str__(self):
+  ...         return self.name
+  ...
+  ...     def __repr__(self):
+  ...         return '<%s %s>' %(self.__class__.__name__, self)
+
+We will fill out the other objects later. But for now, let's create a new
+person and store it in Mongo:
+
+  >>> stephan = Person(u'Stephan')
+  >>> stephan
+  <Person Stephan>
+
+The datamanager provides a ``root`` attribute in which the object tree roots
+can be stored. It is special in the sense that it immediately writes the data
+to the DB:
+
+  >>> dm.root['stephan'] = stephan
+  >>> dm.root['stephan']
+  <Person Stephan>
+
+Custom Persistence Collections
+------------------------------
+
+By default, persistent objects are stored in a collection having the Python
+path of the class:
+
+  >>> from mongopersist import serialize
+  >>> person_cn = serialize.get_dotted_name(Person)
+  >>> person_cn
+  '__main__.Person'
+
+  >>> import pprint
+  >>> pprint.pprint(list(conn[DBNAME][person_cn].find()))
+  [{u'_id': ObjectId('4e7ddf12e138237403000000'),
+    u'address': None,
+    u'birthday': None,
+    u'friends': {},
+    u'name': u'Stephan',
+    u'phone': None,
+    u'today': datetime.datetime(2011, 10, 1, 9, 45),
+    u'visited': []}]
+
+As you can see, the stored document for the person looks very Mongo. But oh
+no, I forgot to specify the full name for Stephan. Let's do that:
+
+  >>> dm.root['stephan'].name = u'Stephan Richter'
+
+This time, the data is not automatically saved:
+
+  >>> conn[DBNAME][person_cn].find_one()['name']
+  u'Stephan'
+
+So we have to commit the transaction first:
+
+  >>> transaction.commit()
+  >>> conn[DBNAME][person_cn].find_one()['name']
+  u'Stephan Richter'
+
+Let's now add an address for Stephan. Addresses are also persistent objects:
+
+  >>> class Address(persistent.Persistent):
+  ...     _p_mongo_collection = 'address'
+  ...
+  ...     def __init__(self, city, zip):
+  ...         self.city = city
+  ...         self.zip = zip
+  ...
+  ...     def __str__(self):
+  ...         return '%s (%s)' %(self.city, self.zip)
+  ...
+  ...     def __repr__(self):
+  ...         return '<%s %s>' %(self.__class__.__name__, self)
+
+MongoPersist supports a special attribute called ``_p_mongo_collection``,
+which allows you to specify a custom collection to use.
+
+  >>> dm.root['stephan'].address = Address('Maynard', '01754')
+  >>> dm.root['stephan'].address
+  <Address Maynard (01754)>
+
+Note that the address is not immediately saved in the database:
+
+  >>> list(conn[DBNAME]['address'].find())
+  []
+
+But once we commit the transaction, everything is available:
+
+  >>> transaction.commit()
+  >>> pprint.pprint(list(conn[DBNAME]['address'].find()))
+  [{u'_id': ObjectId('4e7de388e1382377f4000003'),
+    u'city': u'Maynard',
+    u'zip': u'01754'}]
+
+  >>> pprint.pprint(list(conn[DBNAME][person_cn].find()))
+  [{u'_id': ObjectId('4e7ddf12e138237403000000'),
+    u'address': DBRef(u'address',
+                      ObjectId('4e7ddf12e138237403000000'),
+                      u'mongopersist_test'),
+    u'birthday': None,
+    u'friends': {},
+    u'name': u'Stephan Richter',
+    u'phone': None,
+    u'today': datetime.datetime(2011, 10, 1, 9, 45)
+    u'visited': []}]
+
+  >>> dm.root['stephan'].address
+  <Address Maynard (01754)>
+
+
+Non-Persistent Objects
+----------------------
+
+As you can see, even the reference looks nice and uses the standard Mongo DB
+reference construct. But what about arbitrary non-persistent, but pickable,
+objects? Well, let's create a phone number object for that:
+
+  >>> class Phone(object):
+  ...
+  ...     def __init__(self, country, area, number):
+  ...         self.country = country
+  ...         self.area = area
+  ...         self.number = number
+  ...
+  ...     def __str__(self):
+  ...         return '%s-%s-%s' %(self.country, self.area, self.number)
+  ...
+  ...     def __repr__(self):
+  ...         return '<%s %s>' %(self.__class__.__name__, self)
+
+  >>> dm.root['stephan'].phone = Phone('+1', '978', '394-5124')
+  >>> dm.root['stephan'].phone
+  <Phone +1-978-394-5124>
+
+Let's now commit the transaction and look at the Mongo document again:
+
+  >>> transaction.commit()
+  >>> dm.root['stephan'].phone
+  <Phone +1-978-394-5124>
+
+  >>> pprint.pprint(list(conn[DBNAME][person_cn].find()))
+  [{u'_id': ObjectId('4e7ddf12e138237403000000'),
+    u'address': DBRef(u'address',
+                      ObjectId('4e7ddf12e138237403000000'),
+                      u'mongopersist_test'),
+    u'birthday': None,
+    u'friends': {},
+    u'name': u'Stephan Richter',
+    u'phone': {u'_py_type': u'__main__.Phone',
+               u'area': u'978',
+               u'country': u'+1',
+               u'number': u'394-5124'},
+    u'today': datetime.datetime(2011, 10, 1, 9, 45)
+    u'visited': []}]
+
+As you can see, for arbitrary non-persistent objects we need a small hint in
+the sub-document, but it is very minimal. If the ``__reduce__`` method returns
+a more complex construct, more meta-data is written. We will see that next
+when storing a date and other arbitrary data:
+
+  >>> dm.root['stephan'].friends = {'roy': Person(u'Roy Mathew')}
+  >>> dm.root['stephan'].visited = (u'Germany', u'USA')
+  >>> dm.root['stephan'].birthday = datetime.date(1980, 1, 25)
+
+  >>> transaction.commit()
+  >>> dm.root['stephan'].friends
+  {u'roy': <Person Roy Mathew>}
+  >>> dm.root['stephan'].visited
+  [u'Germany', u'USA']
+  >>> dm.root['stephan'].birthday
+  datetime.date(1980, 1, 25)
+
+As you can see, a dictionary key is always converted to unicode and tuples are
+always maintained as lists, since BSON does not have two sequence types.
+
+  >>> pprint.pprint(conn[DBNAME][person_cn].find_one(
+  ...     {'name': 'Stephan Richter'}))
+  {u'_id': ObjectId('4e7df744e138230a3e000000'),
+   u'address': DBRef(u'address',
+                     ObjectId('4e7df744e138230a3e000003'),
+                     u'mongopersist_test'),
+   u'birthday': {u'_py_factory': u'datetime.date',
+                 u'_py_factory_args': [Binary('\x07\xbc\x01\x19', 0)]},
+   u'friends': {u'roy': DBRef(u'__main__.Person',
+                              ObjectId('4e7df745e138230a3e000004'),
+                              u'mongopersist_test')},
+   u'name': u'Stephan Richter',
+   u'phone': {u'_py_type': u'__main__.Phone',
+              u'area': u'978',
+              u'country': u'+1',
+              u'number': u'394-5124'},
+   u'today': datetime.datetime(2011, 9, 24, 11, 29, 8, 930000),
+   u'visited': [u'Germany', u'USA']}
+
+
+Custom Serializers
+------------------
+
+As you can see, the serialization of the birthay is all but ideal. We can,
+however, provide a custom serializer that uses the ordinal to store the data.
+
+  >>> class DateSerializer(serialize.ObjectSerializer):
+  ...
+  ...     def can_read(self, state):
+  ...         return isinstance(state, dict) and \
+  ...                state.get('_py_type') == 'datetime.date'
+  ...
+  ...     def read(self, state):
+  ...         return datetime.date.fromordinal(state['ordinal'])
+  ...
+  ...     def can_write(self, obj):
+  ...         return isinstance(obj, datetime.date)
+  ...
+  ...     def write(self, obj):
+  ...         return {'_py_type': 'datetime.date',
+  ...                 'ordinal': obj.toordinal()}
+
+  >>> serialize.SERIALIZERS.append(DateSerializer())
+  >>> dm.root['stephan']._p_changed = True
+  >>> transaction.commit()
+
+Let's have a look again:
+
+  >>> dm.root['stephan'].birthday
+  datetime.date(1980, 1, 25)
+
+  >>> pprint.pprint(conn[DBNAME][person_cn].find_one(
+  ...     {'name': 'Stephan Richter'}))
+  {u'_id': ObjectId('4e7df803e138230aeb000000'),
+   u'address': DBRef(u'address',
+                     ObjectId('4e7df803e138230aeb000003'),
+                     u'mongopersist_test'),
+   u'birthday': {u'_py_type': u'datetime.date', u'ordinal': 722839},
+   u'friends': {u'roy': DBRef(u'__main__.Person',
+                              ObjectId('4e7df803e138230aeb000004'),
+                              u'mongopersist_test')},
+   u'name': u'Stephan Richter',
+   u'phone': {u'_py_type': u'__main__.Phone',
+              u'area': u'978',
+              u'country': u'+1',
+              u'number': u'394-5124'},
+   u'today': datetime.datetime(2011, 9, 24, 11, 32, 19, 640000),
+   u'visited': [u'Germany', u'USA']}
+
+Much better!
+
+
+Persistent Objects as Sub-Documents
+-----------------------------------
+
+In order to give more control over which objects receive their own collections
+and which do not, the developer can provide a special flag marking a
+persistent class so that it becomes part of its parent object's document:
+
+  >>> class Car(persistent.Persistent):
+  ...     _p_mongo_sub_object = True
+  ...
+  ...     def __init__(self, year, make, model):
+  ...         self.year = year
+  ...         self.make = make
+  ...         self.model = model
+  ...
+  ...     def __str__(self):
+  ...         return '%s %s %s' %(self.year, self.make, self.model)
+  ...
+  ...     def __repr__(self):
+  ...         return '<%s %s>' %(self.__class__.__name__, self)
+
+The ``_p_mongo_sub_object`` is used to mark a type of object to be just part
+of another document:
+
+  >>> dm.root['stephan'].car = car = Car('2005', 'Ford', 'Explorer')
+  >>> transaction.commit()
+
+  >>> dm.root['stephan'].car
+  <Car 2005 Ford Explorer>
+
+  >>> pprint.pprint(conn[DBNAME][person_cn].find_one(
+  ...     {'name': 'Stephan Richter'}))
+  {u'_id': ObjectId('4e7dfac7e138230d3d000000'),
+   u'address': DBRef(u'address',
+                     ObjectId('4e7dfac7e138230d3d000003'),
+                     u'mongopersist_test'),
+   u'birthday': {u'_py_type': u'datetime.date', u'ordinal': 722839},
+   u'car': {u'_py_persistent_type': u'__main__.Car',
+            u'make': u'Ford',
+            u'model': u'Explorer',
+            u'year': u'2005'},
+   u'friends': {u'roy': DBRef(u'__main__.Person',
+                              ObjectId('4e7dfac7e138230d3d000004'),
+                              u'mongopersist_test')},
+   u'name': u'Stephan Richter',
+   u'phone': {u'_py_type': u'__main__.Phone',
+              u'area': u'978',
+              u'country': u'+1',
+              u'number': u'394-5124'},
+   u'today': datetime.datetime(2011, 9, 24, 11, 44, 7, 662000),
+   u'visited': [u'Germany', u'USA']}
+
+The reason we want objects to be persistent is so that they pick up changes
+automatically:
+
+  >>> dm.root['stephan'].car.year = '2004'
+  >>> transaction.commit()
+  >>> dm.root['stephan'].car
+  <Car 2004 Ford Explorer>
+
+
+Collection Sharing
+------------------
+
+Since Mongo is so flexible, it sometimes makes sense to store multiple types
+of (similar) objects in the same collection. In those cases you instruct the
+object type to store its Python path as part of the document.
+
+Warning: Please note though that this method is less efficient, since the
+document must be loaded in order to create a ghost causing more database
+access.
+
+  >>> class ExtendedAddress(Address):
+  ...
+  ...     def __init__(self, city, zip, country):
+  ...         super(ExtendedAddress, self).__init__(city, zip)
+  ...         self.country = country
+  ...
+  ...     def __str__(self):
+  ...         return '%s (%s) in %s' %(self.city, self.zip, self.country)
+
+In order to accomplish collection sharing, you simply create another class
+that has the same ``_p_mongo_collection`` string as another (sub-classing will
+ensure that).
+
+So let's give Stephan an extended address now.
+
+  >>> dm.root['stephan'].address2 = ExtendedAddress(
+  ...     'Tettau', '01945', 'Germany')
+  >>> dm.root['stephan'].address2
+  <ExtendedAddress Tettau (01945) in Germany>
+  >>> transaction.commit()
+
+When loading the addresses, they should be of the right type:
+
+  >>> dm.root['stephan'].address
+  <Address Maynard (01754)>
+  >>> dm.root['stephan'].address2
+  <ExtendedAddress Tettau (01945) in Germany>
+
+
+Tricky Cases
+------------
+
+Changes in Basic Mutable Type
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Tricky, tricky. How do we make the framework detect changes in mutable
+objects, such as lists and dictionaries? Answer: We keep track of which
+persistent object they belong to and provide persistent implementations.
+
+  >>> type(dm.root['stephan'].friends)
+   <class 'mongopersist.serialize.PersistentDict'>
+
+  >>> dm.root['stephan'].friends[u'roger'] = Person(u'Roger')
+  >>> transaction.commit()
+  >>> dm.root['stephan'].friends.keys()
+  [u'roy', u'roger']
+
+The same is true for lists:
+
+  >>> type(dm.root['stephan'].visited)
+   <class 'mongopersist.serialize.PersistentList'>
+
+  >>> dm.root['stephan'].visited.append('France')
+  >>> transaction.commit()
+  >>> dm.root['stephan'].visited
+  [u'Germany', u'USA', u'France']
+
+
+Circular Non-Persistent References
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Any mutable object that is stored in a sub-document, cannot have multiple
+references in the object tree, since there is no global referencing. These
+circular references are detected and reported:
+
+  >>> class Top(persistent.Persistent):
+  ...     foo = None
+
+  >>> class Foo(object):
+  ...     bar = None
+
+  >>> class Bar(object):
+  ...     foo = None
+
+  >>> top = Top()
+  >>> foo = Foo()
+  >>> bar = Bar()
+  >>> top.foo = foo
+  >>> foo.bar = bar
+  >>> bar.foo = foo
+
+  >>> dm.root['top'] = top
+  Traceback (most recent call last):
+  ...
+  CircularReferenceError: <__main__.Foo object at 0x7fec75731890>
+
+
+Circular Persistent References
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In general, circular references among persistent objects are not a problem,
+since we always only store a link to the object. However, there is a case when
+the circular dependencies become a problem.
+
+If you set up an object tree with circular references and then add the tree to
+the storage at once, it must insert objects during serialization, so that
+references can be created. However, care needs to be taken to only create a
+minimal reference object, so that the system does not try to recursively
+reduce the state.
+
+  >>> class PFoo(persistent.Persistent):
+  ...     bar = None
+
+  >>> class PBar(persistent.Persistent):
+  ...     foo = None
+
+  >>> top = Top()
+  >>> foo = PFoo()
+  >>> bar = PBar()
+  >>> top.foo = foo
+  >>> foo.bar = bar
+  >>> bar.foo = foo
+
+  >>> dm.root['ptop'] = top
+
+
+Containers and Collections
+--------------------------
+
+Now that we have talked so much about the gory details on storing one object,
+what about mappings that reflect an entire collection, for example a
+collection of people.
+
+There are many approaches that can be taken. The folowing implementation
+defines an attribute in the document as the mapping key and names a
+collection:
+
+  >>> from mongopersist import mapping
+  >>> class People(mapping.MongoCollectionMapping):
+  ...     __mongo_collection__ = person_cn
+  ...     __mongo_mapping_key__ = 'short_name'
+
+The mapping takes the data manager as an argument. One can easily create a
+sub-class that assigns the data manager automatically. Let's have a look:
+
+  >>> People(dm).keys()
+  []
+
+The reason no person is in the list yet, is because no document has the key
+yet or the key is null. Let's change that:
+
+  >>> People(dm)['stephan'] = dm.root['stephan']
+  >>> transaction.commit()
+
+  >>> People(dm).keys()
+  [u'stephan']
+  >>> People(dm)['stephan']
+  <Person Stephan Richter>
+
+Also note that setting the "short-name" attribute on any other person will add
+it to the mapping:
+
+  >>> dm.root['stephan'].friends['roy'].short_name = 'roy'
+  >>> transaction.commit()
+  >>> People(dm).keys()
+  [u'roy', u'stephan']
+
+
+Write-Conflict Detection
+------------------------
+
+Since Mongo has no support for MVCC, it does not provide a concept of write
+conflict detection. However, a simple write-conflict detection can be easily
+implemented using a serial number on the document.
+
+Let's reset the database and create a data manager with enabled conflict
+detection:
+
+  >>> from mongopersist import datamanager
+  >>> conn.drop_database(DBNAME)
+  >>> dm2 = datamanager.MongoDataManager(
+  ...     conn,
+  ...     default_database=DBNAME,
+  ...     root_database=DBNAME,
+  ...     detect_conflicts=True)
+
+Now we add a person and see that the serial got stored.
+
+  >>> dm2.root['stephan'] = Person(u'Stephan')
+  >>> dm2.root['stephan']._p_serial
+  '\x00\x00\x00\x00\x00\x00\x00\x01'
+  >>> pprint.pprint(dm2._conn[DBNAME][person_cn].find_one())
+  {u'_id': ObjectId('4e7fe18de138233a5b000009'),
+   u'_py_serial': 1,
+   u'address': None,
+   u'birthday': None,
+   u'friends': {},
+   u'name': u'Stephan',
+   u'phone': None,
+   u'today': datetime.datetime(2011, 9, 25, 22, 21, 1, 656000),
+   u'visited': []}
+
+Next we change the person and commit it again:
+
+  >>> dm2.root['stephan'].name = u'Stephan <Unknown>'
+  >>> transaction.commit()
+  >>> pprint.pprint(dm2._conn[DBNAME][person_cn].find_one())
+  {u'_id': ObjectId('4e7fe18de138233a5b000009'),
+   u'_py_serial': 2,
+   u'address': None,
+   u'birthday': None,
+   u'friends': {},
+   u'name': u'Stephan <Unknown>',
+   u'phone': None,
+   u'today': datetime.datetime(2011, 9, 25, 22, 21, 1, 656000),
+   u'visited': []}
+
+Let's now start a new transaction with some modifications:
+
+  >>> dm2.root['stephan'].name = u'Stephan Richter'
+
+However, in the mean time another transaction modifies the object. (We will do
+this here directly via Mongo for simplicity.)
+
+  >>> dm2._conn[DBNAME][person_cn].update(
+  ...     {'name': u'Stephan <Unknown>'},
+  ...     {'$set': {'name': u'Stephan R.', '_py_serial': 3}})
+  >>> pprint.pprint(dm2._conn[DBNAME][person_cn].find_one())
+  {u'_id': ObjectId('4e7fe1f4e138233ac4000009'),
+   u'_py_serial': 3,
+   u'address': None,
+   u'birthday': None,
+   u'friends': {},
+   u'name': u'Stephan R.',
+   u'phone': None,
+   u'today': datetime.datetime(2011, 9, 25, 22, 22, 44, 343000),
+   u'visited': []}
+
+Now our changing transaction tries to commit:
+
+  >>> transaction.commit()
+  Traceback (most recent call last):
+  ...
+  ConflictError: database conflict error
+      (oid ..., class Person, start serial 2, current serial 3)
+
+  >>> transaction.abort()


Property changes on: mongopersist/trunk/src/mongopersist/README.txt
___________________________________________________________________
Added: svn:eol-style
   + native

Added: mongopersist/trunk/src/mongopersist/__init__.py
===================================================================
--- mongopersist/trunk/src/mongopersist/__init__.py	                        (rev 0)
+++ mongopersist/trunk/src/mongopersist/__init__.py	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1 @@
+# Make a package.


Property changes on: mongopersist/trunk/src/mongopersist/__init__.py
___________________________________________________________________
Added: svn:keywords
   + Id

Added: mongopersist/trunk/src/mongopersist/datamanager.py
===================================================================
--- mongopersist/trunk/src/mongopersist/datamanager.py	                        (rev 0)
+++ mongopersist/trunk/src/mongopersist/datamanager.py	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,174 @@
+##############################################################################
+#
+# Copyright (c) 2011 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Mongo Persistent Data Manager"""
+from __future__ import absolute_import
+import UserDict
+import persistent
+import pymongo
+import pymongo.dbref
+import transaction
+import zope.interface
+
+from mongopersist import interfaces, serialize
+
+def create_conflict_error(obj, new_doc):
+    return interfaces.ConflictError(
+        None, obj,
+        (new_doc.get('_py_serial', 0), serialize.u64(obj._p_serial)))
+
+
+class Root(UserDict.DictMixin):
+
+    database='mongopersist'
+    collection = 'persistence_root'
+
+    def __init__(self, jar, database=None, collection=None):
+        self._jar = jar
+        if database is not None:
+            self.database = database
+        if collection is not None:
+            self.collection = collection
+        db = self._jar._conn[self.database]
+        self._collection_inst = db[self.collection]
+
+    def __getitem__(self, key):
+        doc = self._collection_inst.find_one({'name': key})
+        if doc is None:
+            raise KeyError(key)
+        return self._jar.load(doc['ref'])
+
+    def __setitem__(self, key, value):
+        dbref = self._jar.dump(value)
+        if self.get(key) is not None:
+            del self[key]
+        doc = {'ref': dbref, 'name': key}
+        self._collection_inst.insert(doc)
+
+    def __delitem__(self, key):
+        doc = self._collection_inst.find_one({'name': key})
+        coll = self._jar._conn[doc['ref'].database][doc['ref'].collection]
+        coll.remove(doc['ref'].id)
+        self._collection_inst.remove({'name': key})
+
+    def keys(self):
+        return [doc['name'] for doc in self._collection_inst.find()]
+
+
+class MongoDataManager(object):
+    zope.interface.implements(interfaces.IMongoDataManager)
+
+    detect_conflicts = False
+    default_database = 'mongopersist'
+    name_map_collection = 'persistence_name_map'
+    conflict_error_factory = staticmethod(create_conflict_error)
+
+    def __init__(self, conn, detect_conflicts=None, default_database=None,
+                 root_database=None, root_collection=None,
+                 name_map_collection=None, conflict_error_factory=None):
+        self._conn = conn
+        self._reader = serialize.ObjectReader(self)
+        self._writer = serialize.ObjectWriter(self)
+        self._registered_objects = []
+        self._loaded_objects = []
+        self._needs_to_join = True
+        self._object_cache = {}
+        self.annotations = {}
+        if detect_conflicts is not None:
+            self.detect_conflicts = detect_conflicts
+        if default_database is not None:
+            self.default_database = default_database
+        if name_map_collection is not None:
+            self.name_map_collection = name_map_collection
+        if conflict_error_factory is not None:
+            self.conflict_error_factory = conflict_error_factory
+        self.transaction_manager = transaction.manager
+        self.root = Root(self, root_database, root_collection)
+
+    def dump(self, obj):
+        return self._writer.store(obj)
+
+    def load(self, dbref):
+        return self._reader.get_ghost(dbref)
+
+    def reset(self):
+        root = self.root
+        self.__init__(self._conn)
+        self.root = root
+
+    def setstate(self, obj):
+        # When reading a state from Mongo, we also need to join the
+        # transaction, because we keep an active object cache that gets stale
+        # after the transaction is complete and must be cleaned.
+        if self._needs_to_join:
+            self.transaction_manager.get().join(self)
+            self._needs_to_join = False
+        self._reader.set_ghost_state(obj)
+        self._loaded_objects.append(obj)
+
+    def oldstate(self, obj, tid):
+        # I cannot find any code using this method. Also, since we do not keep
+        # version history, we always raise an error.
+        raise KeyError(tid)
+
+    def register(self, obj):
+        if self._needs_to_join:
+            self.transaction_manager.get().join(self)
+            self._needs_to_join = False
+
+        if obj is not None and obj not in self._registered_objects:
+            self._registered_objects.append(obj)
+
+    def abort(self, transaction):
+        self.reset()
+
+    def commit(self, transaction):
+        if not self.detect_conflicts:
+            return
+        # Check each modified object to see whether Mongo has a new version of
+        # the object.
+        for obj in self._registered_objects:
+            # This object is not even added to the database yet, so there
+            # cannot be a conflict.
+            if obj._p_oid is None:
+                continue
+            db_name, coll_name = self._writer.get_collection_name(obj)
+            coll = self._conn[db_name][coll_name]
+            new_doc = coll.find_one(obj._p_oid.id, fields=('_py_serial',))
+            if new_doc is None:
+                continue
+            if new_doc.get('_py_serial', 0) != serialize.u64(obj._p_serial):
+                raise self.conflict_error_factory(obj, new_doc)
+
+    def tpc_begin(self, transaction):
+        pass
+
+    def tpc_vote(self, transaction):
+        pass
+
+    def tpc_finish(self, transaction):
+        written = []
+        for obj in self._registered_objects:
+            if getattr(obj, '_p_mongo_sub_object', False):
+                obj = obj._p_mongo_doc_object
+            if obj in written:
+                continue
+            self._writer.store(obj)
+            written.append(obj)
+        self.reset()
+
+    def tpc_abort(self, transaction):
+        self.abort(transaction)
+
+    def sortKey(self):
+        return ('MongoDataManager', 0)


Property changes on: mongopersist/trunk/src/mongopersist/datamanager.py
___________________________________________________________________
Added: svn:keywords
   + Id

Added: mongopersist/trunk/src/mongopersist/interfaces.py
===================================================================
--- mongopersist/trunk/src/mongopersist/interfaces.py	                        (rev 0)
+++ mongopersist/trunk/src/mongopersist/interfaces.py	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,168 @@
+##############################################################################
+#
+# Copyright (c) 2011 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Mongo Persistence Interfaces"""
+from __future__ import absolute_import
+import datetime
+import persistent.interfaces
+import transaction.interfaces
+import types
+import zope.interface
+import zope.schema
+from pymongo import objectid, dbref
+
+MONGO_NATIVE_TYPES = (
+    int, float, unicode, datetime.datetime, types.NoneType,
+    objectid.ObjectId, dbref.DBRef)
+
+class ConflictError(transaction.interfaces.TransientError):
+
+    def __init__(self, message=None, object=None, serials=None):
+        self.message = message or "database conflict error"
+        self.object = object
+        self.serials = serials
+
+    @property
+    def new_serial(self):
+        return self.serials[0]
+
+    @property
+    def old_serial(self):
+        return self.serials[1]
+
+    def __str__(self):
+        extras = [
+            'oid %s' %self.object._p_oid,
+            'class %s' %self.object.__class__.__name__,
+            'start serial %s' %self.old_serial,
+            'current serial %s' %self.new_serial]
+        return "%s (%s)" % (self.message, ", ".join(extras))
+
+    def __repr__(self):
+        return '%s: %s' %(self.__class__.__name__, self)
+
+
+class CircularReferenceError(Exception):
+    pass
+
+class IObjectSerializer(zope.interface.Interface):
+    """An object serializer allows for custom serialization output for
+    objects."""
+
+    def can_read(state):
+        """Returns a boolean indicating whether this serializer can deserialize
+        this state."""
+
+    def get_object(state):
+        """Convert the state to an object."""
+
+    def can_write(obj):
+        """Returns a boolean indicating whether this serializer can serialize
+        this object."""
+
+    def get_state(obj):
+        """Convert the object to a state/document."""
+
+
+class IObjectWriter(zope.interface.Interface):
+    """The object writer stores an object in the database."""
+
+    def get_non_persistent_state(obj, seen):
+        """Convert a non-persistent object to a Mongo state/document."""
+
+    def get_persistent_state(obj, seen):
+        """Convert a persistent object to a Mongo state/document."""
+
+    def get_state(obj, seen=None):
+        """Convert an arbitrary object to a Mongo state/document.
+
+        A ``CircularReferenceError`` is raised, if a non-persistent loop is
+        detected.
+        """
+
+    def store(obj):
+        """Store an object in the database."""
+
+
+class IObjectReader(zope.interface.Interface):
+    """The object reader reads an object from the database."""
+
+    def resolve(path):
+        """Resolve a path to a class.
+
+        The path can be any string. It is the responsibility of the resolver
+        to maintain the mapping from path to class.
+        """
+
+    def get_object(state, obj):
+        """Get an object from the given state.
+
+        The ``obj`` is the Mongo document of which the created object is part
+        of.
+        """
+
+    def set_ghost_state(obj):
+        """Convert a ghosted object to an active object by loading its state.
+        """
+
+    def get_ghost(coll_name, oid):
+        """Get the ghosted version of the object.
+        """
+
+
+class IMongoDataManager(persistent.interfaces.IPersistentDataManager):
+    """A persistent data manager that stores data in Mongo."""
+
+    root = zope.interface.Attribute(
+        """Get the root object, which is a mapping.""")
+
+    def reset():
+        """Reset the datamanager for the next transaction."""
+
+    def dump(obj):
+        """Store the object to Mongo and return its DBRef."""
+
+    def load(dbref):
+        """Load the object from Mongo by using its DBRef.
+
+        Note: The returned object is in the ghost state.
+        """
+
+
+class IMongoConnectionPool(zope.interface.Interface):
+    """MongoDB connection pool"""
+
+    connection = zope.interface.Attribute('MongoDBConnection instance')
+
+    host = zope.schema.TextLine(
+        title=u'MongoDB Server Hostname (without protocol)',
+        description=u'MongoDB Server Hostname or IPv4 address',
+        default=u'localhost',
+        required=True)
+
+    port = zope.schema.Int(
+        title=u'MongoDB Server Port',
+        description=u'MongoDB Server Port',
+        default=27017,
+        required=True)
+
+
+class IMongoDataManagerProvider(zope.interface.Interface):
+    """Utility to get a mongo data manager.
+
+    Implementations of this utility ususally maintain connection information
+    and ensure that there is one consistent datamanager per thread.
+    """
+
+    def get():
+        """Return a mongo data manager."""


Property changes on: mongopersist/trunk/src/mongopersist/interfaces.py
___________________________________________________________________
Added: svn:keywords
   + Id

Added: mongopersist/trunk/src/mongopersist/mapping.py
===================================================================
--- mongopersist/trunk/src/mongopersist/mapping.py	                        (rev 0)
+++ mongopersist/trunk/src/mongopersist/mapping.py	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,64 @@
+##############################################################################
+#
+# Copyright (c) 2011 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Mongo Mapping Implementations"""
+import UserDict
+import pymongo
+
+from mongopersist import interfaces
+
+class MongoCollectionMapping(UserDict.DictMixin, object):
+    __mongo_database__ = None
+    __mongo_collection__ = None
+    __mongo_mapping_key__ = 'key'
+
+    def __init__(self, jar):
+        self._m_jar = jar
+
+    def __mongo_filter__(self):
+        return {}
+
+    def get_mongo_collection(self):
+        db_name = self.__mongo_database__ or self._m_jar.default_database
+        return self._m_jar._conn[db_name][self.__mongo_collection__]
+
+    def __getitem__(self, key):
+        filter = self.__mongo_filter__()
+        filter[self.__mongo_mapping_key__] = key
+        doc = self.get_mongo_collection().find_one(filter)
+        if doc is None:
+            raise KeyError(key)
+        db_name = self.__mongo_database__ or self._m_jar.default_database
+        dbref = pymongo.dbref.DBRef(
+            self.__mongo_collection__, doc['_id'], db_name)
+        return self._m_jar._reader.get_ghost(dbref)
+
+    def __setitem__(self, key, value):
+        # Even though setting the attribute should register the object with
+        # the data manager, the value might not be in the DB at all at this
+        # point, so registering it manually ensures that new objects get added.
+        self._m_jar.register(value)
+        setattr(value, self.__mongo_mapping_key__, key)
+
+    def __delitem__(self, key):
+        # Deleting the object from the database is not our job. We simply
+        # remove it from the dictionary.
+        value = self[key]
+        setattr(value, self.__mongo_mapping_key__, None)
+
+    def keys(self):
+        filter = self.__mongo_filter__()
+        filter[self.__mongo_mapping_key__] = {'$ne': None}
+        return [
+            doc[self.__mongo_mapping_key__]
+            for doc in self.get_mongo_collection().find(filter)]


Property changes on: mongopersist/trunk/src/mongopersist/mapping.py
___________________________________________________________________
Added: svn:keywords
   + Id

Added: mongopersist/trunk/src/mongopersist/pool.py
===================================================================
--- mongopersist/trunk/src/mongopersist/pool.py	                        (rev 0)
+++ mongopersist/trunk/src/mongopersist/pool.py	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,88 @@
+##############################################################################
+#
+# Copyright (c) 2011 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Thread-aware Mongo Connection Pool"""
+from __future__ import absolute_import
+import logging
+import threading
+import pymongo
+import zope.interface
+
+from mongopersist import datamanager, interfaces
+
+log = logging.getLogger('mongopersist')
+
+class MongoConnectionPool(object):
+    """MongoDB connection pool contains the connection to a mongodb server.
+
+    MongoConnectionPool is a global named utility, knows how to setup a
+    thread (safe) shared mongodb connection instance.
+
+    Note: pymongo offers connection pooling which we do not need since we use
+    one connection per thread
+    """
+    zope.interface.implements(interfaces.IMongoConnectionPool)
+
+    _mongoConnectionFactory = pymongo.Connection
+
+    def __init__(self, host='localhost', port=27017, logLevel=20,
+        tz_aware=True, connectionFactory=None):
+        self.host = host
+        self.port = port
+        self.key = 'mongopersist-%s-%s' %(self.host, self.port)
+        self.tz_aware = tz_aware
+        if connectionFactory is not None:
+            self._mongoConnectionFactory = connectionFactory
+        self.logLevel = logLevel
+
+    @property
+    def storage(self):
+        return LOCAL.__dict__
+
+    def disconnect(self):
+        conn = self.storage.get(self.key, None)
+        if conn is not None:
+            conn.disconnect()
+        self.storage[self.key] = None
+
+    @property
+    def connection(self):
+        conn = self.storage.get(self.key, None)
+        if conn is None:
+            self.storage[self.key] = conn = self._mongoConnectionFactory(
+                self.host, self.port, tz_aware=self.tz_aware)
+            if self.logLevel:
+                log.log(self.logLevel, "Create connection for %s:%s" % (
+                    self.host, self.port))
+
+        return conn
+
+
+LOCAL = threading.local()
+
+class MongoDataManagerProvider(object):
+    zope.interface.implements(interfaces.IMongoDataManagerProvider)
+
+    def __init__(self, host='localhost', port=27017,
+                 logLevel=20, tz_aware=True, **dm_kwargs):
+        self.pool = MongoConnectionPool(host, port, logLevel, tz_aware)
+        self.dm_kwargs = dm_kwargs
+
+    def get(self):
+        try:
+            dm = LOCAL.data_manager
+        except AttributeError, err:
+            conn = self.pool.connection
+            dm = LOCAL.data_manager = datamanager.MongoDataManager(
+                conn, **self.dm_kwargs)
+        return dm


Property changes on: mongopersist/trunk/src/mongopersist/pool.py
___________________________________________________________________
Added: svn:keywords
   + Id

Added: mongopersist/trunk/src/mongopersist/serialize.py
===================================================================
--- mongopersist/trunk/src/mongopersist/serialize.py	                        (rev 0)
+++ mongopersist/trunk/src/mongopersist/serialize.py	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,407 @@
+##############################################################################
+#
+# Copyright (c) 2011 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Object Serialization for Mongo/BSON"""
+from __future__ import absolute_import
+import copy_reg
+import struct
+
+import lru
+import persistent.interfaces
+import persistent.dict
+import persistent.list
+import pymongo.binary
+import pymongo.dbref
+import pymongo.objectid
+import types
+import zope.interface
+from zope.dottedname.resolve import resolve
+
+from mongopersist import interfaces
+
+SERIALIZERS = []
+OID_CLASS_LRU = lru.LRUCache(20000)
+
+def p64(v):
+    """Pack an integer or long into a 8-byte string"""
+    return struct.pack(">Q", v)
+
+def u64(v):
+    """Unpack an 8-byte string into a 64-bit long integer."""
+    return struct.unpack(">Q", v)[0]
+
+def get_dotted_name(obj):
+    return obj.__module__+'.'+obj.__name__
+
+class PersistentDict(persistent.dict.PersistentDict):
+    _p_mongo_sub_object = True
+
+class PersistentList(persistent.list.PersistentList):
+    _p_mongo_sub_object = True
+
+
+class ObjectSerializer(object):
+    zope.interface.implements(interfaces.IObjectSerializer)
+
+    def can_read(self, state):
+        raise NotImplementedError
+
+    def read(self, state):
+        raise NotImplementedError
+
+    def can_write(self, obj):
+        raise NotImplementedError
+
+    def write(self, obj):
+        raise NotImplementedError
+
+
+class ObjectWriter(object):
+    zope.interface.implements(interfaces.IObjectWriter)
+
+    def __init__(self, jar):
+        self._jar = jar
+
+    def get_collection_name(self, obj):
+        db_name = getattr(obj, '_p_mongo_database', self._jar.default_database)
+        try:
+            coll_name = obj._p_mongo_collection
+        except AttributeError:
+            return db_name, get_dotted_name(obj.__class__)
+        # Make sure that the coll_name to class path mapping is available.
+        db = self._jar._conn[self._jar.default_database]
+        coll = db[self._jar.name_map_collection]
+        map = {'collection': coll_name,
+               'database': db_name,
+               'path': get_dotted_name(obj.__class__)}
+        result = coll.find_one(map)
+        if result is None:
+            # If there is already a map for this collection, the next map must
+            # force the object to store the type.
+            result = coll.find({'collection': coll_name,
+                                'database': db_name})
+            if result.count() > 0:
+                setattr(obj, '_p_mongo_store_type', True)
+            map['doc_has_type'] = getattr(obj, '_p_mongo_store_type', False)
+            coll.save(map)
+        return db_name, coll_name
+
+    def get_non_persistent_state(self, obj, seen):
+        __traceback_info__ = obj
+        # XXX: Look at the pickle library how to properly handle all types and
+        # old-style classes with all of the possible pickle extensions.
+
+        # Only non-persistent, custom objects can produce unresolvable
+        # circular references.
+        if obj in seen:
+            raise interfaces.CircularReferenceError(obj)
+        # Add the current object to the list of seen objects.
+        seen.append(obj)
+        # Get the state of the object. Only pickable objects can be reduced.
+        reduced = obj.__reduce__()
+        # The full object state (item 3) seems to be optional, so let's make
+        # sure we handle that case gracefully.
+        if len(reduced) == 2:
+            factory, args = obj.__reduce__()
+            obj_state = {}
+        else:
+            factory, args, obj_state = reduced
+        # We are trying very hard to create a clean Mongo (sub-)document. But
+        # we need a little bit of meta-data to help us out later.
+        if factory == copy_reg._reconstructor and \
+               args == (obj.__class__, object, None):
+            # This is the simple case, which means we can produce a nicer
+            # Mongo output.
+            state = {'_py_type': get_dotted_name(args[0])}
+        elif factory == copy_reg.__newobj__ and args == (obj.__class__,):
+            # Another simple case for persistent objects that do not want
+            # their own document.
+            state = {'_py_persistent_type': get_dotted_name(args[0])}
+        else:
+            state = {'_py_factory': get_dotted_name(factory),
+                     '_py_factory_args': self.get_state(args, seen)}
+        for name, value in obj_state.items():
+            state[name] = self.get_state(value, seen)
+        return state
+
+    def get_persistent_state(self, obj, seen):
+        __traceback_info__ = obj
+        # Persistent sub-objects are stored by reference, the key being
+        # (collection name, oid).
+        # Getting the collection name is easy, but if we have an unsaved
+        # persistent object, we do not yet have an OID. This must be solved by
+        # storing the persistent object.
+        if obj._p_oid is None:
+            dbref = self.store(obj, ref_only=True)
+        else:
+            db_name, coll_name = self.get_collection_name(obj)
+            dbref = obj._p_oid
+        # Create the reference sub-document. The _p_type value helps with the
+        # deserialization later.
+        return dbref
+
+    def get_state(self, obj, seen=None):
+        seen = seen or []
+        if isinstance(obj, interfaces.MONGO_NATIVE_TYPES):
+            # If we have a native type, we'll just use it as the state.
+            return obj
+        if isinstance(obj, str):
+            # In Python 2, strings can be ASCII, encoded unicode or binary
+            # data. Unfortunately, BSON cannot handle that. So, if we have a
+            # string that cannot be UTF-8 decoded (luckily ASCII is a valid
+            # subset of UTF-8), then we use the BSON binary type.
+            try:
+                obj.decode('utf-8')
+                return obj
+            except UnicodeError:
+                return pymongo.binary.Binary(obj)
+
+        # Some objects might not naturally serialize well and create a very
+        # ugly Mongo entry. Thus, we allow custom serializers to be
+        # registered, which can encode/decode different types of objects.
+        for serializer in SERIALIZERS:
+            if serializer.can_write(obj):
+                return serializer.write(obj)
+
+        if isinstance(obj, (type, types.ClassType)):
+            # We frequently store class and function paths as meta-data, so we
+            # need to be able to properly encode those.
+            return {'_py_type': 'type',
+                    'path': get_dotted_name(obj)}
+        if isinstance(obj, (tuple, list, PersistentList)):
+            # Make sure that all values within a list are serialized
+            # correctly. Also convert any sequence-type to a simple list.
+            return [self.get_state(value, seen) for value in obj]
+        if isinstance(obj, (dict, PersistentDict)):
+            # Same as for sequences, make sure that the contained values are
+            # properly serialized.
+            # Note: A big constraint in Mongo is that keys must be strings!
+            has_non_string_key = False
+            data = []
+            for key, value in obj.items():
+                data.append((key, self.get_state(value, seen)))
+                has_non_string_key |= not isinstance(key, basestring)
+            if not has_non_string_key:
+                # The easy case: all keys are strings:
+                return dict(data)
+            else:
+                # We first need to reduce the keys and then produce a data
+                # structure.
+                data = [(self.get_state(key), value) for key, value in data]
+                return {'dict_data': data}
+
+        if isinstance(obj, persistent.Persistent):
+            # Only create a persistent reference, if the object does not want
+            # to be a sub-document.
+            if not getattr(obj, '_p_mongo_sub_object', False):
+                return self.get_persistent_state(obj, seen)
+            # This persistent object is a sub-document, so it is treated like
+            # a non-persistent object.
+
+        return self.get_non_persistent_state(obj, seen)
+
+    def store(self, obj, ref_only=False):
+        db_name, coll_name = self.get_collection_name(obj)
+        coll = self._jar._conn[db_name][coll_name]
+        if ref_only:
+            # We only want to get OID quickly. Trying to reduce the full state
+            # might cause infinite recusrion loop. (Example: 2 new objects
+            # reference each other.)
+            doc = {}
+            # Make sure that the object gets saved fully later.
+            self._jar.register(obj)
+        else:
+            # XXX: Handle newargs; see ZODB.serialize.ObjectWriter.serialize
+            # Go through each attribute and search for persistent references.
+            doc = self.get_state(obj.__getstate__())
+        if getattr(obj, '_p_mongo_store_type', False):
+            doc['_py_persistent_type'] = get_dotted_name(obj.__class__)
+        # If conflict detection is turned on, store a serial number for the
+        # document.
+        if self._jar.detect_conflicts:
+            doc['_py_serial'] = u64(getattr(obj, '_p_serial', 0)) + 1
+            obj._p_serial = p64(doc['_py_serial'])
+
+        if obj._p_oid is None:
+            doc_id = coll.insert(doc)
+            obj._p_jar = self._jar
+            obj._p_oid = pymongo.dbref.DBRef(coll_name, doc_id, db_name)
+            # Make sure that any other code accessing this object in this
+            # session, gets the same instance.
+            self._jar._object_cache[doc_id] = obj
+        else:
+            doc['_id'] = obj._p_oid.id
+            coll.save(doc)
+        return obj._p_oid
+
+
+class ObjectReader(object):
+    zope.interface.implements(interfaces.IObjectReader)
+
+    def __init__(self, jar):
+        self._jar = jar
+
+    def simple_resolve(self, path):
+        return resolve(path)
+
+    def resolve(self, dbref):
+        try:
+            return OID_CLASS_LRU[dbref.id]
+        except KeyError:
+            pass
+        # First we try to resolve the path directly.
+        try:
+            return self.simple_resolve(dbref.collection)
+        except ImportError:
+            pass
+        # Let's now try to look up the path from the collection to path
+        # mapping
+        db = self._jar._conn[self._jar.default_database]
+        coll = db[self._jar.name_map_collection]
+        result = coll.find(
+            {'collection': dbref.collection, 'database': dbref.database})
+        if result.count() == 0:
+            raise ImportError(dbref)
+        elif result.count() == 1:
+            # Do not add these results to the LRU cache, since the count might
+            # change later.
+            return self.simple_resolve(result.next()['path'])
+        else:
+            if dbref.id is None:
+                raise ImportError(dbref)
+            # Multiple object types are stored in the collection. We have to
+            # look at the object to find out the type.
+            obj_doc = self._jar._conn[dbref.database][dbref.collection].find_one(
+                dbref.id, fields=('_py_persistent_type',))
+            if '_py_persistent_type' in obj_doc:
+                klass = self.simple_resolve(obj_doc['_py_persistent_type'])
+            else:
+                # Find the name-map entry where "doc_has_type" is False.
+                for name_map_item in result:
+                    if not name_map_item['doc_has_type']:
+                        klass = self.simple_resolve(name_map_item['path'])
+                        break
+                else:
+                    raise ImportError(path)
+            OID_CLASS_LRU[dbref.id] = klass
+            return klass
+
+    def get_non_persistent_object(self, state, obj):
+        if '_py_type' in state:
+            # Handle the simplified case.
+            klass = self.simple_resolve(state.pop('_py_type'))
+            sub_obj = copy_reg._reconstructor(klass, object, None)
+        elif '_py_persistent_type' in state:
+            # Another simple case for persistent objects that do not want
+            # their own document.
+            klass = self.simple_resolve(state.pop('_py_persistent_type'))
+            sub_obj = copy_reg.__newobj__(klass)
+        else:
+            factory = self.simple_resolve(state.pop('_py_factory'))
+            factory_args = self.get_object(state.pop('_py_factory_args'), obj)
+            sub_obj = factory(*factory_args)
+        if len(state):
+            sub_obj_state = self.get_object(state, obj)
+            if isinstance(sub_obj, persistent.Persistent):
+                sub_obj.__setstate__(sub_obj_state)
+            else:
+                sub_obj.__dict__.update(sub_obj_state)
+        if getattr(sub_obj, '_p_mongo_sub_object', False):
+            sub_obj._p_mongo_doc_object = obj
+            sub_obj._p_jar = self._jar
+        return sub_obj
+
+    def get_object(self, state, obj):
+        if isinstance(state, pymongo.objectid.ObjectId):
+            # The object id is special. Preserve it.
+            return state
+        if isinstance(state, pymongo.binary.Binary):
+            # Binary data in Python 2 is presented as a string. We will
+            # convert back to binary when serializing again.
+            return str(state)
+        if isinstance(state, pymongo.dbref.DBRef):
+            # Load a persistent object. Using the get_ghost() method, so that
+            # caching is properly applied.
+            return self.get_ghost(state)
+        if isinstance(state, dict) and state.get('_py_type') == 'type':
+            # Convert a simple object reference, mostly classes.
+            return self.simple_resolve(state['path'])
+
+        # Give the custom serializers a chance to weigh in.
+        for serializer in SERIALIZERS:
+            if serializer.can_read(state):
+                return serializer.read(state)
+
+        if isinstance(state, dict) and ('_py_factory' in state or \
+               '_py_type' in state or '_py_persistent_type' in state):
+            # Load a non-persistent object.
+            return self.get_non_persistent_object(state, obj)
+        if isinstance(state, (tuple, list)):
+            # All lists are converted to persistent lists, so that their state
+            # changes are noticed. Also make sure that all value states are
+            # converted to objects.
+            sub_obj = PersistentList(
+                [self.get_object(value, obj) for value in state])
+            sub_obj._p_mongo_doc_object = obj
+            sub_obj._p_jar = self._jar
+            return sub_obj
+        if isinstance(state, dict):
+            # All dictionaries are converted to persistent dictionaries, so
+            # that state changes are detected. Also convert all value states
+            # to objects.
+            # Handle non-string key dicts.
+            if 'dict_data' in state:
+                items = state['dict_data']
+            else:
+                items = state.items()
+            sub_obj = PersistentDict(
+                [(self.get_object(name, obj), self.get_object(value, obj))
+                 for name, value in items])
+            sub_obj._p_mongo_doc_object = obj
+            sub_obj._p_jar = self._jar
+            return sub_obj
+        return state
+
+    def set_ghost_state(self, obj):
+        # Look up the object state by coll_name and oid.
+        coll = self._jar._conn[obj._p_oid.database][obj._p_oid.collection]
+        doc = coll.find_one({'_id': obj._p_oid.id})
+        doc.pop('_id')
+        doc.pop('_py_persistent_type', None)
+        # Store the serial, if conflict detection is enabled.
+        if self._jar.detect_conflicts:
+            obj._p_serial = p64(doc.pop('_py_serial', 0))
+        # Now convert the document to a proper Python state dict.
+        state = self.get_object(doc, obj)
+        # Set the state.
+        obj.__setstate__(dict(state))
+
+    def get_ghost(self, dbref):
+        # If we can, we return the object from cache.
+        try:
+            return self._jar._object_cache[dbref.id]
+        except KeyError:
+            pass
+        klass = self.resolve(dbref)
+        obj = klass.__new__(klass)
+        obj._p_jar = self._jar
+        obj._p_oid = dbref
+        del obj._p_changed
+        # Assign the collection after deleting _p_changed, since the attribute
+        # is otherwise deleted.
+        obj._p_mongo_database = dbref.database
+        obj._p_mongo_collection = dbref.collection
+        # Adding the object to the cache is very important, so that we get the
+        # same object reference throughout the transaction.
+        self._jar._object_cache[dbref.id] = obj
+        return obj


Property changes on: mongopersist/trunk/src/mongopersist/serialize.py
___________________________________________________________________
Added: svn:keywords
   + Id

Added: mongopersist/trunk/src/mongopersist/serializers.py
===================================================================
--- mongopersist/trunk/src/mongopersist/serializers.py	                        (rev 0)
+++ mongopersist/trunk/src/mongopersist/serializers.py	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,32 @@
+##############################################################################
+#
+# Copyright (c) 2011 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Python Serializers for common objects with weird reduce output."""
+import datetime
+from mongopersist import serialize
+
+class DateSerializer(serialize.ObjectSerializer):
+
+    def can_read(self, state):
+        return isinstance(state, dict) and \
+               state.get('_py_type') == 'datetime.date'
+
+    def read(self, state):
+        return datetime.date.fromordinal(state['ordinal'])
+
+    def can_write(self, obj):
+        return isinstance(obj, datetime.date)
+
+    def write(self, obj):
+        return {'_py_type': 'datetime.date',
+                'ordinal': obj.toordinal()}


Property changes on: mongopersist/trunk/src/mongopersist/serializers.py
___________________________________________________________________
Added: svn:keywords
   + Id

Added: mongopersist/trunk/src/mongopersist/testing.py
===================================================================
--- mongopersist/trunk/src/mongopersist/testing.py	                        (rev 0)
+++ mongopersist/trunk/src/mongopersist/testing.py	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,55 @@
+##############################################################################
+#
+# Copyright (c) 2011 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Mongo Persistence Testing Support"""
+from __future__ import absolute_import
+import doctest
+import pymongo
+import re
+import transaction
+from zope.testing import module, renormalizing
+
+from mongopersist import datamanager, serialize
+
+checker = renormalizing.RENormalizing([
+    (re.compile(r'datetime.datetime(.*)'),
+     'datetime.datetime(2011, 10, 1, 9, 45)'),
+    (re.compile(r"ObjectId\('[0-9a-f]*'\)"),
+     "ObjectId('4e7ddf12e138237403000000')"),
+    (re.compile(r"object at 0x[0-9a-f]*>"),
+     "object at 0x001122>"),
+    ])
+
+OPTIONFLAGS = (doctest.NORMALIZE_WHITESPACE|
+               doctest.ELLIPSIS|
+               doctest.REPORT_ONLY_FIRST_FAILURE
+               #|doctest.REPORT_NDIFF
+               )
+
+def setUp(test):
+    module.setUp(test)
+    test.globs['conn'] = pymongo.Connection('localhost', 27017, tz_aware=False)
+    test.globs['DBNAME'] = 'mongopersist_test'
+    test.globs['conn'].drop_database(test.globs['DBNAME'])
+    test.globs['commit'] = transaction.commit
+    test.globs['dm'] = datamanager.MongoDataManager(
+        test.globs['conn'],
+        default_database=test.globs['DBNAME'],
+        root_database=test.globs['DBNAME'])
+
+def tearDown(test):
+    module.tearDown(test)
+    transaction.abort()
+    test.globs['conn'].drop_database(test.globs['DBNAME'])
+    test.globs['conn'].disconnect()
+    serialize.SERIALIZERS.__init__()


Property changes on: mongopersist/trunk/src/mongopersist/testing.py
___________________________________________________________________
Added: svn:keywords
   + Id

Added: mongopersist/trunk/src/mongopersist/tests/__init__.py
===================================================================
--- mongopersist/trunk/src/mongopersist/tests/__init__.py	                        (rev 0)
+++ mongopersist/trunk/src/mongopersist/tests/__init__.py	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1 @@
+# Make a package.


Property changes on: mongopersist/trunk/src/mongopersist/tests/__init__.py
___________________________________________________________________
Added: svn:keywords
   + Id

Added: mongopersist/trunk/src/mongopersist/tests/test_datamanager.py
===================================================================
--- mongopersist/trunk/src/mongopersist/tests/test_datamanager.py	                        (rev 0)
+++ mongopersist/trunk/src/mongopersist/tests/test_datamanager.py	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,353 @@
+##############################################################################
+#
+# Copyright (c) 2011 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Mongo  Tests"""
+import doctest
+import persistent
+import pprint
+import transaction
+from pymongo import dbref, objectid
+
+from mongopersist import testing, datamanager
+
+class Foo(persistent.Persistent):
+    def __init__(self, name=None):
+        self.name = name
+
+class Bar(persistent.Persistent):
+    _p_mongo_sub_object = True
+
+def doctest_create_conflict_error():
+    r"""create_conflict_error(): General Test
+
+    Simple helper function to create a conflict error.
+
+     >>> foo = Foo()
+     >>> foo._p_serial = '\x00\x00\x00\x00\x00\x00\x00\x01'
+
+     >>> datamanager.create_conflict_error(foo, {'_py_serial': 3})
+     ConflictError: database conflict error
+                    (oid None, class Foo, start serial 1, current serial 3)
+    """
+
+def doctest_Root():
+    r"""Root: General Test
+
+    This class represents the root(s) of the object tree. All roots are stored
+    in a specified collection. Since the rooted object needs to immediately
+    provide a data manager (jar), the operations on the DB root are not art of
+    the transaction mechanism.
+
+      >>> root = datamanager.Root(dm, DBNAME, 'proot')
+
+    Initially the root is empty:
+
+      >>> root.keys()
+      []
+
+    Let's now add an item:
+
+      >>> foo = Foo()
+      >>> root['foo'] = foo
+      >>> root.keys()
+      [u'foo']
+      >>> root['foo'] == foo
+      True
+
+    Root objects can be overridden:
+
+      >>> foo2 = Foo()
+      >>> root['foo'] = foo2
+      >>> root.keys()
+      [u'foo']
+      >>> root['foo'] == foo
+      False
+
+    And of course we can delete an item:
+
+      >>> del root['foo']
+      >>> root.keys()
+      []
+    """
+
+def doctest_MongoDataManager_object_dump_load_reset():
+    r"""MongoDataManager: dump(), load(), reset()
+
+    The Mongo Data Manager is a persistent data manager that manages object
+    states in a Mongo database accross Python transactions.
+
+    There are several arguments to create the data manager, but only the
+    pymongo connection is required:
+
+      >>> dm = datamanager.MongoDataManager(
+      ...     conn,
+      ...     detect_conflicts=True,
+      ...     default_database = DBNAME,
+      ...     root_database = DBNAME,
+      ...     root_collection = 'proot',
+      ...     name_map_collection = 'coll_pypath_map',
+      ...     conflict_error_factory = datamanager.create_conflict_error)
+
+    There are two convenience methods that let you serialize and de-serialize
+    objects explicitly:
+
+      >>> foo = Foo()
+      >>> dm.dump(foo)
+      DBRef('mongopersist.tests.test_datamanager.Foo',
+            ObjectId('4eb2eb7437a08e0156000000'),
+            'mongopersist_test')
+
+    Let's now reset the data manager, so we do not hit a cache while loading
+    the object again:
+
+      >>> dm.reset()
+
+    We can now load the object:
+
+      >>> foo2 = dm.load(foo._p_oid)
+      >>> foo == foo2
+      False
+      >>> foo._p_oid = foo2._p_oid
+    """
+
+def doctest_MongoDataManager_set_state():
+    r"""MongoDataManager: set_state()
+
+    This method loads and sets the state of an object and joins the
+    transaction.
+
+      >>> foo = Foo(u'foo')
+      >>> ref = dm.dump(foo)
+
+      >>> dm.reset()
+      >>> dm._needs_to_join
+      True
+
+      >>> foo2 = Foo()
+      >>> foo2._p_oid = ref
+      >>> dm.setstate(foo2)
+      >>> foo2.name
+      u'foo'
+
+      >>> dm._needs_to_join
+      False
+    """
+
+def doctest_MongoDataManager_oldstate():
+    r"""MongoDataManager: oldstate()
+
+    Loads the state of an object for a given transaction. Since we are not
+    supporting history, this always raises a key error as documented.
+
+      >>> foo = Foo(u'foo')
+      >>> dm.oldstate(foo, '0')
+      Traceback (most recent call last):
+      ...
+      KeyError: '0'
+    """
+
+def doctest_MongoDataManager_register():
+    r"""MongoDataManager: register()
+
+    Registers an object to be stored.
+
+      >>> dm._needs_to_join
+      True
+      >>> len(dm._registered_objects)
+      0
+
+      >>> foo = Foo(u'foo')
+      >>> dm.register(foo)
+
+      >>> dm._needs_to_join
+      False
+      >>> len(dm._registered_objects)
+      1
+
+   But there are no duplicates:
+
+      >>> dm.register(foo)
+      >>> len(dm._registered_objects)
+      1
+    """
+
+def doctest_MongoDataManager_abort():
+    r"""MongoDataManager: abort()
+
+    Aborts a transaction, which clears all object and transaction registrations:
+
+      >>> dm._registered_objects = [Foo()]
+      >>> dm._needs_to_join = False
+
+      >>> dm.abort(transaction.get())
+
+      >>> dm._needs_to_join
+      True
+      >>> len(dm._registered_objects)
+      0
+    """
+
+def doctest_MongoDataManager_commit():
+    r"""MongoDataManager: commit()
+
+    Contrary to what the name suggests, this is the commit called during the
+    first phase of a two-phase commit. Thus, for all practically purposes,
+    this method merely checks whether the commit would potentially fail.
+
+    This means, if conflict detection is disabled, this method does nothing.
+
+      >>> dm.detect_conflicts
+      False
+      >>> dm.commit(transaction.get())
+
+    Let's now turn on conflict detection:
+
+      >>> dm.detect_conflicts = True
+
+    For new objects (not having an oid), it always passes:
+
+      >>> dm.reset()
+      >>> dm._registered_objects = [Foo()]
+      >>> dm.commit(transaction.get())
+
+    If the object has an oid, but is not found in the DB, we also just pass,
+    because the object will be inserted.
+
+      >>> foo = Foo()
+      >>> foo._p_oid =  dbref.DBRef(
+      ...     'mongopersist.tests.test_datamanager.Foo',
+      ...     objectid.ObjectId('4eb2eb7437a08e0156000000'),
+      ...     'mongopersist_test')
+
+      >>> dm.reset()
+      >>> dm._registered_objects = [foo]
+      >>> dm.commit(transaction.get())
+
+    Let's now store an object and make sure it does not conflict:
+
+      >>> foo = Foo()
+      >>> ref = dm.dump(foo)
+      >>> ref
+      DBRef('mongopersist.tests.test_datamanager.Foo',
+            ObjectId('4eb3468037a08e1b74000000'),
+            'mongopersist_test')
+
+      >>> dm.reset()
+      >>> dm._registered_objects = [foo]
+      >>> dm.commit(transaction.get())
+
+    Next, let's cause a conflict byt simulating a conflicting transaction:
+
+      >>> dm.reset()
+      >>> foo2 = dm.load(ref)
+      >>> foo2.name = 'foo2'
+      >>> transaction.commit()
+
+      >>> dm.reset()
+      >>> dm._registered_objects = [foo]
+      >>> dm.commit(transaction.get())
+      Traceback (most recent call last):
+      ...
+      ConflictError: database conflict error
+          (oid DBRef('mongopersist.tests.test_datamanager.Foo',
+                     ObjectId('4eb3499637a08e1c5a000000'),
+                     'mongopersist_test'),
+           class Foo, start serial 1, current serial 2)
+    """
+
+def doctest_MongoDataManager_tpc_begin():
+    r"""MongoDataManager: tpc_begin()
+
+    This is a non-op for the mongo data manager.
+
+      >>> dm.tpc_begin(transaction.get())
+    """
+
+def doctest_MongoDataManager_tpc_vote():
+    r"""MongoDataManager: tpc_vote()
+
+    This is a non-op for the mongo data manager.
+
+      >>> dm.tpc_vote(transaction.get())
+    """
+
+def doctest_MongoDataManager_tpc_finish():
+    r"""MongoDataManager: tpc_finish()
+
+    This method finishes the two-phase commit. So let's store a simple object:
+
+      >>> foo = Foo()
+      >>> dm.detect_conflicts = True
+      >>> dm._registered_objects = [foo]
+      >>> dm.tpc_finish(transaction.get())
+      >>> foo._p_serial
+      '\x00\x00\x00\x00\x00\x00\x00\x01'
+
+    Note that objects cannot be stored twice in the same transation:
+
+      >>> dm.reset()
+      >>> dm._registered_objects = [foo, foo]
+      >>> dm.tpc_finish(transaction.get())
+      >>> foo._p_serial
+      '\x00\x00\x00\x00\x00\x00\x00\x02'
+
+    Also, when a persistent sub-object is stored that does not want its own
+    document, then its parent is stored instead, still avoiding dual storage.
+
+      >>> dm.reset()
+      >>> foo2 = dm.load(foo._p_oid)
+      >>> foo2.bar = Bar()
+
+      >>> dm.tpc_finish(transaction.get())
+      >>> foo2._p_serial
+      '\x00\x00\x00\x00\x00\x00\x00\x03'
+
+      >>> dm.reset()
+      >>> foo3 = dm.load(foo._p_oid)
+      >>> dm._registered_objects = [foo3.bar, foo3]
+      >>> dm.tpc_finish(transaction.get())
+      >>> foo3._p_serial
+      '\x00\x00\x00\x00\x00\x00\x00\x04'
+
+    """
+
+def doctest_MongoDataManager_tpc_abort():
+    r"""MongoDataManager: tpc_abort()
+
+    Aborts a two-phase commit. This is simply the same as the regular abort.
+
+      >>> dm._registered_objects = [Foo()]
+      >>> dm._needs_to_join = False
+
+      >>> dm.tpc_abort(transaction.get())
+
+      >>> dm._needs_to_join
+      True
+      >>> len(dm._registered_objects)
+      0
+    """
+
+def doctest_MongoDataManager_sortKey():
+    r"""MongoDataManager: sortKey()
+
+    The data manager's sort key is trivial.
+
+      >>> dm.sortKey()
+      ('MongoDataManager', 0)
+    """
+
+def test_suite():
+    return doctest.DocTestSuite(
+        setUp=testing.setUp, tearDown=testing.tearDown,
+        checker=testing.checker,
+        optionflags=testing.OPTIONFLAGS)


Property changes on: mongopersist/trunk/src/mongopersist/tests/test_datamanager.py
___________________________________________________________________
Added: svn:keywords
   + Id

Added: mongopersist/trunk/src/mongopersist/tests/test_doc.py
===================================================================
--- mongopersist/trunk/src/mongopersist/tests/test_doc.py	                        (rev 0)
+++ mongopersist/trunk/src/mongopersist/tests/test_doc.py	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,25 @@
+##############################################################################
+#
+# Copyright (c) 2011 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Mongo Persistence Doc Tests"""
+import doctest
+
+from mongopersist import testing
+
+def test_suite():
+    return doctest.DocFileSuite(
+        '../README.txt',
+        setUp=testing.setUp, tearDown=testing.tearDown,
+        checker=testing.checker,
+        optionflags=testing.OPTIONFLAGS
+        )


Property changes on: mongopersist/trunk/src/mongopersist/tests/test_doc.py
___________________________________________________________________
Added: svn:keywords
   + Id

Added: mongopersist/trunk/src/mongopersist/tests/test_mapping.py
===================================================================
--- mongopersist/trunk/src/mongopersist/tests/test_mapping.py	                        (rev 0)
+++ mongopersist/trunk/src/mongopersist/tests/test_mapping.py	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,128 @@
+##############################################################################
+#
+# Copyright (c) 2011 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Mongo  Tests"""
+import doctest
+import persistent
+import pprint
+import transaction
+from pymongo import dbref, objectid
+
+from mongopersist import testing, mapping
+
+class Item(persistent.Persistent):
+    def __init__(self, name=None, site=None):
+        self.name = name
+        self.site = site
+
+def doctest_MongoCollectionMapping_simple():
+    r"""MongoCollectionMapping: simple
+
+    The Mongo Collection Mapping provides a Python dict interface for a mongo
+    collection. Here is a simple example for our Item class/collection:
+
+      >>> class SimpleContainer(mapping.MongoCollectionMapping):
+      ...     __mongo_collection__ = 'mongopersist.tests.test_mapping.Item'
+      ...     __mongo_mapping_key__ = 'name'
+
+    To initialize the mapping, we need a data manager:
+
+      >>> container = SimpleContainer(dm)
+
+    Let's do some obvious initial manipulations:
+
+      >>> container['one'] = one = Item()
+      >>> one.name
+      'one'
+      >>> transaction.commit()
+
+    After the transaction is committed, we can access the item:
+
+      >>> container.keys()
+      [u'one']
+      >>> container['one'].name
+      u'one'
+
+      >>> container['two']
+      Traceback (most recent call last):
+      ...
+      KeyError: 'two'
+
+    Of course we can delete an item, but note that it only removes the name,
+    but does not delete the document by default:
+
+      >>> del container['one']
+      >>> transaction.commit()
+      >>> container.keys()
+      []
+
+    Finally, you can always get to the collection that the mapping is
+    managing:
+
+      >>> container.get_mongo_collection()
+      Collection(Database(Connection('localhost', 27017),
+                          u'mongopersist_test'),
+                          u'mongopersist.tests.test_mapping.Item')
+    """
+
+def doctest_MongoCollectionMapping_filter():
+    r"""MongoCollectionMapping: filter
+
+    It is often desirable to manage multiple mappings for the same type of
+    object and thus same collection. The mongo mapping thus supports filtering
+    for all its functions.
+
+      >>> class SiteContainer(mapping.MongoCollectionMapping):
+      ...     __mongo_collection__ = 'mongopersist.tests.test_mapping.Item'
+      ...     __mongo_mapping_key__ = 'name'
+      ...     def __init__(self, jar, site):
+      ...         super(SiteContainer, self).__init__(jar)
+      ...         self.site = site
+      ...     def __mongo_filter__(self):
+      ...         return {'site': self.site}
+
+      >>> container1 = SiteContainer(dm, 'site1')
+      >>> container2 = SiteContainer(dm, 'site2')
+
+    Let's now add some items:
+
+      >>> ref11 = dm.dump(Item('1-1', 'site1'))
+      >>> ref12 = dm.dump(Item('1-2', 'site1'))
+      >>> ref13 = dm.dump(Item('1-3', 'site1'))
+      >>> ref21 = dm.dump(Item('2-1', 'site2'))
+
+    And accessing the items works as expected:
+
+      >>> dm.reset()
+      >>> container1.keys()
+      [u'1-1', u'1-2', u'1-3']
+      >>> container1['1-1'].name
+      u'1-1'
+      >>> container1['2-1']
+      Traceback (most recent call last):
+      ...
+      KeyError: '2-1'
+
+      >>> container2.keys()
+      [u'2-1']
+
+    Note: The mutator methods (``__setitem__`` and ``__delitem__``) do nto
+    take the filter into account by default. They need to be extended to
+    properly setup and tear down the filter criteria.
+    """
+
+def test_suite():
+    return doctest.DocTestSuite(
+        setUp=testing.setUp, tearDown=testing.tearDown,
+        checker=testing.checker,
+        optionflags=testing.OPTIONFLAGS)


Property changes on: mongopersist/trunk/src/mongopersist/tests/test_mapping.py
___________________________________________________________________
Added: svn:keywords
   + Id

Added: mongopersist/trunk/src/mongopersist/tests/test_serialize.py
===================================================================
--- mongopersist/trunk/src/mongopersist/tests/test_serialize.py	                        (rev 0)
+++ mongopersist/trunk/src/mongopersist/tests/test_serialize.py	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,720 @@
+##############################################################################
+#
+# Copyright (c) 2011 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Mongo Persistence Serializeation Tests"""
+import datetime
+import doctest
+import persistent
+import pprint
+
+from pymongo import binary, dbref, objectid
+
+from mongopersist import testing, serialize
+
+class Top(persistent.Persistent):
+    _p_mongo_collection = 'Top'
+
+def create_top(name):
+    top = Top()
+    top.name = name
+    return top
+
+class Top2(Top):
+    pass
+
+class Tier2(persistent.Persistent):
+    _p_mongo_sub_object = True
+
+class Foo(persistent.Persistent):
+    _p_mongo_collection = 'Foo'
+
+class Anything(persistent.Persistent):
+    pass
+
+class Simple(object):
+    pass
+
+def doctest_ObjectSerializer():
+    """Test the abstract ObjectSerializer class.
+
+    Object serializers are hooks into the serialization process to allow
+    better serialization for particular objects. For example, the result of
+    reducing a datetime.date object is a short, optimized binary string. This
+    representation might be optimal for pickles, but is really aweful for
+    Mongo, since it does not allow querying for dates. An object serializer
+    can be used to use a better representation, such as the date ordinal
+    number.
+
+      >>> os = serialize.ObjectSerializer()
+
+    So here are the methods that must be implemented by an object serializer:
+
+      >>> os.can_read({})
+      Traceback (most recent call last):
+      ...
+      NotImplementedError
+
+      >>> os.read({})
+      Traceback (most recent call last):
+      ...
+      NotImplementedError
+
+      >>> os.can_write(object())
+      Traceback (most recent call last):
+      ...
+      NotImplementedError
+
+      >>> os.write(object())
+      Traceback (most recent call last):
+      ...
+      NotImplementedError
+    """
+
+def doctest_ObjectWriter_get_collection_name():
+    """ObjectWriter: get_collection_name()
+
+    This method determines the collection name and database for a given
+    object. It can either be specified via '_p_mongo_collection' or is
+    determined from the class path. When the collection name is specified, the
+    mapping from collection name to class path is stored.
+
+      >>> print tuple(conn[DBNAME][dm.name_map_collection].find())
+      ()
+
+      >>> writer = serialize.ObjectWriter(dm)
+      >>> writer.get_collection_name(Anything())
+      ('mongopersist_test', 'mongopersist.tests.test_serialize.Anything')
+
+      >>> top = Top()
+      >>> writer.get_collection_name(top)
+      ('mongopersist_test', 'Top')
+
+      >>> print tuple(conn[DBNAME][dm.name_map_collection].find())
+      ({u'path': u'mongopersist.tests.test_serialize.Top',
+        u'doc_has_type': False,
+        u'_id': ObjectId('4eb19f9937a08e27b7000000'),
+        u'collection': u'Top',
+        u'database': u'mongopersist_test'},)
+
+      >>> getattr(top, '_p_mongo_store_type', None)
+
+    When classes use inheritance, it often happens that all sub-objects share
+    the same collection. However, only one can have an entry in our mapping
+    table to avoid non-unique answers. Thus we require all sub-types after the
+    first one to store their typing providing a hint for deseriealization:
+
+      >>> top2 = Top2()
+      >>> writer.get_collection_name(top2)
+      ('mongopersist_test', 'Top')
+
+      >>> pprint.pprint(tuple(conn[DBNAME][dm.name_map_collection].find()))
+      ({u'_id': ObjectId('4eb1b5ab37a08e2f06000000'),
+        u'collection': u'Top',
+        u'database': u'mongopersist_test',
+        u'doc_has_type': False,
+        u'path': u'mongopersist.tests.test_serialize.Top'},
+       {u'_id': ObjectId('4eb1b5ab37a08e2f06000001'),
+        u'collection': u'Top',
+        u'database': u'mongopersist_test',
+        u'doc_has_type': True,
+        u'path': u'mongopersist.tests.test_serialize.Top2'})
+
+      >>> getattr(top2, '_p_mongo_store_type', None)
+      True
+    """
+
+def doctest_ObjectWriter_get_non_persistent_state():
+    r"""ObjectWriter: get_non_persistent_state()
+
+    This method produces a proper reduced state for custom, non-persistent
+    objects.
+
+      >>> writer = serialize.ObjectWriter(dm)
+
+    A simple new-style class:
+
+      >>> class This(object):
+      ...     def __init__(self, num):
+      ...         self.num = num
+
+      >>> this = This(1)
+      >>> writer.get_non_persistent_state(this, [])
+      {'num': 1, '_py_type': '__main__.This'}
+
+    A simple old-style class:
+
+      >>> class That(object):
+      ...     def __init__(self, num):
+      ...         self.num = num
+
+      >>> that = That(1)
+      >>> writer.get_non_persistent_state(that, [])
+      {'num': 1, '_py_type': '__main__.That'}
+
+    The method also handles persistent classes that do not want their own
+    document:
+
+      >>> top = Top()
+      >>> writer.get_non_persistent_state(top, [])
+      {'_py_persistent_type': 'mongopersist.tests.test_serialize.Top'}
+
+    And then there are the really weird cases:
+
+      >>> writer.get_non_persistent_state(datetime.date(2011, 11, 1), [])
+      {'_py_factory': 'datetime.date',
+       '_py_factory_args': [Binary('\x07\xdb\x0b\x01', 0)]}
+
+    Circular object references cause an error:
+
+      >>> writer.get_non_persistent_state(this, [this])
+      Traceback (most recent call last):
+      ...
+      CircularReferenceError: <__main__.This object at 0x3051550>
+    """
+
+def doctest_ObjectWriter_get_persistent_state():
+    r"""ObjectWriter: get_persistent_state()
+
+    This method produces a proper reduced state for a persistent object, which
+    is basically a Mongo DBRef.
+
+      >>> writer = serialize.ObjectWriter(dm)
+
+      >>> foo = Foo()
+      >>> foo._p_oid
+      >>> list(conn[DBNAME]['Foo'].find())
+      []
+
+      >>> writer.get_persistent_state(foo, [])
+      DBRef('Foo', ObjectId('4eb1a87f37a08e29ff000002'), 'mongopersist_test')
+
+      >>> foo._p_oid
+      DBRef('Foo', ObjectId('4eb1a87f37a08e29ff000002'), 'mongopersist_test')
+      >>> pprint.pprint(list(conn[DBNAME]['Foo'].find()))
+      [{u'_id': ObjectId('4eb1a96c37a08e2a7b000002')}]
+
+    The next time the object simply returns its reference:
+
+      >>> writer.get_persistent_state(foo, [])
+      DBRef('Foo', ObjectId('4eb1a87f37a08e29ff000002'), 'mongopersist_test')
+      >>> pprint.pprint(list(conn[DBNAME]['Foo'].find()))
+      [{u'_id': ObjectId('4eb1a96c37a08e2a7b000002')}]
+    """
+
+
+def doctest_ObjectWriter_get_state_MONGO_NATIVE_TYPES():
+    """ObjectWriter: get_state(): Mongo-native Types
+
+      >>> writer = serialize.ObjectWriter(None)
+      >>> writer.get_state(1)
+      1
+      >>> writer.get_state(1.0)
+      1.0
+      >>> writer.get_state(u'Test')
+      u'Test'
+      >>> writer.get_state(datetime.datetime(2011, 11, 1, 12, 0, 0))
+      datetime.datetime(2011, 11, 1, 12, 0, 0)
+      >>> print writer.get_state(None)
+      None
+      >>> writer.get_state(objectid.ObjectId('4e7ddf12e138237403000000'))
+      ObjectId('4e7ddf12e138237403000000')
+      >>> writer.get_state(dbref.DBRef('4e7ddf12e138237403000000', 'test'))
+      DBRef('4e7ddf12e138237403000000', 'test')
+    """
+
+def doctest_ObjectWriter_get_state_types():
+    """ObjectWriter: get_state(): types (type, class)
+
+      >>> writer = serialize.ObjectWriter(None)
+      >>> writer.get_state(Top)
+      {'path': 'mongopersist.tests.test_serialize.Top', '_py_type': 'type'}
+      >>> writer.get_state(str)
+      {'path': '__builtin__.str', '_py_type': 'type'}
+    """
+
+def doctest_ObjectWriter_get_state_sequences():
+    """ObjectWriter: get_state(): sequences (tuple, list, PersistentList)
+
+    We convert any sequence into a simple list, since Mongo supports that
+    type natively. But also reduce any sub-objects.
+
+      >>> class Number(object):
+      ...     def __init__(self, num):
+      ...         self.num = num
+
+      >>> writer = serialize.ObjectWriter(None)
+      >>> writer.get_state((1, '2', Number(3)))
+      [1, '2', {'num': 3, '_py_type': '__main__.Number'}]
+      >>> writer.get_state([1, '2', Number(3)])
+      [1, '2', {'num': 3, '_py_type': '__main__.Number'}]
+    """
+
+def doctest_ObjectWriter_get_state_mappings():
+    """ObjectWriter: get_state(): mappings (dict, PersistentDict)
+
+    We convert any mapping into a simple dict, since Mongo supports that
+    type natively. But also reduce any sub-objects.
+
+      >>> class Number(object):
+      ...     def __init__(self, num):
+      ...         self.num = num
+
+      >>> writer = serialize.ObjectWriter(None)
+      >>> writer.get_state({'1': 1, '2': '2', '3': Number(3)})
+      {'1': 1, '3': {'num': 3, '_py_type': '__main__.Number'}, '2': '2'}
+
+    Unfortunately, Mongo only supports text keys. So whenever we have non-text
+    keys, we need to create a less natural, but consistent structure:
+
+      >>> writer.get_state({1: 'one', 2: 'two', 3: 'three'})
+      {'dict_data': [(1, 'one'), (2, 'two'), (3, 'three')]}
+    """
+
+def doctest_ObjectWriter_get_state_Persistent():
+    """ObjectWriter: get_state(): Persistent objects
+
+      >>> writer = serialize.ObjectWriter(dm)
+
+      >>> top = Top()
+      >>> writer.get_state(top)
+      DBRef('Top', ObjectId('4eb1aede37a08e2c8d000004'), 'mongopersist_test')
+
+    But a persistent object can declare that it does not want a separate
+    document:
+
+      >>> top2 = Top()
+      >>> top2._p_mongo_sub_object = True
+      >>> writer.get_state(top2)
+      {'_py_persistent_type': 'mongopersist.tests.test_serialize.Top'}
+    """
+
+def doctest_ObjectWriter_store():
+    """ObjectWriter: store()
+
+      >>> writer = serialize.ObjectWriter(dm)
+
+    Simply store an object:
+
+      >>> pprint.pprint(list(conn[DBNAME]['Top'].find()))
+      []
+
+      >>> top = Top()
+      >>> writer.store(top)
+      DBRef('Top', ObjectId('4eb1b16537a08e2d1a000001'), 'mongopersist_test')
+      >>> pprint.pprint(list(conn[DBNAME]['Top'].find()))
+      [{u'_id': ObjectId('4eb1b17937a08e2d29000001')}]
+
+    Now that we have an object, storing an object simply means updating the
+    existing document:
+
+      >>> top.name = 'top'
+      >>> writer.store(top)
+      DBRef('Top', ObjectId('4eb1b16537a08e2d1a000001'), 'mongopersist_test')
+      >>> pprint.pprint(list(conn[DBNAME]['Top'].find()))
+      [{u'_id': ObjectId('4eb1b17937a08e2d29000001'), u'name': u'top'}]
+
+    """
+
+def doctest_ObjectWriter_store_with_mongo_store_type():
+    """ObjectWriter: store(): _p_mongo_store_type = True
+
+      >>> writer = serialize.ObjectWriter(dm)
+
+      >>> top = Top()
+      >>> top._p_mongo_store_type = True
+      >>> writer.store(top)
+      DBRef('Top', ObjectId('4eb1b16537a08e2d1a000001'), 'mongopersist_test')
+      >>> pprint.pprint(list(conn[DBNAME]['Top'].find()))
+      [{u'_id': ObjectId('4eb1b27437a08e2d7d000003'),
+        u'_py_persistent_type': u'mongopersist.tests.test_serialize.Top'}]
+    """
+
+def doctest_ObjectWriter_store_with_conflict_detection():
+    """ObjectWriter: store(): conflict detection
+
+    The writer supports the data manager's conflict detection by storing a
+    serial number, which is effectively the version of the object. The data
+    manager can then use the serial to detect whether a competing transaction
+    has written to the document.
+
+      >>> dm.detect_conflicts = True
+      >>> writer = serialize.ObjectWriter(dm)
+
+      >>> top = Top()
+      >>> writer.store(top)
+      DBRef('Top', ObjectId('4eb1b16537a08e2d1a000001'), 'mongopersist_test')
+      >>> pprint.pprint(list(conn[DBNAME]['Top'].find()))
+      [{u'_id': ObjectId('4eb1b31137a08e2d9d000003'), u'_py_serial': 1}]
+    """
+
+def doctest_ObjectWriter_store_with_new_object_references():
+    """ObjectWriter: store(): new object references
+
+    When two new objects reference each other, extracting the full state would
+    cause infinite recursion errors. The code protects against that by
+    optionally only creating an initial empty reference document.
+
+      >>> writer = serialize.ObjectWriter(dm)
+
+      >>> top = Top()
+      >>> top.foo = Foo()
+      >>> top.foo.top = top
+      >>> writer.store(top)
+      DBRef('Top', ObjectId('4eb1b16537a08e2d1a000001'), 'mongopersist_test')
+      >>> pprint.pprint(list(conn[DBNAME]['Top'].find()))
+      [{u'_id': ObjectId('4eb1b3d337a08e2de7000009'),
+        u'foo': DBRef(u'Foo', ObjectId('4eb1b3d337a08e2de7000008'),
+                      u'mongopersist_test')}]
+    """
+
+def doctest_ObjectReader_simple_resolve():
+    """ObjectReader: simple_resolve()
+
+    This methods simply resolves a Python path to the represented object.
+
+      >>> reader = serialize.ObjectReader(dm)
+      >>> reader.simple_resolve('mongopersist.tests.test_serialize.Top')
+      <class 'mongopersist.tests.test_serialize.Top'>
+    """
+
+def doctest_ObjectReader_resolve_simple():
+    """ObjectReader: resolve(): simple
+
+    This methods resolves a collection name to its class. The collection name
+    can be either any arbitrary string or a Python path.
+
+      >>> reader = serialize.ObjectReader(dm)
+      >>> ref = dbref.DBRef('mongopersist.tests.test_serialize.Top',
+      ...                   '4eb1b3d337a08e2de7000100')
+      >>> reader.resolve(ref)
+      <class 'mongopersist.tests.test_serialize.Top'>
+    """
+
+def doctest_ObjectReader_resolve_lookup():
+    """ObjectReader: resolve(): lookup
+
+    If Python path resolution fails, we try to lookup the path from the
+    collection mapping collection names to Python paths.
+
+      >>> reader = serialize.ObjectReader(dm)
+      >>> ref = dbref.DBRef('Top', '4eb1b3d337a08e2de7000100', DBNAME)
+      >>> reader.resolve(ref)
+      Traceback (most recent call last):
+      ...
+      ImportError: DBRef('Top', '4eb1b3d337a08e2de7000100', 'mongopersist_test')
+
+    The lookup failed, because there is no map entry yet for the 'Top'
+    collection. The easiest way to create one is with the object writer:
+
+      >>> top = Top()
+      >>> writer = serialize.ObjectWriter(dm)
+      >>> writer.get_collection_name(top)
+      ('mongopersist_test', 'Top')
+
+      >>> reader.resolve(ref)
+      <class 'mongopersist.tests.test_serialize.Top'>
+    """
+
+def doctest_ObjectReader_resolve_lookup_with_multiple_maps():
+    """ObjectReader: resolve(): lookup with multiple maps entries
+
+    When the collection name to Python path map has multiple entries, things
+    are more interesting. In this case, we need to lookup the object, if it
+    stores its persistent type otherwise we use the first map entry.
+
+      >>> writer = serialize.ObjectWriter(dm)
+      >>> top = Top()
+      >>> writer.store(top)
+      DBRef('Top', ObjectId('4eb1e0f237a08e38dd000002'), 'mongopersist_test')
+      >>> top2 = Top2()
+      >>> writer.store(top2)
+      DBRef('Top', ObjectId('4eb1e10437a08e38e8000004'), 'mongopersist_test')
+
+      >>> reader = serialize.ObjectReader(dm)
+      >>> reader.resolve(top._p_oid)
+      <class 'mongopersist.tests.test_serialize.Top'>
+      >>> reader.resolve(top2._p_oid)
+      <class 'mongopersist.tests.test_serialize.Top2'>
+
+      >>> pprint.pprint(list(conn[DBNAME]['Top'].find()))
+      [{u'_id': ObjectId('4eb1e13337a08e392d000002')},
+       {u'_id': ObjectId('4eb1e13337a08e392d000004'),
+        u'_py_persistent_type': u'mongopersist.tests.test_serialize.Top2'}]
+
+    If the DBRef does not have an object id, then an import error is raised:
+
+      >>> reader.resolve(dbref.DBRef('Top', None, 'mongopersist_test'))
+      Traceback (most recent call last):
+      ...
+      ImportError: DBRef('Top', None, 'mongopersist_test')
+    """
+
+def doctest_ObjectReader_get_non_persistent_object_py_type():
+    """ObjectReader: get_non_persistent_object(): _py_type
+
+    The simplest case is a document with a _py_type:
+
+      >>> reader = serialize.ObjectReader(dm)
+      >>> reader.get_non_persistent_object(
+      ...    {'_py_type': 'mongopersist.tests.test_serialize.Simple'}, None)
+      <mongopersist.tests.test_serialize.Simple object at 0x306f410>
+
+    It is a little bit more interesting when there is some additional state:
+
+      >>> simple = reader.get_non_persistent_object(
+      ...    {u'_py_type': 'mongopersist.tests.test_serialize.Simple',
+      ...     u'name': u'Here'},
+      ...    None)
+      >>> simple.name
+      u'Here'
+    """
+
+def doctest_ObjectReader_get_non_persistent_object_py_persistent_type():
+    """ObjectReader: get_non_persistent_object(): _py_persistent_type
+
+    In this case the document has a _py_persistent_type attribute, which
+    signals a persistent object living in its parent's document:
+
+      >>> top = Top()
+
+      >>> reader = serialize.ObjectReader(dm)
+      >>> tier2 = reader.get_non_persistent_object(
+      ...    {'_py_persistent_type': 'mongopersist.tests.test_serialize.Tier2',
+      ...     'name': 'Number 2'},
+      ...    top)
+      >>> tier2
+      <mongopersist.tests.test_serialize.Tier2 object at 0x306f410>
+
+    We keep track of the containing object, so we can set _p_changed when this
+    object changes.
+
+      >>> tier2._p_mongo_doc_object
+      <mongopersist.tests.test_serialize.Top object at 0x7fa30b534050>
+      >>> tier2._p_jar
+      <mongopersist.datamanager.MongoDataManager object at 0x7fc3cab375d0>
+    """
+
+def doctest_ObjectReader_get_non_persistent_object_py_factory():
+    """ObjectReader: get_non_persistent_object(): _py_factory
+
+    This is the case of last resort. Specify a factory and its arguments:
+
+      >>> reader = serialize.ObjectReader(dm)
+      >>> top = reader.get_non_persistent_object(
+      ...    {'_py_factory': 'mongopersist.tests.test_serialize.create_top',
+      ...     '_py_factory_args': ('TOP',)},
+      ...    None)
+      >>> top
+      <mongopersist.tests.test_serialize.Top object at 0x306f410>
+      >>> top.name
+      'TOP'
+    """
+
+def doctest_ObjectReader_get_object_ObjectId():
+    """ObjectReader: get_object(): ObjectId
+
+    The object id is special and we simply conserve it:
+
+      >>> reader = serialize.ObjectReader(dm)
+      >>> reader.get_object(
+      ...     objectid.ObjectId('4e827608e13823598d000003'), None)
+      ObjectId('4e827608e13823598d000003')
+    """
+
+def doctest_ObjectReader_get_object_binary():
+    """ObjectReader: get_object(): binary data
+
+    Binary data is just converted to a string:
+
+      >>> reader = serialize.ObjectReader(dm)
+      >>> reader.get_object(binary.Binary('hello'), None)
+      'hello'
+    """
+
+def doctest_ObjectReader_get_object_dbref():
+    """ObjectReader: get_object(): DBRef
+
+      >>> writer = serialize.ObjectWriter(dm)
+      >>> top = Top()
+      >>> writer.store(top)
+      DBRef('Top', ObjectId('4eb1e0f237a08e38dd000002'), 'mongopersist_test')
+
+    Database references load the ghost state of the obejct they represent:
+
+      >>> reader = serialize.ObjectReader(dm)
+      >>> reader.get_object(top._p_oid, None)
+      <mongopersist.tests.test_serialize.Top object at 0x2801938>
+    """
+
+def doctest_ObjectReader_get_object_type_ref():
+    """ObjectReader: get_object(): type reference
+
+    Type references are resolved.
+
+      >>> reader = serialize.ObjectReader(dm)
+      >>> reader.get_object(
+      ...     {'_py_type': 'type',
+      ...      'path': 'mongopersist.tests.test_serialize.Simple'},
+      ...     None)
+      <class 'mongopersist.tests.test_serialize.Simple'>
+    """
+
+def doctest_ObjectReader_get_object_instance():
+    """ObjectReader: get_object(): instance
+
+    Instances are completely loaded:
+
+      >>> reader = serialize.ObjectReader(dm)
+      >>> simple = reader.get_object(
+      ...     {u'_py_type': 'mongopersist.tests.test_serialize.Simple',
+      ...      u'name': u'easy'},
+      ...     None)
+      >>> simple
+      <mongopersist.tests.test_serialize.Simple object at 0x2bcc950>
+      >>> simple.name
+      u'easy'
+    """
+
+def doctest_ObjectReader_get_object_sequence():
+    """ObjectReader: get_object(): sequence
+
+    Sequences become persistent lists with all obejcts deserialized.
+
+      >>> reader = serialize.ObjectReader(dm)
+      >>> reader.get_object([1, '2', 3.0], None)
+      [1, '2', 3.0]
+    """
+
+def doctest_ObjectReader_get_object_mapping():
+    """ObjectReader: get_object(): mapping
+
+    Mappings become persistent dicts with all obejcts deserialized.
+
+      >>> reader = serialize.ObjectReader(dm)
+      >>> pprint.pprint(reader.get_object({'1': 1, '2': 2, '3': 3}, None))
+      {'1': 1, '3': 3, '2': 2}
+
+    Since Mongo does not allow for non-string keys, the state for a dict with
+    non-string keys looks different:
+
+      >>> pprint.pprint(reader.get_object(
+      ...     {'dict_data': [(1, '1'), (2, '2'), (3, '3')]},
+      ...     None))
+      {1: '1', 2: '2', 3: '3'}
+    """
+
+def doctest_ObjectReader_get_ghost():
+    """ObjectReader: get_ghost()
+
+      >>> writer = serialize.ObjectWriter(dm)
+      >>> top = Top()
+      >>> writer.store(top)
+      DBRef('Top', ObjectId('4eb1e0f237a08e38dd000002'), 'mongopersist_test')
+
+    The ghost object is a shell without any loaded object state:
+
+      >>> reader = serialize.ObjectReader(dm)
+      >>> gobj = reader.get_ghost(top._p_oid)
+      >>> gobj._p_jar
+      <mongopersist.datamanager.MongoDataManager object at 0x2720e50>
+      >>> gobj._p_state
+      0
+
+    The second time we look up the object, it comes from cache:
+
+      >>> gobj = reader.get_ghost(top._p_oid)
+      >>> gobj._p_state
+      0
+    """
+
+def doctest_ObjectReader_set_ghost_state():
+    r"""ObjectReader: set_ghost_state()
+
+      >>> dm.detect_conflicts = True
+
+      >>> writer = serialize.ObjectWriter(dm)
+      >>> top = Top()
+      >>> top.name = 'top'
+      >>> writer.store(top)
+      DBRef('Top', ObjectId('4eb1e0f237a08e38dd000002'), 'mongopersist_test')
+
+    The ghost object is a shell without any loaded object state:
+
+      >>> reader = serialize.ObjectReader(dm)
+      >>> gobj = reader.get_ghost(top._p_oid)
+      >>> gobj._p_jar
+      <mongopersist.datamanager.MongoDataManager object at 0x2720e50>
+      >>> gobj._p_state
+      0
+
+    Now load the state:
+
+      >>> reader.set_ghost_state(gobj)
+      >>> gobj.name
+      u'top'
+      >>> gobj._p_serial
+      '\x00\x00\x00\x00\x00\x00\x00\x01'
+    """
+
+
+
+def doctest_deserialize_persistent_references():
+    """Deserialization o persistent references.
+
+    The purpose of this test is to demonstrate the proper deserialization of
+    persistent object references.
+
+    Let's create a simple object hierarchy:
+
+      >>> top = Top()
+      >>> top.name = 'top'
+      >>> top.foo = Foo()
+      >>> top.foo.name = 'foo'
+
+      >>> dm.root['top'] = top
+      >>> commit()
+
+    Let's check that the objects were properly serialized.
+
+      >>> pprint.pprint(list(conn[DBNAME]['Top'].find()))
+      [{u'_id': ObjectId('4e827608e13823598d000003'),
+        u'foo': DBRef(u'Foo',
+                      ObjectId('4e827608e13823598d000002'),
+                      u'mongopersist_test'),
+        u'name': u'top'}]
+      >>> pprint.pprint(list(conn[DBNAME]['Foo'].find()))
+      [{u'_id': ObjectId('4e8276c3e138235a2e000002'), u'name': u'foo'}]
+
+    Now we access the objects objects again to see whether they got properly
+    deserialized.
+
+      >>> top2 = dm.root['top']
+      >>> id(top2) == id(top)
+      False
+      >>> top2.name
+      u'top'
+
+      >>> id(top2.foo) == id(top.foo)
+      False
+      >>> top2.foo
+      <mongopersist.tests.test_serialize.Foo object at 0x7fb1a0c0b668>
+      >>> top2.foo.name
+      u'foo'
+    """
+
+
+def test_suite():
+    return doctest.DocTestSuite(
+        setUp=testing.setUp, tearDown=testing.tearDown,
+        checker=testing.checker,
+        optionflags=testing.OPTIONFLAGS)


Property changes on: mongopersist/trunk/src/mongopersist/tests/test_serialize.py
___________________________________________________________________
Added: svn:keywords
   + Id

Added: mongopersist/trunk/src/mongopersist/zope/__init__.py
===================================================================
--- mongopersist/trunk/src/mongopersist/zope/__init__.py	                        (rev 0)
+++ mongopersist/trunk/src/mongopersist/zope/__init__.py	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1 @@
+# Make a package.


Property changes on: mongopersist/trunk/src/mongopersist/zope/__init__.py
___________________________________________________________________
Added: svn:keywords
   + Id

Added: mongopersist/trunk/src/mongopersist/zope/container.py
===================================================================
--- mongopersist/trunk/src/mongopersist/zope/container.py	                        (rev 0)
+++ mongopersist/trunk/src/mongopersist/zope/container.py	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,249 @@
+##############################################################################
+#
+# Copyright (c) 2011 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Mongo Persistence Zope Containers"""
+import UserDict
+import persistent
+import pymongo.dbref
+import zope.component
+from rwproperty import getproperty, setproperty
+from zope.container import contained, sample
+
+from mongopersist import interfaces, serialize
+
+
+class MongoContained(contained.Contained):
+
+    @getproperty
+    def __name__(self):
+        return getattr(self, '_v_key', None)
+    @setproperty
+    def __name__(self, value):
+        setattr(self, '_v_key', value)
+
+    @getproperty
+    def __parent__(self):
+        return getattr(self, '_v_parent', None)
+    @setproperty
+    def __parent__(self, value):
+        setattr(self, '_v_parent', value)
+
+
+class SimpleMongoContainer(sample.SampleContainer, persistent.Persistent):
+
+    def __getstate__(self):
+        state = super(SimpleMongoContainer, self).__getstate__()
+        state['data'] = state.pop('_SampleContainer__data')
+        return state
+
+    def __setstate__(self, state):
+        state['_SampleContainer__data'] = state.pop('data', {})
+        super(SimpleMongoContainer, self).__setstate__(state)
+
+    def __getitem__(self, key):
+        obj = super(SimpleMongoContainer, self).__getitem__(key)
+        obj._v_key = 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_parent = self
+        return obj
+
+    def items(self):
+        items = super(SimpleMongoContainer, self).items()
+        for key, obj in items:
+            obj._v_key = key
+            obj._v_parent = self
+        return items
+
+    def values(self):
+        return [v for k, v in self.items()]
+
+    def __setitem__(self, key, object):
+        super(SimpleMongoContainer, self).__setitem__(key, object)
+        self._p_changed = True
+
+    def __delitem__(self, key):
+        super(SimpleMongoContainer, self).__delitem__(key)
+        self._p_changed = True
+
+class MongoContainer(contained.Contained,
+                     persistent.Persistent,
+                     UserDict.DictMixin):
+    _m_database = None
+    _m_collection = None
+    _m_mapping_key = 'key'
+    _m_parent_key = 'parent'
+
+    def __init__(self, collection=None, database=None,
+                 mapping_key=None, parent_key=None):
+        if collection:
+            self._m_collection = collection
+        if database:
+            self._m_database = database
+        if mapping_key is not None:
+            self._m_mapping_key = mapping_key
+        if parent_key is not None:
+            self._m_parent_key = parent_key
+
+    @property
+    def _added(self):
+        ann = self._m_jar.annotations.setdefault(self._p_oid or id(self), {})
+        return ann.setdefault('added', {})
+
+    @property
+    def _deleted(self):
+        ann = self._m_jar.annotations.setdefault(self._p_oid or id(self), {})
+        return ann.setdefault('deleted', {})
+
+    @property
+    def _m_jar(self):
+        # If the container is in a Mongo storage hierarchy, then getting the
+        # datamanager is easy, otherwise we do an adapter lookup.
+        if interfaces.IMongoDataManager.providedBy(self._p_jar):
+            return self._p_jar
+        else:
+            provider = zope.component.getUtility(
+                interfaces.IMongoDataManagerProvider)
+            return provider.get()
+
+    def get_collection(self):
+        db_name = self._m_database or self._m_jar.default_database
+        return self._m_jar._conn[db_name][self._m_collection]
+
+    def _m_get_parent_key_value(self):
+        if getattr(self, '_p_jar', None) is None:
+            raise ValueError('_p_jar not found.')
+        if interfaces.IMongoDataManager.providedBy(self._p_jar):
+            return self
+        else:
+            return 'zodb-'+''.join("%02x" % ord(x) for x in self._p_oid).strip()
+
+    def _m_get_items_filter(self):
+        filter = {}
+        # Make sure that we only look through objects that have the mapping
+        # key. Objects not having the mapping key cannot be part of the
+        # collection.
+        if self._m_mapping_key is not None:
+            filter[self._m_mapping_key] = {'$exists': True}
+        if self._m_parent_key is not None:
+            gs = self._m_jar._writer.get_state
+            filter[self._m_parent_key] = gs(self._m_get_parent_key_value())
+        return filter
+
+    def __getitem__(self, key):
+        if key in self._added:
+            return self._added[key]
+        if key in self._deleted:
+            raise KeyError(key)
+        filter = self._m_get_items_filter()
+        filter[self._m_mapping_key] = key
+        doc = self.get_collection().find_one(filter, fields=())
+        if doc is None:
+            raise KeyError(key)
+        dbref = pymongo.dbref.DBRef(
+            self._m_collection, doc['_id'],
+            self._m_database or self._m_jar.default_database)
+        obj = self._m_jar._reader.get_ghost(dbref)
+        obj._v_key = key
+        obj._v_parent = self
+        return obj
+
+    def __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_parent_key is not None:
+            setattr(value, self._m_parent_key, self._m_get_parent_key_value())
+        self._m_jar.register(value)
+        # Temporarily store the added object, so it is immediately available
+        # via the API.
+        value._v_key = key
+        value._v_parent = self
+        self._added[key] = value
+        self._deleted.pop(key, None)
+
+    def __delitem__(self, key):
+        # Deleting the object from the database is not our job. We simply
+        # remove it from the dictionary.
+        value = self[key]
+        if self._m_mapping_key is not None:
+            delattr(value, self._m_mapping_key)
+        if self._m_parent_key is not None:
+            delattr(value, self._m_parent_key)
+        self._deleted[key] = value
+        self._added.pop(key, None)
+
+    def keys(self):
+        filter = self._m_get_items_filter()
+        filter[self._m_mapping_key] = {'$ne': None}
+        keys = [
+            doc[self._m_mapping_key]
+            for doc in self.get_collection().find(filter)
+            if not doc[self._m_mapping_key] in self._deleted]
+        keys += self._added.keys()
+        return keys
+
+    def raw_find(self, spec=None, *args, **kwargs):
+        if spec is None:
+            spec  = {}
+        spec.update(self._m_get_items_filter())
+        return self.get_collection().find(spec, *args, **kwargs)
+
+    def find(self, spec=None, fields=None, *args, **kwargs):
+        # If fields were not specified, we only request the oid and the key.
+        fields = tuple(fields or ())
+        fields += (self._m_mapping_key,)
+        result = self.raw_find(spec, fields, *args, **kwargs)
+        for doc in result:
+            dbref = pymongo.dbref.DBRef(
+                self._m_collection, doc['_id'],
+                self._m_database or self._m_jar.default_database)
+            obj = self._m_jar._reader.get_ghost(dbref)
+            obj._v_key = doc[self._m_mapping_key]
+            obj._v_parent = self
+            yield obj
+
+    def raw_find_one(self, spec_or_id=None, *args, **kwargs):
+        if spec_or_id is None:
+            spec_or_id  = {}
+        if not isinstance(spec_or_id, dict):
+            spec_or_id = {'_id': spec_or_id}
+        spec_or_id.update(self._m_get_items_filter())
+        return self.get_collection().find_one(spec_or_id, *args, **kwargs)
+
+    def find_one(self, spec_or_id=None, fields=None, *args, **kwargs):
+        # If fields were not specified, we only request the oid and the key.
+        fields = tuple(fields or ())
+        fields += (self._m_mapping_key,)
+        doc = self.raw_find_one(spec_or_id, fields, *args, **kwargs)
+        if doc is None:
+            return None
+        dbref = pymongo.dbref.DBRef(
+            self._m_collection, doc['_id'],
+            self._m_database or self._m_jar.default_database)
+        obj = self._m_jar._reader.get_ghost(dbref)
+        obj._v_key = doc[self._m_mapping_key]
+        obj._v_parent = self
+        return obj
+
+class AllItemsMongoContainer(MongoContainer):
+    _m_parent_key = None
+
+
+class SubDocumentMongoContainer(MongoContained, MongoContainer):
+    _p_mongo_sub_object = True


Property changes on: mongopersist/trunk/src/mongopersist/zope/container.py
___________________________________________________________________
Added: svn:keywords
   + Id

Added: mongopersist/trunk/src/mongopersist/zope/schema.py
===================================================================
--- mongopersist/trunk/src/mongopersist/zope/schema.py	                        (rev 0)
+++ mongopersist/trunk/src/mongopersist/zope/schema.py	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,26 @@
+##############################################################################
+#
+# Copyright (c) 2011 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Mongo Persistence Schema Fields"""
+import zope.interface
+import zope.schema._field
+import zope.schema.interfaces
+import mongopersist.serialize
+
+class MongoSequence(zope.schema._field.AbstractCollection):
+    zope.interface.implements(zope.schema.interfaces.IList)
+    _type = (tuple, list, mongopersist.serialize.PersistentList)
+
+class MongoMapping(zope.schema._field.Dict):
+    zope.interface.implements(zope.schema.interfaces.IDict)
+    _type = (dict, mongopersist.serialize.PersistentDict)


Property changes on: mongopersist/trunk/src/mongopersist/zope/schema.py
___________________________________________________________________
Added: svn:keywords
   + Id

Added: mongopersist/trunk/src/mongopersist/zope/tests/__init__.py
===================================================================
--- mongopersist/trunk/src/mongopersist/zope/tests/__init__.py	                        (rev 0)
+++ mongopersist/trunk/src/mongopersist/zope/tests/__init__.py	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1 @@
+# Make a package.


Property changes on: mongopersist/trunk/src/mongopersist/zope/tests/__init__.py
___________________________________________________________________
Added: svn:keywords
   + Id

Added: mongopersist/trunk/src/mongopersist/zope/tests/test_container.py
===================================================================
--- mongopersist/trunk/src/mongopersist/zope/tests/test_container.py	                        (rev 0)
+++ mongopersist/trunk/src/mongopersist/zope/tests/test_container.py	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,566 @@
+##############################################################################
+#
+# Copyright (c) 2011 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Mongo Persistence Doc Tests"""
+import doctest
+
+import ZODB
+import ZODB.DemoStorage
+import persistent
+import pymongo
+import re
+import transaction
+import zope.component
+import zope.interface
+from pprint import pprint
+from zope.app.testing import placelesssetup
+from zope.container import contained, btree
+from zope.testing import module, renormalizing
+
+from mongopersist import datamanager, interfaces, serialize
+from mongopersist.zope import container
+
+class ApplicationRoot(container.SimpleMongoContainer):
+    _p_mongo_collection = 'root'
+
+class SimplePerson(contained.Contained, persistent.Persistent):
+    _p_mongo_collection = 'person'
+
+    def __init__(self, name):
+        self.name = name
+
+    def __str__(self):
+        return self.name
+
+    def __repr__(self):
+        return '<%s %s>' %(self.__class__.__name__, self)
+
+class Person(container.MongoContained, SimplePerson):
+    pass
+
+
+def doctest_SimpleMongoContainer_basic():
+    """SimpleMongoContainer: basic
+
+      >>> cn = 'mongopersist.zope.container.SimpleMongoContainer'
+
+    Let's add a container to the root:
+
+      >>> dm.reset()
+      >>> dm.root['c'] = container.SimpleMongoContainer()
+
+      >>> db = dm._conn[DBNAME]
+      >>> pprint(list(db[cn].find()))
+      [{u'_id': ObjectId('4e7ea146e13823316f000000'), u'data': {}}]
+
+    As you can see, the serialization is very clean. Next we add a person.
+
+      >>> dm.root['c'][u'stephan'] = SimplePerson(u'Stephan')
+      >>> dm.root['c'].keys()
+      [u'stephan']
+      >>> dm.root['c'][u'stephan']
+      <SimplePerson Stephan>
+
+      >>> dm.root['c']['stephan'].__parent__
+      <mongopersist.zope.container.SimpleMongoContainer object at 0x7fec50f86500>
+      >>> dm.root['c']['stephan'].__name__
+      u'stephan'
+
+    You can also access objects using the ``get()`` method of course:
+
+      >>> stephan = dm.root['c'].get(u'stephan')
+      >>> stephan.__parent__
+      <mongopersist.zope.container.SimpleMongoContainer object at 0x7fec50f86500>
+      >>> stephan.__name__
+      u'stephan'
+
+    Let's commit and access the data again:
+
+      >>> transaction.commit()
+
+      >>> pprint(list(db['person'].find()))
+      [{u'__name__': u'stephan',
+        u'__parent__':
+            DBRef(u'mongopersist.zope.container.SimpleMongoContainer',
+                  ObjectId('4e7ddf12e138237403000000'),
+                  u'mongopersist_container_test'),
+        u'_id': ObjectId('4e7ddf12e138237403000000'),
+        u'name': u'Stephan'}]
+
+      >>> dm.root['c'].keys()
+      [u'stephan']
+      >>> dm.root['c']['stephan'].__parent__
+      <mongopersist.zope.container.SimpleMongoContainer object at 0x7fec50f86500>
+      >>> dm.root['c']['stephan'].__name__
+      u'stephan'
+
+      >>> dm.root['c'].items()
+      [(u'stephan', <SimplePerson Stephan>)]
+
+      >>> dm.root['c'].values()
+      [<SimplePerson Stephan>]
+
+    Now remove the item:
+
+      >>> del dm.root['c']['stephan']
+
+    The changes are immediately visible.
+
+      >>> dm.root['c'].keys()
+      []
+      >>> dm.root['c']['stephan']
+      Traceback (most recent call last):
+      ...
+      KeyError: 'stephan'
+
+    Make sure it is really gone after committing:
+
+      >>> transaction.commit()
+      >>> dm.root['c'].keys()
+      []
+    """
+
+
+def doctest_MongoContainer_basic():
+    """MongoContainer: basic
+
+    Let's add a container to the root:
+
+      >>> transaction.commit()
+      >>> dm.root['c'] = container.MongoContainer('person')
+
+      >>> db = dm._conn[DBNAME]
+      >>> pprint(list(db['mongopersist.zope.container.MongoContainer'].find()))
+      [{u'_id': ObjectId('4e7ddf12e138237403000000'),
+        u'_m_collection': u'person'}]
+
+    It is unfortunate that the '_m_collection' attribute is set. This is
+    avoidable using a sub-class.
+
+      >>> dm.root['c'][u'stephan'] = Person(u'Stephan')
+      >>> dm.root['c'].keys()
+      [u'stephan']
+      >>> dm.root['c'][u'stephan']
+      <Person Stephan>
+
+      >>> dm.root['c']['stephan'].__parent__
+      <mongopersist.zope.container.MongoContainer object at 0x7fec50f86500>
+      >>> dm.root['c']['stephan'].__name__
+      u'stephan'
+
+    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()
+
+      >>> pprint(list(db['person'].find()))
+      [{u'_id': ObjectId('4e7e9d3ae138232d7b000003'),
+        u'key': u'stephan',
+        u'name': u'Stephan',
+        u'parent': DBRef(u'mongopersist.zope.container.MongoContainer',
+                         ObjectId('4e7e9d3ae138232d7b000000'),
+                         u'mongopersist_container_test')}]
+
+      >>> dm.root['c'].keys()
+      [u'stephan']
+      >>> dm.root['c']['stephan'].__parent__
+      <mongopersist.zope.container.MongoContainer object at 0x7fec50f86500>
+      >>> dm.root['c']['stephan'].__name__
+      'stephan'
+
+    We get a usual key error, if an object does not exist:
+
+      >>> dm.root['c']['roy']
+      Traceback (most recent call last):
+      ...
+      KeyError: 'roy'
+
+    Now remove the item:
+
+      >>> del dm.root['c']['stephan']
+
+    The changes are immediately visible.
+
+      >>> dm.root['c'].keys()
+      []
+      >>> dm.root['c']['stephan']
+      Traceback (most recent call last):
+      ...
+      KeyError: 'stephan'
+
+    Make sure it is really gone after committing:
+
+      >>> transaction.commit()
+      >>> dm.root['c'].keys()
+      []
+    """
+
+def doctest_MongoContainer_constructor():
+    """MongoContainer: constructor
+
+    The constructor of the MongoContainer class has several advanced arguments
+    that allow customizing the storage options.
+
+      >>> transaction.commit()
+      >>> c = container.MongoContainer(
+      ...     'person',
+      ...     database = 'testdb',
+      ...     mapping_key = 'name',
+      ...     parent_key = 'site')
+
+    The database allows you to specify a custom database in which the items
+    are located. Otherwise the datamanager's default database is used.
+
+      >>> c._m_database
+      'testdb'
+
+    The mapping key is the key/attribute of the contained items in which their
+    name/key within the mapping is stored.
+
+      >>> c._m_mapping_key
+      'name'
+
+    The parent key is the key/attribute in which the parent reference is
+    stored. This is used to suport multiple containers per Mongo collection.
+
+      >>> c._m_parent_key
+      'site'
+    """
+def doctest_MongoContainer_m_parent_key_value():
+    r"""MongoContainer: _m_parent_key_value()
+
+    This method is used to extract the parent refernce for the item.
+
+      >>> c = container.MongoContainer('person')
+
+    The default implementation requires the container to be in some sort of
+    persistent store, though it does not care whether this store is Mongo or a
+    classic ZODB. This feature allows one to mix and match ZODB and Mongo
+    storage.
+
+      >>> c._m_get_parent_key_value()
+      Traceback (most recent call last):
+      ...
+      ValueError: _p_jar not found.
+
+    Now the ZODB case:
+
+      >>> c._p_jar = object()
+      >>> c._p_oid = '\x00\x00\x00\x00\x00\x00\x00\x01'
+      >>> c._m_get_parent_key_value()
+      'zodb-0000000000000001'
+
+    And finally the Mongo case:
+
+      >>> c._p_jar = c._p_oid = None
+      >>> dm.root['people'] = c
+      >>> c._m_get_parent_key_value()
+      <mongopersist.zope.container.MongoContainer object at 0x32deed8>
+
+    In that final case, the container itself is returned, because upon
+    serialization, we simply look up the dbref.
+    """
+
+def doctest_MongoContainer_many_items():
+    """MongoContainer: many items
+
+    Let's create an interesting set of data:
+
+      >>> transaction.commit()
+      >>> dm.root['people'] = container.MongoContainer('person')
+      >>> dm.root['people'][u'stephan'] = Person(u'Stephan')
+      >>> dm.root['people'][u'roy'] = Person(u'Roy')
+      >>> dm.root['people'][u'roger'] = Person(u'Roger')
+      >>> dm.root['people'][u'adam'] = Person(u'Adam')
+      >>> dm.root['people'][u'albertas'] = Person(u'Albertas')
+      >>> dm.root['people'][u'russ'] = Person(u'Russ')
+
+    In order for find to work, the data has to be committed:
+
+      >>> transaction.commit()
+
+    Let's now search and receive documents as result:
+
+      >>> sorted(dm.root['people'].keys())
+      [u'adam', u'albertas', u'roger', u'roy', u'russ', u'stephan']
+      >>> dm.root['people'][u'stephan']
+      <Person Stephan>
+      >>> dm.root['people'][u'adam']
+      <Person Adam>
+"""
+
+def doctest_MongoContainer_find():
+    """MongoContainer: find
+
+    The Mongo Container supports direct Mongo queries. It does, however,
+    insert the additional container filter arguments and can optionally
+    convert the documents to objects.
+
+    Let's create an interesting set of data:
+
+      >>> transaction.commit()
+      >>> dm.root['people'] = container.MongoContainer('person')
+      >>> dm.root['people'][u'stephan'] = Person(u'Stephan')
+      >>> dm.root['people'][u'roy'] = Person(u'Roy')
+      >>> dm.root['people'][u'roger'] = Person(u'Roger')
+      >>> dm.root['people'][u'adam'] = Person(u'Adam')
+      >>> dm.root['people'][u'albertas'] = Person(u'Albertas')
+      >>> dm.root['people'][u'russ'] = Person(u'Russ')
+
+    In order for find to work, the data has to be committed:
+
+      >>> transaction.commit()
+
+    Let's now search and receive documents as result:
+
+      >>> res = dm.root['people'].raw_find({'name': {'$regex': '^Ro.*'}})
+      >>> pprint(list(res))
+      [{u'_id': ObjectId('4e7eb152e138234158000004'),
+        u'key': u'roy',
+        u'name': u'Roy',
+        u'parent': DBRef(u'mongopersist.zope.container.MongoContainer',
+                         ObjectId('4e7eb152e138234158000000'),
+                         u'mongopersist_container_test')},
+       {u'_id': ObjectId('4e7eb152e138234158000005'),
+        u'key': u'roger',
+        u'name': u'Roger',
+        u'parent': DBRef(u'mongopersist.zope.container.MongoContainer',
+                         ObjectId('4e7eb152e138234158000000'),
+                         u'mongopersist_container_test')}]
+
+    And now the same query, but this time with object results:
+
+      >>> res = dm.root['people'].find({'name': {'$regex': '^Ro.*'}})
+      >>> pprint(list(res))
+      [<Person Roy>, <Person Roger>]
+
+    When no spec is specified, all items are returned:
+
+      >>> res = dm.root['people'].find()
+      >>> pprint(list(res))
+      [<Person Stephan>, <Person Roy>, <Person Roger>, <Person Adam>,
+       <Person Albertas>, <Person Russ>]
+
+    You can also search for a single result:
+
+      >>> res = dm.root['people'].raw_find_one({'name': {'$regex': '^St.*'}})
+      >>> pprint(res)
+      {u'_id': ObjectId('4e7eb259e138234289000003'),
+       u'key': u'stephan',
+       u'name': u'Stephan',
+       u'parent': DBRef(u'mongopersist.zope.container.MongoContainer',
+                        ObjectId('4e7eb259e138234289000000'),
+                        u'mongopersist_container_test')}
+
+      >>> stephan = dm.root['people'].find_one({'name': {'$regex': '^St.*'}})
+      >>> pprint(stephan)
+      <Person Stephan>
+
+    If no result is found, ``None`` is returned:
+
+      >>> dm.root['people'].find_one({'name': {'$regex': '^XXX.*'}})
+
+    If there is no spec, then simply the first item is returned:
+
+      >>> dm.root['people'].find_one()
+      <Person Stephan>
+
+    On the other hand, if the spec is an id, we look for it instead:
+
+      >>> dm.root['people'].find_one(stephan._p_oid.id)
+      <Person Stephan>
+    """
+
+def doctest_AllItemsMongoContainer_basic():
+    """AllItemsMongoContainer: basic
+
+    This type of container returns all items of the collection without regard
+    of a parenting hierarchy.
+
+    Let's start by creating two person containers that service different
+    purposes:
+
+      >>> transaction.commit()
+
+      >>> dm.root['friends'] = container.MongoContainer('person')
+      >>> dm.root['friends'][u'roy'] = Person(u'Roy')
+      >>> dm.root['friends'][u'roger'] = Person(u'Roger')
+
+      >>> dm.root['family'] = container.MongoContainer('person')
+      >>> dm.root['family'][u'anton'] = Person(u'Anton')
+      >>> dm.root['family'][u'konrad'] = Person(u'Konrad')
+
+      >>> transaction.commit()
+      >>> sorted(dm.root['friends'].keys())
+      [u'roger', u'roy']
+      >>> sorted(dm.root['family'].keys())
+      [u'anton', u'konrad']
+
+    Now we can create an all-items-container that allows us to view all
+    people.
+
+      >>> dm.root['all-people'] = container.AllItemsMongoContainer('person')
+      >>> sorted(dm.root['all-people'].keys())
+      [u'anton', u'konrad', u'roger', u'roy']
+    """
+
+def doctest_SubDocumentMongoContainer_basic():
+    r"""SubDocumentMongoContainer: basic
+
+    Sub_document Mongo containers are useful, since they avoid the creation of
+    a commonly trivial collections holding meta-data for the collection
+    object. But they require a root document:
+
+      >>> dm.reset()
+      >>> dm.root['app_root'] = ApplicationRoot()
+
+    Let's add a container to the app root:
+
+      >>> dm.root['app_root']['people'] = \
+      ...     container.SubDocumentMongoContainer('person')
+
+      >>> transaction.commit()
+      >>> db = dm._conn[DBNAME]
+      >>> pprint(list(db['root'].find()))
+      [{u'_id': ObjectId('4e7ea67be138233711000001'),
+        u'data':
+         {u'people':
+          {u'_m_collection': u'person',
+           u'_py_persistent_type':
+               u'mongopersist.zope.container.SubDocumentMongoContainer'}}}]
+
+    It is unfortunate that the '_m_collection' attribute is set. This is
+    avoidable using a sub-class. Let's make sure the container can be loaded
+    correctly:
+
+      >>> dm.root['app_root']['people']
+      <mongopersist.zope.container.SubDocumentMongoContainer ...>
+      >>> dm.root['app_root']['people'].__parent__
+      <mongopersist.zope.tests.test_container.ApplicationRoot object at 0x7f>
+      >>> dm.root['app_root']['people'].__name__
+      'people'
+
+    Let's add an item to the container:
+
+      >>> dm.root['app_root']['people'][u'stephan'] = Person(u'Stephan')
+      >>> dm.root['app_root']['people'].keys()
+      [u'stephan']
+      >>> dm.root['app_root']['people'][u'stephan']
+      <Person Stephan>
+
+      >>> transaction.commit()
+      >>> dm.root['app_root']['people'].keys()
+      [u'stephan']
+    """
+
+def doctest_MongoContainer_with_ZODB():
+    r"""MongoContainer: with ZODB
+
+    This test demonstrates how a Mongo Container lives inside a ZODB tree:
+
+      >>> zodb = ZODB.DB(ZODB.DemoStorage.DemoStorage())
+      >>> root = zodb.open().root()
+      >>> root['app'] = btree.BTreeContainer()
+      >>> root['app']['people'] = container.MongoContainer('person')
+
+    Let's now commit the transaction and make sure everything is cool.
+
+      >>> transaction.commit()
+      >>> root = zodb.open().root()
+      >>> root['app']
+      <zope.container.btree.BTreeContainer object at 0x7fbb5842f578>
+      >>> root['app']['people']
+      <mongopersist.zope.container.MongoContainer object at 0x7fd6e23555f0>
+
+    Trying accessing people fails:
+
+      >>> root['app']['people'].keys()
+      Traceback (most recent call last):
+      ...
+      ComponentLookupError:
+       (<InterfaceClass mongopersist.interfaces.IMongoDataManagerProvider>, '')
+
+    This is because we have not told the system how to get a datamanager:
+
+      >>> class Provider(object):
+      ...     zope.interface.implements(interfaces.IMongoDataManagerProvider)
+      ...     def get(self):
+      ...         return dm
+      >>> zope.component.provideUtility(Provider())
+
+    So let's try again:
+
+      >>> root['app']['people'].keys()
+      []
+
+    Next we create a person object and make sure it gets properly persisted.
+
+      >>> root['app']['people']['stephan'] = Person(u'Stephan')
+      >>> transaction.commit()
+      >>> root = zodb.open().root()
+      >>> root['app']['people'].keys()
+      [u'stephan']
+
+      >>> stephan = root['app']['people']['stephan']
+      >>> stephan.__name__
+      'stephan'
+      >>> stephan.__parent__
+      <mongopersist.zope.container.MongoContainer object at 0x7f6b6273b7d0>
+
+      >>> pprint(list(dm._conn[DBNAME]['person'].find()))
+      [{u'_id': ObjectId('4e7ed795e1382366a0000001'),
+        u'key': u'stephan',
+        u'name': u'Stephan',
+        u'parent': u'zodb-1058e89d27d8afd9'}]
+
+    Note that we produced a nice hex-presentation of the ZODB's OID.
+    """
+
+checker = renormalizing.RENormalizing([
+    (re.compile(r'datetime.datetime(.*)'),
+     'datetime.datetime(2011, 10, 1, 9, 45)'),
+    (re.compile(r"ObjectId\('[0-9a-f]*'\)"),
+     "ObjectId('4e7ddf12e138237403000000')"),
+    (re.compile(r"object at 0x[0-9a-f]*>"),
+     "object at 0x001122>"),
+    (re.compile(r"zodb-[0-9a-f].*"),
+     "zodb-01af3b00c5"),
+    ])
+
+def setUp(test):
+    placelesssetup.setUp(test)
+    module.setUp(test)
+    test.globs['conn'] = pymongo.Connection('localhost', 27017, tz_aware=False)
+    test.globs['DBNAME'] = 'mongopersist_container_test'
+    test.globs['conn'].drop_database(test.globs['DBNAME'])
+    test.globs['dm'] = datamanager.MongoDataManager(
+        test.globs['conn'],
+        default_database=test.globs['DBNAME'],
+        root_database=test.globs['DBNAME'])
+
+def tearDown(test):
+    placelesssetup.tearDown(test)
+    module.tearDown(test)
+    test.globs['conn'].disconnect()
+    serialize.SERIALIZERS.__init__()
+
+def test_suite():
+    return doctest.DocTestSuite(
+        setUp=setUp, tearDown=tearDown, checker=checker,
+        optionflags=(doctest.NORMALIZE_WHITESPACE|
+                     doctest.ELLIPSIS|
+                     doctest.REPORT_ONLY_FIRST_FAILURE
+                     #|doctest.REPORT_NDIFF
+                     )
+        )


Property changes on: mongopersist/trunk/src/mongopersist/zope/tests/test_container.py
___________________________________________________________________
Added: svn:keywords
   + Id

Added: mongopersist/trunk/src/mongopersist.egg-info/PKG-INFO
===================================================================
--- mongopersist/trunk/src/mongopersist.egg-info/PKG-INFO	                        (rev 0)
+++ mongopersist/trunk/src/mongopersist.egg-info/PKG-INFO	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,16 @@
+Metadata-Version: 1.0
+Name: mongopersist
+Version: 0.1dev
+Summary: Mongo Persistence Backend
+Home-page: UNKNOWN
+Author: Stephan Richter
+Author-email: stephan.richter at gmail.com
+License: ZPL 2.1
+Description: UNKNOWN
+Keywords: mongo persistent
+Platform: UNKNOWN
+Classifier: Development Status :: 3 - Alpha
+Classifier: Intended Audience :: Developers
+Classifier: Programming Language :: Python
+Classifier: Natural Language :: English
+Classifier: Operating System :: OS Independent

Added: mongopersist/trunk/src/mongopersist.egg-info/SOURCES.txt
===================================================================
--- mongopersist/trunk/src/mongopersist.egg-info/SOURCES.txt	                        (rev 0)
+++ mongopersist/trunk/src/mongopersist.egg-info/SOURCES.txt	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,27 @@
+bootstrap.py
+buildout.cfg
+setup.py
+versions.cfg
+src/mongopersist/README.txt
+src/mongopersist/__init__.py
+src/mongopersist/datamanager.py
+src/mongopersist/interfaces.py
+src/mongopersist/mapping.py
+src/mongopersist/pool.py
+src/mongopersist/serialize.py
+src/mongopersist/serializers.py
+src/mongopersist/testing.py
+src/mongopersist.egg-info/PKG-INFO
+src/mongopersist.egg-info/SOURCES.txt
+src/mongopersist.egg-info/dependency_links.txt
+src/mongopersist.egg-info/not-zip-safe
+src/mongopersist.egg-info/requires.txt
+src/mongopersist.egg-info/top_level.txt
+src/mongopersist/tests/__init__.py
+src/mongopersist/tests/test_doc.py
+src/mongopersist/tests/test_serialize.py
+src/mongopersist/zope/__init__.py
+src/mongopersist/zope/container.py
+src/mongopersist/zope/schema.py
+src/mongopersist/zope/tests/__init__.py
+src/mongopersist/zope/tests/test_container.py
\ No newline at end of file


Property changes on: mongopersist/trunk/src/mongopersist.egg-info/SOURCES.txt
___________________________________________________________________
Added: svn:eol-style
   + native

Added: mongopersist/trunk/src/mongopersist.egg-info/dependency_links.txt
===================================================================
--- mongopersist/trunk/src/mongopersist.egg-info/dependency_links.txt	                        (rev 0)
+++ mongopersist/trunk/src/mongopersist.egg-info/dependency_links.txt	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1 @@
+


Property changes on: mongopersist/trunk/src/mongopersist.egg-info/dependency_links.txt
___________________________________________________________________
Added: svn:eol-style
   + native

Added: mongopersist/trunk/src/mongopersist.egg-info/not-zip-safe
===================================================================
--- mongopersist/trunk/src/mongopersist.egg-info/not-zip-safe	                        (rev 0)
+++ mongopersist/trunk/src/mongopersist.egg-info/not-zip-safe	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1 @@
+

Added: mongopersist/trunk/src/mongopersist.egg-info/requires.txt
===================================================================
--- mongopersist/trunk/src/mongopersist.egg-info/requires.txt	                        (rev 0)
+++ mongopersist/trunk/src/mongopersist.egg-info/requires.txt	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,14 @@
+ZODB3
+lru
+pymongo
+setuptools
+zope.dottedname
+zope.interface
+
+[zope]
+rwproperty
+zope.container
+
+[test]
+zope.app.testing
+zope.testing
\ No newline at end of file


Property changes on: mongopersist/trunk/src/mongopersist.egg-info/requires.txt
___________________________________________________________________
Added: svn:eol-style
   + native

Added: mongopersist/trunk/src/mongopersist.egg-info/top_level.txt
===================================================================
--- mongopersist/trunk/src/mongopersist.egg-info/top_level.txt	                        (rev 0)
+++ mongopersist/trunk/src/mongopersist.egg-info/top_level.txt	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1 @@
+mongopersist


Property changes on: mongopersist/trunk/src/mongopersist.egg-info/top_level.txt
___________________________________________________________________
Added: svn:eol-style
   + native

Added: mongopersist/trunk/versions.cfg
===================================================================
--- mongopersist/trunk/versions.cfg	                        (rev 0)
+++ mongopersist/trunk/versions.cfg	2011-11-04 17:20:59 UTC (rev 123285)
@@ -0,0 +1,200 @@
+[versions]
+
+# Added by Buildout Versions at 2011-09-23 13:05:14.263622
+buildout-versions = 1.6
+transaction = 1.1.1
+z3c.recipe.scripts = 1.0.1
+zc.recipe.egg = 1.3.2
+zc.recipe.testrunner = 1.4.0
+
+# Required by:
+# ZODB3==3.10.3
+# zdaemon==2.0.4
+ZConfig = 2.8.0
+
+# Required by:
+# mongopersist==0.1dev
+ZODB3 = 3.10.3
+
+# Required by:
+# mongopersist==0.1dev
+pymongo = 2.0.1
+
+# Required by:
+# mongopersist==0.1dev
+# zope.dottedname==3.4.6
+# zope.exceptions==3.6.1
+# zope.interface==3.6.3
+# zope.testrunner==4.0.3
+setuptools = 0.6c12dev-r88846
+
+# Required by:
+# ZODB3==3.10.3
+zc.lockfile = 1.0.0
+
+# Required by:
+# ZODB3==3.10.3
+zdaemon = 2.0.4
+
+# Required by:
+# mongopersist==0.1dev
+zope.dottedname = 3.4.6
+
+# Required by:
+# ZODB3==3.10.3
+zope.event = 3.5.0-1
+
+# Required by:
+# zope.testrunner==4.0.3
+zope.exceptions = 3.6.1
+
+# Required by:
+# mongopersist==0.1dev
+# zope.testrunner==4.0.3
+zope.interface = 3.6.3
+
+# Required by:
+# zc.recipe.testrunner==1.4.0
+zope.testrunner = 4.0.3
+
+# Added by Buildout Versions at 2011-09-24 09:27:33.494988
+zope.testing = 3.10.2
+
+# Added by Buildout Versions at 2011-09-24 14:51:21.175710
+
+# Required by:
+# mongopersist==0.1dev
+lru = 0.1
+
+# Added by Buildout Versions at 2011-09-24 22:10:37.470253
+zope.container = 3.12.0
+zope.contenttype = 3.5.3
+zope.lifecycleevent = 3.6.2
+zope.location = 3.9.0
+
+# Required by:
+# zope.i18n==3.7.4
+pytz = 2011g
+
+# Required by:
+# zope.container==3.12.0
+zope.broken = 3.6.0
+
+# Required by:
+# zope.publisher==3.12.6
+zope.browser = 1.3
+
+# Required by:
+# zope.container==3.12.0
+# zope.lifecycleevent==3.6.2
+# zope.publisher==3.12.6
+# zope.security==3.8.2
+# zope.traversing==3.14.0
+zope.component = 3.10.0
+
+# Required by:
+# zope.publisher==3.12.6
+zope.configuration = 3.7.4
+
+# Required by:
+# zope.container==3.12.0
+zope.filerepresentation = 3.6.0
+
+# Required by:
+# zope.publisher==3.12.6
+# zope.traversing==3.14.0
+zope.i18n = 3.7.4
+
+# Required by:
+# zope.container==3.12.0
+# zope.size==3.4.1
+# zope.traversing==3.14.0
+zope.i18nmessageid = 3.5.3
+
+# Required by:
+# zope.publisher==3.12.6
+# zope.traversing==3.14.0
+zope.proxy = 3.6.1
+
+# Required by:
+# zope.container==3.12.0
+zope.publisher = 3.12.6
+
+# Required by:
+# zope.container==3.12.0
+# zope.security==3.8.2
+zope.schema = 3.8.0
+
+# Required by:
+# zope.container==3.12.0
+# zope.publisher==3.12.6
+# zope.traversing==3.14.0
+zope.security = 3.8.2
+
+# Required by:
+# zope.container==3.12.0
+zope.size = 3.4.1
+
+# Required by:
+# zope.container==3.12.0
+zope.traversing = 3.14.0
+
+# Added by Buildout Versions at 2011-09-24 23:56:34.485542
+
+# Required by:
+# mongopersist==0.1dev
+rwproperty = 1.0
+
+# Added by Buildout Versions at 2011-09-25 03:08:16.489883
+zope.app.appsetup = 3.15.0
+zope.app.testing = 3.8.1
+
+# Required by:
+# zope.app.dependable==3.5.1
+# zope.app.testing==3.8.1
+# zope.site==3.9.2
+zope.annotation = 3.5.0
+
+# Required by:
+# zope.app.testing==3.8.1
+zope.app.debug = 3.4.1
+
+# Required by:
+# zope.app.testing==3.8.1
+zope.app.dependable = 3.5.1
+
+# Required by:
+# zope.app.testing==3.8.1
+zope.app.publication = 3.12.0
+
+# Required by:
+# zope.app.publication==3.12.0
+zope.authentication = 3.7.1
+
+# Required by:
+# zope.app.appsetup==3.15.0
+# zope.app.publication==3.12.0
+zope.error = 3.7.2
+
+# Required by:
+# zope.session==3.9.4
+zope.minmax = 1.1.2
+
+# Required by:
+# zope.app.testing==3.8.1
+zope.password = 3.6.1
+
+# Required by:
+# zope.app.testing==3.8.1
+zope.processlifetime = 1.0
+
+# Required by:
+# zope.app.appsetup==3.15.0
+zope.session = 3.9.4
+
+# Required by:
+# zope.app.testing==3.8.1
+zope.site = 3.9.2
+
+# Added by Buildout Versions at 2011-11-02 11:39:09.460549
+z3c.coverage = 1.2.0



More information about the checkins mailing list