[Checkins] SVN: grok/trunk/src/grok/ Implement traversal via @grok.traverse or grok.Traverser.

Philipp von Weitershausen philikon at philikon.de
Thu Oct 19 04:06:42 EDT 2006


Log message for revision 70797:
  Implement traversal via @grok.traverse or grok.Traverser.
  
  Also moved all utility functions from _grok to util.
  Use removeSecurityProxy instead of removeAllProxies (I still hope we can get rid of it eventually...)
  Whitespace consistency cleanup
  

Changed:
  U   grok/trunk/src/grok/__init__.py
  U   grok/trunk/src/grok/_grok.py
  U   grok/trunk/src/grok/components.py
  U   grok/trunk/src/grok/ftests/test_grok_functional.py
  A   grok/trunk/src/grok/ftests/traversal/
  A   grok/trunk/src/grok/ftests/traversal/__init__.py
  A   grok/trunk/src/grok/ftests/traversal/modeltraverse.py
  A   grok/trunk/src/grok/ftests/traversal/traverser.py
  U   grok/trunk/src/grok/util.py

-=-
Modified: grok/trunk/src/grok/__init__.py
===================================================================
--- grok/trunk/src/grok/__init__.py	2006-10-19 05:25:28 UTC (rev 70796)
+++ grok/trunk/src/grok/__init__.py	2006-10-19 08:06:38 UTC (rev 70797)
@@ -29,10 +29,11 @@
     IContainerModifiedEvent, ContainerModifiedEvent)
 
 from grok.components import Model, Adapter, MultiAdapter, View, XMLRPC
-from grok.components import PageTemplate, Utility, Container
+from grok.components import PageTemplate, Utility, Container, Traverser
 from grok.directive import context, name, template, templatedir
 from grok._grok import do_grok as grok  # Avoid name clash within _grok
 from grok._grok import SubscribeDecorator as subscribe
+from grok._grok import traverseDecorator as traverse
 from grok.error import GrokError, GrokImportError
 
 # Our __init__ provides the grok API directly so using 'import grok' is enough.

Modified: grok/trunk/src/grok/_grok.py
===================================================================
--- grok/trunk/src/grok/_grok.py	2006-10-19 05:25:28 UTC (rev 70796)
+++ grok/trunk/src/grok/_grok.py	2006-10-19 08:06:38 UTC (rev 70797)
@@ -24,7 +24,8 @@
 from zope.security.checker import (defineChecker, getCheckerForInstancesOf,
                                    NoProxy)
 from zope.publisher.interfaces.browser import (IDefaultBrowserLayer,
-                                               IBrowserRequest)
+                                               IBrowserRequest,
+                                               IBrowserPublisher)
 
 from zope.app.publisher.xmlrpc import MethodPublisher
 from zope.publisher.interfaces.xmlrpc import IXMLRPCRequest
@@ -36,19 +37,31 @@
 from grok.directive import (ClassDirectiveContext, ModuleDirectiveContext,
                             ClassOrModuleDirectiveContext,
                             TextDirective, InterfaceOrClassDirective,
-                            frame_is_module)
+                            frame_is_module, frame_is_class)
 
 
-AMBIGUOUS_CONTEXT = object()
-
-
-def do_grok(dotted_name):
+_bootstrapped = False
+def bootstrap():
+    component.provideAdapter(components.ModelTraverser)
     # register the name 'index' as the default view name
-    # TODO this needs to be moved to grok startup time (similar to ZCML-time)
     component.provideAdapter('index',
                              adapts=(grok.Model, IBrowserRequest),
                              provides=IDefaultViewName)
 
+# add a cleanup hook so that grok will bootstrap itself again whenever
+# the Component Architecture is torn down.
+def resetBootstrap():
+    global _bootstrapped
+    _bootstrapped = False
+from zope.testing.cleanup import addCleanUp
+addCleanUp(resetBootstrap)
+
+
+def do_grok(dotted_name):
+    global _bootstrapped
+    if not _bootstrapped:
+        bootstrap()
+
     module_info = scan.module_info_from_dotted_name(dotted_name)
     grok_tree(module_info)
 
@@ -78,12 +91,12 @@
 
 
 def grok_module(module_info):
-    (models, adapters, multiadapters, utilities,
-     views, xmlrpc_views, templates, subscribers) = scan_module(module_info)
+    (models, adapters, multiadapters, utilities, views, xmlrpc_views,
+     traversers, templates, subscribers) = scan_module(module_info)
 
     find_filesystem_templates(module_info, templates)
 
