delegation.py :  » Database » Modeling-Framework » Modeling-0.9 » Modeling » Python Open Source

Home
Python Open Source
1.3.1.2 Python
2.Ajax
3.Aspect Oriented
4.Blog
5.Build
6.Business Application
7.Chart Report
8.Content Management Systems
9.Cryptographic
10.Database
11.Development
12.Editor
13.Email
14.ERP
15.Game 2D 3D
16.GIS
17.GUI
18.IDE
19.Installer
20.IRC
21.Issue Tracker
22.Language Interface
23.Log
24.Math
25.Media Sound Audio
26.Mobile
27.Network
28.Parser
29.PDF
30.Project Management
31.RSS
32.Search
33.Security
34.Template Engines
35.Test
36.UML
37.USB Serial
38.Web Frameworks
39.Web Server
40.Web Services
41.Web Unit
42.Wiki
43.Windows
44.XML
Python Open Source » Database » Modeling Framework 
Modeling Framework » Modeling 0.9 » Modeling » delegation.py
# -*- coding: iso-8859-1 -*-
"""delegation module.

The underlying philosophy
-------------------------

Suppose you are about to develop a reusable component offering a secure
login service. This component might need two different sorts of features:
say, a mandatory procedure to check login and password, and some optional
others, e.g. a special procedure to be called when the maximal number of
login attempts is reached. Your component implements a default procedure
to check credentials against, say, the ``/etc/password`` file, and does not
have any limit on the number of retries after failure.

However and since this is a reusable component you wish that the procedure
that checks credentials, the maximum number of failures and the action to
be taken can be conveniently changed at runtime.

One approach consists in making these parameters settable in your
component ; this will work well, but this may not be really convenient:

- for you, as the developper of the reusable component, because this
  make the API of the component all the more cumbersome since the number
  of possible external "tune-ups" grows

- for the users of your component, for which it may be handier to be
  able to provide a single object gathering its own mechanisms to tune
  the reusable component [1]_. A good reason for this is when the some
  processings involved depends on some other objects the reusable
  component has no way to know about, and/or if answers to some questions
  cannot be determined but at runtime.

The delegation offers you a general mechanism to solve these two issues.
The DelegateWrapper class allows the following features:

- it makes it simple to have a single place to specify and document what
  can be changed along with specific documentation: this is the
  delegate's interface.

- The users of your component do not have to implement *all*
  methods of the delegate's API, they only provide whatever methods is
  adequate for their own use [2]_.

- It can also be used to notify a delegate when some actions are about
  to be done, have been processed, etc., thus making it possible to
  keep an external object in sync. with the reusable component's
  internal state, without violating encapsulation.

.. [1] of course, you may run into cases where such a design is inaccurate.
   All this is obviously a general discussion which might be inadequate given
   a specific architecture or design philosophy!

.. [2] except if you enforce your own rules, such as "if the delegates
   provides methodOne it should provide methodTwo as well", but this an other
   story (see DelegateWrapper.respondsTo())
    
How to use
----------

We will stick on the example exposed above to explain how delegation is
achieved with this module.

First, define a general "interface": a class, which defines and
documents the methods a delegate for your component might implement:

.. code-block:: Python

  class LoginComponentDelegate:
    'Delegate for the reusable LoginComponent'

    def checkCredentials(self, login, password):
      '''
      Implement this method if you want to replace the default checking
      mechanism by yours. This method should raise InvalidCredentials
      if parameters 'login' and 'password' do not match an valid user.

      Parameters: both 'login' and 'password' are strings.
      '''

    def maximumNumberOfFailures(self):
      '''
      Implement this method to ...
      A return value equal to '-1' means no limit.
      '''

    def numberOfFailuresHasReachedItsMaximum(self):
      '''
      ...
      '''

The `delegation` module supplies one class, `DelegateWrapper`, to handle
delegate. Its role is to inspect a delegate's interface...

To use it, your reusable component's class will look like:

.. code-block:: Python

      class LoginComponent:

        def __init__(self):
          '... Initialization  ...'
          self._delegate=DelegateWrapper(LoginComponentDelegate)
          self._nbOfFailures=0
          
        def setDelegate(self, object):
          '''Sets the login component's delegate'''
          self._delegate.setDelegateObject(object)

        def validateCredentials(self, login, password):
          '''
          delegate's 
          '''
          try:
            if self._delegate.respondsTo('checkCredentials'):
               # DelegateWrapper acts as a proxy for the wrapped object
              self._delegate.checkCredentials(login, password)
            else:
              # the default mechanism, raising InvalidCredentials if no user
              # can be found
          except:
            self._nbOfFailures+=1
            if self._nbOfFailures>=self.maximumNumberOfFailures():
              [...you get the idea...]
            raise

        def maximumNumberOfFailures(self):
          '''
          Defaults to -1 (no limit). You can change this limit by implementing
          the delegate's method 'maximumNumberOfFailures'
          '''
          if self._delegate.respondsTo('maximumNumberOfFailures'):
            return self._delegate.maximumNumberOfFailures()
          else:
            return -1

        [...]

The delegate itself, as it will be provided by the users of the reusable
component, can be an instance of any class. You should have noticed in the
sample code above that the `DelegateWrapper` acts as a proxy : it
automatically forwards any function call it cannot service (see Gotchas,
below) to the wrapped object --the real delegate). Note, however, that the
`DelegateWrapper` is **not** an "absolute" proxy: the forwarded messages
are __only__ those which are declared in the delegate's interface.

Gotchas
-------

There are several points which you should be aware of when using this
class, some of which should be indicated in your own documentation:

- the DelegateWrapper works by inspecting the interface-class and
  registering the signatures of its methods. By signature we mean: name,
  parameters *and* their default values, if any. These signatures are then
  compared to those of the delegate object when it is supplied. Thus:

    - if a delegate object does implement a function whose name matches
      one of the delegate's API but which uses different number of
      parameters, the same number of parameter with different names, or
      the same number of parameters and the same names but with different
      default values, this function will not be considered as an valid
      delegate method (hence 'respondsTo(thatMethodName)' will be false).

    - The DelegateWrapper determines the mapping between the delegate's
      API and the delegate object when this object is supplied
      ('setDelegateObject()') ; this mapping determines the answer for
      message 'respondsTo' as well as whether that message should be
      forwarded to the delegate by the DelegateWrapper.

      Thus, if the delegate object can change at runtime, e.g. not
      responding to a message at some point of runtime and being able to
      service the same message later, that message will never be
      considered available. In that case, the provider for the delegate
      object should take care to call the component's 'setDelegate()'
      method each time the delegate object changes --this ensures that the
      mapping between the delegate's API and the object's capabilities is
      correctly recomputed.

- The `DelegateWrapper` itself declares some methods:
  `DelegateWrapper.respondsTo`, `DelegateWrapper.delegateObject`,
  `DelegateWrapper.setDelegateObject`,
  `DelegateWrapper.reevaluateDelegateObject`. If the delegate's interface uses
  at least one of these methods, the automatic forwarding of method calls is
  automatically disabled. In that case, a warnings is issued (warning level:
  ``RuntimeWarning``) when the delegate object is set. You can still send
  messages to the delegate object by sending the messages to the
  `DelegateWrapper.delegateObject()`.
    
Licence
-------

::
  
    Copyright (c) 2001-2004, Sebastien Bigaret
    All rights reserved.
    
    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions are
    met:
    
        * Redistributions of source code must retain the above copyright
          notice, this list of conditions and the following disclaimer.

        * Redistributions in binary form must reproduce the above copyright
          notice, this list of conditions and the following disclaimer in the
          documentation and/or other materials provided with the distribution.

        * Neither the name of the software's copyright holder, Sebastien
          Bigaret, nor the names of its contributors may be used to endorse or
          promote products derived from this software without specific prior
          written permission.
    
    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
    IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
    THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
    PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
    CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
    EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
    LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
    NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    
CVS information
---------------

    $Id: delegation.py 937 2004-08-02 20:54:47Z sbigaret $

""" 

