/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/lazy_import.py

  • Committer: Jelmer Vernooij
  • Date: 2020-02-18 01:57:45 UTC
  • mto: This revision was merged to the branch mainline in revision 7493.
  • Revision ID: jelmer@jelmer.uk-20200218015745-q2ss9tsk74h4nh61
drop use of future.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006 Canonical Ltd
 
1
# Copyright (C) 2006-2010 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
19
19
This includes waiting to import a module until it is actually used.
20
20
 
21
21
Most commonly, the 'lazy_import' function is used to import other modules
22
 
in an on-demand fashion. Typically use looks like:
23
 
    from bzrlib.lazy_import import lazy_import
 
22
in an on-demand fashion. Typically use looks like::
 
23
 
 
24
    from .lazy_import import lazy_import
24
25
    lazy_import(globals(), '''
25
 
    from bzrlib import (
 
26
    from breezy import (
26
27
        errors,
27
28
        osutils,
28
29
        branch,
29
30
        )
30
 
    import bzrlib.branch
 
31
    import breezy.branch
31
32
    ''')
32
33
 
33
 
    Then 'errors, osutils, branch' and 'bzrlib' will exist as lazy-loaded
34
 
    objects which will be replaced with a real object on first use.
 
34
Then 'errors, osutils, branch' and 'breezy' will exist as lazy-loaded
 
35
objects which will be replaced with a real object on first use.
35
36
 
36
 
    In general, it is best to only load modules in this way. This is because
37
 
    it isn't safe to pass these variables to other functions before they
38
 
    have been replaced. This is especially true for constants, sometimes
39
 
    true for classes or functions (when used as a factory, or you want
40
 
    to inherit from them).
 
37
In general, it is best to only load modules in this way. This is because
 
38
it isn't safe to pass these variables to other functions before they
 
39
have been replaced. This is especially true for constants, sometimes
 
40
true for classes or functions (when used as a factory, or you want
 
41
to inherit from them).
41
42
"""
42
43
 
 
44
from .errors import BzrError, InternalBzrError
 
45
 
 
46
 
 
47
class ImportNameCollision(InternalBzrError):
 
48
 
 
49
    _fmt = ("Tried to import an object to the same name as"
 
50
            " an existing object. %(name)s")
 
51
 
 
52
    def __init__(self, name):
 
53
        BzrError.__init__(self)
 
54
        self.name = name
 
55
 
 
56
 
 
57
class IllegalUseOfScopeReplacer(InternalBzrError):
 
58
 
 
59
    _fmt = ("ScopeReplacer object %(name)r was used incorrectly:"
 
60
            " %(msg)s%(extra)s")
 
61
 
 
62
    def __init__(self, name, msg, extra=None):
 
63
        BzrError.__init__(self)
 
64
        self.name = name
 
65
        self.msg = msg
 
66
        if extra:
 
67
            self.extra = ': ' + str(extra)
 
68
        else:
 
69
            self.extra = ''
 
70
 
 
71
 
 
72
class InvalidImportLine(InternalBzrError):
 
73
 
 
74
    _fmt = "Not a valid import statement: %(msg)\n%(text)s"
 
75
 
 
76
    def __init__(self, text, msg):
 
77
        BzrError.__init__(self)
 
78
        self.text = text
 
79
        self.msg = msg
 
80
 
43
81
 
44
82
class ScopeReplacer(object):
45
83
    """A lazy object that will replace itself in the appropriate scope.
50
88
 
51
89
    __slots__ = ('_scope', '_factory', '_name', '_real_obj')
52
90
 
53
 
    # Setting this to True will allow you to do x = y, and still access members
54
 
    # from both variables. This should not normally be enabled, but is useful
55
 
    # when building documentation.
56
 
    _should_proxy = False
 
91
    # If you to do x = y, setting this to False will disallow access to
 
92
    # members from the second variable (i.e. x). This should normally
 
93
    # be enabled for reasons of thread safety and documentation, but
 
