~bzr-pqm/bzr/bzr.dev

5387.2.7 by John Arbash Meinel
Merge bzr.dev 5444 to resolve some small text conflicts.
1
# Copyright (C) 2006-2010 Canonical Ltd
1996.1.1 by John Arbash Meinel
Adding a ScopeReplacer class, which can replace itself on demand
2
#
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.
7
#
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.
12
#
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
4183.7.1 by Sabin Iacob
update FSF mailing address
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
1996.1.1 by John Arbash Meinel
Adding a ScopeReplacer class, which can replace itself on demand
16
17
"""Functionality to create lazy evaluation objects.
18
19
This includes waiting to import a module until it is actually used.
1996.1.26 by John Arbash Meinel
Update HACKING and docstrings
20
21
Most commonly, the 'lazy_import' function is used to import other modules
5891.1.2 by Andrew Bennetts
Fix a bunch of docstring formatting nits, making pydoctor a bit happier.
22
in an on-demand fashion. Typically use looks like::
23
1996.1.26 by John Arbash Meinel
Update HACKING and docstrings
24
    from bzrlib.lazy_import import lazy_import
25
    lazy_import(globals(), '''
26
    from bzrlib import (
27
        errors,
28
        osutils,
29
        branch,
30
        )
31
    import bzrlib.branch
32
    ''')
33
5891.1.2 by Andrew Bennetts
Fix a bunch of docstring formatting nits, making pydoctor a bit happier.
34
Then 'errors, osutils, branch' and 'bzrlib' will exist as lazy-loaded
35
objects which will be replaced with a real object on first use.
1996.1.26 by John Arbash Meinel
Update HACKING and docstrings
36
5891.1.2 by Andrew Bennetts
Fix a bunch of docstring formatting nits, making pydoctor a bit happier.
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).
1996.1.1 by John Arbash Meinel
Adding a ScopeReplacer class, which can replace itself on demand
42
"""
43
44
45
class ScopeReplacer(object):
46
    """A lazy object that will replace itself in the appropriate scope.
47
48
    This object sits, ready to create the real object the first time it is
49
    needed.
50
    """
51
2399.1.8 by John Arbash Meinel
Change lazy_import to allow proxying when necessary.
52
    __slots__ = ('_scope', '_factory', '_name', '_real_obj')
2413.4.1 by John Arbash Meinel
Cherrypick just the epydoc builder changes.
53
2399.1.11 by John Arbash Meinel
Update lazy_import with tests for the new '_should_proxy' variable.
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.
57
    _should_proxy = False
58
1996.1.1 by John Arbash Meinel
Adding a ScopeReplacer class, which can replace itself on demand
59
    def __init__(self, scope, factory, name):
60
        """Create a temporary object in the specified scope.
61
        Once used, a real object will be placed in the scope.
62
63
        :param scope: The scope the object should appear in
64
        :param factory: A callable that will create the real object.
65
            It will be passed (self, scope, name)
66
        :param name: The variable name in the given scope.
67
        """
1551.18.22 by Aaron Bentley
Fix exception when ScopeReplacer is assigned to before retrieving any members
68
        object.__setattr__(self, '_scope', scope)
69
        object.__setattr__(self, '_factory', factory)
70
        object.__setattr__(self, '_name', name)
71
        object.__setattr__(self, '_real_obj', None)
1996.1.1 by John Arbash Meinel
Adding a ScopeReplacer class, which can replace itself on demand
72
        scope[name] = self
73
74
    def _replace(self):
75
        """Actually replace self with other in the given scope"""
76
        name = object.__getattribute__(self, '_name')
1996.1.16 by John Arbash Meinel
Raise an exception when ScopeReplacer has been misused
77
        try:
78
            factory = object.__getattribute__(self, '_factory')