-    context = determine_module_context(module_info, models)
+    context = util.determine_module_context(module_info, models)
 
     register_models(models)
     register_adapters(context, adapters)
@@ -91,6 +104,7 @@
     register_utilities(utilities)
     register_views(context, views, templates)
     register_xmlrpc(context, xmlrpc_views)
+    register_traversers(context, traversers)
     register_unassociated_templates(context, templates)
     register_subscribers(subscribers)
 
@@ -102,7 +116,8 @@
             grok.MultiAdapter: [],
             grok.Utility: [],
             grok.View: [],
-            grok.XMLRPC: []
+            grok.XMLRPC: [],
+            grok.Traverser: []
             }
     templates = TemplateRegistry()
     subscribers = module_info.getAnnotation('grok.subscribers', [])
@@ -111,7 +126,7 @@
     for name in dir(module):
         obj = getattr(module, name)
 
-        if not defined_locally(obj, module_info.dotted_name):
+        if not util.defined_locally(obj, module_info.dotted_name):
             continue
 
         if isinstance(obj, grok.PageTemplate):
@@ -126,10 +141,12 @@
 
     return (components[grok.Model], components[grok.Adapter], 
             components[grok.MultiAdapter], components[grok.Utility],
-            components[grok.View], components[grok.XMLRPC], templates, subscribers)
+            components[grok.View], components[grok.XMLRPC],
+            components[grok.Traverser], templates, subscribers)
 
 def find_filesystem_templates(module_info, templates):
-    template_dir_name = module_info.getAnnotation('grok.templatedir', module_info.name)
+    template_dir_name = module_info.getAnnotation('grok.templatedir',
+                                                  module_info.name)
     template_dir = module_info.getResourcePath(template_dir_name)
     if os.path.isdir(template_dir):
         template_files = os.listdir(template_dir)
@@ -179,27 +196,27 @@
 
 def register_adapters(context, adapters):
     for factory in adapters:
-        adapter_context = determine_class_context(factory, context)
-        check_implements_one(factory)
-        name = class_annotation(factory, 'grok.name', '')
+        adapter_context = util.determine_class_context(factory, context)
+        util.check_implements_one(factory)
+        name = util.class_annotation(factory, 'grok.name', '')
         component.provideAdapter(factory, adapts=(adapter_context,), name=name)
 
 def register_multiadapters(multiadapters):
     for factory in multiadapters:
-        check_implements_one(factory)
-        check_adapts(factory)
-        name = class_annotation(factory, 'grok.name', '')
+        util.check_implements_one(factory)
+        util.check_adapts(factory)
+        name = util.class_annotation(factory, 'grok.name', '')
         component.provideAdapter(factory, name=name)
 
 def register_utilities(utilities):
     for factory in utilities:
-        check_implements_one(factory)
-        name = class_annotation(factory, 'grok.name', '')
+        util.check_implements_one(factory)
+        name = util.class_annotation(factory, 'grok.name', '')
         component.provideUtility(factory(), name=name)
 
 def register_xmlrpc(context, views):
     for view in views:
-        view_context = determine_class_context(view, context)
+        view_context = util.determine_class_context(view, context)
         candidates = [getattr(view, name) for name in dir(view)]
         methods = [c for c in candidates if inspect.ismethod(c)]
 
@@ -216,12 +233,12 @@
 
 def register_views(context, views, templates):
     for factory in views:
-        view_context = determine_class_context(factory, context)
+        view_context = util.determine_class_context(factory, context)
         factory_name = factory.__name__.lower()
 
         # find inline templates
-        template_name = class_annotation(factory, 'grok.template',
-                                             factory_name)
+        template_name = util.class_annotation(factory, 'grok.template',
+                                              factory_name)
         template = templates.get(template_name)
 
         if factory_name != template_name:
@@ -248,7 +265,7 @@
                                 "'render' method." % factory,
                                 factory)
 
-        view_name = class_annotation(factory, 'grok.name', factory_name)
+        view_name = util.class_annotation(factory, 'grok.name', factory_name)
         component.provideAdapter(factory,
                                  adapts=(view_context, IDefaultBrowserLayer),
                                  provides=interface.Interface,
@@ -257,9 +274,16 @@
         # TODO minimal security here (read: everything is public)
         defineChecker(factory, NoProxy)
 
+def register_traversers(context, traversers):
+    for factory in traversers:
+        factory_context = util.determine_class_context(factory, context)
+        component.provideAdapter(factory,
+                                 adapts=(factory_context, IBrowserRequest),
+                                 provides=IBrowserPublisher)
+
 def register_unassociated_templates(context, templates):
     for name, unassociated in templates.listUnassociatedTemplates():
-        check_context(unassociated, context)
+        util.check_context(unassociated, context)
 
         class TemplateView(grok.View):
             template = unassociated
@@ -302,55 +326,7 @@
             if not entry['associated']:
                 yield name, entry['template']
 
-def defined_locally(obj, dotted_name):
-    obj_module = getattr(obj, '__grok_module__', None)
-    if obj_module is None:
-        obj_module = getattr(obj, '__module__', None)
-    return obj_module == dotted_name
 
-def check_context(component, context):
-    if context is None:
-        raise GrokError("No module-level context for %r, please use "
-                        "grok.context." % component, component)
-    elif context is AMBIGUOUS_CONTEXT:
-        raise GrokError("Multiple possible contexts for %r, please use "
-                        "grok.context." % component, component)
-
-def check_implements_one(class_):
-    if len(list(interface.implementedBy(class_))) != 1:
-        raise GrokError("%r must implement exactly one interface "
-                        "(use grok.implements to specify)."
-                        % class_, class_)
-
-def check_adapts(class_):
-    if component.adaptedBy(class_) is None:
-        raise GrokError("%r must specify which contexts it adapts "
-                        "(use grok.adapts to specify)."
-                        % class_, class_)
-
-def determine_module_context(module_info, models):
-    if len(models) == 0:
-        context = None
-    elif len(models) == 1:
-        context = models[0]
-    else:
-        context = AMBIGUOUS_CONTEXT
-
-    module_context = module_info.getAnnotation('grok.context', None)
-    if module_context:
-        context = module_context
-
-    return context
-
-def determine_class_context(class_, module_context):
-    context = class_annotation(class_, 'grok.context', module_context)
-    check_context(class_, context)
-    return context
-
-def class_annotation(obj, name, default):
-    return getattr(obj, '__%s__' % name.replace('.', '_'), default)
-
-
 # decorators
 class SubscribeDecorator:
     def __init__(self, *args):
@@ -370,3 +346,11 @@
         if subscribers is None:
             frame.f_locals['__grok_subscribers__'] = subscribers = []
         subscribers.append((function, self.subscribed))
+
+def traverseDecorator(function):
+    frame = sys._getframe(1)
+    if not frame_is_class(frame):
+        raise GrokImportError("@grok.traverse can only be used on class "
+                              "level.")
+
+    frame.f_locals['__grok_traverse__'] = function

Modified: grok/trunk/src/grok/components.py
===================================================================
--- grok/trunk/src/grok/components.py	2006-10-19 05:25:28 UTC (rev 70796)
+++ grok/trunk/src/grok/components.py	2006-10-19 08:06:38 UTC (rev 70797)
@@ -17,11 +17,15 @@
 import persistent
 from zope import component
 from zope import interface
-from zope.proxy import removeAllProxies
+from zope.security.proxy import removeSecurityProxy
 from zope.publisher.browser import BrowserPage
+from zope.publisher.interfaces import NotFound
+from zope.publisher.interfaces.browser import (IBrowserPublisher,
+                                               IBrowserRequest)
 from zope.pagetemplate import pagetemplate
 
 from zope.app.pagetemplate.engine import TrustedAppPT
+from zope.app.publisher.browser import getDefaultViewName
 from zope.app.publisher.browser import directoryresource
 from zope.app.publisher.browser.pagetemplateresource import \
     PageTemplateResourceFactory
@@ -46,14 +50,17 @@
 class Utility(object):
     pass
 
+
 class MultiAdapter(object):
     pass
 
+
 class View(BrowserPage):
 
     def __init__(self, context, request):
-        self.context = removeAllProxies(context)
-        self.request = removeAllProxies(request)
+        # Jim would say: WAAAAAAAAAAAAH!
+        self.context = removeSecurityProxy(context)
+        self.request = removeSecurityProxy(request)
 
     def __call__(self):
         self.before()
@@ -64,9 +71,8 @@
 
         namespace = template.pt_getContext()
         namespace['request'] = self.request
-        # Jim would say: WAAAAAAAAAAAAH!
         namespace['view'] = self
-        namespace['context'] = removeAllProxies(self.context)
+        namespace['context'] = self.context
 
         module_info = template.__grok_module_info__
         directory_resource = component.queryAdapter(self.request,
@@ -87,7 +93,6 @@
     pass
 
 
-
 class PageTemplate(TrustedAppPT, pagetemplate.PageTemplate):
     expand = 0
 
@@ -142,3 +147,40 @@
         resource.__name__ = self.__name
         return resource
 
+
+class Traverser(object):
+    interface.implements(IBrowserPublisher)
+
+    def __init__(self, context, request):
+        # Jim would say: WAAAAAAAAAAAAH!
+        self.context = removeSecurityProxy(context)
+        self.request = removeSecurityProxy(request)
+
+    def browserDefault(self, request):
+        view_name = getDefaultViewName(self.context, request)
+        view_uri = "@@%s" % view_name
+        return self.context, (view_uri,)
+
+    def publishTraverse(self, request, name):
+        subob = self.traverse(name)
+        if subob:
+            return subob
+
+        view = component.queryMultiAdapter((self.context, request), name=name)
+        if view:
+            return view
+
+        raise NotFound(self.context, name, request)
+
+    def traverse(self, name):
+        # this will be overridden by subclasses
+        pass
+
+
+class ModelTraverser(Traverser):
+    component.adapts(Model, IBrowserRequest)
+
+    def traverse(self, name):
+        traverser = util.class_annotation(self.context, 'grok.traverse', None)
+        if traverser:
+            return traverser(name)

Modified: grok/trunk/src/grok/ftests/test_grok_functional.py
===================================================================
--- grok/trunk/src/grok/ftests/test_grok_functional.py	2006-10-19 05:25:28 UTC (rev 70796)
+++ grok/trunk/src/grok/ftests/test_grok_functional.py	2006-10-19 08:06:38 UTC (rev 70797)
@@ -56,7 +56,7 @@
 
 def test_suite():
     suite = unittest.TestSuite()
-    for name in ['view', 'static', 'xmlrpc']:
+    for name in ['view', 'static', 'xmlrpc', 'traversal']:
         suite.addTest(suiteFromPackage(name))
     return suite
 

Copied: grok/trunk/src/grok/ftests/traversal/__init__.py (from rev 70796, grok/trunk/src/grok/ftests/__init__.py)

Added: grok/trunk/src/grok/ftests/traversal/modeltraverse.py
===================================================================
--- grok/trunk/src/grok/ftests/traversal/modeltraverse.py	2006-10-19 05:25:28 UTC (rev 70796)
+++ grok/trunk/src/grok/ftests/traversal/modeltraverse.py	2006-10-19 08:06:38 UTC (rev 70797)
@@ -0,0 +1,50 @@
+"""
+Models can determine how they want to be traversed by using the
+``@grok.traverse`` decorator:
+
+  >>> import grok
+  >>> from grok.ftests.traversal.modeltraverse import Herd
+  >>> grok.grok('grok.ftests.traversal.modeltraverse')
+  >>> getRootFolder()["herd"] = Herd()
+
+  >>> from zope.testbrowser.testing import Browser
+  >>> browser = Browser()
+  >>> browser.handleErrors = False
+  >>> browser.open("http://localhost/herd/manfred")
+  >>> print browser.contents
+  <html>
+  <body>
+  <h1>Hello, Manfred!</h1>
+  </body>
+  </html>
+
+  >>> browser.open("http://localhost/herd/ellie")
+  >>> print browser.contents
+  <html>
+  <body>
+  <h1>Hello, Ellie!</h1>
+  </body>
+  </html>
+
+"""
+import grok
+
+class Herd(grok.Model):
+
+    @grok.traverse
+    def getMammoth(self, name):
+        return Mammoth(name)
+
+class Mammoth(grok.Model):
+
+    def __init__(self, name):
+        self.name = name
+
+grok.context(Mammoth)
+index = grok.PageTemplate("""\
+<html>
+<body>
+<h1>Hello, <span tal:replace="context/name/title" />!</h1>
+</body>
+</html>
+""")


Property changes on: grok/trunk/src/grok/ftests/traversal/modeltraverse.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: grok/trunk/src/grok/ftests/traversal/traverser.py
===================================================================
--- grok/trunk/src/grok/ftests/traversal/traverser.py	2006-10-19 05:25:28 UTC (rev 70796)
+++ grok/trunk/src/grok/ftests/traversal/traverser.py	2006-10-19 08:06:38 UTC (rev 70797)
@@ -0,0 +1,53 @@
+"""
+Apart from using the ``@grok.traverse`` decorator on a model, you can
+also create a separate traverser component:
+
+  >>> import grok
+  >>> from grok.ftests.traversal.modeltraverse import Herd
+  >>> grok.grok('grok.ftests.traversal.modeltraverse')
+  >>> getRootFolder()["herd"] = Herd()
+
+  >>> from zope.testbrowser.testing import Browser
+  >>> browser = Browser()
+  >>> browser.handleErrors = False
+  >>> browser.open("http://localhost/herd/manfred")
+  >>> print browser.contents
+  <html>
+  <body>
+  <h1>Hello, Manfred!</h1>
+  </body>
+  </html>
+
+  >>> browser.open("http://localhost/herd/ellie")
+  >>> print browser.contents
+  <html>
+  <body>
+  <h1>Hello, Ellie!</h1>
+  </body>
+  </html>
+
+"""
+import grok
+
+class Herd(grok.Model):
+    pass
+
+class HerdTraverser(grok.Traverser):
+    grok.context(Herd)
+
+    def traverse(self, name):
+        return Mammoth(name)
+
+class Mammoth(grok.Model):
+
+    def __init__(self, name):
+        self.name = name
+
+grok.context(Mammoth)
+index = grok.PageTemplate("""\
+<html>
+<body>
+<h1>Hello, <span tal:replace="context/name/title" />!</h1>
+</body>
+</html>
+""")


Property changes on: grok/trunk/src/grok/ftests/traversal/traverser.py
___________________________________________________________________
Name: svn:eol-style
   + native

Modified: grok/trunk/src/grok/util.py
===================================================================
--- grok/trunk/src/grok/util.py	2006-10-19 05:25:28 UTC (rev 70796)
+++ grok/trunk/src/grok/util.py	2006-10-19 08:06:38 UTC (rev 70797)
@@ -18,6 +18,11 @@
 import types
 import sys
 
+from zope import component
+from zope import interface
+
+from grok.error import GrokError, GrokImportError
+
 def not_unicode_or_ascii(value):
     if isinstance(value, unicode):
         return False
@@ -27,15 +32,74 @@
 
 is_not_ascii = re.compile(eval(r'u"[\u0080-\uffff]"')).search
 
+
 def isclass(obj):
     """We cannot use ``inspect.isclass`` because it will return True
     for interfaces"""
     return type(obj) in (types.ClassType, type)
 
+
 def check_subclass(obj, class_):
     if not isclass(obj):
         return False
     return issubclass(obj, class_)
 
+
 def caller_module():
     return sys._getframe(2).f_globals['__name__']
+
+
+def class_annotation(obj, name, default):
+    return getattr(obj, '__%s__' % name.replace('.', '_'), default)
+
+
+def defined_locally(obj, dotted_name):
+    obj_module = getattr(obj, '__grok_module__', None)
+    if obj_module is None:
+        obj_module = getattr(obj, '__module__', None)
+    return obj_module == dotted_name
+
+
+AMBIGUOUS_CONTEXT = object()
+def check_context(component, context):
+    if context is None:
+        raise GrokError("No module-level context for %r, please use "
+                        "grok.context." % component, component)
+    elif context is AMBIGUOUS_CONTEXT:
+        raise GrokError("Multiple possible contexts for %r, please use "
+                        "grok.context." % component, component)
+
+
+def check_implements_one(class_):
+    if len(list(interface.implementedBy(class_))) != 1:
+        raise GrokError("%r must implement exactly one interface "
+                        "(use grok.implements to specify)."
+                        % class_, class_)
+
+
+def check_adapts(class_):
+    if component.adaptedBy(class_) is None:
+        raise GrokError("%r must specify which contexts it adapts "
+                        "(use grok.adapts to specify)."
+                        % class_, class_)
+
+
+def determine_module_context(module_info, models):
+    if len(models) == 0:
+        context = None
+    elif len(models) == 1:
+        context = models[0]
+    else:
+        context = AMBIGUOUS_CONTEXT
+
+    module_context = module_info.getAnnotation('grok.context', None)
+    if module_context:
+        context = module_context
+
+    return context
+
+
+def determine_class_context(class_, module_context):
+    context = class_annotation(class_, 'grok.context', module_context)
+    check_context(class_, context)
+    return context



More information about the Checkins mailing list