~launchpad-pqm/launchpad/devel

8687.15.17 by Karl Fogel
Add the copyright header block to the rest of the files under lib/lp/.
1
# Copyright 2009 Canonical Ltd.  This software is licensed under the
2
# GNU Affero General Public License version 3 (see the file LICENSE).
2097 by Canonical.com Patch Queue Manager
[trivial] more import fascist improvements
3
14550.1.1 by Steve Kowalik
Run format-imports over lib/lp and lib/canonical/launchpad
4
import __builtin__
2097 by Canonical.com Patch Queue Manager
[trivial] more import fascist improvements
5
import atexit
6
import itertools
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
7
from operator import attrgetter
3203.1.16 by Steve Alexander
Make the fascist ignore names imported not appearing in __all__ when the
8
import types
11403.1.4 by Henning Eggers
Reformatted imports using format-imports script r32.
9
2097 by Canonical.com Patch Queue Manager
[trivial] more import fascist improvements
10
11
original_import = __builtin__.__import__
12
database_root = 'canonical.launchpad.database'
13
naughty_imports = set()
14
6002.2.4 by Barry Warsaw
Drive-by to silence the UserWarnings caused by Hardy packages.
15
# Silence bogus warnings from Hardy's python-pkg-resources package.
6019.1.1 by Barry Warsaw
Shut up UserWarnings on Hardy.
16
import warnings
6002.2.4 by Barry Warsaw
Drive-by to silence the UserWarnings caused by Hardy packages.
17
warnings.filterwarnings('ignore', category=UserWarning, append=True,
18
                        message=r'Module .*? is being added to sys.path')
19
10100.1.2 by Jonathan Lange
Allow ComponentLookupError. Fix flakes.
20
2097 by Canonical.com Patch Queue Manager
[trivial] more import fascist improvements
21
def text_lines_to_set(text):
22
    return set(line.strip() for line in text.splitlines() if line.strip())
23
3560.1.2 by Steve Alexander
fix bug 39393, reenabling the importfascist, and cleaning a couple of import-related thing up on the way.
24
2097 by Canonical.com Patch Queue Manager
[trivial] more import fascist improvements
25
permitted_database_imports = text_lines_to_set("""
7675.85.2 by Jonathan Lange
Undo revision generated by step 2 of process.
26
    canonical.archivepublisher.deathrow
27
    canonical.archivepublisher.domination
3691.93.7 by Christian Reis
Step 2 in refactoring: move FTPArchive stuff out of Publisher.
28
    canonical.archivepublisher.ftparchive
3691.93.6 by Christian Reis
Checkpoint first part of publisher refactoring: moving code from publish-distro to Publishing, seriously.
29
    canonical.archivepublisher.publishing
8426.6.2 by Michael Hudson
a few more references in comments and docstrings
30
    lp.codehosting.inmemory
7860.1.4 by Jonathan Lange
Move the _listingToSortOrder method to browser, since it's really view
31
    canonical.launchpad.browser.branchlisting
8138.1.1 by Jonathan Lange
Prepare the branch for lp.code extraction.
32
    lp.code.browser.branchlisting
14578.2.1 by William Grant
Move librarian stuff from canonical.launchpad to lp.services.librarian. canonical.librarian remains untouched.
33
    lp.services.librarian.browser
7860.1.1 by Jonathan Lange
Make the branch feed view use collections.
34
    canonical.launchpad.feed.branch
8138.1.1 by Jonathan Lange
Prepare the branch for lp.code extraction.
35
    lp.code.feed.branch
11703.1.1 by Tim Penhey
Move the garbo script into lp.scripts.
36
    lp.scripts.garbo
14435.1.8 by Curtis Hovey
Renamed lp/bugs/vocabulary to vocaularies to be consitent with the other modules.
37
    lp.bugs.vocabularies
14583.1.20 by Curtis Hovey
Fixed importfacist.
38
    lp.registry.interfaces.person
7675.161.1 by Curtis Hovey
Ported the original registry vocab branch to db-devel. This means that the classes and tests for ValidPersonOrTeam, Milestone, and DistributionOrProduct were reconciled.
39
    lp.registry.vocabularies
14435.1.7 by Curtis Hovey
Fixed all references to c.l.vocabularies.
40
    lp.services.worlddata.vocabularies
41
    lp.soyuz.vocabularies
42
    lp.translations.vocabularies
2097 by Canonical.com Patch Queue Manager
[trivial] more import fascist improvements
43
    canonical.librarian.client
7675.85.2 by Jonathan Lange
Undo revision generated by step 2 of process.
44
    canonical.librarian.db
11128.2.3 by Jonathan Lange
Change the fascist to not warn when we use doctest on database classes.
45
    doctest
2097 by Canonical.com Patch Queue Manager
[trivial] more import fascist improvements
46
    """)
