[Checkins] SVN: grok/trunk/ Implement annotations, pretty much according to the original design notes.

Philipp von Weitershausen philikon at philikon.de
Fri Jan 19 18:35:03 EST 2007


Log message for revision 72111:
  Implement annotations, pretty much according to the original design notes.
  
  You inherit from grok.Annotation. The resulting class can be thought of as a
  persistent adapter. It gets instantiated the first time an object is adapted
  and then persisted as part of the object.  Like with adapters, use grok.context,
  grok.implements/grok.provides to specify what's adapted and what's provided.
  Use grok.name to specify the annotation key, which is always a good idea if
  you anticipate moving the class around, because otherwise the annotation key
  will be the dotted class path.
  

Changed:
  U   grok/trunk/TODO.txt
  U   grok/trunk/doc/design/annotations.py
  U   grok/trunk/src/grok/__init__.py
  U   grok/trunk/src/grok/components.py
  U   grok/trunk/src/grok/interfaces.py
  U   grok/trunk/src/grok/meta.py
  A   grok/trunk/src/grok/tests/annotation/
  A   grok/trunk/src/grok/tests/annotation/__init__.py
  A   grok/trunk/src/grok/tests/annotation/annotation.py
  A   grok/trunk/src/grok/tests/annotation/implementsmany.py
  A   grok/trunk/src/grok/tests/annotation/implementsnone.py
  A   grok/trunk/src/grok/tests/annotation/name.py
  A   grok/trunk/src/grok/tests/annotation/provides.py
  U   grok/trunk/src/grok/tests/test_grok.py

-=-
Modified: grok/trunk/TODO.txt
===================================================================
--- grok/trunk/TODO.txt	2007-01-19 21:58:52 UTC (rev 72110)
+++ grok/trunk/TODO.txt	2007-01-19 23:35:02 UTC (rev 72111)
@@ -13,8 +13,6 @@
 
 - choice fields / sources (theuni)
 
-- annotations (faassen)
-
 - testing strategy for the tutorial (faassen)
 
 - make it easier to write tests (wosc, faassen)

Modified: grok/trunk/doc/design/annotations.py
===================================================================
--- grok/trunk/doc/design/annotations.py	2007-01-19 21:58:52 UTC (rev 72110)
+++ grok/trunk/doc/design/annotations.py	2007-01-19 23:35:02 UTC (rev 72111)
@@ -1,14 +1,24 @@
 import grok
+from zope import interface
+from BTrees.OOBTree import OOTreeSet
 
 class Article(grok.Model):
     pass
 
+class IComments(interface.Interface):
+
+    def addComment(text):
+        pass
+
+    def getComments():
+        pass
+
 class Comments(grok.Annotation):
     grok.context(Article)  # this is actually the default
     grok.implements(IComments)
+    grok.name('annotations.Comments')  # this is actually the default
 
     def __init__(self): 
-        # XXX need super?!?
         self.comments = OOTreeSet()
 
     def addComment(self, text):

Modified: grok/trunk/src/grok/__init__.py
===================================================================
--- grok/trunk/src/grok/__init__.py	2007-01-19 21:58:52 UTC (rev 72110)
+++ grok/trunk/src/grok/__init__.py	2007-01-19 23:35:02 UTC (rev 72111)
@@ -32,7 +32,7 @@
 from grok.components import ClassGrokker, InstanceGrokker, ModuleGrokker
 from grok.components import Model, Adapter, MultiAdapter, View, XMLRPC
 from grok.components import PageTemplate, PageTemplateFile, Container, Traverser
