diff --git a/composer/README.txt b/composer/README.txt new file mode 100644 index 0000000..541f5a6 --- /dev/null +++ b/composer/README.txt @@ -0,0 +1,78 @@ +================================================================ +Composer - Building Complex Structures with Templates or Schemas +================================================================ + + ($Id$) + + >>> from cybertools.composer.base import Element, Compound, Template + >>> from cybertools.composer.client import Instance, Client + +We set up a very simple demonstration system using a PC configurator. +We start with two classes denoting a configuration and a simple +component within this configuration. + + >>> class Configuration(Template): + ... def __init__(self, name): + ... self.name = name + ... super(Configuration, self).__init__() + + >>> class BasicComponent(Element): + ... def __init__(self, name): + ... self.name = name + ... def __repr__(self): + ... return self.name + + >>> desktop = Configuration('Desktop') + >>> desktop.components.append(BasicComponent('case')) + >>> desktop.components.append(BasicComponent('mainboard')) + >>> desktop.components.append(BasicComponent('cpu')) + >>> desktop.components.append(BasicComponent('harddisk')) + +Now somebody wants to configure a desktop PC using this configuration. +We need another class denoting the product that will be created. + + >>> class Product(object): + ... def __init__(self, productId): + ... self.productId = productId + ... self.parts = {} + ... def __repr__(self): + ... return self.productId + + >>> c001 = Product('c001') + +The real stuff will be done by an instance that connects the product +(via the client) with the template. + + >>> class ConfigurationInstance(Instance): + ... def applyTemplate(self): + ... for c in self.template.components: + ... print c, self.parent.context.parts.get(c.name, '-') + +In this case we can directly use the basic client adapter for setting up the +connection. As we have only one template we also associate only one +instance with the client. + + >>> client = Client(c001) + >>> client.instances.append(ConfigurationInstance(client, desktop)) + >>> client.applyTemplates() + case - + mainboard - + cpu - + harddisk - + +If we have configured a CPU for our configuration this will be listed. + + >>> c001.parts['cpu'] = Product('z80') + >>> client.applyTemplates() + case - + mainboard - + cpu z80 + harddisk - + +Note that the ConfigurationInstance's applyTemplate() method is fairly +primitive. In a real-world application there usually are a lot more methods +that do more stuff. In our PC configurator application there might be +methods that just list components (e.g. to provide a user interface), +retrieve candidate products (e.g. CPUs) to use in the +configuration and store the user's selection in the context object. + diff --git a/composer/__init__.py b/composer/__init__.py new file mode 100644 index 0000000..4bc90fb --- /dev/null +++ b/composer/__init__.py @@ -0,0 +1,4 @@ +""" +$Id$ +""" + diff --git a/composer/base.py b/composer/base.py new file mode 100644 index 0000000..0a0af1b --- /dev/null +++ b/composer/base.py @@ -0,0 +1,55 @@ +# +# 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 +# + +""" +Basic classes for a complex template structures. + +$Id$ +""" + +from zope.interface import implements + +from cybertools.composer.interfaces import IComponent, IElement, ICompound +from cybertools.composer.interfaces import ITemplate + + +class Component(object): + + implements(IComponent) + + +class Element(Component): + + implements(IElement) + + +class Compound(Component): + + implements(ICompound) + + def __init__(self): + self.parts = [] + + +class Template(object): + + implements(ITemplate) + + def __init__(self): + self.components = [] + diff --git a/composer/client.py b/composer/client.py new file mode 100644 index 0000000..c4a6041 --- /dev/null +++ b/composer/client.py @@ -0,0 +1,56 @@ +# +# 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 +# + +""" +Base classes to be used for client adapters. + +$Id$ +""" + +from zope.interface import implements + +from cybertools.composer.interfaces import IInstance, IClient + + +class Instance(object): + + implements(IInstance) + + parent = None + template = None + + def __init__(self, parent, template): + self.parent = parent + self.template = template + + def applyTemplate(self, *args, **kw): + raise ValueError('To be implemented by subclass') + + +class Client(object): + + implements(IClient) + + def __init__(self, context): + self.context = context + self.instances = [] + + def applyTemplates(self, *args, **kw): + for inst in self.instances: + inst.applyTemplate(*args, **kw) + diff --git a/composer/interfaces.py b/composer/interfaces.py new file mode 100644 index 0000000..f80233a --- /dev/null +++ b/composer/interfaces.py @@ -0,0 +1,86 @@ +# +# 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 +# + +""" +Complex structures with templates/schemas. + +$Id$ +""" + +from zope.interface import Interface, Attribute + + +# template side interfaces + +class IComponent(Interface): + """ Basic building block. A component may be part of other components. + """ + + +class IElement(IComponent): + """ A final or elementary component, i.e. one that does not consist + of other components. + """ + + +class ICompound(IComponent): + """ A component that consists of other components. + """ + + parts = Attribute('An ordered sequence of the components this ' + 'object consists of') + + +class ITemplate(Interface): + """ A structure, consisting of components, that may be used as a + template/schema/blueprint for client objects. + """ + + components = Attribute('An ordered sequence of the components this ' + 'object is built upon') + + +# client side interfaces + +class IInstance(Interface): + """ Represents an object that uses a template. + """ + + parent = Attribute('The client this instance belongs to') + template = Attribute('Template this instance is associated with') + + def applyTemplate(*args, **kw): + """ Apply the template (in the parent's context). Note that this + method is just an example - instance classes may define + other methods that provide more specific actions. + """ + + +class IClient(Interface): + """ Represents an object that uses a set of templates via its instances. + """ + + context = Attribute('Object this client adapter has been created for') + instances = Attribute('An ordered or unordered sequence of instance objects') + + def applyTemplates(*args, **kw): + """ Apply the templates of all instances. Note that this + method is just an example - client classes may define + other methods that provide more specific actions. + """ + diff --git a/composer/schema/__init__.py b/composer/schema/__init__.py new file mode 100644 index 0000000..4bc90fb --- /dev/null +++ b/composer/schema/__init__.py @@ -0,0 +1,4 @@ +""" +$Id$ +""" + diff --git a/composer/tests.py b/composer/tests.py new file mode 100755 index 0000000..ccfbd9a --- /dev/null +++ b/composer/tests.py @@ -0,0 +1,22 @@ +# $Id$ + +import unittest, doctest +from zope.testing.doctestunit import DocFileSuite + + +class Test(unittest.TestCase): + "Basic tests for the cybertools.composer package." + + def testBasics(self): + pass + + +def test_suite(): + flags = doctest.NORMALIZE_WHITESPACE | doctest.ELLIPSIS + return unittest.TestSuite(( + unittest.makeSuite(Test), + DocFileSuite('README.txt', optionflags=flags), + )) + +if __name__ == '__main__': + unittest.main(defaultTest='test_suite') diff --git a/organize/README.txt b/organize/README.txt index 5b64ed5..dca6b51 100644 --- a/organize/README.txt +++ b/organize/README.txt @@ -51,4 +51,4 @@ Let's create an address and assign it to a person: Service Management ================== - >>> from cybertools.organize.service import ServiceInstance + >>> from cybertools.organize.service import Service diff --git a/organize/interfaces.py b/organize/interfaces.py index a13533c..6bc6d99 100644 --- a/organize/interfaces.py +++ b/organize/interfaces.py @@ -122,30 +122,29 @@ class ITask(Interface): # services - -class IService(Interface): - """ A general service definition or a group of service instances, +class IServiceGroup(Interface): + """ A group of related services or a general service definition, e.g. a regular bus service or a series of trainings. """ -class IServiceInstance(Interface): - """ A concrete service instance that clients may register with. +class IService(Interface): + """ A service that clients may register with. """ - service = Attribute('The service this object is an instance of.') + serviceGroup = Attribute('The service group this object is an instance of.') - seats = schema.Int( - title=_(u'Number of Seats'), + capacity = schema.Int( + title=_(u'Capacity'), description=_(u'The capacity (maximum number of clients) ' 'of this service; a negative number means: ' 'no restriction, i.e. unlimited capacity.'), required=False,) - availableSeats = Attribute('Available capacity, i.e. number of seats ' + availableCapacity = Attribute('Available capacity, i.e. number of seats ' 'still available; a negative number means: ' - 'no restriction, i.e. unlimited capacity. ' - 'Read-only attribute') + 'no restriction, i.e. unlimited capacity; ' + 'read-only') serviceProviders = Attribute('A collection of one or more service providers.') @@ -156,12 +155,12 @@ class IServiceInstance(Interface): def register(client): """ Register a client with this service. Return an IRegistration object if the registration is successful, otherwise - (e.g. if no seat is available) return None. + (e.g. if the service's capacity is exhausted) return None. """ -class IScheduledServiceInstance(IServiceInstance): - """ A service instance that starts at a certain date/time and +class IScheduledService(IService): + """ A service that starts at a certain date/time and usually ends a certain time later. """ @@ -187,13 +186,12 @@ class IRegistration(Interface): class IResource(Interface): """ A resource is needed by a service to be able to work, e.g. a room or a bus. A resource may have a limited capacity so that - at a certain time it may only be used by services to certain + at a certain time it may only be used by services to a certain extent. """ class IServiceProvider(Interface): - """ An entity, e.g. a person or an institution, that is responsible - for providing a service or a service instance. + """ A party, that is responsible for providing a service. """ diff --git a/organize/service.py b/organize/service.py new file mode 100644 index 0000000..7bafba7 --- /dev/null +++ b/organize/service.py @@ -0,0 +1,62 @@ +# +# 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 +# + +""" +Service instance classes. + +$Id$ +""" + +from zope.interface import implements + +from cybertools.organize.interfaces import IService +from cybertools.organize.interfaces import IScheduledService + + +class Registration(object): + + def __init__(self, client): + self.client = client + + +class Service(object): + + implements(IService) + + def __init__(self, seats=-1): + self.capacity = capacity + self.registrations = [] + + @property + def availableCapacity(self): + if self.capacity >= 0 and len(self.registrations) >= self.capacity: + return 0 + return self.capacity - len(self.registrations) + + def register(self, client): + if self.availableCapacity: + reg = Registration(client) + self.registrations.append(reg) + return reg + return None + + +class ScheduledService(Service): + + implements(IScheduledService) +