94
    # will be disabled during the selftest command to check for abuse.
 
95
    _should_proxy = True
57
96
 
58
97
    def __init__(self, scope, factory, name):
59
98
        """Create a temporary object in the specified scope.
70
109
        object.__setattr__(self, '_real_obj', None)
71
110
        scope[name] = self
72
111
 
73
 
    def _replace(self):
74
 
        """Actually replace self with other in the given scope"""
 
112
    def _resolve(self):
 
113
        """Return the real object for which this is a placeholder"""
75
114
        name = object.__getattribute__(self, '_name')
76
 
        try:
 
115
        real_obj = object.__getattribute__(self, '_real_obj')
 
116
        if real_obj is None:
 
117
            # No obj generated previously, so generate from factory and scope.
77
118
            factory = object.__getattribute__(self, '_factory')
78
119
            scope = object.__getattribute__(self, '_scope')
79
 
        except AttributeError, e:
80
 
            # Because ScopeReplacer objects only replace a single
81
 
            # item, passing them to another variable before they are
82
 
            # replaced would cause them to keep getting replaced
83
 
            # (only they are replacing the wrong variable). So we
84
 
            # make it forbidden, and try to give a good error.
85
 
            raise errors.IllegalUseOfScopeReplacer(
86
 
                name, msg="Object already cleaned up, did you assign it"
87
 
                          " to another variable?",
88
 
                extra=e)
89
 
        obj = factory(self, scope, name)
90
 
        if ScopeReplacer._should_proxy:
91
 
            object.__setattr__(self, '_real_obj', obj)
92
 
        scope[name] = obj
93
 
        return obj
94
 
 
95
 
    def _cleanup(self):
96
 
        """Stop holding on to all the extra stuff"""
97
 
        del self._factory
98
 
        del self._scope
99
 
        # We keep _name, so that we can report errors
100
 
        # del self._name
 
120
            obj = factory(self, scope, name)
 
121
            if obj is self:
 
122
                raise IllegalUseOfScopeReplacer(
 
123
                    name, msg="Object tried"
 
124
                    " to replace itself, check it's not using its own scope.")
 
125
 
 
126
            # Check if another thread has jumped in while obj was generated.
 
127
            real_obj = object.__getattribute__(self, '_real_obj')
 
128
            if real_obj is None:
 
129
                # Still no prexisting obj, so go ahead and assign to scope and
 
130
                # return. There is still a small window here where races will
 
131
                # not be detected, but safest to avoid additional locking.
 
132
                object.__setattr__(self, '_real_obj', obj)
 
133
                scope[name] = obj
 
134
                return obj
 
135
 
 
136
        # Raise if proxying is disabled as obj has already been generated.
 
137
        if not ScopeReplacer._should_proxy:
 
138
            raise IllegalUseOfScopeReplacer(
 
139
                name, msg="Object already replaced, did you assign it"
 
140
                          " to another variable?")
 
141
        return real_obj
101
142
 
102
143
    def __getattribute__(self, attr):
103
 
        obj = object.__getattribute__(self, '_real_obj')
104
 
        if obj is None:
105
 
            _replace = object.__getattribute__(self, '_replace')
106
 
            obj = _replace()
107
 
            _cleanup = object.__getattribute__(self, '_cleanup')
108
 
            _cleanup()
 
144
        obj = object.__getattribute__(self, '_resolve')()
109
145
        return getattr(obj, attr)
110
146
 
111
147
    def __setattr__(self, attr, value):
112
 
        obj = object.__getattribute__(self, '_real_obj')
113
 
        if obj is None:
114
 
            _replace = object.__getattribute__(self, '_replace')
115
 
            obj = _replace()
116
 
            _cleanup = object.__getattribute__(self, '_cleanup')
117
 
            _cleanup()
 
148
        obj = object.__getattribute__(self, '_resolve')()
118
149
        return setattr(obj, attr, value)
119
150
 
120
151
    def __call__(self, *args, **kwargs):
121
 
        _replace = object.__getattribute__(self, '_replace')
122
 
        obj = _replace()
123
 
        _cleanup = object.__getattribute__(self, '_cleanup')
124
 
        _cleanup()
 
152
        obj = object.__getattribute__(self, '_resolve')()
125
153
        return obj(*args, **kwargs)
126
154
 
127
155
 
 
156
def disallow_proxying():
 
157
    """Disallow lazily imported modules to be used as proxies.
 
