From 56fea28640ed12338e0c31234119caf8dd1eb80f Mon Sep 17 00:00:00 2001 From: Romain Bignon Date: Tue, 6 Apr 2010 21:17:51 +0200 Subject: [PATCH] split module loader in ModulesLoader and BackendsConfig classes --- weboob/backend.py | 4 +- weboob/capabilities/bank.py | 4 +- weboob/capabilities/messages.py | 6 ++- weboob/capabilities/travel.py | 4 +- weboob/capabilities/updatable.py | 3 +- weboob/modules.py | 90 +++++++++++++++++++------------- weboob/ouiboube.py | 40 +++++++++++--- weboob/tools/misc.py | 39 ++++++++++++++ 8 files changed, 141 insertions(+), 49 deletions(-) diff --git a/weboob/backend.py b/weboob/backend.py index 81d7780e..0b2908b7 100644 --- a/weboob/backend.py +++ b/weboob/backend.py @@ -20,7 +20,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. import re -class Backend: +class Backend(object): # Module name. NAME = None # Name of the maintainer of this module. @@ -29,6 +29,8 @@ class Backend: EMAIL = '' # Version of module (for information only). VERSION = '' + # Description + DESCRIPTION = '' # License of this module. LICENSE = '' # Configuration required for this module. # Values must be ConfigField diff --git a/weboob/capabilities/bank.py b/weboob/capabilities/bank.py index 22c8e62e..cf14ddf8 100644 --- a/weboob/capabilities/bank.py +++ b/weboob/capabilities/bank.py @@ -18,6 +18,8 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. """ +from .cap import ICap + class AccountNotFound(Exception): pass class Account(object): @@ -69,7 +71,7 @@ class Operation(object): self.amount = amount -class ICapBank: +class ICapBank(ICap): def iter_accounts(self): raise NotImplementedError() diff --git a/weboob/capabilities/messages.py b/weboob/capabilities/messages.py index 7e905883..cb01785a 100644 --- a/weboob/capabilities/messages.py +++ b/weboob/capabilities/messages.py @@ -21,6 +21,8 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. import datetime import time +from .cap import ICap + class Message: def __init__(self, thread_id, _id, title, sender, date=None, reply_id=u'', content=u'', signature=u''): self.thread_id = unicode(thread_id) @@ -80,7 +82,7 @@ class Message: self.id, self.title, self.date, self.sender) return result.encode('utf-8') -class ICapMessages: +class ICapMessages(ICap): def iter_new_messages(self): """ Iterates on new messages from last time this function has been called. @@ -95,6 +97,6 @@ class ICapMessages: """ raise NotImplementedError() -class ICapMessagesReply: +class ICapMessagesReply(ICap): def post_reply(self, to, message): raise NotImplementedError() diff --git a/weboob/capabilities/travel.py b/weboob/capabilities/travel.py index 193d86c9..9103da38 100644 --- a/weboob/capabilities/travel.py +++ b/weboob/capabilities/travel.py @@ -20,7 +20,9 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. from datetime import time -class ICapTravel: +from .cap import ICap + +class ICapTravel(ICap): def iter_station_search(self, pattern): """ Iterates on search results of stations. diff --git a/weboob/capabilities/updatable.py b/weboob/capabilities/updatable.py index b0ef785d..e06e136b 100644 --- a/weboob/capabilities/updatable.py +++ b/weboob/capabilities/updatable.py @@ -18,8 +18,9 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. """ +from .cap import ICap -class ICapUpdatable: +class ICapUpdatable(ICap): snapshots = {} def take_snapshot(self, name, collection): diff --git a/weboob/modules.py b/weboob/modules.py index 8feb53b7..a2669627 100644 --- a/weboob/modules.py +++ b/weboob/modules.py @@ -22,10 +22,11 @@ import re import os from ConfigParser import SafeConfigParser from logging import warning, debug -from types import ClassType import weboob.backends as backends from weboob.backend import Backend +from weboob.capabilities.cap import ICap +from weboob.tools.misc import itersubclasses class Module: def __init__(self, name, module): @@ -34,7 +35,7 @@ class Module: self.klass = None for attrname in dir(self.module): attr = getattr(self.module, attrname) - if isinstance(attr, ClassType) and issubclass(attr, Backend) and attr != Backend: + if isinstance(attr, type) and issubclass(attr, Backend) and attr != Backend: self.klass = attr if not self.klass: @@ -43,6 +44,26 @@ class Module: def get_name(self): return self.klass.NAME + def get_maintainer(self): + return '%s <%s>' % (self.klass.MAINTAINER, self.klass.EMAIL) + + def get_version(self): + return self.klass.VERSION + + def get_description(self): + return self.klass.DESCRIPTION + + def get_license(self): + return self.klass.LICENSE + + def get_config(self): + return self.klass.CONFIG + + def iter_caps(self): + for subclass in itersubclasses(self.klass): + if issubclass(subclass, ICap): + yield subclass + def has_caps(self, *caps): for c in caps: if issubclass(self.klass, c): @@ -52,6 +73,36 @@ class Module: def create_backend(self, weboob, name, config): return self.klass(weboob, name, config) +class BackendsConfig: + def __init__(self, confpath): + self.confpath = confpath + + def iter_backends(self): + config = SafeConfigParser() + config.read(self.confpath) + for name in config.sections(): + params = dict(config.items(name)) + try: + yield name, params.pop('_type'), params + except KeyError: + warning("Missing field '_type' for backend '%s'", name) + continue + + def add_backend(self, name, _type, params): + config = SafeConfigParser() + config.read(self.confpath) + config.add_section(name) + config.set(name, '_type', _type) + for key, value in params.iteritems(): + config.set(name, key, value) + config.save(self.confpath) + + def remove_backend(self, name): + config = SafeConfigParser() + config.read(self.confpath) + config.remove_section(name) + config.save(self.confpath) + class ModulesLoader: def __init__(self): self.modules = {} @@ -75,38 +126,3 @@ class ModulesLoader: return self.modules[module.get_name()] = module debug('Loaded module %s (%s)' % (name, module.module.__name__)) - - def load_backends(self, confpath, caps, names): - config = SafeConfigParser() - config.read(confpath) - backends = {} - for name in config.sections(): - params = dict(config.items(name)) - try: - module = self.modules[params['_type']] - except KeyError: - warning('Unable to find module %s', name) - continue - - # Check conditions - if (not caps is None and not module.has_caps(caps)) or \ - (not names is None and not module.name in name): - continue - - try: - backends[name] = module.create_backend(self, name, params) - except Exception, e: - warning('Unable to load %s backend: %s' % (name, e)) - - return backends - - def load_modules_as_backends(self, caps, names): - backends = {} - for name, module in self.modules.iteritems(): - if (caps is None or module.has_caps(caps)) and \ - (names is None or module.name in names): - try: - backends[module.name] = module.create_backend(self, module.name, {}) - except Exception, e: - warning('Unable to load %s backend: %s' % (name, e)) - return backends diff --git a/weboob/ouiboube.py b/weboob/ouiboube.py index 952b3147..e11f28a9 100644 --- a/weboob/ouiboube.py +++ b/weboob/ouiboube.py @@ -20,34 +20,62 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. import os -from weboob.modules import ModulesLoader +from weboob.modules import ModulesLoader, BackendsConfig from weboob.scheduler import Scheduler class Weboob: WORKDIR = os.path.join(os.path.expanduser('~'), '.weboob') BACKENDS_FILENAME = 'backends' - def __init__(self, app_name, workdir=WORKDIR, scheduler=None): + def __init__(self, app_name, workdir=WORKDIR, backends_filename=None, scheduler=None): self.app_name = app_name self.workdir = workdir self.backends = {} + # Scheduler if scheduler is None: scheduler = Scheduler() self.scheduler = scheduler + # Modules loader self.modules_loader = ModulesLoader() self.modules_loader.load() - def get_backends_filename(self): - return os.path.join(self.workdir, self.BACKENDS_FILENAME) + # Backends config + if not backends_filename: + backends_filename = os.path.join(self.workdir, self.BACKENDS_FILENAME) + elif not backends_filename.startswith('/'): + backends_filename = os.path.join(self.workdir, backends_filename) + self.backends_config = BackendsConfig(backends_filename) def load_backends(self, caps=None, names=None): - self.backends.update(self.modules_loader.load_backends(self.get_backends_filename(), caps, names)) + for name, _type, params in self.backends_config.iter_backends(): + try: + module = self.modules_loader.modules[_type] + except KeyError: + warning('Unable to find module %s', name) + continue + + # Check conditions + if (not caps is None and not module.has_caps(caps)) or \ + (not names is None and not module.name in name): + continue + + try: + self.backends[name] = module.create_backend(self, name, params) + except Exception, e: + warning('Unable to load %s backend: %s' % (name, e)) + return self.backends def load_modules(self, caps=None, names=None): - self.backends.update(self.modules_loader.load_modules_as_backends(caps, names)) + for name, module in self.modules_loader.modules.iteritems(): + if (caps is None or module.has_caps(caps)) and \ + (names is None or module.name in names): + try: + self.backends[module.name] = module.create_backend(self, module.name, {}) + except Exception, e: + warning('Unable to load %s backend: %s' % (name, e)) return self.backends def iter_backends(self, caps=None): diff --git a/weboob/tools/misc.py b/weboob/tools/misc.py index 94c0ec46..a8172d68 100644 --- a/weboob/tools/misc.py +++ b/weboob/tools/misc.py @@ -44,3 +44,42 @@ def local2utc(d): d = d.astimezone(tz.tzutc()) return d +def itersubclasses(cls, _seen=None): + """ + itersubclasses(cls) + + Generator over all subclasses of a given class, in depth first order. + + >>> list(itersubclasses(int)) == [bool] + True + >>> class A(object): pass + >>> class B(A): pass + >>> class C(A): pass + >>> class D(B,C): pass + >>> class E(D): pass + >>> + >>> for cls in itersubclasses(A): + ... print(cls.__name__) + B + D + E + C + >>> # get ALL (new-style) classes currently defined + >>> [cls.__name__ for cls in itersubclasses(object)] #doctest: +ELLIPSIS + ['type', ...'tuple', ...] + """ + + if not isinstance(cls, type): + raise TypeError('itersubclasses must be called with ' + 'new-style classes, not %.100r' % cls) + if _seen is None: _seen = set() + try: + subs = cls.__subclasses__() + except TypeError: # fails only when cls is type + subs = cls.__subclasses__(cls) + for sub in subs: + if sub not in _seen: + _seen.add(sub) + yield sub + for sub in itersubclasses(sub, _seen): + yield sub