79
            scope = object.__getattribute__(self, '_scope')
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.
86
            raise errors.IllegalUseOfScopeReplacer(
87
                name, msg="Object already cleaned up, did you assign it"
1996.1.21 by John Arbash Meinel
fix typo
88
                          " to another variable?",
1996.1.16 by John Arbash Meinel
Raise an exception when ScopeReplacer has been misused
89
                extra=e)
1996.1.1 by John Arbash Meinel
Adding a ScopeReplacer class, which can replace itself on demand
90
        obj = factory(self, scope, name)
5409.2.1 by Martin
Add check in lazy import ScopeReplacer to ensure it's not trying to replace itself
91
        if obj is self:
92
            raise errors.IllegalUseOfScopeReplacer(name, msg="Object tried"
93
                " to replace itself, check it's not using its own scope.")
2399.1.11 by John Arbash Meinel
Update lazy_import with tests for the new '_should_proxy' variable.
94
        if ScopeReplacer._should_proxy:
1551.18.22 by Aaron Bentley
Fix exception when ScopeReplacer is assigned to before retrieving any members
95
            object.__setattr__(self, '_real_obj', obj)
1996.1.1 by John Arbash Meinel
Adding a ScopeReplacer class, which can replace itself on demand
96
        scope[name] = obj
97
        return obj
98
99
    def _cleanup(self):
100
        """Stop holding on to all the extra stuff"""
6082.4.1 by Benji York
fix bug 702914
101
        try:
102
            del self._factory
103
        except AttributeError:
104
            # Oops, we just lost a race with another caller of _cleanup.  Just
105
            # ignore it.
106
            pass
107
108
        try:
109
            del self._scope
110
        except AttributeError:
111
            # Another race loss.  See above.
112
            pass
113
1996.1.16 by John Arbash Meinel
Raise an exception when ScopeReplacer has been misused
114
        # We keep _name, so that we can report errors
115
        # del self._name
1996.1.1 by John Arbash Meinel
Adding a ScopeReplacer class, which can replace itself on demand
116
117
    def __getattribute__(self, attr):
2399.1.8 by John Arbash Meinel
Change lazy_import to allow proxying when necessary.
118
        obj = object.__getattribute__(self, '_real_obj')
119
        if obj is None:
120
            _replace = object.__getattribute__(self, '_replace')
121
            obj = _replace()
122
            _cleanup = object.__getattribute__(self, '_cleanup')
123
            _cleanup()
1996.1.1 by John Arbash Meinel
Adding a ScopeReplacer class, which can replace itself on demand
124
        return getattr(obj, attr)
125
1551.18.22 by Aaron Bentley
Fix exception when ScopeReplacer is assigned to before retrieving any members
126
    def __setattr__(self, attr, value):
127
        obj = object.__getattribute__(self, '_real_obj')
128
        if obj is None:
129
            _replace = object.__getattribute__(self, '_replace')
130
            obj = _replace()
131
            _cleanup = object.__getattribute__(self, '_cleanup')
132
            _cleanup()
133
        return setattr(obj, attr, value)
134
1996.1.1 by John Arbash Meinel
Adding a ScopeReplacer class, which can replace itself on demand
135
    def __call__(self, *args, **kwargs):
1996.1.17 by John Arbash Meinel
Small cleanup
136
        _replace = object.__getattribute__(self, '_replace')
137
        obj = _replace()
138
        _cleanup = object.__getattribute__(self, '_cleanup')
139
        _cleanup()
1996.1.1 by John Arbash Meinel
Adding a ScopeReplacer class, which can replace itself on demand
140
        return obj(*args, **kwargs)
141
142
1996.1.2 by John Arbash Meinel
start working on some lazy importing code
143
class ImportReplacer(ScopeReplacer):
144
    """This is designed to replace only a portion of an import list.
145
146
    It will replace itself with a module, and then make children
147
    entries also ImportReplacer objects.
148
149
    At present, this only supports 'import foo.bar.baz' syntax.
150
    """
151
1996.1.23 by John Arbash Meinel
Clean up comment as suggested by Robert
152
    # '_import_replacer_children' is intentionally a long semi-unique name