47
3560.1.2 by Steve Alexander
fix bug 39393, reenabling the importfascist, and cleaning a couple of import-related thing up on the way.
48
2097 by Canonical.com Patch Queue Manager
[trivial] more import fascist improvements
49
warned_database_imports = text_lines_to_set("""
14565.2.15 by Curtis Hovey
Moved canonical.launchpad.scripts __init__ to lp.services.scripts.
50
    lp.soyuz.scripts.ftpmaster
51
    lp.soyuz.scripts.gina.handlers
52
    lp.registry.browser.distroseries
8751.1.1 by Danilo Šegan
Store migration changes so far.
53
    lp.translations.scripts.po_import
14600.1.9 by Curtis Hovey
Moved systemhomes to lp.
54
    lp.systemhomes
2097 by Canonical.com Patch Queue Manager
[trivial] more import fascist improvements
55
    """)
56
3560.1.2 by Steve Alexander
fix bug 39393, reenabling the importfascist, and cleaning a couple of import-related thing up on the way.
57
10085.1.6 by Jonathan Lange
Use a data-driven approach for exceptions to the import rule.
58
# Sometimes, third-party modules don't export all of their public APIs through
59
# __all__. The following dict maps from such modules to a list of attributes
60
# that are allowed to be imported, whether or not they are in __all__.
61
valid_imports_not_in_all = {
11057.9.12 by Maris Fogels
Silence the fascist
62
    'bzrlib.lsprof': set(['BzrProfiler']),
10085.1.8 by Jonathan Lange
Handle the spurious warnings.
63
    'cookielib': set(['domain_match']),
64
    'email.Utils': set(['mktime_tz']),
10212.7.9 by Guilherme Salgado
Change +login to not start an OpenID dance when the user is already logged in
65
    'openid.fetchers': set(['Urllib2Fetcher']),
10150.1.2 by Stuart Bishop
Review feedback
66
    'storm.database': set(['STATE_DISCONNECTED']),
10085.1.8 by Jonathan Lange
Handle the spurious warnings.
67
    'textwrap': set(['dedent']),
14565.2.22 by Curtis Hovey
Add a shim to support database/schema/fti.py.
68
    'testtools.testresult.real': set(['_details_to_str']),
10224.22.11 by Gavin Panella
Revert the previous revision - go back to setting up a thread pool explicitly - and add an exception for deferToThreadPool to importfascist.
69
    'twisted.internet.threads': set(['deferToThreadPool']),
10100.1.2 by Jonathan Lange
Allow ComponentLookupError. Fix flakes.
70
    'zope.component': set(
71
        ['adapter',
72
         'ComponentLookupError',
10150.1.2 by Stuart Bishop
Review feedback
73
         'provideAdapter',
10100.1.2 by Jonathan Lange
Allow ComponentLookupError. Fix flakes.
74
         'provideHandler',
75
         ]),
10085.1.6 by Jonathan Lange
Use a data-driven approach for exceptions to the import rule.
76
    }
77
78
2097 by Canonical.com Patch Queue Manager
[trivial] more import fascist improvements
79
def database_import_allowed_into(module_path):
80
    """Return True if database code is allowed to be imported into the given
81
    module path.  Otherwise, returns False.
82
83
    It is allowed if:
84
        - The import was made with the __import__ hook.
85
        - The importer is from within canonical.launchpad.database.
86
        - The importer is a 'test' module.
87
        - The importer is in the set of permitted_database_imports.
7944.3.22 by Francis J. Lacoste
Renamed database to model.
88
        - The importer is within a model module or package.
2097 by Canonical.com Patch Queue Manager
[trivial] more import fascist improvements
89
90
    Note that being in the set of warned_database_imports does not make
91
    the import allowed.
92
93
    """
