Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/creactistore/_templates/lib/peak/util/imports.py
blob: 36ab04deb2f3cf0464680b4feb63279c546a197e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
"""Tools for doing dynamic imports"""

__all__ = [
    'importString', 'importObject', 'importSequence', 'importSuite',
    'lazyModule', 'joinPath', 'whenImported', 'getModuleHooks',
]

import __main__, sys

from types import StringTypes, ModuleType
from sys import modules
from imp import acquire_lock, release_lock

defaultGlobalDict = __main__.__dict__

try:
    from peak.util.EigenData import AlreadyRead
except ImportError:
    class AlreadyRead(Exception):pass


def importSuite(specs, globalDict=defaultGlobalDict):
    """Create a test suite from import specs"""

    from unittest import TestSuite

    return TestSuite(
        [t() for t in importSequence(specs,globalDict)]
    )












def joinPath(modname, relativePath):
    """Adjust a module name by a '/'-separated, relative or absolute path"""

    module = modname.split('.')
    for p in relativePath.split('/'):

        if p=='..':
            module.pop()
        elif not p:
            module = []
        elif p!='.':
            module.append(p)

    return '.'.join(module)



























def importString(name, globalDict=defaultGlobalDict):
    """Import an item specified by a string

    Example Usage::

        attribute1 = importString('some.module:attribute1')
        attribute2 = importString('other.module:nested.attribute2')

    'importString' imports an object from a module, according to an
    import specification string: a dot-delimited path to an object
    in the Python package namespace.  For example, the string
    '"some.module.attribute"' is equivalent to the result of
    'from some.module import attribute'.

    For readability of import strings, it's sometimes helpful to use a ':' to
    separate a module name from items it contains.  It's optional, though,
    as 'importString' will convert the ':' to a '.' internally anyway."""

    if ':' in name:
        name = name.replace(':','.')

    parts = filter(None,name.split('.'))
    item = __import__(parts.pop(0), globalDict, globalDict, ['__name__'])

    # Fast path for the common case, where everything is imported already
    for attr in parts:
        try:
            item = getattr(item, attr)
        except AttributeError:
            break   # either there's an error, or something needs importing
    else:
        return item

    # We couldn't get there with just getattrs from the base import.  So now
    # we loop *backwards* trying to import longer names, then shorter, until
    # we find the longest possible name that can be handled with __import__,
    # then loop forward again with getattr.  This lets us give more meaningful
    # error messages than if we only went forwards.
    attrs = []
    exc = None

    try:
        while True:
            try:
                # Exit as soon as we find a prefix of the original `name`
                # that's an importable *module* or package
                item = __import__(name, globalDict, globalDict, ['__name__'])
                break
            except ImportError:
                if not exc:
                    # Save the first ImportError, as it's usually the most
                    # informative, especially w/Python < 2.4
                    exc = sys.exc_info()

                if '.' not in name:
                    # We've backed up all the way to the beginning, so reraise
                    # the first ImportError we got
                    raise exc[0],exc[1],exc[2]

                # Otherwise back up one position and try again
                parts = name.split('.')
                attrs.append(parts[-1])
                name = '.'.join(parts[:-1])
    finally:
        exc = None

    # Okay, the module object is now in 'item', so we can just loop forward
    # to retrieving the desired attribute.
    #
    while attrs:
        attr = attrs.pop()
        try:
            item = getattr(item,attr)
        except AttributeError:
            raise ImportError("%r has no %r attribute" % (item,attr))

    return item





