[Checkins] SVN: megrok.trails/trunk/ Continued development on "megrok.trails". Destroyed the central Trail

Brandon Rhodes brandon at rhodesmill.org
Mon Oct 15 00:42:13 EDT 2007


Log message for revision 80873:
  Continued development on "megrok.trails".  Destroyed the central Trail
  registry which the initial version contained, in favor of a factory
  that produces AbsoluteURL factories, which makes things much simpler
  and requires no global storage beyond the adapter registry itself.
  Added the beginnings of a functional test suite.
  
  And, most importantly from the point of view of people using the
  prototype, it should now work when you ask for the URL of a view atop
  a class to which a Trail exists.  Before, it would only work when the
  URL was requested for the object itself, because IAbsoluteURL adapters
  were created but not named "absolute_url" adapters.  It turns out that
  Zope sometimes uses one, and sometimes the other.  This is quite
  mysterious to me.  I will have to read more about named adapters
  before I understand why they are used intead of interface-identified
  adapters.
  

Changed:
  A   megrok.trails/trunk/TODO.txt
  U   megrok.trails/trunk/src/megrok/trails/__init__.py
  U   megrok.trails/trunk/src/megrok/trails/components.py
  A   megrok.trails/trunk/src/megrok/trails/ftesting.zcml
  A   megrok.trails/trunk/src/megrok/trails/ftests/
  A   megrok.trails/trunk/src/megrok/trails/ftests/__init__.py
  A   megrok.trails/trunk/src/megrok/trails/ftests/test_trails_functional.py
  A   megrok.trails/trunk/src/megrok/trails/ftests/traversal/
  A   megrok.trails/trunk/src/megrok/trails/ftests/traversal/__init__.py
  A   megrok.trails/trunk/src/megrok/trails/ftests/traversal/traversal.py
  A   megrok.trails/trunk/src/megrok/trails/ftests/url/
  A   megrok.trails/trunk/src/megrok/trails/ftests/url/__init__.py
  A   megrok.trails/trunk/src/megrok/trails/ftests/url/url.py

-=-
Added: megrok.trails/trunk/TODO.txt
===================================================================
--- megrok.trails/trunk/TODO.txt	                        (rev 0)
+++ megrok.trails/trunk/TODO.txt	2007-10-15 04:42:12 UTC (rev 80873)
@@ -0,0 +1,13 @@
+
+ - I am not sure whether the functional tests are written correctly;
+   in particular, I have no idea what to do with the first few entries
+   in "ftesting.zcml", and largely copied them from the Grok
+   functional testing suite without understanding them.
+
+ - The design needs a Grokker; Trails should register themselves not
+   at creation time, but at grokking time, so they can register
+   themselves as local rather than global utilities.  They should put
+   themselves atop the site they live on, if possible.  Should they
+   insist on living on contained objects?
+
+ - Many more tests are necessary.

Modified: megrok.trails/trunk/src/megrok/trails/__init__.py
===================================================================
--- megrok.trails/trunk/src/megrok/trails/__init__.py	2007-10-14 17:45:51 UTC (rev 80872)
+++ megrok.trails/trunk/src/megrok/trails/__init__.py	2007-10-15 04:42:12 UTC (rev 80873)
@@ -1,6 +1,6 @@
 ##############################################################################
 #
-# Copyright (c) 2006-2007 Zope Corporation and Contributors.
+# Copyright (c) 2007 Zope Corporation and Contributors.
 # All Rights Reserved.
 #
 # This software is subject to the provisions of the Zope Public License,

Modified: megrok.trails/trunk/src/megrok/trails/components.py
===================================================================
--- megrok.trails/trunk/src/megrok/trails/components.py	2007-10-14 17:45:51 UTC (rev 80872)
+++ megrok.trails/trunk/src/megrok/trails/components.py	2007-10-15 04:42:12 UTC (rev 80873)
@@ -1,6 +1,6 @@
 ##############################################################################
 #
-# Copyright (c) 2006-2007 Zope Corporation and Contributors.
+# Copyright (c) 2007 Zope Corporation and Contributors.
 # All Rights Reserved.
 #
 # This software is subject to the provisions of the Zope Public License,
@@ -16,51 +16,59 @@
 
 import grok
 import urllib
