~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/lazy_import.py

  • Committer: Robert Collins
  • Date: 2007-06-19 00:48:22 UTC
  • mto: This revision was merged to the branch mainline in revision 2539.
  • Revision ID: robertc@robertcollins.net-20070619004822-wsop5g2arwu1lti4
Draft proposed integration order for performance changes.

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
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
"""Functionality to create lazy evaluation objects.
18
18
 
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
 
 
 
22
in an on-demand fashion. Typically use looks like:
24
23
    from bzrlib.lazy_import import lazy_import
25
24
    lazy_import(globals(), '''
26
25
    from bzrlib import (
31
30
    import bzrlib.branch
32
31
    ''')
33
32
 
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.
 
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
43
 
47
44
class ScopeReplacer(object):
48
45
    """A lazy object that will replace itself in the appropriate scope.
53
50
 
54
51
    __slots__ = ('_scope', '_factory', '_name', '_real_obj')
55
52
 
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.
60
 
    _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
61
57
 
62
58
    def __init__(self, scope, factory, name):
63
59
        """Create a temporary object in the specified scope.
68
64
            It will be passed (self, scope, name)
69
65
        :param name: The variable name in the given scope.
70
66
        """
71
 
        object.__setattr__(self, '_scope', scope)
72
 
        object.__setattr__(self, '_factory', factory)
73
 
        object.__setattr__(self, '_name', name)
74
 
        object.__setattr__(self, '_real_obj', None)
 
67
        self._scope = scope
 
68
        self._factory = factory
 
69
        self._name = name
 
70
        self._real_obj = None
75
71
        scope[name] = self
76
72
 
77
 
    def _resolve(self):
78
 
        """Return the real object for which this is a placeholder"""
 
73
    def _replace(self):
 
74
        """Actually replace self with other in the given scope"""
79
75
        name = object.__getattribute__(self, '_name')
80
 
        real_obj = object.__getattribute__(self, '_real_obj')
81
 
        if real_obj is None:
82
 
            # No obj generated previously, so generate from factory and scope.
 
76
        try:
83
77
            factory = object.__getattribute__(self, '_factory')
84
78
            scope = object.__getattribute__(self, '_scope')
85
 
            obj = factory(self, scope, name)
86
 
            if obj is self:
87
 
                raise errors.IllegalUseOfScopeReplacer(name, msg="Object tried"
88
 
                    " to replace itself, check it's not using its own scope.")
89
 
 
90
 
            # Check if another thread has jumped in while obj was generated.
91
 
            real_obj = object.__getattribute__(self, '_real_obj')
92
 
            if real_obj is None:
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)
97
 
                scope[name] = obj
98
 
                return obj
99
 
 
100
 
        # Raise if proxying is disabled as obj has already been generated.
101
 
        if not ScopeReplacer._should_proxy:
 
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.
102
85
            raise errors.IllegalUseOfScopeReplacer(
103
 
                name, msg="Object already replaced, did you assign it"
104
 
                          " to another variable?")
105
 
        return real_obj
 
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
            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
106
101
 
107
102
    def __getattribute__(self, attr):
108
 
        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()
109
109
        return getattr(obj, attr)
110
110
 
111
 
    def __setattr__(self, attr, value):
112
 
        obj = object.__getattribute__(self, '_resolve')()
113
 
        return setattr(obj, attr, value)
114
 
 
115
111
    def __call__(self, *args, **kwargs):
116
 
        obj = object.__getattribute__(self, '_resolve')()
 
112
        _replace = object.__getattribute__(self, '_replace')
 
113
        obj = _replace()
 
114
        _cleanup = object.__getattribute__(self, '_cleanup')
 
115
        _cleanup()
117
116
        return obj(*args, **kwargs)
118
117
 
119
118
 
120
 
def disallow_proxying():
121
 
    """Disallow lazily imported modules to be used as proxies.
122
 
 
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.
126
 
 
127
 
    Only lazy imports that happen after this call are affected.
128
 
    """
129
 
    ScopeReplacer._should_proxy = False
