created util package with a simple adapter factory and lazy properties
git-svn-id: svn://svn.cy55.de/Zope3/src/cybertools/trunk@1540 fd906abe-77d9-0310-91a1-e0d9ade77398
This commit is contained in:
parent
0dda2f4b12
commit
174a0ab4f1
7 changed files with 304 additions and 0 deletions
26
util/README.txt
Normal file
26
util/README.txt
Normal file
|
@ -0,0 +1,26 @@
|
|||
================
|
||||
Common Utilities
|
||||
================
|
||||
|
||||
$Id$
|
||||
|
||||
This package contains a set of miscellaneous utility modules that
|
||||
are to small for creating a separate package for them.
|
||||
|
||||
As each utility is developed further it may eventually get its own
|
||||
package some time.
|
||||
|
||||
Usually each utility has a <name>.py file with an implementation and a
|
||||
corresponding <name>.txt file with a description that can be run as a
|
||||
doctest.
|
||||
|
||||
The doctests can be run by issuing
|
||||
|
||||
python cybertools/util/tests.py
|
||||
|
||||
in the directory above the cybertools directory or via
|
||||
|
||||
bin/test -vs cybertools.util
|
||||
|
||||
from within a Zope 3 instance that contains the cybertools package in
|
||||
its python path.
|
3
util/__init__.py
Normal file
3
util/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
"""
|
||||
$Id$
|
||||
"""
|
52
util/adapter.py
Normal file
52
util/adapter.py
Normal file
|
@ -0,0 +1,52 @@
|
|||
#
|
||||
# Copyright (c) 2007 Helmut Merz helmutm@cy55.de
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
|
||||
"""
|
||||
A simple adapter framework.
|
||||
|
||||
$Id$
|
||||
"""
|
||||
|
||||
|
||||
class AdapterFactory(object):
|
||||
|
||||
def __init__(self):
|
||||
self._registry = {}
|
||||
|
||||
def register(self, adapter, adapted, name=''):
|
||||
""" Register `adapter` class for objects of class `adapted`.
|
||||
Optionally associate the adapter with a `name`; thus there
|
||||
can be more than one adapter for one class.
|
||||
"""
|
||||
self._registry[(adapted, name)] = adapter
|
||||
|
||||
def __call__(self, obj, name=''):
|
||||
""" Return an adapter instance on `obj` with the `name` given.
|
||||
if obj is a class use this class for the adapter lookup, else
|
||||
use obj's class.
|
||||
If there isn't an adapter directly for the class
|
||||
check also for its base classes.
|
||||
"""
|
||||
class_ = type(obj) is type and obj or obj.__class__
|
||||
adapter = None
|
||||
while adapter is None and class_:
|
||||
adapter = self._registry.get((class_, name))
|
||||
if adapter is None:
|
||||
bases = class_.__bases__
|
||||
class_ = bases and bases[0] or None
|
||||
return adapter is not None and adapter(obj) or None
|
102
util/adapter.txt
Normal file
102
util/adapter.txt
Normal file
|
@ -0,0 +1,102 @@
|
|||
==========================
|
||||
A Simple Adapter Framework
|
||||
==========================
|
||||
|
||||
$Id$
|
||||
|
||||
To work with adapters we need at least two classes, one for objects
|
||||
that we want to adapt to (the adapter's `context`) and one for the adapters.
|
||||
|
||||
>>> class Content(object):
|
||||
... pass
|
||||
|
||||
>>> class StateAdapter(object):
|
||||
... def __init__(self, context):
|
||||
... self.context = context
|
||||
... def setState(self, state):
|
||||
... self.context._state = state
|
||||
... def getState(self):
|
||||
... return getattr(self.context, '_state', 'initial')
|
||||
|
||||
In order to use the adapters in a flexible way we create adapter objects
|
||||
by using an adapter factory. The stateful factory is intended to create
|
||||
adapters that allow setting and retrieving the state of objects.
|
||||
|
||||
>>> from cybertools.util.adapter import AdapterFactory
|
||||
>>> stateful = AdapterFactory()
|
||||
|
||||
We can now register our StateAdapter class with the `stateful` AdapterFactory.
|
||||
We have to tell the factory which class of adapted objects a certain
|
||||
adapter class is responsible for.
|
||||
|
||||
>>> stateful.register(StateAdapter, Content)
|
||||
|
||||
Now we are ready to create an object of the Content class
|
||||
|
||||
>>> c1 = Content()
|
||||
|
||||
and a corresponding `stateful` adapter:
|
||||
|
||||
>>> c1State = stateful(c1)
|
||||
|
||||
The adapter can now be used to access the Content object's state:
|
||||
|
||||
>>> c1State.getState()
|
||||
'initial'
|
||||
>>> c1State.setState('visible')
|
||||
>>> c1State.getState()
|
||||
'visible'
|
||||
|
||||
The procedure also works with subclasses of the Content class.
|
||||
|
||||
>>> class SpecialContent(Content):
|
||||
... pass
|
||||
|
||||
>>> sc1 = SpecialContent()
|
||||
>>> sc1State = stateful(sc1)
|
||||
>>> sc1State.getState()
|
||||
'initial'
|
||||
>>> sc1State.setState('public')
|
||||
>>> sc1State.getState()
|
||||
'public'
|
||||
|
||||
But if we try to use an adapter factory for objects for which there is
|
||||
no adapter registered we get back None.
|
||||
|
||||
>>> class UnknownContent(object):
|
||||
... pass
|
||||
|
||||
>>> uc1 = UnknownContent()
|
||||
>>> uc1State = stateful(uc1)
|
||||
>>> uc1State is None
|
||||
True
|
||||
|
||||
Class adapters as factories
|
||||
---------------------------
|
||||
|
||||
There is a special case that can be handled with an adapter factory:
|
||||
creating objects via an adapter to a class. An example for this is
|
||||
a class that creates objects by loading them from a file or database,
|
||||
so this would be a storage adapter class.
|
||||
|
||||
In our example we avoid reading from disk or from a database but just
|
||||
create the object wanted by using the context's constructor.
|
||||
|
||||
>>> class ContentLoader(object):
|
||||
... def __init__(self, context):
|
||||
... self.context = context
|
||||
... def load(self):
|
||||
... return Content()
|
||||
|
||||
>>> storage = AdapterFactory()
|
||||
>>> storage.register(ContentLoader, Content)
|
||||
|
||||
In this case we get the adapter from the adapter factory by providing
|
||||
it with the context class (instead of an instance of it like in the
|
||||
example above.
|
||||
|
||||
>>> loader = storage(Content)
|
||||
>>> c2 = loader.load()
|
||||
>>> c2
|
||||
<Content object ...>
|
||||
|
41
util/property.py
Normal file
41
util/property.py
Normal file
|
@ -0,0 +1,41 @@
|
|||
#
|
||||
# Copyright (c) 2007 Helmut Merz helmutm@cy55.de
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
#
|
||||
|
||||
"""
|
||||
Advanced properties, esp the lazy one...
|
||||
|
||||
Based on zope.cachedescriptors.property.Lazy
|
||||
|
||||
$Id$
|
||||
"""
|
||||
|
||||
class lzprop(object):
|
||||
"""Declare lazy properties that are calculated only if needed,
|
||||
and just once.
|
||||
|
||||
See property.txt for example usage.
|
||||
"""
|
||||
|
||||
def __init__(self, func):
|
||||
self.func = func
|
||||
|
||||
def __get__(self, inst, class_):
|
||||
if inst is None: return self
|
||||
value = self.func(inst)
|
||||
inst.__dict__[self.func.__name__] = value
|
||||
return value
|
54
util/property.txt
Normal file
54
util/property.txt
Normal file
|
@ -0,0 +1,54 @@
|
|||
================
|
||||
Smart Properties
|
||||
================
|
||||
|
||||
$Id$
|
||||
|
||||
lzprop
|
||||
======
|
||||
|
||||
The `lzprop` decorator allows the declaration of lazy properties - attributes
|
||||
that are calculated only on first access, and then just once. This is
|
||||
extremely useful when working with objects of a limited lifetime (e.g.
|
||||
adapters) that provide results from expensive calculations.
|
||||
|
||||
We use a simple class with one lazy property that tracks the calculation
|
||||
by printing an informative message:
|
||||
|
||||
>>> from cybertools.util.property import lzprop
|
||||
|
||||
>>> class Demo(object):
|
||||
... base = 6
|
||||
... @lzprop
|
||||
... def value(self):
|
||||
... print 'calculating'
|
||||
... return self.base * 7
|
||||
|
||||
>>> demo = Demo()
|
||||
|
||||
When we first access the `value` attribute the corresponding method will be
|
||||
called:
|
||||
|
||||
>>> demo.value
|
||||
calculating
|
||||
42
|
||||
|
||||
On subsequent accesses the previously calculated value will be returned:
|
||||
|
||||
>>> demo.value
|
||||
42
|
||||
>>> demo.base = 15
|
||||
>>> demo.value
|
||||
42
|
||||
|
||||
Let's make sure the value is really calculated upon first access (and not
|
||||
already during compilation or object creation):
|
||||
|
||||
>>> demo2 = Demo()
|
||||
>>> demo2.base = 15
|
||||
>>> demo2.value
|
||||
calculating
|
||||
105
|
||||
>>> demo2.value
|
||||
105
|
||||
|
26
util/tests.py
Executable file
26
util/tests.py
Executable file
|
@ -0,0 +1,26 @@
|
|||
# $Id$
|
||||
|
||||
import unittest
|
||||
import doctest
|
||||
|
||||
import cybertools.util.property
|
||||
|
||||
|
||||
class Test(unittest.TestCase):
|
||||
"Basic tests for modules in the util package."
|
||||
|
||||
def testBasicStuff(self):
|
||||
pass
|
||||
|
||||
|
||||
def test_suite():
|
||||
flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS
|
||||
return unittest.TestSuite((
|
||||
#unittest.makeSuite(Test), # we don't need this
|
||||
#doctest.DocTestSuite(cybertools.util.property, optionflags=flags),
|
||||
doctest.DocFileSuite('adapter.txt', optionflags=flags),
|
||||
doctest.DocFileSuite('property.txt', optionflags=flags),
|
||||
))
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main(defaultTest='test_suite')
|
Loading…
Add table
Reference in a new issue