1
# Copyright (C) 2006 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.
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
24
lazy_import(globals(), '''
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.
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
class ScopeReplacer(object):
45
"""A lazy object that will replace itself in the appropriate scope.
47
This object sits, ready to create the real object the first time it is
51
__slots__ = ('_scope', '_factory', '_name', '_real_obj')
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.
58
def __init__(self, scope, factory, name):
59
"""Create a temporary object in the specified scope.
60
Once used, a real object will be placed in the scope.
62
:param scope: The scope the object should appear in
63
:param factory: A callable that will create the real object.
64
It will be passed (self, scope, name)
65
:param name: The variable name in the given scope.
68
self._factory = factory
74
"""Actually replace self with other in the given scope"""
75
name = object.__getattribute__(self, '_name')
77
factory = object.__getattribute__(self, '_factory')
78
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?",
89
obj = factory(self, scope, name)
90
if ScopeReplacer._should_proxy:
96
"""Stop holding on to all the extra stuff"""
99
# We keep _name, so that we can report errors
102
def __getattribute__(self, attr):
103
obj = object.__getattribute__(self, '_real_obj')
105
_replace = object.__getattribute__(self, '_replace')
107
_cleanup = object.__getattribute__(self, '_cleanup')
109
return getattr(obj, attr)
111
def __call__(self, *args, **kwargs):
112
_replace = object.__getattribute__(self, '_replace')
114
_cleanup = object.__getattribute__(self, '_cleanup')
116
return obj(*args, **kwargs)
119
class ImportReplacer(ScopeReplacer):
120
"""This is designed to replace only a portion of an import list.
122
It will replace itself with a module, and then make children
123
entries also ImportReplacer objects.
125
At present, this only supports 'import foo.bar.baz' syntax.
128
# '_import_replacer_children' is intentionally a long semi-unique name
129
# that won't likely exist elsewhere. This allows us to detect an
130
# ImportReplacer object by using
131
# object.__getattribute__(obj, '_import_replacer_children')
132
# We can't just use 'isinstance(obj, ImportReplacer)', because that
133
# accesses .__class__, which goes through __getattribute__, and triggers
135
__slots__ = ('_import_replacer_children', '_member', '_module_path')
137
def __init__(self, scope, name, module_path, member=None, children={}):
138
"""Upon request import 'module_path' as the name 'module_name'.
139
When imported, prepare children to also be imported.
141
:param scope: The scope that objects should be imported into.
142
Typically this is globals()
143
:param name: The variable name. Often this is the same as the
144
module_path. 'bzrlib'
145
:param module_path: A list for the fully specified module path
146
['bzrlib', 'foo', 'bar']
147
:param member: The member inside the module to import, often this is
148
None, indicating the module is being imported.
149
:param children: Children entries to be imported later.
150
This should be a map of children specifications.
151
{'foo':(['bzrlib', 'foo'], None,
152
{'bar':(['bzrlib', 'foo', 'bar'], None {})})
155
import foo => name='foo' module_path='foo',
156
member=None, children={}
157
import foo.bar => name='foo' module_path='foo', member=None,
158
children={'bar':(['foo', 'bar'], None, {}}
159
from foo import bar => name='bar' module_path='foo', member='bar'
161
from foo import bar, baz would get translated into 2 import
162
requests. On for 'name=bar' and one for 'name=baz'
164
if member is not None:
165
assert not children, \
166
'Cannot supply both a member and children'
168
self._import_replacer_children = children
169
self._member = member
170
self._module_path = module_path
172
# Indirecting through __class__ so that children can
173
# override _import (especially our instrumented version)
174
cls = object.__getattribute__(self, '__class__')
175
ScopeReplacer.__init__(self, scope=scope, name=name,
178
def _import(self, scope, name):
179
children = object.__getattribute__(self, '_import_replacer_children')
180
member = object.__getattribute__(self, '_member')
181
module_path = object.__getattribute__(self, '_module_path')
182
module_python_path = '.'.join(module_path)
183
if member is not None:
184
module = __import__(module_python_path, scope, scope, [member])
185
return getattr(module, member)
187
module = __import__(module_python_path, scope, scope, [])
188
for path in module_path[1:]:
189
module = getattr(module, path)
191
# Prepare the children to be imported
192
for child_name, (child_path, child_member, grandchildren) in \
193
children.iteritems():
194
# Using self.__class__, so that children get children classes
195
# instantiated. (This helps with instrumented tests)
196
cls = object.__getattribute__(self, '__class__')
197
cls(module.__dict__, name=child_name,
198
module_path=child_path, member=child_member,
199
children=grandchildren)
203
class ImportProcessor(object):
204
"""Convert text that users input into lazy import requests"""
206
# TODO: jam 20060912 This class is probably not strict enough about
207
# what type of text it allows. For example, you can do:
208
# import (foo, bar), which is not allowed by python.
209
# For now, it should be supporting a superset of python import
210
# syntax which is all we really care about.
212
__slots__ = ['imports', '_lazy_import_class']
214
def __init__(self, lazy_import_class=None):
216
if lazy_import_class is None:
217
self._lazy_import_class = ImportReplacer
219
self._lazy_import_class = lazy_import_class
221
def lazy_import(self, scope, text):
222
"""Convert the given text into a bunch of lazy import objects.
224
This takes a text string, which should be similar to normal python
227
self._build_map(text)
228
self._convert_imports(scope)
230
def _convert_imports(self, scope):
231
# Now convert the map into a set of imports
232
for name, info in self.imports.iteritems():
233
self._lazy_import_class(scope, name=name, module_path=info[0],
234
member=info[1], children=info[2])
236
def _build_map(self, text):
237
"""Take a string describing imports, and build up the internal map"""
238
for line in self._canonicalize_import_text(text):
239
if line.startswith('import '):
240
self._convert_import_str(line)
241
elif line.startswith('from '):
242
self._convert_from_str(line)
244
raise errors.InvalidImportLine(line,
245
"doesn't start with 'import ' or 'from '")
247
def _convert_import_str(self, import_str):
248
"""This converts a import string into an import map.
250
This only understands 'import foo, foo.bar, foo.bar.baz as bing'
252
:param import_str: The import string to process
254
assert import_str.startswith('import ')
255
import_str = import_str[len('import '):]
257
for path in import_str.split(','):
261
as_hunks = path.split(' as ')
262
if len(as_hunks) == 2:
263
# We have 'as' so this is a different style of import
264
# 'import foo.bar.baz as bing' creates a local variable
265
# named 'bing' which points to 'foo.bar.baz'
266
name = as_hunks[1].strip()
267
module_path = as_hunks[0].strip().split('.')
268
if name in self.imports:
269
raise errors.ImportNameCollision(name)
270
# No children available in 'import foo as bar'
271
self.imports[name] = (module_path, None, {})
273
# Now we need to handle
274
module_path = path.split('.')
275
name = module_path[0]
276
if name not in self.imports:
277
# This is a new import that we haven't seen before
278
module_def = ([name], None, {})
279
self.imports[name] = module_def
281
module_def = self.imports[name]
285
for child in module_path[1:]:
286
cur_path.append(child)
290
next = (cur_path[:], None, {})
294
def _convert_from_str(self, from_str):
295
"""This converts a 'from foo import bar' string into an import map.
297
:param from_str: The import string to process
299
assert from_str.startswith('from ')
300
from_str = from_str[len('from '):]
302
from_module, import_list = from_str.split(' import ')
304
from_module_path = from_module.split('.')
306
for path in import_list.split(','):
310
as_hunks = path.split(' as ')
311
if len(as_hunks) == 2:
312
# We have 'as' so this is a different style of import
313
# 'import foo.bar.baz as bing' creates a local variable
314
# named 'bing' which points to 'foo.bar.baz'
315
name = as_hunks[1].strip()
316
module = as_hunks[0].strip()
319
if name in self.imports:
320
raise errors.ImportNameCollision(name)
321
self.imports[name] = (from_module_path, module, {})
323
def _canonicalize_import_text(self, text):
324
"""Take a list of imports, and split it into regularized form.
326
This is meant to take regular import text, and convert it to
327
the forms that the rest of the converters prefer.
333
for line in text.split('\n'):
337
line = line[:loc].strip()
342
if line.endswith(')'):
343
out.append(cur + ' ' + line[:-1])
348
if '(' in line and ')' not in line:
349
cur = line.replace('(', '')
351
out.append(line.replace('(', '').replace(')', ''))
353
raise errors.InvalidImportLine(cur, 'Unmatched parenthesis')
357
def lazy_import(scope, text, lazy_import_class=None):
358
"""Create lazy imports for all of the imports in text.
360
This is typically used as something like:
361
from bzrlib.lazy_import import lazy_import
362
lazy_import(globals(), '''
369
import bzrlib.transport
372
Then 'foo, bar, baz' and 'bzrlib' will exist as lazy-loaded
373
objects which will be replaced with a real object on first use.
375
In general, it is best to only load modules in this way. This is
376
because other objects (functions/classes/variables) are frequently
377
used without accessing a member, which means we cannot tell they
380
# This is just a helper around ImportProcessor.lazy_import
381
proc = ImportProcessor(lazy_import_class=lazy_import_class)
382
return proc.lazy_import(scope, text)
385
# The only module that this module depends on is 'bzrlib.errors'. But it
386
# can actually be imported lazily, since we only need it if there is a
389
lazy_import(globals(), """
390
from bzrlib import errors