~bzr-pqm/bzr/bzr.dev

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