153
    # that won't likely exist elsewhere. This allows us to detect an
154
    # ImportReplacer object by using
155
    #       object.__getattribute__(obj, '_import_replacer_children')
156
    # We can't just use 'isinstance(obj, ImportReplacer)', because that
157
    # accesses .__class__, which goes through __getattribute__, and triggers
158
    # the replacement.
1996.1.2 by John Arbash Meinel
start working on some lazy importing code
159
    __slots__ = ('_import_replacer_children', '_member', '_module_path')
160
1996.1.15 by John Arbash Meinel
Everything is now hooked up
161
    def __init__(self, scope, name, module_path, member=None, children={}):
1996.1.2 by John Arbash Meinel
start working on some lazy importing code
162
        """Upon request import 'module_path' as the name 'module_name'.
163
        When imported, prepare children to also be imported.
164
165
        :param scope: The scope that objects should be imported into.
166
            Typically this is globals()
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
167
        :param name: The variable name. Often this is the same as the
1996.1.2 by John Arbash Meinel
start working on some lazy importing code
168
            module_path. 'bzrlib'
1996.1.4 by John Arbash Meinel
Change how parameters are passed to support 'import root1.mod1 as mod1'
169
        :param module_path: A list for the fully specified module path
170
            ['bzrlib', 'foo', 'bar']
1996.1.2 by John Arbash Meinel
start working on some lazy importing code
171
        :param member: The member inside the module to import, often this is
172
            None, indicating the module is being imported.
173
        :param children: Children entries to be imported later.
1996.1.15 by John Arbash Meinel
Everything is now hooked up
174
            This should be a map of children specifications.
5891.1.2 by Andrew Bennetts
Fix a bunch of docstring formatting nits, making pydoctor a bit happier.
175
            ::
176
            
177
                {'foo':(['bzrlib', 'foo'], None,
178
                    {'bar':(['bzrlib', 'foo', 'bar'], None {})})
179
                }
180
181
        Examples::
182
1996.1.2 by John Arbash Meinel
start working on some lazy importing code
183
            import foo => name='foo' module_path='foo',
1996.1.15 by John Arbash Meinel
Everything is now hooked up
184
                          member=None, children={}
1996.1.2 by John Arbash Meinel
start working on some lazy importing code
185
            import foo.bar => name='foo' module_path='foo', member=None,
1996.1.15 by John Arbash Meinel
Everything is now hooked up
186
                              children={'bar':(['foo', 'bar'], None, {}}
1996.1.2 by John Arbash Meinel
start working on some lazy importing code
187
            from foo import bar => name='bar' module_path='foo', member='bar'
1996.1.15 by John Arbash Meinel
Everything is now hooked up
188
                                   children={}
1996.1.2 by John Arbash Meinel
start working on some lazy importing code
189
            from foo import bar, baz would get translated into 2 import
190
            requests. On for 'name=bar' and one for 'name=baz'
191
        """
3376.2.4 by Martin Pool
Remove every assert statement from bzrlib!
192
        if (member is not None) and children:
193
            raise ValueError('Cannot supply both a member and children')
1996.1.2 by John Arbash Meinel
start working on some lazy importing code
194
1551.18.22 by Aaron Bentley
Fix exception when ScopeReplacer is assigned to before retrieving any members
195
        object.__setattr__(self, '_import_replacer_children', children)
196
        object.__setattr__(self, '_member', member)
197
        object.__setattr__(self, '_module_path', module_path)
1996.1.3 by John Arbash Meinel
Basic single-level imports work
198
199
        # Indirecting through __class__ so that children can
200
        # override _import (especially our instrumented version)
201
        cls = object.__getattribute__(self, '__class__')
1996.1.2 by John Arbash Meinel
start working on some lazy importing code
202
        ScopeReplacer.__init__(self, scope=scope, name=name,
1996.1.3 by John Arbash Meinel
Basic single-level imports work
203
                               factory=cls._import)