130
 
 
131
 
 
132
119
class ImportReplacer(ScopeReplacer):
133
120
    """This is designed to replace only a portion of an import list.
134
121
 
153
140
 
154
141
        :param scope: The scope that objects should be imported into.
155
142
            Typically this is globals()
156
 
        :param name: The variable name. Often this is the same as the
 
143
        :param name: The variable name. Often this is the same as the 
157
144
            module_path. 'bzrlib'
158
145
        :param module_path: A list for the fully specified module path
159
146
            ['bzrlib', 'foo', 'bar']
161
148
            None, indicating the module is being imported.
162
149
        :param children: Children entries to be imported later.
163
150
            This should be a map of children specifications.
164
 
            ::
165
 
            
166
 
                {'foo':(['bzrlib', 'foo'], None,
167
 
                    {'bar':(['bzrlib', 'foo', 'bar'], None {})})
168
 
                }
169
 
 
170
 
        Examples::
171
 
 
 
151
            {'foo':(['bzrlib', 'foo'], None, 
 
152
                {'bar':(['bzrlib', 'foo', 'bar'], None {})})
 
153
            }
 
154
        Examples:
172
155
            import foo => name='foo' module_path='foo',
173
156
                          member=None, children={}
174
157
            import foo.bar => name='foo' module_path='foo', member=None,
178
161
            from foo import bar, baz would get translated into 2 import
179
162
            requests. On for 'name=bar' and one for 'name=baz'
180
163
        """
181
 
        if (member is not None) and children:
182
 
            raise ValueError('Cannot supply both a member and children')
 
164
        if member is not None:
 
165
            assert not children, \
 
166
                'Cannot supply both a member and children'
183
167
 
184
 
        object.__setattr__(self, '_import_replacer_children', children)
185
 
        object.__setattr__(self, '_member', member)
186
 
        object.__setattr__(self, '_module_path', module_path)
 
168
        self._import_replacer_children = children
 
169
        self._member = member
 
170
        self._module_path = module_path
187
171
 
188
172
        # Indirecting through __class__ so that children can
189
173
        # override _import (especially our instrumented version)
197
181
        module_path = object.__getattribute__(self, '_module_path')
198
182
        module_python_path = '.'.join(module_path)
199
183
        if member is not None:
200
 
            module = __import__(module_python_path, scope, scope, [member], level=0)
 
184
            module = __import__(module_python_path, scope, scope, [member])
201
185
            return getattr(module, member)
202
186
        else:
203
 
            module = __import__(module_python_path, scope, scope, [], level=0)
 
187
            module = __import__(module_python_path, scope, scope, [])
204
188
            for path in module_path[1:]:
205
189
                module = getattr(module, path)
206
190
 
267
251
 
268
252
        :param import_str: The import string to process
269
253
        """
270
 
        if not import_str.startswith('import '):
271
 
            raise ValueError('bad import string %r' % (import_str,))
 
254
        assert import_str.startswith('import ')
272
255
        import_str = import_str[len('import '):]
273
256
 
274
257
        for path in import_str.split(','):
313
296
 
314
297
        :param from_str: The import string to process
315
298
        """
316
 
        if not from_str.startswith('from '):
317
 
            raise ValueError('bad from/import %r' % from_str)
 
299
        assert from_str.startswith('from ')
318
300
        from_str = from_str[len('from '):]
319
301
 
320
302
        from_module, import_list = from_str.split(' import ')
375
357
def lazy_import(scope, text, lazy_import_class=None):
376
358
    """Create lazy imports for all of the imports in text.
377
359
 
378
 
    This is typically used as something like::
379
 
 
380
 
        from bzrlib.lazy_import import lazy_import
381
 
        lazy_import(globals(), '''
382
 
        from bzrlib import (
383
 
            foo,
384
 
            bar,
385
 
            baz,
386
 
            )
387
 
        import bzrlib.branch
388
 
        import bzrlib.transport
389
 
        ''')
 
360
    This is typically used as something like:
 
361
    from bzrlib.lazy_import import lazy_import
 
362
    lazy_import(globals(), '''
 
363
    from bzrlib import (
 
364
        foo,
 
365
        bar,
 
366
        baz,
 
367
        )
 
368
    import bzrlib.branch
 
369
    import bzrlib.transport
 
370
    ''')
390
371
 
391
372
    Then 'foo, bar, baz' and 'bzrlib' will exist as lazy-loaded
392
373
    objects which will be replaced with a real object on first use.