-from grok.components import Site, GlobalUtility, LocalUtility
+from grok.components import Site, GlobalUtility, LocalUtility, Annotation
 from grok.components import EditForm, DisplayForm, AddForm
 from grok.directive import (context, name, template, templatedir, provides,
                             baseclass, global_utility, local_utility,

Modified: grok/trunk/src/grok/components.py
===================================================================
--- grok/trunk/src/grok/components.py	2007-01-19 21:58:52 UTC (rev 72110)
+++ grok/trunk/src/grok/components.py	2007-01-19 23:35:02 UTC (rev 72111)
@@ -32,6 +32,7 @@
 from zope.traversing.browser.interfaces import IAbsoluteURL
 from zope.traversing.browser.absoluteurl import AbsoluteURL
 from zope.traversing.browser.absoluteurl import _safe as SAFE_URL_CHARACTERS
+from zope.annotation.interfaces import IAttributeAnnotatable
 
 from zope.app.pagetemplate.engine import TrustedAppPT
 from zope.app.publisher.browser import getDefaultViewName
@@ -99,7 +100,7 @@
     # XXX Inheritance order is important here. If we reverse this,
     # then containers can't be models anymore because no unambigous MRO
     # can be established.
-    pass
+    interface.implements(IAttributeAnnotatable)
 
 
 class Container(BTreeContainer):
@@ -128,6 +129,10 @@
     pass
 
 
+class Annotation(persistent.Persistent):
+    pass
+
+
 class View(BrowserPage):
     interface.implements(interfaces.IGrokView)
 

Modified: grok/trunk/src/grok/interfaces.py
===================================================================
--- grok/trunk/src/grok/interfaces.py	2007-01-19 21:58:52 UTC (rev 72110)
+++ grok/trunk/src/grok/interfaces.py	2007-01-19 23:35:02 UTC (rev 72111)
@@ -28,6 +28,7 @@
     Site = interface.Attribute("Mixin class for sites.")
     Adapter = interface.Attribute("Base class for adapters.")
     MultiAdapter = interface.Attribute("Base class for multi-adapters.")
+    Annotation = interface.Attribute("Base class for persistent annotations.")
     GlobalUtility = interface.Attribute("Base class for global utilities.")
     LocalUtility = interface.Attribute("Base class for local utilities.")
     View = interface.Attribute("Base class for browser views.")

Modified: grok/trunk/src/grok/meta.py
===================================================================
--- grok/trunk/src/grok/meta.py	2007-01-19 21:58:52 UTC (rev 72110)
+++ grok/trunk/src/grok/meta.py	2007-01-19 23:35:02 UTC (rev 72111)
@@ -9,10 +9,12 @@
 from zope.security.checker import NamesChecker, defineChecker
 from zope.security.permission import Permission
 from zope.security.interfaces import IPermission
+from zope.annotation.interfaces import IAnnotations
 
+from zope.app.publisher.xmlrpc import MethodPublisher
 from zope.app.container.interfaces import IContainer
-from zope.app.publisher.xmlrpc import MethodPublisher
 from zope.app.container.interfaces import INameChooser
+from zope.app.container.contained import contained
 
 import grok
 from grok import util, components, formlib
@@ -44,7 +46,7 @@
         component.provideAdapter(factory, adapts=(adapter_context,),
                                  provides=provides,
                                  name=name)
-            
+
 class MultiAdapterGrokker(grok.ClassGrokker):
     component_class = grok.MultiAdapter
     
@@ -414,3 +416,39 @@
         for permission in permissions:
             # TODO permission title and description
             component.provideUtility(Permission(permission), name=permission)
+
+class AnnotationGrokker(grok.ClassGrokker):
+    component_class = grok.Annotation
+ 
+    def register(self, context, name, factory, module_info, templates):
+        adapter_context = util.determine_class_context(factory, context)
+        provides = util.class_annotation(factory, 'grok.provides', None)
+        if provides is None:
+            base_interfaces = interface.implementedBy(grok.Annotation)
+            factory_interfaces = interface.implementedBy(factory)
+            real_interfaces = list(factory_interfaces - base_interfaces)
+            util.check_implements_one_from_list(real_interfaces, factory)
+            provides = real_interfaces[0]
+
+        key = util.class_annotation(factory, 'grok.name', None)
+        if key is None:
+            key = factory.__module__ + '.' + factory.__name__
+
+        @component.adapter(adapter_context)
+        @interface.implementer(provides)
+        def getAnnotation(context):
+            annotations = IAnnotations(context)
+            try:
+                result = annotations[key]
+            except KeyError:
+                result = factory()
+                annotations[key] = result
+
+            # Containment has to be set up late to allow containment
+            # proxies to be applied, if needed. This does not trigger
+            # an event and is idempotent if containment is set up
+            # already.
+            contained_result = contained(result, context, key)
+            return contained_result
+
+        component.provideAdapter(getAnnotation)

Copied: grok/trunk/src/grok/tests/annotation/__init__.py (from rev 72076, grok/trunk/src/grok/tests/__init__.py)

Added: grok/trunk/src/grok/tests/annotation/annotation.py
===================================================================
--- grok/trunk/src/grok/tests/annotation/annotation.py	2007-01-19 21:58:52 UTC (rev 72110)
+++ grok/trunk/src/grok/tests/annotation/annotation.py	2007-01-19 23:35:02 UTC (rev 72111)
@@ -0,0 +1,49 @@
+"""
+  >>> grok.grok(__name__)
+  >>> from zope import component
+  >>> from zope.annotation.attribute import AttributeAnnotations
+  >>> component.provideAdapter(AttributeAnnotations)
+
+We can adapt a model to an annotation interface and obtain a
+persistent annotation storage:
+
+  >>> manfred = Mammoth()
+  >>> branding = IBranding(manfred)
+  >>> branding.addBrand('mine')
+  >>> branding.addBrand('yours')
+
+Regetting the adapter will yield the same annotation storage:
+
+  >>> brands = IBranding(manfred).getBrands()
+  >>> brands.sort()
+  >>> brands
+  ['mine', 'yours']
+
+"""
+
+import grok
+from zope import interface
+from BTrees.OOBTree import OOTreeSet
+
+class Mammoth(grok.Model):
+    pass
+
+class IBranding(interface.Interface):
+
+    def addBrand(brand):
+        """Brand an animal with ``brand``, a string."""
+
+    def getBrands():
+        """Return a list of brands."""
+
+class Branding(grok.Annotation):
+    grok.implements(IBranding)
+
+    def __init__(self): 
+        self._brands = OOTreeSet()
+
+    def addBrand(self, brand):
+        self._brands.insert(brand)
+
+    def getBrands(self):
+        return list(self._brands)


Property changes on: grok/trunk/src/grok/tests/annotation/annotation.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: grok/trunk/src/grok/tests/annotation/implementsmany.py
===================================================================
--- grok/trunk/src/grok/tests/annotation/implementsmany.py	2007-01-19 21:58:52 UTC (rev 72110)
+++ grok/trunk/src/grok/tests/annotation/implementsmany.py	2007-01-19 23:35:02 UTC (rev 72111)
@@ -0,0 +1,27 @@
+"""
+An annotations class must implement either exactly one interface, or
+it should cspecify which of the many implemented interfaces it should
+be registered for.  Ambiguities lead to errors:
+
+  >>> grok.grok(__name__)
+  Traceback (most recent call last):
+  GrokError: <class
+  'grok.tests.annotation.implementsmany.MammothAnnotations'> is
+  implementing more than one interface (use grok.provides to specify
+  which one to use).
+"""
+
+import grok
+from zope import interface
+
+class Mammoth(grok.Model):
+    pass
+
+class IOneInterface(interface.Interface):
+    pass
+
+class IAnotherInterface(interface.Interface):
+    pass
+
+class MammothAnnotations(grok.Annotation):
+    grok.implements(IOneInterface, IAnotherInterface)


Property changes on: grok/trunk/src/grok/tests/annotation/implementsmany.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: grok/trunk/src/grok/tests/annotation/implementsnone.py
===================================================================
--- grok/trunk/src/grok/tests/annotation/implementsnone.py	2007-01-19 21:58:52 UTC (rev 72110)
+++ grok/trunk/src/grok/tests/annotation/implementsnone.py	2007-01-19 23:35:02 UTC (rev 72111)
@@ -0,0 +1,20 @@
+"""
+Subclasses of grok.Annotation must implement at least one additional
+interface to indicate which annotation interface they provide and can
+be looked up with:
+
+  >>> grok.grok(__name__)
+  Traceback (most recent call last):
+  GrokError: <class 'grok.tests.annotation.implementsnone.Branding'>
+  must implement at least one interface (use grok.implements to
+  specify).
+
+"""
+
+import grok
+
+class Mammoth(grok.Model):
+    pass
+
+class Branding(grok.Annotation):
+    pass


Property changes on: grok/trunk/src/grok/tests/annotation/implementsnone.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: grok/trunk/src/grok/tests/annotation/name.py
===================================================================
--- grok/trunk/src/grok/tests/annotation/name.py	2007-01-19 21:58:52 UTC (rev 72110)
+++ grok/trunk/src/grok/tests/annotation/name.py	2007-01-19 23:35:02 UTC (rev 72111)
@@ -0,0 +1,49 @@
+"""
+  >>> grok.grok(__name__)
+  >>> from zope import component
+  >>> from zope.annotation.attribute import AttributeAnnotations
+  >>> component.provideAdapter(AttributeAnnotations)
+
+If an annotation class doesn't specify anything else, its dotted name
+will be used as an annotation key:
+
+  >>> manfred = Mammoth()
+  >>> ann = IImplicitName(manfred)
+
+  >>> from zope.annotation.interfaces import IAnnotations
+  >>> 'grok.tests.annotation.name.ImplicitName' in IAnnotations(manfred)
+  True
+
+Of course, annotation classes can explicity specify the name of the
+annotation key that they will be stored under.  That's useful if you
+want a meaningful key that's accessible from other applications and if
+you want to be able to move the class around during refactorings (then
+the dotted name will obviously change)
+
+  >>> ann = IExplicitName(manfred)
+  >>> 'grok.tests.annotation.name.ExplicitName' in IAnnotations(manfred)
+  False
+  >>> 'mammoth.branding' in IAnnotations(manfred)
+  True
+
+"""
+
+import grok
+from zope import interface
+from BTrees.OOBTree import OOTreeSet
+
+class Mammoth(grok.Model):
+    pass
+
+class IExplicitName(interface.Interface):
+    pass
+
+class IImplicitName(interface.Interface):
+    pass
+
+class ExplicitName(grok.Annotation):
+    grok.implements(IExplicitName)
+    grok.name('mammoth.branding')
+
+class ImplicitName(grok.Annotation):
+    grok.implements(IImplicitName)


Property changes on: grok/trunk/src/grok/tests/annotation/name.py
___________________________________________________________________
Name: svn:eol-style
   + native

Added: grok/trunk/src/grok/tests/annotation/provides.py
===================================================================
--- grok/trunk/src/grok/tests/annotation/provides.py	2007-01-19 21:58:52 UTC (rev 72110)
+++ grok/trunk/src/grok/tests/annotation/provides.py	2007-01-19 23:35:02 UTC (rev 72111)
@@ -0,0 +1,37 @@
+"""
+  >>> grok.grok(__name__)
+  >>> from zope import component
+  >>> from zope.annotation.attribute import AttributeAnnotations
+  >>> component.provideAdapter(AttributeAnnotations)
+
+If an annotation class implements more than one interface, it has to
+declare which one it should be registered for using ``grok.provides``.
+
+  >>> manfred = Mammoth()
+  >>> ann = IOneInterface(manfred)
+
+It can then be looked up only using that one interface:
+
+  >>> IAnotherOne(manfred)
+  Traceback (most recent call last):
+  TypeError: ('Could not adapt', <grok.tests.annotation.provides.Mammoth object at ...>, <InterfaceClass grok.tests.annotation.provides.IAnotherOne>)
+
+
+"""
+
+import grok
+from zope import interface
+from BTrees.OOBTree import OOTreeSet
+
+class Mammoth(grok.Model):
+    pass
+
+class IOneInterface(interface.Interface):
+    pass
+
+class IAnotherOne(interface.Interface):
+    pass
+
+class MammothAnnotation(grok.Annotation):
+    grok.implements(IOneInterface, IAnotherOne)
+    grok.provides(IOneInterface)


Property changes on: grok/trunk/src/grok/tests/annotation/provides.py
___________________________________________________________________
Name: svn:eol-style
   + native

Modified: grok/trunk/src/grok/tests/test_grok.py
===================================================================
--- grok/trunk/src/grok/tests/test_grok.py	2007-01-19 21:58:52 UTC (rev 72110)
+++ grok/trunk/src/grok/tests/test_grok.py	2007-01-19 23:35:02 UTC (rev 72111)
@@ -35,7 +35,7 @@
     for name in ['adapter', 'error', 'view', 'scan', 'event', 'security',
                  'zcml', 'static', 'utility', 'xmlrpc', 'container',
                  'traversal', 'form', 'site', 'grokker', 'directive', 'util',
-                 'baseclass']:
+                 'baseclass', 'annotation']:
         suite.addTest(suiteFromPackage(name))
     return suite
 



More information about the Checkins mailing list