158
 
 
159
    Calling this function might cause problems with concurrent imports
 
160
    in multithreaded environments, but will help detecting wasteful
 
161
    indirection, so it should be called when executing unit tests.
 
162
 
 
163
    Only lazy imports that happen after this call are affected.
 
164
    """
 
165
    ScopeReplacer._should_proxy = False
 
166
 
 
167
 
 
168
_builtin_import = __import__
 
169
 
 
170
 
128
171
class ImportReplacer(ScopeReplacer):
129
172
    """This is designed to replace only a portion of an import list.
130
173
 
150
193
        :param scope: The scope that objects should be imported into.
151
194
            Typically this is globals()
152
195
        :param name: The variable name. Often this is the same as the
153
 
            module_path. 'bzrlib'
 
196
            module_path. 'breezy'
154
197
        :param module_path: A list for the fully specified module path
155
 
            ['bzrlib', 'foo', 'bar']
 
198
            ['breezy', 'foo', 'bar']
156
199
        :param member: The member inside the module to import, often this is
157
200
            None, indicating the module is being imported.
158
201
        :param children: Children entries to be imported later.
159
202
            This should be a map of children specifications.
160
 
            {'foo':(['bzrlib', 'foo'], None,
161
 
                {'bar':(['bzrlib', 'foo', 'bar'], None {})})
162
 
            }
163
 
        Examples:
 
203
            ::
 
204
 
 
205
                {'foo':(['breezy', 'foo'], None,
 
206
                    {'bar':(['breezy', 'foo', 'bar'], None {})})
 
207
                }
 
208
 
 
209
        Examples::
 
210
 
164
211
            import foo => name='foo' module_path='foo',
165
212
                          member=None, children={}
166
213
            import foo.bar => name='foo' module_path='foo', member=None,
187
234
        children = object.__getattribute__(self, '_import_replacer_children')
188
235
        member = object.__getattribute__(self, '_member')
189
236
        module_path = object.__getattribute__(self, '_module_path')
190
 
        module_python_path = '.'.join(module_path)
 
237
        name = '.'.join(module_path)
191
238
        if member is not None:
192
 
            module = __import__(module_python_path, scope, scope, [member])
 
239
            module = _builtin_import(name, scope, scope, [member], level=0)
193
240
            return getattr(module, member)
194
241
        else:
195
 
            module = __import__(module_python_path, scope, scope, [])
 
242
            module = _builtin_import(name, scope, scope, [], level=0)
196
243
            for path in module_path[1:]:
197
244
                module = getattr(module, path)
198
245
 
199
246
        # Prepare the children to be imported
200
247
        for child_name, (child_path, child_member, grandchildren) in \
201
 
                children.iteritems():
 
248
                children.items():
202
249
            # Using self.__class__, so that children get children classes
203
250
            # instantiated. (This helps with instrumented tests)
204
251
            cls = object.__getattribute__(self, '__class__')
237
284
 
238
285
    def _convert_imports(self, scope):
239
286
        # Now convert the map into a set of imports
240
 
        for name, info in self.imports.iteritems():
 
287
        for name, info in self.imports.items():
241
288
            self._lazy_import_class(scope, name=name, module_path=info[0],
242
289
                                    member=info[1], children=info[2])
243
290
 
249
296
            elif line.startswith('from '):
250
297
                self._convert_from_str(line)
251
298
            else:
