1
# Copyright (C) 2006 by Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
"""Functionality to create lazy evaluation objects.
19
This includes waiting to import a module until it is actually used.
25
class ScopeReplacer(object):
26
"""A lazy object that will replace itself in the appropriate scope.
28
This object sits, ready to create the real object the first time it is
32
__slots__ = ('_scope', '_factory', '_name')
34
def __init__(self, scope, factory, name):
35
"""Create a temporary object in the specified scope.
36
Once used, a real object will be placed in the scope.
38
:param scope: The scope the object should appear in
39
:param factory: A callable that will create the real object.
40
It will be passed (self, scope, name)
41
:param name: The variable name in the given scope.
44
self._factory = factory
49
"""Actually replace self with other in the given scope"""
50
factory = object.__getattribute__(self, '_factory')
51
scope = object.__getattribute__(self, '_scope')
52
name = object.__getattribute__(self, '_name')
53
obj = factory(self, scope, name)
58
"""Stop holding on to all the extra stuff"""
63
def __getattribute__(self, attr):
64
obj = object.__getattribute__(self, '_replace')()
65
object.__getattribute__(self, '_cleanup')()
66
return getattr(obj, attr)
68
def __call__(self, *args, **kwargs):
69
obj = object.__getattribute__(self, '_replace')()
70
object.__getattribute__(self, '_cleanup')()
71
return obj(*args, **kwargs)
74
class ImportReplacer(ScopeReplacer):
75
"""This is designed to replace only a portion of an import list.
77
It will replace itself with a module, and then make children
78
entries also ImportReplacer objects.
80
At present, this only supports 'import foo.bar.baz' syntax.
83
# Intentially a long semi-unique name that won't likely exist
84
# elsewhere. (We can't use isinstance because that accesses __class__
85
# which causes the __getattribute__ to trigger)
86
__slots__ = ('_import_replacer_children', '_member', '_module_path')
88
def __init__(self, scope, name, module_path, member=None, children=[]):
89
"""Upon request import 'module_path' as the name 'module_name'.
90
When imported, prepare children to also be imported.
92
:param scope: The scope that objects should be imported into.
93
Typically this is globals()
94
:param name: The variable name. Often this is the same as the
96
:param module_path: The fully specified dotted path to the module.
98
:param member: The member inside the module to import, often this is
99
None, indicating the module is being imported.
100
:param children: Children entries to be imported later.
101
This should be a list of children specifications.
102
[('foo', 'bzrlib.foo', [('bar', 'bzrlib.foo.bar'),])]
104
import foo => name='foo' module_path='foo',
105
member=None, children=[]
106
import foo.bar => name='foo' module_path='foo', member=None,
107
children=[('bar', 'foo.bar', [])]
108
from foo import bar => name='bar' module_path='foo', member='bar'
110
from foo import bar, baz would get translated into 2 import
111
requests. On for 'name=bar' and one for 'name=baz'
113
if member is not None:
114
assert not children, \
115
'Cannot supply both a member and children'
117
self._import_replacer_children = children
118
self._member = member
119
self._module_path = module_path
120
ScopeReplacer.__init__(self, scope=scope, name=name,
121
factory=ImportReplacer._import)
123
def _import(self, scope, name):
124
children = object.__getattribute__(self, '_import_replacer_children')
125
member = object.__getattribute__(self, '_member')
126
module_path = object.__getattribute__(self, '_module_path')
127
if member is not None:
128
module = __import__(module_path, scope, scope, (member,))
129
return getattr(module, member)
131
module = __import__(module_path, scope, scope, None)
133
# Prepare the children to be imported
134
for child_name, child_path, grandchildren in children:
135
ImportReplacer(module.__dict__, name=child_name,
136
module_path=child_path, member=None,
137
children=grandchildren)
140
"""Actually replace self with other in the given scope"""
141
factory = object.__getattribute__(self, '_factory')
142
scope = object.__getattribute__(self, '_scope')
143
name = object.__getattribute__(self, '_name')
149
class _Importer(object):
150
"""Helper for importing modules, but waiting until they are used.
152
This also helps to ensure that existing ScopeReplacer objects are
153
re-used in the current scope.
156
__slots__ = ['scope', 'modname', 'fromlist', 'mod']
158
def __init__(self, scope, modname, fromlist):
160
:param scope: calling context globals() where the import should be made
161
:param modname: The name of the module
162
:param fromlist: the fromlist portion of 'from foo import bar'
165
self.modname = modname
166
self.fromlist = fromlist
170
"""Import a module if not imported yet, and return"""
172
self.mod = __import__(self.modname, self.scope, self.scope,
174
if isinstance(self.mod, _replacer):
175
del sys.modules[self.modname]
176
self.mod = __import__(self.modname, self.scope, self.scope,
178
del self.modname, self.fromlist
181
class _replacer(object):
182
'''placeholder for a demand loaded module. demandload puts this in
183
a target scope. when an attribute of this object is looked up,
184
this object is replaced in the target scope with the actual
187
we use __getattribute__ to avoid namespace clashes between
188
placeholder object and real module.'''
190
def __init__(self, importer, target):
191
self.importer = importer
193
# consider case where we do this:
194
# demandload(globals(), 'foo.bar foo.quux')
195
# foo will already exist in target scope when we get to
196
# foo.quux. so we remember that we will need to demandload
197
# quux into foo's scope when we really load it.
201
return object.__getattribute__(self, 'importer').module()
203
def __getattribute__(self, key):
204
'''look up an attribute in a module and return it. replace the
205
name of the module in the caller\'s dict with the actual
208
module = object.__getattribute__(self, 'module')()
209
target = object.__getattribute__(self, 'target')
210
importer = object.__getattribute__(self, 'importer')
211
later = object.__getattribute__(self, 'later')
214
demandload(module.__dict__, ' '.join(later))
216
importer.scope[target] = module
218
return getattr(module, key)
220
class _replacer_from(_replacer):
221
'''placeholder for a demand loaded module. used for "from foo
222
import ..." emulation. semantics of this are different than
223
regular import, so different implementation needed.'''
226
importer = object.__getattribute__(self, 'importer')
227
target = object.__getattribute__(self, 'target')
229
return getattr(importer.module(), target)
231
def __call__(self, *args, **kwargs):
232
target = object.__getattribute__(self, 'module')()
233
return target(*args, **kwargs)
235
def demandload(scope, modules):
236
'''import modules into scope when each is first used.
238
scope should be the value of globals() in the module calling this
239
function, or locals() in the calling function.
241
modules is a string listing module names, separated by white
242
space. names are handled like this:
245
foo bar import foo, bar
246
foo.bar import foo.bar
247
foo:bar from foo import bar
248
foo:bar,quux from foo import bar, quux
249
foo.bar:quux from foo.bar import quux'''
251
for mod in modules.split():
254
fromlist = mod[col+1:].split(',')
258
importer = _importer(scope, mod, fromlist)
260
for name in fromlist:
261
scope[name] = _replacer_from(importer, name)
266
val = scope.get(basemod)
267
# if base module has already been demandload()ed,
268
# remember to load this submodule into its namespace
270
if isinstance(val, _replacer):
271
later = object.__getattribute__(val, 'later')
272
later.append(mod[dot+1:])
276
scope[basemod] = _replacer(importer, basemod)
278
def lazy_import(scope, module_name, member=None, import_as=None):
279
"""Lazily import a module into the correct scope.
281
This is meant as a possible replacement for __import__.
282
It will return a ScopeReplacer object, which will call the real
283
'__import__' at the appropriate time.
285
:param module_name: The dotted module name
286
:param member: Optional, if supplied return the sub member instead of
288
:param import_as: Use this as the local object name instead of the
291
if import_as is None:
293
module_pieces = module_name.split('.')
294
final_name = module_pieces[0]
296
return __import__(module_name, scope, locals(), [])
300
raise NotImplemented('import_as is not yet implemented')