+from zope.interface import implements
 from zope.component import provideAdapter
 from zope.traversing.browser.interfaces import IAbsoluteURL
 from zope.publisher.interfaces.http import IHTTPRequest
 from zope.publisher.browser import BrowserView
 
+#
 
-class TrailRegistry(object):
-    """Remembers which Trails have been defined for which classes.
+_safe = '@+' # Characters that we don't want to have quoted
 
-    When Zope tries to determine the URL of an object
-    and asks our TrailAbsoluteURL adapter for its opinion,
-    it consults an instance of this registry which stores,
-    for each class for which a Trail has been defined,
-    the corresponding Trail.
+class TrailAbsoluteURLFactory(object):
+    """Factory that allows Trails to provide AbsoluteURLs.
 
-    """
-    def __init__(self):
-        self.classes = {}
+    The goal is that each time the programmer creates a Trail, an
+    adapter will be registered that allows the objects at the end of
+    the Trail to have the URL described by the Trail's arguments.
 
-    def register(self, pattern):
-        self.classes[pattern.cls] = pattern
+    For example, if one creates a Trail('/person/:name', Person) and,
+    later, a page template calls view.url(person), then an adapter
+    should be available for IAbsoluteURL that will construct the URL
+    '/person/Edward' or whatever.
 
-    def __getitem__(self, key):
-        return self.classes[key]
+    Therefore, the code below calls this *class* like:
 
-# At the moment, we keep a single global registry here in the module.
-# Someday we might wish to make this a local utility inside of each
-# Grok site, so that different sites can return different URLs for the
-# same sorts of object.
-    
-_registry = TrailRegistry()
+        TrialAbsoluteURLFactory(trail)
 
+    in order to return an *instance* of this class that the Zope
+    multi-adapter component logic will later call like:
 
-_safe = '@+' # Characters that we don't want to have quoted
+        instance(obj, request)
 
+    at which point we will return an actual instance of
+    TrailAbsoluteURL, on which we have installed the actual Trail
+    instance that it will need in order to compute its response.
+
+    """
+    def __init__(self, trail):
+        self.trail = trail
+
+    def __call__(self, *args):
+        t = TrailAbsoluteURL(*args)
+        t.trail = self.trail
+        return t
+
 class TrailAbsoluteURL(BrowserView):
     """Return the Absolute URL of an object for which a Trail is defined."""
+    implements(IAbsoluteURL)
 
     def __unicode__(self):
         return urllib.unquote(self.__str__()).decode('utf-8')
 
     def __str__(self):
-        cls = type(self.context)
-        pattern = _registry[cls]
-        return pattern.url(self.context, self.request)
+        return self.trail.url(self.context, self.request)
         #url += '/' + urllib.quote(name.encode('utf-8'), _safe)
 
     __call__ = __str__
@@ -87,8 +95,10 @@
         self.spec = spec
         self.parts = spec.strip('/').split('/')
         self.cls = cls
-        _registry.register(self)
-        provideAdapter(TrailAbsoluteURL, (cls, IHTTPRequest), IAbsoluteURL)
+        tf = TrailAbsoluteURLFactory(self)
+        provideAdapter(tf, (cls, IHTTPRequest), IAbsoluteURL)
+        provideAdapter(tf, (cls, IHTTPRequest), IAbsoluteURL,
+                       name = 'absolute_url')
 
     def match(self, namelist):
         """Determine whether this Trail matches a URL.

Added: megrok.trails/trunk/src/megrok/trails/ftesting.zcml
===================================================================
--- megrok.trails/trunk/src/megrok/trails/ftesting.zcml	                        (rev 0)
+++ megrok.trails/trunk/src/megrok/trails/ftesting.zcml	2007-10-15 04:42:12 UTC (rev 80873)
@@ -0,0 +1,35 @@
+<configure
+   xmlns="http://namespaces.zope.org/zope"
+   xmlns:grok="http://namespaces.zope.org/grok"
+   i18n_domain="grok"
+   package="megrok.trails"
+   >
+
+  <include package="grok" />
+  <grok:grok package="megrok.trails.ftests" />
+
+  <securityPolicy
+      component="zope.app.securitypolicy.zopepolicy.ZopeSecurityPolicy"
+      />
+
+  <unauthenticatedPrincipal
+      id="zope.anybody"
+      title="Unauthenticated User"
+      />
+  <grant
+      permission="zope.View"
+      principal="zope.anybody"
+      />
+
+  <principal
+      id="zope.mgr"
+      title="Manager"
+      login="mgr"
+      password="mgrpw"
+      />
+
+  <role id="zope.Manager" title="Site Manager" />
+  <grantAll role="zope.Manager" />
+  <grant role="zope.Manager" principal="zope.mgr" />
+
+</configure>

Added: megrok.trails/trunk/src/megrok/trails/ftests/__init__.py
===================================================================
--- megrok.trails/trunk/src/megrok/trails/ftests/__init__.py	                        (rev 0)
+++ megrok.trails/trunk/src/megrok/trails/ftests/__init__.py	2007-10-15 04:42:12 UTC (rev 80873)
@@ -0,0 +1,2 @@
+# this is a package
+

Added: megrok.trails/trunk/src/megrok/trails/ftests/test_trails_functional.py
===================================================================
--- megrok.trails/trunk/src/megrok/trails/ftests/test_trails_functional.py	                        (rev 0)
+++ megrok.trails/trunk/src/megrok/trails/ftests/test_trails_functional.py	2007-10-15 04:42:12 UTC (rev 80873)
@@ -0,0 +1,58 @@
+import re
+import unittest
+import megrok.trails
+import os.path
+
+from pkg_resources import resource_listdir
+from zope.testing import doctest, renormalizing
+from zope.app.testing.functional import (HTTPCaller, getRootFolder,
+                                         FunctionalTestSetup, sync, ZCMLLayer)
+
+ftesting_zcml = os.path.join(os.path.dirname(megrok.trails.__file__),
+                             'ftesting.zcml')
+FunctionalLayer = ZCMLLayer(ftesting_zcml, __name__, 'FunctionalLayer')
+
+def setUp(test):
+    FunctionalTestSetup().setUp()
+
+def tearDown(test):
+    FunctionalTestSetup().tearDown()
+
+checker = renormalizing.RENormalizing([
+    # Accommodate to exception wrapping in newer versions of mechanize
+    (re.compile(r'httperror_seek_wrapper:', re.M), 'HTTPError:'),
+    ])
+
+def suiteFromPackage(name):
+    files = resource_listdir(__name__, name)
+    suite = unittest.TestSuite()
+    for filename in files:
+        if not filename.endswith('.py'):
+            continue
+        if filename == '__init__.py':
+            continue
+
+        dottedname = 'megrok.trails.ftests.%s.%s' % (name, filename[:-3])
+        test = doctest.DocTestSuite(
+            dottedname, setUp=setUp, tearDown=tearDown,
+            checker=checker,
+            extraglobs=dict(http=HTTPCaller(),
+                            getRootFolder=getRootFolder,
+                            sync=sync),
+            optionflags=(doctest.ELLIPSIS+
+                         doctest.NORMALIZE_WHITESPACE+
+                         doctest.REPORT_NDIFF)
+            )
+        test.layer = FunctionalLayer
+
+        suite.addTest(test)
+    return suite
+
+def test_suite():
+    suite = unittest.TestSuite()
+    for name in ['traversal', 'url']:
+        suite.addTest(suiteFromPackage(name))
+    return suite
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')

Added: megrok.trails/trunk/src/megrok/trails/ftests/traversal/__init__.py
===================================================================
--- megrok.trails/trunk/src/megrok/trails/ftests/traversal/__init__.py	                        (rev 0)
+++ megrok.trails/trunk/src/megrok/trails/ftests/traversal/__init__.py	2007-10-15 04:42:12 UTC (rev 80873)
@@ -0,0 +1 @@
+# this is a package

Added: megrok.trails/trunk/src/megrok/trails/ftests/traversal/traversal.py
===================================================================
--- megrok.trails/trunk/src/megrok/trails/ftests/traversal/traversal.py	                        (rev 0)
+++ megrok.trails/trunk/src/megrok/trails/ftests/traversal/traversal.py	2007-10-15 04:42:12 UTC (rev 80873)
@@ -0,0 +1,67 @@
+##############################################################################
+#
+# Copyright (c) 2007 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.
+#
+##############################################################################
+"""
+
+A TrailHead is a special kind of grok.Traverser that attempts to match
+URLs against a list of URL patterns, called Trails, that it contains.
+
+  >>> getRootFolder()["app"] = App()
+
+  >>> from zope.testbrowser.testing import Browser
+  >>> browser = Browser()
+  >>> browser.addHeader('Authorization', 'Basic mgr:mgrpw')
+  >>> browser.handleErrors = False
+
+When we traverse to a mammoth URL, the name is supplied as the
+argument for its constructor.  In this example, having "Knuth" in
+place of the ":name" in the URL pattern means that the Mammoth will be
+instantiated as Mammoth(name="Knuth").
+
+  >>> browser.open("http://localhost/app/mammoth/Knuth")
+  >>> print browser.contents
+  The name of this mammoth is Knuth.
+
+"""
+
+import grok
+from megrok.trails import TrailHead, Trail
+from zope.interface import Interface, implements
+
+class IMammoth(Interface):
+    """Interface for a Mammoth."""
+
+class Mammoth(grok.Model):
+    implements(IMammoth)
+    def __init__(self, name):
+        self.name = name
+
+class MammothIndex(grok.View):
+    grok.context(Mammoth)
+    grok.name('index')
+    def render(self):
+        return 'The name of this mammoth is %s.' % self.context.name
+
+class App(grok.Application, grok.Container):
+    pass
+
+class AppTrailHead(TrailHead):
+    grok.context(App)
+    trails = [
+        Trail('/mammoth/:name', Mammoth),
+        ]
+    #def __init__(self, *args):
+    #    self.trails = [
+    #        Trail('/mammoth/:name', Mammoth),
+    #        ]
+    #    TrailHead.__init__(self, *args)

Added: megrok.trails/trunk/src/megrok/trails/ftests/url/__init__.py
===================================================================
--- megrok.trails/trunk/src/megrok/trails/ftests/url/__init__.py	                        (rev 0)
+++ megrok.trails/trunk/src/megrok/trails/ftests/url/__init__.py	2007-10-15 04:42:12 UTC (rev 80873)
@@ -0,0 +1 @@
+# this is a package

Added: megrok.trails/trunk/src/megrok/trails/ftests/url/url.py
===================================================================
--- megrok.trails/trunk/src/megrok/trails/ftests/url/url.py	                        (rev 0)
+++ megrok.trails/trunk/src/megrok/trails/ftests/url/url.py	2007-10-15 04:42:12 UTC (rev 80873)
@@ -0,0 +1,72 @@
+##############################################################################
+#
+# Copyright (c) 2007 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.
+#
+##############################################################################
+"""
+
+For each Trail you register, an IAbsoluteURL adapter is created so
+that the objects you are wrapping can be assigned useful URLs.
+
+  >>> getRootFolder()["app"] = app = App()
+  >>> from zope.app.component.hooks import setSite
+  >>> setSite(app)
+
+This should not be necessary, but at the moment is, possibly because
+the test suite groks things different than a production instance:
+
+  >>> Trail('/mammoth/:name', IMammoth) and None
+
+Anyway:
+
+  >>> from zope.testbrowser.testing import Browser
+  >>> browser = Browser()
+  >>> browser.addHeader('Authorization', 'Basic mgr:mgrpw')
+  >>> browser.handleErrors = False
+
+When constructing the URL, any parameters are pulled off of the object
+as attributes.  In the example below, the Mammoth URL is formed using
+the parameter ":name", which will be filled in using the Mammoth's
+attribute "mammoth.name".
+
+  >>> browser.open("http://localhost/app")
+  >>> print browser.contents
+  The URL of the mammoth is http://localhost/app/mammoth/Knuth.
+
+"""
+
+import grok
+from megrok.trails import TrailHead, Trail
+from zope.interface import Interface
+
+class IMammoth(Interface):
+    """Interface of a Mammoth."""
+
+class Mammoth(grok.Model):
+    grok.implements(IMammoth)
+    def __init__(self, name):
+        self.name = name
+
+class App(grok.Application, grok.Container):
+    pass
+
+class AppTrailHead(TrailHead):
+    grok.context(App)
+    trails = [
+        Trail('/mammoth/:name', IMammoth),
+        ]
+
+class AppIndex(grok.View):
+    grok.context(App)
+    grok.name('index')
+    def render(self):
+        print>>open('/tmp/foof','w'), 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', type(self.request)
+        return 'The URL of the mammoth is %s.' % self.url(Mammoth('Knuth'))



More information about the Checkins mailing list