[Zope3-checkins] SVN: Zope3/branches/srichter-blow-services/ Added
some bbb and updated the final packages to the new layout. Merged
Stephan Richter
srichter at cosmos.phy.tufts.edu
Mon Jan 17 10:30:11 EST 2005
Log message for revision 28853:
Added some bbb and updated the final packages to the new layout. Merged
trunk.
Changed:
D Zope3/branches/srichter-blow-services/package-includes/zope.app.registration.fssync-configure.zcml
D Zope3/branches/srichter-blow-services/package-includes/zope.app.site.fssync-configure.zcml
D Zope3/branches/srichter-blow-services/package-includes/zope.app.utility.fssync-configure.zcml
U Zope3/branches/srichter-blow-services/src/BTrees/Interfaces.py
U Zope3/branches/srichter-blow-services/src/BTrees/check.py
U Zope3/branches/srichter-blow-services/src/BTrees/floatvaluemacros.h
U Zope3/branches/srichter-blow-services/src/BTrees/tests/testBTrees.py
U Zope3/branches/srichter-blow-services/src/ZEO/ClientStorage.py
U Zope3/branches/srichter-blow-services/src/ZEO/__init__.py
U Zope3/branches/srichter-blow-services/src/ZEO/cache.py
U Zope3/branches/srichter-blow-services/src/ZEO/stats.py
U Zope3/branches/srichter-blow-services/src/ZEO/tests/ConnectionTests.py
U Zope3/branches/srichter-blow-services/src/ZEO/tests/test_cache.py
U Zope3/branches/srichter-blow-services/src/ZEO/version.txt
U Zope3/branches/srichter-blow-services/src/ZODB/BaseStorage.py
U Zope3/branches/srichter-blow-services/src/ZODB/ConflictResolution.py
U Zope3/branches/srichter-blow-services/src/ZODB/Connection.py
U Zope3/branches/srichter-blow-services/src/ZODB/DB.py
U Zope3/branches/srichter-blow-services/src/ZODB/DemoStorage.py
U Zope3/branches/srichter-blow-services/src/ZODB/ExportImport.py
U Zope3/branches/srichter-blow-services/src/ZODB/FileStorage/FileStorage.py
U Zope3/branches/srichter-blow-services/src/ZODB/FileStorage/fsdump.py
A Zope3/branches/srichter-blow-services/src/ZODB/FileStorage/fsoids.py
U Zope3/branches/srichter-blow-services/src/ZODB/FileStorage/fspack.py
U Zope3/branches/srichter-blow-services/src/ZODB/MappingStorage.py
U Zope3/branches/srichter-blow-services/src/ZODB/Mount.py
U Zope3/branches/srichter-blow-services/src/ZODB/POSException.py
U Zope3/branches/srichter-blow-services/src/ZODB/__init__.py
A Zope3/branches/srichter-blow-services/src/ZODB/collaborations.txt
U Zope3/branches/srichter-blow-services/src/ZODB/dbmStorage.py
U Zope3/branches/srichter-blow-services/src/ZODB/interfaces.py
U Zope3/branches/srichter-blow-services/src/ZODB/serialize.py
A Zope3/branches/srichter-blow-services/src/ZODB/tests/dbopen.txt
D Zope3/branches/srichter-blow-services/src/ZODB/tests/loggingsupport.py
U Zope3/branches/srichter-blow-services/src/ZODB/tests/testConnection.py
U Zope3/branches/srichter-blow-services/src/ZODB/tests/testDB.py
U Zope3/branches/srichter-blow-services/src/ZODB/tests/testFileStorage.py
U Zope3/branches/srichter-blow-services/src/ZODB/tests/testUtils.py
U Zope3/branches/srichter-blow-services/src/ZODB/tests/testZODB.py
A Zope3/branches/srichter-blow-services/src/ZODB/tests/test_doctest_files.py
A Zope3/branches/srichter-blow-services/src/ZODB/tests/testfsoids.py
U Zope3/branches/srichter-blow-services/src/ZODB/utils.py
U Zope3/branches/srichter-blow-services/src/bugtracker/browser/bug.py
U Zope3/branches/srichter-blow-services/src/bugtracker/browser/bug_edit.pt
U Zope3/branches/srichter-blow-services/src/bugtracker/browser/comment.py
U Zope3/branches/srichter-blow-services/src/bugtracker/tests/placelesssetup.py
U Zope3/branches/srichter-blow-services/src/bugtracker/tests/test_mail.py
U Zope3/branches/srichter-blow-services/src/bugtracker/tests/test_vocabularies.py
U Zope3/branches/srichter-blow-services/src/bugtracker/vocabulary.py
U Zope3/branches/srichter-blow-services/src/persistent/cPersistence.c
U Zope3/branches/srichter-blow-services/src/persistent/interfaces.py
U Zope3/branches/srichter-blow-services/src/persistent/tests/test_persistent.py
U Zope3/branches/srichter-blow-services/src/transaction/_manager.py
U Zope3/branches/srichter-blow-services/src/transaction/_transaction.py
U Zope3/branches/srichter-blow-services/src/transaction/interfaces.py
A Zope3/branches/srichter-blow-services/src/transaction/tests/test_SampleResourceManager.py
_U Zope3/branches/srichter-blow-services/src/z3checkins/__init__.py
A Zope3/branches/srichter-blow-services/src/z3checkins/browser.py
U Zope3/branches/srichter-blow-services/src/z3checkins/configure.zcml
UU Zope3/branches/srichter-blow-services/src/z3checkins/folder.py
UU Zope3/branches/srichter-blow-services/src/z3checkins/interfaces.py
UU Zope3/branches/srichter-blow-services/src/z3checkins/message.py
D Zope3/branches/srichter-blow-services/src/z3checkins/message_plain.pt
U Zope3/branches/srichter-blow-services/src/z3checkins/tests/svn_msg4.txt
A Zope3/branches/srichter-blow-services/src/z3checkins/tests/test_browser.py
A Zope3/branches/srichter-blow-services/src/z3checkins/tests/test_folder.py
U Zope3/branches/srichter-blow-services/src/z3checkins/tests/test_message.py
A Zope3/branches/srichter-blow-services/src/z3checkins/tests/test_timeutils.py
A Zope3/branches/srichter-blow-services/src/z3checkins/timeutils.py
U Zope3/branches/srichter-blow-services/src/zope/app/apidoc/classmodule/menu.pt
U Zope3/branches/srichter-blow-services/src/zope/app/applicationcontrol/browser/zodbcontrol.pt
U Zope3/branches/srichter-blow-services/src/zope/app/catalog/browser/advanced.pt
U Zope3/branches/srichter-blow-services/src/zope/app/component/__init__.py
A Zope3/branches/srichter-blow-services/src/zope/app/component/bbb/
A Zope3/branches/srichter-blow-services/src/zope/app/component/bbb/__init__.py
A Zope3/branches/srichter-blow-services/src/zope/app/component/bbb/localservice.py
U Zope3/branches/srichter-blow-services/src/zope/app/component/browser/configure.zcml
U Zope3/branches/srichter-blow-services/src/zope/app/configure.zcml
U Zope3/branches/srichter-blow-services/src/zope/app/container/browser/contents.pt
U Zope3/branches/srichter-blow-services/src/zope/app/container/browser/metaconfigure.py
U Zope3/branches/srichter-blow-services/src/zope/app/dav/configure.zcml
U Zope3/branches/srichter-blow-services/src/zope/app/dublincore/browser/edit.pt
U Zope3/branches/srichter-blow-services/src/zope/app/event/dispatching.py
U Zope3/branches/srichter-blow-services/src/zope/app/exception/browser/tests/test_unauthorized.py
U Zope3/branches/srichter-blow-services/src/zope/app/file/browser/file_add.pt
U Zope3/branches/srichter-blow-services/src/zope/app/file/browser/file_upload.pt
U Zope3/branches/srichter-blow-services/src/zope/app/form/browser/edit.pt
U Zope3/branches/srichter-blow-services/src/zope/app/form/browser/editwizard.pt
U Zope3/branches/srichter-blow-services/src/zope/app/homefolder/homefolder.pt
U Zope3/branches/srichter-blow-services/src/zope/app/introspector/introspector.pt
U Zope3/branches/srichter-blow-services/src/zope/app/introspector/marker.pt
U Zope3/branches/srichter-blow-services/src/zope/app/meta.zcml
U Zope3/branches/srichter-blow-services/src/zope/app/observable/README.txt
U Zope3/branches/srichter-blow-services/src/zope/app/pau/README.txt
U Zope3/branches/srichter-blow-services/src/zope/app/pau/browser/groupfolder.txt
U Zope3/branches/srichter-blow-services/src/zope/app/pau/browser/principalfolder.txt
U Zope3/branches/srichter-blow-services/src/zope/app/pau/groupfolder.py
U Zope3/branches/srichter-blow-services/src/zope/app/pau/groupfolder.txt
U Zope3/branches/srichter-blow-services/src/zope/app/pau/pau.py
U Zope3/branches/srichter-blow-services/src/zope/app/pau/principalfolder.txt
U Zope3/branches/srichter-blow-services/src/zope/app/pluggableauth/__init__.py
U Zope3/branches/srichter-blow-services/src/zope/app/pluggableauth/browser/configure.zcml
U Zope3/branches/srichter-blow-services/src/zope/app/pluggableauth/configure.zcml
U Zope3/branches/srichter-blow-services/src/zope/app/pluggableauth/interfaces.py
U Zope3/branches/srichter-blow-services/src/zope/app/pluggableauth/tests/authsetup.py
U Zope3/branches/srichter-blow-services/src/zope/app/pluggableauth/tests/test_pluggableauth.py
U Zope3/branches/srichter-blow-services/src/zope/app/presentation/__init__.py
U Zope3/branches/srichter-blow-services/src/zope/app/presentation/browser/configure.zcml
U Zope3/branches/srichter-blow-services/src/zope/app/presentation/browser/zpt.zcml
U Zope3/branches/srichter-blow-services/src/zope/app/presentation/configure.zcml
U Zope3/branches/srichter-blow-services/src/zope/app/presentation/presentation.py
U Zope3/branches/srichter-blow-services/src/zope/app/presentation/xxx_Tests/test_presentation.py
U Zope3/branches/srichter-blow-services/src/zope/app/publication/httpfactory.py
U Zope3/branches/srichter-blow-services/src/zope/app/publication/interfaces.py
A Zope3/branches/srichter-blow-services/src/zope/app/publication/soap.py
U Zope3/branches/srichter-blow-services/src/zope/app/publication/zopepublication.py
U Zope3/branches/srichter-blow-services/src/zope/app/pythonpage/edit.pt
U Zope3/branches/srichter-blow-services/src/zope/app/rdb/browser/configure.zcml
U Zope3/branches/srichter-blow-services/src/zope/app/schema/browser/configure.zcml
U Zope3/branches/srichter-blow-services/src/zope/app/schema/browser/schema_edit.pt
U Zope3/branches/srichter-blow-services/src/zope/app/schema/fields.zcml
U Zope3/branches/srichter-blow-services/src/zope/app/schema/tests/test_fieldfactory.zcml
U Zope3/branches/srichter-blow-services/src/zope/app/schemacontent/browser/configure.zcml
U Zope3/branches/srichter-blow-services/src/zope/app/schemacontent/browser/permission_edit.pt
U Zope3/branches/srichter-blow-services/src/zope/app/security/browser/auth.py
U Zope3/branches/srichter-blow-services/src/zope/app/security/browser/configure.zcml
U Zope3/branches/srichter-blow-services/src/zope/app/security/browser/principalterms.txt
U Zope3/branches/srichter-blow-services/src/zope/app/security/configure.zcml
U Zope3/branches/srichter-blow-services/src/zope/app/security/interfaces.py
U Zope3/branches/srichter-blow-services/src/zope/app/security/principal.py
U Zope3/branches/srichter-blow-services/src/zope/app/security/principalregistry.py
U Zope3/branches/srichter-blow-services/src/zope/app/security/tests/test_securitydirectives.py
U Zope3/branches/srichter-blow-services/src/zope/app/security/vocabulary.py
U Zope3/branches/srichter-blow-services/src/zope/app/securitypolicy/browser/granting.pt
U Zope3/branches/srichter-blow-services/src/zope/app/securitypolicy/browser/granting.txt
U Zope3/branches/srichter-blow-services/src/zope/app/securitypolicy/tests/test_principalpermissionmanager.py
U Zope3/branches/srichter-blow-services/src/zope/app/securitypolicy/tests/test_principalrolemanager.py
U Zope3/branches/srichter-blow-services/src/zope/app/securitypolicy/tests/test_securitydirectives.py
U Zope3/branches/srichter-blow-services/src/zope/app/securitypolicy/zopepolicy.txt
A Zope3/branches/srichter-blow-services/src/zope/app/site/
A Zope3/branches/srichter-blow-services/src/zope/app/site/__init__.py
A Zope3/branches/srichter-blow-services/src/zope/app/site/interfaces.py
A Zope3/branches/srichter-blow-services/src/zope/app/site/meta.zcml
A Zope3/branches/srichter-blow-services/src/zope/app/site/metaconfigure.py
A Zope3/branches/srichter-blow-services/src/zope/app/site/tests/
A Zope3/branches/srichter-blow-services/src/zope/app/site/tests/__init__.py
A Zope3/branches/srichter-blow-services/src/zope/app/site/tests/placefulsetup.py
A Zope3/branches/srichter-blow-services/src/zope/app/table/
U Zope3/branches/srichter-blow-services/src/zope/app/workflow/browser/configure.zcml
U Zope3/branches/srichter-blow-services/src/zope/app/workflow/stateful/browser/configure.zcml
U Zope3/branches/srichter-blow-services/src/zope/app/workflow/stateful/browser/contentworkflow_registry.pt
U Zope3/branches/srichter-blow-services/src/zope/app/workflow/stateful/browser/definition_edit.pt
U Zope3/branches/srichter-blow-services/src/zope/app/workflow/stateful/browser/instance_manage.pt
U Zope3/branches/srichter-blow-services/src/zope/app/zapi/README.txt
U Zope3/branches/srichter-blow-services/src/zope/app/zapi/__init__.py
U Zope3/branches/srichter-blow-services/src/zope/app/zptpage/browser/inlinecode.pt
U Zope3/branches/srichter-blow-services/src/zope/component/README.txt
U Zope3/branches/srichter-blow-services/src/zope/component/bbb/service.py
U Zope3/branches/srichter-blow-services/src/zope/event/README.txt
U Zope3/branches/srichter-blow-services/src/zope/interface/README.txt
U Zope3/branches/srichter-blow-services/src/zope/interface/adapter.py
U Zope3/branches/srichter-blow-services/src/zope/schema/README.txt
A Zope3/branches/srichter-blow-services/src/zope/testing/formparser.py
A Zope3/branches/srichter-blow-services/src/zope/testing/formparser.txt
U Zope3/branches/srichter-blow-services/src/zope/testing/tests.py
U Zope3/branches/srichter-blow-services/src/zwiki/browser/add.pt
U Zope3/branches/srichter-blow-services/src/zwiki/browser/wikipage.py
U Zope3/branches/srichter-blow-services/src/zwiki/traversal.py
U Zope3/branches/srichter-blow-services/zopeskel/bin/debugzope.in
U Zope3/branches/srichter-blow-services/zopeskel/bin/i18nextract.in
U Zope3/branches/srichter-blow-services/zopeskel/bin/pyskel.in
U Zope3/branches/srichter-blow-services/zopeskel/bin/test.in
U Zope3/branches/srichter-blow-services/zopeskel/bin/zopectl.in
-=-
Deleted: Zope3/branches/srichter-blow-services/package-includes/zope.app.registration.fssync-configure.zcml
===================================================================
--- Zope3/branches/srichter-blow-services/package-includes/zope.app.registration.fssync-configure.zcml 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/package-includes/zope.app.registration.fssync-configure.zcml 2005-01-17 15:30:10 UTC (rev 28853)
@@ -1,3 +0,0 @@
-<configure>
-<!-- XXX: include package="zope.app.registration.fssync" / -->
-</configure>
Deleted: Zope3/branches/srichter-blow-services/package-includes/zope.app.site.fssync-configure.zcml
===================================================================
--- Zope3/branches/srichter-blow-services/package-includes/zope.app.site.fssync-configure.zcml 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/package-includes/zope.app.site.fssync-configure.zcml 2005-01-17 15:30:10 UTC (rev 28853)
@@ -1,3 +0,0 @@
-<configure>
-<!-- XXX: include package="zope.app.site.fssync" / -->
-</configure>
Deleted: Zope3/branches/srichter-blow-services/package-includes/zope.app.utility.fssync-configure.zcml
===================================================================
--- Zope3/branches/srichter-blow-services/package-includes/zope.app.utility.fssync-configure.zcml 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/package-includes/zope.app.utility.fssync-configure.zcml 2005-01-17 15:30:10 UTC (rev 28853)
@@ -1,3 +0,0 @@
-<configure>
-<!-- XXX: include package="zope.app.utility.fssync" /-->
-</configure>
Modified: Zope3/branches/srichter-blow-services/src/BTrees/Interfaces.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/BTrees/Interfaces.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/BTrees/Interfaces.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -12,8 +12,7 @@
#
##############################################################################
-import OOBTree, Interface
-from Interface import Interface
+from zope.interface import Interface
class ICollection(Interface):
@@ -390,11 +389,3 @@
# Eventually, I need to express this through the interfaces.
#
################################################################
-
-# XXX Need to use the new declaration syntax once it is available
-# for Zope 2.
-
-## OOBTree.OOSet.__implements__=ISet
-## OOBTree.OOTreeSet.__implements__=ITreeSet
-## OOBTree.OOBucket.__implements__=IDictionaryIsh
-## OOBTree.OOBTree.__implements__=IBTree
Modified: Zope3/branches/srichter-blow-services/src/BTrees/check.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/BTrees/check.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/BTrees/check.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -40,7 +40,7 @@
from BTrees.IIBTree import IIBTree, IIBucket, IISet, IITreeSet
from BTrees.IFBTree import IFBTree, IFBucket, IFSet, IFTreeSet
-from ZODB.utils import positive_id
+from ZODB.utils import positive_id, oid_repr
TYPE_UNKNOWN, TYPE_BTREE, TYPE_BUCKET = range(3)
@@ -208,7 +208,11 @@
return keys, values
def type_and_adr(obj):
- return "%s (0x%x)" % (type(obj).__name__, positive_id(obj))
+ if hasattr(obj, '_p_oid'):
+ oid = oid_repr(obj._p_oid)
+ else:
+ oid = 'None'
+ return "%s (0x%x oid=%s)" % (type(obj).__name__, positive_id(obj), oid)
# Walker implements a depth-first search of a BTree (or TreeSet or Set or
# Bucket). Subclasses must implement the visit_btree() and visit_bucket()
Modified: Zope3/branches/srichter-blow-services/src/BTrees/floatvaluemacros.h
===================================================================
--- Zope3/branches/srichter-blow-services/src/BTrees/floatvaluemacros.h 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/BTrees/floatvaluemacros.h 2005-01-17 15:30:10 UTC (rev 28853)
@@ -3,21 +3,21 @@
#define VALUE_TYPE float
#undef VALUE_TYPE_IS_PYOBJECT
-#define TEST_VALUE(K, T) (((K) < (T)) ? -1 : (((K) > (T)) ? 1: 0))
+#define TEST_VALUE(K, T) (((K) < (T)) ? -1 : (((K) > (T)) ? 1: 0))
#define VALUE_SAME(VALUE, TARGET) ( (VALUE) == (TARGET) )
#define DECLARE_VALUE(NAME) VALUE_TYPE NAME
#define VALUE_PARSE "f"
#define DECREF_VALUE(k)
#define INCREF_VALUE(k)
#define COPY_VALUE(V, E) (V=(E))
-#define COPY_VALUE_TO_OBJECT(O, K) O=PyFloat_FromDouble(K)
+#define COPY_VALUE_TO_OBJECT(O, K) O=PyFloat_FromDouble(K)
#define COPY_VALUE_FROM_ARG(TARGET, ARG, STATUS) \
if (PyFloat_Check(ARG)) TARGET = (float)PyFloat_AsDouble(ARG); \
else if (PyInt_Check(ARG)) TARGET = (float)PyInt_AsLong(ARG); \
else { \
PyErr_SetString(PyExc_TypeError, "expected float or int value"); \
- (STATUS)=0; (TARGET)=0; }
-
+ (STATUS)=0; (TARGET)=0; }
+
#define NORMALIZE_VALUE(V, MIN) ((MIN) > 0) ? ((V)/=(MIN)) : 0
#define MERGE_DEFAULT 1.0f
Modified: Zope3/branches/srichter-blow-services/src/BTrees/tests/testBTrees.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/BTrees/tests/testBTrees.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/BTrees/tests/testBTrees.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -206,7 +206,7 @@
lst = list(self.t.values(max=99-x, min=0+x))
lst.sort()
self.assertEqual(lst,range(0+x,99-x+1))
-
+
def testValuesNegativeIndex(self):
L = [-3, 6, -11, 4]
for i in L:
@@ -235,7 +235,7 @@
self.assertEqual(list(lst), range(0+x, 99-x+1))
self.assertEqual(len(v), 100)
-
+
def testKeysNegativeIndex(self):
L = [-3, 6, -11, 4]
for i in L:
@@ -267,7 +267,7 @@
items = list(self.t.iteritems(min=12, max=20))
self.assertEqual(items, zip(range(12, 21), range(24, 43, 2)))
-
+
def testItemsNegativeIndex(self):
L = [-3, 6, -11, 4]
for i in L:
Modified: Zope3/branches/srichter-blow-services/src/ZEO/ClientStorage.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/ZEO/ClientStorage.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/ZEO/ClientStorage.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -309,7 +309,7 @@
cache_path = os.path.join(dir, "%s-%s.zec" % (client, storage))
else:
cache_path = None
- self._cache = self.ClientCacheClass(cache_path)
+ self._cache = self.ClientCacheClass(cache_path, size=cache_size)
# XXX When should it be opened?
self._cache.open()
@@ -353,21 +353,29 @@
self._wait_sync(deadline)
def _wait_sync(self, deadline=None):
- # If there is no mainloop running, this code needs
- # to call poll() to cause asyncore to handle events.
- while 1:
- if self._ready.isSet():
- break
- if deadline and time.time() > deadline:
+ # Log no more than one "waiting" message per LOG_THROTTLE seconds.
+ LOG_THROTTLE = 300 # 5 minutes
+ next_log_time = time.time()
+
+ while not self._ready.isSet():
+ now = time.time()
+ if deadline and now > deadline:
log2("Timed out waiting for connection", level=logging.WARNING)
break
- log2("Waiting for cache verification to finish")
+ if now >= next_log_time:
+ log2("Waiting for cache verification to finish")
+ next_log_time = now + LOG_THROTTLE
if self._connection is None:
# If the connection was closed while we were
# waiting for it to become ready, start over.
- return self._wait(deadline - time.time())
- else:
- self._connection.pending(30)
+ if deadline is None:
+ timeout = None
+ else:
+ timeout = deadline - now
+ return self._wait(timeout)
+ # No mainloop ia running, so we need to call something fancy to
+ # handle asyncore events.
+ self._connection.pending(30)
def close(self):
"""Storage API: finalize the storage, releasing external resources."""
Modified: Zope3/branches/srichter-blow-services/src/ZEO/__init__.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/ZEO/__init__.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/ZEO/__init__.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -22,4 +22,4 @@
"""
# The next line must use double quotes, so release.py recognizes it.
-version = "2.3"
+version = "2.4a0"
Modified: Zope3/branches/srichter-blow-services/src/ZEO/cache.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/ZEO/cache.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/ZEO/cache.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -13,13 +13,13 @@
##############################################################################
"""Disk-based client cache for ZEO.
-ClientCache exposes an API used by the ZEO client storage. FileCache
-stores objects one disk using a 2-tuple of oid and tid as key.
+ClientCache exposes an API used by the ZEO client storage. FileCache stores
+objects on disk using a 2-tuple of oid and tid as key.
-The upper cache's API is similar to a storage API with methods like
-load(), store(), and invalidate(). It manages in-memory data
-structures that allow it to map this richer API onto the simple
-key-based API of the lower-level cache.
+ClientCaches API is similar to a storage API with methods like load(),
+store(), and invalidate(). It manages in-memory data structures that allow
+it to map this richer API onto the simple key-based API of the lower-level
+FileCache.
"""
import bisect
@@ -31,6 +31,8 @@
from ZODB.utils import z64, u64
+logger = logging.getLogger("zeo.cache")
+
##
# A disk-based cache for ZEO clients.
# <p>
@@ -54,9 +56,9 @@
# <h3>Cache verification</h3>
# <p>
# When the client is connected to the server, it receives
-# invalidations every time an object is modified. Whe the client is
-# disconnected, it must perform cache verification to make sure its
-# cached data is synchronized with the storage's current state.
+# invalidations every time an object is modified. When the client is
+# disconnected then reconnects, it must perform cache verification to make
+# sure its cached data is synchronized with the storage's current state.
# <p>
# quick verification
# full verification
@@ -67,23 +69,22 @@
##
# Do we put the constructor here?
- # @param path path of persistent snapshot of cache state
- # @param size maximum size of object data, in bytes
+ # @param path path of persistent snapshot of cache state (a file path)
+ # @param size size of cache file, in bytes
- def __init__(self, path=None, size=None, trace=False):
+ # The default size of 200MB makes a lot more sense than the traditional
+ # default of 20MB. The default here is misleading, though, since
+ # ClientStorage is the only user of ClientCache, and it always passes an
+ # explicit size of its own choosing.
+ def __init__(self, path=None, size=200*1024**2, trace=False):
self.path = path
self.size = size
- self.log = logging.getLogger("zeo.cache")
if trace and path:
self._setup_trace()
else:
self._trace = self._notrace
- # Last transaction seen by the cache, either via setLastTid()
- # or by invalidate().
- self.tid = None
-
# The cache stores objects in a dict mapping (oid, tid) pairs
# to Object() records (see below). The tid is the transaction
# id that wrote the object. An object record includes data,
@@ -95,25 +96,32 @@
# records. The in-memory form can be reconstructed from these
# records.
- # Maps oid to current tid. Used to find compute key for objects.
+ # Maps oid to current tid. Used to compute key for objects.
self.current = {}
+
# Maps oid to list of (start_tid, end_tid) pairs in sorted order.
# Used to find matching key for load of non-current data.
self.noncurrent = {}
- # Map oid to version, tid pair. If there is no entry, the object
+
+ # Map oid to (version, tid) pair. If there is no entry, the object
# is not modified in a version.
self.version = {}
- # A double-linked list is used to manage the cache. It makes
- # decisions about which objects to keep and which to evict.
- self.fc = FileCache(size or 10**6, self.path, self)
+ # A FileCache instance does all the low-level work of storing
+ # and retrieving objects to/from the cache file.
+ self.fc = FileCache(size, self.path, self)
def open(self):
self.fc.scan(self.install)
+ ##
+ # Callback for FileCache.scan(), when a pre-existing file cache is
+ # used. For each object in the file, `install()` is invoked. `f`
+ # is the file object, positioned at the start of the serialized Object.
+ # `ent` is an Entry giving the object's key ((oid, start_tid) pair).
def install(self, f, ent):
- # Called by cache storage layer to insert object
- o = Object.fromFile(f, ent.key, header_only=True)
+ # Called by cache storage layer to insert object.
+ o = Object.fromFile(f, ent.key, skip_data=True)
if o is None:
return
oid = o.key[0]
@@ -122,8 +130,13 @@
elif o.end_tid is None:
self.current[oid] = o.start_tid
else:
- L = self.noncurrent.setdefault(oid, [])
- bisect.insort_left(L, (o.start_tid, o.end_tid))
+ assert o.start_tid < o.end_tid
+ this_span = o.start_tid, o.end_tid
+ span_list = self.noncurrent.get(oid)
+ if span_list:
+ bisect.insort_left(span_list, this_span)
+ else:
+ self.noncurrent[oid] = [this_span]
def close(self):
self.fc.close()
@@ -139,7 +152,7 @@
##
# Return the last transaction seen by the cache.
# @return a transaction id
- # @defreturn string
+ # @defreturn string, or None if no transaction is yet known
def getLastTid(self):
if self.fc.tid == z64:
@@ -151,7 +164,7 @@
# Return the current data record for oid and version.
# @param oid object id
# @param version a version string
- # @return data record, serial number, tid or None if the object is not
+ # @return (data record, serial number, tid), or None if the object is not
# in the cache
# @defreturn 3-tuple: (string, string, string)
@@ -207,7 +220,7 @@
return o.data, o.start_tid, o.end_tid
##
- # Return the version an object is modified in or None for an
+ # Return the version an object is modified in, or None for an
# object that is not modified in a version.
# @param oid object id
# @return name of version in which the object is modified
@@ -225,7 +238,7 @@
# @param oid object id
# @param version name of version that oid was modified in. The cache
# only stores current version data, so end_tid should
- # be None.
+ # be None if version is not the empty string.
# @param start_tid the id of the transaction that wrote this revision
# @param end_tid the id of the transaction that created the next
# revision of oid. If end_tid is None, the data is
@@ -235,11 +248,11 @@
def store(self, oid, version, start_tid, end_tid, data):
# It's hard for the client to avoid storing the same object
- # more than once. One case is whether the client requests
+ # more than once. One case is when the client requests
# version data that doesn't exist. It checks the cache for
# the requested version, doesn't find it, then asks the server
# for that data. The server returns the non-version data,
- # which may already by in the cache.
+ # which may already be in the cache.
if (oid, start_tid) in self.fc:
return
o = Object((oid, start_tid), version, data, start_tid, end_tid)
@@ -274,48 +287,97 @@
self.fc.add(o)
##
- # Mark the current data for oid as non-current. If there is no
- # current data for oid, do nothing.
+ # Remove all knowledge of noncurrent revisions of oid, both in
+ # self.noncurrent and in our FileCache. `version` and `tid` are used
+ # only for trace records.
+ def _remove_noncurrent_revisions(self, oid, version, tid):
+ noncurrent_list = self.noncurrent.get(oid)
+ if noncurrent_list:
+ # Note: must iterate over a copy of noncurrent_list. The
+ # FileCache remove() calls our _evicted() method, and that
+ # mutates the list.
+ for old_tid, dummy in noncurrent_list[:]:
+ # 0x1E = invalidate (hit, discarding current or non-current)
+ self._trace(0x1E, oid, version, tid)
+ self.fc.remove((oid, old_tid))
+ del self.noncurrent[oid]
+
+ ##
+ # If `tid` is None, or we have data for `oid` in a (non-empty) version,
+ # forget all knowledge of `oid`. (`tid` can be None only for
+ # invalidations generated by startup cache verification.) If `tid`
+ # isn't None, we don't have version data for `oid`, and we had current
+ # data for `oid`, stop believing we have current data, and mark the
+ # data we had as being valid only up to `tid`. In all other cases, do
+ # nothing.
# @param oid object id
# @param version name of version to invalidate.
- # @param tid the id of the transaction that wrote a new revision of oid
-
+ # @param tid the id of the transaction that wrote a new revision of oid,
+ # or None to forget all cached info about oid (version, current
+ # revision, and non-current revisions)
def invalidate(self, oid, version, tid):
- if tid > self.fc.tid:
+ if tid > self.fc.tid and tid is not None:
self.fc.settid(tid)
+
+ remove_all_knowledge_of_oid = tid is None
+
if oid in self.version:
+ # Forget we know about the version data.
+ # 0x1A = invalidate (hit, version)
self._trace(0x1A, oid, version, tid)
dllversion, dlltid = self.version[oid]
assert not version or version == dllversion, (version, dllversion)
- # remove() will call unlink() to delete from self.version
self.fc.remove((oid, dlltid))
- # And continue on, we must also remove any non-version data
- # from the cache. This is a bit of a failure of the current
- # cache consistency approach as the new tid of the version
- # data gets confused with the old tid of the non-version data.
- # I could sort this out, but it seems simpler to punt and
- # have the cache invalidation too much for versions.
+ assert oid not in self.version # .remove() got rid of it
+ # And continue: we must also remove any non-version data from
+ # the cache. Or, at least, I have such a poor understanding of
+ # versions that anything less drastic would probably be wrong.
+ remove_all_knowledge_of_oid = True
- if oid not in self.current:
+ if remove_all_knowledge_of_oid:
+ self._remove_noncurrent_revisions(oid, version, tid)
+
+ # Only current, non-version data remains to be handled.
+
+ cur_tid = self.current.get(oid)
+ if not cur_tid:
+ # 0x10 == invalidate (miss)
self._trace(0x10, oid, version, tid)
return
- cur_tid = self.current.pop(oid)
+
+ # We had current data for oid, but no longer.
+
+ if remove_all_knowledge_of_oid:
+ # 0x1E = invalidate (hit, discarding current or non-current)
+ self._trace(0x1E, oid, version, tid)
+ self.fc.remove((oid, cur_tid))
+ assert cur_tid not in self.current # .remove() got rid of it
+ return
+
+ # Add the data we have to the list of non-current data for oid.
+ assert tid is not None and cur_tid < tid
+ # 0x1C = invalidate (hit, saving non-current)
+ self._trace(0x1C, oid, version, tid)
+ del self.current[oid] # because we no longer have current data
+
+ # Update the end_tid half of oid's validity range on disk.
# XXX Want to fetch object without marking it as accessed
o = self.fc.access((oid, cur_tid))
+ assert o is not None
+ assert o.end_tid is None # i.e., o was current
if o is None:
- # XXX is this possible?
- return None
+ # XXX is this possible? (doubt it; added an assert just above)
+ return
o.end_tid = tid
- self.fc.update(o)
- self._trace(0x1C, oid, version, tid)
+ self.fc.update(o) # record the new end_tid on disk
+ # Add to oid's list of non-current data.
L = self.noncurrent.setdefault(oid, [])
bisect.insort_left(L, (cur_tid, tid))
##
# Return the number of object revisions in the cache.
-
+ #
# XXX just return len(self.cache)?
-
def __len__(self):
n = len(self.current) + len(self.version)
if self.noncurrent:
@@ -323,7 +385,7 @@
return n
##
- # Generates over, version, serial triples for all objects in the
+ # Generates (oid, serial, version) triples for all objects in the
# cache. This generator is used by cache verification.
def contents(self):
@@ -346,14 +408,14 @@
print oid_repr(oid), oid_repr(tid), repr(version)
print "dll contents"
L = list(self.fc)
- L.sort(lambda x,y:cmp(x.key, y.key))
+ L.sort(lambda x, y: cmp(x.key, y.key))
for x in L:
end_tid = x.end_tid or z64
print oid_repr(x.key[0]), oid_repr(x.key[1]), oid_repr(end_tid)
print
def _evicted(self, o):
- # Called by Object o to signal its eviction
+ # Called by the FileCache to signal that Object o has been evicted.
oid, tid = o.key
if o.end_tid is None:
if o.version:
@@ -361,7 +423,7 @@
else:
del self.current[oid]
else:
- # XXX Although we use bisect to keep the list sorted,
+ # Although we use bisect to keep the list sorted,
# we never expect the list to be very long. So the
# brute force approach should normally be fine.
L = self.noncurrent[oid]
@@ -375,8 +437,8 @@
self._trace(0x00)
except IOError, msg:
self.tracefile = None
- self.log.warning("Could not write to trace file %s: %s",
- tfn, msg)
+ logger.warning("Could not write to trace file %s: %s",
+ tfn, msg)
def _notrace(self, *arg, **kwargs):
pass
@@ -419,82 +481,135 @@
# data and whether it is in a version.
# <p>
# The serialized format does not include the key, because it is stored
-# in the header used by the cache's storage format.
+# in the header used by the cache file's storage format.
+# <p>
+# Instances of Object are generally short-lived -- they're really a way to
+# package data on the way to or from the disk file.
class Object(object):
- __slots__ = (# pair, object id, txn id -- something usable as a dict key
- # the second part of the part is equal to start_tid below
+ __slots__ = (# pair (object id, txn id) -- something usable as a dict key;
+ # the second part of the pair is equal to start_tid
"key",
- "start_tid", # string, id of txn that wrote the data
- "end_tid", # string, id of txn that wrote next revision
- # or None
- "version", # string, name of version
- "data", # string, the actual data record for the object
+ # string, tid of txn that wrote the data
+ "start_tid",
- "size", # total size of serialized object
+ # string, tid of txn that wrote next revision, or None
+ # if the data is current; if not None, end_tid is strictly
+ # greater than start_tid
+ "end_tid",
+
+ # string, name of version
+ "version",
+
+ # string, the actual data record for the object
+ "data",
+
+ # total size of serialized object; this includes the
+ # data, version, and all overhead (header) bytes.
+ "size",
)
+ # A serialized Object on disk looks like:
+ #
+ # offset # bytes value
+ # ------ ------- -----
+ # 0 8 end_tid; string
+ # 8 2 len(version); 2-byte signed int
+ # 10 4 len(data); 4-byte signed int
+ # 14 len(version) version; string
+ # 14+len(version) len(data) the object pickle; string
+ # 14+len(version)+
+ # len(data) 8 oid; string
+
+ # The serialization format uses an end tid of "\0"*8 (z64), the least
+ # 8-byte string, to represent None. It isn't possible for an end_tid
+ # to be 0, because it must always be strictly greater than the start_tid.
+
+ fmt = ">8shi" # end_tid, len(self.version), len(self.data)
+ FIXED_HEADER_SIZE = struct.calcsize(fmt)
+ assert FIXED_HEADER_SIZE == 14
+ TOTAL_FIXED_SIZE = FIXED_HEADER_SIZE + 8 # +8 for the oid at the end
+
def __init__(self, key, version, data, start_tid, end_tid):
self.key = key
self.version = version
self.data = data
self.start_tid = start_tid
self.end_tid = end_tid
- # The size of a the serialized object on disk, include the
- # 14-byte header, the length of data and version, and a
+ # The size of the serialized object on disk, including the
+ # 14-byte header, the lengths of data and version, and a
# copy of the 8-byte oid.
if data is not None:
- self.size = 22 + len(data) + len(version)
+ self.size = self.TOTAL_FIXED_SIZE + len(data) + len(version)
- # The serialization format uses an end tid of "\0" * 8, the least
- # 8-byte string, to represent None. It isn't possible for an
- # end_tid to be 0, because it must always be strictly greater
- # than the start_tid.
+ ##
+ # Return the fixed-sized serialization header as a string: pack end_tid,
+ # and the lengths of the .version and .data members.
+ def get_header(self):
+ return struct.pack(self.fmt,
+ self.end_tid or z64,
+ len(self.version),
+ len(self.data))
- fmt = ">8shi"
-
+ ##
+ # Write the serialized representation of self to file f, at its current
+ # position.
def serialize(self, f):
- # Write standard form of Object to file, f.
- self.serialize_header(f)
- f.write(self.data)
- f.write(self.key[0])
+ f.writelines([self.get_header(),
+ self.version,
+ self.data,
+ self.key[0]])
+ ##
+ # Write the fixed-size header for self, to file f at its current position.
+ # The only real use for this is when the current revision of an object
+ # in cache is invalidated. Then the end_tid field gets set to the tid
+ # of the transaction that caused the invalidation.
def serialize_header(self, f):
- s = struct.pack(self.fmt, self.end_tid or "\0" * 8,
- len(self.version), len(self.data))
- f.write(s)
- f.write(self.version)
+ f.write(self.get_header())
- def fromFile(cls, f, key, header_only=False):
- s = f.read(struct.calcsize(cls.fmt))
+ ##
+ # fromFile is a class constructor, unserializing an Object from the
+ # current position in file f. Exclusive access to f for the duration
+ # is assumed. The key is a (oid, start_tid) pair, and the oid must
+ # match the serialized oid. If `skip_data` is true, .data is left
+ # None in the Object returned, but all the other fields are populated.
+ # Else (`skip_data` is false, the default), all fields including .data
+ # are populated. .data can be big, so it's prudent to skip it when it
+ # isn't needed.
+ def fromFile(cls, f, key, skip_data=False):
+ s = f.read(cls.FIXED_HEADER_SIZE)
if not s:
return None
oid, start_tid = key
+
end_tid, vlen, dlen = struct.unpack(cls.fmt, s)
if end_tid == z64:
end_tid = None
+
version = f.read(vlen)
if vlen != len(version):
raise ValueError("corrupted record, version")
- if header_only:
+
+ if skip_data:
data = None
+ f.seek(dlen, 1)
else:
data = f.read(dlen)
if dlen != len(data):
raise ValueError("corrupted record, data")
- s = f.read(8)
- if s != oid:
- raise ValueError("corrupted record, oid")
+
+ s = f.read(8)
+ if s != oid:
+ raise ValueError("corrupted record, oid")
+
return cls((oid, start_tid), version, data, start_tid, end_tid)
fromFile = classmethod(fromFile)
-def sync(f):
- f.flush()
- if hasattr(os, 'fsync'):
- os.fsync(f.fileno())
+# Entry just associates a key with a file offset. It's used by FileCache.
class Entry(object):
__slots__ = (# object key -- something usable as a dict key.
'key',
@@ -513,22 +628,22 @@
self.offset = offset
-magic = "ZEC3"
-OBJECT_HEADER_SIZE = 1 + 4 + 16
-
##
# FileCache stores a cache in a single on-disk file.
#
-# On-disk cache structure
+# On-disk cache structure.
#
# The file begins with a 12-byte header. The first four bytes are the
# file's magic number - ZEC3 - indicating zeo cache version 3. The
# next eight bytes are the last transaction id.
+
+magic = "ZEC3"
+ZEC3_HEADER_SIZE = 12
+
+# After the header, the file contains a contiguous sequence of blocks. All
+# blocks begin with a one-byte status indicator:
#
-# The file is a contiguous sequence of blocks. All blocks begin with
-# a one-byte status indicator:
-#
# 'a'
# Allocated. The block holds an object; the next 4 bytes are >I
# format total block size.
@@ -540,22 +655,20 @@
# '1', '2', '3', '4'
# The block is free, and consists of 1, 2, 3 or 4 bytes total.
#
-# 'Z'
-# File header. The file starts with a magic number, currently
-# 'ZEC3' and an 8-byte transaction id.
-#
# "Total" includes the status byte, and size bytes. There are no
# empty (size 0) blocks.
-# XXX This needs a lot more hair.
-# The structure of an allocated block is more complicated:
+# Allocated blocks have more structure:
#
# 1 byte allocation status ('a').
# 4 bytes block size, >I format.
# 16 bytes oid + tid, string.
-# size-OBJECT_HEADER_SIZE bytes, the object pickle.
+# size-OBJECT_HEADER_SIZE bytes, the serialization of an Object (see
+# class Object for details).
+OBJECT_HEADER_SIZE = 1 + 4 + 16
+
# The cache's currentofs goes around the file, circularly, forever.
# It's always the starting offset of some block.
#
@@ -564,47 +677,80 @@
# blocks needed to make enough room for the new object are evicted,
# starting at currentofs. Exception: if currentofs is close enough
# to the end of the file that the new object can't fit in one
-# contiguous chunk, currentofs is reset to 0 first.
+# contiguous chunk, currentofs is reset to ZEC3_HEADER_SIZE first.
-# Do all possible to ensure that the bytes we wrote are really on
+# Do all possible to ensure that the bytes we wrote to file f are really on
# disk.
+def sync(f):
+ f.flush()
+ if hasattr(os, 'fsync'):
+ os.fsync(f.fileno())
class FileCache(object):
def __init__(self, maxsize, fpath, parent, reuse=True):
- # Maximum total of object sizes we keep in cache.
+ # - `maxsize`: total size of the cache file, in bytes; this is
+ # ignored if reuse is true and fpath names an existing file;
+ # perhaps we should attempt to change the cache size in that
+ # case
+ # - `fpath`: filepath for the cache file, or None; see `reuse`
+ # - `parent`: the ClientCache this FileCache is part of
+ # - `reuse`: If true, and fpath is not None, and fpath names a
+ # file that exists, that pre-existing file is used (persistent
+ # cache). In all other cases a new file is created: a temp
+ # file if fpath is None, else with path fpath.
self.maxsize = maxsize
- # Current total of object sizes in cache.
- self.currentsize = 0
self.parent = parent
+
+ # tid for the most recent transaction we know about. This is also
+ # stored near the start of the file.
self.tid = None
+ # There's one Entry instance, kept in memory, for each currently
+ # allocated block in the file, and there's one allocated block in the
+ # file per serialized Object. filemap retrieves the Entry given the
+ # starting offset of a block, and key2entry retrieves the Entry given
+ # an object revision's key (an (oid, start_tid) pair). From an
+ # Entry, we can get the Object's key and file offset.
+
# Map offset in file to pair (data record size, Entry).
# Entry is None iff the block starting at offset is free.
# filemap always contains a complete account of what's in the
# file -- study method _verify_filemap for executable checking
# of the relevant invariants. An offset is at the start of a
- # block iff it's a key in filemap.
+ # block iff it's a key in filemap. The data record size is
+ # stored in the file too, so we could just seek to the offset
+ # and read it up; keeping it in memory is an optimization.
self.filemap = {}
- # Map key to Entry. There's one entry for each object in the
- # cache file. After
+ # Map key to Entry. After
# obj = key2entry[key]
# then
# obj.key == key
- # is true.
+ # is true. An object is currently stored on disk iff its key is in
+ # key2entry.
self.key2entry = {}
# Always the offset into the file of the start of a block.
# New and relocated objects are always written starting at
# currentofs.
- self.currentofs = 12
+ self.currentofs = ZEC3_HEADER_SIZE
+ # self.f is the open file object.
+ # When we're not reusing an existing file, self.f is left None
+ # here -- the scan() method must be called then to open the file
+ # (and it sets self.f).
+
self.fpath = fpath
- if not reuse or not fpath or not os.path.exists(fpath):
- self.new = True
+ if reuse and fpath and os.path.exists(fpath):
+ # Reuse an existing file. scan() will open & read it.
+ self.f = None
+ else:
+ if reuse:
+ logger.warning("reuse=True but the given file path %r "
+ "doesn't exist; ignoring reuse=True", fpath)
if fpath:
- self.f = file(fpath, 'wb+')
+ self.f = open(fpath, 'wb+')
else:
self.f = tempfile.TemporaryFile()
# Make sure the OS really saves enough bytes for the file.
@@ -616,34 +762,44 @@
self.f.write(magic)
self.f.write(z64)
# and one free block.
- self.f.write('f' + struct.pack(">I", self.maxsize - 12))
+ self.f.write('f' + struct.pack(">I", self.maxsize -
+ ZEC3_HEADER_SIZE))
self.sync()
- self.filemap[12] = self.maxsize - 12, None
- else:
- self.new = False
- self.f = None
+ self.filemap[ZEC3_HEADER_SIZE] = (self.maxsize - ZEC3_HEADER_SIZE,
+ None)
# Statistics: _n_adds, _n_added_bytes,
- # _n_evicts, _n_evicted_bytes
+ # _n_evicts, _n_evicted_bytes,
+ # _n_accesses
self.clearStats()
- # Scan the current contents of the cache file, calling install
+ ##
+ # Scan the current contents of the cache file, calling `install`
# for each object found in the cache. This method should only
# be called once to initialize the cache from disk.
-
def scan(self, install):
- if self.new:
+ if self.f is not None:
return
fsize = os.path.getsize(self.fpath)
- self.f = file(self.fpath, 'rb+')
+ if fsize != self.maxsize:
+ logger.warning("existing cache file %s has size %d; "
+ "requested size %d ignored", self.fpath,
+ fsize, self.maxsize)
+ self.maxsize = fsize
+ self.f = open(self.fpath, 'rb+')
_magic = self.f.read(4)
if _magic != magic:
raise ValueError("unexpected magic number: %r" % _magic)
self.tid = self.f.read(8)
- # Remember the largest free block. That seems a
- # decent place to start currentofs.
+ if len(self.tid) != 8:
+ raise ValueError("cache file too small -- no tid at start")
+
+ # Populate .filemap and .key2entry to reflect what's currently in the
+ # file, and tell our parent about it too (via the `install` callback).
+ # Remember the location of the largest free block That seems a decent
+ # place to start currentofs.
max_free_size = max_free_offset = 0
- ofs = 12
+ ofs = ZEC3_HEADER_SIZE
while ofs < fsize:
self.f.seek(ofs)
ent = None
@@ -659,7 +815,8 @@
elif status in '1234':
size = int(status)
else:
- assert 0, hex(ord(status))
+ raise ValueError("unknown status byte value %s in client "
+ "cache file" % 0, hex(ord(status)))
self.filemap[ofs] = size, ent
if ent is None and size > max_free_size:
@@ -667,7 +824,9 @@
ofs += size
- assert ofs == fsize
+ if ofs != fsize:
+ raise ValueError("final offset %s != file size %s in client "
+ "cache file" % (ofs, fsize))
if __debug__:
self._verify_filemap()
self.currentofs = max_free_offset
@@ -675,49 +834,59 @@
def clearStats(self):
self._n_adds = self._n_added_bytes = 0
self._n_evicts = self._n_evicted_bytes = 0
- self._n_removes = self._n_removed_bytes = 0
self._n_accesses = 0
def getStats(self):
return (self._n_adds, self._n_added_bytes,
self._n_evicts, self._n_evicted_bytes,
- self._n_removes, self._n_removed_bytes,
self._n_accesses
)
+ ##
+ # The number of objects currently in the cache.
def __len__(self):
return len(self.key2entry)
+ ##
+ # Iterate over the objects in the cache, producing an Entry for each.
def __iter__(self):
return self.key2entry.itervalues()
+ ##
+ # Test whether an (oid, tid) pair is in the cache.
def __contains__(self, key):
return key in self.key2entry
+ ##
+ # Do all possible to ensure all bytes written to the file so far are
+ # actually on disk.
def sync(self):
sync(self.f)
+ ##
+ # Close the underlying file. No methods accessing the cache should be
+ # used after this.
def close(self):
if self.f:
self.sync()
self.f.close()
self.f = None
+ ##
# Evict objects as necessary to free up at least nbytes bytes,
# starting at currentofs. If currentofs is closer than nbytes to
- # the end of the file, currentofs is reset to 0. The number of
- # bytes actually freed may be (and probably will be) greater than
- # nbytes, and is _makeroom's return value. The file is not
+ # the end of the file, currentofs is reset to ZEC3_HEADER_SIZE first.
+ # The number of bytes actually freed may be (and probably will be)
+ # greater than nbytes, and is _makeroom's return value. The file is not
# altered by _makeroom. filemap is updated to reflect the
# evictions, and it's the caller's responsibilty both to fiddle
# the file, and to update filemap, to account for all the space
# freed (starting at currentofs when _makeroom returns, and
# spanning the number of bytes retured by _makeroom).
-
def _makeroom(self, nbytes):
- assert 0 < nbytes <= self.maxsize
+ assert 0 < nbytes <= self.maxsize - ZEC3_HEADER_SIZE
if self.currentofs + nbytes > self.maxsize:
- self.currentofs = 12
+ self.currentofs = ZEC3_HEADER_SIZE
ofs = self.currentofs
while nbytes > 0:
size, e = self.filemap.pop(ofs)
@@ -727,11 +896,11 @@
nbytes -= size
return ofs - self.currentofs
+ ##
# Write Object obj, with data, to file starting at currentofs.
# nfreebytes are already available for overwriting, and it's
# guranteed that's enough. obj.offset is changed to reflect the
# new data record position, and filemap is updated to match.
-
def _writeobj(self, obj, nfreebytes):
size = OBJECT_HEADER_SIZE + obj.size
assert size <= nfreebytes
@@ -763,11 +932,18 @@
# written.
self.filemap[self.currentofs] = excess, None
+ ##
+ # Add Object object to the cache. This may evict existing objects, to
+ # make room (and almost certainly will, in steady state once the cache
+ # is first full). The object must not already be in the cache.
def add(self, object):
size = OBJECT_HEADER_SIZE + object.size
- if size > self.maxsize:
+ # A number of cache simulation experiments all concluded that the
+ # 2nd-level ZEO cache got a much higher hit rate if "very large"
+ # objects simply weren't cached. For now, we ignore the request
+ # only if the entire cache file is too small to hold the object.
+ if size > self.maxsize - ZEC3_HEADER_SIZE:
return
- assert size <= self.maxsize
assert object.key not in self.key2entry
assert len(object.key[0]) == 8
@@ -779,43 +955,23 @@
available = self._makeroom(size)
self._writeobj(object, available)
- def _verify_filemap(self, display=False):
- a = 12
- f = self.f
- while a < self.maxsize:
- f.seek(a)
- status = f.read(1)
- if status in 'af':
- size, = struct.unpack(">I", f.read(4))
- else:
- size = int(status)
- if display:
- if a == self.currentofs:
- print '*****',
- print "%c%d" % (status, size),
- size2, obj = self.filemap[a]
- assert size == size2
- assert (obj is not None) == (status == 'a')
- if obj is not None:
- assert obj.offset == a
- assert self.key2entry[obj.key] is obj
- a += size
- if display:
- print
- assert a == self.maxsize
-
+ ##
+ # Evict the object represented by Entry `e` from the cache, freeing
+ # `size` bytes in the file for reuse. `size` is used only for summary
+ # statistics. This does not alter the file, or self.filemap or
+ # self.key2entry (those are the caller's responsibilities). It does
+ # invoke _evicted(Object) on our parent.
def _evictobj(self, e, size):
self._n_evicts += 1
self._n_evicted_bytes += size
# Load the object header into memory so we know how to
# update the parent's in-memory data structures.
self.f.seek(e.offset + OBJECT_HEADER_SIZE)
- o = Object.fromFile(self.f, e.key, header_only=True)
+ o = Object.fromFile(self.f, e.key, skip_data=True)
self.parent._evicted(o)
##
- # Return object for key or None if not in cache.
-
+ # Return Object for key, or None if not in cache.
def access(self, key):
self._n_accesses += 1
e = self.key2entry.get(key)
@@ -829,8 +985,7 @@
return Object.fromFile(self.f, key)
##
- # Remove object for key from cache, if present.
-
+ # Remove Object for key from cache, if present.
def remove(self, key):
# If an object is being explicitly removed, we need to load
# its header into memory and write a free block marker to the
@@ -840,36 +995,76 @@
# XXX Or we could just keep the header in memory at all times.
- e = self.key2entry.get(key)
+ e = self.key2entry.pop(key, None)
if e is None:
return
offset = e.offset
size, e2 = self.filemap[offset]
+ assert e is e2
+ self.filemap[offset] = size, None
self.f.seek(offset + OBJECT_HEADER_SIZE)
- o = Object.fromFile(self.f, key, header_only=True)
- self.f.seek(offset + OBJECT_HEADER_SIZE)
+ o = Object.fromFile(self.f, key, skip_data=True)
+ assert size >= 5 # only free blocks are tiny
+ # Because `size` >= 5, we can change an allocated block to a free
+ # block just by overwriting the 'a' status byte with 'f' -- the
+ # size field stays the same.
+ self.f.seek(offset)
self.f.write('f')
self.f.flush()
self.parent._evicted(o)
- self.filemap[offset] = size, None
##
- # Update on-disk representation of obj.
+ # Update on-disk representation of Object obj.
#
# This method should be called when the object header is modified.
-
+ # obj must be in the cache. The only real use for this is during
+ # invalidation, to set the end_tid field on a revision that was current
+ # (and so had an end_tid of None, but no longer does).
def update(self, obj):
-
e = self.key2entry[obj.key]
self.f.seek(e.offset + OBJECT_HEADER_SIZE)
obj.serialize_header(self.f)
+ ##
+ # Update our idea of the most recent tid. This is stored in the
+ # instance, and also written out near the start of the cache file. The
+ # new tid must be strictly greater than our current idea of the most
+ # recent tid.
def settid(self, tid):
if self.tid is not None and tid <= self.tid:
raise ValueError("new last tid (%s) must be greater than "
"previous one (%s)" % (u64(tid),
u64(self.tid)))
+ assert isinstance(tid, str) and len(tid) == 8
self.tid = tid
- self.f.seek(4)
+ self.f.seek(len(magic))
self.f.write(tid)
self.f.flush()
+
+ ##
+ # This debug method marches over the entire cache file, verifying that
+ # the current contents match the info in self.filemap and self.key2entry.
+ def _verify_filemap(self, display=False):
+ a = ZEC3_HEADER_SIZE
+ f = self.f
+ while a < self.maxsize:
+ f.seek(a)
+ status = f.read(1)
+ if status in 'af':
+ size, = struct.unpack(">I", f.read(4))
+ else:
+ size = int(status)
+ if display:
+ if a == self.currentofs:
+ print '*****',
+ print "%c%d" % (status, size),
+ size2, obj = self.filemap[a]
+ assert size == size2
+ assert (obj is not None) == (status == 'a')
+ if obj is not None:
+ assert obj.offset == a
+ assert self.key2entry[obj.key] is obj
+ a += size
+ if display:
+ print
+ assert a == self.maxsize
Modified: Zope3/branches/srichter-blow-services/src/ZEO/stats.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/ZEO/stats.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/ZEO/stats.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -374,6 +374,8 @@
0x10: "invalidate (miss)",
0x1A: "invalidate (hit, version)",
0x1C: "invalidate (hit, saving non-current)",
+ # 0x1E can occur during startup verification.
+ 0x1E: "invalidate (hit, discarding current or non-current)",
0x20: "load (miss)",
0x22: "load (hit)",
Modified: Zope3/branches/srichter-blow-services/src/ZEO/tests/ConnectionTests.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/ZEO/tests/ConnectionTests.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/ZEO/tests/ConnectionTests.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -386,7 +386,7 @@
# objects that weren't in the _seriald, because the client was
# interrupted waiting for tpc_vote() to return. When the next
# transaction committed, it tried to do something with the
- # bogus _tbuf entries. The exaplanation is wrong/incomplete,
+ # bogus _tbuf entries. The explanation is wrong/incomplete,
# because tpc_begin() should clear the _tbuf.
# 2003-01-15T15:44:19 ERROR(200) ZODB A storage error occurred
@@ -418,26 +418,112 @@
self.assertEqual(revid1, revid2)
self._storage.close()
- def checkRollover(self):
- # Check that the cache works when the files are swapped.
+ def checkDisconnectedCacheWorks(self):
+ # Check that the cache works when the client is disconnected.
+ self._storage = self.openClientStorage('test')
+ oid1 = self._storage.new_oid()
+ obj1 = MinPO("1" * 500)
+ self._dostore(oid1, data=obj1)
+ oid2 = self._storage.new_oid()
+ obj2 = MinPO("2" * 500)
+ self._dostore(oid2, data=obj2)
+ expected1 = self._storage.load(oid1, '')
+ expected2 = self._storage.load(oid2, '')
- # In this case, only one object fits in a cache file. When the
- # cache files swap, the first object is effectively uncached.
+ # Shut it all down, and try loading from the persistent cache file
+ # without a server present.
+ self._storage.close()
+ self.shutdownServer()
+ self._storage = self.openClientStorage('test', wait=False)
+ self.assertEqual(expected1, self._storage.load(oid1, ''))
+ self.assertEqual(expected2, self._storage.load(oid2, ''))
+ self._storage.close()
- self._storage = self.openClientStorage('test', 1000)
+ def checkDisconnectedCacheFails(self):
+ # Like checkDisconnectedCacheWorks above, except the cache
+ # file is so small that only one object can be remembered.
+ self._storage = self.openClientStorage('test', cache_size=900)
oid1 = self._storage.new_oid()
obj1 = MinPO("1" * 500)
self._dostore(oid1, data=obj1)
oid2 = self._storage.new_oid()
obj2 = MinPO("2" * 500)
+ # The cache file is so small that adding oid2 will evict oid1.
self._dostore(oid2, data=obj2)
+ expected2 = self._storage.load(oid2, '')
+
+ # Shut it all down, and try loading from the persistent cache file
+ # without a server present.
self._storage.close()
self.shutdownServer()
- self._storage = self.openClientStorage('test', 1000, wait=0)
- self._storage.load(oid1, '')
- self._storage.load(oid2, '')
+ self._storage = self.openClientStorage('test', cache_size=900,
+ wait=False)
+ # oid2 should still be in cache.
+ self.assertEqual(expected2, self._storage.load(oid2, ''))
+ # But oid1 should have been purged, so that trying to load it will
+ # try to fetch it from the (non-existent) ZEO server.
+ self.assertRaises(ClientDisconnected, self._storage.load, oid1, '')
self._storage.close()
+ def checkVerificationInvalidationPersists(self):
+ # This tests a subtle invalidation bug from ZODB 3.3:
+ # invalidations processed as part of ZEO cache verification acted
+ # kinda OK wrt the in-memory cache structures, but had no effect
+ # on the cache file. So opening the file cache again could
+ # incorrectly believe that a previously invalidated object was
+ # still current. This takes some effort to set up.
+
+ # First, using a persistent cache ('test'), create an object
+ # MinPO(13). We used to see this again at the end of this test,
+ # despite that we modify it, and despite that it gets invalidated
+ # in 'test', before the end.
+ self._storage = self.openClientStorage('test')
+ oid = self._storage.new_oid()
+ obj = MinPO(13)
+ self._dostore(oid, data=obj)
+ self._storage.close()
+
+ # Now modify obj via a temp connection. `test` won't learn about
+ # this until we open a connection using `test` again.
+ self._storage = self.openClientStorage()
+ pickle, rev = self._storage.load(oid, '')
+ newobj = zodb_unpickle(pickle)
+ self.assertEqual(newobj, obj)
+ newobj.value = 42 # .value *should* be 42 forever after now, not 13
+ self._dostore(oid, data=newobj, revid=rev)
+ self._storage.close()
+
+ # Open 'test' again. `oid` in this cache should be (and is)
+ # invalidated during cache verification. The bug was that it
+ # got invalidated (kinda) in memory, but not in the cache file.
+ self._storage = self.openClientStorage('test')
+
+ # The invalidation happened already. Now create and store a new
+ # object before closing this storage: this is so `test` believes
+ # it's seen transactions beyond the one that invalidated `oid`, so
+ # that the *next* time we open `test` it doesn't process another
+ # invalidation for `oid`. It's also important that we not try to
+ # load `oid` now: because it's been (kinda) invalidated in the
+ # cache's memory structures, loading it now would fetch the
+ # current revision from the server, thus hiding the bug.
+ obj2 = MinPO(666)
+ oid2 = self._storage.new_oid()
+ self._dostore(oid2, data=obj2)
+ self._storage.close()
+
+ # Finally, open `test` again and load `oid`. `test` believes
+ # it's beyond the transaction that modified `oid`, so its view
+ # of whether it has an up-to-date `oid` comes solely from the disk
+ # file, unaffected by cache verification.
+ self._storage = self.openClientStorage('test')
+ pickle, rev = self._storage.load(oid, '')
+ newobj_copy = zodb_unpickle(pickle)
+ # This used to fail, with
+ # AssertionError: MinPO(13) != MinPO(42)
+ # That is, `test` retained a stale revision of the object on disk.
+ self.assertEqual(newobj_copy, newobj)
+ self._storage.close()
+
def checkReconnection(self):
# Check that the client reconnects when a server restarts.
Modified: Zope3/branches/srichter-blow-services/src/ZEO/tests/test_cache.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/ZEO/tests/test_cache.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/ZEO/tests/test_cache.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -114,13 +114,11 @@
n = p64(i)
self.cache.store(n, "", n, None, data[i])
self.assertEquals(len(self.cache), i + 1)
- self.assert_(self.cache.fc.currentsize < maxsize)
# The cache now uses 1225 bytes. The next insert
# should delete some objects.
n = p64(50)
self.cache.store(n, "", n, None, data[51])
self.assert_(len(self.cache) < 51)
- self.assert_(self.cache.fc.currentsize <= maxsize)
# XXX Need to make sure eviction of non-current data
# and of version data are handled correctly.
@@ -145,7 +143,7 @@
# Verify that internals of both objects are the same.
# Could also test that external API produces the same results.
eq = self.assertEqual
- eq(copy.tid, self.cache.tid)
+ eq(copy.getLastTid(), self.cache.getLastTid())
eq(len(copy), len(self.cache))
eq(copy.version, self.cache.version)
eq(copy.current, self.cache.current)
Modified: Zope3/branches/srichter-blow-services/src/ZEO/version.txt
===================================================================
--- Zope3/branches/srichter-blow-services/src/ZEO/version.txt 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/ZEO/version.txt 2005-01-17 15:30:10 UTC (rev 28853)
@@ -1 +1 @@
-2.3
+2.4a0
Modified: Zope3/branches/srichter-blow-services/src/ZODB/BaseStorage.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/ZODB/BaseStorage.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/ZODB/BaseStorage.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -20,11 +20,10 @@
import time
import logging
-import UndoLogCompatible
-import POSException
from persistent.TimeStamp import TimeStamp
-from ZODB import POSException, utils
+from ZODB import POSException
+from ZODB.utils import z64, oid_repr
from ZODB.UndoLogCompatible import UndoLogCompatible
log = logging.getLogger("ZODB.BaseStorage")
@@ -98,7 +97,7 @@
t=self._ts=apply(TimeStamp,(time.gmtime(t)[:5]+(t%60,)))
self._tid = `t`
if base is None:
- self._oid='\0\0\0\0\0\0\0\0'
+ self._oid=z64
else:
self._oid=base._oid
@@ -401,7 +400,7 @@
for r in transaction:
oid=r.oid
if verbose:
- print utils.oid_repr(oid), r.version, len(r.data)
+ print oid_repr(oid), r.version, len(r.data)
if restoring:
self.restore(oid, r.tid, r.data, r.version,
r.data_txn, transaction)
Modified: Zope3/branches/srichter-blow-services/src/ZODB/ConflictResolution.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/ZODB/ConflictResolution.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/ZODB/ConflictResolution.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -11,7 +11,7 @@
# FOR A PARTICULAR PURPOSE
#
##############################################################################
-import sys
+
import logging
from cStringIO import StringIO
from cPickle import Unpickler, Pickler
Modified: Zope3/branches/srichter-blow-services/src/ZODB/Connection.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/ZODB/Connection.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/ZODB/Connection.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -20,10 +20,8 @@
import threading
import warnings
from time import time
-from utils import u64
from persistent import PickleCache
-from persistent.interfaces import IPersistent
import transaction
@@ -33,11 +31,13 @@
import ConflictError, ReadConflictError, InvalidObjectReference, \
ConnectionStateError
from ZODB.TmpStore import TmpStore
-from ZODB.utils import oid_repr, z64, positive_id
+from ZODB.utils import u64, oid_repr, z64, positive_id
from ZODB.serialize import ObjectWriter, ConnectionObjectReader, myhasattr
from ZODB.interfaces import IConnection
-from ZODB.interfaces import implements
+from ZODB.utils import DEPRECATED_ARGUMENT, deprecated36
+from zope.interface import implements
+
global_reset_counter = 0
def resetCaches():
@@ -264,9 +264,8 @@
method. You can pass a transaction manager (TM) to DB.open()
to control which TM the Connection uses.
"""
- warnings.warn("getTransaction() is deprecated. "
- "Use the txn_mgr argument to DB.open() instead.",
- DeprecationWarning)
+ deprecated36("getTransaction() is deprecated. "
+ "Use the txn_mgr argument to DB.open() instead.")
return self._txn_mgr.get()
def setLocalTransaction(self):
@@ -278,9 +277,8 @@
can pass a transaction manager (TM) to DB.open() to control
which TM the Connection uses.
"""
- warnings.warn("setLocalTransaction() is deprecated. "
- "Use the txn_mgr argument to DB.open() instead.",
- DeprecationWarning)
+ deprecated36("setLocalTransaction() is deprecated. "
+ "Use the txn_mgr argument to DB.open() instead.")
if self._txn_mgr is transaction.manager:
if self._synch:
self._txn_mgr.unregisterSynch(self)
@@ -462,9 +460,10 @@
self._cache = cache = PickleCache(self, cache_size)
def abort(self, transaction):
- """Abort the object in the transaction.
+ """Abort modifications to registered objects.
- This just deactivates the thing.
+ This tells the cache to invalidate changed objects. _p_jar
+ and _p_oid are deleted from new objects.
"""
for obj in self._registered_objects:
@@ -487,14 +486,14 @@
def cacheFullSweep(self, dt=None):
# XXX needs doc string
- warnings.warn("cacheFullSweep is deprecated. "
- "Use cacheMinimize instead.", DeprecationWarning)
+ deprecated36("cacheFullSweep is deprecated. "
+ "Use cacheMinimize instead.")
if dt is None:
self._cache.full_sweep()
else:
self._cache.full_sweep(dt)
- def cacheMinimize(self, dt=None):
+ def cacheMinimize(self, dt=DEPRECATED_ARGUMENT):
"""Deactivate all unmodified objects in the cache.
Call _p_deactivate() on each cached object, attempting to turn
@@ -504,9 +503,8 @@
:Parameters:
- `dt`: ignored. It is provided only for backwards compatibility.
"""
- if dt is not None:
- warnings.warn("The dt argument to cacheMinimize is ignored.",
- DeprecationWarning)
+ if dt is not DEPRECATED_ARGUMENT:
+ deprecated36("cacheMinimize() dt= is ignored.")
self._cache.minimize()
def cacheGC(self):
@@ -649,6 +647,7 @@
self._cache[oid] = obj
except:
# Dang, I bet it's wrapped:
+ # XXX Deprecate, then remove, this.
if hasattr(obj, 'aq_base'):
self._cache[oid] = obj.aq_base
else:
@@ -781,8 +780,8 @@
# an oid is being registered. I can't think of any way to
# achieve that without assignment to _p_jar. If there is
# a way, this will be a very confusing warning.
- warnings.warn("Assigning to _p_jar is deprecated",
- DeprecationWarning)
+ deprecated36("Assigning to _p_jar is deprecated, and will be "
+ "changed to raise an exception.")
elif obj._p_oid in self._added:
# It was registered before it was added to _added.
return
@@ -798,7 +797,7 @@
def root(self):
"""Return the database root object.
- The root is a PersistentDict.
+ The root is a persistent.mapping.PersistentMapping.
"""
return self.get(z64)
Modified: Zope3/branches/srichter-blow-services/src/ZODB/DB.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/ZODB/DB.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/ZODB/DB.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -16,19 +16,124 @@
$Id$"""
import cPickle, cStringIO, sys
-from thread import allocate_lock
+import threading
from time import time, ctime
-import warnings
import logging
from ZODB.broken import find_global
+from ZODB.utils import z64
from ZODB.Connection import Connection
from ZODB.serialize import referencesf
+from ZODB.utils import WeakSet
+from ZODB.utils import DEPRECATED_ARGUMENT, deprecated36
import transaction
logger = logging.getLogger('ZODB.DB')
+class _ConnectionPool(object):
+ """Manage a pool of connections.
+
+ CAUTION: Methods should be called under the protection of a lock.
+ This class does no locking of its own.
+
+ There's no limit on the number of connections this can keep track of,
+ but a warning is logged if there are more than pool_size active
+ connections, and a critical problem if more than twice pool_size.
+
+ New connections are registered via push(). This will log a message if
+ "too many" connections are active.
+
+ When a connection is explicitly closed, tell the pool via repush().
+ That adds the connection to a stack of connections available for
+ reuse, and throws away the oldest stack entries if the stack is too large.
+ pop() pops this stack.
+
+ When a connection is obtained via pop(), the pool holds only a weak
+ reference to it thereafter. It's not necessary to inform the pool
+ if the connection goes away. A connection handed out by pop() counts
+ against pool_size only so long as it exists, and provided it isn't
+ repush()'ed. A weak reference is retained so that DB methods like
+ connectionDebugInfo() can still gather statistics.
+ """
+
+ def __init__(self, pool_size):
+ # The largest # of connections we expect to see alive simultaneously.
+ self.pool_size = pool_size
+
+ # A weak set of all connections we've seen. A connection vanishes
+ # from this set if pop() hands it out, it's not reregistered via
+ # repush(), and it becomes unreachable.
+ self.all = WeakSet()
+
+ # A stack of connections available to hand out. This is a subset
+ # of self.all. push() and repush() add to this, and may remove
+ # the oldest available connections if the pool is too large.
+ # pop() pops this stack. There are never more than pool_size entries
+ # in this stack.
+ # In Python 2.4, a collections.deque would make more sense than
+ # a list (we push only "on the right", but may pop from both ends).
+ self.available = []
+
+ # Change our belief about the expected maximum # of live connections.
+ # If the pool_size is smaller than the current value, this may discard
+ # the oldest available connections.
+ def set_pool_size(self, pool_size):
+ self.pool_size = pool_size
+ self._reduce_size()
+
+ # Register a new available connection. We must not know about c already.
+ # c will be pushed onto the available stack even if we're over the
+ # pool size limit.
+ def push(self, c):
+ assert c not in self.all
+ assert c not in self.available
+ self._reduce_size(strictly_less=True)
+ self.all.add(c)
+ self.available.append(c)
+ n, limit = len(self.all), self.pool_size
+ if n > limit:
+ reporter = logger.warn
+ if n > 2 * limit:
+ reporter = logger.critical
+ reporter("DB.open() has %s open connections with a pool_size "
+ "of %s", n, limit)
+
+ # Reregister an available connection formerly obtained via pop(). This
+ # pushes it on the stack of available connections, and may discard
+ # older available connections.
+ def repush(self, c):
+ assert c in self.all
+ assert c not in self.available
+ self._reduce_size(strictly_less=True)
+ self.available.append(c)
+
+ # Throw away the oldest available connections until we're under our
+ # target size (strictly_less=False) or no more than that (strictly_less=
+ # True, the default).
+ def _reduce_size(self, strictly_less=False):
+ target = self.pool_size - bool(strictly_less)
+ while len(self.available) > target:
+ c = self.available.pop(0)
+ self.all.remove(c)
+
+ # Pop an available connection and return it, or return None if none are
+ # available. In the latter case, the caller should create a new
+ # connection, register it via push(), and call pop() again. The
+ # caller is responsible for serializing this sequence.
+ def pop(self):
+ result = None
+ if self.available:
+ result = self.available.pop()
+ # Leave it in self.all, so we can still get at it for statistics
+ # while it's alive.
+ assert result in self.all
+ return result
+
+ # For every live connection c, invoke f(c).
+ def map(self, f):
+ self.all.map(f)
+
class DB(object):
"""The Object Database
-------------------
@@ -40,9 +145,9 @@
The DB instance manages a pool of connections. If a connection is
closed, it is returned to the pool and its object cache is
preserved. A subsequent call to open() will reuse the connection.
- There is a limit to the pool size; if all its connections are in
- use, calls to open() will block until one of the open connections
- is closed.
+ There is no hard limit on the pool size. If more than `pool_size`
+ connections are opened, a warning is logged, and if more than twice
+ that many, a critical problem is logged.
The class variable 'klass' is used by open() to create database
connections. It is set to Connection, but a subclass could override
@@ -80,41 +185,42 @@
def __init__(self, storage,
pool_size=7,
cache_size=400,
- cache_deactivate_after=None,
+ cache_deactivate_after=DEPRECATED_ARGUMENT,
version_pool_size=3,
version_cache_size=100,
- version_cache_deactivate_after=None,
+ version_cache_deactivate_after=DEPRECATED_ARGUMENT,
):
"""Create an object database.
:Parameters:
- `storage`: the storage used by the database, e.g. FileStorage
- - `pool_size`: maximum number of open connections
+ - `pool_size`: expected maximum number of open connections
- `cache_size`: target size of Connection object cache
- - `cache_deactivate_after`: ignored
- - `version_pool_size`: maximum number of connections (per version)
+ - `version_pool_size`: expected maximum number of connections (per
+ version)
- `version_cache_size`: target size of Connection object cache for
version connections
+ - `cache_deactivate_after`: ignored
- `version_cache_deactivate_after`: ignored
"""
- # Allocate locks:
- l = allocate_lock()
- self._a = l.acquire
- self._r = l.release
+ # Allocate lock.
+ x = threading.RLock()
+ self._a = x.acquire
+ self._r = x.release
# Setup connection pools and cache info
- self._pools = {},[]
- self._temps = []
+ # _pools maps a version string to a _ConnectionPool object.
+ self._pools = {}
self._pool_size = pool_size
self._cache_size = cache_size
self._version_pool_size = version_pool_size
self._version_cache_size = version_cache_size
# warn about use of deprecated arguments
- if (cache_deactivate_after is not None or
- version_cache_deactivate_after is not None):
- warnings.warn("cache_deactivate_after has no effect",
- DeprecationWarning)
+ if cache_deactivate_after is not DEPRECATED_ARGUMENT:
+ deprecated36("cache_deactivate_after has no effect")
+ if version_cache_deactivate_after is not DEPRECATED_ARGUMENT:
+ deprecated36("version_cache_deactivate_after has no effect")
self._miv_cache = {}
@@ -124,7 +230,7 @@
if not hasattr(storage,'tpc_vote'):
storage.tpc_vote = lambda *args: None
try:
- storage.load('\0\0\0\0\0\0\0\0','')
+ storage.load(z64,'')
except KeyError:
# Create the database's root in the storage if it doesn't exist
from persistent.mapping import PersistentMapping
@@ -138,7 +244,7 @@
t = transaction.Transaction()
t.description = 'initial database creation'
storage.tpc_begin(t)
- storage.store('\0\0\0\0\0\0\0\0', None, file.getvalue(), '', t)
+ storage.store(z64, None, file.getvalue(), '', t)
storage.tpc_vote(t)
storage.tpc_finish(t)
@@ -150,6 +256,7 @@
if hasattr(storage, 'undoInfo'):
self.undoInfo = storage.undoInfo
+ # This is called by Connection.close().
def _closeConnection(self, connection):
"""Return a connection to the pool.
@@ -164,10 +271,10 @@
am = self._activity_monitor
if am is not None:
am.closedConnection(connection)
+
version = connection._version
- pools, pooll = self._pools
try:
- pool, allocated, pool_lock = pools[version]
+ pool = self._pools[version]
except KeyError:
# No such version. We must have deleted the pool.
# Just let the connection go.
@@ -176,30 +283,17 @@
# XXX What objects are involved in the cycle?
connection.__dict__.clear()
return
+ pool.repush(connection)
- pool.append(connection)
- if len(pool) == 1:
- # Pool now usable again, unlock it.
- pool_lock.release()
finally:
self._r()
+ # Call f(c) for all connections c in all pools in all versions.
def _connectionMap(self, f):
self._a()
try:
- pools, pooll = self._pools
- for pool, allocated in pooll:
- for cc in allocated:
- f(cc)
-
- temps = self._temps
- if temps:
- t = []
- rc = sys.getrefcount
- for cc in temps:
- if rc(cc) > 3:
- f(cc)
- self._temps = t
+ for pool in self._pools.values():
+ pool.map(f)
finally:
self._r()
@@ -215,12 +309,12 @@
"""
detail = {}
- def f(con, detail=detail, have_detail=detail.has_key):
+ def f(con, detail=detail):
for oid, ob in con._cache.items():
module = getattr(ob.__class__, '__module__', '')
module = module and '%s.' % module or ''
c = "%s%s" % (module, ob.__class__.__name__)
- if have_detail(c):
+ if c in detail:
detail[c] += 1
else:
detail[c] = 1
@@ -275,7 +369,7 @@
self._connectionMap(lambda c: c._cache.full_sweep())
def cacheLastGCTime(self):
- m=[0]
+ m = [0]
def f(con, m=m):
t = con._cache.cache_last_gc_time
if t > m[0]:
@@ -288,7 +382,7 @@
self._connectionMap(lambda c: c._cache.minimize())
def cacheSize(self):
- m=[0]
+ m = [0]
def f(con, m=m):
m[0] += con._cache.cache_non_ghost_count
@@ -298,9 +392,9 @@
def cacheDetailSize(self):
m = []
def f(con, m=m):
- m.append({'connection':repr(con),
- 'ngsize':con._cache.cache_non_ghost_count,
- 'size':len(con._cache)})
+ m.append({'connection': repr(con),
+ 'ngsize': con._cache.cache_non_ghost_count,
+ 'size': len(con._cache)})
self._connectionMap(f)
m.sort()
return m
@@ -357,39 +451,24 @@
if connection is not None:
version = connection._version
# Update modified in version cache
- # XXX must make this work with list or dict to backport to 2.6
for oid in oids.keys():
h = hash(oid) % 131
o = self._miv_cache.get(h, None)
if o is not None and o[0]==oid:
del self._miv_cache[h]
- # Notify connections
- for pool, allocated in self._pools[1]:
- for cc in allocated:
- if (cc is not connection and
- (not version or cc._version==version)):
- if sys.getrefcount(cc) <= 3:
- cc.close()
- cc.invalidate(tid, oids)
+ # Notify connections.
+ def inval(c):
+ if (c is not connection and
+ (not version or c._version == version)):
+ c.invalidate(tid, oids)
+ self._connectionMap(inval)
- if self._temps:
- t = []
- for cc in self._temps:
- if sys.getrefcount(cc) > 3:
- if (cc is not connection and
- (not version or cc._version == version)):
- cc.invalidate(tid, oids)
- t.append(cc)
- else:
- cc.close()
- self._temps = t
-
def modifiedInVersion(self, oid):
h = hash(oid) % 131
cache = self._miv_cache
- o=cache.get(h, None)
- if o and o[0]==oid:
+ o = cache.get(h, None)
+ if o and o[0] == oid:
return o[1]
v = self._storage.modifiedInVersion(oid)
cache[h] = oid, v
@@ -398,203 +477,111 @@
def objectCount(self):
return len(self._storage)
- def open(self, version='', transaction=None, temporary=0, force=None,
- waitflag=1, mvcc=True, txn_mgr=None, synch=True):
+ def open(self, version='',
+ transaction=DEPRECATED_ARGUMENT, temporary=DEPRECATED_ARGUMENT,
+ force=DEPRECATED_ARGUMENT, waitflag=DEPRECATED_ARGUMENT,
+ mvcc=True, txn_mgr=None, synch=True):
"""Return a database Connection for use by application code.
- The optional version argument can be used to specify that a
+ The optional `version` argument can be used to specify that a
version connection is desired.
- The optional transaction argument can be provided to cause the
- connection to be automatically closed when a transaction is
- terminated. In addition, connections per transaction are
- reused, if possible.
-
Note that the connection pool is managed as a stack, to
- increate the likelihood that the connection's stack will
+ increase the likelihood that the connection's stack will
include useful objects.
:Parameters:
- `version`: the "version" that all changes will be made
in, defaults to no version.
- - `transaction`: XXX
- - `temporary`: XXX
- - `force`: XXX
- - `waitflag`: XXX
- `mvcc`: boolean indicating whether MVCC is enabled
- `txn_mgr`: transaction manager to use. None means
used the default transaction manager.
- `synch`: boolean indicating whether Connection should
register for afterCompletion() calls.
-
"""
- self._a()
- try:
- if transaction is not None:
- connections = transaction._connections
- if connections:
- if connections.has_key(version) and not temporary:
- return connections[version]
- else:
- transaction._connections = connections = {}
- transaction = transaction._connections
+ if temporary is not DEPRECATED_ARGUMENT:
+ deprecated36("DB.open() temporary= ignored. "
+ "open() no longer blocks.")
- if temporary:
- # This is a temporary connection.
- # We won't bother with the pools. This will be
- # a one-use connection.
- c = self.klass(version=version,
- cache_size=self._version_cache_size,
- mvcc=mvcc, txn_mgr=txn_mgr, synch=synch)
- c._setDB(self)
- self._temps.append(c)
- if transaction is not None:
- transaction[id(c)] = c
- return c
+ if force is not DEPRECATED_ARGUMENT:
+ deprecated36("DB.open() force= ignored. "
+ "open() no longer blocks.")
- pools, pooll = self._pools
+ if waitflag is not DEPRECATED_ARGUMENT:
+ deprecated36("DB.open() waitflag= ignored. "
+ "open() no longer blocks.")
- # pools is a mapping object:
- #
- # {version -> (pool, allocated, lock)
- #
- # where:
- #
- # pool is the connection pool for the version,
- # allocated is a list of all of the allocated
- # connections, and
- # lock is a lock that is used to block when a pool is
- # empty and no more connections can be allocated.
- #
- # pooll is a list of all of the pools and allocated for
- # use in cases where we need to iterate over all
- # connections or all inactive connections.
+ if transaction is not DEPRECATED_ARGUMENT:
+ deprecated36("DB.open() transaction= ignored.")
- # Pool locks are tricky. Basically, the lock needs to be
- # set whenever the pool becomes empty so that threads are
- # forced to wait until the pool gets a connection in it.
- # The lock is acquired when the (empty) pool is
- # created. The lock is acquired just prior to removing
- # the last connection from the pool and released just after
- # adding a connection to an empty pool.
+ self._a()
+ try:
+ # pool <- the _ConnectionPool for this version
+ pool = self._pools.get(version)
+ if pool is None:
+ if version:
+ size = self._version_pool_size
+ else:
+ size = self._pool_size
+ self._pools[version] = pool = _ConnectionPool(size)
+ assert pool is not None
-
- if pools.has_key(version):
- pool, allocated, pool_lock = pools[version]
- else:
- pool, allocated, pool_lock = pools[version] = (
- [], [], allocate_lock())
- pooll.append((pool, allocated))
- pool_lock.acquire()
-
-
- if not pool:
- c = None
+ # result <- a connection
+ result = pool.pop()
+ if result is None:
if version:
- if self._version_pool_size > len(allocated) or force:
- c = self.klass(version=version,
- cache_size=self._version_cache_size,
- mvcc=mvcc, txn_mgr=txn_mgr)
- allocated.append(c)
- pool.append(c)
- elif self._pool_size > len(allocated) or force:
- c = self.klass(version=version,
- cache_size=self._cache_size,
- mvcc=mvcc, txn_mgr=txn_mgr, synch=synch)
- allocated.append(c)
- pool.append(c)
+ size = self._version_cache_size
+ else:
+ size = self._cache_size
+ c = self.klass(version=version, cache_size=size,
+ mvcc=mvcc, txn_mgr=txn_mgr)
+ pool.push(c)
+ result = pool.pop()
+ assert result is not None
- if c is None:
- if waitflag:
- self._r()
- pool_lock.acquire()
- self._a()
- if len(pool) > 1:
- # Note that the pool size will normally be 1 here,
- # but it could be higher due to a race condition.
- pool_lock.release()
- else:
- return
+ # Tell the connection it belongs to self.
+ result._setDB(self, mvcc=mvcc, txn_mgr=txn_mgr, synch=synch)
- elif len(pool)==1:
- # Taking last one, lock the pool.
- # Note that another thread might grab the lock
- # before us, so we might actually block, however,
- # when we get the lock back, there *will* be a
- # connection in the pool. OTOH, there's no limit on
- # how long we may need to wait: if the other thread
- # grabbed the lock in this section too, we'll wait
- # here until another connection is closed.
- # checkConcurrentUpdates1Storage provoked this frequently
- # on a hyperthreaded machine, with its second thread
- # timing out after waiting 5 minutes for DB.open() to
- # return. So, if we can't get the pool lock immediately,
- # now we make a recursive call. This allows the current
- # thread to allocate a new connection instead of waiting
- # arbitrarily long for the single connection in the pool
- # right now.
- self._r()
- if not pool_lock.acquire(0):
- result = DB.open(self, version, transaction, temporary,
- force, waitflag)
- self._a()
- return result
- self._a()
- if len(pool) > 1:
- # Note that the pool size will normally be 1 here,
- # but it could be higher due to a race condition.
- pool_lock.release()
+ # A good time to do some cache cleanup.
+ self._connectionMap(lambda c: c.cacheGC())
- c = pool.pop()
- c._setDB(self, mvcc=mvcc, txn_mgr=txn_mgr, synch=synch)
- for pool, allocated in pooll:
- for cc in pool:
- cc.cacheGC()
+ return result
- if transaction is not None:
- transaction[version] = c
- return c
-
finally:
self._r()
def removeVersionPool(self, version):
- pools, pooll = self._pools
- info = pools.get(version)
- if info:
- del pools[version]
- pool, allocated, pool_lock = info
- pooll.remove((pool, allocated))
- try:
- pool_lock.release()
- except: # XXX Do we actually expect this to fail?
- pass
- del pool[:]
- del allocated[:]
+ try:
+ del self._pools[version]
+ except KeyError:
+ pass
def connectionDebugInfo(self):
- r = []
- pools, pooll = self._pools
+ result = []
t = time()
- for version, (pool, allocated, lock) in pools.items():
- for c in allocated:
- o = c._opened
- d = c._debug_info
- if d:
- if len(d)==1:
- d = d[0]
- else:
- d=''
- d = "%s (%s)" % (d, len(c._cache))
- r.append({
- 'opened': o and ("%s (%.2fs)" % (ctime(o), t-o)),
- 'info': d,
- 'version': version,
- })
- return r
+ def get_info(c):
+ # `result`, `time` and `version` are lexically inherited.
+ o = c._opened
+ d = c._debug_info
+ if d:
+ if len(d) == 1:
+ d = d[0]
+ else:
+ d = ''
+ d = "%s (%s)" % (d, len(c._cache))
+ result.append({
+ 'opened': o and ("%s (%.2fs)" % (ctime(o), t-o)),
+ 'info': d,
+ 'version': version,
+ })
+
+ for version, pool in self._pools.items():
+ pool.map(get_info)
+ return result
+
def getActivityMonitor(self):
return self._activity_monitor
@@ -622,34 +609,54 @@
logger.error("packing", exc_info=True)
raise
- def setCacheSize(self, v):
- self._cache_size = v
- d = self._pools[0]
- pool_info = d.get('')
- if pool_info is not None:
- for c in pool_info[1]:
- c._cache.cache_size = v
+ def setActivityMonitor(self, am):
+ self._activity_monitor = am
def classFactory(self, connection, modulename, globalname):
# Zope will rebind this method to arbitrary user code at runtime.
return find_global(modulename, globalname)
- def setPoolSize(self, v):
- self._pool_size = v
+ def setCacheSize(self, size):
+ self._a()
+ try:
+ self._cache_size = size
+ pool = self._pools.get('')
+ if pool is not None:
+ def setsize(c):
+ c._cache.cache_size = size
+ pool.map(setsize)
+ finally:
+ self._r()
- def setActivityMonitor(self, am):
- self._activity_monitor = am
+ def setVersionCacheSize(self, size):
+ self._a()
+ try:
+ self._version_cache_size = size
+ def setsize(c):
+ c._cache.cache_size = size
+ for version, pool in self._pools.items():
+ if version:
+ pool.map(setsize)
+ finally:
+ self._r()
- def setVersionCacheSize(self, v):
- self._version_cache_size = v
- for ver in self._pools[0].keys():
- if ver:
- for c in self._pools[0][ver][1]:
- c._cache.cache_size = v
+ def setPoolSize(self, size):
+ self._pool_size = size
+ self._reset_pool_sizes(size, for_versions=False)
- def setVersionPoolSize(self, v):
- self._version_pool_size=v
+ def setVersionPoolSize(self, size):
+ self._version_pool_size = size
+ self._reset_pool_sizes(size, for_versions=True)
+ def _reset_pool_sizes(self, size, for_versions=False):
+ self._a()
+ try:
+ for version, pool in self._pools.items():
+ if (version != '') == for_versions:
+ pool.set_pool_size(size)
+ finally:
+ self._r()
+
def undo(self, id, txn=None):
"""Undo a transaction identified by id.
@@ -678,23 +685,19 @@
def getCacheDeactivateAfter(self):
"""Deprecated"""
- warnings.warn("cache_deactivate_after has no effect",
- DeprecationWarning)
+ deprecated36("getCacheDeactivateAfter has no effect")
def getVersionCacheDeactivateAfter(self):
"""Deprecated"""
- warnings.warn("cache_deactivate_after has no effect",
- DeprecationWarning)
+ deprecated36("getVersionCacheDeactivateAfter has no effect")
def setCacheDeactivateAfter(self, v):
"""Deprecated"""
- warnings.warn("cache_deactivate_after has no effect",
- DeprecationWarning)
+ deprecated36("setCacheDeactivateAfter has no effect")
def setVersionCacheDeactivateAfter(self, v):
"""Deprecated"""
- warnings.warn("cache_deactivate_after has no effect",
- DeprecationWarning)
+ deprecated36("setVersionCacheDeactivateAfter has no effect")
class ResourceManager(object):
"""Transaction participation for a version or undo resource."""
Modified: Zope3/branches/srichter-blow-services/src/ZODB/DemoStorage.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/ZODB/DemoStorage.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/ZODB/DemoStorage.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -80,8 +80,9 @@
"""
-import base64, time, string
-from ZODB import POSException, BaseStorage, utils
+import base64, time
+from ZODB import POSException, BaseStorage
+from ZODB.utils import z64, oid_repr
from persistent.TimeStamp import TimeStamp
from cPickle import loads
from BTrees import OOBTree
@@ -409,7 +410,7 @@
# Now build an index of *only* those objects reachable
# from the root.
- rootl = ['\0\0\0\0\0\0\0\0']
+ rootl = [z64]
pindex = {}
while rootl:
oid = rootl.pop()
@@ -508,8 +509,8 @@
o.append(" %s %s" % (TimeStamp(tid), p))
for r in t:
oid, pre, vdata, p, tid = r
- oid = utils.oid_repr(oid)
- tid = utils.oid_repr(tid)
+ oid = oid_repr(oid)
+ tid = oid_repr(tid)
## if serial is not None: serial=str(TimeStamp(serial))
pre=id(pre)
if vdata and vdata[1]: vdata=vdata[0], id(vdata[1])
@@ -522,7 +523,7 @@
items.sort()
for oid, r in items:
if r: r=id(r)
- o.append(' %s: %s' % (utils.oid_repr(oid), r))
+ o.append(' %s: %s' % (oid_repr(oid), r))
o.append('\nVersion Index:')
items=self._vindex.items()
@@ -533,6 +534,6 @@
vitems.sort()
for oid, r in vitems:
if r: r=id(r)
- o.append(' %s: %s' % (utils.oid_repr(oid), r))
+ o.append(' %s: %s' % (oid_repr(oid), r))
- return string.join(o,'\n')
+ return '\n'.join(o)
Modified: Zope3/branches/srichter-blow-services/src/ZODB/ExportImport.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/ZODB/ExportImport.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/ZODB/ExportImport.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -21,7 +21,6 @@
from ZODB.POSException import ExportError
from ZODB.utils import p64, u64
from ZODB.serialize import referencesf
-import sys
logger = logging.getLogger('ZODB.ExportImport')
Modified: Zope3/branches/srichter-blow-services/src/ZODB/FileStorage/FileStorage.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/ZODB/FileStorage/FileStorage.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/ZODB/FileStorage/FileStorage.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -20,7 +20,6 @@
from cPickle import Pickler, Unpickler, loads
import errno
import os
-import struct
import sys
import time
import logging
@@ -454,7 +453,7 @@
# middle holds bytes 16:34 of a data record:
# pos of transaction, len of version name, data length
# commit version never writes data, so data length is always 0
- middle = struct.pack(">8sH8s", p64(self._pos), len(dest), z64)
+ middle = pack(">8sH8s", p64(self._pos), len(dest), z64)
if dest:
sd = p64(self._vindex_get(dest, 0))
@@ -697,7 +696,7 @@
# XXX isn't the same, 0 is returned. Why the discrepancy?
self._file.seek(tpos)
h = self._file.read(TRANS_HDR_LEN)
- tid, tl, status, ul, dl, el = struct.unpack(TRANS_HDR, h)
+ tid, tl, status, ul, dl, el = unpack(TRANS_HDR, h)
self._file.read(ul + dl + el)
tend = tpos + tl + 8
pos = self._file.tell()
@@ -1802,9 +1801,12 @@
def _skip_to_start(self, start):
# Scan through the transaction records doing almost no sanity
# checks.
+ file = self._file
+ read = file.read
+ seek = file.seek
while 1:
- self._file.seek(self._pos)
- h = self._file.read(16)
+ seek(self._pos)
+ h = read(16)
if len(h) < 16:
return
tid, stl = unpack(">8s8s", h)
@@ -1817,13 +1819,12 @@
self._pos = long(self._pos) + tl + 8
if __debug__:
# Sanity check
- self._file.seek(self._pos - 8, 0)
- rtl = self._file.read(8)
+ seek(self._pos - 8, 0)
+ rtl = read(8)
if rtl != stl:
- pos = self._file.tell() - 8
+ pos = file.tell() - 8
panic("%s has inconsistent transaction length at %s "
- "(%s != %s)",
- self._file.name, pos, u64(rtl), u64(stl))
+ "(%s != %s)", file.name, pos, u64(rtl), u64(stl))
def next(self, index=0):
if self._file is None:
@@ -2008,7 +2009,7 @@
self.pos -= u64(self.file.read(8)) + 8
self.file.seek(self.pos)
h = self.file.read(TRANS_HDR_LEN)
- tid, tl, status, ul, dl, el = struct.unpack(TRANS_HDR, h)
+ tid, tl, status, ul, dl, el = unpack(TRANS_HDR, h)
if status == 'p':
self.stop = 1
return None
Modified: Zope3/branches/srichter-blow-services/src/ZODB/FileStorage/fsdump.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/ZODB/FileStorage/fsdump.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/ZODB/FileStorage/fsdump.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -1,5 +1,16 @@
-from cPickle import Unpickler
-from cStringIO import StringIO
+##############################################################################
+#
+# Copyright (c) 2003 Zope Corporation 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.
+#
+##############################################################################
import md5
import struct
@@ -7,48 +18,9 @@
from ZODB.FileStorage.format \
import TRANS_HDR, TRANS_HDR_LEN, DATA_HDR, DATA_HDR_LEN
from ZODB.TimeStamp import TimeStamp
-from ZODB.utils import u64
+from ZODB.utils import u64, get_pickle_metadata
from ZODB.tests.StorageTestBase import zodb_unpickle
-def get_pickle_metadata(data):
- # ZODB's data records contain two pickles. The first is the class
- # of the object, the second is the object. We're only trying to
- # pick apart the first here, to extract the module and class names.
- if data.startswith('(c'): # pickle MARK GLOBAL opcode sequence
- global_prefix = 2
- elif data.startswith('c'): # pickle GLOBAL opcode
- global_prefix = 1
- else:
- global_prefix = 0
-
- if global_prefix:
- # Don't actually unpickle a class, because it will attempt to
- # load the class. Just break open the pickle and get the
- # module and class from it. The module and the class names are
- # given by newline-terminated strings following the GLOBAL opcode.
- modname, classname, rest = data.split('\n', 2)
- modname = modname[global_prefix:] # strip GLOBAL opcode
- return modname, classname
-
- # Else there are a bunch of other possible formats.
- f = StringIO(data)
- u = Unpickler(f)
- try:
- class_info = u.load()
- except Exception, err:
- print "Error", err
- return '', ''
- if isinstance(class_info, tuple):
- if isinstance(class_info[0], tuple):
- modname, classname = class_info[0]
- else:
- modname, classname = class_info
- else:
- # XXX not sure what to do here
- modname = repr(class_info)
- classname = ''
- return modname, classname
-
def fsdump(path, file=None, with_offset=1):
i = 0
iter = FileIterator(path)
Copied: Zope3/branches/srichter-blow-services/src/ZODB/FileStorage/fsoids.py (from rev 28844, Zope3/trunk/src/ZODB/FileStorage/fsoids.py)
Property changes on: Zope3/branches/srichter-blow-services/src/ZODB/FileStorage/fsoids.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Modified: Zope3/branches/srichter-blow-services/src/ZODB/FileStorage/fspack.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/ZODB/FileStorage/fspack.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/ZODB/FileStorage/fspack.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -264,7 +264,7 @@
if tlen != th.tlen:
self.fail(pos, "redundant transaction length does not "
"match initial transaction length: %d != %d",
- u64(s), th.tlen)
+ tlen, th.tlen)
pos += 8
self.packpos = pos
@@ -359,7 +359,7 @@
if tlen != th.tlen:
self.fail(pos, "redundant transaction length does not "
"match initial transaction length: %d != %d",
- u64(s), th.tlen)
+ tlen, th.tlen)
pos += 8
for pos in extra_roots:
@@ -553,7 +553,7 @@
if tlen != th.tlen:
self.fail(pos, "redundant transaction length does not "
"match initial transaction length: %d != %d",
- u64(s), th.tlen)
+ tlen, th.tlen)
pos += 8
return pos, new_pos
Modified: Zope3/branches/srichter-blow-services/src/ZODB/MappingStorage.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/ZODB/MappingStorage.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/ZODB/MappingStorage.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -21,16 +21,18 @@
The Mapping storage uses a single data structure to map object ids to data.
"""
-from ZODB import utils
-from ZODB import BaseStorage
+from ZODB.utils import u64, z64
+from ZODB.BaseStorage import BaseStorage
from ZODB import POSException
from persistent.TimeStamp import TimeStamp
-class MappingStorage(BaseStorage.BaseStorage):
+class MappingStorage(BaseStorage):
+
def __init__(self, name='Mapping Storage'):
- BaseStorage.BaseStorage.__init__(self, name)
+ BaseStorage.__init__(self, name)
self._index = {}
+ # FIXME: Why we don't use dict for _tindex?
self._tindex = []
self._ltid = None
# Note: If you subclass this and use a persistent mapping facility
@@ -41,12 +43,15 @@
return len(self._index)
def getSize(self):
- # These constants are for Python object memory overheads
- s = 32
- for oid in self._index.keys():
- p = self._index[oid]
- s += 56 + len(p)
- return s
+ self._lock_acquire()
+ try:
+ # These constants are for Python object memory overheads
+ s = 32
+ for p in self._index.itervalues():
+ s += 56 + len(p)
+ return s
+ finally:
+ self._lock_release()
def load(self, oid, version):
self._lock_acquire()
@@ -70,8 +75,7 @@
self._lock_acquire()
try:
# The tid is the first 8 bytes of the buffer.
- s = self._index[oid]
- return s[:8]
+ return self._index[oid][:8]
finally:
self._lock_release()
@@ -81,13 +85,12 @@
raise POSException.StorageTransactionError(self, transaction)
if version:
- raise POSException.Unsupported, "Versions aren't supported"
+ raise POSException.Unsupported("Versions aren't supported")
self._lock_acquire()
try:
- if self._index.has_key(oid):
- old = self._index[oid]
- oserial = old[:8]
+ if oid in self._index:
+ oserial = self._index[oid][:8]
if serial != oserial:
raise POSException.ConflictError(oid=oid,
serials=(oserial, serial),
@@ -102,8 +105,7 @@
self._tindex = []
def _finish(self, tid, user, desc, ext):
- for oid, p in self._tindex:
- self._index[oid] = p
+ self._index.update(dict(self._tindex))
self._ltid = self._tid
def lastTransaction(self):
@@ -115,21 +117,20 @@
if not self._index:
return
# Build an index of *only* those objects reachable from the root.
- rootl = ['\0\0\0\0\0\0\0\0']
+ rootl = [z64]
pindex = {}
while rootl:
oid = rootl.pop()
- if pindex.has_key(oid):
+ if oid in pindex:
continue
# Scan non-version pickle for references
r = self._index[oid]
pindex[oid] = r
- p = r[8:]
- referencesf(p, rootl)
+ referencesf(r[8:], rootl)
# Now delete any unreferenced entries:
for oid in self._index.keys():
- if not pindex.has_key(oid):
+ if oid not in pindex:
del self._index[oid]
finally:
@@ -137,13 +138,12 @@
def _splat(self):
"""Spit out a string showing state."""
- o = []
- o.append('Index:')
+ o = ['Index:']
keys = self._index.keys()
keys.sort()
for oid in keys:
r = self._index[oid]
o.append(' %s: %s, %s' %
- (utils.u64(oid),TimeStamp(r[:8]),`r[8:]`))
+ (u64(oid), TimeStamp(r[:8]), repr(r[8:])))
return '\n'.join(o)
Modified: Zope3/branches/srichter-blow-services/src/ZODB/Mount.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/ZODB/Mount.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/ZODB/Mount.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -15,9 +15,7 @@
$Id$"""
-import string
import time
-import sys
import thread
import logging
import persistent
@@ -187,7 +185,7 @@
if newMount:
try: id = data.getId()
except: id = '???' # data has no getId() method. Bad.
- p = string.join(parent.getPhysicalPath() + (id,), '/')
+ p = '/'.join(parent.getPhysicalPath() + (id,))
logger.info('Mounted database %s at %s',
self._getMountParams(), p)
else:
Modified: Zope3/branches/srichter-blow-services/src/ZODB/POSException.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/ZODB/POSException.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/ZODB/POSException.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -90,8 +90,8 @@
if data is not None:
# avoid circular import chain
- from ZODB.serialize import SimpleObjectReader
- self.class_name = SimpleObjectReader().getClassName(data)
+ from ZODB.utils import get_pickle_metadata
+ self.class_name = "%s.%s" % get_pickle_metadata(data)
## else:
## if message != "data read conflict error":
## raise RuntimeError
Modified: Zope3/branches/srichter-blow-services/src/ZODB/__init__.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/ZODB/__init__.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/ZODB/__init__.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -13,7 +13,7 @@
##############################################################################
# The next line must use double quotes, so release.py recognizes it.
-__version__ = "3.3"
+__version__ = "3.4a0"
import sys
import __builtin__
Copied: Zope3/branches/srichter-blow-services/src/ZODB/collaborations.txt (from rev 28844, Zope3/trunk/src/ZODB/collaborations.txt)
Property changes on: Zope3/branches/srichter-blow-services/src/ZODB/collaborations.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Modified: Zope3/branches/srichter-blow-services/src/ZODB/dbmStorage.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/ZODB/dbmStorage.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/ZODB/dbmStorage.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -19,6 +19,8 @@
in undo or versions.
"""
+from ZODB.utils import z64
+
from MappingStorage import MappingStorage
from BaseStorage import BaseStorage
import anydbm, os
@@ -56,7 +58,7 @@
self._index=index=gdbm.open(filename, flag[:1]+'f', mode)
self._tindex=[]
- m='\0\0\0\0\0\0\0\0'
+ m=z64
oid=index.firstkey()
while oid != None:
m=max(m, oid)
@@ -76,7 +78,7 @@
# Build an index of *only* those objects reachable
# from the root.
index=self._index
- rootl=['\0\0\0\0\0\0\0\0']
+ rootl=[z64]
pop=rootl.pop
pindex={}
referenced=pindex.has_key
Modified: Zope3/branches/srichter-blow-services/src/ZODB/interfaces.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/ZODB/interfaces.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/ZODB/interfaces.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -16,233 +16,9 @@
$Id$
"""
-try:
- from zope.interface import Interface, Attribute, implements
- from zope.interface.verify import verifyObject
-except ImportError:
- class Interface:
- pass
+import zope.interface
- class Attribute:
- def __init__(self, __name__, __doc__):
- self.__name__ = __name__
- self.__doc__ = __doc__
-
- def implements(*args):
- pass
-
- def verifyObject(*args):
- pass
-
-class IDataManager(Interface):
- """Objects that manage transactional storage.
-
- These object's may manage data for other objects, or they may manage
- non-object storages, such as relational databases.
- """
-
- def abort_sub(transaction):
- """Discard all subtransaction data.
-
- See subtransaction.txt
-
- This is called when top-level transactions are aborted.
-
- No further subtransactions can be started once abort_sub()
- has been called; this is only used when the transaction is
- being aborted.
-
- abort_sub also implies the abort of a 2-phase commit.
-
- This should never fail.
- """
-
- def commit_sub(transaction):
- """Commit all changes made in subtransactions and begin 2-phase commit
-
- Data are saved *as if* they are part of the current transaction.
- That is, they will not be persistent unless the current transaction
- is committed.
-
- This is called when the current top-level transaction is committed.
-
- No further subtransactions can be started once commit_sub()
- has been called; this is only used when the transaction is
- being committed.
-
- This call also implied the beginning of 2-phase commit.
- """
-
- # Two-phase commit protocol. These methods are called by the
- # ITransaction object associated with the transaction being
- # committed.
-
- def tpc_begin(transaction, subtransaction=False):
- """Begin commit of a transaction, starting the two-phase commit.
-
- transaction is the ITransaction instance associated with the
- transaction being committed.
-
- subtransaction is a Boolean flag indicating whether the
- two-phase commit is being invoked for a subtransaction.
-
- Important note: Subtransactions are modelled in the sense that
- when you commit a subtransaction, subsequent commits should be
- for subtransactions as well. That is, there must be a
- commit_sub() call between a tpc_begin() call with the
- subtransaction flag set to true and a tpc_begin() with the
- flag set to false.
-
- """
-
-
- def tpc_abort(transaction):
- """Abort a transaction.
-
- This is always called after a tpc_begin call.
-
- transaction is the ITransaction instance associated with the
- transaction being committed.
-
- This should never fail.
- """
-
- def tpc_finish(transaction):
- """Indicate confirmation that the transaction is done.
-
- transaction is the ITransaction instance associated with the
- transaction being committed.
-
- This should never fail. If this raises an exception, the
- database is not expected to maintain consistency; it's a
- serious error.
-
- """
-
- def tpc_vote(transaction):
- """Verify that a data manager can commit the transaction
-
- This is the last chance for a data manager to vote 'no'. A
- data manager votes 'no' by raising an exception.
-
- transaction is the ITransaction instance associated with the
- transaction being committed.
- """
-
- def commit(object, transaction):
- """CCCommit changes to an object
-
- Save the object as part of the data to be made persistent if
- the transaction commits.
- """
-
- def abort(object, transaction):
- """Abort changes to an object
-
- Only changes made since the last transaction or
- sub-transaction boundary are discarded.
-
- This method may be called either:
-
- o Outside of two-phase commit, or
-
- o In the first phase of two-phase commit
-
- """
-
- def sortKey():
- """
- Return a key to use for ordering registered DataManagers
-
- ZODB uses a global sort order to prevent deadlock when it commits
- transactions involving multiple resource managers. The resource
- manager must define a sortKey() method that provides a global ordering
- for resource managers.
- """
-
-
-class ITransaction(Interface):
- """Object representing a running transaction.
-
- Objects with this interface may represent different transactions
- during their lifetime (.begin() can be called to start a new
- transaction using the same instance).
- """
-
- user = Attribute(
- "user",
- "The name of the user on whose behalf the transaction is being\n"
- "performed. The format of the user name is defined by the\n"
- "application.")
- # XXX required to be a string?
-
- description = Attribute(
- "description",
- "Textual description of the transaction.")
-
- def begin(info=None, subtransaction=None):
- """Begin a new transaction.
-
- If the transaction is in progress, it is aborted and a new
- transaction is started using the same transaction object.
- """
-
- def commit(subtransaction=None):
- """Finalize the transaction.
-
- This executes the two-phase commit algorithm for all
- IDataManager objects associated with the transaction.
- """
-
- def abort(subtransaction=0, freeme=1):
- """Abort the transaction.
-
- This is called from the application. This can only be called
- before the two-phase commit protocol has been started.
- """
-
- def join(datamanager):
- """Add a datamanager to the transaction.
-
- The datamanager must implement the
- transactions.interfaces.IDataManager interface, and be
- adaptable to ZODB.interfaces.IDataManager.
- """
-
- def register(object):
- """Register the given object for transaction control."""
-
- def note(text):
- """Add text to the transaction description.
-
- If a description has already been set, text is added to the
- end of the description following two newline characters.
- Surrounding whitespace is stripped from text.
- """
- # XXX does impl do the right thing with ''? Not clear what
- # the "right thing" is.
-
- def setUser(user_name, path="/"):
- """Set the user name.
-
- path should be provided if needed to further qualify the
- identified user.
- """
-
- def setExtendedInfo(name, value):
- """Add extension data to the transaction.
-
- name is the name of the extension property to set; value must
- be a picklable value.
-
- Storage implementations may limit the amount of extension data
- which can be stored.
- """
- # XXX is this this allowed to cause an exception here, during
- # the two-phase commit, or can it toss data silently?
-
-
-class IConnection(Interface):
+class IConnection(zope.interface.Interface):
"""ZODB connection.
XXX: This interface is incomplete.
Modified: Zope3/branches/srichter-blow-services/src/ZODB/serialize.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/ZODB/serialize.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/ZODB/serialize.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -34,36 +34,44 @@
provide backwards compatibility with earlier versions of Zope. The
two current formats for class description are:
- - type(obj)
- - type(obj), obj.__getnewargs__()
+ 1. type(obj)
+ 2. type(obj), obj.__getnewargs__()
-The second of these options is used if the object has a
-__getnewargs__() method. It is intended to support objects like
-persistent classes that have custom C layouts that are determined by
-arguments to __new__().
+The second of these options is used if the object has a __getnewargs__()
+method. It is intended to support objects like persistent classes that have
+custom C layouts that are determined by arguments to __new__().
-The type object is usually stored using the standard pickle mechanism,
-which uses a string containing the class's module and name. The type
-may itself be a persistent object, in which case a persistent
-reference (see below) is used.
+The type object is usually stored using the standard pickle mechanism, which
+involves the pickle GLOBAL opcode (giving the type's module and name as
+strings). The type may itself be a persistent object, in which case a
+persistent reference (see below) is used.
+It's unclear what "usually" means in the last paragraph. There are two
+useful places to concentrate confusion about exactly which formats exist:
+
+- BaseObjectReader.getClassName() below returns a dotted "module.class"
+ string, via actually loading a pickle. This requires that the
+ implementation of application objects be available.
+
+- ZODB/utils.py's get_pickle_metadata() tries to return the module and
+ class names (as strings) without importing any application modules or
+ classes, via analyzing the pickle.
+
Earlier versions of Zope supported several other kinds of class
-descriptions. The current serialization code reads these
-descriptions, but does not write them.
+descriptions. The current serialization code reads these descriptions, but
+does not write them. The four earlier formats are:
-The four formats are:
+ 3. (module name, class name), None
+ 4. (module name, class name), __getinitargs__()
+ 5. class, None
+ 6. class, __getinitargs__()
- 1. (module name, class name), None
- 2. (module name, class name), __getinitargs__()
- 3. class, None
- 4. class, __getinitargs__()
+Formats 4 and 6 are used only if the class defines a __getinitargs__()
+method. Formats 5 and 6 are used if the class does not have a __module__
+attribute (I'm not sure when this applies, but I think it occurs for some
+but not all ZClasses).
-Formats 2 and 4 are used only if the class defines an
-__getinitargs__() method. Formats 3 and 4 are used if the class does
-not have an __module__ attribute. (I'm not sure when this applies,
-but I think it occurs for some but not all ZClasses.)
-
Persistent references
---------------------
@@ -79,7 +87,6 @@
changed the class of an object, a new record with new class metadata
would be written but all the old references would still include the
old class.
-
"""
import cPickle
Copied: Zope3/branches/srichter-blow-services/src/ZODB/tests/dbopen.txt (from rev 28844, Zope3/trunk/src/ZODB/tests/dbopen.txt)
Property changes on: Zope3/branches/srichter-blow-services/src/ZODB/tests/dbopen.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Deleted: Zope3/branches/srichter-blow-services/src/ZODB/tests/loggingsupport.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/ZODB/tests/loggingsupport.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/ZODB/tests/loggingsupport.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -1,122 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2004 Zope Corporation 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.
-#
-##############################################################################
-"""Support for testing logging code
-
-If you want to test that your code generates proper log output, you
-can create and install a handler that collects output:
-
- >>> handler = InstalledHandler('foo.bar')
-
-The handler is installed into loggers for all of the names passed. In
-addition, the logger level is set to 1, which means, log
-everything. If you want to log less than everything, you can provide a
-level keyword argument. The level setting effects only the named
-loggers.
-
-Then, any log output is collected in the handler:
-
- >>> logging.getLogger('foo.bar').exception('eek')
- >>> logging.getLogger('foo.bar').info('blah blah')
-
- >>> for record in handler.records:
- ... print record.name, record.levelname
- ... print ' ', record.getMessage()
- foo.bar ERROR
- eek
- foo.bar INFO
- blah blah
-
-A similar effect can be gotten by just printing the handler:
-
- >>> print handler
- foo.bar ERROR
- eek
- foo.bar INFO
- blah blah
-
-After checking the log output, you need to uninstall the handler:
-
- >>> handler.uninstall()
-
-At which point, the handler won't get any more log output.
-Let's clear the handler:
-
- >>> handler.clear()
- >>> handler.records
- []
-
-And then log something:
-
- >>> logging.getLogger('foo.bar').info('blah')
-
-and, sure enough, we still have no output:
-
- >>> handler.records
- []
-
-$Id$
-"""
-
-import logging
-
-class Handler(logging.Handler):
-
- def __init__(self, *names, **kw):
- logging.Handler.__init__(self)
- self.names = names
- self.records = []
- self.setLoggerLevel(**kw)
-
- def setLoggerLevel(self, level=1):
- self.level = level
- self.oldlevels = {}
-
- def emit(self, record):
- self.records.append(record)
-
- def clear(self):
- del self.records[:]
-
- def install(self):
- for name in self.names:
- logger = logging.getLogger(name)
- self.oldlevels[name] = logger.level
- logger.setLevel(self.level)
- logger.addHandler(self)
-
- def uninstall(self):
- for name in self.names:
- logger = logging.getLogger(name)
- logger.setLevel(self.oldlevels[name])
- logger.removeHandler(self)
-
- def __str__(self):
- return '\n'.join(
- [("%s %s\n %s" %
- (record.name, record.levelname,
- '\n'.join([line
- for line in record.getMessage().split('\n')
- if line.strip()])
- )
- )
- for record in self.records]
- )
-
-
-class InstalledHandler(Handler):
-
- def __init__(self, *names):
- Handler.__init__(self, *names)
- self.install()
-
Modified: Zope3/branches/srichter-blow-services/src/ZODB/tests/testConnection.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/ZODB/tests/testConnection.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/ZODB/tests/testConnection.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -22,7 +22,7 @@
from ZODB.config import databaseFromString
from ZODB.utils import p64, u64
from ZODB.tests.warnhook import WarningsHook
-from ZODB.interfaces import verifyObject
+from zope.interface.verify import verifyObject
class ConnectionDotAdd(unittest.TestCase):
@@ -414,8 +414,9 @@
>>> len(hook.warnings)
1
>>> message, category, filename, lineno = hook.warnings[0]
- >>> message
- 'The dt argument to cacheMinimize is ignored.'
+ >>> print message
+ This will be removed in ZODB 3.6:
+ cacheMinimize() dt= is ignored.
>>> category.__name__
'DeprecationWarning'
>>> hook.clear()
@@ -434,8 +435,9 @@
>>> len(hook.warnings)
2
>>> message, category, filename, lineno = hook.warnings[0]
- >>> message
- 'cacheFullSweep is deprecated. Use cacheMinimize instead.'
+ >>> print message
+ This will be removed in ZODB 3.6:
+ cacheFullSweep is deprecated. Use cacheMinimize instead.
>>> category.__name__
'DeprecationWarning'
>>> message, category, filename, lineno = hook.warnings[1]
@@ -632,7 +634,7 @@
class TestConnectionInterface(unittest.TestCase):
- def test(self):
+ def test_connection_interface(self):
from ZODB.interfaces import IConnection
db = databaseFromString("<zodb>\n<mappingstorage/>\n</zodb>")
cn = db.open()
Modified: Zope3/branches/srichter-blow-services/src/ZODB/tests/testDB.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/ZODB/tests/testDB.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/ZODB/tests/testDB.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -23,6 +23,10 @@
from ZODB.tests.MinPO import MinPO
+# Return total number of connections across all pools in a db._pools.
+def nconn(pools):
+ return sum([len(pool.all) for pool in pools.values()])
+
class DBTests(unittest.TestCase):
def setUp(self):
@@ -75,22 +79,22 @@
c12.close() # return to pool
self.assert_(c1 is c12) # should be same
- pools, pooll = self.db._pools
+ pools = self.db._pools
self.assertEqual(len(pools), 3)
- self.assertEqual(len(pooll), 3)
+ self.assertEqual(nconn(pools), 3)
self.db.removeVersionPool('v1')
self.assertEqual(len(pools), 2)
- self.assertEqual(len(pooll), 2)
+ self.assertEqual(nconn(pools), 2)
c12 = self.db.open('v1')
c12.close() # return to pool
self.assert_(c1 is not c12) # should be different
self.assertEqual(len(pools), 3)
- self.assertEqual(len(pooll), 3)
+ self.assertEqual(nconn(pools), 3)
def _test_for_leak(self):
self.dowork()
@@ -112,27 +116,27 @@
c12 = self.db.open('v1')
self.assert_(c1 is c12) # should be same
- pools, pooll = self.db._pools
+ pools = self.db._pools
self.assertEqual(len(pools), 3)
- self.assertEqual(len(pooll), 3)
+ self.assertEqual(nconn(pools), 3)
self.db.removeVersionPool('v1')
self.assertEqual(len(pools), 2)
- self.assertEqual(len(pooll), 2)
+ self.assertEqual(nconn(pools), 2)
c12.close() # should leave pools alone
self.assertEqual(len(pools), 2)
- self.assertEqual(len(pooll), 2)
+ self.assertEqual(nconn(pools), 2)
c12 = self.db.open('v1')
c12.close() # return to pool
self.assert_(c1 is not c12) # should be different
self.assertEqual(len(pools), 3)
- self.assertEqual(len(pooll), 3)
+ self.assertEqual(nconn(pools), 3)
def test_suite():
Modified: Zope3/branches/srichter-blow-services/src/ZODB/tests/testFileStorage.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/ZODB/tests/testFileStorage.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/ZODB/tests/testFileStorage.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -167,7 +167,56 @@
self.failUnless(self._storage._records_before_save > 20)
+ def checkCorruptionInPack(self):
+ # This sets up a corrupt .fs file, with a redundant transaction
+ # length mismatch. The implementation of pack in many releases of
+ # ZODB blew up if the .fs file had such damage: it detected the
+ # damage, but the code to raise CorruptedError referenced an undefined
+ # global.
+ import time
+ from ZODB.DB import DB
+ from ZODB.utils import U64, p64
+ from ZODB.FileStorage.format import CorruptedError
+
+ db = DB(self._storage)
+ conn = db.open()
+ conn.root()['xyz'] = 1
+ get_transaction().commit()
+
+ # Ensure it's all on disk.
+ db.close()
+ self._storage.close()
+
+ # Reopen before damaging.
+ self.open()
+
+ # Open .fs directly, and damage content.
+ f = open('FileStorageTests.fs', 'r+b')
+ f.seek(0, 2)
+ pos2 = f.tell() - 8
+ f.seek(pos2)
+ tlen2 = U64(f.read(8)) # length-8 of the last transaction
+ pos1 = pos2 - tlen2 + 8 # skip over the tid at the start
+ f.seek(pos1)
+ tlen1 = U64(f.read(8)) # should be redundant length-8
+ self.assertEqual(tlen1, tlen2) # verify that it is redundant
+
+ # Now damage the second copy.
+ f.seek(pos2)
+ f.write(p64(tlen2 - 1))
+ f.close()
+
+ # Try to pack. This used to yield
+ # NameError: global name 's' is not defined
+ try:
+ self._storage.pack(time.time(), None)
+ except CorruptedError, detail:
+ self.assert_("redundant transaction length does not match "
+ "initial transaction length" in str(detail))
+ else:
+ self.fail("expected CorruptedError")
+
class FileStorageRecoveryTest(
StorageTestBase.StorageTestBase,
RecoveryStorage.RecoveryStorage,
@@ -227,7 +276,7 @@
>>> from ZODB.FileStorage import FileStorage
>>> from ZODB.DB import DB
>>> import transaction
- >>> from ZODB.tests.loggingsupport import InstalledHandler
+ >>> from zope.testing.loggingsupport import InstalledHandler
Arrange to capture log messages -- they're an important part of
this test!
Modified: Zope3/branches/srichter-blow-services/src/ZODB/tests/testUtils.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/ZODB/tests/testUtils.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/ZODB/tests/testUtils.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -53,6 +53,41 @@
writer = BaseObjectWriter(None)
self.assertEqual(writer.persistent_id(P), None)
+ # It's hard to know where to put this test. We're checking that the
+ # ConflictError constructor uses utils.py's get_pickle_metadata() to
+ # deduce the class path from a pickle, instead of actually loading
+ # the pickle (and so also trying to import application module and
+ # class objects, which isn't a good idea on a ZEO server when avoidable).
+ def checkConflictErrorDoesntImport(self):
+ from ZODB.serialize import BaseObjectWriter
+ from ZODB.POSException import ConflictError
+ from ZODB.tests.MinPO import MinPO
+ import cPickle as pickle
+
+ obj = MinPO()
+ data = BaseObjectWriter().serialize(obj)
+
+ # The pickle contains a GLOBAL ('c') opcode resolving to MinPO's
+ # module and class.
+ self.assert_('cZODB.tests.MinPO\nMinPO\n' in data)
+
+ # Fiddle the pickle so it points to something "impossible" instead.
+ data = data.replace('cZODB.tests.MinPO\nMinPO\n',
+ 'cpath.that.does.not.exist\nlikewise.the.class\n')
+ # Pickle can't resolve that GLOBAL opcode -- gets ImportError.
+ self.assertRaises(ImportError, pickle.loads, data)
+
+ # Verify that building ConflictError doesn't get ImportError.
+ try:
+ raise ConflictError(object=obj, data=data)
+ except ConflictError, detail:
+ # And verify that the msg names the impossible path.
+ self.assert_('path.that.does.not.exist.likewise.the.class' in
+ str(detail))
+ else:
+ self.fail("expected ConflictError, but no exception raised")
+
+
def test_suite():
return unittest.makeSuite(TestUtils, 'check')
Modified: Zope3/branches/srichter-blow-services/src/ZODB/tests/testZODB.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/ZODB/tests/testZODB.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/ZODB/tests/testZODB.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -243,9 +243,13 @@
self.assertEqual(r1['item'], 2)
self.assertEqual(r2['item'], 2)
for msg, obj, filename, lineno in hook.warnings:
- self.assert_(
- msg.startswith("setLocalTransaction() is deprecated.") or
- msg.startswith("getTransaction() is deprecated."))
+ self.assert_(msg in [
+ "This will be removed in ZODB 3.6:\n"
+ "setLocalTransaction() is deprecated. "
+ "Use the txn_mgr argument to DB.open() instead.",
+ "This will be removed in ZODB 3.6:\n"
+ "getTransaction() is deprecated. "
+ "Use the txn_mgr argument to DB.open() instead."])
finally:
conn1.close()
conn2.close()
Copied: Zope3/branches/srichter-blow-services/src/ZODB/tests/test_doctest_files.py (from rev 28844, Zope3/trunk/src/ZODB/tests/test_doctest_files.py)
Property changes on: Zope3/branches/srichter-blow-services/src/ZODB/tests/test_doctest_files.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Copied: Zope3/branches/srichter-blow-services/src/ZODB/tests/testfsoids.py (from rev 28844, Zope3/trunk/src/ZODB/tests/testfsoids.py)
Property changes on: Zope3/branches/srichter-blow-services/src/ZODB/tests/testfsoids.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Modified: Zope3/branches/srichter-blow-services/src/ZODB/utils.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/ZODB/utils.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/ZODB/utils.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -16,13 +16,61 @@
import time
from struct import pack, unpack
from binascii import hexlify
+import cPickle as pickle
+from cStringIO import StringIO
+import weakref
+import warnings
from persistent.TimeStamp import TimeStamp
+__all__ = ['z64',
+ 't32',
+ 'p64',
+ 'u64',
+ 'U64',
+ 'cp',
+ 'newTimeStamp',
+ 'oid_repr',
+ 'serial_repr',
+ 'tid_repr',
+ 'positive_id',
+ 'get_refs',
+ 'readable_tid_repr',
+ 'WeakSet',
+ 'DEPRECATED_ARGUMENT',
+ 'deprecated36',
+ 'get_pickle_metadata',
+ ]
+
+# A unique marker to give as the default value for a deprecated argument.
+# The method should then do a
+#
+# if that_arg is not DEPRECATED_ARGUMENT:
+# complain
+#
+# dance.
+DEPRECATED_ARGUMENT = object()
+
+# Raise DeprecationWarning, noting that the deprecated thing will go
+# away in ZODB 3.6. Point to the caller of our caller (i.e., at the
+# code using the deprecated thing).
+def deprecated36(msg):
+ warnings.warn("This will be removed in ZODB 3.6:\n%s" % msg,
+ DeprecationWarning, stacklevel=3)
+
z64 = '\0'*8
+
+# TODO The purpose of t32 is unclear. Code that uses it is usually
+# of the form:
+#
+# if e < 0:
+# e = t32 - e
+#
+# Doesn't make sense (since e is negative, it creates a number larger than
+# t32). If users said "e += t32", *maybe* it would make sense.
t32 = 1L << 32
-assert sys.hexversion >= 0x02020000
+assert sys.hexversion >= 0x02030000
# The distinction between ints and longs is blurred in Python 2.2,
# so u64() are U64() really the same.
@@ -109,3 +157,147 @@
result += 1L << 64
assert result >= 0 # else addresses are fatter than 64 bits
return result
+
+# So full of undocumented magic it's hard to fathom.
+# The existence of cPickle.noload() isn't documented, and what it
+# does isn't documented either. In general it unpickles, but doesn't
+# actually build any objects of user-defined classes. Despite that
+# persistent_load is documented to be a callable, there's an
+# undocumented gimmick where if it's actually a list, for a PERSID or
+# BINPERSID opcode cPickle just appends "the persistent id" to that list.
+# Also despite that "a persistent id" is documented to be a string,
+# ZODB persistent ids are actually (often? always?) tuples, most often
+# of the form
+# (oid, (module_name, class_name))
+# So the effect of the following is to dig into the object pickle, and
+# return a list of the persistent ids found (which are usually nested
+# tuples), without actually loading any modules or classes.
+# Note that pickle.py doesn't support any of this, it's undocumented code
+# only in cPickle.c.
+def get_refs(a_pickle):
+ # The pickle is in two parts. First there's the class of the object,
+ # needed to build a ghost, See get_pickle_metadata for how complicated
+ # this can get. The second part is the state of the object. We want
+ # to find all the persistent references within both parts (although I
+ # expect they can only appear in the second part).
+ f = StringIO(a_pickle)
+ u = pickle.Unpickler(f)
+ u.persistent_load = refs = []
+ u.noload() # class info
+ u.noload() # instance state info
+ return refs
+
+# Given a ZODB pickle, return pair of strings (module_name, class_name).
+# Do this without importing the module or class object.
+# See ZODB/serialize.py's module docstring for the only docs that exist about
+# ZODB pickle format. If the code here gets smarter, please update those
+# docs to be at least as smart. The code here doesn't appear to make sense
+# for what serialize.py calls formats 5 and 6.
+
+def get_pickle_metadata(data):
+ # ZODB's data records contain two pickles. The first is the class
+ # of the object, the second is the object. We're only trying to
+ # pick apart the first here, to extract the module and class names.
+ if data.startswith('(c'): # pickle MARK GLOBAL opcode sequence
+ global_prefix = 2
+ elif data.startswith('c'): # pickle GLOBAL opcode
+ global_prefix = 1
+ else:
+ global_prefix = 0
+
+ if global_prefix:
+ # Formats 1 and 2.
+ # Don't actually unpickle a class, because it will attempt to
+ # load the class. Just break open the pickle and get the
+ # module and class from it. The module and class names are given by
+ # newline-terminated strings following the GLOBAL opcode.
+ modname, classname, rest = data.split('\n', 2)
+ modname = modname[global_prefix:] # strip GLOBAL opcode
+ return modname, classname
+
+ # Else there are a bunch of other possible formats.
+ f = StringIO(data)
+ u = pickle.Unpickler(f)
+ try:
+ class_info = u.load()
+ except Exception, err:
+ print "Error", err
+ return '', ''
+ if isinstance(class_info, tuple):
+ if isinstance(class_info[0], tuple):
+ # Formats 3 and 4.
+ modname, classname = class_info[0]
+ else:
+ # Formats 5 and 6 (probably) end up here.
+ modname, classname = class_info
+ else:
+ # This isn't a known format.
+ modname = repr(class_info)
+ classname = ''
+ return modname, classname
+
+# A simple implementation of weak sets, supplying just enough of Python's
+# sets.Set interface for our needs.
+
+class WeakSet(object):
+ """A set of objects that doesn't keep its elements alive.
+
+ The objects in the set must be weakly referencable.
+ The objects need not be hashable, and need not support comparison.
+ Two objects are considered to be the same iff their id()s are equal.
+
+ When the only references to an object are weak references (including
+ those from WeakSets), the object can be garbage-collected, and
+ will vanish from any WeakSets it may be a member of at that time.
+ """
+
+ def __init__(self):
+ # Map id(obj) to obj. By using ids as keys, we avoid requiring
+ # that the elements be hashable or comparable.
+ self.data = weakref.WeakValueDictionary()
+
+ def __len__(self):
+ return len(self.data)
+
+ def __contains__(self, obj):
+ return id(obj) in self.data
+
+ # Same as a Set, add obj to the collection.
+ def add(self, obj):
+ self.data[id(obj)] = obj
+
+ # Same as a Set, remove obj from the collection, and raise
+ # KeyError if obj not in the collection.
+ def remove(self, obj):
+ del self.data[id(obj)]
+
+ # f is a one-argument function. Execute f(elt) for each elt in the
+ # set. f's return value is ignored.
+ def map(self, f):
+ for wr in self.as_weakref_list():
+ elt = wr()
+ if elt is not None:
+ f(elt)
+
+ # Return a list of weakrefs to all the objects in the collection.
+ # Because a weak dict is used internally, iteration is dicey (the
+ # underlying dict may change size during iteration, due to gc or
+ # activity from other threads). as_weakef_list() is safe.
+ #
+ # Something like this should really be a method of Python's weak dicts.
+ # If we invoke self.data.values() instead, we get back a list of live
+ # objects instead of weakrefs. If gc occurs while this list is alive,
+ # all the objects move to an older generation (because they're strongly
+ # referenced by the list!). They can't get collected then, until a
+ # less frequent collection of the older generation. Before then, if we
+ # invoke self.data.values() again, they're still alive, and if gc occurs
+ # while that list is alive they're all moved to yet an older generation.
+ # And so on. Stress tests showed that it was easy to get into a state
+ # where a WeakSet grows without bounds, despite that almost all its
+ # elements are actually trash. By returning a list of weakrefs instead,
+ # we avoid that, although the decision to use weakrefs is now# very
+ # visible to our clients.
+ def as_weakref_list(self):
+ # We're cheating by breaking into the internals of Python's
+ # WeakValueDictionary here (accessing its .data attribute).
+ return self.data.data.values()
Modified: Zope3/branches/srichter-blow-services/src/bugtracker/browser/bug.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/bugtracker/browser/bug.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/bugtracker/browser/bug.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -74,7 +74,8 @@
if ttype is not None:
source = zapi.createObject(None, self.context.description.ttype,
self.context.description)
- view = zapi.getView(removeAllProxies(source), '', self.request)
+ view = zapi.getMultiAdapter(
+ (removeAllProxies(source), self.request))
html = view.render()
else:
html = self.context.description
Modified: Zope3/branches/srichter-blow-services/src/bugtracker/browser/bug_edit.pt
===================================================================
--- Zope3/branches/srichter-blow-services/src/bugtracker/browser/bug_edit.pt 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/bugtracker/browser/bug_edit.pt 2005-01-17 15:30:10 UTC (rev 28853)
@@ -11,7 +11,7 @@
<body>
<div metal:fill-slot="body">
- <form action="." tal:attributes="action request/URL" method="POST"
+ <form action="." tal:attributes="action request/URL" method="post"
enctype="multipart/form-data">
<p tal:define="status view/update"
Modified: Zope3/branches/srichter-blow-services/src/bugtracker/browser/comment.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/bugtracker/browser/comment.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/bugtracker/browser/comment.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -55,7 +55,8 @@
if ttype is not None:
source = zapi.createObject(None, self.context.body.ttype,
self.context.body)
- view = zapi.getView(removeAllProxies(source), '', self.request)
+ view = zapi.getMultiAdapter(
+ (removeAllProxies(source), self.request))
html = view.render()
else:
html = self.context.body
Modified: Zope3/branches/srichter-blow-services/src/bugtracker/tests/placelesssetup.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/bugtracker/tests/placelesssetup.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/bugtracker/tests/placelesssetup.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -22,7 +22,6 @@
from datetime import datetime
from zope.component.interfaces import IFactory
-from zope.component.service import defineService, serviceManager
from zope.interface import classImplements, implements
from zope.schema.vocabulary import getVocabularyRegistry
@@ -41,7 +40,7 @@
from zope.app.renderer.plaintext import IPlainTextSource
from zope.app.renderer.plaintext import PlainTextToHTMLRenderer
from zope.app.renderer.plaintext import PlainTextSourceFactory
-from zope.app.security.interfaces import IAuthenticationService
+from zope.app.security.interfaces import IAuthentication
from zope.app.size.interfaces import ISized
from zope.app.traversing.interfaces import IContainmentRoot, ITraverser
from zope.app.traversing.interfaces import ITraversable, IPhysicallyLocatable
@@ -105,10 +104,7 @@
registry.register('Releases', ReleaseVocabulary)
registry.register('Users', UserVocabulary)
- defineService(zapi.servicenames.Authentication,
- IAuthenticationService)
- serviceManager.provideService(zapi.servicenames.Authentication,
- principalRegistry)
+ ztapi.provideUtility(IAuthentication, principalRegistry)
principalRegistry.definePrincipal(u'zope.srichter',
u'Stephan Richter', u'',
Modified: Zope3/branches/srichter-blow-services/src/bugtracker/tests/test_mail.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/bugtracker/tests/test_mail.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/bugtracker/tests/test_mail.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -17,7 +17,6 @@
"""
import unittest
-from zope.component.service import defineService, serviceManager
from zope.component.tests.placelesssetup import PlacelessSetup
from zope.interface import classImplements, implements
Modified: Zope3/branches/srichter-blow-services/src/bugtracker/tests/test_vocabularies.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/bugtracker/tests/test_vocabularies.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/bugtracker/tests/test_vocabularies.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -17,7 +17,6 @@
"""
import unittest
-from zope.component.service import defineService, serviceManager
from zope.interface import classImplements, implements
from zope.schema.interfaces import ITokenizedTerm
from zope.schema.vocabulary import getVocabularyRegistry
@@ -28,7 +27,7 @@
from zope.app.annotation.attribute import AttributeAnnotations
from zope.app.annotation.interfaces import IAnnotations, IAttributeAnnotatable
from zope.app.container.contained import contained, Contained
-from zope.app.security.interfaces import IAuthenticationService
+from zope.app.security.interfaces import IAuthentication
from zope.app.security.principalregistry import principalRegistry, Principal
from bugtracker.interfaces import IManagableVocabulary
@@ -195,9 +194,7 @@
def setUp(self):
PlacelessSetup.setUp(self)
- defineService(zapi.servicenames.Authentication, IAuthenticationService)
- serviceManager.provideService(zapi.servicenames.Authentication,
- principalRegistry)
+ ztapi.provideUtility(IAuthentication, principalRegistry)
principalRegistry.definePrincipal(
'0', 'title0', 'desc0', 'zero', 'pass0')
principalRegistry.definePrincipal(
Modified: Zope3/branches/srichter-blow-services/src/bugtracker/vocabulary.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/bugtracker/vocabulary.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/bugtracker/vocabulary.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -191,7 +191,7 @@
implements(IVocabulary, IVocabularyTokenized)
def __init__(self, context):
- self.auth = zapi.getService(zapi.servicenames.Authentication)
+ self.auth = zapi.principals()
def __contains__(self, value):
ids = map(lambda p: p.id, self.auth.getPrincipals(''))
Modified: Zope3/branches/srichter-blow-services/src/persistent/cPersistence.c
===================================================================
--- Zope3/branches/srichter-blow-services/src/persistent/cPersistence.c 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/persistent/cPersistence.c 2005-01-17 15:30:10 UTC (rev 28853)
@@ -30,7 +30,6 @@
static PyObject *py_keys, *py_setstate, *py___dict__, *py_timeTime;
static PyObject *py__p_changed, *py__p_deactivate;
static PyObject *py___getattr__, *py___setattr__, *py___delattr__;
-static PyObject *py___getstate__;
static PyObject *py___slotnames__, *copy_reg_slotnames, *__newobj__;
static PyObject *py___getnewargs__, *py___getstate__;
@@ -50,7 +49,6 @@
INIT_STRING(__getattr__);
INIT_STRING(__setattr__);
INIT_STRING(__delattr__);
- INIT_STRING(__getstate__);
INIT_STRING(__slotnames__);
INIT_STRING(__getnewargs__);
INIT_STRING(__getstate__);
@@ -58,6 +56,24 @@
return 0;
}
+#ifdef Py_DEBUG
+static void
+fatal_1350(cPersistentObject *self, const char *caller, const char *detail)
+{
+ char buf[1000];
+
+ PyOS_snprintf(buf, sizeof(buf),
+ "cPersistence.c %s(): object at %p with type %.200s\n"
+ "%s.\n"
+ "The only known cause is multiple threads trying to ghost and\n"
+ "unghost the object simultaneously.\n"
+ "That's not legal, but ZODB can't stop it.\n"
+ "See Collector #1350.\n",
+ caller, self, self->ob_type->tp_name, detail);
+ Py_FatalError(buf);
+}
+#endif
+
static void ghostify(cPersistentObject*);
/* Load the state of the object, unghostifying it. Upon success, return 1.
@@ -88,6 +104,18 @@
}
self->state = cPersistent_UPTODATE_STATE;
Py_DECREF(r);
+ if (self->cache && self->ring.r_next == NULL) {
+#ifdef Py_DEBUG
+ fatal_1350(self, "unghostify",
+ "is not in the cache despite that we just "
+ "unghostified it");
+#else
+ PyErr_Format(PyExc_SystemError, "object at %p with type "
+ "%.200s not in the cache despite that we just "
+ "unghostified it", self, self->ob_type->tp_name);
+ return -1;
+#endif
+ }
}
return 1;
}
@@ -134,13 +162,19 @@
return;
}
- /* If the cache is still active, we must unlink the object. */
- if (self->ring.r_next) {
- /* if we're ghostifying an object, we better have some non-ghosts */
- assert(self->cache->non_ghost_count > 0);
- self->cache->non_ghost_count--;
- ring_del(&self->ring);
+ if (self->ring.r_next == NULL) {
+ /* There's no way to raise an error in this routine. */
+#ifdef Py_DEBUG
+ fatal_1350(self, "ghostify", "claims to be in a cache but isn't");
+#else
+ return;
+#endif
}
+
+ /* If we're ghostifying an object, we better have some non-ghosts. */
+ assert(self->cache->non_ghost_count > 0);
+ self->cache->non_ghost_count--;
+ ring_del(&self->ring);
self->state = cPersistent_GHOST_STATE;
dictptr = _PyObject_GetDictPtr((PyObject *)self);
if (dictptr && *dictptr) {
@@ -249,7 +283,7 @@
return slotnames;
}
- slotnames = PyObject_CallFunctionObjArgs(copy_reg_slotnames,
+ slotnames = PyObject_CallFunctionObjArgs(copy_reg_slotnames,
(PyObject*)cls, NULL);
if (slotnames && !(slotnames == Py_None || PyList_Check(slotnames))) {
PyErr_SetString(PyExc_TypeError,
@@ -257,7 +291,7 @@
Py_DECREF(slotnames);
return NULL;
}
-
+
return slotnames;
}
@@ -288,7 +322,7 @@
if (PyObject_SetItem(copy, key, value) < 0)
goto err;
}
-
+
return copy;
err:
Py_DECREF(copy);
@@ -366,13 +400,13 @@
}
}
- if (n)
+ if (n)
state = Py_BuildValue("(NO)", state, slots);
end:
Py_XDECREF(slotnames);
Py_XDECREF(slots);
-
+
return state;
}
@@ -381,12 +415,12 @@
{
PyObject *key, *value;
int pos = 0;
-
+
if (!PyDict_Check(dict)) {
PyErr_SetString(PyExc_TypeError, "Expected dictionary");
return -1;
}
-
+
while (PyDict_Next(dict, &pos, &key, &value)) {
if (PyObject_SetAttr(self, key, value) < 0)
return -1;
@@ -451,7 +485,7 @@
return Py_None;
}
-static char pickle___reduce__doc[] =
+static char pickle___reduce__doc[] =
"Reduce an object to contituent parts for serialization\n"
;
@@ -475,18 +509,18 @@
PyErr_Clear();
l = 0;
}
-
+
args = PyTuple_New(l+1);
if (args == NULL)
goto end;
-
+
Py_INCREF(self->ob_type);
PyTuple_SET_ITEM(args, 0, (PyObject*)(self->ob_type));
for (i = 0; i < l; i++) {
Py_INCREF(PyTuple_GET_ITEM(bargs, i));
PyTuple_SET_ITEM(args, i+1, PyTuple_GET_ITEM(bargs, i));
}
-
+
state = PyObject_CallMethodObjArgs(self, py___getstate__, NULL);
if (!state)
goto end;
@@ -680,7 +714,7 @@
}
else
result = Py_True;
-
+
Py_INCREF(result);
Done:
@@ -688,7 +722,7 @@
return result;
}
-/*
+/*
XXX we should probably not allow assignment of __class__ and __dict__.
*/
@@ -1012,7 +1046,7 @@
{"__getstate__", (PyCFunction)Per__getstate__, METH_NOARGS,
pickle___getstate__doc },
{"__setstate__", (PyCFunction)pickle___setstate__, METH_O,
- pickle___setstate__doc},
+ pickle___setstate__doc},
{"__reduce__", (PyCFunction)pickle___reduce__, METH_NOARGS,
pickle___reduce__doc},
Modified: Zope3/branches/srichter-blow-services/src/persistent/interfaces.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/persistent/interfaces.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/persistent/interfaces.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -15,19 +15,10 @@
$Id$
"""
-try:
- from zope.interface import Interface
- from zope.interface import Attribute
-except ImportError:
- # just allow the module to compile if zope isn't available
+from zope.interface import Interface
+from zope.interface import Attribute
- class Interface(object):
- pass
-
- def Attribute(s):
- return s
-
class IPersistent(Interface):
"""Python persistent interface
@@ -149,7 +140,8 @@
- call _p_deactivate()
- set _p_changed to None
- There is one way to invalidate an object: delete its _p_changed
+ There are two ways to invalidate an object: call the
+ _p_invalidate() method (preferred) or delete its _p_changed
attribute. This cannot be ignored, and is used when semantics
require invalidation. Normally, an invalidated object transitions
to the ghost state. However, some objects cannot be ghosts. When
@@ -235,6 +227,15 @@
may choose to keep an object in the saved state.
"""
+ def _p_invalidate():
+ """Invalidate the object.
+
+ Invalidate the object. This causes any data to be thrown
+ away, even if the object is in the changed state. The object
+ is moved to the ghost state; further accesses will cause
+ object data to be reloaded.
+ """
+
class IPersistentNoReadConflicts(IPersistent):
def _p_independent():
"""Hook for subclasses to prevent read conflict errors.
@@ -277,84 +278,3 @@
is returned. If non-None, the return value is the kind of
timestamp supplied by Python's time.time().
"""
-
-# XXX Should we keep the following? Doesn't seem too useful, and
-# XXX we don't actually implement this interface (e.g., we have no
-# XXX .statistics() method).
-
-class ICache(Interface):
- """In-memory object cache.
-
- The cache serves two purposes. It peforms pointer swizzling, and
- it keeps a bounded set of recently used but otherwise unreferenced
- in objects to avoid the cost of re-loading them.
-
- Pointer swizzling is the process of converting between persistent
- object ids and Python object ids. When a persistent object is
- serialized, its references to other persistent objects are
- represented as persitent object ids (oids). When the object is
- unserialized, the oids are converted into references to Python
- objects. If several different serialized objects refer to the
- same object, they must all refer to the same object when they are
- unserialized.
-
- A cache stores persistent objects, but it treats ghost objects and
- non-ghost or active objects differently. It has weak references
- to ghost objects, because ghost objects are only stored in the
- cache to satisfy the pointer swizzling requirement. It has strong
- references to active objects, because it caches some number of
- them even if they are unreferenced.
-
- The cache keeps some number of recently used but otherwise
- unreferenced objects in memory. We assume that there is a good
- chance the object will be used again soon, so keeping it memory
- avoids the cost of recreating the object.
-
- An ICache implementation is intended for use by an
- IPersistentDataManager.
- """
-
- def get(oid):
- """Return the object from the cache or None."""
-
- def set(oid, obj):
- """Store obj in the cache under oid.
-
- obj must implement IPersistent
- """
-
- def remove(oid):
- """Remove oid from the cache if it exists."""
-
- def invalidate(oids):
- """Make all of the objects in oids ghosts.
-
- `oids` is an iterable object that yields oids.
-
- The cache must attempt to change each object to a ghost by
- calling _p_deactivate().
-
- If an oid is not in the cache, ignore it.
- """
-
- def clear():
- """Invalidate all the active objects."""
-
- def activate(oid):
- """Notification that object oid is now active.
-
- The caller is notifying the cache of a state change.
-
- Raises LookupError if oid is not in cache.
- """
-
- def shrink():
- """Remove excess active objects from the cache."""
-
- def statistics():
- """Return dictionary of statistics about cache size.
-
- Contains at least the following keys:
- active -- number of active objects
- ghosts -- number of ghost objects
- """
Modified: Zope3/branches/srichter-blow-services/src/persistent/tests/test_persistent.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/persistent/tests/test_persistent.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/persistent/tests/test_persistent.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -11,44 +11,16 @@
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
-import doctest
-import os
-import sys
-import unittest
-import persistent.tests
from persistent import Persistent
+from zope.testing.doctestunit import DocFileSuite
+
class P(Persistent):
def __init__(self):
self.x = 0
def inc(self):
self.x += 1
-try:
- DocFileSuite = doctest.DocFileSuite # >= Python 2.4.0a2
-except AttributeError:
- # <= Python 2.4.0a1
- def DocFileSuite(path, globs=None):
- # It's not entirely obvious how to connection this single string
- # with unittest. For now, re-use the _utest() function that comes
- # standard with doctest in Python 2.3. One problem is that the
- # error indicator doesn't point to the line of the doctest file
- # that failed.
-
- path = os.path.join(persistent.tests.__path__[0], path)
-
- source = open(path).read()
- if globs is None:
- globs = sys._getframe(1).f_globals
- t = doctest.Tester(globs=globs)
- def runit():
- doctest._utest(t, path, source, path, 0)
- f = unittest.FunctionTestCase(runit,
- description="doctest from %s" % path)
- suite = unittest.TestSuite()
- suite.addTest(f)
- return suite
-
def test_suite():
return DocFileSuite("persistent.txt", globs={"P": P})
Modified: Zope3/branches/srichter-blow-services/src/transaction/_manager.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/transaction/_manager.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/transaction/_manager.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -18,7 +18,6 @@
"""
import thread
-import weakref
from transaction._transaction import Transaction
@@ -28,60 +27,28 @@
# practice not to explicitly close Connection objects, and keeping
# a Connection alive keeps a potentially huge number of other objects
# alive (e.g., the cache, and everything reachable from it too).
+# Therefore we use "weak sets" internally.
#
-# Therefore we use "weak sets" internally. The implementation here
-# implements just enough of Python's sets.Set interface for our needs.
+# Obscure: because of the __init__.py maze, we can't import WeakSet
+# at top level here.
-class WeakSet(object):
- """A set of objects that doesn't keep its elements alive.
-
- The objects in the set must be weakly referencable.
- The objects need not be hashable, and need not support comparison.
- Two objects are considered to be the same iff their id()s are equal.
-
- When the only references to an object are weak references (including
- those from WeakSets), the object can be garbage-collected, and
- will vanish from any WeakSets it may be a member of at that time.
- """
-
- def __init__(self):
- # Map id(obj) to obj. By using ids as keys, we avoid requiring
- # that the elements be hashable or comparable.
- self.data = weakref.WeakValueDictionary()
-
- # Same as a Set, add obj to the collection.
- def add(self, obj):
- self.data[id(obj)] = obj
-
- # Same as a Set, remove obj from the collection, and raise
- # KeyError if obj not in the collection.
- def remove(self, obj):
- del self.data[id(obj)]
-
- # Return a list of all the objects in the collection.
- # Because a weak dict is used internally, iteration
- # is dicey (the underlying dict may change size during
- # iteration, due to gc or activity from other threads).
- # as_list() attempts to be safe.
- def as_list(self):
- return self.data.values()
-
-
class TransactionManager(object):
def __init__(self):
+ from ZODB.utils import WeakSet
+
self._txn = None
self._synchs = WeakSet()
def begin(self):
if self._txn is not None:
self._txn.abort()
- self._txn = Transaction(self._synchs.as_list(), self)
+ self._txn = Transaction(self._synchs, self)
return self._txn
def get(self):
if self._txn is None:
- self._txn = Transaction(self._synchs.as_list(), self)
+ self._txn = Transaction(self._synchs, self)
return self._txn
def free(self, txn):
@@ -104,8 +71,8 @@
# _threads maps thread ids to transactions
self._txns = {}
# _synchs maps a thread id to a WeakSet of registered synchronizers.
- # The set elements are passed to the Transaction constructor,
- # because the latter needs to call the synchronizers when it commits.
+ # The WeakSet is passed to the Transaction constructor, because the
+ # latter needs to call the synchronizers when it commits.
self._synchs = {}
def begin(self):
@@ -114,8 +81,6 @@
if txn is not None:
txn.abort()
synchs = self._synchs.get(tid)
- if synchs is not None:
- synchs = synchs.as_list()
txn = self._txns[tid] = Transaction(synchs, self)
return txn
@@ -124,8 +89,6 @@
txn = self._txns.get(tid)
if txn is None:
synchs = self._synchs.get(tid)
- if synchs is not None:
- synchs = synchs.as_list()
txn = self._txns[tid] = Transaction(synchs, self)
return txn
@@ -135,6 +98,8 @@
del self._txns[tid]
def registerSynch(self, synch):
+ from ZODB.utils import WeakSet
+
tid = thread.get_ident()
ws = self._synchs.get(tid)
if ws is None:
Modified: Zope3/branches/srichter-blow-services/src/transaction/_transaction.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/transaction/_transaction.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/transaction/_transaction.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -172,8 +172,15 @@
self.status = Status.ACTIVE
# List of resource managers, e.g. MultiObjectResourceAdapters.
self._resources = []
- self._synchronizers = synchronizers or []
+
+ # Weak set of synchronizer objects to call.
+ if synchronizers is None:
+ from ZODB.utils import WeakSet
+ synchronizers = WeakSet()
+ self._synchronizers = synchronizers
+
self._manager = manager
+
# _adapters: Connection/_p_jar -> MultiObjectResourceAdapter[Sub]
self._adapters = {}
self._voted = {} # id(Connection) -> boolean, True if voted
@@ -261,9 +268,10 @@
self._resources.append(adapter)
def begin(self):
- warnings.warn("Transaction.begin() should no longer be used; use "
- "the begin() method of a transaction manager.",
- DeprecationWarning, stacklevel=2)
+ from ZODB.utils import deprecated36
+
+ deprecated36("Transaction.begin() should no longer be used; use "
+ "the begin() method of a transaction manager.")
if (self._resources or
self._sub or
self._nonsub or
@@ -285,8 +293,7 @@
self.commit(True)
if not subtransaction:
- for s in self._synchronizers:
- s.beforeCompletion(self)
+ self._synchronizers.map(lambda s: s.beforeCompletion(self))
self.status = Status.COMMITTING
try:
@@ -310,8 +317,7 @@
self.status = Status.COMMITTED
if self._manager:
self._manager.free(self)
- for s in self._synchronizers:
- s.afterCompletion(self)
+ self._synchronizers.map(lambda s: s.afterCompletion(self))
self.log.debug("commit")
def _commitResources(self, subtransaction):
@@ -359,8 +365,7 @@
self._cleanup(L)
finally:
if not subtransaction:
- for s in self._synchronizers:
- s.afterCompletion(self)
+ self._synchronizers.map(lambda s: s.afterCompletion(self))
raise t, v, tb
def _cleanup(self, L):
@@ -426,8 +431,7 @@
def abort(self, subtransaction=False):
if not subtransaction:
- for s in self._synchronizers:
- s.beforeCompletion(self)
+ self._synchronizers.map(lambda s: s.beforeCompletion(self))
if subtransaction and self._nonsub:
from ZODB.POSException import TransactionError
@@ -457,8 +461,7 @@
if not subtransaction:
if self._manager:
self._manager.free(self)
- for s in self._synchronizers:
- s.afterCompletion(self)
+ self._synchronizers.map(lambda s: s.afterCompletion(self))
self.log.debug("abort")
if tb is not None:
@@ -630,4 +633,3 @@
def sortKey(self):
return self._datamanager.sortKey()
-
Modified: Zope3/branches/srichter-blow-services/src/transaction/interfaces.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/transaction/interfaces.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/transaction/interfaces.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -15,25 +15,246 @@
$Id$
"""
-try:
- from zope.interface import Interface
-except ImportError:
- class Interface:
- pass
+import zope.interface
-class IDataManager(Interface):
- """Data management interface for storing objects transactionally
+class IResourceManager(zope.interface.Interface):
+ """Objects that manage resources transactionally.
- This is currently implemented by ZODB database connections.
+ These objects may manage data for other objects, or they may manage
+ non-object storages, such as relational databases.
- XXX This exists to document ZODB4 behavior, to help create some
- backward-compatability support for Zope 3. New classes shouldn't
- implement this. They should implement ZODB.interfaces.IDataManager
- for now. Our hope is that there will eventually be an interface
- like this or that this interface will evolve and become the
- standard interface. There are some issues to be resolved first, like:
+ IDataManagerOriginal is the interface currently provided by ZODB
+ database connections, but the intent is to move to the newer
+ IDataManager.
+ """
+ # Two-phase commit protocol. These methods are called by the
+ # ITransaction object associated with the transaction being
+ # committed.
+
+ def tpc_begin(transaction):
+ """Begin two-phase commit, to save data changes.
+
+ An implementation should do as much work as possible without
+ making changes permanent. Changes should be made permanent
+ when tpc_finish is called (or aborted if tpc_abort is called).
+ The work can be divided between tpc_begin() and tpc_vote(), and
+ the intent is that tpc_vote() be as fast as possible (to minimize
+ the period of uncertainty).
+
+ transaction is the ITransaction instance associated with the
+ transaction being committed.
+ """
+
+ def tpc_vote(transaction):
+ """Verify that a resource manager can commit the transaction.
+
+ This is the last chance for a resource manager to vote 'no'. A
+ resource manager votes 'no' by raising an exception.
+
+ transaction is the ITransaction instance associated with the
+ transaction being committed.
+ """
+
+ def tpc_finish(transaction):
+ """Indicate confirmation that the transaction is done.
+
+ transaction is the ITransaction instance associated with the
+ transaction being committed.
+
+ This should never fail. If this raises an exception, the
+ database is not expected to maintain consistency; it's a
+ serious error.
+ """
+
+ def tpc_abort(transaction):
+ """Abort a transaction.
+
+ transaction is the ITransaction instance associated with the
+ transaction being committed.
+
+ All changes made by the current transaction are aborted. Note
+ that this includes all changes stored in any savepoints that may
+ be associated with the current transaction.
+
+ tpc_abort() can be called at any time, either in or out of the
+ two-phase commit.
+
+ This should never fail.
+ """
+
+ # The savepoint/rollback API.
+
+ def savepoint(transaction):
+ """Save partial transaction changes.
+
+ There are two purposes:
+
+ 1) To allow discarding partial changes without discarding all
+ dhanges.
+
+ 2) To checkpoint changes to disk that would otherwise live in
+ memory for the duration of the transaction.
+
+ Returns an object implementing ISavePoint2 that can be used
+ to discard changes made since the savepoint was captured.
+
+ An implementation that doesn't support savepoints should implement
+ this method by returning a savepoint object that raises an
+ exception when its rollback method is called. The savepoint method
+ shouldn't raise an error. This way, transactions that create
+ savepoints can proceed as long as an attempt is never made to roll
+ back a savepoint.
+ """
+
+ def discard(transaction):
+ """Discard changes within the transaction since the last savepoint.
+
+ That means changes made since the last savepoint if one exists, or
+ since the start of the transaction.
+ """
+
+class IDataManagerOriginal(zope.interface.Interface):
+ """Objects that manage transactional storage.
+
+ These objects may manage data for other objects, or they may manage
+ non-object storages, such as relational databases.
+
+ IDataManagerOriginal is the interface currently provided by ZODB
+ database connections, but the intent is to move to the newer
+ IDataManager.
+ """
+
+ def abort_sub(transaction):
+ """Discard all subtransaction data.
+
+ See subtransaction.txt
+
+ This is called when top-level transactions are aborted.
+
+ No further subtransactions can be started once abort_sub()
+ has been called; this is only used when the transaction is
+ being aborted.
+
+ abort_sub also implies the abort of a 2-phase commit.
+
+ This should never fail.
+ """
+
+ def commit_sub(transaction):
+ """Commit all changes made in subtransactions and begin 2-phase commit
+
+ Data are saved *as if* they are part of the current transaction.
+ That is, they will not be persistent unless the current transaction
+ is committed.
+
+ This is called when the current top-level transaction is committed.
+
+ No further subtransactions can be started once commit_sub()
+ has been called; this is only used when the transaction is
+ being committed.
+
+ This call also implied the beginning of 2-phase commit.
+ """
+
+ # Two-phase commit protocol. These methods are called by the
+ # ITransaction object associated with the transaction being
+ # committed.
+
+ def tpc_begin(transaction, subtransaction=False):
+ """Begin commit of a transaction, starting the two-phase commit.
+
+ transaction is the ITransaction instance associated with the
+ transaction being committed.
+
+ subtransaction is a Boolean flag indicating whether the
+ two-phase commit is being invoked for a subtransaction.
+
+ Important note: Subtransactions are modelled in the sense that
+ when you commit a subtransaction, subsequent commits should be
+ for subtransactions as well. That is, there must be a
+ commit_sub() call between a tpc_begin() call with the
+ subtransaction flag set to true and a tpc_begin() with the
+ flag set to false.
+
+ """
+
+
+ def tpc_abort(transaction):
+ """Abort a transaction.
+
+ This is always called after a tpc_begin call.
+
+ transaction is the ITransaction instance associated with the
+ transaction being committed.
+
+ This should never fail.
+ """
+
+ def tpc_finish(transaction):
+ """Indicate confirmation that the transaction is done.
+
+ transaction is the ITransaction instance associated with the
+ transaction being committed.
+
+ This should never fail. If this raises an exception, the
+ database is not expected to maintain consistency; it's a
+ serious error.
+
+ """
+
+ def tpc_vote(transaction):
+ """Verify that a data manager can commit the transaction
+
+ This is the last chance for a data manager to vote 'no'. A
+ data manager votes 'no' by raising an exception.
+
+ transaction is the ITransaction instance associated with the
+ transaction being committed.
+ """
+
+ def commit(object, transaction):
+ """CCCommit changes to an object
+
+ Save the object as part of the data to be made persistent if
+ the transaction commits.
+ """
+
+ def abort(object, transaction):
+ """Abort changes to an object
+
+ Only changes made since the last transaction or
+ sub-transaction boundary are discarded.
+
+ This method may be called either:
+
+ o Outside of two-phase commit, or
+
+ o In the first phase of two-phase commit
+
+ """
+
+ def sortKey():
+ """
+ Return a key to use for ordering registered DataManagers
+
+ ZODB uses a global sort order to prevent deadlock when it commits
+ transactions involving multiple resource managers. The resource
+ manager must define a sortKey() method that provides a global ordering
+ for resource managers.
+ """
+
+class IDataManager(zope.interface.Interface):
+ """Data management interface for storing objects transactionally.
+
+ ZODB database connections currently provides the older
+ IDataManagerOriginal interface, but the intent is to move to this newer
+ IDataManager interface.
+
+ Our hope is that this interface will evolve and become the standard
+ interface. There are some issues to be resolved first, like:
+
- Probably want separate abort methods for use in and out of
two-phase commit.
@@ -103,10 +324,128 @@
"""
+ def sortKey():
+ """
+ Return a key to use for ordering registered DataManagers
-class IRollback(Interface):
+ ZODB uses a global sort order to prevent deadlock when it commits
+ transactions involving multiple resource managers. The resource
+ manager must define a sortKey() method that provides a global ordering
+ for resource managers.
+ """
+class ITransaction(zope.interface.Interface):
+ """Object representing a running transaction.
+
+ Objects with this interface may represent different transactions
+ during their lifetime (.begin() can be called to start a new
+ transaction using the same instance).
+ """
+
+ user = zope.interface.Attribute(
+ "user",
+ "The name of the user on whose behalf the transaction is being\n"
+ "performed. The format of the user name is defined by the\n"
+ "application.")
+ # XXX required to be a string?
+
+ description = zope.interface.Attribute(
+ "description",
+ "Textual description of the transaction.")
+
+ def begin(info=None, subtransaction=None):
+ """Begin a new transaction.
+
+ If the transaction is in progress, it is aborted and a new
+ transaction is started using the same transaction object.
+ """
+
+ def commit(subtransaction=None):
+ """Finalize the transaction.
+
+ This executes the two-phase commit algorithm for all
+ IDataManager objects associated with the transaction.
+ """
+
+ def abort(subtransaction=0, freeme=1):
+ """Abort the transaction.
+
+ This is called from the application. This can only be called
+ before the two-phase commit protocol has been started.
+ """
+
+ def join(datamanager):
+ """Add a datamanager to the transaction.
+
+ The datamanager must implement the
+ transactions.interfaces.IDataManager interface, and be
+ adaptable to ZODB.interfaces.IDataManager.
+ """
+
+ def register(object):
+ """Register the given object for transaction control."""
+
+ def note(text):
+ """Add text to the transaction description.
+
+ If a description has already been set, text is added to the
+ end of the description following two newline characters.
+ Surrounding whitespace is stripped from text.
+ """
+ # XXX does impl do the right thing with ''? Not clear what
+ # the "right thing" is.
+
+ def setUser(user_name, path="/"):
+ """Set the user name.
+
+ path should be provided if needed to further qualify the
+ identified user.
+ """
+
+ def setExtendedInfo(name, value):
+ """Add extension data to the transaction.
+
+ name is the name of the extension property to set; value must
+ be a picklable value.
+
+ Storage implementations may limit the amount of extension data
+ which can be stored.
+ """
+ # XXX is this this allowed to cause an exception here, during
+ # the two-phase commit, or can it toss data silently?
+
+class ISavePoint(zope.interface.Interface):
+ """ISavePoint objects represent partial transaction changes.
+
+ Sequences of savepoint objects are associated with transactions,
+ and with IResourceManagers.
+ """
+
def rollback():
+ """Discard changes made after this savepoint.
+
+ This includes discarding (call the discard method on) all
+ subsequent savepoints.
+ """
+
+ def discard():
+ """Discard changes saved by this savepoint.
+
+ That means changes made since the immediately preceding
+ savepoint if one exists, or since the start of the transaction,
+ until this savepoint.
+
+ Once a savepoint has been discarded, it's an error to attempt
+ to rollback or discard it again.
+ """
+
+ next_savepoint = zope.interface.Attribute(
+ """The next savepoint (later in time), or None if self is the
+ most recent savepoint.""")
+
+class IRollback(zope.interface.Interface):
+
+ def rollback():
"""Rollback changes since savepoint.
IOW, rollback to the last savepoint.
Copied: Zope3/branches/srichter-blow-services/src/transaction/tests/test_SampleResourceManager.py (from rev 28844, Zope3/trunk/src/transaction/tests/test_SampleResourceManager.py)
Property changes on: Zope3/branches/srichter-blow-services/src/transaction/tests/test_SampleResourceManager.py
___________________________________________________________________
Name: cvs2svn:cvs-rev
+ 1.3
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Property changes on: Zope3/branches/srichter-blow-services/src/z3checkins/__init__.py
___________________________________________________________________
Name: svn:keywords
+ Id
Copied: Zope3/branches/srichter-blow-services/src/z3checkins/browser.py (from rev 28844, Zope3/trunk/src/z3checkins/browser.py)
===================================================================
--- Zope3/trunk/src/z3checkins/browser.py 2005-01-15 16:23:27 UTC (rev 28844)
+++ Zope3/branches/srichter-blow-services/src/z3checkins/browser.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -0,0 +1,456 @@
+"""
+Browser views for z3checkins.
+
+$Id$
+"""
+import re
+import mailbox
+from StringIO import StringIO
+
+from zope.interface import implements
+from zope.exceptions import DuplicationError
+from zope.proxy import removeAllProxies
+
+from zope.app import zapi
+from zope.app.datetimeutils import parseDatetimetz, DateTimeError
+from zope.app.publisher.browser import BrowserView
+from zope.app.form import CustomWidgetFactory
+from zope.app.form.browser import FileWidget
+from zope.app.dublincore.interfaces import IZopeDublinCore
+from zope.app.pagetemplate import ViewPageTemplateFile
+
+from z3checkins.message import Bookmark
+from z3checkins.interfaces import IMessage, ICheckinMessage, IMessageContained
+from z3checkins.interfaces import IMessageUpload, IMessageParser, IBookmark
+
+__metaclass__ = type
+
+
+class MessageUpload:
+ """Adding view mixin for uploading checkin messages."""
+
+ implements(IMessageUpload, IMessageContained)
+ data_widget = CustomWidgetFactory(FileWidget)
+
+ def createAndAdd(self, data):
+ if data.has_key('data'): # XXX should we bark if no data is given?
+ msg_raw = data['data']
+ parser = zapi.getUtility(IMessageParser)
+ if msg_raw.startswith("From "):
+ # detected an mbox file
+ mbox = StringIO(msg_raw)
+ messages = mailbox.PortableUnixMailbox(mbox,
+ factory=parser.parse)
+ for message in messages:
+ try:
+ self.add(message)
+ dc = IZopeDublinCore(message, None)
+ if dc is not None:
+ # TODO: should handle RFC-2047
+ dc.title = unicode(message.subject)
+ dc.created = message.date
+ except DuplicationError:
+ pass # leave the old mesage unchanged
+ else:
+ message = parser.parse(msg_raw)
+ self.add(message)
+
+
+class ContainerView:
+ """View mixin for locating checkin messages in a container."""
+
+ max_bookmarks = 5
+
+ def title(self):
+ """Returns the title of this archive.
+
+ Title is obtained from Dublin Core metadata of the folder. If it is
+ empty, "Zope 3 Checkins" is used.
+ """
+ dc = IZopeDublinCore(self.context, None)
+ if dc is not None:
+ title = dc.title
+ else:
+ title = ''
+ return title or "Zope 3 Checkins"
+
+ def description(self):
+ """Return the description of this archive."""
+ return self.context.description
+
+ def archive_url(self):
+ """Return the URL for mailing list archives."""
+ return self.context.archive_url
+
+ def bookmarks(self):
+ """Return a list of bookmarks from a cookie.
+
+ Each bookmark is expressed as a datetime object.
+ """
+ bookmarks = []
+ cookie = self.request.get('bookmarks', '')
+ for item in cookie.split():
+ try:
+ bookmarks.append(parseDatetimetz(item))
+ except (DateTimeError, IndexError):
+ pass
+ return bookmarks
+
+ def placeBookmark(self):
+ """Place a new bookmark after the latest message in a cookie."""
+ if int(self.request.get('start', 0)) > 0:
+ return # The user can't see the newest checkins
+ if not self.context.messages:
+ return # No messages -- no bookmarks
+ bookmarks = self.bookmarks()
+ bookmarks.sort()
+ # Do not insert a bookmark if there were no checkins since the last
+ # bookmark
+ if (bookmarks and bookmarks[-1] >= self.context.messages[0].date):
+ return
+ bookmarks.append(self.context.messages[0].date)
+ if len(bookmarks) > self.max_bookmarks:
+ del bookmarks[:-self.max_bookmarks]
+ cookie = " ".join([dt.isoformat() for dt in bookmarks])
+ self.request.response.setCookie('bookmarks', cookie,
+ max_age=365*24*60*60) # 1 year
+
+ def checkins(self, start=None, size=None):
+ """Return a list of messages.
+
+ Returns the last 'size' checkin messages in self.context, newest
+ first, skipping the first 'start' messages.
+ """
+ if start is None:
+ start = int(self.request.get('start', 0))
+ if size is None:
+ size = int(self.request.get('size', 20))
+ last = start + size
+ items = self.context.messages[start:last]
+ items = removeAllProxies(items)
+
+ # insert bookmarks
+
+ def bookmarkBetween(msg1, msg2, bookmarks=self.bookmarks()):
+ for b in bookmarks:
+ if msg1.date > b >= msg2.date:
+ return True
+ return False
+
+ n = 0
+ while n < len(items) - 1:
+ if bookmarkBetween(items[n], items[n+1]):
+ items.insert(n+1, Bookmark())
+ n += 1
+ n += 1
+ # insert bookmarks before the first / after the last batch item
+ if items:
+ before = self.context.messages[:1]
+ if before and bookmarkBetween(before[0], items[0]):
+ items.insert(0, Bookmark())
+ after = self.context.messages[-1:]
+ if after and bookmarkBetween(items[-1], after[0]):
+ items.append(Bookmark())
+ return items
+
+ def renderCheckins(self, start=None, size=None):
+ """Return a list of checkins rendered into HTML.
+
+ See `checkins` for description of parameters."""
+ html = []
+ previous_message = None
+ for item in self.checkins(start=start, size=size):
+ if ICheckinMessage.providedBy(item):
+ same_as_previous = item.log_message == previous_message
+ previous_message = item.log_message
+ else:
+ same_as_previous = None
+ view = zapi.getMultiAdapter((item, self.request), name='html')
+ output = view(same_as_previous=same_as_previous)
+ html.append(output)
+ return "".join(html)
+
+ def count(self):
+ """Return the number of checkin messages in the archive."""
+ return len(self.context.messages)
+
+
+class MessageRSSView(BrowserView):
+ """View for messages.
+
+ Makes sure the page template is treated as XML.
+ """
+
+ index = ViewPageTemplateFile('rss_message.pt', content_type='text/xml')
+
+
+class MessageView:
+ """View mixin for messages."""
+
+ _archive = None
+ _index = None
+
+ def _calc_index(self):
+
+ if not self.context.__parent__:
+ return
+
+ self._archive = self.context.__parent__.messages
+ try:
+ self._index = self._archive.index(self.context)
+ except ValueError:
+ pass
+
+ def previous(self):
+ """Return the previous message in archive."""
+ self._calc_index()
+ if self._index is not None and self._index < len(self._archive) - 1:
+ return self._archive[self._index + 1]
+ else:
+ return None
+
+ def next(self):
+ """Return the next message in archive."""
+ self._calc_index()
+ if self._index is not None and self._index > 0:
+ return self._archive[self._index - 1]
+ else:
+ return None
+
+ def last(self):
+ """Return the last message in archive."""
+ self._calc_index()
+ if self._archive:
+ return self._archive[0]
+ else:
+ return None
+
+ def first(self):
+ """Return the first message in archive."""
+ self._calc_index()
+ if self._archive:
+ return self._archive[-1]
+ else:
+ return None
+
+ def icon(self):
+ """Return a mapping describing an icon for this checkin."""
+ return {'src': '++resource++message.png',
+ 'alt': 'Message',
+ 'title': 'Email message'}
+
+ def body(self):
+ """Colorize the body of a message."""
+ text = self.context.body.replace('\r', '')\
+ .replace('&', '&') \
+ .replace('<', '<') \
+ .replace('>', '>') \
+ .replace('"', '"')
+ # It would be nice to highlight quoted text here
+ return '<pre>%s</pre>' % text
+
+ def body_plain(self):
+ """Return the full text of the message."""
+ return self.context.full_text
+
+
+class CheckinMessageView(MessageView):
+ """View mixin for checkin messages."""
+
+ _subtrees = None
+
+ def subtrees(self):
+ """Return a sequence of tuples (prefix, icon, alt, title).
+
+ (icon, alt, title) are the resource name, alt text and tooltip used
+ for any checkin messages that have directory starting with prefix.
+
+ This information is currently taken from Dublin Core metadata
+ description field, third paragraph. Every line in that paragraph
+ defines a subtree, with all fields separated by spaces or tabs.
+ """
+ if self._subtrees is not None:
+ return self._subtrees
+ self._subtrees = []
+ container = self.context.__parent__
+ icons = container.icons
+ if not icons:
+ return self._subtrees
+ for line in icons.splitlines():
+ if line.startswith('#'):
+ continue
+ items = line.split(None, 3)
+ if len(items) < 4:
+ continue
+ if items[0] == '*': # catch-all
+ items[0] = ''
+ self._subtrees.append(items)
+ return self._subtrees
+
+ def icon(self):
+ """Return a mapping describing an icon for this checkin."""
+ for prefix, icon, alt, title in self.subtrees():
+ if self.context.directory.startswith(prefix):
+ return {'src': '++resource++%s' % icon,
+ 'alt': alt,
+ 'title': title}
+ return {'src': '++resource++product.png',
+ 'alt': 'Checkin',
+ 'title': 'Checkin'}
+
+ def body(self):
+ """Colorize checkin message body."""
+ # XXX This method is rather bloated and hard to understand.
+ text = self.context.body.replace('\r', '') \
+ .replace('&', '&') \
+ .replace('<', '<') \
+ .replace('>', '>') \
+ .replace('"', '"')
+
+ text = re.sub(r'(https?://.+?)'
+ r'($|[ \t\r\n)]|>|"|[.,](?:$|[ \t\r\n]))',
+ r'<a href="\1">\1</a>\2', text)
+
+ log_idx = text.find('\nLog message:\n')
+ if log_idx == -1:
+ log_idx = text.find('\nLog Message:\n')
+ if log_idx != -1:
+ log_idx += len('\nLog message:\n')
+ if log_idx == -1:
+ log_idx = text.find('\nLog:\n')
+ if log_idx != -1:
+ log_idx += len('\nLog:\n')
+ else:
+ log_idx = text.find('Log message')
+ # XXX: Specific to Zope3 checkins
+ if log_idx != -1:
+ log_idx = text.find('\n', log_idx) + 1
+ if log_idx == -1:
+ return '<pre>%s</pre>' % text
+
+ sig_idx = text.rfind(
+ '\n_______________________________________________')
+ if sig_idx == -1:
+ sig_idx = len(text)
+
+ diff_idx = text.find('\n===')
+ if diff_idx == -1:
+ diff_idx = sig_idx
+
+ status_idx = text.find('\nStatus:\n')
+ if status_idx == -1:
+ status_idx = text.find('\nChanged:\n')
+ if status_idx == -1:
+ if text[diff_idx:diff_idx+5] == "\n====":
+ # Subversion
+ status_idx = text.rfind('\n', 0, diff_idx)
+ else:
+ status_idx = diff_idx
+
+ propchange_idx = text.find('\nProperty changes on:')
+ if propchange_idx != -1 and propchange_idx < status_idx:
+ status_idx = propchange_idx
+
+ assert log_idx <= status_idx <= diff_idx <= sig_idx
+
+ intro = text[:log_idx]
+ log = text[log_idx:status_idx].strip()
+ import_status = text[status_idx:diff_idx]
+ diff = text[diff_idx:sig_idx]
+ sig = text[sig_idx:]
+
+ def empty2nbsp(s):
+ if not s:
+ return ' '
+ n = 0
+ while n < len(s) and s[n] == ' ':
+ n += 1
+ if n:
+ return ' ' * n + s[n:]
+ else:
+ return s
+ log = '<p>%s</p>' % '</p>\n<p>'.join(map(empty2nbsp, log.splitlines()))
+
+ if import_status is None:
+ import_status = ''
+
+ if diff is None:
+ diff = ''
+
+ diff = "\n".join(map(self.mark_whitespace, diff.splitlines()))
+
+ def colorize(style):
+ return r'<div class="%s">\1</div>' % style
+
+ # Unified diff
+ diff = re.sub(r'(?m)^(===.*)$', colorize("file"), diff)
+ diff = re.sub(r'(?m)^(---.*)$', colorize("oldfile"), diff)
+ diff = re.sub(r'(?m)^(\+\+\+.*)$', colorize("newfile"), diff)
+ diff = re.sub(r'(?m)^(@@.*)$', colorize("chunk"), diff)
+ diff = re.sub(r'(?m)^(-.*)$', colorize("old"), diff)
+ diff = re.sub(r'(?m)^(\+.*)$', colorize("new"), diff)
+
+ # Postprocess for Mozilla
+ diff = re.sub('</div>\n', '\n</div>', diff)
+
+ if sig:
+ sig = '<div class="signature">%s</div>' % sig
+
+ text = '<pre>%s</pre><div class="log">%s</div><pre>%s%s%s</pre>' \
+ % (intro, log, import_status, diff, sig)
+ # TODO: find out the actual encoding instead of assuming UTF-8
+ return unicode(text, 'UTF-8', 'replace')
+
+ def mark_whitespace(self, line, tab=('>', '-'), trail='.'):
+ """Mark whitespace in diff lines.
+
+ Suggested values for tab: ('>', '-'), ('»', ' '),
+ ('»', '‐')
+
+ Suggested values for trail: '.', '␣'
+ """
+ if line == ' ' or (not line.endswith(' ') and '\t' not in line):
+ return line
+ m = re.search(r'\s+\Z', line)
+ if m:
+ n = m.start()
+ if n == 0:
+ n = 1 # don't highlight the first space in a diff
+ line = '%s<span class="trail">%s</span>' % (line[:n],
+ line[n:].replace(' ', trail))
+ if '\t' in line:
+ NORMAL, TAG, ENTITY = 0, 1, 2
+ idx = col = 0
+ mode = NORMAL
+ tabs = []
+ for c in line[1:]: # ignore first space in a diff
+ idx += 1
+ if mode == TAG:
+ if c == '>':
+ mode = NORMAL
+ elif mode == ENTITY:
+ if c == ';':
+ col += 1
+ mode = NORMAL
+ else:
+ if c == '<':
+ mode = TAG
+ elif c == '&':
+ mode = ENTITY
+ elif c == '\t':
+ width = 8 - (col % 8)
+ tabs.append((idx, width))
+ col += width
+ else:
+ col += 1
+ if tabs:
+ parts = []
+ last = 0
+ for idx, width in tabs:
+ parts.append(line[last:idx])
+ parts.append('<span class="tab">%s%s</span>'
+ % (tab[0], tab[1] * (width - 1)))
+ last = idx + 1
+ parts.append(line[last:])
+ line = "".join(parts)
+ return line
Property changes on: Zope3/branches/srichter-blow-services/src/z3checkins/browser.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Modified: Zope3/branches/srichter-blow-services/src/z3checkins/configure.zcml
===================================================================
--- Zope3/branches/srichter-blow-services/src/z3checkins/configure.zcml 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/z3checkins/configure.zcml 2005-01-17 15:30:10 UTC (rev 28853)
@@ -1,8 +1,7 @@
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser"
- i18n_domain='z3checkins'
- >
+ i18n_domain='z3checkins'>
<!-- CheckinMessage content object -->
@@ -40,6 +39,9 @@
<require permission="zope.View"
interface=".interfaces.ICheckinFolderSchema" />
+ <require permission="zope.View"
+ attributes="messages" />
+
<factory id="z3checkins.CheckinFolder"
title="Checkin Folder"
description="A checkin folder" />
@@ -51,11 +53,6 @@
<!-- Utilities -->
- <adapter for="zope.app.container.interfaces.IReadContainer"
- factory=".message.MessageContainerAdapter"
- permission="zope.View"
- provides=".interfaces.IMessageArchive" />
-
<adapter for=".interfaces.ICheckinFolder"
factory=".folder.MessageNameChooser"
permission="zope.View"
@@ -73,12 +70,12 @@
<!-- Generic views for date/time formatting -->
- <!-- XXX: there should be an interface that datetime.datetime implements -->
+ <!-- TODO: there should be an interface that datetime.datetime implements -->
<view
for="*"
name="rfc822"
- factory=".message.RFCDateTimeFormatter"
+ factory=".timeutils.RFCDateTimeFormatter"
type="zope.publisher.interfaces.http.IHTTPRequest"
permission="zope.Public"
/>
@@ -86,7 +83,7 @@
<view
for="*"
name="isodatetime"
- factory=".message.ISODateTimeFormatter"
+ factory=".timeutils.ISODateTimeFormatter"
type="zope.publisher.interfaces.http.IHTTPRequest"
permission="zope.Public"
/>
@@ -99,11 +96,11 @@
fields="data"
label="Upload a checkin message"
permission="zope.ManageContent"
- class=".message.MessageUpload" />
+ class=".browser.MessageUpload" />
<browser:addMenuItem
title="Checkin message"
- class=".message.MessageUpload"
+ class=".browser.MessageUpload"
permission="zope.ManageContent"
view="CheckinMessage" />
@@ -135,9 +132,9 @@
<browser:editform
name="EditFolder"
- schema=".interfaces.ICheckinFolder"
+ schema=".interfaces.ICheckinFolderSchema"
label="Change properties of a checkin message folder"
- menu="zmi_views" title="Edit"
+ menu="zmi_views" title="Edit properties"
permission="zope.ManageContent" />
<!-- Browser views: email message -->
@@ -145,7 +142,7 @@
<browser:page
for=".interfaces.IMessage"
name="rss"
- class=".message.MessageRSSView"
+ class=".browser.MessageRSSView"
attribute="index"
permission="zope.View" />
@@ -153,28 +150,28 @@
for=".interfaces.IMessage"
name="html"
template="message_part.pt"
- class=".message.MessageView"
+ class=".browser.MessageView"
permission="zope.View" />
<browser:page
for=".interfaces.IMessage"
name="html-sidebar"
template="message_sidebar.pt"
- class=".message.MessageView"
+ class=".browser.MessageView"
permission="zope.View" />
<browser:page
for=".interfaces.IMessage"
name="index.html"
template="message.pt"
- class=".message.MessageView"
+ class=".browser.MessageView"
permission="zope.View" />
<browser:page
for=".interfaces.IMessage"
name="index.txt"
- template="message_plain.pt"
- class=".message.MessageView"
+ class=".browser.MessageView"
+ attribute="body_plain"
permission="zope.View" />
<!-- Browser views: checkin message -->
@@ -182,7 +179,7 @@
<browser:page
for=".interfaces.ICheckinMessage"
name="rss"
- class=".message.MessageRSSView"
+ class=".browser.MessageRSSView"
attribute="index"
permission="zope.View" />
@@ -190,31 +187,24 @@
for=".interfaces.ICheckinMessage"
name="html"
template="message_part.pt"
- class=".message.CheckinMessageView"
+ class=".browser.CheckinMessageView"
permission="zope.View" />
<browser:page
for=".interfaces.ICheckinMessage"
name="html-sidebar"
template="message_sidebar.pt"
- class=".message.CheckinMessageView"
+ class=".browser.CheckinMessageView"
permission="zope.View" />
<browser:page
for=".interfaces.ICheckinMessage"
name="index.html"
template="message.pt"
- class=".message.CheckinMessageView"
+ class=".browser.CheckinMessageView"
menu="zmi_views" title="Preview"
permission="zope.View" />
- <browser:page
- for=".interfaces.ICheckinMessage"
- name="index.txt"
- template="message_plain.pt"
- class=".message.CheckinMessageView"
- permission="zope.View" />
-
<!-- Browser views: bookmark -->
<browser:page
@@ -225,25 +215,30 @@
<!-- Browser views: containers -->
+ <browser:containerViews
+ for=".interfaces.ICheckinFolder"
+ contents="zope.ManageContent" />
+
<browser:page
for=".interfaces.ICheckinFolder"
name="checkins.rss"
template="rss_container.pt"
- class=".message.ContainerView"
+ class=".browser.ContainerView"
permission="zope.View" />
<browser:page
for=".interfaces.ICheckinFolder"
name="index.html"
template="container.pt"
- class=".message.ContainerView"
- permission="zope.View" />
+ class=".browser.ContainerView"
+ permission="zope.View"
+ menu="zmi_views" title="View" />
<browser:page
for=".interfaces.ICheckinFolder"
name="checkins-sidebar.html"
template="container_sidebar.pt"
- class=".message.ContainerView"
+ class=".browser.ContainerView"
permission="zope.View" />
<!-- Resources -->
Modified: Zope3/branches/srichter-blow-services/src/z3checkins/folder.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/z3checkins/folder.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/z3checkins/folder.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -3,22 +3,71 @@
Checkin message folder handling.
-$Id: folder.py,v 1.5 2004/03/14 10:56:48 gintautasm Exp $
+$Id$
"""
+from persistent.list import PersistentList
from zope.interface import implements
from zope.app.container.btree import BTreeContainer
from zope.app.container.interfaces import INameChooser
from zope.app.container.interfaces import IContainerNamesContainer
from zope.app.size.interfaces import ISized
-from interfaces import ICheckinFolder
+from z3checkins.interfaces import ICheckinFolder, IMessage
+
class CheckinFolder(BTreeContainer):
- """A message folder."""
+ """A message folder.
+ The attribute `messages` is a list of Messages sorted by date in
+ reverse order (that is, latest messages first). It was introduced for
+ performance reasons.
+ """
+
implements(ICheckinFolder, IContainerNamesContainer)
+ description = None
+ archive_url = None
+ icons = None
+ def __init__(self):
+ BTreeContainer.__init__(self)
+ self.messages = PersistentList()
+
+ def __setitem__(self, key, message):
+ BTreeContainer.__setitem__(self, key, message)
+ if IMessage.providedBy(message):
+ for ind, oldmsg in enumerate(self.messages):
+ if message.date > oldmsg.date:
+ self.messages.insert(ind, message)
+ break
+ else:
+ self.messages.append(message)
+
+ def __delitem__(self, key):
+ message = self.get(key)
+ if IMessage.providedBy(message):
+ for ind, oldmsg in enumerate(self.messages):
+ if oldmsg is message:
+ self.messages.pop(ind)
+ break
+ BTreeContainer.__delitem__(self, key)
+
+ def __setstate__(self, state):
+ """Rebuild the internal message date index.
+
+ This method is for backwards compatibility, so that data does
+ not need to be reimported into existing z3checkins instances.
+ """
+ # BBB 2005-01-05
+ BTreeContainer.__setstate__(self, state)
+ if not hasattr(self, 'messages'):
+ self.messages = [msg for msg in self.values()
+ if IMessage.providedBy(msg)]
+ self.messages.sort(lambda msg1, msg2: cmp(msg2.date, msg1.date))
+ if not isinstance(self.messages, PersistentList):
+ self.messages = PersistentList(self.messages)
+
+
class MessageNameChooser:
"""An adapter to choose names for messages."""
@@ -36,6 +85,7 @@
class MessageSized:
"""An adapter to calculate size of a message."""
+
implements(ISized)
def __init__(self, message):
Property changes on: Zope3/branches/srichter-blow-services/src/z3checkins/folder.py
___________________________________________________________________
Name: svn:keywords
+ Id
Modified: Zope3/branches/srichter-blow-services/src/z3checkins/interfaces.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/z3checkins/interfaces.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/z3checkins/interfaces.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -1,15 +1,13 @@
"""
Interfaces for the z3checkins product.
-$Id: interfaces.py,v 1.14 2004/05/15 13:23:57 gintautasm Exp $
+$Id$
"""
from zope.interface import Interface, Attribute
-from zope.app.folder.interfaces import IFolder
from zope.app.container.interfaces import IContainer, IContained
-from zope.app.container.constraints import ContainerTypesConstraint
-from zope.app.container.constraints import ItemTypePrecondition
-from zope.schema import Field, Text, TextLine
+from zope.app.container.constraints import contains, containers
+from zope.schema import Field, Text, TextLine, List, Object, Datetime
class IMessageUpload(Interface):
@@ -19,13 +17,13 @@
class IMessage(IMessageUpload):
"""Mail message."""
- message_id = Attribute("Unique message ID")
- author_name = Attribute("Author's real name")
- author_email = Attribute("Author's email address")
- subject = Attribute("Subject line of the message")
- date = Attribute("Date and time of the message")
- body = Attribute("Body of the message")
- full_text = Attribute("Full message text (headers and body)")
+ message_id = TextLine(title=u"Unique message ID")
+ author_name = TextLine(title=u"Author's real name")
+ author_email = TextLine(title=u"Author's email address")
+ subject = TextLine(title=u"Subject line of the message")
+ date = Datetime(title=u"Date and time of the message")
+ body = Text(title=u"Body of the message")
+ full_text = Text(title=u"Full message text (headers and body)")
class ICheckinMessage(IMessage):
@@ -69,42 +67,20 @@
icons = Text(title=u"Icon definitions", required=False)
-class ICheckinFolder(IFolder, ICheckinFolderSchema):
+class ICheckinFolder(IContainer, ICheckinFolderSchema):
"""A marker interface for the checkins folder."""
- def __setitem__(name, object):
- """Add a message"""
+ contains(IMessageUpload)
- __setitem__.precondition = ItemTypePrecondition(IMessageUpload)
+ messages = List(title=u"Messages",
+ description=u"""
+ Messages in this container, sorted by date in
+ descending order.
+ """,
+ value_type=Object(title=u"A message", schema=IMessage))
class IMessageContained(IContained):
"""A contained message."""
- __parent__ = Field(constraint=ContainerTypesConstraint(ICheckinFolder))
-
-
-class IMessageArchive(Interface):
- """A chronologically ordered sequence of messages.
-
- Implements the Python sequence procotol.
- """
-
- def __len__():
- """Returns the number of messages in the archive."""
-
- def __getitem__(index):
- """Returns a given message."""
-
- def __getslice__(start, stop):
- """Returns a range of messages."""
-
- def __iter__():
- """Returns an iterator."""
-
- def index(message):
- """Returns the index of a given message.
-
- Raises ValueError if message is not in the archive.
- """
-
+ containers(ICheckinFolder)
Property changes on: Zope3/branches/srichter-blow-services/src/z3checkins/interfaces.py
___________________________________________________________________
Name: svn:keywords
+ Id
Modified: Zope3/branches/srichter-blow-services/src/z3checkins/message.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/z3checkins/message.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/z3checkins/message.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -1,128 +1,55 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation 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 code for z3checkins product.
+The z3checkins product.
-# This module could be split into three: timeutils.py, message.py and views.py
-# but it is small enough IMHO.
-
-$Id: message.py,v 1.40 2004/05/14 19:56:05 gintautasm Exp $
+$Id$
"""
-
-import re
import email
import email.Utils
import mailbox
import time
-from StringIO import StringIO
from datetime import datetime, tzinfo, timedelta
-
from persistent import Persistent
-from zope.app.form import CustomWidgetFactory
-from zope.app.form.browser import FileWidget
+from StringIO import StringIO
+
+from zope.exceptions import DuplicationError
+from zope.interface import implements
+from zope.proxy import removeAllProxies
+
+from zope.app import zapi
+from zope.app.container.contained import Contained
from zope.app.container.interfaces import IReadContainer
from zope.app.datetimeutils import parseDatetimetz, DateTimeError
from zope.app.dublincore.interfaces import IZopeDublinCore
+from zope.app.form import CustomWidgetFactory
+from zope.app.form.browser import FileWidget
from zope.app.pagetemplate import ViewPageTemplateFile
-from zope.component import getUtility
-from zope.component import getView
-from zope.exceptions import DuplicationError
-from zope.interface import implements
-from zope.proxy import removeAllProxies
from zope.app.publisher.browser import BrowserView
-from interfaces import IMessage, ICheckinMessage, IMessageContained
-from interfaces import IMessageUpload, IBookmark
-from interfaces import IMessageParser, IMessageArchive
-from interfaces import FormatError
+from z3checkins.interfaces import IMessage, ICheckinMessage, IMessageContained
+from z3checkins.interfaces import IBookmark, IMessageParser, FormatError
+from z3checkins.timeutils import FixedTimezone
__metaclass__ = type
-#
-# Date/time utils
-#
-class FixedTimezone(tzinfo):
- """Timezone with a fixed UTC offset"""
-
- def __init__(self, offset=None):
- """Creates a timezone with a given UTC offset (minutes east of UTC)."""
- self._offset = offset
-
- def tzname(self, dt):
- if self._offset >= 0:
- sign = '+'
- h, m = divmod(self._offset, 60)
- else:
- sign = '-'
- h, m = divmod(-self._offset, 60)
- return '%c%02d%02d' % (sign, h, m)
-
- def utcoffset(self, dt):
- return timedelta(minutes=self._offset)
-
- def dst(self, dt):
- return timedelta(0)
-
-
-class RFCDateTimeFormatter:
- """RFC822 view for datetime objects."""
-
- def __init__(self, context, request):
- self.context = context
- self.request = request
-
- def __str__(self):
- """Renders datetime objects in RFC822 format."""
- return self.context.strftime("%a, %d %b %Y %H:%M:%S %z")
-
- __call__ = __str__
-
-
-class ISODateTimeFormatter:
- """ISO 8601 view for datetime objects."""
-
- if time.localtime()[-1]:
- userstz = FixedTimezone(-time.altzone / 60)
- else:
- userstz = FixedTimezone(-time.timezone / 60)
-
- def __init__(self, context, request):
- self.context = context
- self.request = request
-
- def __str__(self):
- """Renders datetime objects as "YYYY-MM-DD hh:mm" in the local time
- zone."""
- return self.context.astimezone(self.userstz).strftime("%Y-%m-%d %H:%M")
-
- __call__ = __str__
-
-
-#
-# Checkin message content object
-#
-
-def find_body_start(full_text):
- """Find the body of an RFC-822 message and return its index in full_text."""
- pos1 = full_text.find('\n\n')
- pos2 = full_text.find('\r\n\r\n')
- if pos1 == -1:
- pos1 = len(full_text)
- else:
- pos1 += 2
- if pos2 == -1:
- pos2 = len(full_text)
- else:
- pos2 += 4
- return min(pos1, pos2)
-
-
-class Message(Persistent):
+class Message(Persistent, Contained):
"""Persistent email message."""
implements(IMessage, IMessageContained)
- __parent__ = __name__ = None
-
def __init__(self, message_id=None, author_name=None,
author_email=None, subject=None, date=None,
full_text=None):
@@ -174,7 +101,6 @@
def parse(self, input):
"""See IMessageParser."""
-
if not hasattr(input, 'readline'):
full_text = str(input)
elif hasattr(input, 'seek') and hasattr(input, 'tell'):
@@ -189,7 +115,7 @@
message_id = m.get('Message-Id', None)
if message_id is None:
raise FormatError("Message does not have a message id")
- if message_id[0] == "<" and message_id[-1] == ">":
+ if message_id.startswith("<") and message_id.endswith(">"):
message_id = message_id[1:-1] # strip angle brackets
author = m.get('From', '')
author_name, author_email = email.Utils.parseaddr(author)
@@ -202,7 +128,7 @@
(year, month, day, hours, minutes, seconds,
weekday, yearday, dst, tzoffset) = email.Utils.parsedate_tz(date)
- # TODO: a workaround to deal with messages that don't specify a timezone
+ # A workaround to deal with messages that don't specify a timezone.
if tzoffset is None:
tzoffset = 0
@@ -230,7 +156,6 @@
Returns a tuple (directory, log_message, branch) for checkin
messages, and None if the message is not a checkin message.
"""
-
if subject.startswith("Re:"):
return None
@@ -242,7 +167,7 @@
subject = parts[1]
directory = subject.split(' - ')[0]
elif "SVN:" in subject:
- # TODO: Format specific to the Zope3 mailing list
+ # XXX: Format specific to the Zope3 mailing list
# [foo-bar] SVN: foobaz/boo/bar.py log message
parts = subject.split("SVN: ", 1)
if len(parts) < 2:
@@ -276,14 +201,14 @@
for line in lines:
if in_log_msg:
if (line.startswith('=== ')
- or line.startswith('-=-') # TODO: Zope3 ML specific
+ or line.startswith("Changed:")
or line.startswith("Added:")
or line.startswith("Modified:")
or line.startswith("Removed:")
or line.startswith("Deleted:")
+ or line.startswith("Copied:")
or line.startswith("Property changes on:")
- or line == "Status:"
- ):
+ or line == "Status:"):
break
else:
log_message.append(line)
@@ -298,471 +223,25 @@
return "\n".join(log_message).strip(), branch
-class MessageContainerAdapter:
- """Adapts a container to a message archive."""
-
- implements(IMessageArchive)
- __used_for__ = IReadContainer
-
- def __init__(self, context):
- self.context = context
- items = []
- for key, item in self.context.items():
- if IMessage.providedBy(item):
- items.append((item.date, key, item))
- items.sort()
- self.messages = []
- for date, key, item in items:
- # TODO: is this nice?
- #item.__parent__ = self.context
- #item.__name__ = key
- self.messages.append(item)
-
- def __len__(self):
- return len(self.messages)
-
- def __getitem__(self, index):
- return self.messages[index]
-
- def __getslice__(self, start, stop):
- return self.messages[start:stop]
-
- def __iter__(self):
- return iter(self.messages)
-
- def index(self, message):
- return self.messages.index(message)
-
-
class Bookmark:
+ """A bookmark placed between messages."""
implements(IBookmark)
-#
-# Browser views
-#
+def find_body_start(full_text):
+ """Find the body of an RFC-822 message and return its index in full_text."""
+ pos1 = full_text.find('\n\n')
+ pos2 = full_text.find('\r\n\r\n')
+ if pos1 == -1:
+ pos1 = len(full_text)
+ else:
+ pos1 += 2
+ if pos2 == -1:
+ pos2 = len(full_text)
+ else:
+ pos2 += 4
+ return min(pos1, pos2)
-class MessageUpload:
- """Adding view mixin for uploading checkin messages."""
-
implements(IMessageUpload, IMessageContained)
data_widget = CustomWidgetFactory(FileWidget)
-
- def createAndAdd(self, data):
- if data.has_key('data'): # XXX should we bark if no data is given?
- msg_raw = data['data']
- parser = getUtility(IMessageParser)
- if msg_raw.startswith("From "):
- # detected an mbox file
- mbox = StringIO(msg_raw)
- messages = mailbox.PortableUnixMailbox(mbox,
- factory=parser.parse)
- for message in messages:
- try:
- self.add(message)
- dc = IZopeDublinCore(message, None)
- if dc is not None:
- # TODO: should handle RFC-2047
- dc.title = unicode(message.subject)
- dc.created = message.date
- except DuplicationError:
- pass # leave the old mesage unchanged
- else:
- message = parser.parse(msg_raw)
- self.add(message)
-
-
-class ContainerView:
- """View mixin for locating checkin messages in a container."""
-
- max_bookmarks = 5
-
- def title(self):
- """Returns the title of this archive.
-
- Title is obtained from Dublin Core metadata of the folder. If it is
- empty, "Zope 3 Checkins" is used.
- """
- dc = IZopeDublinCore(self.context, None)
- if dc is not None:
- title = dc.title
- else:
- title = ''
- return title or "Zope 3 Checkins"
-
- def description(self):
- """Returns the description of this archive.
- """
- return self.context.description
-
- def archive_url(self):
- """Returns the URL for mailing list archives.
- """
- return self.context.archive_url
-
- def bookmarks(self):
- """Returns a list of bookmarks from a cookie. Each bookmark is
- expressed as a datetime object.
- """
- bookmarks = []
- cookie = self.request.get('bookmarks', '')
- for item in cookie.split():
- try:
- bookmarks.append(parseDatetimetz(item))
- except (DateTimeError, IndexError):
- pass
- return bookmarks
-
- def placeBookmark(self):
- """Place a new bookmark after the latest checkin message in a
- cookie."""
- if int(self.request.get('start', 0)) > 0:
- return # The user can't see the newest checkins
- if not hasattr(self, '_archive'):
- self._archive = IMessageArchive(self.context)
- if not self._archive:
- return # No messages -- no bookmarks
- bookmarks = self.bookmarks()
- bookmarks.sort()
- # Do not insert a bookmark if there were no checkins since the last
- # bookmark
- if (bookmarks and bookmarks[-1] >= self._archive[-1].date):
- return
- bookmarks.append(self._archive[-1].date)
- if len(bookmarks) > self.max_bookmarks:
- del bookmarks[:-self.max_bookmarks]
- cookie = " ".join([dt.isoformat() for dt in bookmarks])
- self.request.response.setCookie('bookmarks', cookie,
- max_age=365*24*60*60) # 1 year
-
- def checkins(self, start=None, size=None):
- """Returns a list of the last 'size' checkin messages in
- self.context, newest first, skipping the first 'start' messages.
- """
- if start is None: start = int(self.request.get('start', 0))
- if size is None: size = int(self.request.get('size', 20))
- if not hasattr(self, '_archive'):
- self._archive = IMessageArchive(self.context)
- idx = len(self._archive) - start
- items = self._archive[max(0, idx-size):idx]
- items = removeAllProxies(items)
- # insert bookmarks
- def bookmarkBetween(msg1, msg2, bookmarks=self.bookmarks()):
- for b in bookmarks:
- if msg1.date <= b < msg2.date:
- return True
- return False
- n = 1
- while n < len(items):
- if bookmarkBetween(items[n-1], items[n]):
- items.insert(n, Bookmark())
- n += 2
- else:
- n += 1
- # insert bookmarks before the first/after the last batch item
- if items:
- before = self._archive[max(0, idx-size-1):max(0, idx-size)]
- if before and bookmarkBetween(before[0], items[0]):
- items.insert(0, Bookmark())
- after = self._archive[idx:idx+1]
- if after and bookmarkBetween(items[-1], after[0]):
- items.insert(len(items), Bookmark())
- # reverse order to present newest checkins first
- items.reverse()
- return items
-
- def renderCheckins(self, start=None, size=None):
- """Returns a list of checkins rendered into HTML. See `checkins` for
- description of parameters."""
- html = []
- previous_message = None
- for item in self.checkins(start=start, size=size):
- if ICheckinMessage.providedBy(item):
- same_as_previous = item.log_message == previous_message
- previous_message = item.log_message
- else:
- same_as_previous = None
- view = getView(item, 'html', self.request)
- output = view(same_as_previous=same_as_previous)
- html.append(output)
- return "".join(html)
-
- def count(self):
- """Returns the number of checkin messages in the archive."""
- if not hasattr(self, '_archive'):
- self._archive = IMessageArchive(self.context)
- return len(self._archive)
-
-
-class MessageRSSView(BrowserView):
- """View for messages.
-
- Makes sure the page template is treated as XML.
- """
-
- index = ViewPageTemplateFile('rss_message.pt', content_type='text/xml')
-
-
-class MessageView:
- """View mixin for messages."""
-
- def _calc_index(self):
- if not hasattr(self, '_archive'):
- container = self.context.__parent__
- self._archive = container and IMessageArchive(container, None)
- if not self._archive:
- self._index = None
- elif not hasattr(self, '_index'):
- self._index = self._archive.index(self.context)
-
- def next(self):
- """Returns the next message in archive."""
- self._calc_index()
- if self._index is not None and self._index < len(self._archive) - 1:
- return self._archive[self._index + 1]
- else:
- return None
-
- def previous(self):
- """Returns the previous message in archive."""
- self._calc_index()
- if self._index is not None and self._index > 0:
- return self._archive[self._index - 1]
- else:
- return None
-
- def first(self):
- """Returns the first message in archive."""
- self._calc_index()
- if self._archive:
- return self._archive[0]
- else:
- return None
-
- def last(self):
- """Returns the last message in archive."""
- self._calc_index()
- if self._archive:
- return self._archive[-1]
- else:
- return None
-
- def icon(self):
- """Returns a mapping describing an icon for this checkin. The mapping
- contains 'src', 'alt' and 'title' attributes."""
- return {'src': '++resource++message.png',
- 'alt': 'Message',
- 'title': 'Email message'}
-
- def body(self):
- """Colorizes the body of a checkin message."""
-
- text = self.context.body.replace('\r', '')\
- .replace('&', '&') \
- .replace('<', '<') \
- .replace('>', '>') \
- .replace('"', '"')
- # It would be nice to highlight quoted text here
- return '<pre>%s</pre>' % text
-
-
-class CheckinMessageView(MessageView):
- """View mixin for checkin messages."""
-
- _subtrees = None
- def subtrees(self):
- """Returns a sequence of tuples (prefix, icon, alt, title).
-
- (icon, alt, title) are the resource name, alt text and tooltip used
- for any checkin messages that have directory starting with prefix.
-
- This information is currently taken from Dublin Core metadata
- description field, third paragraph. Every line in that paragraph
- defines a subtree, with all fields separated by spaces or tabs.
- """
- if self._subtrees is not None:
- return self._subtrees
- self._subtrees = []
- container = self.context.__parent__
- icons = container.icons
- if not icons:
- return self._subtrees
- for line in icons.splitlines():
- if line.startswith('#'):
- continue
- items = line.split(None, 3)
- if len(items) < 4:
- continue
- if items[0] == '*': # catch-all
- items[0] = ''
- self._subtrees.append(items)
- return self._subtrees
-
- def icon(self):
- """Returns a mapping describing an icon for this checkin. The mapping
- contains 'src', 'alt' and 'title' attributes."""
- for prefix, icon, alt, title in self.subtrees():
- if self.context.directory.startswith(prefix):
- return {'src': '++resource++%s' % icon,
- 'alt': alt,
- 'title': title}
- return {'src': '++resource++product.png',
- 'alt': 'Checkin',
- 'title': 'Checkin'}
-
- def body(self):
- """Colorizes checkin message body."""
-
- text = self.context.body.replace('\r', '')\
- .replace('&', '&') \
- .replace('<', '<') \
- .replace('>', '>') \
- .replace('"', '"')
-
- text = re.sub(r'(https?://.+?)'
- r'($|[ \t\r\n)]|>|"|[.,](?:$|[ \t\r\n]))',
- r'<a href="\1">\1</a>\2', text)
-
- log_idx = text.find('\nLog message:\n')
- if log_idx == -1:
- log_idx = text.find('\nLog Message:\n')
- if log_idx != -1:
- log_idx += len('\nLog message:\n')
- if log_idx == -1:
- log_idx = text.find('\nLog:\n')
- if log_idx != -1:
- log_idx += len('\nLog:\n')
- else:
- log_idx = text.find('Log message') #TODO: Zope3 checkin-specific
- if log_idx != -1:
- log_idx = text.find('\n', log_idx) + 1
- # TODO: This is yucky...
- text = text.replace('\n-=-\n', '')
- if log_idx == -1:
- return '<pre>%s</pre>' % text
-
- sig_idx = text.rfind(
- '\n_______________________________________________')
- if sig_idx == -1:
- sig_idx = len(text)
-
- diff_idx = text.find('\n===')
- if diff_idx == -1:
- diff_idx = sig_idx
-
- status_idx = text.find('\nStatus:\n')
- if status_idx == -1:
- if text[diff_idx:diff_idx+5] == "\n====":
- # Subversion
- status_idx = text.rfind('\n', 0, diff_idx)
- else:
- status_idx = diff_idx
-
- propchange_idx = text.find('\nProperty changes on:')
- if propchange_idx != -1 and propchange_idx < status_idx:
- status_idx = propchange_idx
-
- assert log_idx <= status_idx <= diff_idx <= sig_idx
-
- intro = text[:log_idx]
- log = text[log_idx:status_idx].strip()
- import_status = text[status_idx:diff_idx]
- diff = text[diff_idx:sig_idx]
- sig = text[sig_idx:]
-
- def empty2nbsp(s):
- if not s:
- return ' '
- n = 0
- while n < len(s) and s[n] == ' ':
- n += 1
- if n:
- return ' ' * n + s[n:]
- else:
- return s
- log = '<p>%s</p>' % '</p>\n<p>'.join(map(empty2nbsp, log.splitlines()))
-
- if import_status is None:
- import_status = ''
-
- if diff is None:
- diff = ''
-
- diff = "\n".join(map(self.mark_whitespace, diff.splitlines()))
-
- def colorize(style):
- return r'<div class="%s">\1</div>' % style
-
- # Unified diff
- diff = re.sub(r'(?m)^(===.*)$', colorize("file"), diff)
- diff = re.sub(r'(?m)^(---.*)$', colorize("oldfile"), diff)
- diff = re.sub(r'(?m)^(\+\+\+.*)$', colorize("newfile"), diff)
- diff = re.sub(r'(?m)^(@@.*)$', colorize("chunk"), diff)
- diff = re.sub(r'(?m)^(-.*)$', colorize("old"), diff)
- diff = re.sub(r'(?m)^(\+.*)$', colorize("new"), diff)
-
- # Postprocess for Mozilla
- diff = re.sub('</div>\n', '\n</div>', diff)
-
- if sig:
- sig = '<div class="signature">%s</div>' % sig
-
- text = '<pre>%s</pre><div class="log">%s</div><pre>%s%s%s</pre>' \
- % (intro, log, import_status, diff, sig)
- # TODO: find out the actual encoding instead of assuming UTF-8
- return unicode(text, 'UTF-8', 'replace')
-
- def mark_whitespace(self, line, tab=('>', '-'), trail='.'):
- """Mark whitespace in diff lines.
-
- Suggested values for tab: ('>', '-'), ('»', ' '),
- ('»', '‐')
-
- Suggested values for trail: '.', '␣'
- """
- if line == ' ' or (not line.endswith(' ') and '\t' not in line):
- return line
- m = re.search('\s+\Z', line)
- if m:
- n = m.start()
- if n == 0:
- n = 1 # don't highlight the first space in a diff
- line = '%s<span class="trail">%s</span>' % (line[:n],
- line[n:].replace(' ', trail))
- if '\t' in line:
- NORMAL, TAG, ENTITY = 0, 1, 2
- idx = col = 0
- mode = NORMAL
- tabs = []
- for c in line[1:]: # ignore first space in a diff
- idx += 1
- if mode == TAG:
- if c == '>':
- mode = NORMAL
- elif mode == ENTITY:
- if c == ';':
- col += 1
- mode = NORMAL
- else:
- if c == '<':
- mode = TAG
- elif c == '&':
- mode = ENTITY
- elif c == '\t':
- width = 8 - (col % 8)
- tabs.append((idx, width))
- col += width
- else:
- col += 1
- if tabs:
- parts = []
- last = 0
- for idx, width in tabs:
- parts.append(line[last:idx])
- parts.append('<span class="tab">%s%s</span>'
- % (tab[0], tab[1] * (width - 1)))
- last = idx + 1
- parts.append(line[last:])
- line = "".join(parts)
- return line
Property changes on: Zope3/branches/srichter-blow-services/src/z3checkins/message.py
___________________________________________________________________
Name: svn:keywords
+ Id
Deleted: Zope3/branches/srichter-blow-services/src/z3checkins/message_plain.pt
===================================================================
--- Zope3/branches/srichter-blow-services/src/z3checkins/message_plain.pt 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/z3checkins/message_plain.pt 2005-01-17 15:30:10 UTC (rev 28853)
@@ -1 +0,0 @@
-<tal:block tal:replace="structure context/full_text" />
Modified: Zope3/branches/srichter-blow-services/src/z3checkins/tests/svn_msg4.txt
===================================================================
--- Zope3/branches/srichter-blow-services/src/z3checkins/tests/svn_msg4.txt 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/z3checkins/tests/svn_msg4.txt 2005-01-17 15:30:10 UTC (rev 28853)
@@ -9,6 +9,9 @@
Changed a comment.
+Changed:
+ foo bar baz
+
-=-
Modified: Zope3/trunk/src/zope/app/traversing/interfaces.py
===================================================================
Copied: Zope3/branches/srichter-blow-services/src/z3checkins/tests/test_browser.py (from rev 28844, Zope3/trunk/src/z3checkins/tests/test_browser.py)
Property changes on: Zope3/branches/srichter-blow-services/src/z3checkins/tests/test_browser.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Copied: Zope3/branches/srichter-blow-services/src/z3checkins/tests/test_folder.py (from rev 28844, Zope3/trunk/src/z3checkins/tests/test_folder.py)
Property changes on: Zope3/branches/srichter-blow-services/src/z3checkins/tests/test_folder.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Modified: Zope3/branches/srichter-blow-services/src/z3checkins/tests/test_message.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/z3checkins/tests/test_message.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/z3checkins/tests/test_message.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -9,66 +9,16 @@
import os
import sys
import time
-from difflib import SequenceMatcher
-from datetime import datetime, timedelta
+from datetime import datetime
-from zope.app.tests import ztapi
-from zope.app.tests.placelesssetup import PlacelessSetup
-from zope.publisher.browser import TestRequest
from zope.interface import Interface, implements
from zope.interface.verify import verifyObject
-from zope.exceptions import DuplicationError
+from zope.app.container.contained import Contained
-from z3checkins.interfaces import IMessage, IMessageContained, \
- ICheckinMessage, IBookmark, IMessageParser, IMessageArchive
+from z3checkins.interfaces import IMessage, IMessageContained
+from z3checkins.interfaces import ICheckinMessage, IBookmark, IMessageParser
-class TestFixedTimezone(unittest.TestCase):
-
- def test_timezone(self):
- from z3checkins.message import FixedTimezone
- for tzoff, name in ((30, "+0030"), (-300, "-0500")):
- tz = FixedTimezone(tzoff)
- self.assertEquals(tz.tzname(None), name)
- self.assertEquals(tz.utcoffset(None), timedelta(minutes=tzoff))
- self.assertEquals(tz.dst(None), timedelta(0))
-
-
-class TestRFCDateTimeFormatter(unittest.TestCase):
-
- times = ((2003, 4, 2, 12, 33, 41, 3*60, "Wed, 02 Apr 2003 12:33:41 +0300"),
- (2000, 1, 2, 17, 41, 33, -5*60, "Sun, 02 Jan 2000 17:41:33 -0500"))
-
- def test_rfctime(self):
- from z3checkins.message import FixedTimezone, RFCDateTimeFormatter
- for Y, M, D, h, m, s, tz, res in self.times:
- dt = datetime(Y, M, D, h, m, s, tzinfo=FixedTimezone(tz))
- view = RFCDateTimeFormatter(dt, None)
- self.assertEquals(str(view), res)
- self.assertEquals(view(), res)
-
-
-class TestISODateTimeFormatter(unittest.TestCase):
-
- times = ((2003, 4, 2, 12, 33, 41, 3*60, "2003-04-02 09:33"),
- (2000, 1, 2, 17, 41, 33, -5*60, "2000-01-02 22:41"))
-
- def test_usertz(self):
- from z3checkins.message import ISODateTimeFormatter
- t = time.time()
- delta = ISODateTimeFormatter.userstz._offset * 60
- self.assertEquals(time.gmtime(t)[:8], time.localtime(t - delta)[:8])
-
- def test_isotime(self):
- from z3checkins.message import FixedTimezone, ISODateTimeFormatter
- for Y, M, D, h, m, s, tz, res in self.times:
- dt = datetime(Y, M, D, h, m, s, tzinfo=FixedTimezone(tz))
- dt -= ISODateTimeFormatter.userstz.utcoffset(None)
- view = ISODateTimeFormatter(dt, None)
- self.assertEquals(str(view), res)
- self.assertEquals(view(), res)
-
-
class TestCheckinMessage(unittest.TestCase):
def test_find_body_start(self):
@@ -113,7 +63,7 @@
def test_parser1(self):
from z3checkins.message import CheckinMessageParser
- from z3checkins.message import FixedTimezone
+ from z3checkins.timeutils import FixedTimezone
sample_msg1 = open_test_data("sample_msg1.txt")
sample_msg1_text = sample_msg1.read()
sample_msg1.seek(0)
@@ -135,7 +85,7 @@
def test_parser2(self):
from z3checkins.message import CheckinMessageParser
- from z3checkins.message import FixedTimezone
+ from z3checkins.timeutils import FixedTimezone
sample_msg2 = open_test_data("sample_msg2.txt")
sample_msg2_text = sample_msg2.read()
sample_msg2.seek(0)
@@ -163,7 +113,7 @@
def test_parser_importmsg(self):
from z3checkins.message import CheckinMessageParser
- from z3checkins.message import FixedTimezone
+ from z3checkins.timeutils import FixedTimezone
sample_import_msg = open_test_data("sample_import_msg.txt")
sample_import_msg_text = sample_import_msg.read()
sample_import_msg.seek(0)
@@ -185,7 +135,7 @@
def test_parser_simplemsg(self):
from z3checkins.message import CheckinMessageParser
- from z3checkins.message import FixedTimezone
+ from z3checkins.timeutils import FixedTimezone
simple_msg = open_test_data("simple_msg.txt")
simple_msg_text = simple_msg.read()
simple_msg.seek(0)
@@ -202,7 +152,7 @@
def test_parser_svnmsg(self):
from z3checkins.message import CheckinMessageParser
- from z3checkins.message import FixedTimezone
+ from z3checkins.timeutils import FixedTimezone
svn_msg = open_test_data("svn_msg.txt")
svn_msg_text = svn_msg.read()
svn_msg.seek(0)
@@ -222,7 +172,7 @@
def test_parser_svnmsg_with_split_subject(self):
from z3checkins.message import CheckinMessageParser
- from z3checkins.message import FixedTimezone
+ from z3checkins.timeutils import FixedTimezone
svn_msg2 = open_test_data("svn_msg2.txt")
svn_msg2_text = svn_msg2.read()
svn_msg2.seek(0)
@@ -242,7 +192,7 @@
def test_parser_svnmsg_with_rev(self):
from z3checkins.message import CheckinMessageParser
- from z3checkins.message import FixedTimezone
+ from z3checkins.timeutils import FixedTimezone
svn_msg3 = open_test_data("svn_msg3.txt")
svn_msg3_text = svn_msg3.read()
svn_msg3.seek(0)
@@ -258,7 +208,7 @@
def test_parser_svnmsg_zope(self):
from z3checkins.message import CheckinMessageParser
- from z3checkins.message import FixedTimezone
+ from z3checkins.timeutils import FixedTimezone
# TODO: The Zope 3 checkin mailing list uses a non-standard format.
# This test checks the compatibility hacks planted in the code.
# I hope that they will be removed in the future.
@@ -281,11 +231,11 @@
self.assertEquals(msg.body, svn_msg4_text.split("\n\n", 1)[1])
-class MessageStub:
+class MessageStub(Contained):
implements(ICheckinMessage, IMessageContained)
- __name__ = __parent__ = None
+ full_text = "full text of this message"
def __init__(self, data=None, date=None, body=None, log_message='',
subject='', message_id="message at id"):
@@ -297,775 +247,6 @@
self.message_id = message_id
-class TestMessageContainerAdapter(unittest.TestCase):
-
- def test_interface(self):
- from z3checkins.message import MessageContainerAdapter
- verifyObject(IMessageArchive, MessageContainerAdapter({}))
-
- def test_len(self):
- from z3checkins.message import MessageContainerAdapter
- a = MessageContainerAdapter({})
- self.assertEquals(len(a), 0)
- a = MessageContainerAdapter({'1': 2, '3': 'abc'})
- self.assertEquals(len(a), 0)
- a = MessageContainerAdapter({'1': 2, '3': 'abc', 4: MessageStub()})
- self.assertEquals(len(a), 1)
-
- def test_getitem(self):
- from z3checkins.message import MessageContainerAdapter
- a = MessageContainerAdapter({'1': 2, '3': 'abc',
- '4': MessageStub(date=1, message_id='1'),
- '5': MessageStub(date=4, message_id='2'),
- '6': MessageStub(date=3, message_id='3'),
- '7': MessageStub(date=2, message_id='4')})
- self.assertEquals(a[0].message_id, '1')
- self.assertEquals(a[1].message_id, '4')
- self.assertEquals(a[2].message_id, '3')
- self.assertEquals(a[3].message_id, '2')
- self.assertEquals(a[-1].message_id, '2')
- self.assertRaises(IndexError, a.__getitem__, 4)
- self.assertRaises(IndexError, a.__getitem__, -5)
- self.assertRaises(TypeError, a.__getitem__, 'xyzzy')
- self.assertRaises(TypeError, a.__getitem__, None)
- self.assertEquals(len(a[1:3]), 2)
- self.assertEquals(len(a[1:-1]), 2)
- self.assertEquals(len(a[3:1]), 0)
-
- def test_iter(self):
- from z3checkins.message import MessageContainerAdapter
- a = MessageContainerAdapter({'1': 2, '3': 'abc',
- '4': MessageStub(date=1, message_id='1'),
- '5': MessageStub(date=4, message_id='2'),
- '6': MessageStub(date=3, message_id='3'),
- '7': MessageStub(date=2, message_id='4')})
- b = [x.message_id for x in a]
- self.assertEquals(b, ['1', '4', '3', '2'])
- self.assert_(MessageStub(message_id='5') not in a)
- self.assert_(a.context['6'] in a)
-
- def test_index(self):
- from z3checkins.message import MessageContainerAdapter
- m1 = MessageStub(date=1, message_id='1')
- m2 = MessageStub(date=4, message_id='2')
- m3 = MessageStub(date=3, message_id='3')
- m4 = MessageStub(date=2, message_id='4')
- a = MessageContainerAdapter({'1': 2, '3': 'abc',
- '4': m1, '5': m2, '6': m3, '7': m4})
- self.assertEquals(a.index(m1), 0)
- self.assertEquals(a.index(m4), 1)
- self.assertEquals(a.index(m3), 2)
- self.assertEquals(a.index(m2), 3)
- self.assertRaises(ValueError, a.index, MessageStub)
-
-
-class ParserStub:
-
- implements(IMessageParser)
-
- def parse(self, data):
- if hasattr(data, 'read'):
- full_text = data.read()
- else:
- full_text = data
-
- message_id = "message at id"
- id_lines = filter(lambda s: s.lower().startswith("message-id: "),
- full_text.splitlines())
- if len(id_lines) == 1:
- message_id = id_lines[0][len("message-id: "):]
- return MessageStub(data=full_text, message_id=message_id)
-
-class AddingStub:
-
- def __init__(self):
- self.added = []
-
- def add(self, obj):
- # ignore duplicates happening with default messages
- if obj.message_id != "message at id":
- for message in self.added:
- if message.message_id == obj.message_id:
- raise DuplicationError()
- self.added.append(obj)
-
-class TestMessageUpload(PlacelessSetup, unittest.TestCase):
-
- def setUp(self):
- PlacelessSetup.setUp(self)
- ztapi.provideUtility(IMessageParser, ParserStub())
-
- def test_createAndAdd(self):
- from z3checkins.message import MessageUpload
- view = MessageUpload()
- view.context = AddingStub()
- view.add = view.context.add
- added = view.context.added
- self.assertEquals(len(added), 0)
- view.createAndAdd({})
- self.assertEquals(len(added), 0)
- view.createAndAdd({'data': 'Ipsum suum'})
- self.assertEquals(len(added), 1)
- self.assertEquals(added[0].__class__, MessageStub)
- self.assertEquals(added[0].message_id, "message at id")
- self.assertEquals(added[0].data, "Ipsum suum")
-
- def test_createAndAdd_mbox(self):
- from z3checkins.message import MessageUpload
- view = MessageUpload()
- view.context = AddingStub()
- view.add = view.context.add
- added = view.context.added
- data = open_test_data('mbox.txt').read()
- self.assertEquals(len(added), 0)
- view.createAndAdd({'data': data})
- self.assertEquals(len(added), 4)
- for message in added:
- self.assertEquals(message.__class__, MessageStub)
- self.assertEquals(added[0].data.count("Steve Alexander"), 1)
- self.assertEquals(added[1].data.count("Steve Alexander"), 1)
- self.assertEquals(added[2].data.count("Tim Peters"), 1)
- self.assertEquals(added[3].data.count("Tim Peters"), 1)
-
- def test_createAndAdd_mbox_with_dupes(self):
- from z3checkins.message import MessageUpload
- view = MessageUpload()
- view.context = AddingStub()
- view.add = view.context.add
- added = view.context.added
- data = open_test_data('mbox_with_dupes.txt').read()
- self.assertEquals(len(added), 0)
- view.createAndAdd({'data': data})
- self.assertEquals(len(added), 2)
- for message in added:
- self.assertEquals(message.__class__, MessageStub)
- self.assertEquals(added[0].data.count("Steve Alexander"), 1)
- self.assertEquals(added[1].data.count("Tim Peters"), 1)
-
-
-class IUnitTestPresentation(Interface):
- pass
-
-class MessageTestView:
- def __init__(self, context, request):
- self.context = context
- def __call__(self, same_as_previous=False):
- result = 'msg%d' % self.context.date
- if same_as_previous:
- result += '*'
- return result + '\n'
-
-class BookmarkTestView:
- def __init__(self, context, request):
- self.context = context
- def __call__(self, same_as_previous=False):
- return '-\n'
-
-class RequestStub(dict):
-
- _cookies = ()
-
- def __init__(self, **kw):
- super(RequestStub, self).__init__()
- self.update(kw)
- self.response = self
-
- def setCookie(self, name, value, **kw):
- self._cookies += (name, value, kw)
-
- def getPresentationType(self):
- return IUnitTestPresentation
-
-
-class TestContainerView(PlacelessSetup, unittest.TestCase):
-
- def setUp(self):
- from z3checkins.message import MessageContainerAdapter
- PlacelessSetup.setUp(self)
- ztapi.provideAdapter(None, IMessageArchive, MessageContainerAdapter)
-
- def test_checkins(self):
- from z3checkins.message import ContainerView
- view = ContainerView()
- view.context = {'x': 123, 'y': object(), 'z': MessageStub(date=1),
- 'a': MessageStub(date=2), 'c': MessageStub(date=3)}
- view.request = {}
- res = view.checkins()
- self.assertEquals(len(res), 3)
- self.assertEquals(view.count(), 3)
- self.assertEquals(res[0].date, 3)
- self.assertEquals(res[1].date, 2)
- self.assertEquals(res[2].date, 1)
-
- def test_checkins_limited(self):
- from z3checkins.message import ContainerView
- view = ContainerView()
- view.context = {'x': 123, 'y': object(), 'z': MessageStub(date=1),
- 'a': MessageStub(date=2), 'c': MessageStub(date=3)}
- view.request = {}
- res = view.checkins(size=2)
- self.assertEquals(len(res), 2)
- self.assertEquals(res[0].date, 3)
- self.assertEquals(res[1].date, 2)
-
- res = view.checkins(start=1, size=3)
- self.assertEquals(len(res), 2)
- self.assertEquals(res[0].date, 2)
- self.assertEquals(res[1].date, 1)
-
- def test_checkins_bookmarks(self):
- from z3checkins.message import ContainerView
- view = ContainerView()
- view.context = {'x': 123, 'y': object(), 'z': MessageStub(date=1),
- 'a': MessageStub(date=2), 'c': MessageStub(date=4)}
- view.request = {}
- view.bookmarks = lambda: [3]
- res = view.checkins()
- self.assertEquals(len(res), 4)
- self.assertEquals(res[0].date, 4)
- self.assert_(IBookmark.providedBy(res[1]))
- self.assertEquals(res[2].date, 2)
- self.assertEquals(res[3].date, 1)
-
- view.bookmarks = lambda: [2]
- res = view.checkins()
- self.assertEquals(len(res), 4)
- self.assertEquals(res[0].date, 4)
- self.assert_(IBookmark.providedBy(res[1]))
- self.assertEquals(res[2].date, 2)
- self.assertEquals(res[3].date, 1)
-
- view.bookmarks = lambda: [0, 1, 2, 3, 4, 5, 6, 2, 3, 1]
- res = view.checkins()
- self.assertEquals(len(res), 5)
- self.assertEquals(res[0].date, 4)
- self.assert_(IBookmark.providedBy(res[1]))
- self.assertEquals(res[2].date, 2)
- self.assert_(IBookmark.providedBy(res[3]))
- self.assertEquals(res[4].date, 1)
-
- res = view.checkins(start=1, size=1)
- self.assertEquals(len(res), 3)
- self.assert_(IBookmark.providedBy(res[0]))
- self.assertEquals(res[1].date, 2)
- self.assert_(IBookmark.providedBy(res[2]))
-
- def test_bookmarks(self):
- from z3checkins.message import ContainerView
- from z3checkins.message import FixedTimezone
- view = ContainerView()
- view.request = {}
- self.assertEquals(view.bookmarks(), [])
- view.request = {'bookmarks': '2003-01-04T21:33:04-05:00'}
- self.assertEquals(view.bookmarks(),
- [datetime(2003, 01, 04, 21, 33, 04,
- tzinfo=FixedTimezone(-5*60))])
- view.request = {'bookmarks': '2003-01-04T21:33:04-05:00 '
- 'errors are ignored '
- '2004-05-06T07:08:09+10:00 '
- '2002-02-02T02:02:02+02:00 '
- '2005-02-29T07:08:09+10:00'}
- self.assertEquals(view.bookmarks(),
- [datetime(2003, 1, 4, 21, 33, 4,
- tzinfo=FixedTimezone(-5*60)),
- datetime(2004, 5, 6, 7, 8, 9,
- tzinfo=FixedTimezone(10*60)),
- datetime(2002, 2, 2, 2, 2, 2,
- tzinfo=FixedTimezone(2*60))])
-
- def test_placeBookmark_empty_archive(self):
- from z3checkins.message import ContainerView
- from z3checkins.message import FixedTimezone
- view = ContainerView()
- view.context = {}
- view.request = RequestStub()
- view.placeBookmark()
- self.assertEquals(view.request._cookies, ())
-
- def test_placeBookmark_empty_bookmarks(self):
- from z3checkins.message import ContainerView
- from z3checkins.message import FixedTimezone
- view = ContainerView()
- view.context = {'x': 123, 'y': object(),
- 'z': MessageStub(date=datetime(2003, 1, 4, 21, 33, 4,
- tzinfo=FixedTimezone(-5*60)))}
- view.request = RequestStub()
- view.placeBookmark()
- self.assertEquals(view.request._cookies,
- ('bookmarks', '2003-01-04T21:33:04-05:00',
- {'max_age': 31536000}))
-
- def test_placeBookmark_not_at_start(self):
- from z3checkins.message import ContainerView
- from z3checkins.message import FixedTimezone
- view = ContainerView()
- view.context = {'x': 123, 'y': object(),
- 'z': MessageStub(date=datetime(2003, 1, 4, 21, 33, 4,
- tzinfo=FixedTimezone(-5*60)))}
- view.request = RequestStub(start=1)
- view.placeBookmark()
- self.assertEquals(view.request._cookies, ())
-
- def test_placeBookmark_no_new_checkins(self):
- from z3checkins.message import ContainerView
- from z3checkins.message import FixedTimezone
- view = ContainerView()
- view.context = {'x': 123, 'y': object(),
- 'z': MessageStub(date=datetime(2003, 1, 4, 21, 33, 4,
- tzinfo=FixedTimezone(-5*60)))}
- view.request = RequestStub(bookmarks='2003-01-04T21:33:04-05:00 '
- 'errors are ignored '
- '2002-02-02T02:02:02+02:00')
- view.placeBookmark()
- self.assertEquals(view.request._cookies, ())
-
- view = ContainerView()
- view.context = {'x': 123, 'y': object(),
- 'z': MessageStub(date=datetime(2003, 1, 4, 21, 33, 4,
- tzinfo=FixedTimezone(-5*60)))}
- view.request = RequestStub(bookmarks='2004-01-04T21:33:04-05:00 '
- 'errors are ignored '
- '2002-02-02T02:02:02+02:00')
- view.placeBookmark()
- self.assertEquals(view.request._cookies, ())
-
- def test_placeBookmark_new_checkins(self):
- from z3checkins.message import ContainerView
- from z3checkins.message import FixedTimezone
- view = ContainerView()
- view.context = {'x': 123, 'y': object(),
- 'z': MessageStub(date=datetime(2003, 1, 4, 21, 33, 4,
- tzinfo=FixedTimezone(-5*60))),
- 'w': MessageStub(date=datetime(2003, 1, 6, 22, 33, 44,
- tzinfo=FixedTimezone(+3*60)))}
- view.request = RequestStub(bookmarks='2003-01-04T21:33:04-05:00 '
- 'errors are ignored '
- '2002-02-02T02:02:02+02:00')
- view.placeBookmark()
- self.assertEquals(view.request._cookies,
- ('bookmarks', '2002-02-02T02:02:02+02:00 '
- '2003-01-04T21:33:04-05:00 '
- '2003-01-06T22:33:44+03:00',
- {'max_age': 31536000}))
-
- def test_placeBookmark_new_checkins_overflow(self):
- from z3checkins.message import ContainerView
- from z3checkins.message import FixedTimezone
- view = ContainerView()
- view.context = {'x': 123, 'y': object(),
- 'z': MessageStub(date=datetime(2003, 1, 4, 21, 33, 4,
- tzinfo=FixedTimezone(-5*60))),
- 'w': MessageStub(date=datetime(2003, 1, 6, 22, 33, 44,
- tzinfo=FixedTimezone(+3*60)))}
- view.request = RequestStub(bookmarks='2003-01-04T21:33:04-05:00 '
- 'errors are ignored '
- '2002-01-01T02:02:02+02:00 '
- '2002-01-02T02:02:02+02:00 '
- '2002-01-03T02:02:02+02:00 '
- '2002-01-04T02:02:02+02:00 '
- '2002-02-02T02:02:02+02:00 ')
- view.placeBookmark()
- self.assertEquals(view.request._cookies,
- ('bookmarks', '2002-01-03T02:02:02+02:00 '
- '2002-01-04T02:02:02+02:00 '
- '2002-02-02T02:02:02+02:00 '
- '2003-01-04T21:33:04-05:00 '
- '2003-01-06T22:33:44+03:00',
- {'max_age': 31536000}))
-
- def test_renderCheckins(self):
- from z3checkins.message import ContainerView
- view = ContainerView()
- view.context = {'x': 123, 'y': object(),
- 'z': MessageStub(date=1, log_message='xxx'),
- 'a': MessageStub(date=2, log_message='xxx'),
- 'c': MessageStub(date=3, log_message='yyy')}
- view.request = TestRequest()
- view.index = view
- ztapi.browserView(ICheckinMessage, 'html', MessageTestView)
-
- res = view.renderCheckins()
- self.assertEquals(res, 'msg3\nmsg2\nmsg1*\n')
- res = view.renderCheckins(start=1, size=1)
- self.assertEquals(res, 'msg2\n')
-
- def test_renderCheckins_with_bookmarks(self):
- from z3checkins.message import ContainerView
- view = ContainerView()
- view.context = {'x': 123, 'y': object(),
- 'z': MessageStub(date=1, log_message='xxx'),
- 'a': MessageStub(date=2, log_message='xxx'),
- 'c': MessageStub(date=3, log_message='yyy')}
- view.request = TestRequest()
- view.index = view
- view.bookmarks = lambda: [1]
- ztapi.browserView(ICheckinMessage, 'html', MessageTestView)
- ztapi.browserView(IBookmark, 'html', BookmarkTestView)
-
- res = view.renderCheckins()
- self.assertEquals(res, 'msg3\nmsg2\n-\nmsg1*\n')
-
-
-def diff(a, b):
- "Compare the differences of two sequences of strings"
-
- if isinstance(a, (str, unicode)): a = a.splitlines()
- if isinstance(b, (str, unicode)): b = b.splitlines()
-
- diff = []
- def dump(tag, x, lo, hi, diff=diff):
- for i in xrange(lo, hi):
- diff.append(tag + x[i])
-
- differ = SequenceMatcher(a=a, b=b)
- for tag, alo, ahi, blo, bhi in differ.get_opcodes():
- if tag == 'replace':
- dump('-', a, alo, ahi)
- dump('+', b, blo, bhi)
- elif tag == 'delete':
- dump('-', a, alo, ahi)
- elif tag == 'insert':
- dump('+', b, blo, bhi)
- elif tag == 'equal':
- dump(' ', a, alo, ahi)
- else:
- raise ValueError, 'unknown tag ' + `tag`
- return "\n".join(diff)
-
-class TestCheckinMessageView(PlacelessSetup, unittest.TestCase):
-
- def setUp(self):
- PlacelessSetup.setUp(self)
- from z3checkins.message import MessageContainerAdapter
- ztapi.provideAdapter(None, IMessageArchive, MessageContainerAdapter)
-
- def test_body_strange(self):
- from z3checkins.message import CheckinMessageView
- view = CheckinMessageView()
- view.context = MessageStub(body="Something & strange")
- self.assertEquals(view.body(), "<pre>Something & strange</pre>")
-
- def test_body(self):
- from z3checkins.message import CheckinMessageView
- view = CheckinMessageView()
- view.context = MessageStub(body="Blah blah\n"
- "blah\n"
- "Log message:\n"
- "Blurb blurb\n"
- "blurb.\n"
- "\n"
- "=== foo.py: 1.2 -> 1.3 ===\n"
- "--- foo.py:1.2\tdatetime\n"
- "+++ foo.py\tdatetime\n"
- "@@@ -123,4 +567,8 @@@\n"
- " fwoosh <>&\"\n"
- "-fouoww\n"
- "+fruuuh\n"
- " fargle\n"
- "_______________________________________________\n"
- "signature\n")
- result = view.body()
- expected = ('<pre>Blah blah\n'
- 'blah\n'
- 'Log message:\n'
- '</pre>'
- '<div class="log">'
- '<p>Blurb blurb</p>\n'
- '<p>blurb.</p>'
- '</div>'
- '<pre>\n'
- '<div class="file">=== foo.py: 1.2 -> 1.3 ===\n</div>'
- '<div class="oldfile">--- foo.py:1.2<span class="tab">>--</span>datetime\n</div>'
- '<div class="newfile">+++ foo.py<span class="tab">>------</span>datetime\n</div>'
- '<div class="chunk">@@@ -123,4 +567,8 @@@\n</div>'
- ' fwoosh <>&"\n'
- '<div class="old">-fouoww\n</div>'
- '<div class="new">+fruuuh\n</div>'
- ' fargle'
- '<div class="signature">\n'
- '_______________________________________________\n'
- 'signature\n'
- '</div>'
- '</pre>')
- self.assertEquals(result, expected, diff(expected, result))
-
- def test_body_svn(self):
- from z3checkins.message import CheckinMessageView
- view = CheckinMessageView()
- view.context = MessageStub(body="""\
-Author: mg
-Date: 2003-09-09 21:21:09 +0300 (Tue, 09 Sep 2003)
-New Revision: 15
-
-Added:
- trunk/schooltool/schooltool/ftests/
- trunk/schooltool/schooltool/ftests/__init__.py
- trunk/schooltool/schooltool/ftests/test_rest.py
- trunk/schooltool/schooltool/main.py
- trunk/schooltool/schooltool/tests/test_main.py
-Removed:
- trunk/schooltool/schooltool/tests/test_rest.py
-Modified:
- trunk/schooltool/schooltool/tests/__init__.py
-Log:
-First prototype of SchoolTool HTTP server that serves RESTful pages, complete
-with unit and functional tests.
-
-
-
-Added: trunk/schooltool/schooltool/ftests/__init__.py
-===================================================================
---- trunk/schooltool/schooltool/ftests/__init__.py 2003-09-09 18:03:43 UTC (rev 14)
-+++ trunk/schooltool/schooltool/ftests/__init__.py 2003-09-09 18:21:09 UTC (rev 15)
-@@ -0,0 +1,21 @@
-+#
-+# SchoolTool - common information systems platform for school administration
-"""
-)
- result = view.body()
- expected = ("""\
-<pre>Author: mg
-Date: 2003-09-09 21:21:09 +0300 (Tue, 09 Sep 2003)
-New Revision: 15
-
-Added:
- trunk/schooltool/schooltool/ftests/
- trunk/schooltool/schooltool/ftests/__init__.py
- trunk/schooltool/schooltool/ftests/test_rest.py
- trunk/schooltool/schooltool/main.py
- trunk/schooltool/schooltool/tests/test_main.py
-Removed:
- trunk/schooltool/schooltool/tests/test_rest.py
-Modified:
- trunk/schooltool/schooltool/tests/__init__.py
-Log:
-</pre><div class="log"><p>First prototype of SchoolTool HTTP server that serves RESTful pages, complete</p>
-<p>with unit and functional tests.</p></div><pre>
-Added: trunk/schooltool/schooltool/ftests/__init__.py
-<div class="file">===================================================================
-</div><div class="oldfile">--- trunk/schooltool/schooltool/ftests/__init__.py<span class="tab">>------</span>2003-09-09 18:03:43 UTC (rev 14)
-</div><div class="newfile">+++ trunk/schooltool/schooltool/ftests/__init__.py<span class="tab">>------</span>2003-09-09 18:21:09 UTC (rev 15)
-</div><div class="chunk">@@ -0,0 +1,21 @@
-</div><div class="new">+#
-</div><div class="new">+# SchoolTool - common information systems platform for school administration</div></pre>"""
- )
- self.assertEquals(result, expected, diff(expected, result))
-
- def test_body_crlf(self):
- from z3checkins.message import CheckinMessageView
- view = CheckinMessageView()
- view.context = MessageStub(body="Blah blah\r\n"
- "blah\r\n"
- "Log message:\r\n"
- "Blurb blurb\r\n"
- "blurb.\r\n"
- "\r\n"
- "=== foo.py: 1.2 -> 1.3 ===\r\n"
- "--- foo.py:1.2\tdatetime\r\n"
- "+++ foo.py\tdatetime\r\n"
- "@@@ -123,4 +567,8 @@@\r\n"
- " fwoosh <>&\"\r\n"
- "-fouoww \r\n"
- "+fruuuh\r\n"
- " fargle\r\n"
- " \r\n"
- " \r\n"
- "_______________________________________________\r\n"
- "signature\r\n")
- result = view.body()
- expected = ('<pre>Blah blah\n'
- 'blah\n'
- 'Log message:\n'
- '</pre>'
- '<div class="log">'
- '<p>Blurb blurb</p>\n'
- '<p>blurb.</p>'
- '</div>'
- '<pre>\n'
- '<div class="file">=== foo.py: 1.2 -> 1.3 ===\n</div>'
- '<div class="oldfile">--- foo.py:1.2<span class="tab">>--</span>datetime\n</div>'
- '<div class="newfile">+++ foo.py<span class="tab">>------</span>datetime\n</div>'
- '<div class="chunk">@@@ -123,4 +567,8 @@@\n</div>'
- ' fwoosh <>&"\n'
- '<div class="old">-fouoww<span class="trail">..</span>\n</div>'
- '<div class="new">+fruuuh\n</div>'
- ' fargle\n'
- ' <span class="trail">..</span>\n'
- ' '
- '<div class="signature">\n'
- '_______________________________________________\n'
- 'signature\n'
- '</div>'
- '</pre>')
- self.assertEquals(result, expected, diff(expected, result))
-
- def test_body_nosig(self):
- from z3checkins.message import CheckinMessageView
- view = CheckinMessageView()
- view.context = MessageStub(body="Blah blah\n"
- "blah\n"
- "Log message:\n"
- "Blurb blurb\n"
- "blurb.\n"
- "\n"
- "=== foo.py: 1.2 -> 1.3 ===\n"
- "--- foo.py:1.2\tdatetime\n"
- "+++ foo.py\tdatetime\n"
- "@@@ -123,4 +567,8 @@@\n"
- " fwoosh <>&\"\n"
- "-fouoww\n"
- "+fruuuh\n"
- " fargle")
- result = view.body()
- expected = ('<pre>Blah blah\n'
- 'blah\n'
- 'Log message:\n'
- '</pre>'
- '<div class="log">'
- '<p>Blurb blurb</p>\n'
- '<p>blurb.</p>'
- '</div>'
- '<pre>\n'
- '<div class="file">=== foo.py: 1.2 -> 1.3 ===\n</div>'
- '<div class="oldfile">--- foo.py:1.2<span class="tab">>--</span>datetime\n</div>'
- '<div class="newfile">+++ foo.py<span class="tab">>------</span>datetime\n</div>'
- '<div class="chunk">@@@ -123,4 +567,8 @@@\n</div>'
- ' fwoosh <>&"\n'
- '<div class="old">-fouoww\n</div>'
- '<div class="new">+fruuuh\n</div>'
- ' fargle'
- '</pre>')
- self.assertEquals(result, expected, diff(expected, result))
-
- def test_body_importmsg(self):
- from z3checkins.message import CheckinMessageView
- view = CheckinMessageView()
- view.context = MessageStub(body="Blah blah\n"
- "blah\n"
- "Log message:\n"
- "Blurb blurb\n"
- "blurb.\n"
- "\n"
- "Status:\n"
- "\n"
- "Vendor Tag:\tnovendor\n"
- "Release Tags:\tstart\n"
- "\n"
- "N foo/bar.py\n"
- "N foo/baz.pt\n"
- "\n"
- "No conflicts created by this import\n")
- result = view.body()
- expected = ('<pre>Blah blah\n'
- 'blah\n'
- 'Log message:\n'
- '</pre>'
- '<div class="log">'
- '<p>Blurb blurb</p>\n'
- '<p>blurb.</p>'
- '</div>'
- '<pre>\n'
- 'Status:\n'
- '\n'
- 'Vendor Tag:\tnovendor\n'
- 'Release Tags:\tstart\n'
- '\n'
- 'N foo/bar.py\n'
- 'N foo/baz.pt\n'
- '\n'
- 'No conflicts created by this import\n'
- '</pre>')
- self.assertEquals(result, expected, diff(expected, result))
-
- def test_markwitespace(self):
- from z3checkins.message import CheckinMessageView
- view = CheckinMessageView()
- m = view.mark_whitespace
- self.assertEquals(m(''), '')
- self.assertEquals(m('xyzzy'), 'xyzzy')
- self.assertEquals(m(' '), ' <span class="trail">.</span>')
- self.assertEquals(m(' xy z '), ' xy z<span class="trail">..</span>')
- self.assertEquals(m(' xy z \t '), ' xy z<span class="trail">.<span class="tab">>-</span>.</span>')
- self.assertEquals(m(' \t|'), ' <span class="tab">>-------</span>|')
- self.assertEquals(m(' |\t|'), ' |<span class="tab">>------</span>|')
- self.assertEquals(m(' xxxxxx|\t|'), ' xxxxxx|<span class="tab">></span>|')
- self.assertEquals(m(' x<tag\t>xxxxx|\t|'), ' x<tag\t>xxxxx|<span class="tab">></span>|')
- self.assertEquals(m(' x&ent;xxxx|\t|'), ' x&ent;xxxx|<span class="tab">></span>|')
-
- def test_urls(self):
- from z3checkins.message import CheckinMessageView
- view = CheckinMessageView()
- prefixes = ['', 'A link: ', '(', '<']
- suffixes = ['', ' etc.', ')', '>', '.', ',', '\n']
- urls = ['http://www.example.com', 'https://www.example.com', 'http://localhost:8080/foo?q=a&w=b']
-
- def quote(text):
- return (text.replace('&', '&')
- .replace('<', '<')
- .replace('>', '>')
- .replace('"', '"'))
-
- for prefix in prefixes:
- for link in urls:
- for suffix in suffixes:
- view.context = MessageStub(body="%s%s%s" % (prefix, link, suffix))
- prefix = quote(prefix)
- link = quote(link)
- suffix = quote(suffix)
- self.assertEquals(view.body(), '<pre>%s<a href="%s">%s</a>%s</pre>' % (prefix, link, link, suffix))
-
- def test_navigation_no_archive(self):
- from z3checkins.message import CheckinMessageView
- view = CheckinMessageView()
- view.context = MessageStub()
- self.assertEquals(view.first(), None)
- self.assertEquals(view.last(), None)
- self.assertEquals(view.next(), None)
- self.assertEquals(view.previous(), None)
-
- def test_navigation_empty_archive(self):
- from z3checkins.message import CheckinMessageView
- view = CheckinMessageView()
- view.context = MessageStub()
- view.context.__parent__ = {'1': 2}
- self.assertEquals(view.first(), None)
- self.assertEquals(view.last(), None)
- self.assertEquals(view.next(), None)
- self.assertEquals(view.previous(), None)
-
- def test_navigation(self):
- from z3checkins.message import CheckinMessageView
- m1 = MessageStub(date=1, message_id='1')
- m2 = MessageStub(date=2, message_id='2')
- m3 = MessageStub(date=3, message_id='3')
- m4 = MessageStub(date=4, message_id='4')
-
- folder = {'1': 2, '3': 'abc', '4': m1, '5': m2, '6': m3, '7': m4}
- view = CheckinMessageView()
- view.context = m3
- m3.__parent__ = folder
- self.assertEquals(view.first(), m1)
- self.assertEquals(view.last(), m4)
- self.assertEquals(view.next(), m4)
- self.assertEquals(view.previous(), m2)
-
- view = CheckinMessageView()
- view.context = m1
- m1.__parent__ = folder
- self.assertEquals(view.first(), m1)
- self.assertEquals(view.last(), m4)
- self.assertEquals(view.next(), m2)
- self.assertEquals(view.previous(), None)
-
- view = CheckinMessageView()
- view.context = m4
- m4.__parent__ = folder
- self.assertEquals(view.first(), m1)
- self.assertEquals(view.last(), m4)
- self.assertEquals(view.next(), None)
- self.assertEquals(view.previous(), m3)
-
-
class TestMessageNameChooser(unittest.TestCase):
def test_chooseName(self):
@@ -1119,15 +300,8 @@
def test_suite():
suite = unittest.TestSuite()
- suite.addTest(unittest.makeSuite(TestFixedTimezone))
- suite.addTest(unittest.makeSuite(TestRFCDateTimeFormatter))
- suite.addTest(unittest.makeSuite(TestISODateTimeFormatter))
suite.addTest(unittest.makeSuite(TestCheckinMessage))
suite.addTest(unittest.makeSuite(TestCheckinMessageParser))
- suite.addTest(unittest.makeSuite(TestMessageContainerAdapter))
- suite.addTest(unittest.makeSuite(TestMessageUpload))
- suite.addTest(unittest.makeSuite(TestContainerView))
- suite.addTest(unittest.makeSuite(TestCheckinMessageView))
suite.addTest(unittest.makeSuite(TestMessageNameChooser))
suite.addTest(unittest.makeSuite(TestMessageSized))
return suite
Copied: Zope3/branches/srichter-blow-services/src/z3checkins/tests/test_timeutils.py (from rev 28844, Zope3/trunk/src/z3checkins/tests/test_timeutils.py)
Property changes on: Zope3/branches/srichter-blow-services/src/z3checkins/tests/test_timeutils.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Copied: Zope3/branches/srichter-blow-services/src/z3checkins/timeutils.py (from rev 28844, Zope3/trunk/src/z3checkins/timeutils.py)
Property changes on: Zope3/branches/srichter-blow-services/src/z3checkins/timeutils.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Modified: Zope3/branches/srichter-blow-services/src/zope/app/apidoc/classmodule/menu.pt
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/apidoc/classmodule/menu.pt 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/apidoc/classmodule/menu.pt 2005-01-17 15:30:10 UTC (rev 28853)
@@ -17,7 +17,7 @@
<div>
<span i18n:translate="">Class Finder:</span> <br/>
<i i18n:translate="">(Enter partial Python path)</i></div>
- <form action="menu.html" method="POST">
+ <form action="menu.html" method="post">
<input type="text" name="path"
style="font-size: 80%; width=95%" />
<input type="submit" name="SUBMIT" value="Find"
Modified: Zope3/branches/srichter-blow-services/src/zope/app/applicationcontrol/browser/zodbcontrol.pt
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/applicationcontrol/browser/zodbcontrol.pt 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/applicationcontrol/browser/zodbcontrol.pt 2005-01-17 15:30:10 UTC (rev 28853)
@@ -19,7 +19,7 @@
tal:condition="status"
tal:content="status" />
- <form action="." method="POST" tal:attributes="action request/URL">
+ <form action="." method="post" tal:attributes="action request/URL">
<div class="row">
<div class="label" i18n:translate="">Keep up to:</div>
<div class="view">
Modified: Zope3/branches/srichter-blow-services/src/zope/app/catalog/browser/advanced.pt
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/catalog/browser/advanced.pt 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/catalog/browser/advanced.pt 2005-01-17 15:30:10 UTC (rev 28853)
@@ -17,7 +17,7 @@
</tr>
</table>
-<form method="POST" action="reindex.html">
+<form method="post" action="reindex.html">
<input type="submit" value="Reindex"
i18n:attributes="value reindex-button"/>
</form>
Modified: Zope3/branches/srichter-blow-services/src/zope/app/component/__init__.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/component/__init__.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/component/__init__.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -19,6 +19,14 @@
import zope.component
from zope.app import zapi
+##############################################################################
+# BBB: Goes away in 3.3
+import bbb
+bbb.install()
+del bbb
+##############################################################################
+
+
_marker = object()
def getNextSiteManager(context):
Added: Zope3/branches/srichter-blow-services/src/zope/app/component/bbb/__init__.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/component/bbb/__init__.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/component/bbb/__init__.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -0,0 +1,8 @@
+
+
+def install():
+
+ import sys
+ import localservice
+
+ sys.modules['zope.app.component.localservice'] = localservice
Added: Zope3/branches/srichter-blow-services/src/zope/app/component/bbb/localservice.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/component/bbb/localservice.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/component/bbb/localservice.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -0,0 +1,16 @@
+# BBB: Goes away in 3.3
+from zope.component.exceptions import ComponentLookupError
+
+
+def queryNextService(context, name, default=None):
+ try:
+ return getNextService(context, name)
+ except ComponentLookupError:
+ return default
+
+def getNextService(context, name):
+ """Returns the service with the given name from the next service manager.
+ """
+ from zope.component.bbb.service import IService
+ from zope.app.component import getNextSiteManager
+ return getNextSiteManager(context).queryUtility(IService, name)
Modified: Zope3/branches/srichter-blow-services/src/zope/app/component/browser/configure.zcml
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/component/browser/configure.zcml 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/component/browser/configure.zcml 2005-01-17 15:30:10 UTC (rev 28853)
@@ -4,8 +4,8 @@
<!-- Registration Managemenet -->
- <!-- BBB: Backward-compatibility: 12/07/2004 -->
- <!-- XXX: temp. deactivated
+ <!-- BBB: Gone with 3.3 -->
+ <!-- XXX
<zope:view
for="zope.app.component.interfaces.registration.IComponentPath"
type="zope.publisher.interfaces.browser.IBrowserRequest"
@@ -21,7 +21,7 @@
factory=".registration.ComponentPathWidget"
permission="zope.Public"
/>
- -->
+ -->
<!-- BBB: End of backward-compatibility block -->
<zope:view
Modified: Zope3/branches/srichter-blow-services/src/zope/app/configure.zcml
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/configure.zcml 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/configure.zcml 2005-01-17 15:30:10 UTC (rev 28853)
@@ -48,7 +48,7 @@
<include package=".zopeappgenerations" />
<!-- Services -->
- <!-- XXX: include package="zope.app.pluggableauth" /-->
+ <include package="zope.app.pluggableauth" />
<include package="zope.app.principalannotation" />
<!-- Utilities -->
Modified: Zope3/branches/srichter-blow-services/src/zope/app/container/browser/contents.pt
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/container/browser/contents.pt 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/container/browser/contents.pt 2005-01-17 15:30:10 UTC (rev 28853)
@@ -175,7 +175,6 @@
</form>
<script type="text/javascript"><!--
- prettydump('focus', LG_INFO);
if (document.containerContentsForm.new_value)
document.containerContentsForm.new_value.focus();
//-->
Modified: Zope3/branches/srichter-blow-services/src/zope/app/container/browser/metaconfigure.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/container/browser/metaconfigure.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/container/browser/metaconfigure.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -15,6 +15,7 @@
$Id$
"""
+
__docformat__ = 'restructuredtext'
from zope.interface import Interface
@@ -25,35 +26,33 @@
from zope.app.i18n import ZopeMessageIDFactory as _
from zope.app.security.fields import Permission
+
class IContainerViews(Interface):
"""Define several container views for an `IContainer` implementation."""
for_ = GlobalInterface(
title=u"The interface this containerViews are for.",
description=u"""
- The containerViews will be for all objects that implement this
- interface.""",
- required=True
- )
-
+ The containerViews will be available for all objects that
+ implement this interface.
+ """,
+ required=True)
+
contents = Permission(
title=u"The permission needed for content page.",
- required=False,
- )
+ required=False)
index = Permission(
title=u"The permission needed for index page.",
- required=False,
- )
+ required=False)
add = Permission(
title=u"The permission needed for add page.",
- required=False,
- )
+ required=False)
def containerViews(_context, for_, contents=None, add=None, index=None):
- """Create an container view for a given content type"""
+ """Set up container views for a given content type."""
if for_ is None:
raise ValueError("A for interface must be specified.")
@@ -63,9 +62,9 @@
page(_context, name='contents.html', permission=contents,
for_=for_, class_=Contents, attribute='contents',
menu=zmi_views, title=_('Contents'))
-
+
if index is not None:
- page(_context, name='index.html', permission=index, for_=for_,
+ page(_context, name='index.html', permission=index, for_=for_,
class_=Contents, attribute='index')
if add is not None:
Modified: Zope3/branches/srichter-blow-services/src/zope/app/dav/configure.zcml
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/dav/configure.zcml 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/dav/configure.zcml 2005-01-17 15:30:10 UTC (rev 28853)
@@ -108,7 +108,7 @@
permission="zope.Public"
factory=".adapter.DAVSchemaAdapter" />
- <!-- XXX: This interface needs to be split up so we can apply seperate
+ <!-- TODO: This interface needs to be split up so we can apply seperate
permissions for reading and writing -->
<adapter
factory=".opaquenamespaces.DAVOpaqueNamespacesAdapter"
Modified: Zope3/branches/srichter-blow-services/src/zope/app/dublincore/browser/edit.pt
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/dublincore/browser/edit.pt 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/dublincore/browser/edit.pt 2005-01-17 15:30:10 UTC (rev 28853)
@@ -5,7 +5,7 @@
<form action="request/URL"
tal:attributes="action request/URL"
tal:define="data view/edit"
- method="POST"
+ method="post"
>
<p tal:condition="data/message"
@@ -29,15 +29,6 @@
</div>
<div class="row">
- <div class="controls">
- <input type="submit" value="Refresh"
- i18n:attributes="value refresh-button" />
- <input type="submit" name="save" value="Save"
- i18n:attributes="value save-changes-button"/>
- </div>
- </div>
-
- <div class="row">
<div class="label" i18n:translate="">Created</div>
<div class="field" tal:content="data/created">2000-01-01 01:01:01</div>
</div>
@@ -53,6 +44,15 @@
</div>
</div>
+ <div class="row">
+ <div class="controls">
+ <input type="submit" value="Refresh"
+ i18n:attributes="value refresh-button" />
+ <input type="submit" name="save" value="Save"
+ i18n:attributes="value save-changes-button"/>
+ </div>
+ </div>
+
</form>
</div>
Modified: Zope3/branches/srichter-blow-services/src/zope/app/event/dispatching.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/event/dispatching.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/event/dispatching.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -13,7 +13,7 @@
##############################################################################
"""Implement zope-specific event dispatching, based on subscription adapters
-This package instals an event dispatcher that calls event handlers,
+This package installs an event dispatcher that calls event handlers,
registered as subscription adapters providing ``None``.
So, to subscribe to an event, use a subscription adapter to ``None``:
Modified: Zope3/branches/srichter-blow-services/src/zope/app/exception/browser/tests/test_unauthorized.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/exception/browser/tests/test_unauthorized.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/exception/browser/tests/test_unauthorized.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -19,7 +19,7 @@
from zope.interface import implements
from zope.publisher.browser import TestRequest
from zope.app.testing import ztapi
-from zope.app.security.interfaces import IAuthenticationUtility, IPrincipal
+from zope.app.security.interfaces import IAuthentication, IPrincipal
from zope.app.exception.browser.unauthorized import Unauthorized
from zope.app.testing.placelesssetup import PlacelessSetup
@@ -41,7 +41,7 @@
return self.id
class DummyAuthUtility(object):
- implements(IAuthenticationUtility) # this is a lie
+ implements(IAuthentication) # this is a lie
def unauthorized(self, principal_id, request):
self.principal_id = principal_id
@@ -56,7 +56,7 @@
def setUp(self):
super(Test, self).setUp()
self.auth = DummyAuthUtility()
- ztapi.provideUtility(IAuthenticationUtility, self.auth)
+ ztapi.provideUtility(IAuthentication, self.auth)
def tearDown(self):
super(Test, self).tearDown()
Modified: Zope3/branches/srichter-blow-services/src/zope/app/file/browser/file_add.pt
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/file/browser/file_add.pt 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/file/browser/file_add.pt 2005-01-17 15:30:10 UTC (rev 28853)
@@ -3,7 +3,7 @@
<div metal:fill-slot="body">
<form action="." tal:attributes="action request/URL"
- method="POST" enctype="multipart/form-data">
+ method="post" enctype="multipart/form-data">
<h3>Add a File</h3>
Modified: Zope3/branches/srichter-blow-services/src/zope/app/file/browser/file_upload.pt
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/file/browser/file_upload.pt 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/file/browser/file_upload.pt 2005-01-17 15:30:10 UTC (rev 28853)
@@ -3,7 +3,7 @@
<div metal:fill-slot="body">
<form action="." tal:attributes="action request/URL"
- method="POST" enctype="multipart/form-data">
+ method="post" enctype="multipart/form-data">
<h3>Upload a file</h3>
Modified: Zope3/branches/srichter-blow-services/src/zope/app/form/browser/edit.pt
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/form/browser/edit.pt 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/form/browser/edit.pt 2005-01-17 15:30:10 UTC (rev 28853)
@@ -5,7 +5,7 @@
<div metal:define-macro="body">
- <form action="." tal:attributes="action request/URL" method="POST"
+ <form action="." tal:attributes="action request/URL" method="post"
enctype="multipart/form-data">
<div metal:define-macro="formbody">
Modified: Zope3/branches/srichter-blow-services/src/zope/app/form/browser/editwizard.pt
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/form/browser/editwizard.pt 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/form/browser/editwizard.pt 2005-01-17 15:30:10 UTC (rev 28853)
@@ -4,7 +4,7 @@
<div metal:define-macro="body">
- <form action="." tal:attributes="action request/URL" method="POST"
+ <form action="." tal:attributes="action request/URL" method="post"
enctype="multipart/form-data" >
<div metal:define-macro="formbody">
Modified: Zope3/branches/srichter-blow-services/src/zope/app/homefolder/homefolder.pt
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/homefolder/homefolder.pt 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/homefolder/homefolder.pt 2005-01-17 15:30:10 UTC (rev 28853)
@@ -7,7 +7,7 @@
tal:condition="status"
tal:content="status" />
- <form action="" method="POST">
+ <form action="" method="post">
<h3 i18n:translate="">Assign a Principal</h3>
<div class="row" tal:define="widget nocall:view/principal_widget">
@@ -41,4 +41,4 @@
</div>
</body>
-</html>
\ No newline at end of file
+</html>
Modified: Zope3/branches/srichter-blow-services/src/zope/app/introspector/introspector.pt
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/introspector/introspector.pt 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/introspector/introspector.pt 2005-01-17 15:30:10 UTC (rev 28853)
@@ -184,7 +184,7 @@
</div>
<form tal:attributes="
action string:${request/URL/-1}/@@objectMarker.html"
- method="POST">
+ method="post">
<input type="submit" value="Modify"
i18n:attributes="value modify-button"/>
</form>
Modified: Zope3/branches/srichter-blow-services/src/zope/app/introspector/marker.pt
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/introspector/marker.pt 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/introspector/marker.pt 2005-01-17 15:30:10 UTC (rev 28853)
@@ -19,7 +19,7 @@
tal:define ="global introspector view/getIntrospector;
global status view/update">
- <form action="" tal:attributes="action request/URL" method="POST">
+ <form action="" tal:attributes="action request/URL" method="post">
<table>
<tr>
Modified: Zope3/branches/srichter-blow-services/src/zope/app/meta.zcml
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/meta.zcml 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/meta.zcml 2005-01-17 15:30:10 UTC (rev 28853)
@@ -13,4 +13,8 @@
<include package="zope.app.schema" file="meta.zcml" />
<include package="zope.app.container.browser" file="meta.zcml" />
+<!-- BBB: Goes away in 3.3 -->
+<include package="zope.app.site" file="meta.zcml" />
+
+
</configure>
Modified: Zope3/branches/srichter-blow-services/src/zope/app/observable/README.txt
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/observable/README.txt 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/observable/README.txt 2005-01-17 15:30:10 UTC (rev 28853)
@@ -1,10 +1,9 @@
-Instance Based Event Subscription Support
+Instance-based Event Subscription Support
=========================================
This package implements support for subscribing to events for a
-particular instance of an object. This package
-implements the proposal found at
-http://dev.zope.org/Zope3/InstanceAndTypeBasedSubscriptions .
+particular instance of an object. This package implements the proposal
+found at http://dev.zope.org/Zope3/InstanceAndTypeBasedSubscriptions .
The package provides an event channel for dispatching events to the
appropriate instance as well as an adapter from `IAnnotatable` to
Modified: Zope3/branches/srichter-blow-services/src/zope/app/pau/README.txt
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/pau/README.txt 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/pau/README.txt 2005-01-17 15:30:10 UTC (rev 28853)
@@ -98,7 +98,7 @@
... notify(interfaces.FoundPrincipalCreated(principal, info))
... return principal
- >>> provideUtility(interfaces.IPrincipalFactoryPlugin, PrincipalFactory(),
+ >>> provideUtility(interfaces.IPrincipalFactoryPlugin, PrincipalFactory(),
... name='pf')
Finally, we create a PAU instance:
@@ -207,7 +207,7 @@
>>> provideUtility(interfaces.IExtractionPlugin, OddExtractor(), name='eodd')
>>> auth.extractors = 'eodd', 'emy'
-
+
>>> request = TestRequest(credentials=41)
>>> auth.authenticate(request)
Principal('1', "{'int': 1}")
@@ -245,15 +245,15 @@
... principal, info))
... return principal
- >>> provideUtility(interfaces.IPrincipalFactoryPlugin, OddFactory(),
+ >>> provideUtility(interfaces.IPrincipalFactoryPlugin, OddFactory(),
... name='oddf')
>>> auth.factories = 'oddf', 'pf'
-
+
>>> request = TestRequest(credentials=41)
>>> auth.authenticate(request)
OddPrincipal('1', "{'int': 1}")
-
+
>>> request = TestRequest(credentials=42)
>>> auth.authenticate(request)
Principal('42', "{'domain': 42}")
@@ -269,7 +269,7 @@
Get a principal given an id
===========================
-We can ask the PAU for a principal, given an id.
+We can ask the PAU for a principal, given an id.
To do this, the PAU uses principal search plugins:
@@ -281,7 +281,7 @@
... if principal_id == '42':
... return {'domain': 42}
- >>> provideUtility(interfaces.IPrincipalSearchPlugin, Search42(),
+ >>> provideUtility(interfaces.IPrincipalSearchPlugin, Search42(),
... name='s42')
>>> class IntSearch:
@@ -296,9 +296,9 @@
... if (i >= 0 and i < 100):
... return {'int': i}
- >>> provideUtility(interfaces.IPrincipalSearchPlugin, IntSearch(),
+ >>> provideUtility(interfaces.IPrincipalSearchPlugin, IntSearch(),
... name='sint')
-
+
>>> auth.searchers = 's42', 'sint'
>>> auth.getPrincipal('41')
@@ -322,11 +322,11 @@
a fake utility.
>>> from zope.app.component.testing import testingNextUtility
- >>> from zope.app.security.interfaces import IAuthenticationUtility
+ >>> from zope.app.security.interfaces import IAuthentication
>>> class FakeAuthUtility:
...
- ... zope.interface.implements(IAuthenticationUtility)
+ ... zope.interface.implements(IAuthentication)
...
... lastGetPrincipalCall = lastUnauthorizedCall = None
...
@@ -337,11 +337,11 @@
... self.lastUnauthorizedCall = id
>>> nextauth = FakeAuthUtility()
- >>> testingNextUtility(auth, nextauth, IAuthenticationUtility)
+ >>> testingNextUtility(auth, nextauth, IAuthentication)
>>> auth.getPrincipal('123')
- >>> '123' == nextauth.lastGetPrincipalCall
- True
+ >>> nextauth.lastGetPrincipalCall
+ '123'
Issuing a challenge
===================
@@ -364,9 +364,9 @@
create a plugin that sets a response header:
>>> class Challenge:
- ...
+ ...
... zope.interface.implements(interfaces.IChallengePlugin)
- ...
+ ...
... def challenge(self, requests, response):
... response.setHeader('X-Unauthorized', 'True')
... return True
@@ -387,8 +387,8 @@
-----------------------------
To understand how the challenge plugins work, it's helpful to
-understand how the unauthorized method of authenticaton services
-get called.
+understand how the unauthorized method of authentication utilities
+gets called.
If an 'Unauthorized' exception is raised and not caught by application
code, then the following things happen:
@@ -420,12 +420,12 @@
>>> class ColorChallenge:
... zope.interface.implements(interfaces.IChallengePlugin)
- ...
+ ...
... protocol = 'bridge'
- ...
+ ...
... def challenge(self, requests, response):
... challenge = response.getHeader('X-Challenge', '')
- ... response.setHeader('X-Challenge',
+ ... response.setHeader('X-Challenge',
... challenge + 'favorite color? ')
... return True
@@ -434,12 +434,12 @@
>>> class BirdChallenge:
... zope.interface.implements(interfaces.IChallengePlugin)
- ...
+ ...
... protocol = 'bridge'
- ...
+ ...
... def challenge(self, requests, response):
... challenge = response.getHeader('X-Challenge', '')
- ... response.setHeader('X-Challenge',
+ ... response.setHeader('X-Challenge',
... challenge + 'swallow air speed? ')
... return True
@@ -523,10 +523,10 @@
Different search plugins are likely to use very different search
criteria. There are two approaches a plugin can use to support
-searching:
+searching:
- A plugin can provide IQuerySchemaSearch, in addition to
- `IPrincipalSearchPlugin`. In this case, the plugin provises a search
+ `IPrincipalSearchPlugin`. In this case, the plugin provides a search
method and a schema that describes the input to be provided to the
search method.
@@ -539,7 +539,7 @@
>>> [id for (id, queriable) in auth.getQueriables()]
['s42', 'sint']
- >>> [queriable.__class__.__name__
+ >>> [queriable.__class__.__name__
... for (id, queriable) in auth.getQueriables()]
['Search42', 'IntSearch']
@@ -550,4 +550,4 @@
search or extraction and challenge. See
`ISearchableAuthenticationPlugin` and
`IExtractionAndChallengePlugin`.
-
+
Modified: Zope3/branches/srichter-blow-services/src/zope/app/pau/browser/groupfolder.txt
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/pau/browser/groupfolder.txt 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/pau/browser/groupfolder.txt 2005-01-17 15:30:10 UTC (rev 28853)
@@ -443,7 +443,7 @@
... ------------01mLTAiXW04sKSCkjnjf13EYQIvFsu2svmDMwutSuvLCNXx7JekIhxq
... Content-Disposition: form-data; name="field.provided"
...
- ... zope.app.security.interfaces.IAuthenticationUtility
+ ... zope.app.security.interfaces.IAuthentication
... ------------01mLTAiXW04sKSCkjnjf13EYQIvFsu2svmDMwutSuvLCNXx7JekIhxq
... Content-Disposition: form-data; name="field.provided-empty-marker"
...
Modified: Zope3/branches/srichter-blow-services/src/zope/app/pau/browser/principalfolder.txt
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/pau/browser/principalfolder.txt 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/pau/browser/principalfolder.txt 2005-01-17 15:30:10 UTC (rev 28853)
@@ -171,7 +171,7 @@
... ------------01mLTAiXW04sKSCkjnjf13EYQIvFsu2svmDMwutSuvLCNXx7JekIhxq
... Content-Disposition: form-data; name="field.provided"
...
- ... zope.app.security.interfaces.IAuthenticationUtility
+ ... zope.app.security.interfaces.IAuthentication
... ------------01mLTAiXW04sKSCkjnjf13EYQIvFsu2svmDMwutSuvLCNXx7JekIhxq
... Content-Disposition: form-data; name="field.provided-empty-marker"
...
Modified: Zope3/branches/srichter-blow-services/src/zope/app/pau/groupfolder.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/pau/groupfolder.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/pau/groupfolder.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -63,7 +63,7 @@
prefix = zope.schema.TextLine(
title=u"Group ID prefix",
- description=u"Prefix added to is of groups in this folder",
+ description=u"Prefix added to IDs of groups in this folder",
readonly=True,
)
Modified: Zope3/branches/srichter-blow-services/src/zope/app/pau/groupfolder.txt
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/pau/groupfolder.txt 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/pau/groupfolder.txt 2005-01-17 15:30:10 UTC (rev 28853)
@@ -16,14 +16,14 @@
>>> groups['g1'] = g1
Groups are defined with respect to an authentication service. Groups
-must be accessable via an authentication service and can contain
-principals accessable via an authentication service.
+must be accessible via an authentication service and can contain
+principals accessible via an authentication service.
To illustrate the group interaction with the authentication service,
we'll create a sample authentication service:
>>> import zope.interface
- >>> from zope.app.security.interfaces import IAuthenticationUtility
+ >>> from zope.app.security.interfaces import IAuthentication
>>> from zope.app.pau.groupfolder import setGroupsForPrincipal
>>> class Principal:
@@ -37,7 +37,7 @@
>>> class Principals:
...
- ... zope.interface.implements(IAuthenticationUtility)
+ ... zope.interface.implements(IAuthentication)
...
... def __init__(self, groups):
... self.principals = {
@@ -56,7 +56,7 @@
... setGroupsForPrincipal(PrincipalCreatedEvent(principal))
... return principal
-This class doesn't really implement the fill `IAuthenticationService`
+This class doesn't really implement the full `IAuthenticationService`
interface, but it implements the `getPrincipal` method used by groups.
It works very much like PAU. It creates principals on demand. It
calls `setGroupsForPrincipal`, which is normally called as an event
@@ -72,7 +72,7 @@
`principals` dictionary and a groups folder.
>>> principals = Principals(groups)
- >>> ztapi.provideUtility(IAuthenticationUtility, principals)
+ >>> ztapi.provideUtility(IAuthentication, principals)
Now we can set the principals on the group:
@@ -85,7 +85,7 @@
>>> groups.getGroupsForPrincipal('p1')
(u'group.g1',)
-Note that the group id is a concatination of the group-folder prefix
+Note that the group id is a concatenation of the group-folder prefix
and the name of the group-information object within the folder.
If we delete a group:
Modified: Zope3/branches/srichter-blow-services/src/zope/app/pau/pau.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/pau/pau.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/pau/pau.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -24,7 +24,7 @@
from zope.app import zapi
-from zope.app.security.interfaces import IAuthenticationUtility
+from zope.app.security.interfaces import IAuthentication
from zope.app.component import queryNextUtility
from zope.app.container.contained import Contained
from zope.app.component.interfaces import ILocalUtility
@@ -74,7 +74,7 @@
class PAU(object):
- zope.interface.implements(IPAU, IAuthenticationUtility, ISourceQueriables)
+ zope.interface.implements(IPAU, IAuthentication, ISourceQueriables)
authenticators = extractors = challengers = factories = searchers = ()
@@ -164,11 +164,20 @@
def _delegate(self, meth, *args):
# delegate to next AU
- next = queryNextUtility(self, IAuthenticationUtility)
+ next = queryNextUtility(self, IAuthentication)
if next is None:
return None
return getattr(next, meth)(*args)
+ # BBB
+ def getPrincipals(self, name):
+ import warnings
+ warnings.warn(
+ "The getPrincipals method has been deprecicated. "
+ "It will be removed in Zope X3.3. "
+ "You'll find no principals here.",
+ DeprecationWarning, stacklevel=2)
+ return ()
class LocalPAU(PAU, Persistent, Contained):
zope.interface.implements(IPAU, ILocation, ILocalUtility)
Modified: Zope3/branches/srichter-blow-services/src/zope/app/pau/principalfolder.txt
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/pau/principalfolder.txt 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/pau/principalfolder.txt 2005-01-17 15:30:10 UTC (rev 28853)
@@ -29,9 +29,9 @@
'login': 'login1',
'title': 'Principal 1'})
-we get back a principal id and supplimentary information, including the
+we get back a principal id and supplementary information, including the
principal title and description. Note that the principal id is a
-concatination of the principal-folder prefix and the name of the
+concatenation of the principal-folder prefix and the name of the
principal-information object within the folder.
None is returned if the credentials are invalid:
@@ -121,7 +121,7 @@
Changing credentials
--------------------
-Creedentials can be changed by modifying principal-information
+Credentials can be changed by modifying principal-information
objects:
>>> p1.login = 'bob'
Modified: Zope3/branches/srichter-blow-services/src/zope/app/pluggableauth/__init__.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/pluggableauth/__init__.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/pluggableauth/__init__.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -11,9 +11,9 @@
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
-"""Pluggable Authentication service implementation.
+"""Pluggable Authentication utility implementation.
-BBB: ENTIRE PACKAGE IS DEPRECATED!!! 12/05/2004
+BBB: ENTIRE PACKAGE IS DEPRECATED!!! 12/05/2004 Gone in 3.3
$Id$
"""
@@ -24,7 +24,6 @@
import time
import random
import zope.schema
-
from warnings import warn
from persistent import Persistent
from BTrees.IOBTree import IOBTree
@@ -32,12 +31,9 @@
from zope.interface import implements
from zope.component.interfaces import IViewFactory
-from zope.app.security.interfaces import PrincipalLookupError
from zope.app import zapi
-from zope.app.location import locate
-from zope.app.traversing.api import getPath
-
+from zope.app.component import queryNextUtility
from zope.app.container.interfaces import IOrderedContainer
from zope.app.container.interfaces import IContainerNamesContainer, INameChooser
from zope.app.container.interfaces import IContained
@@ -45,12 +41,12 @@
from zope.app.container.constraints import ContainerTypesConstraint
from zope.app.container.contained import Contained, setitem, uncontained
from zope.app.container.ordered import OrderedContainer
+from zope.app.location import locate
+from zope.app.security.interfaces import ILoginPassword, IAuthentication
+from zope.app.security.interfaces import PrincipalLookupError
+from zope.app.traversing.api import getPath
-from zope.app.servicenames import Authentication
-from zope.app.security.interfaces import ILoginPassword
-from zope.app.component.localservice import queryNextService
-
-from interfaces import IUserSchemafied, IPluggableAuthenticationService
+from interfaces import IUserSchemafied, IPluggableAuthentication
from interfaces import IPrincipalSource, ILoginPasswordPrincipalSource
from interfaces import IContainedPrincipalSource, IContainerPrincipalSource
@@ -59,9 +55,9 @@
return random.randint(0, sys.maxint-1)
-class PluggableAuthenticationService(OrderedContainer):
+class PluggableAuthentication(OrderedContainer):
- implements(IPluggableAuthenticationService, IOrderedContainer)
+ implements(IPluggableAuthentication, IOrderedContainer)
def __init__(self, earmark=None, hide_deprecation_warning=False):
if not hide_deprecation_warning:
@@ -70,18 +66,18 @@
DeprecationWarning, 2)
self.earmark = earmark
# The earmark is used as a token which can uniquely identify
- # this authentication service instance even if the service moves
+ # this authentication utility instance even if the utility moves
# from place to place within the same context chain or is renamed.
# It is included in principal ids of principals which are obtained
- # from this auth service, so code which dereferences a principal
- # (like getPrincipal of this auth service) needs to take the earmark
+ # from this auth utility, so code which dereferences a principal
+ # (like getPrincipal of this auth utility) needs to take the earmark
# into account. The earmark cannot change once it is assigned. If it
# does change, the system will not be able to dereference principal
# references which embed the old earmark.
OrderedContainer.__init__(self)
def authenticate(self, request):
- """ See `IAuthenticationService`. """
+ """ See `IAuthentication`. """
for ps_key, ps in self.items():
loginView = zapi.queryView(ps, "login", request)
if loginView is not None:
@@ -89,36 +85,36 @@
if principal is not None:
return principal
- next = queryNextService(self, Authentication, None)
+ next = queryNextUtility(self, IAuthentication, None)
if next is not None:
return next.authenticate(request)
return None
def unauthenticatedPrincipal(self):
- # It's safe to assume that the global auth service will
+ # It's safe to assume that the global auth utility will
# provide an unauthenticated principal, so we won't bother.
return None
def unauthorized(self, id, request):
- """ See `IAuthenticationService`. """
+ """ See `IAuthentication`. """
- next = queryNextService(self, Authentication, None)
+ next = queryNextUtility(self, IAuthentication)
if next is not None:
return next.unauthorized(id, request)
return None
def getPrincipal(self, id):
- """ See `IAuthenticationService`.
+ """ See `IAuthentication`.
For this implementation, an `id` is a string which can be
split into a 3-tuple by splitting on tab characters. The
- three tuple consists of (`auth_service_earmark`,
+ three tuple consists of (`auth_utility_earmark`,
`principal_source_id`, `principal_id`).
In the current strategy, the principal sources that are members
- of this authentication service cannot be renamed; if they are,
+ of this authentication utility cannot be renamed; if they are,
principal references that embed the old name will not be
dereferenceable.
@@ -130,11 +126,11 @@
auth_svc_earmark, principal_src_id, principal_id = id.split('\t',2)
except (TypeError, ValueError, AttributeError):
auth_svc_earmark, principal_src_id, principal_id = None, None, None
- next = queryNextService(self, Authentication, None)
+ next = queryNextUtility(self, IAuthentication)
if auth_svc_earmark != self.earmark:
# this is not our reference because its earmark doesnt match ours
- next = queryNextService(self, Authentication, None)
+ next = queryNextUtility(self, IAuthentication)
if next is not None:
return next.getPrincipal(id)
@@ -145,21 +141,21 @@
return source.getPrincipal(id)
def getPrincipals(self, name):
- """ See `IAuthenticationService`. """
+ """ See `IAuthentication`. """
for ps_key, ps in self.items():
for p in ps.getPrincipals(name):
yield p
- next = queryNextService(self, Authentication, None)
+ next = queryNextUtility(self, IAuthentication)
if next is not None:
for p in next.getPrincipals(name):
yield p
def addPrincipalSource(self, id, principal_source):
- """ See `IPluggableAuthenticationService`.
+ """ See `IPluggableAuthentication`.
- >>> pas = PluggableAuthenticationService(None, True)
+ >>> pas = PluggableAuthentication(None, True)
>>> sps = BTreePrincipalSource()
>>> pas.addPrincipalSource('simple', sps)
>>> sps2 = BTreePrincipalSource()
@@ -174,9 +170,9 @@
self[id] = principal_source
def removePrincipalSource(self, id):
- """ See `IPluggableAuthenticationService`.
+ """ See `IPluggableAuthentication`.
- >>> pas = PluggableAuthenticationService(None, True)
+ >>> pas = PluggableAuthentication(None, True)
>>> sps = BTreePrincipalSource()
>>> pas.addPrincipalSource('simple', sps)
>>> sps2 = BTreePrincipalSource()
@@ -193,10 +189,10 @@
del self[id]
-def PluggableAuthenticationServiceAddSubscriber(self, event):
+def PluggableAuthenticationAddSubscriber(self, event):
r"""Generates an earmark if one is not provided.
- Define a stub for `PluggableAuthenticationService`
+ Define a stub for `PluggableAuthentication`
>>> from zope.app.traversing.interfaces import IPhysicallyLocatable
>>> class PluggableAuthStub(object):
@@ -206,22 +202,22 @@
... def getName(self):
... return 'PluggableAuthName'
- The subscriber generates an earmark for the auth service if one is not
+ The subscriber generates an earmark for the auth utility if one is not
set in the init.
>>> stub = PluggableAuthStub()
>>> event = ''
- >>> PluggableAuthenticationServiceAddSubscriber(stub, event)
+ >>> PluggableAuthenticationAddSubscriber(stub, event)
>>> stub.earmark is not None
True
- The subscriber does not modify an earmark for the auth service if one
+ The subscriber does not modify an earmark for the auth utility if one
exists already.
>>> earmark = 'my sample earmark'
>>> stub = PluggableAuthStub(earmark=earmark)
>>> event = ''
- >>> PluggableAuthenticationServiceAddSubscriber(stub, event)
+ >>> PluggableAuthenticationAddSubscriber(stub, event)
>>> stub.earmark == earmark
True
"""
Modified: Zope3/branches/srichter-blow-services/src/zope/app/pluggableauth/browser/configure.zcml
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/pluggableauth/browser/configure.zcml 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/pluggableauth/browser/configure.zcml 2005-01-17 15:30:10 UTC (rev 28853)
@@ -2,20 +2,20 @@
xmlns="http://namespaces.zope.org/browser"
xmlns:zope="http://namespaces.zope.org/zope">
-<!-- Pluggable Authentication Service -->
+<!-- Pluggable Authentication -->
<!-- BBB: Deactivated as a deprecation measure. -->
<!--addMenuItem
- class="zope.app.pluggableauth.PluggableAuthenticationService"
- title="Authentication Service"
+ class="zope.app.pluggableauth.PluggableAuthentication"
+ title="Authentication"
description="A Pluggable Authentication uses plug-in principal sources."
- permission="zope.ManageServices"
+ permission="zope.ManageSite"
/-->
<containerViews
- for="zope.app.pluggableauth.interfaces.IPluggableAuthenticationService"
- contents="zope.ManageServices"
- add="zope.ManageServices"
+ for="zope.app.pluggableauth.interfaces.IPluggableAuthentication"
+ contents="zope.ManageSite"
+ add="zope.ManageSite"
/>
<!-- Principal Source -->
@@ -23,13 +23,13 @@
<addMenuItem
title="Add Principal Source"
class="zope.app.pluggableauth.BTreePrincipalSource"
- permission="zope.ManageServices"
+ permission="zope.ManageSite"
/>
<containerViews
for="zope.app.pluggableauth.interfaces.IContainerPrincipalSource"
- contents="zope.ManageServices"
- add="zope.ManageServices"
+ contents="zope.ManageSite"
+ add="zope.ManageSite"
/>
<!-- Principal -->
@@ -48,7 +48,7 @@
<addMenuItem
title="Principal"
class="zope.app.pluggableauth.SimplePrincipal"
- permission="zope.ManageServices"
+ permission="zope.ManageSite"
view="AddPrincipalForm.html"
/>
Modified: Zope3/branches/srichter-blow-services/src/zope/app/pluggableauth/configure.zcml
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/pluggableauth/configure.zcml 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/pluggableauth/configure.zcml 2005-01-17 15:30:10 UTC (rev 28853)
@@ -2,13 +2,13 @@
xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser">
- <localService class=".PluggableAuthenticationService">
+ <localService class=".PluggableAuthentication">
<factory
- id="zope.app.services.PluggableAuthenticationService"
+ id="zope.app.services.PluggableAuthentication"
/>
<require
- permission="zope.ManageServices"
- interface=".interfaces.IPluggableAuthenticationService"
+ permission="zope.ManageSite"
+ interface=".interfaces.IPluggableAuthentication"
/>
<!--
<allow
@@ -16,20 +16,20 @@
/>
<require
- permission="zope.ManageServices"
+ permission="zope.ManageSite"
interface="zope.app.container.interfaces.IWriteContainer"
/>
-->
<require
- permission="zope.ManageServices"
+ permission="zope.ManageSite"
interface="zope.app.site.interfaces.ISimpleService"
/>
</localService>
<subscriber
- for=".interfaces.IPluggableAuthenticationService
+ for=".interfaces.IPluggableAuthentication
zope.app.container.interfaces.IObjectAddedEvent"
- factory=".PluggableAuthenticationServiceAddSubscriber"
+ factory=".PluggableAuthenticationAddSubscriber"
/>
<content class=".BTreePrincipalSource">
@@ -40,7 +40,7 @@
interface="zope.app.container.interfaces.IReadContainer"
/>
<require
- permission="zope.ManageServices"
+ permission="zope.ManageSite"
interface="zope.app.container.interfaces.IWriteContainer
zope.app.container.interfaces.INameChooser"
/>
@@ -57,7 +57,7 @@
interface=".interfaces.IUserSchemafied"
/>
<require
- permission="zope.ManageServices"
+ permission="zope.ManageSite"
set_schema=".interfaces.IUserSchemafied"
/>
</content>
Modified: Zope3/branches/srichter-blow-services/src/zope/app/pluggableauth/interfaces.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/pluggableauth/interfaces.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/pluggableauth/interfaces.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -11,7 +11,7 @@
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
-"""Pluggable Authentication service.
+"""Pluggable Authentication Utility.
$Id$
"""
@@ -21,7 +21,7 @@
from zope.app.container.interfaces import IContainer, IContained
from zope.app.container.constraints import ItemTypePrecondition
from zope.app.container.constraints import ContainerTypesConstraint
-from zope.app.security.interfaces import IAuthenticationService, IPrincipal
+from zope.app.security.interfaces import IAuthentication, IPrincipal
from zope.interface import Interface
from zope.schema import Text, TextLine, Password, Field
@@ -55,14 +55,14 @@
found.
Note that the id has three parts, separated by tabs. The
- first two part are an authentication service id and a
+ first two part are an authentication utility id and a
principal source id. The pricipal source will typically need
to remove the two leading parts from the id when doing it's
own internal lookup.
- Note that the authentication service nearest to the requested
- resource is called. It is up to authentication service
- implementations to collaborate with services higher in the
+ Note that the authentication utility nearest to the requested
+ resource is called. It is up to authentication utility
+ implementations to collaborate with utilities higher in the
object hierarchy.
"""
@@ -74,8 +74,8 @@
"""
-class IPluggableAuthenticationService(IAuthenticationService, IContainer):
- """An `AuthenticationService` that can contain multiple pricipal sources.
+class IPluggableAuthentication(IAuthentication, IContainer):
+ """An `Authentication` utility that can contain multiple pricipal sources.
"""
def __setitem__(id, principal_source):
@@ -113,8 +113,8 @@
class IContainedPrincipalSource(IPrincipalSource, IContained):
"""This is a marker interface for principal sources that can be directly
- added to an authentication service. It ensures that principal source can
- **only** be added to pluggable authentication services."""
+ added to an authentication utility. It ensures that principal source can
+ **only** be added to pluggable authentication utilities."""
__parent__= Field(
- constraint = ContainerTypesConstraint(IPluggableAuthenticationService))
+ constraint = ContainerTypesConstraint(IPluggableAuthentication))
Modified: Zope3/branches/srichter-blow-services/src/zope/app/pluggableauth/tests/authsetup.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/pluggableauth/tests/authsetup.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/pluggableauth/tests/authsetup.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -11,7 +11,7 @@
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
-"""Setup local Pluggable Authentication Service for tests
+"""Setup local Pluggable Authentication for tests
This setup class can be used, if a set of local principals are required for a
test.
@@ -24,10 +24,10 @@
from zope.app.testing import ztapi, setup
from zope.app.component.testing import PlacefulSetup
from zope.publisher.interfaces.http import IHTTPCredentials
-from zope.app.security.interfaces import ILoginPassword
+from zope.app.security.interfaces import ILoginPassword, IAuthentication
from zope.app.security.basicauthadapter import BasicAuthAdapter
from zope.app.pluggableauth import \
- PrincipalAuthenticationView, PluggableAuthenticationService, \
+ PrincipalAuthenticationView, PluggableAuthentication, \
BTreePrincipalSource, SimplePrincipal
from zope.app.pluggableauth.interfaces import IPrincipalSource
@@ -40,8 +40,8 @@
ztapi.browserView(IPrincipalSource, "login",
PrincipalAuthenticationView)
- auth = setup.addService(sm, "PluggableAuthService",
- PluggableAuthenticationService(None, True))
+ auth = setup.addUtility(sm, '', IAuthentication,
+ PluggableAuthentication(None, True))
one = BTreePrincipalSource()
two = BTreePrincipalSource()
Modified: Zope3/branches/srichter-blow-services/src/zope/app/pluggableauth/tests/test_pluggableauth.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/pluggableauth/tests/test_pluggableauth.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/pluggableauth/tests/test_pluggableauth.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -27,12 +27,13 @@
from zope.publisher.interfaces.http import IHTTPCredentials
from zope.app.pluggableauth import BTreePrincipalSource, \
- SimplePrincipal, PluggableAuthenticationService, \
+ SimplePrincipal, PluggableAuthentication, \
PrincipalAuthenticationView
from zope.app.pluggableauth.interfaces import IPrincipalSource
from zope.app.pluggableauth.interfaces import IUserSchemafied
from zope.app.security.interfaces import IPrincipal, ILoginPassword
+from zope.app.security.interfaces import IAuthentication
from zope.app.security.basicauthadapter import BasicAuthAdapter
from zope.publisher.browser import TestRequest as Request
@@ -51,8 +52,8 @@
ztapi.browserView(IPrincipalSource, "login",
PrincipalAuthenticationView)
- auth = setup.addService(sm, "TestPluggableAuthenticationService",
- PluggableAuthenticationService(None, True))
+ auth = setup.addUtility(sm, "", IAuthentication,
+ PluggableAuthentication(None, True))
one = BTreePrincipalSource()
two = BTreePrincipalSource()
@@ -87,9 +88,9 @@
return Request(**dict)
-class AuthServiceTest(Setup):
+class AuthUtilityTest(Setup):
- def testAuthServiceAuthenticate(self):
+ def testAuthUtilityAuthenticate(self):
auth = self._auth
req = self.getRequest('slinkp', '123')
pid = auth.authenticate(req).getLogin()
@@ -119,7 +120,7 @@
def _fail_BadIdLength(self):
self._auth.getPrincipal((self._auth.earmark, None, None))
- def testAuthServiceGetPrincipal(self):
+ def testAuthUtilityGetPrincipal(self):
auth = self._auth
id = self._slinkp.id
self.assertEqual(self._slinkp, auth.getPrincipal(id))
@@ -158,7 +159,7 @@
def test_suite():
- t1 = makeSuite(AuthServiceTest)
+ t1 = makeSuite(AuthUtilityTest)
t2 = DocTestSuite('zope.app.pluggableauth',
setUp=setUp, tearDown=tearDown)
t3 = makeSuite(BTreePrincipalSourceTest)
Modified: Zope3/branches/srichter-blow-services/src/zope/app/presentation/__init__.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/presentation/__init__.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/presentation/__init__.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -17,5 +17,5 @@
"""
__docformat__ = 'restructuredtext'
-from zope.app.presentation.presentation import IPageRegistration
+from zope.app.presentation.interfaces import IPageRegistration
from zope.app.presentation.presentation import PageRegistration
Modified: Zope3/branches/srichter-blow-services/src/zope/app/presentation/browser/configure.zcml
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/presentation/browser/configure.zcml 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/presentation/browser/configure.zcml 2005-01-17 15:30:10 UTC (rev 28853)
@@ -1,7 +1,7 @@
<configure xmlns="http://namespaces.zope.org/browser">
<editform
- schema="...presentation.IPageRegistration"
+ schema="..interfaces.IPageRegistration"
name="index.html"
class=".PageRegistrationView"
menu="zmi_views"
@@ -9,7 +9,7 @@
permission="zope.ManageServices" />
<addform
- schema="..presentation.IPageRegistration"
+ schema="..interfaces.IPageRegistration"
name="PageRegistration"
content_factory="..presentation.PageRegistration"
keyword_arguments="required factoryName name permission
Modified: Zope3/branches/srichter-blow-services/src/zope/app/presentation/browser/zpt.zcml
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/presentation/browser/zpt.zcml 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/presentation/browser/zpt.zcml 2005-01-17 15:30:10 UTC (rev 28853)
@@ -23,7 +23,7 @@
<addform
for="zope.app.presentation.zpt.IZPTTemplate"
- schema="zope.app.presentation.IPageRegistration"
+ schema="zope.app.presentation.interfaces.IPageRegistration"
name="addRegistration.html"
class="zope.app.registration.browser.AddComponentRegistration"
content_factory="zope.app.presentation.PageRegistration"
Modified: Zope3/branches/srichter-blow-services/src/zope/app/presentation/configure.zcml
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/presentation/configure.zcml 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/presentation/configure.zcml 2005-01-17 15:30:10 UTC (rev 28853)
@@ -7,7 +7,7 @@
<content class=".presentation.ViewRegistration">
<require
- permission="zope.ManageServices"
+ permission="zope.ManageSite"
interface=".presentation.IViewRegistration"
set_schema="zope.app.registration.interfaces.IRegistration"
/>
@@ -15,21 +15,21 @@
<content class=".presentation.PageRegistration">
<require
- permission="zope.ManageServices"
- interface=".presentation.IPageRegistration"
- set_schema=".presentation.IPageRegistration"
+ permission="zope.ManageSite"
+ interface=".interfaces.IPageRegistration"
+ set_schema=".interfaces.IPageRegistration"
/>
</content>
<subscriber
factory=".presentation.PageRegistrationAddSubscriber"
- for=".presentation.IPageRegistration
+ for=".interfaces.IPageRegistration
zope.app.container.interfaces.IObjectAddedEvent"
/>
<subscriber
factory=".presentation.PageRegistrationRemoveSubscriber"
- for=".presentation.IPageRegistration
+ for=".interfaces.IPageRegistration
zope.app.container.interfaces.IObjectRemovedEvent"
/>
Modified: Zope3/branches/srichter-blow-services/src/zope/app/presentation/presentation.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/presentation/presentation.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/presentation/presentation.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -22,20 +22,21 @@
from zope.interface import implements, providedBy, Interface, Attribute
from zope.security.checker import NamesChecker, ProxyFactory
-import zope.app.container.contained
-import zope.app.component.interfaces.registration
-import zope.app.component.adapter
-import zope.app.interface.interfaces
import zope.component.interfaces
import zope.configuration.exceptions
import zope.proxy
import zope.publisher.interfaces.browser
-import zope.schema
+import zope.app.component.interfaces.registration
+import zope.app.component.adapter
+import zope.app.container.contained
+import zope.app.interface.interfaces
from zope.app import zapi
from zope.app.i18n import ZopeMessageIDFactory as _
from zope.app.dependable.interfaces import IDependable, DependencyError
+import interfaces
+
class GlobalViewRegistration(object):
"""Registrations representing global view thingies."""
@@ -83,35 +84,8 @@
self.__parent__ = parent
self.__name__ = name
-class IViewRegistration(zope.app.component.interfaces.IAdapterRegistration):
-
- required = zope.schema.Choice(
- title = _(u"For interface"),
- description = _(u"The interface of the objects being viewed"),
- vocabulary="Interfaces",
- readonly = True,
- required = True,
- )
-
- requestType = zope.schema.Choice(
- title = _(u"Request type"),
- description = _(u"The type of requests the view works with"),
- vocabulary="Interfaces",
- readonly = True,
- required = True,
- )
-
- layer = zope.schema.BytesLine(
- title = _(u"Layer"),
- description = _(u"The skin layer the view is registered for"),
- required = False,
- readonly = True,
- min_length = 1,
- default = "default",
- )
-
class ViewRegistration(zope.app.component.registration.SimpleRegistration):
- implements(IViewRegistration)
+ implements(interfaces.IViewRegistration)
provided = Interface
@@ -157,36 +131,8 @@
return folder.resolve(self.factoryName)
factory = property(factory)
-
-class IPageRegistration(IViewRegistration):
-
- factoryName = zope.schema.BytesLine(
- title=_(u"Page class"),
- required = False,
- )
-
- template = zope.app.component.interfaces.registration.Component(
- title = _(u"Page template"),
- required = False,
- )
-
- attribute = zope.schema.TextLine(
- title = _(u"Class attribute"),
- required = False,
- )
-
- factory = Attribute(
- _("Factory to be called to construct an adapter")
- )
-
- def validate(self):
- """Verifies that the registration is valid.
-
- Raises a ConfigurationError if the validation is failed.
- """
-
class PageRegistration(ViewRegistration):
- implements(IPageRegistration)
+ implements(interfaces.IPageRegistration)
# We only care about browser pages
requestType = zope.publisher.interfaces.browser.IBrowserRequest
@@ -210,21 +156,6 @@
self.template = template
self.attribute = attribute
- def implementationSummary(self):
- L = []
- if self.template:
- prefix = "/++etc++site/"
- t = self.template
- i = t.rfind(prefix)
- if i >= 0:
- t = t[i + len(prefix):]
- L.append("template=%s" % t)
- if self.factoryName:
- L.append("class=%s" % self.factoryName)
- if self.attribute:
- L.append("attribute=%s" % self.attribute)
- return ", ".join(L)
-
def validate(self):
if self.template and self.attribute:
raise zope.configuration.exceptions.ConfigurationError(
@@ -253,7 +184,7 @@
folder = self.__parent__.__parent__
class_ = folder.resolve(self.factoryName)
else:
- class_ = DefaultClass
+ class_ = BrowserView
if self.attribute:
return AttrViewFactory(class_, self.attribute)
@@ -270,19 +201,21 @@
factory = property(factory)
-def PageRegistrationAddSubscriber(self, event):
- if self.template:
- template = zapi.traverse(self.__parent__.__parent__,self.template)
+def PageRegistrationAddSubscriber(registration, event):
+ if registration.template:
+ template = zapi.traverse(registration.__parent__.__parent__,
+ registration.template)
dependents = IDependable(template)
- objectpath = zapi.getPath(self)
+ objectpath = zapi.getPath(registration)
dependents.addDependent(objectpath)
-def PageRegistrationRemoveSubscriber(self, event):
- if self.template:
- template = zapi.traverse(self.__parent__.__parent__,self.template)
+def PageRegistrationRemoveSubscriber(registration, event):
+ if registration.template:
+ template = zapi.traverse(registration.__parent__.__parent__,
+ registration.template)
dependents = IDependable(template)
- objectpath = zapi.getPath(self)
+ objectpath = zapi.getPath(registration)
dependents.removeDependent(objectpath)
class TemplateViewFactory(object):
@@ -312,12 +245,6 @@
attr = getattr(self.cls(object, request), self.attr)
return ProxyFactory(attr)
-class DefaultClass(object):
-
- def __init__(self, context, request):
- self.context = context
- self.request = request
-
class BoundTemplate(object):
def __init__(self, template, view):
Modified: Zope3/branches/srichter-blow-services/src/zope/app/presentation/xxx_Tests/test_presentation.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/presentation/xxx_Tests/test_presentation.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/presentation/xxx_Tests/test_presentation.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -30,10 +30,10 @@
from zope.app.presentation.zpt import IZPTTemplate
from zope.app.registration.tests.iregistry import TestingIRegistry
from zope.app.site.tests.placefulsetup import PlacefulSetup
+from zope.app.presentation.interfaces import IPageRegistration
from zope.app.presentation.presentation import ViewRegistration
from zope.app.presentation.presentation import PageRegistration
from zope.app.presentation.presentation import BoundTemplate
-from zope.app.presentation.presentation import IPageRegistration
from zope.app.presentation.presentation import PageRegistrationAddSubscriber
from zope.app.presentation.presentation import PageRegistrationRemoveSubscriber
from zope.app.testing import setup
Modified: Zope3/branches/srichter-blow-services/src/zope/app/publication/httpfactory.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/publication/httpfactory.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/publication/httpfactory.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -30,6 +30,8 @@
from zope.app.publication.http import HTTPPublication
from zope.app.publication.browser import BrowserPublication
from zope.app.publication.xmlrpc import XMLRPCPublication
+from zope.app.publication.soap import SOAPPublication
+from zope.app.publication.interfaces import ISOAPRequestFactory
_browser_methods = 'GET', 'POST', 'HEAD'
@@ -41,15 +43,23 @@
self._http = HTTPPublication(db)
self._brower = BrowserPublication(db)
self._xmlrpc = XMLRPCPublication(db)
+ self._soappub = SOAPPublication(db)
+ self._soapreq = zapi.queryUtility(ISOAPRequestFactory)
def __call__(self, input_stream, output_steam, env):
"""See `zope.app.publication.interfaces.IPublicationRequestFactory`"""
method = env.get('REQUEST_METHOD', 'GET').upper()
if method in _browser_methods:
- if (method == 'POST' and
- env.get('CONTENT_TYPE', '').startswith('text/xml')
- ):
+ content_type = env.get('CONTENT_TYPE', '')
+ is_xml = content_type.startswith('text/xml')
+
+ if (method == 'POST' and is_xml and
+ env.get('HTTP_SOAPACTION', None)
+ and self._soapreq is not None):
+ request = self._soapreq(input_stream, output_steam, env)
+ request.setPublication(self._soappub)
+ elif (method == 'POST' and is_xml):
request = XMLRPCRequest(input_stream, output_steam, env)
request.setPublication(self._xmlrpc)
else:
Modified: Zope3/branches/srichter-blow-services/src/zope/app/publication/interfaces.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/publication/interfaces.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/publication/interfaces.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -50,3 +50,12 @@
def __init__(self, ob, request):
self.object = ob
self.request = request
+
+
+class ISOAPRequestFactory(Interface):
+ """SOAP request factory"""
+
+ def __call__(input_stream, output_steam, env):
+ """Create a request object to handle SOAP input."""
+
+
Copied: Zope3/branches/srichter-blow-services/src/zope/app/publication/soap.py (from rev 28844, Zope3/trunk/src/zope/app/publication/soap.py)
Property changes on: Zope3/branches/srichter-blow-services/src/zope/app/publication/soap.py
___________________________________________________________________
Name: svn:executable
+ *
Modified: Zope3/branches/srichter-blow-services/src/zope/app/publication/zopepublication.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/publication/zopepublication.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/publication/zopepublication.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -48,7 +48,7 @@
from zope.app.publication.publicationtraverse import PublicationTraverse
from zope.app.security.principalregistry import principalRegistry as prin_reg
from zope.app.security.interfaces import IUnauthenticatedPrincipal
-from zope.app.security.interfaces import IAuthenticationUtility
+from zope.app.security.interfaces import IAuthentication
from zope.app.component.interfaces import ISite
from zope.app.traversing.interfaces import IPhysicallyLocatable
@@ -95,7 +95,7 @@
sm = removeSecurityProxy(ob).getSiteManager()
- auth = sm.queryUtility(IAuthenticationUtility)
+ auth = sm.queryUtility(IAuthentication)
if auth is None:
# No auth utility here
return
Modified: Zope3/branches/srichter-blow-services/src/zope/app/pythonpage/edit.pt
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/pythonpage/edit.pt 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/pythonpage/edit.pt 2005-01-17 15:30:10 UTC (rev 28853)
@@ -5,7 +5,7 @@
<div metal:define-macro="body">
- <form action="." tal:attributes="action request/URL" method="POST"
+ <form action="." tal:attributes="action request/URL" method="post"
enctype="multipart/form-data">
<div metal:define-macro="formbody">
Modified: Zope3/branches/srichter-blow-services/src/zope/app/rdb/browser/configure.zcml
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/rdb/browser/configure.zcml 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/rdb/browser/configure.zcml 2005-01-17 15:30:10 UTC (rev 28853)
@@ -11,7 +11,7 @@
<!-- ZopeDatabaseAdapter default views -->
- <!-- XXX need an index.html that gives the source and is the def view -->
+ <!-- TODO: need an index.html that gives the source and is the def view -->
<view
name="+"
Modified: Zope3/branches/srichter-blow-services/src/zope/app/schema/browser/configure.zcml
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/schema/browser/configure.zcml 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/schema/browser/configure.zcml 2005-01-17 15:30:10 UTC (rev 28853)
@@ -25,26 +25,12 @@
</view>
-<!-- XXX: Activate again
- <addform
- label="New Mutable Schema Registration"
- for="zope.app.schema.interfaces.ISchemaUtility"
- name="addRegistration.html"
- schema="zope.app.component.interfaces.IUtilityRegistration"
- class="zope.app.component.browser.AddRegistration"
- permission="zope.ManageServices"
- content_factory="zope.app.schema.schema.SchemaRegistration"
- arguments="name interface componentPath"
- set_after_add="status"
- fields="name interface componentPath permission status" />
-
<addMenuItem
title="Mutable Schema"
description="A Persistent Schema that can be edited through the web"
class="zope.app.schema.schema.SchemaUtility"
permission="zope.ManageServices"
/>
--->
<defaultView
for="zope.app.schema.interfaces.IMutableSchema"
Modified: Zope3/branches/srichter-blow-services/src/zope/app/schema/browser/schema_edit.pt
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/schema/browser/schema_edit.pt 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/schema/browser/schema_edit.pt 2005-01-17 15:30:10 UTC (rev 28853)
@@ -9,7 +9,7 @@
<span tal:content="view/name" i18n:name="schema_name"/>
</h3>
- <form action="." tal:attributes="action request/URL" method="POST"
+ <form action="." tal:attributes="action request/URL" method="post"
enctype="multipart/form-data">
<p tal:define="status view/update"
Modified: Zope3/branches/srichter-blow-services/src/zope/app/schema/fields.zcml
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/schema/fields.zcml 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/schema/fields.zcml 2005-01-17 15:30:10 UTC (rev 28853)
@@ -23,7 +23,7 @@
<allow attributes="__name__" />
- <!-- XXX put the whole interface under one permission for now -->
+ <!-- TODO: put the whole interface under one permission for now -->
<require
permission="zope.ManageContent"
Modified: Zope3/branches/srichter-blow-services/src/zope/app/schema/tests/test_fieldfactory.zcml
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/schema/tests/test_fieldfactory.zcml 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/schema/tests/test_fieldfactory.zcml 2005-01-17 15:30:10 UTC (rev 28853)
@@ -25,7 +25,7 @@
/>
-->
- <!-- XXX put the whole interface under one permission for now -->
+ <!-- TODO: put the whole interface under one permission for now -->
<require
permission="zope.ManageContent"
interface="zope.schema.interfaces.IField"
Modified: Zope3/branches/srichter-blow-services/src/zope/app/schemacontent/browser/configure.zcml
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/schemacontent/browser/configure.zcml 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/schemacontent/browser/configure.zcml 2005-01-17 15:30:10 UTC (rev 28853)
@@ -11,20 +11,6 @@
<!-- Content Component Definition -->
- <!-- XXX: addform
- label="Content Component Definition Registration"
- for= "zope.app.schemacontent.interfaces.IContentComponentDefinition"
- name="addRegistration.html"
- schema="zope.app.component.interfaces.IUtilityRegistration"
- class="zope.app.component.browser.registration.AddRegistration"
- permission="zope.ManageServices"
- content_factory="
- zope.app.schemacontent.content.ContentComponentDefinitionRegistration"
- arguments="name interface componentPath"
- set_after_add="status"
- fields="name interface componentPath permission status" /-->
-
-
<!-- Menu entry for "add component" menu -->
<menuItem
for="zope.app.container.interfaces.IAdding"
Modified: Zope3/branches/srichter-blow-services/src/zope/app/schemacontent/browser/permission_edit.pt
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/schemacontent/browser/permission_edit.pt 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/schemacontent/browser/permission_edit.pt 2005-01-17 15:30:10 UTC (rev 28853)
@@ -7,7 +7,7 @@
tal:content="status" />
<form action="./@@edit.html"
- tal:attributes="action request/URL" method="POST">
+ tal:attributes="action request/URL" method="post">
<tal:block define="widgets view/getPermissionWidgets"
condition="widgets">
@@ -42,4 +42,4 @@
</div>
</body>
-</html>
\ No newline at end of file
+</html>
Modified: Zope3/branches/srichter-blow-services/src/zope/app/security/browser/auth.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/security/browser/auth.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/security/browser/auth.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -18,7 +18,7 @@
from zope.interface import implements
from zope.i18n import translate
from zope.app.publisher.interfaces.http import ILogin, ILogout
-from zope.app.security.interfaces import IAuthenticationUtility
+from zope.app.security.interfaces import IAuthentication
from zope.app.security.principalregistry import UnauthenticatedPrincipal
from zope.app.pagetemplate import ViewPageTemplateFile
from zope.proxy import removeAllProxies
@@ -28,7 +28,7 @@
search_label = _('search-button', 'Search')
class AuthUtilitySearchView(object):
- __used_for__ = IAuthenticationUtility
+ __used_for__ = IAuthentication
def __init__(self, context, request):
self.context = context
Modified: Zope3/branches/srichter-blow-services/src/zope/app/security/browser/configure.zcml
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/security/browser/configure.zcml 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/security/browser/configure.zcml 2005-01-17 15:30:10 UTC (rev 28853)
@@ -4,7 +4,7 @@
<adapter
- for="zope.app.security.interfaces.IAuthenticationUtility
+ for="zope.app.security.interfaces.IAuthentication
zope.publisher.interfaces.browser.IBrowserRequest"
provides="zope.app.form.browser.interfaces.ISourceQueryView"
factory="zope.app.security.browser.auth.AuthUtilitySearchView"
Modified: Zope3/branches/srichter-blow-services/src/zope/app/security/browser/principalterms.txt
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/security/browser/principalterms.txt 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/security/browser/principalterms.txt 2005-01-17 15:30:10 UTC (rev 28853)
@@ -11,10 +11,10 @@
... self.id, self.title = id, title
>>> from zope.interface import implements
- >>> from zope.app.security.interfaces import IAuthenticationUtility
+ >>> from zope.app.security.interfaces import IAuthentication
>>> from zope.app.security.interfaces import PrincipalLookupError
>>> class AuthUtility:
- ... implements(IAuthenticationUtility)
+ ... implements(IAuthentication)
... data = {'jim': 'Jim Fulton', 'stephan': 'Stephan Richter'}
...
... def getPrincipal(self, id):
@@ -26,14 +26,14 @@
Now we need to install the authentication utility:
>>> from zope.app.testing import ztapi
- >>> ztapi.provideUtility(IAuthenticationUtility, AuthUtility())
+ >>> ztapi.provideUtility(IAuthentication, AuthUtility())
We need a principal source so that we can create a view from it.
>>> from zope.app import zapi
>>> class PrincipalSource:
... def __contains__(self, id):
- ... auth = zapi.getUtility(IAuthenticationUtility)
+ ... auth = zapi.getUtility(IAuthentication)
... try:
... auth.getPrincipal(id)
... except PrincipalLookupError:
Modified: Zope3/branches/srichter-blow-services/src/zope/app/security/configure.zcml
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/security/configure.zcml 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/security/configure.zcml 2005-01-17 15:30:10 UTC (rev 28853)
@@ -23,7 +23,7 @@
<include file="_protections.zcml" />
<utility
- provides=".interfaces.IAuthenticationUtility"
+ provides=".interfaces.IAuthentication"
component=".principalregistry.principalRegistry" />
<localUtility class=".permission.LocalPermission">
Modified: Zope3/branches/srichter-blow-services/src/zope/app/security/interfaces.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/security/interfaces.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/security/interfaces.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -33,13 +33,13 @@
Authenticated principals are preferable to UnauthenticatedPrincipals.
"""
-class IAuthenticationUtility(Interface):
+class IAuthentication(Interface):
"""Provide support for establishing principals for requests.
This is implemented by performing protocol-specific actions, such as
issuing challenges or providing login interfaces.
- `IAuthenticationUtility` objects are used to implement authentication
+ `IAuthentication` objects are used to implement authentication
utilities. Because they implement utilities, they are expected to
collaborate with utilities in other contexts. Client code doesn't search a
context and call multiple utilities. Instead, client code will call the
@@ -119,11 +119,12 @@
object hierarchy.
"""
+class IAuthenticationUtility(IAuthentication):
+ """This interface is deprecated
+ """
+
def getPrincipals(name):
- """Get principals with matching names.
-
- Get an iterable object with the principals with names that are
- similar to (e.g. contain) the given name.
+ """This interface is deprecated
"""
############################################################################
@@ -134,7 +135,7 @@
class ILoginPassword(Interface):
"""A password based login.
- An `IAuthenticationUtility` would use this (adapting a request),
+ An `IAuthentication` would use this (adapting a request),
to discover the login/password passed from the user, or to
indicate that a login is required.
"""
Modified: Zope3/branches/srichter-blow-services/src/zope/app/security/principal.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/security/principal.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/security/principal.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -17,7 +17,7 @@
"""
from zope.app import zapi
from zope.app.security.interfaces import PrincipalLookupError
-from zope.app.security.interfaces import IAuthenticationUtility
+from zope.app.security.interfaces import IAuthentication
# BBB Backward Compatibility
from zope.exceptions import NotFoundError
@@ -25,7 +25,7 @@
def checkPrincipal(context, principal_id):
- auth = zapi.getUtility(IAuthenticationUtility, context=context)
+ auth = zapi.getUtility(IAuthentication, context=context)
try:
if auth.getPrincipal(principal_id):
return
Modified: Zope3/branches/srichter-blow-services/src/zope/app/security/principalregistry.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/security/principalregistry.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/security/principalregistry.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -20,7 +20,7 @@
from zope.app.security.interfaces import PrincipalLookupError
from zope.app import zapi
from zope.app.security.interfaces import ILoginPassword
-from zope.app.security.interfaces import IAuthenticationUtility, IPrincipal
+from zope.app.security.interfaces import IAuthentication, IPrincipal
from zope.app.security.interfaces import IUnauthenticatedPrincipal
from zope.app.container.contained import Contained, contained
from warnings import warn
@@ -30,9 +30,9 @@
class PrincipalRegistry(object):
- implements(IAuthenticationUtility)
+ implements(IAuthentication)
- # Methods implementing IAuthenticationUtility
+ # Methods implementing IAuthentication
def authenticate(self, request):
a = ILoginPassword(request, None)
Modified: Zope3/branches/srichter-blow-services/src/zope/app/security/tests/test_securitydirectives.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/security/tests/test_securitydirectives.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/security/tests/test_securitydirectives.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -24,7 +24,7 @@
from zope.app.testing.placelesssetup import PlacelessSetup
from zope.app.servicenames import Authentication
-from zope.app.security.interfaces import IAuthenticationUtility, IPermission
+from zope.app.security.interfaces import IAuthentication, IPermission
from zope.app.security.principalregistry import principalRegistry
from zope.app.security.settings import Allow
import zope.app.security.tests
@@ -34,7 +34,7 @@
def setUp(self):
super(TestBase, self).setUp()
- ztapi.provideUtility(IAuthenticationUtility, principalRegistry)
+ ztapi.provideUtility(IAuthentication, principalRegistry)
class TestPrincipalDirective(TestBase, unittest.TestCase):
Modified: Zope3/branches/srichter-blow-services/src/zope/app/security/vocabulary.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/security/vocabulary.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/security/vocabulary.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -22,7 +22,7 @@
from zope.interface import implements
from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary
from zope.schema.interfaces import ISourceQueriables
-from zope.app.security.interfaces import IPermission, IAuthenticationUtility
+from zope.app.security.interfaces import IPermission, IAuthentication
from zope.app.security.interfaces import PrincipalLookupError
from zope.app.component import queryNextUtility
@@ -147,7 +147,7 @@
>>> zapi.getUtility = temp
"""
- auth = zapi.getUtility(IAuthenticationUtility)
+ auth = zapi.getUtility(IAuthentication)
try:
auth.getPrincipal(id)
except PrincipalLookupError:
@@ -174,27 +174,27 @@
authentication utilities to look for queriables.
>>> class DummyUtility1:
- ... implements(IAuthenticationUtility)
+ ... implements(IAuthentication)
... __parent__ = None
... def __repr__(self): return 'dummy1'
>>> dummy1 = DummyUtility1()
>>> class DummyUtility2:
- ... implements(ISourceQueriables, IAuthenticationUtility)
+ ... implements(ISourceQueriables, IAuthentication)
... __parent__ = None
... def getQueriables(self):
... return ('1', 1), ('2', 2), ('3', 3)
>>> dummy2 = DummyUtility2()
>>> class DummyUtility3(DummyUtility2):
- ... implements(IAuthenticationUtility)
+ ... implements(IAuthentication)
... def getQueriables(self):
... return ('4', 4),
>>> dummy3 = DummyUtility3()
>>> from zope.app.component.testing import testingNextUtility
- >>> testingNextUtility(dummy1, dummy2, IAuthenticationUtility)
- >>> testingNextUtility(dummy2, dummy3, IAuthenticationUtility)
+ >>> testingNextUtility(dummy1, dummy2, IAuthentication)
+ >>> testingNextUtility(dummy2, dummy3, IAuthentication)
>>> temp = zapi.getUtility
>>> zapi.getUtility = lambda iface: dummy1
@@ -206,7 +206,7 @@
>>> zapi.getUtility = temp
"""
i = 0
- auth = zapi.getUtility(IAuthenticationUtility)
+ auth = zapi.getUtility(IAuthentication)
while True:
queriables = ISourceQueriables(auth, None)
if queriables is None:
@@ -214,7 +214,7 @@
else:
for qid, queriable in queriables.getQueriables():
yield unicode(i)+'.'+unicode(qid), queriable
- auth = queryNextUtility(auth, IAuthenticationUtility)
+ auth = queryNextUtility(auth, IAuthentication)
if auth is None:
break
i += 1
Modified: Zope3/branches/srichter-blow-services/src/zope/app/securitypolicy/browser/granting.pt
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/securitypolicy/browser/granting.pt 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/securitypolicy/browser/granting.pt 2005-01-17 15:30:10 UTC (rev 28853)
@@ -8,7 +8,7 @@
tal:condition="status"
tal:content="status" />
-<form action="" method="POST">
+<form action="" method="post">
<p i18n:translate="">Select a principal:</p>
<div tal:content="structure view/principal_widget">...</div>
Modified: Zope3/branches/srichter-blow-services/src/zope/app/securitypolicy/browser/granting.txt
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/securitypolicy/browser/granting.txt 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/securitypolicy/browser/granting.txt 2005-01-17 15:30:10 UTC (rev 28853)
@@ -29,11 +29,11 @@
>>> class Principal:
... def __init__(self, id, title): self.id, self.title = id, title
- >>> from zope.app.security.interfaces import IAuthenticationUtility
+ >>> from zope.app.security.interfaces import IAuthentication
>>> from zope.app.security.interfaces import PrincipalLookupError
>>> from zope.interface import implements
>>> class AuthUtility:
- ... implements(IAuthenticationUtility)
+ ... implements(IAuthentication)
... data = {'jim': Principal('jim', 'Jim Fulton'),
... 'stephan': Principal('stephan', 'Stephan Richter')}
...
@@ -48,7 +48,7 @@
... for principal in self.data.values()
... if search in principal.title]
- >>> ztapi.provideUtility(IAuthenticationUtility, AuthUtility())
+ >>> ztapi.provideUtility(IAuthentication, AuthUtility())
- Security-related Adapters
@@ -91,7 +91,7 @@
>>> from zope.app.security.browser.auth import AuthUtilitySearchView
>>> from zope.app.form.browser.interfaces import ISourceQueryView
- >>> ztapi.browserViewProviding(IAuthenticationUtility,
+ >>> ztapi.browserViewProviding(IAuthentication,
... AuthUtilitySearchView,
... ISourceQueryView)
Modified: Zope3/branches/srichter-blow-services/src/zope/app/securitypolicy/tests/test_principalpermissionmanager.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/securitypolicy/tests/test_principalpermissionmanager.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/securitypolicy/tests/test_principalpermissionmanager.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -22,7 +22,7 @@
from zope.app.testing.placelesssetup import PlacelessSetup
from zope.app.security.interfaces import IPermission
-from zope.app.security.interfaces import IAuthenticationUtility
+from zope.app.security.interfaces import IAuthentication
from zope.app.security.permission import Permission
from zope.app.security.settings import Allow, Deny, Unset
@@ -41,7 +41,7 @@
def setUp(self):
super(Test, self).setUp()
- ztapi.provideUtility(IAuthenticationUtility, principalRegistry)
+ ztapi.provideUtility(IAuthentication, principalRegistry)
def _make_principal(self, id=None, title=None):
Modified: Zope3/branches/srichter-blow-services/src/zope/app/securitypolicy/tests/test_principalrolemanager.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/securitypolicy/tests/test_principalrolemanager.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/securitypolicy/tests/test_principalrolemanager.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -21,7 +21,7 @@
from zope.app.testing import ztapi
from zope.app.testing.placelesssetup import PlacelessSetup
-from zope.app.security.interfaces import IAuthenticationUtility
+from zope.app.security.interfaces import IAuthentication
from zope.app.security.settings import Allow, Deny
from zope.app.security.principalregistry import principalRegistry
@@ -38,7 +38,7 @@
def setUp(self):
super(Test, self).setUp()
- ztapi.provideUtility(IAuthenticationUtility, principalRegistry)
+ ztapi.provideUtility(IAuthentication, principalRegistry)
def _make_principal(self, id=None, title=None):
p = principalRegistry.definePrincipal(
Modified: Zope3/branches/srichter-blow-services/src/zope/app/securitypolicy/tests/test_securitydirectives.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/securitypolicy/tests/test_securitydirectives.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/securitypolicy/tests/test_securitydirectives.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -26,7 +26,7 @@
from zope.app.testing.placelesssetup import PlacelessSetup
from zope.app.security.interfaces import IPermission
-from zope.app.security.interfaces import IAuthenticationUtility
+from zope.app.security.interfaces import IAuthentication
from zope.app.security.permission import Permission
from zope.app.security.settings import Allow
from zope.app.security.principalregistry import principalRegistry
@@ -52,7 +52,7 @@
def setUp(self):
super(TestBase, self).setUp()
- ztapi.provideUtility(IAuthenticationUtility, principalRegistry)
+ ztapi.provideUtility(IAuthentication, principalRegistry)
class TestRoleDirective(TestBase, unittest.TestCase):
Modified: Zope3/branches/srichter-blow-services/src/zope/app/securitypolicy/zopepolicy.txt
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/securitypolicy/zopepolicy.txt 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/securitypolicy/zopepolicy.txt 2005-01-17 15:30:10 UTC (rev 28853)
@@ -75,7 +75,7 @@
>>> interaction.checkPermission('P1', ob)
False
-But CheckerPublic permission id always have a permission:
+Note, however, that we always have the CheckerPublic permission:
>>> from zope.security.checker import CheckerPublic
>>> interaction.checkPermission(CheckerPublic, ob)
@@ -496,16 +496,16 @@
For our examples here, we'll create and register a stub principal
authentication service:
- >>> from zope.app.security.interfaces import IAuthenticationUtility
+ >>> from zope.app.security.interfaces import IAuthentication
>>> class FauxPrincipals(dict):
- ... zope.interface.implements(IAuthenticationUtility)
+ ... zope.interface.implements(IAuthentication)
... def getPrincipal(self, id):
... return self[id]
>>> auth = FauxPrincipals()
>>> from zope.app.testing import ztapi
- >>> ztapi.provideUtility(IAuthenticationUtility, auth)
+ >>> ztapi.provideUtility(IAuthentication, auth)
>>> from zope.app import zapi
Let's define a group:
Added: Zope3/branches/srichter-blow-services/src/zope/app/site/__init__.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/site/__init__.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/site/__init__.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -0,0 +1 @@
+# Import this
Added: Zope3/branches/srichter-blow-services/src/zope/app/site/interfaces.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/site/interfaces.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/site/interfaces.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -0,0 +1,16 @@
+from zope.app.component.interfaces import registration
+
+class ILocalService(registration.IRegisterable):
+ """A local service isn't a local service if it doesn't implement this.
+
+ The contract of a local service includes collaboration with
+ services above it. A local service should also implement
+ IRegisterable (which implies that it is adaptable to
+ IRegistered). Implementing ILocalService implies this.
+ """
+class ISimpleService(ILocalService):
+ """Most local services should implement this instead of ILocalService.
+
+ It implies a specific way of implementing IRegisterable,
+ by subclassing IAttributeRegisterable.
+ """
Added: Zope3/branches/srichter-blow-services/src/zope/app/site/meta.zcml
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/site/meta.zcml 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/site/meta.zcml 2005-01-17 15:30:10 UTC (rev 28853)
@@ -0,0 +1,36 @@
+<configure
+ xmlns:meta="http://namespaces.zope.org/meta">
+
+ <meta:directives namespace="http://namespaces.zope.org/zope">
+
+ <meta:complexDirective
+ name="localService"
+ schema="zope.app.component.metadirectives.IClassDirective"
+ handler=".metaconfigure.LocalServiceDirective"
+ >
+
+ <meta:subdirective
+ name="implements"
+ schema="zope.app.component.metadirectives.IImplementsSubdirective"
+ />
+
+ <meta:subdirective
+ name="require"
+ schema="zope.app.component.metadirectives.IRequireSubdirective"
+ />
+
+ <meta:subdirective
+ name="allow"
+ schema="zope.app.component.metadirectives.IAllowSubdirective"
+ />
+
+ <meta:subdirective
+ name="factory"
+ schema="zope.app.component.metadirectives.IFactorySubdirective"
+ />
+
+ </meta:complexDirective>
+
+ </meta:directives>
+
+</configure>
Added: Zope3/branches/srichter-blow-services/src/zope/app/site/metaconfigure.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/site/metaconfigure.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/site/metaconfigure.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -0,0 +1,30 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation 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.
+#
+##############################################################################
+"""Local Service Directive
+
+$Id$
+"""
+__docformat__ = "reStructuredText"
+from zope.interface import classImplements
+from zope.app.component.contentdirective import ContentDirective
+
+from interfaces import ISimpleService
+
+
+class LocalServiceDirective(ContentDirective):
+
+ def __init__(self, _context, class_):
+ if not ISimpleService.implementedBy(class_):
+ classImplements(class_, ISimpleService)
+ super(LocalServiceDirective, self).__init__(_context, class_)
Added: Zope3/branches/srichter-blow-services/src/zope/app/site/tests/__init__.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/site/tests/__init__.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/site/tests/__init__.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -0,0 +1,6 @@
+# BBB: Goes away in 3.3
+
+from zope.app.component.testing import PlacefulSetup
+
+def test_suite():
+ return None
Added: Zope3/branches/srichter-blow-services/src/zope/app/site/tests/placefulsetup.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/site/tests/placefulsetup.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/site/tests/placefulsetup.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -0,0 +1 @@
+from zope.app.component.testing import *
Copied: Zope3/branches/srichter-blow-services/src/zope/app/table (from rev 28844, Zope3/trunk/src/zope/app/table)
Modified: Zope3/branches/srichter-blow-services/src/zope/app/workflow/browser/configure.zcml
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/workflow/browser/configure.zcml 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/workflow/browser/configure.zcml 2005-01-17 15:30:10 UTC (rev 28853)
@@ -45,7 +45,7 @@
name="contents.html" />
<!-- ProcessInstanceContainerAdaptable
- XXX Commented Out .. is just a demo
+ Note: Commented Out ... it is just a demo
<pages
for="zope.app.workflow.interfaces.IProcessInstanceContainerAdaptable"
permission="zope.workflow.UseProcessInstances"
Modified: Zope3/branches/srichter-blow-services/src/zope/app/workflow/stateful/browser/configure.zcml
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/workflow/stateful/browser/configure.zcml 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/workflow/stateful/browser/configure.zcml 2005-01-17 15:30:10 UTC (rev 28853)
@@ -160,7 +160,7 @@
factory=".filteradapter.FilterAdapter"
provides=".interfaces.IContentFilterAdapter"
for="zope.app.annotation.interfaces.IAttributeAnnotatable"
- permission="zope.View" /> <!-- XXX is this permission right? -->
+ permission="zope.View" /> <!-- TODO: is this permission right? -->
<!--include file="testobject.zcml"/-->
Modified: Zope3/branches/srichter-blow-services/src/zope/app/workflow/stateful/browser/contentworkflow_registry.pt
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/workflow/stateful/browser/contentworkflow_registry.pt 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/workflow/stateful/browser/contentworkflow_registry.pt 2005-01-17 15:30:10 UTC (rev 28853)
@@ -16,7 +16,7 @@
This screen let's you specify which content types (by interface) can
receive which workflows (process definitions).</p>
- <form action="." method="POST">
+ <form action="." method="post">
<h3 i18n:translate="">Available Mappings</h3>
<ul tal:condition="view/process_based">
@@ -72,4 +72,4 @@
</div>
</body>
-</html>
\ No newline at end of file
+</html>
Modified: Zope3/branches/srichter-blow-services/src/zope/app/workflow/stateful/browser/definition_edit.pt
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/workflow/stateful/browser/definition_edit.pt 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/workflow/stateful/browser/definition_edit.pt 2005-01-17 15:30:10 UTC (rev 28853)
@@ -12,7 +12,7 @@
tal:condition="status"
tal:content="status" />
- <form action="./@@edit.html" method="POST">
+ <form action="./@@edit.html" method="post">
<h3 i18n:translate="">Set Workflow-Relevant Data Schema</h3>
<div class="row" tal:define="widget nocall:view/relevantDataSchema_widget">
Modified: Zope3/branches/srichter-blow-services/src/zope/app/workflow/stateful/browser/instance_manage.pt
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/workflow/stateful/browser/instance_manage.pt 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/workflow/stateful/browser/instance_manage.pt 2005-01-17 15:30:10 UTC (rev 28853)
@@ -65,7 +65,7 @@
tal:condition="status"
tal:content="status" />
- <form name="." method="POST">
+ <form name="." method="post">
<div metal:use-macro="context/@@form_macros/widget_rows" />
Modified: Zope3/branches/srichter-blow-services/src/zope/app/zapi/README.txt
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/zapi/README.txt 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/zapi/README.txt 2005-01-17 15:30:10 UTC (rev 28853)
@@ -18,19 +18,19 @@
Traceback (most recent call last):
...
ComponentLookupError:
- (<InterfaceClass zope.app.security.interfaces.IAuthenticationUtility>, '')
+ (<InterfaceClass zope.app.security.interfaces.IAuthentication>, '')
But if we provide an authentication service:
>>> import zope.interface
- >>> from zope.app.security.interfaces import IAuthenticationUtility
+ >>> from zope.app.security.interfaces import IAuthentication
>>> class FakeAuthenticationUtility:
- ... zope.interface.implements(IAuthenticationUtility)
+ ... zope.interface.implements(IAuthentication)
>>> fake = FakeAuthenticationUtility()
>>> from zope.app.testing import ztapi
- >>> ztapi.provideUtility(IAuthenticationUtility, fake)
+ >>> ztapi.provideUtility(IAuthentication, fake)
Then we should be able to get the service back when we ask for the
principals:
Modified: Zope3/branches/srichter-blow-services/src/zope/app/zapi/__init__.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/zapi/__init__.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/zapi/__init__.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -41,6 +41,6 @@
name = getName
def principals():
- from zope.app.security.interfaces import IAuthenticationUtility
- return getUtility(IAuthenticationUtility)
+ from zope.app.security.interfaces import IAuthentication
+ return getUtility(IAuthentication)
Modified: Zope3/branches/srichter-blow-services/src/zope/app/zptpage/browser/inlinecode.pt
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/app/zptpage/browser/inlinecode.pt 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/app/zptpage/browser/inlinecode.pt 2005-01-17 15:30:10 UTC (rev 28853)
@@ -5,7 +5,7 @@
<div metal:define-macro="body">
- <form action="." tal:attributes="action request/URL" method="POST"
+ <form action="." tal:attributes="action request/URL" method="post"
enctype="multipart/form-data">
<div metal:define-macro="formbody">
Modified: Zope3/branches/srichter-blow-services/src/zope/component/README.txt
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/component/README.txt 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/component/README.txt 2005-01-17 15:30:10 UTC (rev 28853)
@@ -3,7 +3,7 @@
This package, together with `zope.interface`, provides facilities for
defining, registering and looking up components. There are two basic
-kinds of components, adapters and utilities.
+kinds of components: adapters and utilities.
Utilities
---------
@@ -93,7 +93,7 @@
... print "Hello", self.person.name
The class defines a constructor that takes an argument for every
-object adapted.
+object adapted.
We use `zope.component.adapts` to declare what we adapt. If we
declare the interfaces adapted and if we provide only one interface,
@@ -107,7 +107,7 @@
>>> class Person:
... zope.interface.implements(IPerson)
- ...
+ ...
... def __init__(self, name):
... self.name = name
@@ -131,7 +131,7 @@
... name = "Ted"
>>> zope.component.provideAdapter(
- ... factory=TedPersonGreeter, adapts=[IPerson],
+ ... factory=TedPersonGreeter, adapts=[IPerson],
... provides=IGreeter, name='ted')
For named adapters, use `queryAdapter`, or `getAdapter`:
@@ -141,7 +141,7 @@
>>> zope.component.getAdapter(Person("Sally"), IGreeter, 'ted').greet()
Hello Sally my name is Ted
-
+
If an adapter can't be found, `queryAdapter` returns a default value
and `getAdapter` raises an error:
@@ -181,6 +181,6 @@
.. [1] CAUTION: This API should only be used from test or
- application-setup code. This api shouldn't be used by regular
+ application-setup code. This API shouldn't be used by regular
library modules, as component registration is a configuration
- activity.
+ activity.
Modified: Zope3/branches/srichter-blow-services/src/zope/component/bbb/service.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/component/bbb/service.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/component/bbb/service.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -165,5 +165,8 @@
return GlobalServiceManager('serviceManager', __name__, sitemanager)
def defineService(name, interface, sitemanager=None):
- __getSM().defineService(name, interface)
+ if sitemanager is None:
+ from zope.component.site import globalSiteManager
+ sitemanager = globalSiteManager
+ __getSM(sitemanager).defineService(name, interface)
Modified: Zope3/branches/srichter-blow-services/src/zope/event/README.txt
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/event/README.txt 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/event/README.txt 2005-01-17 15:30:10 UTC (rev 28853)
@@ -4,12 +4,12 @@
This package provides a simple event system on which
application-specific event systems can be built.
-Application code can generate events without be concerned about the
+Application code can generate events without being concerned about the
event-processing frameworks that might handle the events.
Events are objects that represent something happening in a system.
They are used to extend processing by providing processing plug
-points.
+points.
The package has a list of subscribers. Application code can manage
subscriptions by manipulating this list. For the examples here, we'll
Modified: Zope3/branches/srichter-blow-services/src/zope/interface/README.txt
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/interface/README.txt 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/interface/README.txt 2005-01-17 15:30:10 UTC (rev 28853)
@@ -6,7 +6,7 @@
Interfaces are objects that specify (document) the external behavior
of objects that "provide" them. An interface specifies behavior
-through:
+through:
- Informal documentation in a doc string
@@ -42,18 +42,18 @@
>>> type(IFoo)
<class 'zope.interface.interface.InterfaceClass'>
-
-We can ask for the interfaces documentation::
+We can ask for the interface's documentation::
+
>>> IFoo.__doc__
'Foo blah blah'
-and it's name::
+and its name::
>>> IFoo.__name__
'IFoo'
-and even it's module::
+and even its module::
>>> IFoo.__module__
'__main__'
@@ -158,7 +158,7 @@
...
... def __init__(self, x=None):
... self.x = x
- ...
+ ...
... def bar(self, q, r=None):
... return q, r, self.x
...
@@ -173,7 +173,7 @@
>>> IFoo.implementedBy(Foo)
True
-
+
And we can ask whether an interface is provided by an object::
>>> foo = Foo()
@@ -220,7 +220,7 @@
...
... def __call__(x=None):
... """Create a foo
- ...
+ ...
... The argument provides the initial value for x ...
... """
@@ -246,7 +246,7 @@
...
... def __init__(self, x=None):
... self.x = x
- ...
+ ...
... def bar(self, q, r=None):
... return q, r, self.x
...
@@ -380,7 +380,7 @@
>>> class Special2(Foo):
... zope.interface.implementsOnly(
... zope.interface.implementedBy(Foo),
- ... ISpecial,
+ ... ISpecial,
... )
... reason = 'I just am'
... def brag(self):
@@ -402,7 +402,7 @@
>>> class IBlat(zope.interface.Interface):
... """Blat blah blah"""
- ...
+ ...
... y = zope.interface.Attribute("y blah blah")
... def eek():
... """eek blah blah"""
@@ -414,11 +414,11 @@
... """Baz blah"""
... def eek(a=1):
... """eek in baz blah"""
- ...
+ ...
>>> IBaz.__bases__
(<InterfaceClass __main__.IFoo>, <InterfaceClass __main__.IBlat>)
-
+
>>> names = list(IBaz)
>>> names.sort()
>>> names
@@ -472,7 +472,7 @@
inherited from the most specific interface. For example, with:
>>> class IBase(zope.interface.Interface):
- ...
+ ...
... def foo():
... "base foo doc"
@@ -480,7 +480,7 @@
... pass
>>> class IBase2(IBase):
- ...
+ ...
... def foo():
... "base2 foo doc"
@@ -531,10 +531,10 @@
that lists the specification and all of it's ancestors:
>>> baz_implements.__sro__
- (<implementedBy __main__.Baz>,
- <InterfaceClass __main__.IBaz>,
- <InterfaceClass __main__.IFoo>,
- <InterfaceClass __main__.IBlat>,
+ (<implementedBy __main__.Baz>,
+ <InterfaceClass __main__.IBaz>,
+ <InterfaceClass __main__.IFoo>,
+ <InterfaceClass __main__.IBlat>,
<InterfaceClass zope.interface.Interface>)
@@ -625,7 +625,7 @@
Invalid: [RangeError(Range(2, 1))]
And the list will be filled with the individual exceptions::
-
+
>>> errors
[RangeError(Range(2, 1))]
Modified: Zope3/branches/srichter-blow-services/src/zope/interface/adapter.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/interface/adapter.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/interface/adapter.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -625,15 +625,15 @@
# This dictionary is used to catch situations specific adapters
# override less specific adapters.
- # Because subscriptions are cummulative, registered doesn't apply.
+ # Because subscriptions are cumulative, registered doesn't apply.
registered = {}
# Add adapters and interfaces directly implied by same:
for key, value in adapters.iteritems():
- # TODO: Backward compatability
- # Don't need to handle 3-tuples some day
+ # TODO: Backward compatibility
+ # BBB ? Don't need to handle 3-tuples some day
try:
(subscription, with, name, target) = key
except ValueError:
Modified: Zope3/branches/srichter-blow-services/src/zope/schema/README.txt
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/schema/README.txt 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/schema/README.txt 2005-01-17 15:30:10 UTC (rev 28853)
@@ -17,7 +17,7 @@
Zope 3 schemas were born when Jim Fulton and Martijn Faassen thought
about Formulator for Zope 3 and PropertySets while at the `Zope 3
sprint`_ at the `Zope BBQ`_ in Berlin. They realized that if you strip
-all view logic from forms than you have something to interfaces. And
+all view logic from forms then you have something similar to interfaces. And
thus schemas were born.
.. _Zope 3 sprint: http://dev.zope.org/Zope3/ZopeBBQ2002Sprint
Copied: Zope3/branches/srichter-blow-services/src/zope/testing/formparser.py (from rev 28844, Zope3/trunk/src/zope/testing/formparser.py)
Property changes on: Zope3/branches/srichter-blow-services/src/zope/testing/formparser.py
___________________________________________________________________
Name: svn:mime-type
+ text/x-python
Name: svn:eol-style
+ native
Copied: Zope3/branches/srichter-blow-services/src/zope/testing/formparser.txt (from rev 28844, Zope3/trunk/src/zope/testing/formparser.txt)
Property changes on: Zope3/branches/srichter-blow-services/src/zope/testing/formparser.txt
___________________________________________________________________
Name: svn:mime-type
+ text/plain
Name: svn:eol-style
+ native
Modified: Zope3/branches/srichter-blow-services/src/zope/testing/tests.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zope/testing/tests.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zope/testing/tests.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -16,11 +16,12 @@
$Id$
"""
import unittest
-from zope.testing.doctestunit import DocTestSuite
+from zope.testing.doctestunit import DocTestSuite, DocFileSuite
def test_suite():
return unittest.TestSuite((
+ DocFileSuite('formparser.txt'),
DocTestSuite('zope.testing.loggingsupport'),
))
Modified: Zope3/branches/srichter-blow-services/src/zwiki/browser/add.pt
===================================================================
--- Zope3/branches/srichter-blow-services/src/zwiki/browser/add.pt 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zwiki/browser/add.pt 2005-01-17 15:30:10 UTC (rev 28853)
@@ -2,7 +2,7 @@
<body>
<div metal:fill-slot="body">
-<form action="action.html" method="POST">
+<form action="action.html" method="post">
<table class="TypeListing" cellpadding="3">
<caption>Add Content</caption>
Modified: Zope3/branches/srichter-blow-services/src/zwiki/browser/wikipage.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zwiki/browser/wikipage.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zwiki/browser/wikipage.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -126,7 +126,7 @@
def render(self):
"""Render the wiki page source."""
source = zapi.createObject(None, self.context.type, self.context.source)
- view = zapi.getView(removeAllProxies(source), '', self.request)
+ view = zapi.getMultiAdapter((removeAllProxies(source), self.request))
html = view.render()
html = self.renderWikiLinks(html)
return html
@@ -136,7 +136,8 @@
for name, comment in self.context.items():
dc = DublinCoreViews(comment, self.request)
source = zapi.createObject(None, comment.type, comment.source)
- view = zapi.getView(removeAllProxies(source), '', self.request)
+ view = zapi.getMultiAdapter(
+ (removeAllProxies(source), self.request))
result.append({
'name': name,
'title': comment.title,
Modified: Zope3/branches/srichter-blow-services/src/zwiki/traversal.py
===================================================================
--- Zope3/branches/srichter-blow-services/src/zwiki/traversal.py 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/src/zwiki/traversal.py 2005-01-17 15:30:10 UTC (rev 28853)
@@ -15,7 +15,6 @@
"""
from zope.interface import implements
from zope.proxy import removeAllProxies
-from zope.component import getDefaultViewName, queryView
from zope.publisher.interfaces import IPublishTraverse
from zope.publisher.interfaces import NotFound
from zope.app.traversing.interfaces import TraversalError
@@ -43,7 +42,7 @@
if page is None or \
not zapi.getName(self.context) in IWikiPageHierarchy(page).parents:
- view = queryView(self.context, name, request)
+ view = zapi.queryMultiAdapter((self.context, request), name=name)
if view is not None:
return view
@@ -54,7 +53,7 @@
def browserDefault(self, request):
c = self.context
- view_name = getDefaultViewName(c, request)
+ view_name = zapi.getDefaultViewName(c, request)
view_uri = "@@%s" % view_name
return c, (view_uri,)
Modified: Zope3/branches/srichter-blow-services/zopeskel/bin/debugzope.in
===================================================================
--- Zope3/branches/srichter-blow-services/zopeskel/bin/debugzope.in 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/zopeskel/bin/debugzope.in 2005-01-17 15:30:10 UTC (rev 28853)
@@ -20,7 +20,7 @@
import sys
-SOFTWARE_HOME = "<<SOFTWARE_HOME>>"
+SOFTWARE_HOME = r"<<SOFTWARE_HOME>>"
INSTANCE_HOME = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
CONFIG_FILE = os.path.join(INSTANCE_HOME, "etc", "zope.conf")
Modified: Zope3/branches/srichter-blow-services/zopeskel/bin/i18nextract.in
===================================================================
--- Zope3/branches/srichter-blow-services/zopeskel/bin/i18nextract.in 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/zopeskel/bin/i18nextract.in 2005-01-17 15:30:10 UTC (rev 28853)
@@ -45,8 +45,8 @@
"""
import os, sys, getopt
-SOFTWARE_HOME = "<<SOFTWARE_HOME>>"
-INSTANCE_HOME = "<<INSTANCE_HOME>>"
+SOFTWARE_HOME = r"<<SOFTWARE_HOME>>"
+INSTANCE_HOME = r"<<INSTANCE_HOME>>"
def usage(code, msg=''):
# Python 2.1 required
Modified: Zope3/branches/srichter-blow-services/zopeskel/bin/pyskel.in
===================================================================
--- Zope3/branches/srichter-blow-services/zopeskel/bin/pyskel.in 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/zopeskel/bin/pyskel.in 2005-01-17 15:30:10 UTC (rev 28853)
@@ -29,8 +29,8 @@
import sys, os, re
from types import ModuleType
-SOFTWARE_HOME = "<<SOFTWARE_HOME>>"
-INSTANCE_HOME = "<<INSTANCE_HOME>>"
+SOFTWARE_HOME = r"<<SOFTWARE_HOME>>"
+INSTANCE_HOME = r"<<INSTANCE_HOME>>"
sys.path.insert(0, os.getcwd())
Modified: Zope3/branches/srichter-blow-services/zopeskel/bin/test.in
===================================================================
--- Zope3/branches/srichter-blow-services/zopeskel/bin/test.in 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/zopeskel/bin/test.in 2005-01-17 15:30:10 UTC (rev 28853)
@@ -21,7 +21,7 @@
here = os.path.dirname(os.path.realpath(__file__))
here = os.path.dirname(here)
-SOFTWARE_HOME = "<<SOFTWARE_HOME>>"
+SOFTWARE_HOME = r"<<SOFTWARE_HOME>>"
instance_lib = os.path.join(here, "lib", "python")
Modified: Zope3/branches/srichter-blow-services/zopeskel/bin/zopectl.in
===================================================================
--- Zope3/branches/srichter-blow-services/zopeskel/bin/zopectl.in 2005-01-17 00:21:45 UTC (rev 28852)
+++ Zope3/branches/srichter-blow-services/zopeskel/bin/zopectl.in 2005-01-17 15:30:10 UTC (rev 28853)
@@ -20,7 +20,7 @@
import sys
-SOFTWARE_HOME = "<<SOFTWARE_HOME>>"
+SOFTWARE_HOME = r"<<SOFTWARE_HOME>>"
INSTANCE_HOME = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
CONFIG_FILE = os.path.join(INSTANCE_HOME, "etc", "zdaemon.conf")
More information about the Zope3-Checkins
mailing list