__version__='$Revision: 937 $'[11:-3] # original revision
__authors__=('Sebastien Bigaret <sbigaret@users.sourceforge.net>', )

from warnings import warn
import inspect, types

class DelegateWrapper:
  """
  The DelegateWrapper: 
  """
  _delegate=None

  def __init__(self, interface, object=None):
    """
    Initializes the DelegateWrapper

    :Parameters:

      - `interface`: a class object declaring the methods a delegate may
        choose to implement
      - `object`: optional delegate object. The delegate object can be set or
        changed later with `setDelegateObject()`
        
    """
    if not inspect.isclass(interface):
      raise ValueError, 'Invalid parameter interface: should be a class'
    self._interfaceSpec={}
    self._delegateCapabilities={}
    self._proxyDelegate=1

    self._interface=interface
    self._compileInterface()
    if object:
      self.setDelegateObject(object)

  def delegateObject(self):
    """
    Returns the delegate object
    """
    return self._delegate
  
  def respondsTo(self, interfaceMethodName):
    """
    Tells whether the wrapped delegate object can responds to the method
    `interfaceMethodName` as defined in the delegate's interface.

    Returned value is ``None`` for false, ``1`` (integer) for true
    """
    return self._delegateCapabilities.get(interfaceMethodName)

  def reevaluateDelegateObject(self):
    """
    
    """
    if not self._delegate:
      return
    self._delegateCapabilities={}
    functions=self._getClassMethodsSignatures(self._delegate.__class__)
    for nameSignature in functions:
      name, signature=nameSignature
      if signature==self._interfaceSpec.get(name):
        self._delegateCapabilities[name]=1
        
  def setDelegateObject(self, object):
    """
    Sets the delegate object
    """
    if not getattr(object, '__class__', None):
      raise ValueError, 'Invalid parameter object: it should be an instance'

    self._delegate=object
    self.reevaluateDelegateObject()

  def __getattr__(self, name): # proxy object's method only
    if not self._proxyDelegate:
      raise AttributeError, 'Automatic forward of method calls is disabled. '
    if self._delegateCapabilities.get(name):
      return getattr(self._delegate, name)
    raise AttributeError

  # Private methods
  def _compileInterface(self):
    """
    Inspect the delegate's interface and extracts the necessary methods' names
    and signatures
    """
    functions=self._getClassMethodsSignatures(self._interface)
    dict={}
    pbFuncNames=[]
    self._proxyDelegate=1
    for nameSignature in functions:
      name, signature=nameSignature
      dict[name]=signature
      if name in ('delegate', 'respondsTo', 'reevaluateDelegateObject', 'setDelegate'):
        self._proxyDelegate=0
        pbFuncNames.append(name)
    if not self._proxyDelegate:
      warn('DelegateWrapper will not be able to proxy calls to the delegate '\
           "object: the interface declares the methods %s which are also "\
           "methods of the DelegateWrapper itself.\n\nProxy calls disabled "\
           "(see documentation for details)"%str(pbFuncNames), 
           RuntimeWarning)
    self._interfaceSpec=dict
    return

  def _getClassMethodsSignatures(self, aClass):
    """
    Generic function returning a sequence made of couples
    (<functionName>, <functionSignature>), each couple representing one of
    `aClass` 's methods.

    An example of such a couple is (from ``tests/test_delegation.py``)::

      ('canTheWorldBeChanged', "(['self', 'isElvisAlive'], None, None, (1,))")

    See also: `inspect.getargspec()`
    """
    isfunction=inspect.isfunction
    classDict=aClass.__dict__
  
    methods=[(name, str(inspect.getargspec(func)))
             for name, func in map(None,classDict.keys(),classDict.values())
             if isfunction(func)]
    return tuple(methods)



www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.