modules.py 6.33 KB
Newer Older
1 2
# -*- coding: utf-8 -*-

Romain Bignon's avatar
Romain Bignon committed
3
# Copyright(C) 2010-2013 Romain Bignon
4
#
5
# This file is part of weboob.
6
#
7
# weboob is free software: you can redistribute it and/or modify
8
# it under the terms of the GNU Lesser General Public License as published by
9 10 11 12
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# weboob is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU Lesser General Public License for more details.
16
#
17
# You should have received a copy of the GNU Lesser General Public License
18
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
19

Romain Bignon's avatar
Romain Bignon committed
20
import os
21
import imp
22 23
import logging

24
from weboob.tools.backend import Module
25
from weboob.tools.compat import basestring
26
from weboob.tools.log import getLogger
27
from weboob.exceptions import ModuleLoadError
28

29
__all__ = ['LoadedModule', 'ModulesLoader', 'RepositoryModulesLoader']
30

31

32
class LoadedModule(object):
33
    def __init__(self, package):
34
        self.logger = getLogger('backend')
35 36 37 38
        self.package = package
        self.klass = None
        for attrname in dir(self.package):
            attr = getattr(self.package, attrname)
39
            if isinstance(attr, type) and issubclass(attr, Module) and attr != Module:
40 41
                self.klass = attr
        if not self.klass:
42
            raise ImportError('%s is not a backend (no Module class found)' % package)
43 44 45 46 47 48 49

    @property
    def name(self):
        return self.klass.NAME

    @property
    def maintainer(self):
50
        return u'%s <%s>' % (self.klass.MAINTAINER, self.klass.EMAIL)
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69

    @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):
70 71
        if self.klass.BROWSER and hasattr(self.klass.BROWSER, 'BASEURL') and self.klass.BROWSER.BASEURL:
            return self.klass.BROWSER.BASEURL
72
        if self.klass.BROWSER and hasattr(self.klass.BROWSER, 'DOMAIN') and self.klass.BROWSER.DOMAIN:
73 74 75 76 77
            return '%s://%s' % (self.klass.BROWSER.PROTOCOL, self.klass.BROWSER.DOMAIN)
        else:
            return None

    @property
78
    def icon(self):
79 80 81
        return self.klass.ICON

    def iter_caps(self):
82
        return self.klass.iter_caps()
83 84

    def has_caps(self, *caps):
Vincent A's avatar
Vincent A committed
85
        """Return True if module implements at least one of the caps."""
86 87 88 89 90 91
        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

92 93
    def create_instance(self, weboob, backend_name, config, storage, nofail=False, logger=None):
        backend_instance = self.klass(weboob, backend_name, config, storage, logger=logger or self.logger, nofail=nofail)
94
        self.logger.debug(u'Created backend "%s" for module "%s"' % (backend_name, self.name))
95 96 97 98
        return backend_instance


class ModulesLoader(object):
Romain Bignon's avatar
Romain Bignon committed
99 100 101
    """
    Load modules.
    """
102

Romain Bignon's avatar
Romain Bignon committed
103 104 105
    def __init__(self, path, version=None):
        self.version = version
        self.path = path
106
        self.loaded = {}
107
        self.logger = getLogger('modules')
108

109
    def get_or_load_module(self, module_name):
110 111 112
        """
        Can raise a ModuleLoadError exception.
        """
113
        if module_name not in self.loaded:
114
            self.load_module(module_name)
115
        return self.loaded[module_name]
116 117

    def iter_existing_module_names(self):
Romain Bignon's avatar
Romain Bignon committed
118 119 120 121 122 123 124
        for name in os.listdir(self.path):
            try:
                if '__init__.py' in os.listdir(os.path.join(self.path, name)):
                    yield name
            except OSError:
                # if path/name is not a directory
                continue
125

126 127 128 129 130 131
    def module_exists(self, name):
        for existing_module_name in self.iter_existing_module_names():
            if existing_module_name == name:
                return True
        return False

132 133
    def load_all(self):
        for existing_module_name in self.iter_existing_module_names():
134 135
            try:
                self.load_module(existing_module_name)
136
            except ModuleLoadError as e:
137
                self.logger.warning('could not load module %s: %s', existing_module_name, e)
138

139
    def load_module(self, module_name):
140 141
        if module_name in self.loaded:
            self.logger.debug('Module "%s" is already loaded from %s' % (module_name, self.loaded[module_name].package.__path__[0]))
142
            return
143

Romain Bignon's avatar
Romain Bignon committed
144
        path = self.get_module_path(module_name)
145 146

        try:
Romain Bignon's avatar
Romain Bignon committed
147
            fp, pathname, description = imp.find_module(module_name, [path])
148
            try:
149
                module = LoadedModule(imp.load_module(module_name, fp, pathname, description))
150 151 152
            finally:
                if fp:
                    fp.close()
153
        except Exception as e:
154
            if logging.root.level <= logging.DEBUG:
155
                self.logger.exception(e)
156
            raise ModuleLoadError(module_name, e)
157

Romain Bignon's avatar
Romain Bignon committed
158
        if module.version != self.version:
159
            raise ModuleLoadError(module_name, "Module requires Weboob %s, but you use Weboob %s. Hint: use 'weboob-config update'"
Romain Bignon's avatar
Romain Bignon committed
160
                                               % (module.version, self.version))
161

162
        self.loaded[module_name] = module
163
        self.logger.debug('Loaded module "%s" from %s' % (module_name, module.package.__path__[0]))
Romain Bignon's avatar
Romain Bignon committed
164 165 166 167

    def get_module_path(self, module_name):
        return self.path

168

Romain Bignon's avatar
Romain Bignon committed
169 170 171 172
class RepositoryModulesLoader(ModulesLoader):
    """
    Load modules from repositories.
    """
173

Romain Bignon's avatar
Romain Bignon committed
174 175 176 177 178
    def __init__(self, repositories):
        super(RepositoryModulesLoader, self).__init__(repositories.modules_dir, repositories.version)
        self.repositories = repositories

    def iter_existing_module_names(self):
179
        for name in self.repositories.get_all_modules_info():
Romain Bignon's avatar
Romain Bignon committed
180 181 182 183 184 185 186 187 188 189
            yield name

    def get_module_path(self, module_name):
        minfo = self.repositories.get_module_info(module_name)
        if minfo is None:
            raise ModuleLoadError(module_name, 'No such module %s' % module_name)
        if minfo.path is None:
            raise ModuleLoadError(module_name, 'Module %s is not installed' % module_name)

        return minfo.path