1996.1.2 by John Arbash Meinel
start working on some lazy importing code
204
205
    def _import(self, scope, name):
206
        children = object.__getattribute__(self, '_import_replacer_children')
207
        member = object.__getattribute__(self, '_member')
208
        module_path = object.__getattribute__(self, '_module_path')
1996.1.4 by John Arbash Meinel
Change how parameters are passed to support 'import root1.mod1 as mod1'
209
        module_python_path = '.'.join(module_path)
1996.1.2 by John Arbash Meinel
start working on some lazy importing code
210
        if member is not None:
1996.1.4 by John Arbash Meinel
Change how parameters are passed to support 'import root1.mod1 as mod1'
211
            module = __import__(module_python_path, scope, scope, [member])
1996.1.2 by John Arbash Meinel
start working on some lazy importing code
212
            return getattr(module, member)
213
        else:
1996.1.4 by John Arbash Meinel
Change how parameters are passed to support 'import root1.mod1 as mod1'
214
            module = __import__(module_python_path, scope, scope, [])
215
            for path in module_path[1:]:
216
                module = getattr(module, path)
1996.1.2 by John Arbash Meinel
start working on some lazy importing code
217
218
        # Prepare the children to be imported
1996.1.15 by John Arbash Meinel
Everything is now hooked up
219
        for child_name, (child_path, child_member, grandchildren) in \
220
                children.iteritems():
1996.1.5 by John Arbash Meinel
Test that we can lazy import a module, and its children
221
            # Using self.__class__, so that children get children classes
222
            # instantiated. (This helps with instrumented tests)
223
            cls = object.__getattribute__(self, '__class__')
224
            cls(module.__dict__, name=child_name,
1996.1.15 by John Arbash Meinel
Everything is now hooked up
225
                module_path=child_path, member=child_member,
1996.1.5 by John Arbash Meinel
Test that we can lazy import a module, and its children
226
                children=grandchildren)
1996.1.3 by John Arbash Meinel
Basic single-level imports work
227
        return module
1996.1.9 by John Arbash Meinel
Create a method for handling 'import *' syntax.
228
229
1996.1.12 by John Arbash Meinel
Switch from individual functions to a class
230
class ImportProcessor(object):
1996.1.15 by John Arbash Meinel
Everything is now hooked up
231
    """Convert text that users input into lazy import requests"""
232
1996.1.18 by John Arbash Meinel
Add more structured error handling
233
    # TODO: jam 20060912 This class is probably not strict enough about
234
    #       what type of text it allows. For example, you can do:
235
    #       import (foo, bar), which is not allowed by python.
236
    #       For now, it should be supporting a superset of python import
237
    #       syntax which is all we really care about.
238
1996.1.15 by John Arbash Meinel
Everything is now hooked up
239
    __slots__ = ['imports', '_lazy_import_class']
240
241
    def __init__(self, lazy_import_class=None):
1996.1.12 by John Arbash Meinel
Switch from individual functions to a class
242
        self.imports = {}
1996.1.15 by John Arbash Meinel
Everything is now hooked up
243
        if lazy_import_class is None:
244
            self._lazy_import_class = ImportReplacer
245
        else:
246
            self._lazy_import_class = lazy_import_class
247
1996.1.19 by John Arbash Meinel
Write a simple wrapper function to make lazy imports easy.
248
    def lazy_import(self, scope, text):
1996.1.15 by John Arbash Meinel
Everything is now hooked up
249
        """Convert the given text into a bunch of lazy import objects.
250
251
        This takes a text string, which should be similar to normal python
252
        import markup.
253
        """
254
        self._build_map(text)
255
        self._convert_imports(scope)
256
257
    def _convert_imports(self, scope):
258
        # Now convert the map into a set of imports
259
        for name, info in self.imports.iteritems():
260
            self._lazy_import_class(scope, name=name, module_path=info[0],
261
                                    member=info[1], children=info[2])