94
    if (module_path == '__import__ hook' or
95
        module_path.startswith('canonical.launchpad.database') or
7944.3.22 by Francis J. Lacoste
Renamed database to model.
96
        '.model' in module_path or
2097 by Canonical.com Patch Queue Manager
[trivial] more import fascist improvements
97
        is_test_module(module_path)):
98
        return True
99
    return module_path in permitted_database_imports
100
3560.1.2 by Steve Alexander
fix bug 39393, reenabling the importfascist, and cleaning a couple of import-related thing up on the way.
101
2097 by Canonical.com Patch Queue Manager
[trivial] more import fascist improvements
102
def is_test_module(module_path):
103
    """Returns True if the module is for unit or functional tests.
104
105
    Otherwise returns False.
106
    """
107
    name_splitted = module_path.split('.')
5554.4.10 by James Henstridge
* Remove some unnecessary imports from test_pages in page tests.
108
    return ('tests' in name_splitted or
109
            'ftests' in name_splitted or
110
            'testing' in name_splitted)
2097 by Canonical.com Patch Queue Manager
[trivial] more import fascist improvements
111
112
113
class attrsgetter:
114
    """Like operator.attrgetter, but works on multiple attribute names."""
115
116
    def __init__(self, *names):
117
        self.names = names
118
119
    def __call__(self, obj):
120
        return tuple(getattr(obj, name) for name in self.names)
121
122
123
class JackbootError(ImportError):
124
    """Import Fascist says you can't make this import."""
125
126
    def __init__(self, import_into, name, *args):
127
        ImportError.__init__(self, import_into, name, *args)
128
        self.import_into = import_into
129
        self.name = name
130
131
    def format_message(self):
132
        return 'Generic JackbootError: %s imported into %s' % (
133
            self.name, self.import_into)
134
135
    def __str__(self):
136
        return self.format_message()
137
138
139
class DatabaseImportPolicyViolation(JackbootError):
140
    """Database code is imported directly into other code."""
141
142
    def format_message(self):
143
        return 'You should not import %s into %s' % (
144
            self.name, self.import_into)
145
146
147
class FromStarPolicyViolation(JackbootError):
148
    """import * from a module that has no __all__."""
149
150
    def format_message(self):
151
        return ('You should not import * from %s because it has no __all__'
152
                ' (in %s)' % (self.name, self.import_into))
153
154
155
class NotInModuleAllPolicyViolation(JackbootError):
156
    """import of a name that does not appear in a module's __all__."""
157
158
    def __init__(self, import_into, name, attrname):
159
        JackbootError.__init__(self, import_into, name, attrname)
160
        self.attrname = attrname
161
162
    def format_message(self):
163
        return ('You should not import %s into %s from %s,'
164
                ' because it is not in its __all__.' %
165
                (self.attrname, self.import_into, self.name))
166
3560.1.2 by Steve Alexander
fix bug 39393, reenabling the importfascist, and cleaning a couple of import-related thing up on the way.
167
2630 by Canonical.com Patch Queue Manager
[trivial] lots of tidying up. converting all database classes to use NotFoundError consistently, and to import it from launchpad.interfaces in preparation for the move to a new zope3. Also, introduced a NameNotAvailable error. removed browser:traverse rdirective. commented out shipit test that fails sometimes.
168
class NotFoundPolicyViolation(JackbootError):
169
    """import of zope.exceptions.NotFoundError into
170
    canonical.launchpad.database.
171
    """
172
173
    def __init__(self, import_into):
174
        JackbootError.__init__(self, import_into, '')
175
176
    def format_message(self):
177
        return ('%s\nDo not import zope.exceptions.NotFoundError.\n'
11270.1.3 by Tim Penhey
Changed NotFoundError imports - gee there were a lot of them.
178
                'Use lp.app.errors.NotFoundError instead.'
2630 by Canonical.com Patch Queue Manager
[trivial] lots of tidying up. converting all database classes to use NotFoundError consistently, and to import it from launchpad.interfaces in preparation for the move to a new zope3. Also, introduced a NameNotAvailable error. removed browser:traverse rdirective. commented out shipit test that fails sometimes.
179
                % self.import_into)
