summaryrefslogtreecommitdiff
path: root/pecan/extensions.py
blob: 87b9925087aad5e20ab5c52c9fdf29dc9f231486 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
import sys
import pkg_resources
import inspect
import logging

log = logging.getLogger(__name__)


class PecanExtensionMissing(ImportError):
    pass


class PecanExtensionImporter(object):
    """
    Short circuits imports for extensions.

    This is used in combination with ``pecan.ext`` so that when a user does
    ``from pecan.ext import foo``, it will attempt to map ``foo`` to a
    registered setuptools entry point in some other (Pecan extension) project.

    Conversely, an extension developer may define an entry point in his
    ``setup.py``, e.g.,

    setup(
      ...
      entry_points='''
      [pecan.extension]
      celery = pecancelery.lib.core
      '''
    )

    This is mostly for convenience and consistency.  In this way, Pecan can
    maintain an ecosystem of extensions that share a common namespace,
    ``pecan.ext``, while still maintaining backwards compatibility for simple
    package names (e.g., ``pecancelery``).
    """

    extension_module = 'pecan.ext'
    prefix = extension_module + '.'

    def install(self):
        if self not in sys.meta_path:
            sys.meta_path.append(self)

    def __eq__(self, b):
        return self.__class__.__module__ == b.__class__.__module__ and \
            self.__class__.__name__ == b.__class__.__name__

    def __ne__(self, b):
        return not self.__eq__(b)

    def find_module(self, fullname, path=None):
        if fullname.startswith(self.prefix):
            return self

    def load_module(self, fullname):
        if fullname in sys.modules:
            return self
        extname = fullname.split(self.prefix)[1]
        module = self.find_module_for_extension(extname)
        realname = module.__name__
        try:
            __import__(realname)
        except ImportError:
            raise sys.exc_info()
        module = sys.modules[fullname] = sys.modules[realname]
        if '.' not in extname:
            setattr(sys.modules[self.extension_module], extname, module)
        return module

    def find_module_for_extension(self, name):
        for ep in pkg_resources.iter_entry_points('pecan.extension'):
            if ep.name != name:
                continue
            log.debug('%s loading extension %s', self.__class__.__name__, ep)
            module = ep.load()
            if not inspect.ismodule(module):
                log.debug('%s is not a module, skipping...' % module)
                continue
            return module
        raise PecanExtensionMissing(
            'The `pecan.ext.%s` extension is not installed.' % name
        )