[Checkins] SVN: lovely.session/trunk/ initial import
Manfred Schwendinger
manfred.schwendiger at lovelysystems.com
Mon Dec 10 08:00:00 EST 2007
Log message for revision 82223:
initial import
Changed:
A lovely.session/trunk/
A lovely.session/trunk/CHANGES.txt
A lovely.session/trunk/LICENSE.txt
A lovely.session/trunk/bootstrap.py
A lovely.session/trunk/buildout.cfg
A lovely.session/trunk/setup.py
A lovely.session/trunk/src/
A lovely.session/trunk/src/lovely/
A lovely.session/trunk/src/lovely/__init__.py
A lovely.session/trunk/src/lovely/session/
A lovely.session/trunk/src/lovely/session/README.txt
A lovely.session/trunk/src/lovely/session/__init__.py
A lovely.session/trunk/src/lovely/session/configure.zcml
A lovely.session/trunk/src/lovely/session/interfaces.py
A lovely.session/trunk/src/lovely/session/lovely.session-configure.zcml
A lovely.session/trunk/src/lovely/session/memcached.py
A lovely.session/trunk/src/lovely/session/tests.py
-=-
Added: lovely.session/trunk/CHANGES.txt
===================================================================
--- lovely.session/trunk/CHANGES.txt (rev 0)
+++ lovely.session/trunk/CHANGES.txt 2007-12-10 12:59:59 UTC (rev 82223)
@@ -0,0 +1,15 @@
+==============
+lovely.session
+==============
+
+2007/08/13 0.1.2
+----------------
+
+- move source to zope.org
+
+
+2007/08/13 0.1.1
+----------------
+
+- fixed dependency on lovely.memcached
+
Property changes on: lovely.session/trunk/CHANGES.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: lovely.session/trunk/LICENSE.txt
===================================================================
--- lovely.session/trunk/LICENSE.txt (rev 0)
+++ lovely.session/trunk/LICENSE.txt 2007-12-10 12:59:59 UTC (rev 82223)
@@ -0,0 +1,42 @@
+ZPL 2.1
+Zope Public License (ZPL) Version 2.1
+
+A copyright notice accompanies this license document that identifies
+the copyright holders.
+
+This license has been certified as open source. It has also been
+designated as GPL compatible by the Free Software Foundation (FSF).
+
+Redistribution and use in source and binary forms,
+with or without modification, are permitted provided that the following
+conditions are met:
+
+Redistributions in source code must retain the accompanying copyright
+notice, this list of conditions, and the following disclaimer.
+Redistributions in binary form must reproduce the accompanying
+copyright notice, this list of conditions, and the following
+disclaimer in the documentation and/or other materials provided
+with the distribution.
+Names of the copyright holders must not be used to endorse or
+promote products derived from this software without prior written
+permission from the copyright holders.
+The right to distribute this software or to use it for any purpose
+does not give you the right to use Servicemarks (sm) or Trademarks (tm)
+of the copyright holders. Use of them is covered by separate
+agreement with the copyright holders.
+If any files are modified, you must cause the modified files to
+carry prominent notices stating that you changed the files and
+the date of any change.
+
+Disclaimer
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
+EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Property changes on: lovely.session/trunk/LICENSE.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: lovely.session/trunk/bootstrap.py
===================================================================
--- lovely.session/trunk/bootstrap.py (rev 0)
+++ lovely.session/trunk/bootstrap.py 2007-12-10 12:59:59 UTC (rev 82223)
@@ -0,0 +1,47 @@
+##############################################################################
+#
+# Copyright (c) 2007 Lovely Systems 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.
+#
+##############################################################################
+"""
+$Id$
+"""
+
+import os, shutil, sys, tempfile, urllib2
+
+tmpeggs = tempfile.mkdtemp()
+
+ez = {}
+exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py'
+ ).read() in ez
+ez['use_setuptools'](to_dir=tmpeggs, download_delay=0)
+
+import pkg_resources
+
+cmd = 'from setuptools.command.easy_install import main; main()'
+if sys.platform == 'win32':
+ cmd = '"%s"' % cmd # work around spawn lamosity on windows
+
+ws = pkg_resources.working_set
+assert os.spawnle(
+ os.P_WAIT, sys.executable, sys.executable,
+ '-c', cmd, '-mqNxd', tmpeggs, 'zc.buildout',
+ dict(os.environ,
+ PYTHONPATH=
+ ws.find(pkg_resources.Requirement.parse('setuptools')).location
+ ),
+ ) == 0
+
+ws.add_entry(tmpeggs)
+ws.require('zc.buildout')
+import zc.buildout.buildout
+zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap'])
+shutil.rmtree(tmpeggs)
Property changes on: lovely.session/trunk/bootstrap.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: lovely.session/trunk/buildout.cfg
===================================================================
--- lovely.session/trunk/buildout.cfg (rev 0)
+++ lovely.session/trunk/buildout.cfg 2007-12-10 12:59:59 UTC (rev 82223)
@@ -0,0 +1,12 @@
+[buildout]
+develop = .
+parts = test
+extensions = lovely.buildouthttp
+find-links = http://download.lovelysystems.com/eggs/mirror/zope
+ https://download.lovelysystems.com/eggs/tagesspiegel
+
+[test]
+recipe = zc.recipe.testrunner
+eggs = lovely.session [test]
+
+
Added: lovely.session/trunk/setup.py
===================================================================
--- lovely.session/trunk/setup.py (rev 0)
+++ lovely.session/trunk/setup.py 2007-12-10 12:59:59 UTC (rev 82223)
@@ -0,0 +1,50 @@
+import os
+from setuptools import setup, find_packages
+
+def read(*rnames):
+ return open(os.path.join(os.path.dirname(__file__), *rnames)).read()
+
+long_description=(
+ read('src', 'lovely', 'session', 'README.txt')
+ + '\n' +
+ read('CHANGES.txt')
+ + '\n' +
+ 'Download\n'
+ '========\n'
+ )
+
+name='lovely.session'
+setup(
+ name = name
+ version = '0.1.2',
+ author = "Lovely Systems GmbH",
+ author_email = "office at lovelysystems.com",
+ description = "memcache session",
+ long_description = long_description,
+ license = "ZPL 2.1",
+ keywords = "session zope zope3",
+ url = 'https://launchpad.net/lovely.tal',
+ zip_safe = False,
+ packages = find_packages('src'),
+ include_package_data = True,
+ package_dir = {'':'src'},
+ namespace_packages = ['lovely',],
+ install_requires = ['setuptools',
+ 'lovely.memcached',
+ 'zope.app.container',
+ 'zope.app.session',
+ 'zope.schema',
+ ],
+ extras_require = dict(
+ test = ['zope.app.testing',
+ 'zope.interface',
+ 'zope.security',
+ 'zope.testing',]),
+ classifiers = [
+ 'Development Status :: 4 - Beta',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: Zope Public License',
+ 'Topic :: Software Development :: Libraries :: Python Modules',
+ ],
+ )
+
Property changes on: lovely.session/trunk/setup.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: lovely.session/trunk/src/lovely/__init__.py
===================================================================
--- lovely.session/trunk/src/lovely/__init__.py (rev 0)
+++ lovely.session/trunk/src/lovely/__init__.py 2007-12-10 12:59:59 UTC (rev 82223)
@@ -0,0 +1,23 @@
+##############################################################################
+#
+# Copyright (c) 2007 Lovely Systems 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.
+#
+##############################################################################
+"""
+$Id$
+"""
+__docformat__ = "reStructuredText"
+
+
+try:
+ __import__('pkg_resources').declare_namespace(__name__)
+except ImportError:
+ pass
Property changes on: lovely.session/trunk/src/lovely/__init__.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: lovely.session/trunk/src/lovely/session/README.txt
===================================================================
--- lovely.session/trunk/src/lovely/session/README.txt (rev 0)
+++ lovely.session/trunk/src/lovely/session/README.txt 2007-12-10 12:59:59 UTC (rev 82223)
@@ -0,0 +1,68 @@
+===========================
+Session Data Using memcache
+===========================
+
+This package provides a session data manager which stores it's data in
+memcache. The package uses lovely.memcached to store it's data.
+
+IMPORTANT:
+
+This test expects a memcache server running on local port 11211 which
+is the default port for memcached.
+
+This test runs in level 2 because it needs external resources to work. If you
+want to run this test you need to use --all as parameter to your test.
+
+Start a memcache instance with : memcached <optional options>
+
+ >>> from zope import component
+ >>> from lovely.memcached.interfaces import IMemcachedClient
+ >>> from lovely.memcached.utility import MemcachedClient
+ >>> util = MemcachedClient()
+ >>> component.provideUtility(util, IMemcachedClient, name='session')
+ >>> util.invalidateAll()
+
+Now we create a memcache session and connect it to the memcached client.
+
+ >>> from lovely.session.memcached import MemCachedSessionDataContainer
+ >>> sessionData = MemCachedSessionDataContainer()
+ >>> sessionData.cacheName = u'session'
+
+We need to provide a name for the session data manager because it is used to
+identify the cache entry in memcache.
+
+ >>> sessionData.__name__ = 'MemCacheSession'
+
+ >>> session = sessionData['mySessionId']
+ >>> session
+ {}
+ >>> type(session)
+ <class 'lovely.session.memcached.MemCacheSessionData'>
+
+We can now get data from the session.
+
+ >>> data = session['myData']
+ >>> data
+ {}
+ >>> type(data)
+ <class 'lovely.session.memcached.MemCachePkgData'>
+
+ >>> data['info'] = 'stored in memcache'
+ >>> data
+ {'info': 'stored in memcache'}
+
+Because the MemCacheSession is transaction aware we need to commit the
+transaction to store data in the memcache.
+
+ >>> import transaction
+ >>> transaction.commit()
+
+If we now read session data is is read back from the memcache.
+
+ >>> session = sessionData['mySessionId']
+ >>> session['myData']
+ {'info': 'stored in memcache'}
+
+ >>> sessionData.items()
+ [('mySessionId', <lovely.session.memcached.DataManager object at ...>)]
+
Property changes on: lovely.session/trunk/src/lovely/session/README.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: lovely.session/trunk/src/lovely/session/__init__.py
===================================================================
Property changes on: lovely.session/trunk/src/lovely/session/__init__.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: lovely.session/trunk/src/lovely/session/configure.zcml
===================================================================
--- lovely.session/trunk/src/lovely/session/configure.zcml (rev 0)
+++ lovely.session/trunk/src/lovely/session/configure.zcml 2007-12-10 12:59:59 UTC (rev 82223)
@@ -0,0 +1,49 @@
+<configure
+ xmlns="http://namespaces.zope.org/zope"
+ xmlns:browser="http://namespaces.zope.org/browser"
+ i18n_domain="zope"
+ >
+
+ <class class=".memcached.MemCachedSessionDataContainer">
+ <implements
+ interface="zope.annotation.interfaces.IAttributeAnnotatable"
+ />
+
+ <require
+ permission="zope.Public"
+ interface="zope.app.session.interfaces.ISessionDataContainer"
+ />
+
+ <require
+ permission="zope.ManageServices"
+ set_schema="zope.app.session.interfaces.ISessionDataContainer"
+ />
+
+ <require
+ permission="zope.Public"
+ interface=".interfaces.IMemCachedSessionDataContainer"
+ />
+
+ <require
+ permission="zope.ManageServices"
+ set_schema=".interfaces.IMemCachedSessionDataContainer"
+ />
+ </class>
+
+ <browser:addMenuItem
+ title="Sessiondata in memcache"
+ description="Session data memcache"
+ class=".memcached.MemCachedSessionDataContainer"
+ permission="zope.ManageServices"
+ />
+
+ <browser:editform
+ schema=".interfaces.IMemCachedSessionDataContainer"
+ label="Configure"
+ name="configure.html"
+ menu="zmi_views" title="Configure"
+ permission="zope.ManageServices"
+ />
+
+</configure>
+
Property changes on: lovely.session/trunk/src/lovely/session/configure.zcml
___________________________________________________________________
Name: svn:eol-style
+ native
Added: lovely.session/trunk/src/lovely/session/interfaces.py
===================================================================
--- lovely.session/trunk/src/lovely/session/interfaces.py (rev 0)
+++ lovely.session/trunk/src/lovely/session/interfaces.py 2007-12-10 12:59:59 UTC (rev 82223)
@@ -0,0 +1,29 @@
+##############################################################################
+#
+# Copyright (c) 2007 Lovely Systems 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.
+#
+##############################################################################
+"""
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+from zope import interface
+from zope import schema
+
+
+class IMemCachedSessionDataContainer(interface.Interface):
+ """A session container using memcache"""
+
+ cacheName = schema.TextLine(title=u'Cachename',
+ required=False,
+ default=u'')
+
Property changes on: lovely.session/trunk/src/lovely/session/interfaces.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: lovely.session/trunk/src/lovely/session/lovely.session-configure.zcml
===================================================================
--- lovely.session/trunk/src/lovely/session/lovely.session-configure.zcml (rev 0)
+++ lovely.session/trunk/src/lovely/session/lovely.session-configure.zcml 2007-12-10 12:59:59 UTC (rev 82223)
@@ -0,0 +1 @@
+<include package="lovely.session" />
Property changes on: lovely.session/trunk/src/lovely/session/lovely.session-configure.zcml
___________________________________________________________________
Name: svn:eol-style
+ native
Added: lovely.session/trunk/src/lovely/session/memcached.py
===================================================================
--- lovely.session/trunk/src/lovely/session/memcached.py (rev 0)
+++ lovely.session/trunk/src/lovely/session/memcached.py 2007-12-10 12:59:59 UTC (rev 82223)
@@ -0,0 +1,197 @@
+##############################################################################
+#
+# Copyright (c) 2007 Lovely Systems 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.
+#
+##############################################################################
+"""
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import md5
+import cPickle
+import memcache
+import time
+from threading import local
+
+import persistent
+import transaction
+from transaction.interfaces import IDataManager
+
+from zope import interface
+from zope import component
+
+from zope.schema.fieldproperty import FieldProperty
+
+from zope.app.container.contained import Contained
+from zope.app.session.interfaces import (
+ ISessionDataContainer,
+ ISessionData,
+ ISessionPkgData,
+ )
+from zope.app.session.session import PersistentSessionDataContainer
+
+from lovely.memcached.interfaces import IMemcachedClient
+
+from interfaces import IMemCachedSessionDataContainer
+
+
+class MemCachedSessionDataContainer(persistent.Persistent, Contained):
+ interface.implements(ISessionDataContainer, IMemCachedSessionDataContainer)
+
+ cacheName = FieldProperty(IMemCachedSessionDataContainer['cacheName'])
+
+ def __init__(self):
+ self.resolution = 5*60
+ self.timeout = 1 * 60 * 60
+
+ def get(self, key, default=None):
+ return self[key]
+
+ def __contains__(self, key):
+ return key in self.dataManagers
+
+ def __getitem__(self, key):
+ # getitem never fails to make sure the session uses our own PkgData
+ # implementation.
+ if key in self.dataManagers:
+ dm = self.dataManagers[key]
+ else:
+ dm = DataManager(self, key)
+ txn = transaction.manager.get()
+ txn.join(dm)
+ self.dataManagers[key] = dm
+ item = self.client.query(self._buildKey(key))
+ if item is None:
+ item = MemCacheSessionData()
+ dm.data = item
+ return dm.data
+
+ def __setitem__(self, key, value):
+ pass
+
+ def items(self):
+ return self.dataManagers.items()
+
+ def _commitManager(self, key):
+ if key in self.dataManagers:
+ dm = self.dataManagers[key]
+ if not dm.changed:
+ del self.dataManagers[key]
+ return
+ if self.client.set(dm.data,
+ self._buildKey(key),
+ self.timeout):
+ del self.dataManagers[key]
+
+ def _abortManager(self, key):
+ #TODO: ???
+ if key in self.dataManagers:
+ del self.dataManagers[key]
+
+ def _buildKey(self, key):
+ m = md5.new(key)
+ return 'lovely.session.memcached:%s/%s'%(
+ self.__name__.encode('utf-8'),
+ m.hexdigest())
+
+ @property
+ def dataManagers(self):
+ res = getattr(self.storage, 'dataManagers', None)
+ if res is None:
+ res = {}
+ self.storage.dataManagers = res
+ return res
+
+ @property
+ def client(self):
+ return component.getUtility(IMemcachedClient, self.cacheName)
+
+ @property
+ def storage(self):
+ # we use a thread local storage to have a memcache client for every
+ # thread.
+ if not hasattr(self, '_v_storage'):
+ self._v_storage = local()
+ return self._v_storage
+
+
+class MemCacheData(dict):
+ """See zope.app.session.interfaces.ISessionData
+ """
+ interface.implements(ISessionPkgData)
+
+
+class MemCacheSessionData(dict):
+ """See zope.app.session.interfaces.ISessionData"""
+ interface.implements(ISessionData)
+ lastAccessTime = 0
+
+ def __init__(self):
+ self.lastAccessTime = int(time.time())
+
+ def __getitem__(self, key):
+ try:
+ return super(MemCacheSessionData, self).__getitem__(key)
+ except KeyError:
+ data = MemCachePkgData()
+ self[key] = data
+ return data
+
+
+class MemCachePkgData(dict):
+ """"""
+ interface.implements(ISessionPkgData)
+
+
+class DataManager(object):
+ """A data manager for the transaction management of the session data"""
+ interface.implements(IDataManager)
+
+ def __init__(self, sessionData, key):
+ self.sessionData = sessionData
+ self._data = None
+ self.key = key
+
+ @apply
+ def data():
+ def get(self):
+ return self._data
+ def set(self, value):
+ self._data = value
+ self._oldData = cPickle.dumps(value)
+ return property(get, set)
+
+ @property
+ def changed(self):
+ return cPickle.dumps(self.data)!=self._oldData
+
+ def abort(self, trans):
+ self.sessionData._abortManager(self.key)
+
+ def tpc_begin(self, trans):
+ pass
+
+ def commit(self, trans):
+ pass
+
+ def tpc_vote(self, trans):
+ pass
+
+ def tpc_finish(self, trans):
+ self.sessionData._commitManager(self.key)
+
+ def tpc_abort(self, trans):
+ self.sessionData._abortManager(self.key)
+
+ def sortKey(self):
+ return str(id(self))
+
Property changes on: lovely.session/trunk/src/lovely/session/memcached.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: lovely.session/trunk/src/lovely/session/tests.py
===================================================================
--- lovely.session/trunk/src/lovely/session/tests.py (rev 0)
+++ lovely.session/trunk/src/lovely/session/tests.py 2007-12-10 12:59:59 UTC (rev 82223)
@@ -0,0 +1,39 @@
+##############################################################################
+#
+# Copyright (c) 2007 Lovely Systems 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.
+#
+##############################################################################
+"""
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import unittest
+from zope import component
+import zope.interface
+import zope.security
+from zope.testing import doctest
+from zope.testing.doctestunit import DocTestSuite, DocFileSuite
+from zope.app.testing import setup
+
+
+def test_suite():
+ level2Suites = (
+ DocFileSuite('README.txt',
+ optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
+ ),
+ )
+ for suite in level2Suites:
+ suite.level = 2
+ return unittest.TestSuite(level2Suites)
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='test_suite')
Property changes on: lovely.session/trunk/src/lovely/session/tests.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
More information about the Checkins
mailing list