252
 
                raise errors.InvalidImportLine(line,
253
 
                    "doesn't start with 'import ' or 'from '")
 
299
                raise InvalidImportLine(
 
300
                    line, "doesn't start with 'import ' or 'from '")
254
301
 
255
302
    def _convert_import_str(self, import_str):
256
303
        """This converts a import string into an import map.
275
322
                name = as_hunks[1].strip()
276
323
                module_path = as_hunks[0].strip().split('.')
277
324
                if name in self.imports:
278
 
                    raise errors.ImportNameCollision(name)
 
325
                    raise ImportNameCollision(name)
 
326
                if not module_path[0]:
 
327
                    raise ImportError(path)
279
328
                # No children available in 'import foo as bar'
280
329
                self.imports[name] = (module_path, None, {})
281
330
            else:
282
331
                # Now we need to handle
283
332
                module_path = path.split('.')
284
333
                name = module_path[0]
 
334
                if not name:
 
335
                    raise ImportError(path)
285
336
                if name not in self.imports:
286
337
                    # This is a new import that we haven't seen before
287
338
                    module_def = ([name], None, {})
313
364
 
314
365
        from_module_path = from_module.split('.')
315
366
 
 
367
        if not from_module_path[0]:
 
368
            raise ImportError(from_module)
 
369
 
316
370
        for path in import_list.split(','):
317
371
            path = path.strip()
318
372
            if not path:
327
381
            else:
328
382
                name = module = path
329
383
            if name in self.imports:
330
 
                raise errors.ImportNameCollision(name)
 
384
                raise ImportNameCollision(name)
331
385
            self.imports[name] = (from_module_path, module, {})
332
386
 
333
387
    def _canonicalize_import_text(self, text):
338
392
        """
339
393
        out = []
340
394
        cur = None
341
 
        continuing = False
342
395
 
343
396
        for line in text.split('\n'):
344
397
            line = line.strip()
360
413
                else:
361
414
                    out.append(line.replace('(', '').replace(')', ''))
362
415
        if cur is not None:
363
 
            raise errors.InvalidImportLine(cur, 'Unmatched parenthesis')
 
416
            raise InvalidImportLine(cur, 'Unmatched parenthesis')
364
417
        return out
365
418
 
366
419
 
367
420
def lazy_import(scope, text, lazy_import_class=None):
368
421
    """Create lazy imports for all of the imports in text.
369
422
 
370
 
    This is typically used as something like:
371
 
    from bzrlib.lazy_import import lazy_import
372
 
    lazy_import(globals(), '''
373
 
    from bzrlib import (
374
 
        foo,
375
 
        bar,
376
 
        baz,
377
 
        )
378
 
    import bzrlib.branch
379
 
    import bzrlib.transport
380
 
    ''')
381
 
 
382
 
    Then 'foo, bar, baz' and 'bzrlib' will exist as lazy-loaded
 
423
    This is typically used as something like::
 
424
 
 
425
        from breezy.lazy_import import lazy_import
 
426
        lazy_import(globals(), '''
 
427
        from breezy import (
 
428
            foo,
 
429
            bar,
 
430
            baz,
 
431
            )
 
432
        import breezy.branch
 
433
        import breezy.transport
 
434
        ''')
 
435
 
 
436
    Then 'foo, bar, baz' and 'breezy' will exist as lazy-loaded
383
437
    objects which will be replaced with a real object on first use.
384
438
 
385
439
    In general, it is best to only load modules in this way. This is
390
444
    # This is just a helper around ImportProcessor.lazy_import
391
445
    proc = ImportProcessor(lazy_import_class=lazy_import_class)
392
446
    return proc.lazy_import(scope, text)
393
 
 
394
 
 
395
 
# The only module that this module depends on is 'bzrlib.errors'. But it
396
 
# can actually be imported lazily, since we only need it if there is a
397
 
# problem.
398
 
 
399
 
lazy_import(globals(), """
400
 
from bzrlib import errors
401
 
""")