~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/lazy_import.py

  • Committer: Aaron Bentley
  • Date: 2007-12-20 20:44:45 UTC
  • mto: This revision was merged to the branch mainline in revision 3235.
  • Revision ID: abentley@panoramicfeedback.com-20071220204445-9o2f10gvvd8e4rks
Implement hard-link support for branch and checkout

Show diffs side-by-side

added added

removed removed

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