diff --git a/composer/base.py b/composer/base.py index 21a8ae8..fd255f5 100644 --- a/composer/base.py +++ b/composer/base.py @@ -17,7 +17,7 @@ # """ -Basic classes for a complex template structures. +Basic classes for complex template structures. $Id$ """ @@ -43,14 +43,18 @@ class Compound(Component): implements(ICompound) + componentStorage = Jeep + def __init__(self): - self.parts = Jeep() + self.parts = self.componentStorage() class Template(object): implements(ITemplate) - def __init__(self): - self.components = Jeep() + componentStorage = Jeep + + def __init__(self): + self.components = self.componentStorage() diff --git a/composer/instance.py b/composer/instance.py index 8eebcab..4a39c04 100644 --- a/composer/instance.py +++ b/composer/instance.py @@ -31,6 +31,9 @@ class Instance(object): implements(IInstance) + templateStorage = dict + templateAttributeName = '__ctc_templates__' + aspect = 'composer.default' def __init__(self, context): @@ -38,11 +41,12 @@ class Instance(object): self.instances = [] def setTemplate(self, template): - templates = getattr(self.context, '__templates__', {}) + templates = getattr(self.context, + self.templateAttributeName, self.templateStorage()) templates.setdefault(self.aspect, template) - self.context.__templates__ = templates + setattr(self.context, self.templateAttributeName, templates) def getTemplate(self): - templates = getattr(self.context, '__templates__', {}) + templates = getattr(self.context, self.templateAttributeName, {}) return templates.get(self.aspect, None) template = property(getTemplate, setTemplate) diff --git a/organize/interfaces.py b/organize/interfaces.py index 6bc6d99..048fb6f 100644 --- a/organize/interfaces.py +++ b/organize/interfaces.py @@ -122,6 +122,13 @@ class ITask(Interface): # services +class IServiceManager(Interface): + """ A manager or container for a set of services. + """ + + services = Attribute('A collection of services managed by this object.') + + class IServiceGroup(Interface): """ A group of related services or a general service definition, e.g. a regular bus service or a series of trainings. diff --git a/organize/service.py b/organize/service.py index 7bafba7..e929937 100644 --- a/organize/service.py +++ b/organize/service.py @@ -17,21 +17,20 @@ # """ -Service instance classes. +Service management classes. $Id$ """ from zope.interface import implements -from cybertools.organize.interfaces import IService -from cybertools.organize.interfaces import IScheduledService +from cybertools.organize.interfaces import IServiceManager +from cybertools.organize.interfaces import IService, IScheduledService -class Registration(object): +class ServiceManager(object): - def __init__(self, client): - self.client = client + implements(IServiceManager) class Service(object): @@ -60,3 +59,9 @@ class ScheduledService(Service): implements(IScheduledService) + +class Registration(object): + + def __init__(self, client): + self.client = client + diff --git a/stateful/definition.py b/stateful/definition.py index e8dfa25..123411c 100644 --- a/stateful/definition.py +++ b/stateful/definition.py @@ -65,6 +65,9 @@ class StatesDefinition(object): return [ self._transitions[t] for t in self._states[state].transitions ] +def registerStatesDefinition(id, definition): + statesDefinitions[id] = definition + statesDefinitions = { 'default': StatesDefinition(), } diff --git a/xedit/configure.zcml b/xedit/configure.zcml index 5490be7..e5cf3ad 100644 --- a/xedit/configure.zcml +++ b/xedit/configure.zcml @@ -16,17 +16,17 @@ - - + - + --> .py file with an implementation and a +corresponding .txt file with a description that can be run as a +doctest. + +The doctests can be run by issuing + + python cybertools/zutil/tests.py + +in the directory above the cybertools directory or via + + bin/test -vs cybertools.zutil + +from within a Zope 3 instance that contains the cybertools package in +its python path. diff --git a/zutil/__init__.py b/zutil/__init__.py new file mode 100644 index 0000000..38314f3 --- /dev/null +++ b/zutil/__init__.py @@ -0,0 +1,3 @@ +""" +$Id$ +""" diff --git a/zutil/jeep.py b/zutil/jeep.py new file mode 100644 index 0000000..c8b20cc --- /dev/null +++ b/zutil/jeep.py @@ -0,0 +1,123 @@ +# +# 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 general purpose (thus 'Jeep') class that provides most of the interfaces +of sequences and dictionaries and in addition allows attribute access to +the dictionary entries. + +$Id$ +""" + +from persistent.list import PersistentList +from BTrees.OOBTree import OOBTree + +_notfound = object() +_nodefault = object() + + +class Jeep(object): + + _attributes = ('_sequence', '_mapping') + + def __init__(self, seq=[]): + self._sequence = PersistentList() + self._mapping = OOBTree() + for item in seq: + attr, value = item + setattr(self, attr, value) + + def __len__(self): + return len(self._sequence) + + def __iter__(self): + for key in self._sequence: + yield self[key] + + def __getattr__(self, attr, default=_nodefault): + value = self._mapping.get(attr, _notfound) + if value is _notfound: + if default is _nodefault: + raise AttributeError(attr) + else: + return default + return value + + def __setattr__(self, attr, value): + if attr in self._attributes: + object.__setattr__(self, attr, value) + else: + if getattr(self, attr, _notfound) is _notfound: + self._sequence.append(attr) + self._mapping[attr] = value + + def __delattr__(self, attr): + del self._sequence[self.index(attr)] + del self._mapping[attr] + + def __getitem__(self, key): + if type(key) in (int, long): + return getattr(self, self._sequence[key]) + value = getattr(self, key, _notfound) + if value is _notfound: + raise KeyError(key) + return value + + def __setitem__(self, key, value): + setattr(self, key, value) + + def __delitem__(self, key): + delattr(self, key) + + def __contains__(self, key): + return getattr(self, key, _notfound) is not _notfound + + def keys(self): + return [key for key in self._sequence] + + def values(self): + return list(self) + + def items(self): + return [(k, self[k]) for k in self._sequence] + + def get(self, key, default=None): + return getattr(self, key, default) + + def index(self, key): + return self._sequence.index(key) + + def append(self, obj): + self.insert(len(self), obj) + + def insert(self, idx, obj): + key = getattr(obj, '__name__', getattr(obj, 'name', _notfound)) + if key is _notfound: + raise AttributeError("No name attribute present") + if key in self: + raise ValueError("Object already present") + self._sequence.insert(idx, key) + self._mapping[key] = obj + + def pop(self, key=-1): + value = self[key] + if type(key) in (int, long): + key = self._sequence[key] + delattr(self, key) + return value + diff --git a/zutil/jeep.txt b/zutil/jeep.txt new file mode 100644 index 0000000..456780e --- /dev/null +++ b/zutil/jeep.txt @@ -0,0 +1,141 @@ +============================== +Jeep - a General Purpose Class +============================== + +$Id$ + + >>> from cybertools.zutil.jeep import Jeep + >>> jeep = Jeep() + + >>> jeep.first = 'first value' + >>> jeep.second = 'second value' + + >>> jeep.first + 'first value' + +In addition to the usual access via dot notation all attributes can be +accessed via dictionary notation: + +The third type of interface provided by Jeep objects is the sequence or +iterator interface. Converting a jeep object to a list iterates over its +values (that is different from the dictionary behaviour, but is what +you want usually; use the ``.keys()`` method to get at the keys, see below): + + >>> list(jeep) + ['first value', 'second value'] + +Direct index access to certain entries gives the corresponding value, +not the key: + + >>> jeep[1] + 'second value' + +Changing Jeep Objects +--------------------- + +Assignment by dictionary or attribute access appends the newly assigned +attribute: + + >>> jeep['third'] = 'third value' + >>> jeep.third + 'third value' + + >>> list(jeep) + ['first value', 'second value', 'third value'] + +Assigning a new value to an already existing attribute does not change the +order but only changes the attribute's value + + >>> jeep.second = 'new second value' + >>> list(jeep) + ['first value', 'new second value', 'third value'] + >>> jeep[1] + 'new second value' + +More Dictionary Methods +----------------------- + + >>> jeep.keys() + ['first', 'second', 'third'] + + >>> jeep.values() + ['first value', 'new second value', 'third value'] + + >>> jeep.items() + [('first', 'first value'), ('second', 'new second value'), ('third', 'third value')] + + >>> jeep.get('second') + 'new second value' + >>> jeep.get('fourth', 'default') + 'default' + >>> jeep.get('fourth') is None + True + >>> jeep['fourth'] + Traceback (most recent call last): + ... + KeyError: 'fourth' + + >>> dict(jeep) + {'second': 'new second value', 'third': 'third value', 'first': 'first value'} + +More Methods and Operators +-------------------------- + + >>> 'third' in jeep + True + + >>> jeep.pop() + 'third value' + >>> len(jeep) + 2 + + >>> 'third' in jeep + False + + >>> jeep.index('second') + 1 + >>> jeep.index('third') + Traceback (most recent call last): + ... + ValueError: ...not in list + +Sequence Additions with Named Objects +------------------------------------- + +Objects that have a ``__name__`` attribute can be appended +to a Jeep object as the dictionary key can be obtained from these attribute. + + >>> class Term(object): + ... def __init__(self, token, title=None, value=None): + ... self.__name__ = self.token = token + ... self.title = title or token + ... self.value = value or title or token + + >>> t1 = Term('term1', 'title 1') + >>> jeep.append(t1) + >>> jeep.keys() + ['first', 'second', 'term1'] + >>> jeep.term1.title + 'title 1' + + >>> jeep.insert(1, Term('term2', 'title 2')) + >>> jeep.keys() + ['first', 'term2', 'second', 'term1'] + >>> jeep[1].title + 'title 2' + +Inserting or appending an object with a name that's already present raises +an exception: + + >>> jeep.append(t1) + Traceback (most recent call last): + ... + ValueError: ...already present + +Constructors +------------ + + >>> jeep2 = Jeep((('f', '1st'), ('s', '2nd'), ('t', '3rd'))) + >>> list(jeep2) + ['1st', '2nd', '3rd'] + diff --git a/zutil/tests.py b/zutil/tests.py new file mode 100755 index 0000000..2107bfb --- /dev/null +++ b/zutil/tests.py @@ -0,0 +1,25 @@ +# $Id$ + +import unittest, doctest +from zope.testing.doctestunit import DocFileSuite + +import cybertools.zutil.jeep + + +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.zutil.property, optionflags=flags), + DocFileSuite('jeep.txt', optionflags=flags), + )) + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite')