/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: 2018-07-08 14:45:27 UTC
  • mto: This revision was merged to the branch mainline in revision 7036.
  • Revision ID: jelmer@jelmer.uk-20180708144527-codhlvdcdg9y0nji
Fix a bunch of merge tests.

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
 
43
46
 
44
47
class ScopeReplacer(object):
45
48
    """A lazy object that will replace itself in the appropriate scope.
50
53
 
51
54
    __slots__ = ('_scope', '_factory', '_name', '_real_obj')
52
55
 
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
 
56
    # If you to do x = y, setting this to False will disallow access to
 
57
    # members from the second variable (i.e. x). This should normally
 
58
    # be enabled for reasons of thread safety and documentation, but
 
59
    # will be disabled during the selftest command to check for abuse.
 
60
    _should_proxy = True
57
61
 
58
62
    def __init__(self, scope, factory, name):
59
63
        """Create a temporary object in the specified scope.
70
74
        object.__setattr__(self, '_real_obj', None)
71
75
        scope[name] = self
72
76
 
73
 
    def _replace(self):
74
 
        """Actually replace self with other in the given scope"""
 
77
    def _resolve(self):
 
78
        """Return the real object for which this is a placeholder"""
75
79
        name = object.__getattribute__(self, '_name')
76
 
        try:
 
80
        real_obj = object.__getattribute__(self, '_real_obj')
 
81
        if real_obj is None:
 
82
            # No obj generated previously, so generate from factory and scope.
77
83
            factory = object.__getattribute__(self, '_factory')
78
84
            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
            obj = factory(self, scope, name)
 
86
            if obj is self:
 
87
                raise errors.IllegalUseOfScopeReplacer(name, msg="Object tried"
 
88
                    " to replace itself, check it's not using its own scope.")
 
89
 
 
90
            # Check if another thread has jumped in while obj was generated.
 
91
            real_obj = object.__getattribute__(self, '_real_obj')
 
92
            if real_obj is None:
 
93
                # Still no prexisting obj, so go ahead and assign to scope and
 
94
                # return. There is still a small window here where races will
 
95
                # not be detected, but safest to avoid additional locking.
 
96
                object.__setattr__(self, '_real_obj', obj)
 
97
                scope[name] = obj
 
98
                return obj
 
99
 
 
100
        # Raise if proxying is disabled as obj has already been generated.
 
101
        if not ScopeReplacer._should_proxy:
85
102
            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
 
103
                name, msg="Object already replaced, did you assign it"
 
104
                          " to another variable?")
 
105
        return real_obj
101
106
 
102
107
    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()
 
108
        obj = object.__getattribute__(self, '_resolve')()
109
109
        return getattr(obj, attr)
110
110
 
111
111
    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()
 
112
        obj = object.__getattribute__(self, '_resolve')()
118
113
        return setattr(obj, attr, value)
119
114
 
120
115
    def __call__(self, *args, **kwargs):
121
 
        _replace = object.__getattribute__(self, '_replace')
122
 
        obj = _replace()
123
 
        _cleanup = object.__getattribute__(self, '_cleanup')
124
 
        _cleanup()
 
116
        obj = object.__getattribute__(self, '_resolve')()
125
117
        return obj(*args, **kwargs)
126
118
 
127
119
 
 
120
def disallow_proxying():
 
121
    """Disallow lazily imported modules to be used as proxies.
 
122
 
 
123
    Calling this function might cause problems with concurrent imports
 
124
    in multithreaded environments, but will help detecting wasteful
 
125
    indirection, so it should be called when executing unit tests.
 
126
 
 
127
    Only lazy imports that happen after this call are affected.
 
128
    """
 
129
    ScopeReplacer._should_proxy = False
 
130
 
 
131
 
 
132
_builtin_import = __import__
 
133
 
 
134
 
