Hash: SHA1

On 03/20/2011 09:46 AM, Jim Fulton wrote:

> Problem
> =======
> ZTK projects use ZCML too much.  Ideally, ZCML should only
> have to be used when we want to override something.
> Solution sketch
> ===============
> I think we ought to come up with a much cleaner way of defining
> default configuration. (Pyramid does this by passing default values in
> adapter calls, but I think we can do a lot better than that.)

I'm not confident that "better" is achievable for the classic
"dependency injection" case (i.e., the default is the one you almost
always want, except for unit testing).

Typically, I define a utility interface *and* the default implementation
in the module which mostly uses it.  E.g.::

  from zope.interface import Interface
  from zope.component import queryUtility

  class ISomePlugPoint(Interface):
      def __call__(foo, bar):
          """blah blah"""

  # Look, Ma!  No decorator!
  def defaultImpl(foo, bar):
      # DTRT for the normal case

  def clientFunction(request):
      impl = queryUtility(ISomePlugPoint, default=defaultImpl)

Note the absence extra declarations for 'defaultImpl', and of extra
syntax of any kind.  In order to maintain good test isolation, I usually
avoid memoizing the lookup (e.g. as a module scope variable). unless
profiling shows that the lookup ends up on a critical path.

This pattern even works outside of testing:  if you do the frameworky
thing and document how to override the utility as a policy, it becomes a
trivial integration point.

For adapters, the example is a bit noisier, because the component
registry wants to call the factory for you::

  from zope.interface import Interface
  from zope.component import queryAdapter

  class IAdaptsTo(Interface):
      def someMethod():
          """ blah, blah."""

  class DefaultImpl(object):
      # Note that we require no decorator or advice
      def __init__(self, context):
          self.context = context
      def someMethod(self):
          return 'whatever'

  def anotherClient(context, request):
      adapted = queryAdapter(context, IAdaptsTo)
      if adapted is None:
         adapted = DefaultImpl(context)
      # now use it

(Note that memoization isn't a practical optimization doesn't work for

If we added a 'default_factory' argument to 'queryAdapter', we wouldn't
need the 'if' statement, which would make this example as compact as the
utility version.  Or we could add a 'queryAdapterFactory' API instead,
and have 'queryAdapter' use it (what is one more function call between
friends? ;)

The one downside I can see is giving up on the sugar^Wexpressivity of
calling the interface directly -- I guess we could propagate the
'default_factory' argument through to the '__call__' of interface.  Note
that I *wanted* some extra sugar at one point (doing utility lookup when
no arguments were passed to Interface.__call__), but I haven't missed
that convenience much since I went on a low sugar diet with BFG / pyramid.

>  I'd like to see us come up with a "pythonic" way to wire components up
> that can be overridden through registration (through zcml or
> otherwise).  Ideally, the mechanism shouldn't "feel" like
> "configuration" but like "programming".

My example feels like programming to me:  no ZCML, no decorators, and no
advice needed up until the point you want to override the normal defaults.

- -- 
Tres Seaver          +1 540-429-0999          tsea...@palladion.com
Palladion Software   "Excellence by Design"    http://palladion.com
Version: GnuPG v1.4.10 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/


Zope-Dev maillist  -  Zope-Dev@zope.org
**  No cross posts or HTML encoding!  **
(Related lists - 
 https://mail.zope.org/mailman/listinfo/zope )

Reply via email to