1996.1.12 by John Arbash Meinel
Switch from individual functions to a class
262
1996.1.14 by John Arbash Meinel
Add tests for converting from a string to the final map
263
    def _build_map(self, text):
264
        """Take a string describing imports, and build up the internal map"""
265
        for line in self._canonicalize_import_text(text):
1996.1.18 by John Arbash Meinel
Add more structured error handling
266
            if line.startswith('import '):
1996.1.14 by John Arbash Meinel
Add tests for converting from a string to the final map
267
                self._convert_import_str(line)
1996.1.18 by John Arbash Meinel
Add more structured error handling
268
            elif line.startswith('from '):
1996.1.14 by John Arbash Meinel
Add tests for converting from a string to the final map
269
                self._convert_from_str(line)
1996.1.18 by John Arbash Meinel
Add more structured error handling
270
            else:
271
                raise errors.InvalidImportLine(line,
272
                    "doesn't start with 'import ' or 'from '")
1996.1.14 by John Arbash Meinel
Add tests for converting from a string to the final map
273
1996.1.12 by John Arbash Meinel
Switch from individual functions to a class
274
    def _convert_import_str(self, import_str):
275
        """This converts a import string into an import map.
276
277
        This only understands 'import foo, foo.bar, foo.bar.baz as bing'
278
279
        :param import_str: The import string to process
280
        """
3376.2.4 by Martin Pool
Remove every assert statement from bzrlib!
281
        if not import_str.startswith('import '):
3376.2.8 by Martin Pool
Some review cleanups for assertion removal
282
            raise ValueError('bad import string %r' % (import_str,))
1996.1.12 by John Arbash Meinel
Switch from individual functions to a class
283
        import_str = import_str[len('import '):]
284
285
        for path in import_str.split(','):
286
            path = path.strip()
1996.1.14 by John Arbash Meinel
Add tests for converting from a string to the final map
287
            if not path:
288
                continue
1996.1.12 by John Arbash Meinel
Switch from individual functions to a class
289
            as_hunks = path.split(' as ')
290
            if len(as_hunks) == 2:
291
                # We have 'as' so this is a different style of import
292
                # 'import foo.bar.baz as bing' creates a local variable
293
                # named 'bing' which points to 'foo.bar.baz'
294
                name = as_hunks[1].strip()
295
                module_path = as_hunks[0].strip().split('.')
1996.1.18 by John Arbash Meinel
Add more structured error handling
296
                if name in self.imports:
297
                    raise errors.ImportNameCollision(name)
1996.1.12 by John Arbash Meinel
Switch from individual functions to a class
298
                # No children available in 'import foo as bar'
299
                self.imports[name] = (module_path, None, {})
300
            else:
301
                # Now we need to handle
302
                module_path = path.split('.')
303
                name = module_path[0]
304
                if name not in self.imports:
305
                    # This is a new import that we haven't seen before
306
                    module_def = ([name], None, {})
307
                    self.imports[name] = module_def
308
                else:
309
                    module_def = self.imports[name]
310
311
                cur_path = [name]
312
                cur = module_def[2]
313
                for child in module_path[1:]:
314
                    cur_path.append(child)
315
                    if child in cur:
1996.1.15 by John Arbash Meinel
Everything is now hooked up
316
                        cur = cur[child][2]
1996.1.12 by John Arbash Meinel
Switch from individual functions to a class
317
                    else:
318
                        next = (cur_path[:], None, {})
319
                        cur[child] = next
320
                        cur = next[2]
321
322
    def _convert_from_str(self, from_str):
323
        """This converts a 'from foo import bar' string into an import map.
324
325
        :param from_str: The import string to process
326
        """
3376.2.4 by Martin Pool
Remove every assert statement from bzrlib!
327
        if not from_str.startswith('from '):
328
            raise ValueError('bad from/import %r' % from_str)
1996.1.12 by John Arbash Meinel
Switch from individual functions to a class
329
        from_str = from_str[len('from '):]