128
135
class ImportReplacer(ScopeReplacer):
129
136
    """This is designed to replace only a portion of an import list.
130
137
 
150
157
        :param scope: The scope that objects should be imported into.
151
158
            Typically this is globals()
152
159
        :param name: The variable name. Often this is the same as the
153
 
            module_path. 'bzrlib'
 
160
            module_path. 'breezy'
154
161
        :param module_path: A list for the fully specified module path
155
 
            ['bzrlib', 'foo', 'bar']
 
162
            ['breezy', 'foo', 'bar']
156
163
        :param member: The member inside the module to import, often this is
157
164
            None, indicating the module is being imported.
158
165
        :param children: Children entries to be imported later.
159
166
            This should be a map of children specifications.
160
 
            {'foo':(['bzrlib', 'foo'], None,
161
 
                {'bar':(['bzrlib', 'foo', 'bar'], None {})})
162
 
            }
163
 
        Examples:
 
167
            ::
 
168
            
 
169
                {'foo':(['breezy', 'foo'], None,
 
170
                    {'bar':(['breezy', 'foo', 'bar'], None {})})
 
171
                }
 
172
 
 
173
        Examples::
 
174
 
164
175
            import foo => name='foo' module_path='foo',
165
176
                          member=None, children={}
166
177
            import foo.bar => name='foo' module_path='foo', member=None,
187
198
        children = object.__getattribute__(self, '_import_replacer_children')
188
199
        member = object.__getattribute__(self, '_member')
189
200
        module_path = object.__getattribute__(self, '_module_path')
190
 
        module_python_path = '.'.join(module_path)
 
201
        name = '.'.join(module_path)
191
202
        if member is not None:
192
 
            module = __import__(module_python_path, scope, scope, [member])
 
203
            module = _builtin_import(name, scope, scope, [member], level=0)
193
204
            return getattr(module, member)
194
205
        else:
195
 
            module = __import__(module_python_path, scope, scope, [])
 
206
            module = _builtin_import(name, scope, scope, [], level=0)
196
207
            for path in module_path[1:]:
197
208
                module = getattr(module, path)
198
209
 
199
210
        # Prepare the children to be imported
200
211
        for child_name, (child_path, child_member, grandchildren) in \
201
 
                children.iteritems():
 
212
                children.items():
202
213
            # Using self.__class__, so that children get children classes
203
214
            # instantiated. (This helps with instrumented tests)
204
215
            cls = object.__getattribute__(self, '__class__')
237
248
 
238
249
    def _convert_imports(self, scope):
239
250
        # Now convert the map into a set of imports
240
 
        for name, info in self.imports.iteritems():
 
251
        for name, info in self.imports.items():
241
252
            self._lazy_import_class(scope, name=name, module_path=info[0],
242
253
                                    member=info[1], children=info[2])
243
254
 
276
287
                module_path = as_hunks[0].strip().split('.')
277
288
                if name in self.imports:
278
289
                    raise errors.ImportNameCollision(name)
 
290
                if not module_path[0]:
 
291
                    raise ImportError(path)
279
292
                # No children available in 'import foo as bar'
280
293
                self.imports[name] = (module_path, None, {})
281
294
            else:
282
295
                # Now we need to handle
283
296
                module_path = path.split('.')
284
297
                name = module_path[0]
 
298
                if not name:
 
299
                    raise ImportError(path)
285
300
                if name not in self.imports:
286
301
                    # This is a new import that we haven't seen before
287
302
                    module_def = ([name], None, {})
313
328
 
314
329
        from_module_path = from_module.split('.')
315
330
 
 
331
        if not from_module_path[0]:
 
332
            raise ImportError(from_module)
 
333
 
316
334
        for path in import_list.split(','):
317
335
            path = path.strip()
318
336
            if not path:
367
385
def lazy_import(scope, text, lazy_import_class=None):
368
386
    """Create lazy imports for all of the imports in text.
369
387
 
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
 
388
    This is typically used as something like::
 
389
 
 
390
        from breezy.lazy_import import lazy_import
 
391
        lazy_import(globals(), '''
 
392
        from breezy import (
 
393
            foo,
 
394
            bar,
 
395
            baz,
 
396
            )
 
397
        import breezy.branch
 
398
        import breezy.transport
 
399
        ''')
 
400
 
 
401
    Then 'foo, bar, baz' and 'breezy' will exist as lazy-loaded
383
402
    objects which will be replaced with a real object on first use.
384
403
 
385
404
    In general, it is best to only load modules in this way. This is
392
411
    return proc.lazy_import(scope, text)
393
412
 
394
413
 
395
 
# The only module that this module depends on is 'bzrlib.errors'. But it
 
414
# The only module that this module depends on is 'breezy.errors'. But it
396
415
# can actually be imported lazily, since we only need it if there is a
397
416
# problem.
398
417
 
399
418
lazy_import(globals(), """
400
 
from bzrlib import errors
 
419
from breezy import errors
401
420
""")