/brz/remove-bazaar

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