180
181
11318.1.5 by Brad Crittenden
Fixed too long lines
182
# The names of the arguments form part of the interface of __import__(...),
183
# and must not be changed, as code may choose to invoke __import__ using
184
# keyword arguments - e.g. the encodings module in Python 2.6.
7014.4.4 by Guilherme Salgado
Couple changes suggested by Celso.
185
# pylint: disable-msg=W0102,W0602
10365.1.1 by Max Bowsher
Fix the argument names of importfascist to match those of the default
186
def import_fascist(name, globals={}, locals={}, fromlist=[], level=-1):
6667.1.2 by Barry Warsaw
Lint cleanups
187
    global naughty_imports
188
3560.1.2 by Steve Alexander
fix bug 39393, reenabling the importfascist, and cleaning a couple of import-related thing up on the way.
189
    try:
10365.1.1 by Max Bowsher
Fix the argument names of importfascist to match those of the default
190
        module = original_import(name, globals, locals, fromlist, level)
6061.2.28 by Gary Poster
make import fascist generally be gentle to zope.app.layers code; this fixes navigation.txt
191
    except ImportError:
6061.14.19 by Francis J. Lacoste
Add bug number for import_fascist XXX.
192
        # XXX sinzui 2008-04-17 bug=277274:
6061.2.28 by Gary Poster
make import fascist generally be gentle to zope.app.layers code; this fixes navigation.txt
193
        # import_fascist screws zope configuration module which introspects
194
        # the stack to determine if an ImportError means a module
195
        # initialization error or a genuine error. The browser:page always
196
        # tries to load a layer from zope.app.layers first, which most of the
197
        # time doesn't exist and dies a horrible death because of the import
198
        # fascist. That's the long explanation for why we special case this
199
        # module.
10365.1.1 by Max Bowsher
Fix the argument names of importfascist to match those of the default
200
        if name.startswith('zope.app.layers.'):
201
            name = name[16:]
202
            module = original_import(name, globals, locals, fromlist, level)
6061.2.28 by Gary Poster
make import fascist generally be gentle to zope.app.layers code; this fixes navigation.txt
203
        else:
204
            raise
2097 by Canonical.com Patch Queue Manager
[trivial] more import fascist improvements
205
    # Python's re module imports some odd stuff every time certain regexes
206
    # are used.  Let's optimize this.
10365.1.1 by Max Bowsher
Fix the argument names of importfascist to match those of the default
207
    if name == 'sre':
2097 by Canonical.com Patch Queue Manager
[trivial] more import fascist improvements
208
        return module
209
6667.1.2 by Barry Warsaw
Lint cleanups
210
    # Mailman 2.1 code base is originally circa 1998, so yeah, no __all__'s.
10365.1.1 by Max Bowsher
Fix the argument names of importfascist to match those of the default
211
    if name.startswith('Mailman'):
6667.1.2 by Barry Warsaw
Lint cleanups
212
        return module
2097 by Canonical.com Patch Queue Manager
[trivial] more import fascist improvements
213
4331.8.1 by James Henstridge
start of port to python-openid-2.0 library
214
    # Some uses of __import__ pass None for globals, so handle that.
215
    import_into = None
216
    if globals is not None:
4108.5.2 by James Henstridge
Add zstorm initialisation files, hooking it into the test suite
217
        import_into = globals.get('__name__')
4331.8.1 by James Henstridge
start of port to python-openid-2.0 library
218
2097 by Canonical.com Patch Queue Manager
[trivial] more import fascist improvements
219
    if import_into is None:
220
        # We're being imported from the __import__ builtin.
221
        # We could find out by jumping up the stack a frame.
222
        # Let's not for now.
223
        import_into = '__import__ hook'
10085.1.3 by Jonathan Lange
Comments.
224
225
    # Check the "NotFoundError" policy.
