[Checkins] SVN: zope.fssync/trunk/src/zope/fssync/generic.txt Added a doctest that illustrates the use of generic functions.

Uwe Oestermeier u.oestermeier at iwm-kmrc.de
Mon Jun 18 07:29:18 EDT 2007


Log message for revision 76756:
  Added a doctest that illustrates the use of generic functions.

Changed:
  A   zope.fssync/trunk/src/zope/fssync/generic.txt

-=-
Added: zope.fssync/trunk/src/zope/fssync/generic.txt
===================================================================
--- zope.fssync/trunk/src/zope/fssync/generic.txt	                        (rev 0)
+++ zope.fssync/trunk/src/zope/fssync/generic.txt	2007-06-18 11:29:18 UTC (rev 76756)
@@ -0,0 +1,215 @@
+=================
+Generic Functions
+=================
+
+This is an experimental doctest which tries to figure out whether
+generic functions may be usefull building blocks of serialization /
+deserialization adapters. Currently the implementation doesn't use 
+generic functions.
+
+The current synchronizer adapters have many methods since they are 
+mainly focussing on the import/export use case. In this use case it
+is crucial to save the data as completely as possible. In the use 
+case of content management many of these methods may be superfluous.
+Let's assume that we want to write application specific XML files 
+(and only XML files) to a SVN repository. In this case we need not 
+save metadata in an entries.xml file since SVN handles these 
+metadata already. Annotations may also be irrelevant if the 
+application doesn't use DublinCore or other annotation services.
+If we want to write synchronizers for this retricted application 
+we should not depend on basic synchronizers which handle these 
+aspects by default.
+
+This leads to a design of the fssync package where each aspect of 
+an object is handled by a generic function, e.g. metadata, annotions,
+fields, body, and leaves it to the application which aspects are 
+written in which part of the repository.
+
+A generic function in this sense is a multi-adapter with a single 
+__call__ method and thus can easily be replaced by other applications 
+without the need to specialize some powerfull default adapters 
+(the current approach).
+
+
+Defining Generic Functions
+--------------------------
+
+So let's start with a rather broad definition of generic functions:
+
+    >>> class IGenericFunction(zope.interface.Interface):
+    ...     """Base interface for a generic function."""
+    ...     def __call__(*args, **kw):
+    ...         """A generic function is callable like any other function."""
+
+Since a generic function can use the component architecture it's 
+definition can be extremely short if it uses the common adapter lookup
+mechanism to find the specific implementation of a function. Our 
+definition of generic function uses a multi-adapter if the function is
+called with more than one parameter and also provides a `when` 
+decorator to declare the required parameter types:
+
+    >>> class GenericFunction(object):
+    ...     """Basic implementation of a generic function."""
+    ...     zope.interface.implements(IGenericFunction)
+    ...     def __init__(self, provides=None):
+    ...         self.provides = provides
+    ...
+    ...     def __call__(self, *args):
+    ...         if len(args) > 1:
+    ...             return zope.component.queryMultiAdapter(args, self.provides)
+    ...         return self.provides(args[0], None)
+    ... 
+    ...     def when(self, *types):
+    ...         def decorator(f):
+    ...             f = zope.interface.implementer(self.provides)(f)
+    ...             f = zope.component.adapter(*types)(f)
+    ...             return f
+    ...         return decorator
+    ...
+    ...     def register(self):
+    ...         def decorator(f):
+    ...             zope.component.provideAdapter(f)
+    ...             return f
+    ...         return decorator
+    
+Let's take pretty printing as an example of a simple generic
+serialization function:
+
+    >>> class IPrettyPrint(zope.interface.Interface):
+    ...     """Interface for a generic pretty print function."""
+    
+    >>> pprint = GenericFunction(IPrettyPrint)
+    >>> IGenericFunction.providedBy(pprint)
+    True
+
+The above definition of generic function returns None without any further
+registered implementation:
+
+    >>> pprint(42) is None
+    True
+
+The component architecture already allows us to register functions 
+as adapters:
+
+    >>> @zope.interface.implementer(IPrettyPrint)
+    ... @zope.component.adapter(int)
+    ... def pprint_int(num):
+    ...     print num
+    >>> zope.component.provideAdapter(pprint_int)
+    >>> pprint(42)
+    42
+
+Using the equivalent `register`  and `when` decorators looks nicer. 
+Note that the order of decorators is important here. The register 
+decorator is usefull in tests, if you want to extend or replace the
+implementations in your Zope3 application, you should of course use 
+the corresponding ZCML adapter statement:
+
+    >>> @pprint.register()
+    ... @pprint.when(list)
+    ... def pprint_list(l):
+    ...     for x in l:
+    ...         pprint(x)
+     
+    >>> pprint([1, 2, 3])
+    1
+    2
+    3
+    
+The interesting thing about generic functions is that their basic 
+behavior can be adapted to different purposes. If we want a 
+different, e.g. class-based lookup mechanisms as described in README.txt, 
+we simply overwrite the __call__ method. Whereas the above 
+implementation returns None if no function is registered, this 
+implementation throws an informative error if a function for a specific 
+class is missing:
+
+    >>> def dottedname(klass):
+    ...     return "%s.%s" % (klass.__module__, klass.__name__)
+    >>> class MissingImplementation(Exception):
+    ...     pass
+    >>> class ClassBasedGenericFunction(GenericFunction):
+    ...     def __call__(self, *args):
+    ...         name = dottedname(args[0].__class__)
+    ...         try:
+    ...             f = zope.component.getUtility(self.provides, name=name)
+    ...         except zope.component.ComponentLookupError:
+    ...             message = 'Missing %s implementation'\
+    ...                         ' for %s' % (dottedname(self.provides), name)
+    ...             raise MissingImplementation(message)
+    ...         return f(*args)
+    ...
+    ...     def register(self, klass):
+    ...         def decorator(f):
+    ...             zope.component.provideUtility(f,
+    ...                 self.provides,
+    ...                 name=dottedname(klass))
+    ...             return f
+    ...         return decorator
+    
+    >>> pprint2 = ClassBasedGenericFunction(IPrettyPrint)
+        
+Now we can be sure that a class-based function is looked up. We note no
+difference to the former implementation if we have a class specific
+implementation:
+
+    >>> @pprint2.register(list)
+    ... def print_list2(l):
+    ...     for x in l:
+    ...         print(x)
+    >>> pprint2([1, 2, 3])
+    1
+    2
+    3
+    
+But we get an informative exception indicating that we must
+provide a class-based implementation:
+
+    >>> class MyList(list):
+    ...     pass
+    >>> mylist = MyList([1, 2, 3])
+    >>> pprint(mylist)
+    1
+    2
+    3
+    >>> pprint2(mylist)
+    Traceback (most recent call last):
+    ...
+    MissingImplementation: Missing ...IPrettyPrint implementation for ...MyList
+    
+Sometimes it is more usefull to have a fallback behavior and a 
+warning. This can also be achieved easily without changing the basic
+API:
+
+    >>> class FallbackGenericFunction(ClassBasedGenericFunction):
+    ...     def __call__(self, *args):
+    ...         name = dottedname(args[0].__class__)
+    ...         try:
+    ...             f = zope.component.getUtility(self.provides, name=name)
+    ...             return f(*args)
+    ...         except zope.component.ComponentLookupError:
+    ...             message = 'Missing %s implementation'\
+    ...                         ' for %s' % (dottedname(self.provides), name)
+    ...             self.warn(message)
+    ...             self.default(*args)
+    ...     def warn(self, message):
+    ...         print "Warning:", message
+
+    >>> pprint3 = FallbackGenericFunction(IPrettyPrint)
+    >>> def default_pprint(obj):
+    ...     print obj
+    >>> pprint3.default = default_pprint
+    
+    >>> @pprint3.register(list)
+    ... def print_list2(l):
+    ...     for x in l:
+    ...         print(x)
+    >>> pprint3([1, 2, 3])
+    1
+    2
+    3
+    >>> pprint3(mylist)
+    Warning: Missing ...IPrettyPrint implementation for ...MyList
+    [1, 2, 3]
+
+    



More information about the Checkins mailing list