[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