~bzr-pqm/bzr/bzr.dev

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