From 9f5c9aeebc3a2fa016b7c9e05686c62b03f03389 Mon Sep 17 00:00:00 2001 From: Romain Bignon Date: Tue, 24 Aug 2010 11:03:39 +0200 Subject: [PATCH] renamed BackendsLoader to ModulesLoader --- weboob/applications/weboobcfg/weboobcfg.py | 14 +- weboob/core/backends.py | 225 --------------------- weboob/core/backendscfg.py | 103 ++++++++++ weboob/core/modules.py | 141 +++++++++++++ weboob/core/ouiboube.py | 19 +- weboob/tools/application/base.py | 7 +- weboob/tools/application/console.py | 7 +- weboob/tools/application/qt/backendcfg.py | 19 +- weboob/tools/backend.py | 18 ++ 9 files changed, 301 insertions(+), 252 deletions(-) delete mode 100644 weboob/core/backends.py create mode 100644 weboob/core/backendscfg.py create mode 100644 weboob/core/modules.py diff --git a/weboob/applications/weboobcfg/weboobcfg.py b/weboob/applications/weboobcfg/weboobcfg.py index 21b2ed9a..12a58982 100644 --- a/weboob/applications/weboobcfg/weboobcfg.py +++ b/weboob/applications/weboobcfg/weboobcfg.py @@ -46,8 +46,8 @@ class WeboobCfg(ConsoleApplication): @ConsoleApplication.command('Add a configured backend') def command_add(self, name, *options): - self.weboob.backends_loader.load_all() - if name not in [_name for _name, backend in self.weboob.backends_loader.loaded.iteritems()]: + self.weboob.modules_loader.load_all() + if name not in [_name for _name, backend in self.weboob.modules_loader.loaded.iteritems()]: logging.error(u'Backend "%s" does not exist.' % name) return 1 @@ -61,7 +61,7 @@ class WeboobCfg(ConsoleApplication): return 1 params[key] = value # ask for params non-specified on command-line arguments - backend = self.weboob.backends_loader.get_or_load_backend(name) + backend = self.weboob.modules_loader.get_or_load_module(name) asked_config = False for key, value in backend.config.iteritems(): if not asked_config: @@ -105,7 +105,7 @@ class WeboobCfg(ConsoleApplication): def command_listconfigured(self): self.set_default_formatter('table') for instance_name, name, params in sorted(self.weboob.backends_config.iter_backends()): - backend = self.weboob.backends_loader.get_or_load_backend(name) + backend = self.weboob.modules_loader.get_or_load_module(name) row = OrderedDict([('Instance name', instance_name), ('Backend name', name), ('Configuration', ', '.join('%s=%s' % (key, ('*****' if backend.config[key].is_masked else value)) for key, value in params.iteritems())), @@ -127,8 +127,8 @@ class WeboobCfg(ConsoleApplication): @ConsoleApplication.command('Show available backends') def command_backends(self, *caps): self.set_default_formatter('table') - self.weboob.backends_loader.load_all() - for name, backend in sorted(self.weboob.backends_loader.loaded.iteritems()): + self.weboob.modules_loader.load_all() + for name, backend in sorted(self.weboob.modules_loader.loaded.iteritems()): if caps and not self.caps_included(backend.iter_caps(), caps): continue row = OrderedDict([('Name', name), @@ -140,7 +140,7 @@ class WeboobCfg(ConsoleApplication): @ConsoleApplication.command('Display information about a backend') def command_info(self, name): try: - backend = self.weboob.backends_loader.get_or_load_backend(name) + backend = self.weboob.modules_loader.get_or_load_module(name) except KeyError: logging.error('No such backend: "%s"' % name) return 1 diff --git a/weboob/core/backends.py b/weboob/core/backends.py deleted file mode 100644 index 26516369..00000000 --- a/weboob/core/backends.py +++ /dev/null @@ -1,225 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright(C) 2010 Romain Bignon -# -# 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, version 3 of the License. -# -# 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. - - -from __future__ import with_statement - -from ConfigParser import RawConfigParser -import logging -from logging import debug, error, exception, warning -import os -import re -import stat - -from weboob.capabilities.base import IBaseCap -from weboob.tools.backend import BaseBackend - - -__all__ = ['Backend', 'BackendsConfig', 'BackendsLoader'] - - -class Backend(object): - def __init__(self, package): - self.package = package - self.klass = None - for attrname in dir(self.package): - attr = getattr(self.package, attrname) - if isinstance(attr, type) and issubclass(attr, BaseBackend) and attr != BaseBackend: - self.klass = attr - if not self.klass: - raise ImportError('%s is not a backend (no BaseBackend class found)' % package) - - @property - def name(self): - return self.klass.NAME - - @property - def maintainer(self): - return '%s <%s>' % (self.klass.MAINTAINER, self.klass.EMAIL) - - @property - def version(self): - return self.klass.VERSION - - @property - def description(self): - return self.klass.DESCRIPTION - - @property - def license(self): - return self.klass.LICENSE - - @property - def config(self): - return self.klass.CONFIG - - @property - def website(self): - if self.klass.BROWSER and self.klass.BROWSER.DOMAIN: - return '%s://%s' % (self.klass.BROWSER.PROTOCOL, self.klass.BROWSER.DOMAIN) - else: - return None - - @property - def icon_path(self): - if self.klass.ICON is None: - try: - import xdg.IconTheme - except ImportError: - debug(u'Python xdg module was not found. Please install it to read icon files.') - else: - self.klass.ICON = xdg.IconTheme.getIconPath(self.klass.NAME) - return self.klass.ICON - - def iter_caps(self): - for cap in self.klass.__bases__: - if issubclass(cap, IBaseCap) and cap != IBaseCap: - yield cap - - def has_caps(self, *caps): - for c in caps: - if (isinstance(c, basestring) and c in [cap.__name__ for cap in self.iter_caps()]) or \ - (type(c) == type and issubclass(self.klass, c)): - return True - return False - - def create_instance(self, weboob, instance_name, config, storage): - backend_instance = self.klass(weboob, instance_name, config, storage) - debug(u'Created backend instance "%s" for backend "%s"' % (instance_name, self.name)) - return backend_instance - - -class BackendsConfig(object): - class WrongPermissions(Exception): - pass - - def __init__(self, confpath): - self.confpath = confpath - try: - mode = os.stat(confpath).st_mode - except OSError: - os.mknod(confpath, 0600) - else: - if mode & stat.S_IRGRP or mode & stat.S_IROTH: - raise self.WrongPermissions( - u'Weboob will not start until config file %s is readable by group or other users.' % confpath) - - def iter_backends(self): - config = RawConfigParser() - config.read(self.confpath) - for instance_name in config.sections(): - params = dict(config.items(instance_name)) - try: - backend_name = params.pop('_backend') - except KeyError: - try: - backend_name = params.pop('_type') - logging.warning(u'Please replace _type with _backend in your config file "%s", for backend "%s"' % ( - self.confpath, backend_name)) - except KeyError: - warning('Missing field "_backend" for configured backend "%s"', instance_name) - continue - yield instance_name, backend_name, params - - def add_backend(self, instance_name, backend_name, params, edit=False): - if not instance_name: - raise ValueError(u'Please give a name to the configured backend.') - config = RawConfigParser() - config.read(self.confpath) - if not edit: - config.add_section(instance_name) - config.set(instance_name, '_backend', backend_name) - for key, value in params.iteritems(): - config.set(instance_name, key, value) - with open(self.confpath, 'wb') as f: - config.write(f) - - def edit_backend(self, instance_name, backend_name, params): - return self.add_backend(instance_name, backend_name, params, True) - - def get_backend(self, instance_name): - config = RawConfigParser() - config.read(self.confpath) - if not config.has_section(instance_name): - raise KeyError(u'Configured backend "%s" not found' % instance_name) - - items = dict(config.items(instance_name)) - - try: - backend_name = items.pop('_backend') - except KeyError: - try: - backend_name = items.pop('_type') - logging.warning(u'Please replace _type with _backend in your config file "%s"' % self.confpath) - except KeyError: - warning('Missing field "_backend" for configured backend "%s"', instance_name) - raise KeyError(u'Configured backend "%s" not found' % instance_name) - return backend_name, items - - def remove_backend(self, instance_name): - config = RawConfigParser() - config.read(self.confpath) - config.remove_section(instance_name) - with open(self.confpath, 'w') as f: - config.write(f) - - -class BackendsLoader(object): - def __init__(self): - self.loaded = {} - - def get_or_load_backend(self, backend_name): - if backend_name not in self.loaded: - self.load_backend(backend_name) - if backend_name in self.loaded: - return self.loaded[backend_name] - else: - return None - - def iter_existing_backend_names(self): - try: - import weboob.backends - except ImportError: - return - for path in weboob.backends.__path__: - regexp = re.compile('^%s/([\w\d_]+)$' % path) - for root, dirs, files in os.walk(path): - m = regexp.match(root) - if m and '__init__.py' in files: - yield m.group(1) - - def load_all(self): - for existing_backend_name in self.iter_existing_backend_names(): - self.load_backend(existing_backend_name) - - def load_backend(self, backend_name): - try: - package_name = 'weboob.backends.%s' % backend_name - backend = Backend(__import__(package_name, fromlist=[str(package_name)])) - except ImportError, e: - msg = u'Unable to load backend "%s": %s' % (backend_name, e) - if logging.root.level == logging.DEBUG: - exception(msg) - return - else: - error(msg) - return - if backend.name in self.loaded: - debug('Backend "%s" is already loaded from %s' % (backend_name, backend.package.__path__[0])) - return - self.loaded[backend.name] = backend - debug('Loaded backend "%s" from %s' % (backend_name, backend.package.__path__[0])) diff --git a/weboob/core/backendscfg.py b/weboob/core/backendscfg.py new file mode 100644 index 00000000..5be415fa --- /dev/null +++ b/weboob/core/backendscfg.py @@ -0,0 +1,103 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2010 Romain Bignon +# +# 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, version 3 of the License. +# +# 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. + + +from __future__ import with_statement + +import stat +import os +from ConfigParser import RawConfigParser +from logging import warning + +__all__ = ['BackendsConfig'] + + +class BackendsConfig(object): + class WrongPermissions(Exception): + pass + + def __init__(self, confpath): + self.confpath = confpath + try: + mode = os.stat(confpath).st_mode + except OSError: + os.mknod(confpath, 0600) + else: + if mode & stat.S_IRGRP or mode & stat.S_IROTH: + raise self.WrongPermissions( + u'Weboob will not start until config file %s is readable by group or other users.' % confpath) + + def iter_backends(self): + config = RawConfigParser() + config.read(self.confpath) + for instance_name in config.sections(): + params = dict(config.items(instance_name)) + try: + backend_name = params.pop('_backend') + except KeyError: + try: + backend_name = params.pop('_type') + warning(u'Please replace _type with _backend in your config file "%s", for backend "%s"' % ( + self.confpath, backend_name)) + except KeyError: + warning('Missing field "_backend" for configured backend "%s"', instance_name) + continue + yield instance_name, backend_name, params + + def add_backend(self, instance_name, backend_name, params, edit=False): + if not instance_name: + raise ValueError(u'Please give a name to the configured backend.') + config = RawConfigParser() + config.read(self.confpath) + if not edit: + config.add_section(instance_name) + config.set(instance_name, '_backend', backend_name) + for key, value in params.iteritems(): + config.set(instance_name, key, value) + with open(self.confpath, 'wb') as f: + config.write(f) + + def edit_backend(self, instance_name, backend_name, params): + return self.add_backend(instance_name, backend_name, params, True) + + def get_backend(self, instance_name): + config = RawConfigParser() + config.read(self.confpath) + if not config.has_section(instance_name): + raise KeyError(u'Configured backend "%s" not found' % instance_name) + + items = dict(config.items(instance_name)) + + try: + backend_name = items.pop('_backend') + except KeyError: + try: + backend_name = items.pop('_type') + warning(u'Please replace _type with _backend in your config file "%s"' % self.confpath) + except KeyError: + warning('Missing field "_backend" for configured backend "%s"', instance_name) + raise KeyError(u'Configured backend "%s" not found' % instance_name) + return backend_name, items + + def remove_backend(self, instance_name): + config = RawConfigParser() + config.read(self.confpath) + config.remove_section(instance_name) + with open(self.confpath, 'w') as f: + config.write(f) + + diff --git a/weboob/core/modules.py b/weboob/core/modules.py new file mode 100644 index 00000000..9d8ef063 --- /dev/null +++ b/weboob/core/modules.py @@ -0,0 +1,141 @@ +# -*- coding: utf-8 -*- + +# Copyright(C) 2010 Romain Bignon +# +# 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, version 3 of the License. +# +# 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. + + +from __future__ import with_statement + +import logging +from logging import debug, error, exception +import os +import re + +from weboob.capabilities.base import IBaseCap +from weboob.tools.backend import BaseBackend + + +__all__ = ['Module', 'ModulesLoader'] + + +class Module(object): + def __init__(self, package): + self.package = package + self.klass = None + for attrname in dir(self.package): + attr = getattr(self.package, attrname) + if isinstance(attr, type) and issubclass(attr, BaseBackend) and attr != BaseBackend: + self.klass = attr + if not self.klass: + raise ImportError('%s is not a backend (no BaseBackend class found)' % package) + + @property + def name(self): + return self.klass.NAME + + @property + def maintainer(self): + return '%s <%s>' % (self.klass.MAINTAINER, self.klass.EMAIL) + + @property + def version(self): + return self.klass.VERSION + + @property + def description(self): + return self.klass.DESCRIPTION + + @property + def license(self): + return self.klass.LICENSE + + @property + def config(self): + return self.klass.CONFIG + + @property + def website(self): + if self.klass.BROWSER and self.klass.BROWSER.DOMAIN: + return '%s://%s' % (self.klass.BROWSER.PROTOCOL, self.klass.BROWSER.DOMAIN) + else: + return None + + @property + def icon_path(self): + return self.klass.ICON + + def iter_caps(self): + for cap in self.klass.__bases__: + if issubclass(cap, IBaseCap) and cap != IBaseCap: + yield cap + + def has_caps(self, *caps): + for c in caps: + if (isinstance(c, basestring) and c in [cap.__name__ for cap in self.iter_caps()]) or \ + (type(c) == type and issubclass(self.klass, c)): + return True + return False + + def create_instance(self, weboob, instance_name, config, storage): + backend_instance = self.klass(weboob, instance_name, config, storage) + debug(u'Created backend instance "%s" for backend "%s"' % (instance_name, self.name)) + return backend_instance + + +class ModulesLoader(object): + def __init__(self): + self.loaded = {} + + def get_or_load_module(self, module_name): + if module_name not in self.loaded: + self.load_module(module_name) + if module_name in self.loaded: + return self.loaded[module_name] + else: + return None + + def iter_existing_module_names(self): + try: + import weboob.backends + except ImportError: + return + for path in weboob.backends.__path__: + regexp = re.compile('^%s/([\w\d_]+)$' % path) + for root, dirs, files in os.walk(path): + m = regexp.match(root) + if m and '__init__.py' in files: + yield m.group(1) + + def load_all(self): + for existing_module_name in self.iter_existing_module_names(): + self.load_module(existing_module_name) + + def load_module(self, module_name): + try: + package_name = 'weboob.backends.%s' % module_name + module = Module(__import__(package_name, fromlist=[str(package_name)])) + except ImportError, e: + msg = u'Unable to load module "%s": %s' % (module_name, e) + if logging.root.level == logging.DEBUG: + exception(msg) + return + else: + error(msg) + return + if module.name in self.loaded: + debug('Module "%s" is already loaded from %s' % (module_name, module.package.__path__[0])) + return + self.loaded[module.name] = module + debug('Loaded module "%s" from %s' % (module_name, module.package.__path__[0])) diff --git a/weboob/core/ouiboube.py b/weboob/core/ouiboube.py index 97a05905..c8ac3acd 100644 --- a/weboob/core/ouiboube.py +++ b/weboob/core/ouiboube.py @@ -22,7 +22,8 @@ from logging import warning import os from weboob.core.bcall import BackendsCall -from weboob.core.backends import BackendsConfig, BackendsLoader +from weboob.core.modules import ModulesLoader +from weboob.core.backendscfg import BackendsConfig from weboob.core.scheduler import Scheduler from weboob.tools.backend import BaseBackend @@ -50,7 +51,7 @@ class Weboob(object): warning(u'"%s" is not a directory' % self.workdir) # Backends loader - self.backends_loader = BackendsLoader() + self.modules_loader = ModulesLoader() # Backend instances config if not backends_filename: @@ -73,25 +74,25 @@ class Weboob(object): if storage is None: storage = self.storage - for instance_name, backend_name, params in self.backends_config.iter_backends(): + for instance_name, module_name, params in self.backends_config.iter_backends(): if '_enabled' in params and not params['_enabled'] or \ names is not None and instance_name not in names or \ - modules is not None and backend_name not in modules: + modules is not None and module_name not in modules: continue - backend = self.backends_loader.get_or_load_backend(backend_name) - if backend is None: + module = self.modules_loader.get_or_load_module(module_name) + if module is None: warning(u'Backend "%s" is referenced in ~/.weboob/backends ' 'configuration file, but was not found. ' - 'Hint: is it installed?' % backend_name) + 'Hint: is it installed?' % module_name) continue - if caps is not None and not backend.has_caps(caps): + if caps is not None and not module.has_caps(caps): continue if instance_name in self.backend_instances: warning(u'Oops, the backend "%s" is already loaded. Unload it before reloading...' % instance_name) self.unload_backends(instance_name) - backend_instance = backend.create_instance(self, instance_name, params, storage) + backend_instance = module.create_instance(self, instance_name, params, storage) self.backend_instances[instance_name] = loaded[instance_name] = backend_instance return loaded diff --git a/weboob/tools/application/base.py b/weboob/tools/application/base.py index f4fff08f..da6e8eef 100644 --- a/weboob/tools/application/base.py +++ b/weboob/tools/application/base.py @@ -77,7 +77,10 @@ class BaseApplication(object): # Default storage tree STORAGE = {} # Synopsis - SYNOPSIS = 'Usage: %prog [options (-h for help)] ...' + SYNOPSIS = 'Usage: %prog [-h] [-dqv] [-b backends] ...' + SYNOPSIS += ' %prog [--help] [--version]' + # Description + DESCRIPTION = None # Version VERSION = None # Copyright @@ -109,6 +112,8 @@ class BaseApplication(object): self._parser = OptionParser(self.SYNOPSIS, version=self._get_optparse_version()) else: self._parser = option_parser + if self.DESCRIPTION: + self._parser.description = self.DESCRIPTION self._parser.add_option('-b', '--backends', help='what backend(s) to enable (comma separated)') logging_options = OptionGroup(self._parser, 'Logging Options') logging_options.add_option('-d', '--debug', action='store_true', help='display debug messages') diff --git a/weboob/tools/application/console.py b/weboob/tools/application/console.py index e5bca62f..68e26cf2 100644 --- a/weboob/tools/application/console.py +++ b/weboob/tools/application/console.py @@ -28,7 +28,7 @@ import subprocess import sys from weboob.core import CallErrors -from weboob.core.backends import BackendsConfig +from weboob.core.backendscfg import BackendsConfig from .base import BackendNotFound, BaseApplication from .formatters.load import formatters, load_formatter @@ -44,7 +44,8 @@ class ConsoleApplication(BaseApplication): Base application class for CLI applications. """ - SYNOPSIS = 'Usage: %prog [options (-h for help)] command [parameters...]' + SYNOPSIS = 'Usage: %prog [-dqv] [-b backends] [-cnfs] command [arguments..]\n' + SYNOPSIS += ' %prog [--help] [--version]' def __init__(self): option_parser = OptionParser(self.SYNOPSIS, version=self._get_optparse_version()) @@ -62,6 +63,8 @@ class ConsoleApplication(BaseApplication): if self._parser.description is None: self._parser.description = '' + else: + self._parser.description += '\n\n' self._parser.description += 'Available commands:\n' for name, arguments, doc_string in self._commands: command = '%s %s' % (name, arguments) diff --git a/weboob/tools/application/qt/backendcfg.py b/weboob/tools/application/qt/backendcfg.py index 14c12d2a..e2281977 100644 --- a/weboob/tools/application/qt/backendcfg.py +++ b/weboob/tools/application/qt/backendcfg.py @@ -45,12 +45,12 @@ class BackendCfg(QDialog): # is_enabling is a counter to prevent race conditions. self.is_enabling = 0 - self.weboob.backends_loader.load_all() + self.weboob.modules_loader.load_all() self.ui.configuredBackendsList.header().setResizeMode(QHeaderView.ResizeToContents) self.ui.configFrame.hide() - for name, backend in self.weboob.backends_loader.loaded.iteritems(): + for name, backend in self.weboob.modules_loader.loaded.iteritems(): if not self.caps or backend.has_caps(*self.caps): item = QListWidgetItem(name.capitalize()) @@ -74,10 +74,8 @@ class BackendCfg(QDialog): def loadConfiguredBackendsList(self): self.ui.configuredBackendsList.clear() for instance_name, name, params in self.weboob.backends_config.iter_backends(): - if name not in self.weboob.backends_loader.loaded: - continue - backend = self.weboob.backends_loader.loaded[name] - if self.caps and not backend.has_caps(*self.caps): + backend = self.weboob.modules_loader.get_or_load_module(name) + if not backend or self.caps and not backend.has_caps(*self.caps): continue item = QTreeWidgetItem(None, [instance_name, name]) @@ -195,7 +193,12 @@ class BackendCfg(QDialog): self.tr('Please select a backend')) return - backend = self.weboob.backends_loader.loaded[unicode(selection[0].text()).lower()] + backend = self.weboob.modules_loader.get_or_load_module(unicode(selection[0].text()).lower()) + + if not backend: + QMessageBox.critical(self, self.tr('Unable to add a configured backend'), + self.tr('The selected backend does not exist.')) + return params = {} missing = [] @@ -261,7 +264,7 @@ class BackendCfg(QDialog): if not selection: return - backend = self.weboob.backends_loader.loaded[unicode(selection[0].text()).lower()] + backend = self.weboob.modules_loader.loaded[unicode(selection[0].text()).lower()] if backend.icon_path: img = QImage(backend.icon_path) diff --git a/weboob/tools/backend.py b/weboob/tools/backend.py index 3c7ce9ea..580615ce 100644 --- a/weboob/tools/backend.py +++ b/weboob/tools/backend.py @@ -147,6 +147,24 @@ class BaseBackend(object): """ pass + class classprop(object): + def __init__(self, fget): + self.fget = fget + def __get__(self, inst, objtype=None): + if inst: + return self.fget(inst) + else: + return self.fget(objtype) + + @classprop + def ICON(self): + try: + import xdg.IconTheme + except ImportError: + debug(u'Python xdg module was not found. Please install it to read icon files.') + else: + return xdg.IconTheme.getIconPath(self.NAME) + @property def browser(self): """