54
52
__slots__ = ('_scope', '_factory', '_name', '_real_obj')
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.
54
# Setting this to True will allow you to do x = y, and still access members
55
# from both variables. This should not normally be enabled, but is useful
56
# when building documentation.
62
59
def __init__(self, scope, factory, name):
63
60
"""Create a temporary object in the specified scope.
74
71
object.__setattr__(self, '_real_obj', None)
78
"""Return the real object for which this is a placeholder"""
75
"""Actually replace self with other in the given scope"""
79
76
name = object.__getattribute__(self, '_name')
80
real_obj = object.__getattribute__(self, '_real_obj')
82
# No obj generated previously, so generate from factory and scope.
83
78
factory = object.__getattribute__(self, '_factory')
84
79
scope = object.__getattribute__(self, '_scope')
85
obj = factory(self, scope, name)
87
raise errors.IllegalUseOfScopeReplacer(name, msg="Object tried"
88
" to replace itself, check it's not using its own scope.")
90
# Check if another thread has jumped in while obj was generated.
91
real_obj = object.__getattribute__(self, '_real_obj')
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)
100
# Raise if proxying is disabled as obj has already been generated.
101
if not ScopeReplacer._should_proxy:
80
except AttributeError, e:
81
# Because ScopeReplacer objects only replace a single
82
# item, passing them to another variable before they are
83
# replaced would cause them to keep getting replaced
84
# (only they are replacing the wrong variable). So we
85
# make it forbidden, and try to give a good error.
102
86
raise errors.IllegalUseOfScopeReplacer(
103
name, msg="Object already replaced, did you assign it"
104
" to another variable?")
87
name, msg="Object already cleaned up, did you assign it"
88
" to another variable?",
90
obj = factory(self, scope, name)
92
raise errors.IllegalUseOfScopeReplacer(name, msg="Object tried"
93
" to replace itself, check it's not using its own scope.")
94
if ScopeReplacer._should_proxy:
95
object.__setattr__(self, '_real_obj', obj)
100
"""Stop holding on to all the extra stuff"""
103
except AttributeError:
104
# Oops, we just lost a race with another caller of _cleanup. Just
110
except AttributeError:
111
# Another race loss. See above.
114
# We keep _name, so that we can report errors
107
117
def __getattribute__(self, attr):
108
obj = object.__getattribute__(self, '_resolve')()
118
obj = object.__getattribute__(self, '_real_obj')
120
_replace = object.__getattribute__(self, '_replace')
122
_cleanup = object.__getattribute__(self, '_cleanup')
109
124
return getattr(obj, attr)
111
126
def __setattr__(self, attr, value):
112
obj = object.__getattribute__(self, '_resolve')()
127
obj = object.__getattribute__(self, '_real_obj')
129
_replace = object.__getattribute__(self, '_replace')
131
_cleanup = object.__getattribute__(self, '_cleanup')
113
133
return setattr(obj, attr, value)
115
135
def __call__(self, *args, **kwargs):
116
obj = object.__getattribute__(self, '_resolve')()
136
_replace = object.__getattribute__(self, '_replace')
138
_cleanup = object.__getattribute__(self, '_cleanup')
117
140
return obj(*args, **kwargs)
120
def disallow_proxying():
121
"""Disallow lazily imported modules to be used as proxies.
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.
127
Only lazy imports that happen after this call are affected.
129
ScopeReplacer._should_proxy = False
132
143
class ImportReplacer(ScopeReplacer):
133
144
"""This is designed to replace only a portion of an import list.
197
208
module_path = object.__getattribute__(self, '_module_path')
198
209
module_python_path = '.'.join(module_path)
199
210
if member is not None:
200
module = __import__(module_python_path, scope, scope, [member], level=0)
211
module = __import__(module_python_path, scope, scope, [member])
201
212
return getattr(module, member)
203
module = __import__(module_python_path, scope, scope, [], level=0)
214
module = __import__(module_python_path, scope, scope, [])
204
215
for path in module_path[1:]:
205
216
module = getattr(module, path)