From 7ea411b9234f58c0632ef8b0c2632ee55dd9f2d0 Mon Sep 17 00:00:00 2001 From: helmutm Date: Wed, 1 Aug 2007 13:05:27 +0000 Subject: [PATCH] set up Configurator stuff git-svn-id: svn://svn.cy55.de/Zope3/src/loops/trunk@1858 fd906abe-77d9-0310-91a1-e0d9ade77398 --- agent/README.txt | 98 ++++++++++++++++++++++++++++++++++++++++++------ agent/config.py | 79 ++++++++++++++++++++++++++++++++------ agent/core.py | 4 +- agent/tests.py | 1 + 4 files changed, 156 insertions(+), 26 deletions(-) diff --git a/agent/README.txt b/agent/README.txt index b6dfa6a..9767b50 100644 --- a/agent/README.txt +++ b/agent/README.txt @@ -26,8 +26,92 @@ This means that all calls to services (like crawler, transporter, ...) return a deferred that must be supplied with a callback method (and in most cases also an errback method). - >>> from loops.agent.core import Agent - >>> agent = Agent() + >>> from loops.agent import core + >>> agent = core.Agent() + + +Configuration Management +======================== + +Functionality + +- Storage of configuration parameters +- Interface to the browser-based user interface that allows the + editing of configuration parameters + +All configuration parameters are always accessible via the ``config`` +attribute of the agent object. + + >>> config = agent.config + +This already provides all needed sections (transport, crawl, ui), so +we can directly put information into these sections by loading a +string with the corresponding assignment. + + >>> config.load('transport.url = "http://loops.cy55.de"') + >>> config.transport.url + 'http://loops.cy55.de' + +This setting may also contain indexed access; thus we can model +configuration parameters with multiple instances (like crawling +jobs). + + >>> config.load(''' + ... crawl[0].type = "filesystem" + ... crawl[0].directory = "documents/projects" + ... ''') + >>> config.crawl[0].type + 'filesystem' + >>> config.crawl[0].directory + 'documents/projects' + +Subsections are created automatically when they are first accessed. + + >>> config.load('ui.web.port = 8081') + >>> config.ui.web.port + 8081 + +The ``setdefault()`` method allows to retrieve a value and set +it with a default if not found, in one statement. + + >>> config.ui.web.setdefault('port', 8080) + 8081 + >>> config.transport.setdefault('user', 'loops') + 'loops' + +We can output a configuration in a form that is ready for loading +just by converting it to a string representation. + + >>> print config + crawl[0].directory = 'documents/projects' + crawl[0].type = 'filesystem' + transport.url = 'http://loops.cy55.de' + transport.user = 'loops' + ui.web.port = 8081 + +The configuration may also be saved to a file - +for testing purposes let's use the loops.agent package directory +for storage; normally it would be stored in the users home directory. + + >>> import os + >>> os.environ['HOME'] = os.path.dirname(core.__file__) + + >>> config.save() + + >>> fn = config.getDefaultConfigFile() + >>> fn + '....loops.agent.cfg' + + >>> print open(fn).read() + crawl[0].directory = 'documents/projects' + crawl[0].type = 'filesystem' + transport.url = 'http://loops.cy55.de' + transport.user = 'loops' + ui.web.port = 8081 + +Cleaning up up... + + >>> os.unlink(fn) Scheduling @@ -185,16 +269,6 @@ Configuration (per install/update job) - package names -Configuration Management -======================== - -Functionality - -- Storage of configuration parameters -- Interface to the browser-based user interface that allows the - editing of configuration parameters - - Browser-based User Interface ============================ diff --git a/agent/config.py b/agent/config.py index b201788..887af3e 100644 --- a/agent/config.py +++ b/agent/config.py @@ -22,6 +22,7 @@ Management of agent configuration. $Id$ """ +import os from zope.interface import implements from loops.agent.interfaces import IConfigurator @@ -30,21 +31,75 @@ class Configurator(object): implements(IConfigurator) - def loadConfiguration(self): - pass + def __init__(self, *sections, **kw): + for s in sections: + setattr(self, s, ConfigSection()) + self.filename = kw.get('filename') - def addConfigOption(self, key, value): - setattr(self, key, value) + def load(self, p=None, filename=None): + if p is None: + fn = self.getConfigFile(filename) + if fn is not None: + f = open(fn, 'r') + p = f.read() + f.close() + if p is None: + return + exec p in self.__dict__ - def getConfigOption(self, key, value): - return getattr(self, key, None) + def save(self, filename=None): + fn = self.getConfigFile(filename) + if fn is None: + fn = self.getDefaultConfigFile() + if fn is not None: + f = open(fn, 'w') + f.write(repr(self)) + f.close() + + def __repr__(self): + result = [] + for name, value in self.__dict__.items(): + if isinstance(value, ConfigSection): + value.collect(name, result) + return '\n'.join(sorted(result)) + + def getConfigFile(self, filename=None): + if filename is not None: + self.filename = filename + if self.filename is None: + fn = self.getDefaultConfigFile() + if os.path.isfile(fn): + self.filename = fn + return self.filename + + def getDefaultConfigFile(self): + return os.path.join(os.path.expanduser('~'), '.loops.agent.cfg') -conf = Configurator() +class ConfigSection(list): -# this is just for convenience during the development phase, -# thus we can retrieve the port easily via ``conf.ui.web.port`` -conf.addConfigOption('ui', Configurator()) -conf.ui.addConfigOption('web', Configurator()) -conf.ui.web.addConfigOption('port', 10095) + def __getattr__(self, attr): + value = ConfigSection() + setattr(self, attr, value) + return value + + def __getitem__(self, idx): + while idx >= len(self): + self.append(ConfigSection()) + return list.__getitem__(self, idx) + + def setdefault(self, attr, value): + if attr not in self.__dict__: + setattr(self, attr, value) + return value + return getattr(self, attr) + + def collect(self, ident, result): + for idx, element in enumerate(self): + element.collect('%s[%i]' % (ident, idx), result) + for name, value in self.__dict__.items(): + if isinstance(value, ConfigSection): + value.collect('%s.%s' % (ident, name), result) + elif isinstance(value, (str, int)): + result.append('%s.%s = %s' % (ident, name, repr(value))) diff --git a/agent/core.py b/agent/core.py index 9df60ea..2b85374 100644 --- a/agent/core.py +++ b/agent/core.py @@ -33,7 +33,7 @@ class Agent(object): implements(IAgent) def __init__(self): - configurator = self.configurator = Configurator() - configurator.loadConfiguration() + config = self.config = Configurator('ui', 'crawl', 'transport') + config.load() self.scheduler = Scheduler() diff --git a/agent/tests.py b/agent/tests.py index 01f8ff7..7feff39 100755 --- a/agent/tests.py +++ b/agent/tests.py @@ -1,3 +1,4 @@ +#! /usr/bin/env python # # Run with ``trial2.4 tests.py`` to execute the twisted unit tests. # Run with ``python tests.py`` to execute the doctests.