[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