~allenap/maas/xxx-a-thon

« back to all changes in this revision

Viewing changes to src/maasfascist.py

  • Committer: Gavin Panella
  • Date: 2016-03-22 21:14:34 UTC
  • mfrom: (4657.1.157 maas)
  • Revision ID: gavin.panella@canonical.com-20160322211434-xzuovio86zvzo2js
Merge trunk.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright 2014-2015 Canonical Ltd.  This software is licensed under the
2
 
# GNU Affero General Public License version 3 (see the file LICENSE).
3
 
 
4
 
"""MAAS-specific import fascist.
5
 
 
6
 
This is designed to stop the unwary from importing modules from where
7
 
they ought not.
8
 
 
9
 
We do this to help separate concerns. For example, ``provisioningserver`` is
10
 
meant to run on cluster controllers, perhaps using code from ``apiclient``,
11
 
but it must *not* import from ``maas``, ``maasserver``, or ``metadataserver``
12
 
because these are solely the preserve of the region controller, and because
13
 
they contain Django applications which need some extra environmental support
14
 
in order to run.
15
 
 
16
 
See https://docs.python.org/2/library/sys.html#sys.meta_path and
17
 
https://docs.python.org/2.7/reference/simple_stmts.html#the-import-statement
18
 
for more information on how this module is able to work.
19
 
"""
20
 
 
21
 
__all__ = []
22
 
 
23
 
 
24
 
from inspect import (
25
 
    getmodule,
26
 
    getsourcefile,
27
 
)
28
 
from sys import (
29
 
    _getframe as getframe,
30
 
    meta_path,
31
 
)
32
 
 
33
 
 
34
 
class FascistFinder:
35
 
    """Provides the ``find_module`` method.
36
 
 
37
 
    ``find_module`` is a Python-specified hook for overriding the normal
38
 
    import mechanisms. Put an instance of this class into ``sys.meta_hook``
39
 
    and Python will consult it when looking to import a module.
40
 
 
41
 
    If ``find_module`` returns an object other than ``None`` it is then used
42
 
    to load the module. However, it's also possible to use this to *prevent*
43
 
    module loading, which is what this class does in cooperation with
44
 
    `FascistLoader`.
45
 
    """
46
 
 
47
 
    # module to import: forbidden from
48
 
    rules = {
49
 
        "maas": {
50
 
            "apiclient",
51
 
            "maascli",
52
 
            "maasserver",
53
 
            "maastesting",
54
 
            "metadataserver",
55
 
            "provisioningserver",
56
 
        },
57
 
        "maasserver": {
58
 
            "apiclient",
59
 
            "maascli",
60
 
            "maastesting",
61
 
            "provisioningserver",
62
 
        },
63
 
        "metadataserver": {
64
 
            "apiclient",
65
 
            "maascli",
66
 
            "maastesting",
67
 
            "provisioningserver",
68
 
        },
69
 
    }
70
 
 
71
 
    def find_rule(self, name):
72
 
        """Search `rules` for `name`.
73
 
 
74
 
        If `name` isn't found but `name` is a dot-separated name, another
75
 
        search is attempted with the right-most part of the name removed.
76
 
        For example, given `foo.bar.baz` it will search in order::
77
 
 
78
 
          foo.bar.baz
79
 
          foo.bar
80
 
          foo
81
 
 
82
 
        If no name matches, it returns `None`.
83
 
        """
84
 
        if name in self.rules:
85
 
            return self.rules[name]
86
 
        elif "." in name:
87
 
            name, _, _ = name.rpartition(".")
88
 
            return self.find_rule(name)
89
 
        else:
90
 
            return None
91
 
 
92
 
    def is_forbidden(self, forbidden, name):
93
 
        """Search `forbidden` for `name`.
94
 
 
95
 
        If `name` isn't found but `name` is a dot-separated name, another
96
 
        search is attempted with the right-most part of the name removed.
97
 
        See `find_rule` for details.
98
 
        """
99
 
        if name in forbidden:
100
 
            return True
101
 
        elif "." in name:
102
 
            name, _, _ = name.rpartition(".")
103
 
            return self.is_forbidden(forbidden, name)
104
 
        else:
105
 
            return False
106
 
 
107
 
    def find_module(self, fullname, path=None):
108
 
        """Consult `rules` to see if `fullname` can be loaded.
109
 
 
110
 
        This ignores `path`.
111
 
        """
112
 
        forbidden = self.find_rule(fullname)
113
 
        if forbidden is not None:
114
 
            origin_frame = getframe(1)
115
 
            origin_module = getmodule(origin_frame)
116
 
            if origin_module is not None:
117
 
                origin_module_name = origin_module.__name__
118
 
                if self.is_forbidden(forbidden, origin_module_name):
119
 
                    return FascistLoader(origin_module_name)
120
 
        # Good. Out of the door, line on the left, one cross each.
121
 
        return None
122
 
 
123
 
    def install(self):
124
 
        """Install this at the front of `meta_path`.
125
 
 
126
 
        If it's already installed, it is moved to the front.
127
 
        """
128
 
        if self in meta_path:
129
 
            meta_path.remove(self)
130
 
        meta_path.insert(0, self)
131
 
 
132
 
 
133
 
class FascistLoader:
134
 
    """Prevent import of the specified module.
135
 
 
136
 
    With a message explaining what has been prevented and why.
137
 
    """
138
 
 
139
 
    rules_location = getsourcefile(FascistFinder)
140
 
    if rules_location is None:
141
 
        rules_location = FascistFinder.__name__
142
 
 
143
 
    def __init__(self, whence):
144
 
        super(FascistLoader, self).__init__()
145
 
        self.whence = whence
146
 
 
147
 
    def load_module(self, name):
148
 
        """Raises `ImportError` to prevent loading of `name`."""
149
 
        raise ImportError(
150
 
            "Don't import %r from %r. This is MAAS policy. If you "
151
 
            "think this is wrong, amend the rules in %r." % (
152
 
                name, self.whence, self.rules_location))
153
 
 
154
 
 
155
 
fascist = FascistFinder()
156
 
fascist.install()