def lazyModule(modname, relativePath=None):

    """Return module 'modname', but with its contents loaded "on demand"

    This function returns 'sys.modules[modname]', if present.  Otherwise
    it creates a 'LazyModule' object for the specified module, caches it
    in 'sys.modules', and returns it.

    'LazyModule' is a subclass of the standard Python module type, that
    remains empty until an attempt is made to access one of its
    attributes.  At that moment, the module is loaded into memory, and
    any hooks that were defined via 'whenImported()' are invoked.

    Note that calling 'lazyModule' with the name of a non-existent or
    unimportable module will delay the 'ImportError' until the moment
    access is attempted.  The 'ImportError' will occur every time an
    attribute access is attempted, until the problem is corrected.

    This function also takes an optional second parameter, 'relativePath',
    which will be interpreted as a '/'-separated path string relative to
    'modname'.  If a 'relativePath' is supplied, the module found by
    traversing the path will be loaded instead of 'modname'.  In the path,
    '.' refers to the current module, and '..' to the current module's
    parent.  For example::

        fooBaz = lazyModule('foo.bar','../baz')

    will return the module 'foo.baz'.  The main use of the 'relativePath'
    feature is to allow relative imports in modules that are intended for
    use with module inheritance.  Where an absolute import would be carried
    over as-is into the inheriting module, an import relative to '__name__'
    will be relative to the inheriting module, e.g.::

        something = lazyModule(__name__,'../path/to/something')

    The above code will have different results in each module that inherits
    it.

    (Note: 'relativePath' can also be an absolute path (starting with '/');
    this is mainly useful for module '__bases__' lists.)"""

    def _loadModule(module):
        oldGA = LazyModule.__getattribute__
        oldSA = LazyModule.__setattr__

        modGA = ModuleType.__getattribute__
        modSA = ModuleType.__setattr__

        LazyModule.__getattribute__ = modGA
        LazyModule.__setattr__      = modSA

        acquire_lock()
        try:
            try:
                # don't reload if already loaded!
                if module.__dict__.keys()==['__name__']:
                    # Get Python to do the real import!
                    reload(module)           
                try:
                    for hook in getModuleHooks(module.__name__):
                        hook(module)
                finally:
                    # Ensure hooks are not called again, even if they fail
                    postLoadHooks[module.__name__] = None
            except:
                # Reset our state so that we can retry later
                if '__file__' not in module.__dict__:
                    LazyModule.__getattribute__ = oldGA.im_func
                    LazyModule.__setattr__      = oldSA.im_func
                raise

            try:
                # Convert to a real module (if under 2.2)
                module.__class__ = ModuleType
            except TypeError:
                pass    # 2.3 will fail, but no big deal

        finally:
            release_lock()



    class LazyModule(ModuleType):
        __slots__ = ()
        def __init__(self, name):
            ModuleType.__setattr__(self,'__name__',name)
            #super(LazyModule,self).__init__(name)

        def __getattribute__(self,attr):
            _loadModule(self)
            return ModuleType.__getattribute__(self,attr)

        def __setattr__(self,attr,value):
            _loadModule(self)
            return ModuleType.__setattr__(self,attr,value)

    if relativePath:
        modname = joinPath(modname, relativePath)

    acquire_lock()
    try:
        if modname not in modules:
            getModuleHooks(modname) # force an empty hook list into existence
            modules[modname] = LazyModule(modname)
            if '.' in modname:
                # ensure parent module/package is in sys.modules
                # and parent.modname=module, as soon as the parent is imported   
                splitpos = modname.rindex('.')
                whenImported(
                    modname[:splitpos],
                    lambda m: setattr(m,modname[splitpos+1:],modules[modname])
                )
        return modules[modname]
    finally:
        release_lock()


postLoadHooks = {}





def getModuleHooks(moduleName):

    """Get list of hooks for 'moduleName'; error if module already loaded"""

    acquire_lock()
    try:
        hooks = postLoadHooks.setdefault(moduleName,[])
        if hooks is None:
            raise AlreadyRead("Module already imported", moduleName)
        return hooks
    finally:
        release_lock()


def _setModuleHook(moduleName, hook):
    acquire_lock()
    try:   
        if moduleName in modules and postLoadHooks.get(moduleName) is None:
            # Module is already imported/loaded, just call the hook
            module = modules[moduleName]
            hook(module)
            return module
    
        getModuleHooks(moduleName).append(hook)
        return lazyModule(moduleName)
    finally:
        release_lock()














def whenImported(moduleName, hook=None):

    """Call 'hook(module)' when module named 'moduleName' is first used

    'hook' must accept one argument: the module object named by 'moduleName',
    which must be a fully qualified (i.e. absolute) module name.  The hook
    should not raise any exceptions, or it may prevent later hooks from
    running.

    If the module has already been imported normally, 'hook(module)' is
    called immediately, and the module object is returned from this function.
    If the module has not been imported, or has only been imported lazily,
    then the hook is called when the module is first used, and a lazy import
    of the module is returned from this function.  If the module was imported
    lazily and used before calling this function, the hook is called
    immediately, and the loaded module is returned from this function.

    Note that using this function implies a possible lazy import of the
    specified module, and lazy importing means that any 'ImportError' will be
    deferred until the module is used.
    """
    if hook is None:
        def decorate(func):
            whenImported(moduleName, func)
            return func
        return decorate

    if '.' in moduleName:
        # If parent is not yet imported, delay hook installation until the
        # parent is imported.
        splitpos = moduleName.rindex('.')
        whenImported(
            moduleName[:splitpos], lambda m: _setModuleHook(moduleName,hook)
        )
    else:
        return _setModuleHook(moduleName,hook)





def importObject(spec, globalDict=defaultGlobalDict):

    """Convert a possible string specifier to an object

    If 'spec' is a string or unicode object, import it using 'importString()',
    otherwise return it as-is.
    """

    if isinstance(spec,StringTypes):
        return importString(spec, globalDict)

    return spec


def importSequence(specs, globalDict=defaultGlobalDict):

    """Convert a string or list specifier to a list of objects.

    If 'specs' is a string or unicode object, treat it as a
    comma-separated list of import specifications, and return a
    list of the imported objects.

    If the result is not a string but is iterable, return a list
    with any string/unicode items replaced with their corresponding
    imports.
    """

    if isinstance(specs,StringTypes):
        return [importString(x.strip(),globalDict) for x in specs.split(',')]
    else:
        return [importObject(s,globalDict) for s in specs]