/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/lazy_import.py

  • Committer: Robert Collins
  • Date: 2010-05-06 23:41:35 UTC
  • mto: This revision was merged to the branch mainline in revision 5223.
  • Revision ID: robertc@robertcollins.net-20100506234135-yivbzczw1sejxnxc
Lock methods on ``Tree``, ``Branch`` and ``Repository`` are now
expected to return an object which can be used to unlock them. This reduces
duplicate code when using cleanups. The previous 'tokens's returned by
``Branch.lock_write`` and ``Repository.lock_write`` are now attributes
on the result of the lock_write. ``repository.RepositoryWriteLockResult``
and ``branch.BranchWriteLockResult`` document this. (Robert Collins)

``log._get_info_for_log_files`` now takes an add_cleanup callable.
(Robert Collins)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2010 Canonical Ltd
 
1
# Copyright (C) 2006 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
19
19
This includes waiting to import a module until it is actually used.
20
20
 
21
21
Most commonly, the 'lazy_import' function is used to import other modules
22
 
in an on-demand fashion. Typically use looks like::
23
 
 
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(), '''
26
 
    from breezy import (
 
25
    from bzrlib import (
27
26
        errors,
28
27
        osutils,
29
28
        branch,
30
29
        )
31
 
    import breezy.branch
 
30
    import bzrlib.branch
32
31
    ''')
33
32
 
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.
36
35
 
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).
42
41
"""
43
42
 
44
 
from __future__ import absolute_import
45
 
 
46
 
from .errors import BzrError, InternalBzrError
47
 
 
48
 
 
49
 
class ImportNameCollision(InternalBzrError):
50
 
 
51
 
    _fmt = ("Tried to import an object to the same name as"
52
 
            " an existing object. %(name)s")
53
 
 
54
 
    def __init__(self, name):
55
 
        BzrError.__init__(self)
56
 
        self.name = name
57
 
 
58
 
 
59
 
class IllegalUseOfScopeReplacer(InternalBzrError):
60
 
 
61
 
    _fmt = ("ScopeReplacer object %(name)r was used incorrectly:"
62
 
            " %(msg)s%(extra)s")
63
 
 
64
 
    def __init__(self, name, msg, extra=None):
65
 
        BzrError.__init__(self)
66
 
        self.name = name
67
 
        self.msg = msg
68
 
        if extra:
69
 
            self.extra = ': ' + str(extra)
70
 
        else:
71
 
            self.extra = ''
72
 
 
73
 
 
74
 
class InvalidImportLine(InternalBzrError):
75
 
 
76
 
    _fmt = "Not a valid import statement: %(msg)\n%(text)s"
77
 
 
78
 
    def __init__(self, text, msg):
79
 
        BzrError.__init__(self)
80
 
        self.text = text
81
 
        self.msg = msg
82
 
 
83
43
 
84
44
class ScopeReplacer(object):
85
45
    """A lazy object that will replace itself in the appropriate scope.
90
50
 
91
51
    __slots__ = ('_scope', '_factory', '_name', '_real_obj')
92
52
 
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.
97
 
    _should_proxy = True
 
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.
 
56
    _should_proxy = False
98
57
 
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
113
72
 
114
 
    def _resolve(self):
115
 
        """Return the real object for which this is a placeholder"""
 
73
    def _replace(self):
 
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')
118
 
        if real_obj is None:
119
 
            # No obj generated previously, so generate from factory and scope.
 
76
        try:
120
77
            factory = object.__getattribute__(self, '_factory')
121
78
            scope = object.__getattribute__(self, '_scope')
122
 
            obj = factory(self, scope, name)
123
 
            if obj is self:
124
 
                raise IllegalUseOfScopeReplacer(
125
 
                    name, msg="Object tried"
126
 
                    " to replace itself, check it's not using its own scope.")
127
 
 
128
 
            # Check if another thread has jumped in while obj was generated.
129
 
            real_obj = object.__getattribute__(self, '_real_obj')
130
 
            if real_obj is None:
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)
135
 
                scope[name] = obj
136
 
                return obj
137
 
 
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?")
143
 
        return real_obj
 
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?",
 
88
                extra=e)
 
89
        obj = factory(self, scope, name)
 
90
        if ScopeReplacer._should_proxy:
 
91
            object.__setattr__(self, '_real_obj', obj)
 
92
        scope[name] = obj
 
93
        return obj
 
94
 
 
95
    def _cleanup(self):
 
96
        """Stop holding on to all the extra stuff"""
 
97
        del self._factory
 
98
        del self._scope
 
99
        # We keep _name, so that we can report errors
 
100
        # del self._name
144
101
 
145
102
    def __getattribute__(self, attr):
146
 
        obj = object.__getattribute__(self, '_resolve')()
 
103
        obj = object.__getattribute__(self, '_real_obj')
 
104
        if obj is None:
 
105
            _replace = object.__getattribute__(self, '_replace')
 
106
            obj = _replace()
 
107
            _cleanup = object.__getattribute__(self, '_cleanup')
 
108
            _cleanup()
147
109
        return getattr(obj, attr)
148
110
 
149
111
    def __setattr__(self, attr, value):
150
 
        obj = object.__getattribute__(self, '_resolve')()
 
112
        obj = object.__getattribute__(self, '_real_obj')
 
113
        if obj is None:
 
114
            _replace = object.__getattribute__(self, '_replace')
 
115
            obj = _replace()
 
116
            _cleanup = object.__getattribute__(self, '_cleanup')
 