2630 by Canonical.com Patch Queue Manager
[trivial] lots of tidying up. converting all database classes to use NotFoundError consistently, and to import it from launchpad.interfaces in preparation for the move to a new zope3. Also, introduced a NameNotAvailable error. removed browser:traverse rdirective. commented out shipit test that fails sometimes.
226
    if (import_into.startswith('canonical.launchpad.database') and
10365.1.1 by Max Bowsher
Fix the argument names of importfascist to match those of the default
227
        name == 'zope.exceptions'):
228
        if fromlist and 'NotFoundError' in fromlist:
2630 by Canonical.com Patch Queue Manager
[trivial] lots of tidying up. converting all database classes to use NotFoundError consistently, and to import it from launchpad.interfaces in preparation for the move to a new zope3. Also, introduced a NameNotAvailable error. removed browser:traverse rdirective. commented out shipit test that fails sometimes.
229
            raise NotFoundPolicyViolation(import_into)
10085.1.3 by Jonathan Lange
Comments.
230
231
    # Check the database import policy.
10365.1.1 by Max Bowsher
Fix the argument names of importfascist to match those of the default
232
    if (name.startswith(database_root) and
2097 by Canonical.com Patch Queue Manager
[trivial] more import fascist improvements
233
        not database_import_allowed_into(import_into)):
10365.1.1 by Max Bowsher
Fix the argument names of importfascist to match those of the default
234
        error = DatabaseImportPolicyViolation(import_into, name)
2097 by Canonical.com Patch Queue Manager
[trivial] more import fascist improvements
235
        naughty_imports.add(error)
236
        # Raise an error except in the case of browser.traversers.
237
        # This exception to raising an error is only temporary, until
238
        # browser.traversers is cleaned up.
239
        if import_into not in warned_database_imports:
240
            raise error
241
10085.1.3 by Jonathan Lange
Comments.
242
    # Check the import from __all__ policy.
10365.1.1 by Max Bowsher
Fix the argument names of importfascist to match those of the default
243
    if fromlist is not None and (
10085.1.9 by Jonathan Lange
Check the 'lp' package too.
244
        import_into.startswith('canonical') or import_into.startswith('lp')):
10085.1.3 by Jonathan Lange
Comments.
245
        # We only want to warn about "from foo import bar" violations in our
246
        # own code.
10365.1.1 by Max Bowsher
Fix the argument names of importfascist to match those of the default
247
        fromlist = list(fromlist)
10085.1.1 by Jonathan Lange
Tweaks that don't change behaviour.
248
        module_all = getattr(module, '__all__', None)
10085.1.2 by Jonathan Lange
More tweaks without behaviour changes.
249
        if module_all is None:
10365.1.1 by Max Bowsher
Fix the argument names of importfascist to match those of the default
250
            if fromlist == ['*']:
10085.1.3 by Jonathan Lange
Comments.
251
                # "from foo import *" is naughty if foo has no __all__
10365.1.1 by Max Bowsher
Fix the argument names of importfascist to match those of the default
252
                error = FromStarPolicyViolation(import_into, name)
10085.1.2 by Jonathan Lange
More tweaks without behaviour changes.
253
                naughty_imports.add(error)
254
                raise error
255
        else:
10365.1.1 by Max Bowsher
Fix the argument names of importfascist to match those of the default
256
            if fromlist == ['*']:
10085.1.3 by Jonathan Lange
Comments.
257
                # "from foo import *" is allowed if foo has an __all__
10085.1.2 by Jonathan Lange
More tweaks without behaviour changes.
258
                return module
259
            if is_test_module(import_into):
10085.1.3 by Jonathan Lange
Comments.
260
                # We don't bother checking imports into test modules.
10085.1.2 by Jonathan Lange
More tweaks without behaviour changes.
261
                return module
10365.1.1 by Max Bowsher
Fix the argument names of importfascist to match those of the default
262
            allowed_fromlist = valid_imports_not_in_all.get(
263
                name, set())
264
            for attrname in fromlist:
10085.1.3 by Jonathan Lange
Comments.
265
                # Check that each thing we are importing into the module is
266
                # either in __all__, is a module itself, or is a specific
267
                # exception.
10085.1.2 by Jonathan Lange
More tweaks without behaviour changes.
268
                if attrname == '__doc__':
269
                    # You can always import __doc__.
