19
19
This includes waiting to import a module until it is actually used.
21
21
Most commonly, the 'lazy_import' function is used to import other modules
22
in an on-demand fashion. Typically use looks like::
24
from .lazy_import import lazy_import
22
in an on-demand fashion. Typically use looks like:
23
from bzrlib.lazy_import import lazy_import
25
24
lazy_import(globals(), '''
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.
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.
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).
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).
44
from __future__ import absolute_import
46
from .errors import BzrError, InternalBzrError
49
class ImportNameCollision(InternalBzrError):
51
_fmt = ("Tried to import an object to the same name as"
52
" an existing object. %(name)s")
54
def __init__(self, name):
55
BzrError.__init__(self)
59
class IllegalUseOfScopeReplacer(InternalBzrError):
61
_fmt = ("ScopeReplacer object %(name)r was used incorrectly:"
64
def __init__(self, name, msg, extra=None):
65
BzrError.__init__(self)
69
self.extra = ': ' + str(extra)
74
class InvalidImportLine(InternalBzrError):
76
_fmt = "Not a valid import statement: %(msg)\n%(text)s"
78
def __init__(self, text, msg):
79
BzrError.__init__(self)
84
44
class ScopeReplacer(object):
85
45
"""A lazy object that will replace itself in the appropriate scope.
91
51
__slots__ = ('_scope', '_factory', '_name', '_real_obj')
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.
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.
99
58
def __init__(self, scope, factory, name):
100
59
"""Create a temporary object in the specified scope.
111
70
object.__setattr__(self, '_real_obj', None)
112
71
scope[name] = self
115
"""Return the real object for which this is a placeholder"""
74
"""Actually replace self with other in the given scope"""
116
75
name = object.__getattribute__(self, '_name')
117
real_obj = object.__getattribute__(self, '_real_obj')
119
# No obj generated previously, so generate from factory and scope.
120
77
factory = object.__getattribute__(self, '_factory')
121
78
scope = object.__getattribute__(self, '_scope')
122
obj = factory(self, scope, name)
124
raise IllegalUseOfScopeReplacer(
125
name, msg="Object tried"
126
" to replace itself, check it's not using its own scope.")
128
# Check if another thread has jumped in while obj was generated.
129
real_obj = object.__getattribute__(self, '_real_obj')
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)
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?")
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?",
89
obj = factory(self, scope, name)
90
if ScopeReplacer._should_proxy:
91
object.__setattr__(self, '_real_obj', obj)
96
"""Stop holding on to all the extra stuff"""
99
# We keep _name, so that we can report errors
145
102
def __getattribute__(self, attr):
146
obj = object.__getattribute__(self, '_resolve')()
103
obj = object.__getattribute__(self, '_real_obj')
105
_replace = object.__getattribute__(self, '_replace')
107
_cleanup = object.__getattribute__(self, '_cleanup')
147
109
return getattr(obj, attr)
149
111
def __setattr__(self, attr, value):
150
obj = object.__getattribute__(self, '_resolve')()
112
obj = object.__getattribute__(self, '_real_obj')
114
_replace = object.__getattribute__(self, '_replace')
116
_cleanup = object.__getattribute__(self, '_cleanup')
151
118
return setattr(obj, attr, value)
153
120
def __call__(self, *args, **kwargs):
154
obj = object.__getattribute__(self, '_resolve')()
121
_replace = object.__getattribute__(self, '_replace')
123
_cleanup = object.__getattribute__(self, '_cleanup')
155
125
return obj(*args, **kwargs)
158
def disallow_proxying():
159
"""Disallow lazily imported modules to be used as proxies.
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.
165
Only lazy imports that happen after this call are affected.
167
ScopeReplacer._should_proxy = False
170
_builtin_import = __import__
173
128
class ImportReplacer(ScopeReplacer):
174
129
"""This is designed to replace only a portion of an import list.
195
150
:param scope: The scope that objects should be imported into.
196
151
Typically this is globals()
197
152
:param name: The variable name. Often this is the same as the
198
module_path. 'breezy'
153
module_path. 'bzrlib'
199
154
:param module_path: A list for the fully specified module path
200
['breezy', 'foo', 'bar']
155
['bzrlib', 'foo', 'bar']
201
156
:param member: The member inside the module to import, often this is
202
157
None, indicating the module is being imported.
203
158
:param children: Children entries to be imported later.
204
159
This should be a map of children specifications.
207
{'foo':(['breezy', 'foo'], None,
208
{'bar':(['breezy', 'foo', 'bar'], None {})})
160
{'foo':(['bzrlib', 'foo'], None,
161
{'bar':(['bzrlib', 'foo', 'bar'], None {})})
213
164
import foo => name='foo' module_path='foo',
214
165
member=None, children={}
215
166
import foo.bar => name='foo' module_path='foo', member=None,
236
187
children = object.__getattribute__(self, '_import_replacer_children')
237
188
member = object.__getattribute__(self, '_member')
238
189
module_path = object.__getattribute__(self, '_module_path')
239
name = '.'.join(module_path)
190
module_python_path = '.'.join(module_path)
240
191
if member is not None:
241
module = _builtin_import(name, scope, scope, [member], level=0)
192
module = __import__(module_python_path, scope, scope, [member])
242
193
return getattr(module, member)
244
module = _builtin_import(name, scope, scope, [], level=0)
195
module = __import__(module_python_path, scope, scope, [])
245
196
for path in module_path[1:]:
246
197
module = getattr(module, path)
248
199
# Prepare the children to be imported
249
200
for child_name, (child_path, child_member, grandchildren) in \
201
children.iteritems():
251
202
# Using self.__class__, so that children get children classes
252
203
# instantiated. (This helps with instrumented tests)
253
204
cls = object.__getattribute__(self, '__class__')
416
361
out.append(line.replace('(', '').replace(')', ''))
417
362
if cur is not None:
418
raise InvalidImportLine(cur, 'Unmatched parenthesis')
363
raise errors.InvalidImportLine(cur, 'Unmatched parenthesis')
422
367
def lazy_import(scope, text, lazy_import_class=None):
423
368
"""Create lazy imports for all of the imports in text.
425
This is typically used as something like::
427
from breezy.lazy_import import lazy_import
428
lazy_import(globals(), '''
435
import breezy.transport
438
Then 'foo, bar, baz' and 'breezy' will exist as lazy-loaded
370
This is typically used as something like:
371
from bzrlib.lazy_import import lazy_import
372
lazy_import(globals(), '''
379
import bzrlib.transport
382
Then 'foo, bar, baz' and 'bzrlib' will exist as lazy-loaded
439
383
objects which will be replaced with a real object on first use.
441
385
In general, it is best to only load modules in this way. This is