117
            _cleanup()
151
118
        return setattr(obj, attr, value)
152
119
 
153
120
    def __call__(self, *args, **kwargs):
154
 
        obj = object.__getattribute__(self, '_resolve')()
 
121
        _replace = object.__getattribute__(self, '_replace')
 
122
        obj = _replace()
 
123
        _cleanup = object.__getattribute__(self, '_cleanup')
 
124
        _cleanup()
155
125
        return obj(*args, **kwargs)
156
126
 
157
127
 
158
 
def disallow_proxying():
159
 
    """Disallow lazily imported modules to be used as proxies.
160
 
 
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.
164
 
 
165
 
    Only lazy imports that happen after this call are affected.
166
 
    """
167
 
    ScopeReplacer._should_proxy = False
168
 
 
169
 
 
170
 
_builtin_import = __import__
171
 
 
172
 
 
173
128
class ImportReplacer(ScopeReplacer):
174
129
    """This is designed to replace only a portion of an import list.
175
130
 
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.
205
 
            ::
206
 
 
207
 
                {'foo':(['breezy', 'foo'], None,
208
 
                    {'bar':(['breezy', 'foo', 'bar'], None {})})
209
 
                }
210
 
 
211
 
        Examples::
212
 
 
 
160
            {'foo':(['bzrlib', 'foo'], None,
 
161
                {'bar':(['bzrlib', 'foo', 'bar'], None {})})
 
162
            }
 
163
        Examples:
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)
243
194
        else:
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)
247
198
 
248
199
        # Prepare the children to be imported
249
200
        for child_name, (child_path, child_member, grandchildren) in \
250
 
                children.items():
 
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__')
286
237
 
287
238
    def _convert_imports(self, scope):
288
239
        # Now convert the map into a set of imports
289
 
        for name, info in self.imports.items():
 
240
        for name, info in self.imports.iteritems():
290
241
            self._lazy_import_class(scope, name=name, module_path=info[0],
291
242
                                    member=info[1], children=info[2])
292
243
 
298
249
            elif line.startswith('from '):
299
250
                self._convert_from_str(line)
300
251
            else:
301
 
                raise InvalidImportLine(
302
 
                    line, "doesn't start with 'import ' or 'from '")
 
252
                raise errors.InvalidImportLine(line,
 
253
                    "doesn't start with 'import ' or 'from '")
303
254
 
304
255
    def _convert_import_str(self, import_str):
305
256
        """This converts a import string into an import map.
324
275
                name = as_hunks[1].strip()
325
276
                module_path = as_hunks[0].strip().split('.')
326
277
                if name in self.imports:
327
 
                    raise ImportNameCollision(name)
328
 
                if not module_path[0]:
329
 
                    raise ImportError(path)
 
278
                    raise errors.ImportNameCollision(name)
330
279
                # No children available in 'import foo as bar'
331
280
                self.imports[name] = (module_path, None, {})
332
281
            else:
333
282
                # Now we need to handle
334
283
                module_path = path.split('.')
335
284
                name = module_path[0]
336
 
                if not name:
337
 
                    raise ImportError(path)
338
285
                if name not in self.imports:
339
286
                    # This is a new import that we haven't seen before
340
287
                    module_def = ([name], None, {})
366
313
 
367
314
        from_module_path = from_module.split('.')
368
315
 
369
 
        if not from_module_path[0]:
370
 
            raise ImportError(from_module)
371
 
 
372
316
        for path in import_list.split(','):
373
317
            path = path.strip()
374
318
            if not path:
383
327
            else:
384
328
                name = module = path
385
329
            if name in self.imports:
386
 
                raise ImportNameCollision(name)
 
330
                raise errors.ImportNameCollision(name)
387
331
            self.imports[name] = (from_module_path, module, {})
388
332
 
389
333
    def _canonicalize_import_text(self, text):
394
338
        """
395
339
        out = []
396
340
        cur = None
 
341
        continuing = False
397
342
 
398
343
        for line in text.split('\n'):
399
344
            line = line.strip()
415
360
                else:
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')
419
364
        return out
420
365
 
421
366
 
422
367
def lazy_import(scope, text, lazy_import_class=None):
423
368
    """Create lazy imports for all of the imports in text.
424
369
 
425
 
    This is typically used as something like::
426
 
 
427
 
        from breezy.lazy_import import lazy_import
428
 
        lazy_import(globals(), '''
429
 
        from breezy import (
430
 
            foo,
431
 
            bar,
432
 
            baz,
433
 
            )
434
 
        import breezy.branch
435
 
        import breezy.transport
436
 
        ''')
437
 
 
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(), '''
 
373
    from bzrlib import (
 
374
        foo,
 
375
        bar,
 
376
        baz,
 
377
        )
 
378
    import bzrlib.branch
 
379
    import bzrlib.transport
 
380
    ''')
 
381
 
 
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.
440
384
 
441
385
    In general, it is best to only load modules in this way. This is
446
390
    # This is just a helper around ImportProcessor.lazy_import
447
391
    proc = ImportProcessor(lazy_import_class=lazy_import_class)
448
392
    return proc.lazy_import(scope, text)
 
393
 
 
394
 
 
395
# The only module that this module depends on is 'bzrlib.errors'. But it
 
396
# can actually be imported lazily, since we only need it if there is a
 
397
# problem.
 
398
 
 
399
lazy_import(globals(), """
 
400
from bzrlib import errors
 
401
""")