270
                    continue
271
                if isinstance(
272
                    getattr(module, attrname, None), types.ModuleType):
273
                    # You can import modules even when they aren't declared in
274
                    # __all__.
275
                    continue
10365.1.1 by Max Bowsher
Fix the argument names of importfascist to match those of the default
276
                if attrname in allowed_fromlist:
10085.1.6 by Jonathan Lange
Use a data-driven approach for exceptions to the import rule.
277
                    # Some things can be imported even if they aren't in
278
                    # __all__.
7709.4.6 by Guilherme Salgado
A couple changes suggested by Gary
279
                    continue
10085.1.2 by Jonathan Lange
More tweaks without behaviour changes.
280
                if attrname not in module_all:
281
                    error = NotInModuleAllPolicyViolation(
10365.1.1 by Max Bowsher
Fix the argument names of importfascist to match those of the default
282
                        import_into, name, attrname)
10085.1.2 by Jonathan Lange
More tweaks without behaviour changes.
283
                    naughty_imports.add(error)
2097 by Canonical.com Patch Queue Manager
[trivial] more import fascist improvements
284
    return module
285
3560.1.2 by Steve Alexander
fix bug 39393, reenabling the importfascist, and cleaning a couple of import-related thing up on the way.
286
2097 by Canonical.com Patch Queue Manager
[trivial] more import fascist improvements
287
def report_naughty_imports():
288
    if naughty_imports:
289
        print
290
        print '** %d import policy violations **' % len(naughty_imports)
291
292
        database_violations = []
293
        fromstar_violations = []
294
        notinall_violations = []
295
        sorting_map = {
296
            DatabaseImportPolicyViolation: database_violations,
297
            FromStarPolicyViolation: fromstar_violations,
10100.1.2 by Jonathan Lange
Allow ComponentLookupError. Fix flakes.
298
            NotInModuleAllPolicyViolation: notinall_violations,
2097 by Canonical.com Patch Queue Manager
[trivial] more import fascist improvements
299
            }
300
        for error in naughty_imports:
301
            sorting_map[error.__class__].append(error)
302
303
        if database_violations:
304
            print
305
            print "There were %s database import violations." % (
306
                len(database_violations))
307
            sorted_violations = sorted(
308
                database_violations,
309
                key=attrsgetter('name', 'import_into'))
310
311
            for name, sequence in itertools.groupby(
312
                sorted_violations, attrgetter('name')):
313
                print "You should not import %s into:" % name
314
                for import_into, unused_duplicates_seq in itertools.groupby(
315
                    sequence, attrgetter('import_into')):
316
                    # Show first occurrence only, to avoid duplicates.
317
                    print "   ", import_into
318
319
        if fromstar_violations:
320
            print
321
            print "There were %s imports 'from *' without an __all__." % (
322
                len(fromstar_violations))
323
            sorted_violations = sorted(
324
                fromstar_violations,
325
                key=attrsgetter('import_into', 'name'))
326
327
            for import_into, sequence in itertools.groupby(
328
                sorted_violations, attrgetter('import_into')):
329
                print "You should not import * into %s from" % import_into
330
                for error in sequence:
331
                    print "   ", error.name
332
333
        if notinall_violations:
334
            print
335
            print (
336
                "There were %s imports of names not appearing in the __all__."
337
                % len(notinall_violations))
338
            sorted_violations = sorted(
339
                notinall_violations,
340
                key=attrsgetter('name', 'attrname', 'import_into'))
341
342
            for (name, attrname), sequence in itertools.groupby(
343
                sorted_violations, attrsgetter('name', 'attrname')):
344
                print "You should not import %s from %s:" % (attrname, name)
345
                import_intos = sorted(
346
                    set([error.import_into for error in sequence]))
347
                for import_into in import_intos:
348
                    print "   ", import_into
349
3560.1.2 by Steve Alexander
fix bug 39393, reenabling the importfascist, and cleaning a couple of import-related thing up on the way.
350
2097 by Canonical.com Patch Queue Manager
[trivial] more import fascist improvements
351
def install_import_fascist():
352
    __builtin__.__import__ = import_fascist
353
    atexit.register(report_naughty_imports)