/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-04-05 19:11:34 UTC
  • mto: (7490.7.16 work)
  • mto: This revision was merged to the branch mainline in revision 7501.
  • Revision ID: jelmer@jelmer.uk-20200405191134-0aebh8ikiwygxma5
Populate the .gitignore file.

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 __future__ import absolute_import
 
45
 
 
46
from .errors import BzrError, InternalBzrError
 
47
 
 
48
 
 
49
class ImportNameCollision(InternalBzrError):
 
50
 
 
51
    _fmt = ("Tried to import an object to the same name as"
 
52
            " an existing object. %(name)s")
 
53
 
 
54
    def __init__(self, name):
 
55
        BzrError.__init__(self)
 
56
        self.name = name
 
57
 
 
58
 
 
59
class IllegalUseOfScopeReplacer(InternalBzrError):
 
60
 
 
61
    _fmt = ("ScopeReplacer object %(name)r was used incorrectly:"
 
62
            " %(msg)s%(extra)s")
 
63
 
 
64
    def __init__(self, name, msg, extra=None):
 
65
        BzrError.__init__(self)
 
66
        self.name = name
 
67
        self.msg = msg
 
68
        if extra:
 
69
            self.extra = ': ' + str(extra)
 
70
        else:
 
71
            self.extra = ''
 
72
 
 
73
 
 
74
class InvalidImportLine(InternalBzrError):
 
75
 
 
76
    _fmt = "Not a valid import statement: %(msg)\n%(text)s"
 
77
 
 
78
    def __init__(self, text, msg):
 
79
        BzrError.__init__(self)
 
80
        self.text = text
 
81
        self.msg = msg
 
82
 
43
83
 
44
84
class ScopeReplacer(object):
45
85
    """A lazy object that will replace itself in the appropriate scope.
50
90
 
51
91
    __slots__ = ('_scope', '_factory', '_name', '_real_obj')
52
92
 
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
 
93
    # If you to do x = y, setting this to False will disallow access to
 
94
    # members from the second variable (i.e. x). This should normally
 
95
    # be enabled for reasons of thread safety and documentation, but
 
96
    # will be disabled during the selftest command to check for abuse.
 
97
    _should_proxy = True
57
98
 
58
99
    def __init__(self, scope, factory, name):
59
100
        """Create a temporary object in the specified scope.
70
111
        object.__setattr__(self, '_real_obj', None)
71
112
        scope[name] = self
72
113
 
73
 
    def _replace(self):
74
 
        """Actually replace self with other in the given scope"""
 
114
    def _resolve(self):
 
115
        """Return the real object for which this is a placeholder"""
75
116
        name = object.__getattribute__(self, '_name')
76
 
        try:
 
117
        real_obj = object.__getattribute__(self, '_real_obj')
 
118
        if real_obj is None:
 
119
            # No obj generated previously, so generate from factory and scope.
77
120
            factory = object.__getattribute__(self, '_factory')
78
121
            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
 
122
            obj = factory(self, scope, name)
 
123
            if obj is self:
 
124
                raise IllegalUseOfScopeReplacer(
 
125
                    name, msg="Object tried"
 
126
                    " to replace itself, check it's not using its own scope.")
 
127
 
 
128
            # Check if another thread has jumped in while obj was generated.
 
129
            real_obj = object.__getattribute__(self, '_real_obj')
 
130
            if real_obj is None:
 
131
                # Still no prexisting obj, so go ahead and assign to scope and
 
132
                # return. There is still a small window here where races will
 
133
                # not be detected, but safest to avoid additional locking.
 
134
                object.__setattr__(self, '_real_obj', obj)
 
135
                scope[name] = obj
 
136
                return obj
 
137
 
 
138
        # Raise if proxying is disabled as obj has already been generated.
 
139
        if not ScopeReplacer._should_proxy:
 
140
            raise IllegalUseOfScopeReplacer(
 
141
                name, msg="Object already replaced, did you assign it"
 
142
                          " to another variable?")
 
143
        return real_obj
101
144
 
102
145
    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()
 
146
        obj = object.__getattribute__(self, '_resolve')()
109
147
        return getattr(obj, attr)
110
148
 
111
149
    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()
 
150
        obj = object.__getattribute__(self, '_resolve')()
118
151
        return setattr(obj, attr, value)
119
152
 
120
153
    def __call__(self, *args, **kwargs):
121
 
        _replace = object.__getattribute__(self, '_replace')
122
 
        obj = _replace()
123
 
        _cleanup = object.__getattribute__(self, '_cleanup')
124
 
        _cleanup()
 
154
        obj = object.__getattribute__(self, '_resolve')()
125
155
        return obj(*args, **kwargs)
126
156
 
127
157
 
 
158
def disallow_proxying():
 
159
    """Disallow lazily imported modules to be used as proxies.
 
160
 
 
161
    Calling this function might cause problems with concurrent imports
 
162
    in multithreaded environments, but will help detecting wasteful
 
163
    indirection, so it should be called when executing unit tests.
 
164
 
 
165
    Only lazy imports that happen after this call are affected.
 
166
    """
 
167
    ScopeReplacer._should_proxy = False
 
168
 
 
169
 
 
170
_builtin_import = __import__
 
171
 
 
172
 
