[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"> </td> <td>covered 99% (1 of 695 uncovered)</td></tr>
+<tr><td><a href="mongopersist.testing.html"> testing.py</a></td> <td style="background: green"> </td> <td>covered 100% (0 of 33 uncovered)</td></tr>
+<tr><td><a href="mongopersist.interfaces.html"> interfaces.py</a></td> <td style="background: green"> </td> <td>covered 100% (0 of 71 uncovered)</td></tr>
+<tr><td><a href="mongopersist.serialize.html"> serialize.py</a></td> <td style="background: yellow"> </td> <td>covered 99% (1 of 250 uncovered)</td></tr>
+<tr><td><a href="mongopersist.mapping.html"> mapping.py</a></td> <td style="background: green"> </td> <td>covered 100% (0 of 36 uncovered)</td></tr>
+<tr><td><a href="mongopersist.datamanager.html"> datamanager.py</a></td> <td style="background: green"> </td> <td>covered 100% (0 of 124 uncovered)</td></tr>
+<tr><td><a href="mongopersist.zope.html"> zope/</a></td> <td style="background: green"> </td> <td>covered 100% (0 of 180 uncovered)</td></tr>
+<tr><td><a href="mongopersist.zope.container.html"> container.py</a></td> <td style="background: green"> </td> <td>covered 100% (0 of 179 uncovered)</td></tr>
+<tr><td><a href="mongopersist.zope.__init__.html"> __init__.py</a></td> <td style="background: green"> </td> <td>covered 100% (0 of 1 uncovered)</td></tr>
+<tr><td><a href="mongopersist.__init__.html"> __init__.py</a></td> <td style="background: green"> </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"> </td> <td>covered 99% (1 of 695 uncovered)</td></tr>
+<tr><td><a href="mongopersist.__init__.html"> __init__.py</a></td> <td style="background: green"> </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"> </td> <td>covered 99% (1 of 695 uncovered)</td></tr>
+<tr><td><a href="mongopersist.datamanager.html"> datamanager.py</a></td> <td style="background: green"> </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 "AS IS" 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">"""Mongo Persistent Data Manager"""</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"> </td> <td>covered 99% (1 of 695 uncovered)</td></tr>
+<tr><td><a href="mongopersist.serialize.html"> serialize.py</a></td> <td style="background: yellow"> </td> <td>covered 99% (1 of 250 uncovered)</td></tr>
+<tr><td><a href="mongopersist.__init__.html"> __init__.py</a></td> <td style="background: green"> </td> <td>covered 100% (0 of 1 uncovered)</td></tr>
+<tr><td><a href="mongopersist.datamanager.html"> datamanager.py</a></td> <td style="background: green"> </td> <td>covered 100% (0 of 124 uncovered)</td></tr>
+<tr><td><a href="mongopersist.interfaces.html"> interfaces.py</a></td> <td style="background: green"> </td> <td>covered 100% (0 of 71 uncovered)</td></tr>
+<tr><td><a href="mongopersist.mapping.html"> mapping.py</a></td> <td style="background: green"> </td> <td>covered 100% (0 of 36 uncovered)</td></tr>
+<tr><td><a href="mongopersist.testing.html"> testing.py</a></td> <td style="background: green"> </td> <td>covered 100% (0 of 33 uncovered)</td></tr>
+<tr><td><a href="mongopersist.zope.html"> zope/</a></td> <td style="background: green"> </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"> </td> <td>covered 99% (1 of 695 uncovered)</td></tr>
+<tr><td><a href="mongopersist.interfaces.html"> interfaces.py</a></td> <td style="background: green"> </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 "AS IS" 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">"""Mongo Persistence Interfaces"""</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">"database conflict error"</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">"%s (%s)"</FONT></B> % (self.message, <B><FONT COLOR="#BC8F8F">", "</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">"""An object serializer allows for custom serialization output for
+ 1: objects."""</FONT></B>
+
+ 1: <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">can_read</FONT></B>(state):
+ <B><FONT COLOR="#BC8F8F">"""Returns a boolean indicating whether this serializer can deserialize
+ this state."""</FONT></B>
+
+ 1: <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">get_object</FONT></B>(state):
+ <B><FONT COLOR="#BC8F8F">"""Convert the state to an object."""</FONT></B>
+
+ 1: <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">can_write</FONT></B>(obj):
+ <B><FONT COLOR="#BC8F8F">"""Returns a boolean indicating whether this serializer can serialize
+ this object."""</FONT></B>
+
+ 1: <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">get_state</FONT></B>(obj):
+ <B><FONT COLOR="#BC8F8F">"""Convert the object to a state/document."""</FONT></B>
+
+
+ 2: <B><FONT COLOR="#A020F0">class</FONT></B> IObjectWriter(zope.interface.Interface):
+ 1: <B><FONT COLOR="#BC8F8F">"""The object writer stores an object in the database."""</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">"""Convert a non-persistent object to a Mongo state/document."""</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">"""Convert a persistent object to a Mongo state/document."""</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">"""Convert an arbitrary object to a Mongo state/document.
+
+ A ``CircularReferenceError`` is raised, if a non-persistent loop is
+ detected.
+ """</FONT></B>
+
+ 1: <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">store</FONT></B>(obj):
+ <B><FONT COLOR="#BC8F8F">"""Store an object in the database."""</FONT></B>
+
+
+ 2: <B><FONT COLOR="#A020F0">class</FONT></B> IObjectReader(zope.interface.Interface):
+ 1: <B><FONT COLOR="#BC8F8F">"""The object reader reads an object from the database."""</FONT></B>
+
+ 1: <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">resolve</FONT></B>(path):
+ <B><FONT COLOR="#BC8F8F">"""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.
+ """</FONT></B>
+
+ 1: <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">get_object</FONT></B>(state, obj):
+ <B><FONT COLOR="#BC8F8F">"""Get an object from the given state.
+
+ The ``obj`` is the Mongo document of which the created object is part
+ of.
+ """</FONT></B>
+
+ 1: <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">set_ghost_state</FONT></B>(obj):
+ <B><FONT COLOR="#BC8F8F">"""Convert a ghosted object to an active object by loading its state.
+ """</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">"""Get the ghosted version of the object.
+ """</FONT></B>
+
+
+ 2: <B><FONT COLOR="#A020F0">class</FONT></B> IMongoDataManager(persistent.interfaces.IPersistentDataManager):
+ 1: <B><FONT COLOR="#BC8F8F">"""A persistent data manager that stores data in Mongo."""</FONT></B>
+
+ 1: root = zope.interface.Attribute(
+ 1: <B><FONT COLOR="#BC8F8F">"""Get the root object, which is a mapping."""</FONT></B>)
+
+ 1: <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">reset</FONT></B>():
+ <B><FONT COLOR="#BC8F8F">"""Reset the datamanager for the next transaction."""</FONT></B>
+
+ 1: <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">dump</FONT></B>(obj):
+ <B><FONT COLOR="#BC8F8F">"""Store the object to Mongo and return its DBRef."""</FONT></B>
+
+ 1: <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">load</FONT></B>(dbref):
+ <B><FONT COLOR="#BC8F8F">"""Load the object from Mongo by using its DBRef.
+
+ Note: The returned object is in the ghost state.
+ """</FONT></B>
+
+
+ 2: <B><FONT COLOR="#A020F0">class</FONT></B> IMongoConnectionPool(zope.interface.Interface):
+ 1: <B><FONT COLOR="#BC8F8F">"""MongoDB connection pool"""</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">"""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: """</FONT></B>
+
+ 1: <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">get</FONT></B>():
+ <B><FONT COLOR="#BC8F8F">"""Return a mongo data manager."""</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"> </td> <td>covered 99% (1 of 695 uncovered)</td></tr>
+<tr><td><a href="mongopersist.mapping.html"> mapping.py</a></td> <td style="background: green"> </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 "AS IS" 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">"""Mongo Mapping Implementations"""</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"> </td> <td>covered 99% (1 of 695 uncovered)</td></tr>
+<tr><td><a href="mongopersist.serialize.html"> serialize.py</a></td> <td style="background: yellow"> </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 "AS IS" 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">"""Object Serialization for Mongo/BSON"""</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">"""Pack an integer or long into a 8-byte string"""</FONT></B>
+ 16: <B><FONT COLOR="#A020F0">return</FONT></B> struct.pack(<B><FONT COLOR="#BC8F8F">">Q"</FONT></B>, v)
+
+ 1: <B><FONT COLOR="#A020F0">def</FONT></B> <B><FONT COLOR="#0000FF">u64</FONT></B>(v):
+ <B><FONT COLOR="#BC8F8F">"""Unpack an 8-byte string into a 64-bit long integer."""</FONT></B>
+ 19: <B><FONT COLOR="#A020F0">return</FONT></B> struct.unpack(<B><FONT COLOR="#BC8F8F">">Q"</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() > 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 "doc_has_type" 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">>>>>>> <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"> </td> <td>covered 99% (1 of 695 uncovered)</td></tr>
+<tr><td><a href="mongopersist.testing.html"> testing.py</a></td> <td style="background: green"> </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 "AS IS" 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">"""Mongo Persistence Testing Support"""</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">"ObjectId\('[0-9a-f]*'\)"</FONT></B>),
+ 1: <B><FONT COLOR="#BC8F8F">"ObjectId('4e7ddf12e138237403000000')"</FONT></B>),
+ 1: (re.compile(r<B><FONT COLOR="#BC8F8F">"object at 0x[0-9a-f]*>"</FONT></B>),
+ 1: <B><FONT COLOR="#BC8F8F">"object at 0x001122>"</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"> </td> <td>covered 99% (1 of 695 uncovered)</td></tr>
+<tr><td><a href="mongopersist.zope.html"> zope/</a></td> <td style="background: green"> </td> <td>covered 100% (0 of 180 uncovered)</td></tr>
+<tr><td><a href="mongopersist.zope.__init__.html"> __init__.py</a></td> <td style="background: green"> </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"> </td> <td>covered 99% (1 of 695 uncovered)</td></tr>
+<tr><td><a href="mongopersist.zope.html"> zope/</a></td> <td style="background: green"> </td> <td>covered 100% (0 of 180 uncovered)</td></tr>
+<tr><td><a href="mongopersist.zope.container.html"> container.py</a></td> <td style="background: green"> </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 "AS IS" 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">"""Mongo Persistence Zope Containers"""</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">"%02x"</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"> </td> <td>covered 99% (1 of 695 uncovered)</td></tr>
+<tr><td><a href="mongopersist.zope.html"> zope/</a></td> <td style="background: green"> </td> <td>covered 100% (0 of 180 uncovered)</td></tr>
+<tr><td><a href="mongopersist.zope.__init__.html"> __init__.py</a></td> <td style="background: green"> </td> <td>covered 100% (0 of 1 uncovered)</td></tr>
+<tr><td><a href="mongopersist.zope.container.html"> container.py</a></td> <td style="background: green"> </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