330
331
        from_module, import_list = from_str.split(' import ')
332
333
        from_module_path = from_module.split('.')
334
335
        for path in import_list.split(','):
336
            path = path.strip()
1996.1.14 by John Arbash Meinel
Add tests for converting from a string to the final map
337
            if not path:
338
                continue
1996.1.12 by John Arbash Meinel
Switch from individual functions to a class
339
            as_hunks = path.split(' as ')
340
            if len(as_hunks) == 2:
341
                # We have 'as' so this is a different style of import
342
                # 'import foo.bar.baz as bing' creates a local variable
343
                # named 'bing' which points to 'foo.bar.baz'
344
                name = as_hunks[1].strip()
345
                module = as_hunks[0].strip()
346
            else:
347
                name = module = path
1996.1.18 by John Arbash Meinel
Add more structured error handling
348
            if name in self.imports:
349
                raise errors.ImportNameCollision(name)
1996.1.12 by John Arbash Meinel
Switch from individual functions to a class
350
            self.imports[name] = (from_module_path, module, {})
351
1996.1.14 by John Arbash Meinel
Add tests for converting from a string to the final map
352
    def _canonicalize_import_text(self, text):
1996.1.12 by John Arbash Meinel
Switch from individual functions to a class
353
        """Take a list of imports, and split it into regularized form.
354
355
        This is meant to take regular import text, and convert it to
356
        the forms that the rest of the converters prefer.
357
        """
358
        out = []
359
        cur = None
360
        continuing = False
361
362
        for line in text.split('\n'):
363
            line = line.strip()
364
            loc = line.find('#')
365
            if loc != -1:
366
                line = line[:loc].strip()
367
368
            if not line:
369
                continue
370
            if cur is not None:
371
                if line.endswith(')'):
372
                    out.append(cur + ' ' + line[:-1])
373
                    cur = None
374
                else:
375
                    cur += ' ' + line
376
            else:
377
                if '(' in line and ')' not in line:
378
                    cur = line.replace('(', '')
379
                else:
380
                    out.append(line.replace('(', '').replace(')', ''))
1996.1.18 by John Arbash Meinel
Add more structured error handling
381
        if cur is not None:
382
            raise errors.InvalidImportLine(cur, 'Unmatched parenthesis')
1996.1.12 by John Arbash Meinel
Switch from individual functions to a class
383
        return out
1996.1.19 by John Arbash Meinel
Write a simple wrapper function to make lazy imports easy.
384
385
386
def lazy_import(scope, text, lazy_import_class=None):
387
    """Create lazy imports for all of the imports in text.
388
5891.1.2 by Andrew Bennetts
Fix a bunch of docstring formatting nits, making pydoctor a bit happier.
389
    This is typically used as something like::
390
391
        from bzrlib.lazy_import import lazy_import
392
        lazy_import(globals(), '''
393
        from bzrlib import (
394
            foo,
395
            bar,
396
            baz,
397
            )
398
        import bzrlib.branch
399
        import bzrlib.transport
400
        ''')
1996.1.19 by John Arbash Meinel
Write a simple wrapper function to make lazy imports easy.
401
402
    Then 'foo, bar, baz' and 'bzrlib' will exist as lazy-loaded
403
    objects which will be replaced with a real object on first use.
404
405
    In general, it is best to only load modules in this way. This is
406
    because other objects (functions/classes/variables) are frequently
407
    used without accessing a member, which means we cannot tell they
408
    have been used.
409
    """
410
    # This is just a helper around ImportProcessor.lazy_import
411
    proc = ImportProcessor(lazy_import_class=lazy_import_class)
412
    return proc.lazy_import(scope, text)
1996.3.19 by John Arbash Meinel
make lazy_import lazily import errors
413
414
415
# The only module that this module depends on is 'bzrlib.errors'. But it
416
# can actually be imported lazily, since we only need it if there is a
417
# problem.
418
419
lazy_import(globals(), """
420
from bzrlib import errors
421
""")