128
173
class ImportReplacer(ScopeReplacer):
129
174
    """This is designed to replace only a portion of an import list.
130
175
 
150
195
        :param scope: The scope that objects should be imported into.
151
196
            Typically this is globals()
152
197
        :param name: The variable name. Often this is the same as the
153
 
            module_path. 'bzrlib'
 
198
            module_path. 'breezy'
154
199
        :param module_path: A list for the fully specified module path
155
 
            ['bzrlib', 'foo', 'bar']
 
200
            ['breezy', 'foo', 'bar']
156
201
        :param member: The member inside the module to import, often this is
157
202
            None, indicating the module is being imported.
158
203
        :param children: Children entries to be imported later.
159
204
            This should be a map of children specifications.
160
 
            {'foo':(['bzrlib', 'foo'], None,
161
 
                {'bar':(['bzrlib', 'foo', 'bar'], None {})})
162
 
            }
163
 
        Examples:
 
205
            ::
 
206
 
 
207
                {'foo':(['breezy', 'foo'], None,
 
208
                    {'bar':(['breezy', 'foo', 'bar'], None {})})
 
209
                }
 
210
 
 
211
        Examples::
 
212
 
164
213
            import foo => name='foo' module_path='foo',
165
214
                          member=None, children={}
166
215
            import foo.bar => name='foo' module_path='foo', member=None,
187
236
        children = object.__getattribute__(self, '_import_replacer_children')
188
237
        member = object.__getattribute__(self, '_member')
189
238
        module_path = object.__getattribute__(self, '_module_path')
190
 
        module_python_path = '.'.join(module_path)
 
239
        name = '.'.join(module_path)
191
240
        if member is not None:
192
 
            module = __import__(module_python_path, scope, scope, [member])
 
241
            module = _builtin_import(name, scope, scope, [member], level=0)
193
242
            return getattr(module, member)
194
243
        else:
195
 
            module = __import__(module_python_path, scope, scope, [])
 
244
            module = _builtin_import(name, scope, scope, [], level=0)
196
245
            for path in module_path[1:]:
197
246
                module = getattr(module, path)
198
247
 
199
248
        # Prepare the children to be imported
200
249
        for child_name, (child_path, child_member, grandchildren) in \
201
 
                children.iteritems():
 
250
                children.items():
202
251
            # Using self.__class__, so that children get children classes
203
252
            # instantiated. (This helps with instrumented tests)
204
253
            cls = object.__getattribute__(self, '__class__')
237
286
 
238
287
    def _convert_imports(self, scope):
239
288
        # Now convert the map into a set of imports
240
 
        for name, info in self.imports.iteritems():
 
289
        for name, info in self.imports.items():
241
290
            self._lazy_import_class(scope, name=name, module_path=info[0],
242
291
                                    member=info[1], children=info[2])
243
292
 
249
298
            elif line.startswith('from '):
250
299
                self._convert_from_str(line)
251
300
            else:
252
 
                raise errors.InvalidImportLine(line,
253
 
                    "doesn't start with 'import ' or 'from '")
 
301
                raise InvalidImportLine(
 
302
                    line, "doesn't start with 'import ' or 'from '")
254
303
 
255
304
    def _convert_import_str(self, import_str):
256
305
        """This converts a import string into an import map.
275
324
                name = as_hunks[1].strip()
276
325
                module_path = as_hunks[0].strip().split('.')
277
326
                if name in self.imports:
278
 
                    raise errors.ImportNameCollision(name)
 
327
                    raise ImportNameCollision(name)
 
328
                if not module_path[0]:
 
329
                    raise ImportError(path)
279
330
                # No children available in 'import foo as bar'
280
331
                self.imports[name] = (module_path, None, {})
281
332
            else:
282
333
                # Now we need to handle
283
334
                module_path = path.split('.')
284
335
                name = module_path[0]
 
336
                if not name:
 
337
                    raise ImportError(path)
285
338
                if name not in self.imports:
286
339
                    # This is a new import that we haven't seen before
287
340
                    module_def = ([name], None, {})
313
366
 
314
367
        from_module_path = from_module.split('.')
315
368
 
 
369
        if not from_module_path[0]:
 
370
            raise ImportError(from_module)
 
371
 
316
372
        for path in import_list.split(','):
317
373
            path = path.strip()
318
374
            if not path:
327
383
            else:
328
384
                name = module = path
329
385
            if name in self.imports:
330
 
                raise errors.ImportNameCollision(name)
 
386
                raise ImportNameCollision(name)
331
387
            self.imports[name] = (from_module_path, module, {})
332
388
 
333
389
    def _canonicalize_import_text(self, text):
338
394
        """
339
395
        out = []
340
396
        cur = None
341
 
        continuing = False
342
397
 
343
398
        for line in text.split('\n'):
344
399
            line = line.strip()
360
415
                else:
361
416
                    out.append(line.replace('(', '').replace(')', ''))
362
417
        if cur is not None:
363
 
            raise errors.InvalidImportLine(cur, 'Unmatched parenthesis')
 
418
            raise InvalidImportLine(cur, 'Unmatched parenthesis')
364
419
        return out
365
420
 
366
421
 
367
422
def lazy_import(scope, text, lazy_import_class=None):
368
423
    """Create lazy imports for all of the imports in text.
369
424
 
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
 
425
    This is typically used as something like::
 
426
 
 
427
        from breezy.lazy_import import lazy_import
 
428
        lazy_import(globals(), '''
 
429
        from breezy import (
 
430
            foo,
 
431
            bar,
 
432
            baz,
 
433
            )
 
434
        import breezy.branch
 
435
        import breezy.transport
 
436
        ''')
 
437
 
 
438
    Then 'foo, bar, baz' and 'breezy' will exist as lazy-loaded
383
439
    objects which will be replaced with a real object on first use.
384
440
 
385
441
    In general, it is best to only load modules in this way. This is
390
446
    # This is just a helper around ImportProcessor.lazy_import
391
447
    proc = ImportProcessor(lazy_import_class=lazy_import_class)
392
448
    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
 
""")