summaryrefslogtreecommitdiffstats
path: root/Lib/runpy.py
blob: 0af9a503a6591873e236f5041fe77227b626ab28 (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
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
"""runpy.py - locating and running Python code using the module namespace

Provides support for locating and running Python scripts using the Python
module namespace instead of the native filesystem.

This allows Python code to play nicely with non-filesystem based PEP 302
importers when locating support scripts as well as when importing modules.
"""
# Written by Nick Coghlan <ncoghlan at gmail.com>
#    to implement PEP 338 (Executing Modules as Scripts)

import sys
import imp

__all__ = [
    "run_module",
]

try:
    _get_loader = imp.get_loader
except AttributeError:
    # get_loader() is not provided by the imp module, so emulate it
    # as best we can using the PEP 302 import machinery exposed since
    # Python 2.3. The emulation isn't perfect, but the differences
    # in the way names are shadowed shouldn't matter in practice.
    import os.path
    import marshal                           # Handle compiled Python files

    # This helper is needed in order for the PEP 302 emulation to
    # correctly handle compiled files
    def _read_compiled_file(compiled_file):
        magic = compiled_file.read(4)
        if magic != imp.get_magic():
            return None
        try:
            compiled_file.read(4) # Skip timestamp
            return marshal.load(compiled_file)
        except Exception:
            return None

    class _AbsoluteImporter(object):
        """PEP 302 importer wrapper for top level import machinery"""
        def find_module(self, mod_name, path=None):
            if path is not None:
                return None
            try:
                file, filename, mod_info = imp.find_module(mod_name)
            except ImportError:
                return None
            suffix, mode, mod_type = mod_info
            if mod_type == imp.PY_SOURCE:
                loader = _SourceFileLoader(mod_name, file,
                                           filename, mod_info)
            elif mod_type == imp.PY_COMPILED:
                loader = _CompiledFileLoader(mod_name, file,
                                             filename, mod_info)
            elif mod_type == imp.PKG_DIRECTORY:
                loader = _PackageDirLoader(mod_name, file,
                                           filename, mod_info)
            elif mod_type == imp.C_EXTENSION:
                loader = _FileSystemLoader(mod_name, file,
                                           filename, mod_info)
            else:
                loader = _BasicLoader(mod_name, file,
                                      filename, mod_info)
            return loader


    class _FileSystemImporter(object):
        """PEP 302 importer wrapper for filesystem based imports"""
        def __init__(self, path_item=None):
            if path_item is not None:
                if path_item != '' and not os.path.isdir(path_item):
                    raise ImportError("%s is not a directory" % path_item)
                self.path_dir = path_item
            else:
                raise ImportError("Filesystem importer requires "
                                  "a directory name")

        def find_module(self, mod_name, path=None):
            if path is not None:
                return None
            path_dir = self.path_dir
            if path_dir == '':
                path_dir = os.getcwd()
            sub_name = mod_name.rsplit(".", 1)[-1]
            try:
                file, filename, mod_info = imp.find_module(sub_name,
                                                           [path_dir])
            except ImportError:
                return None
            if not filename.startswith(path_dir):
                return None
            suffix, mode, mod_type = mod_info
            if mod_type == imp.PY_SOURCE:
                loader = _SourceFileLoader(mod_name, file,
                                           filename, mod_info)
            elif mod_type == imp.PY_COMPILED:
                loader = _CompiledFileLoader(mod_name, file,
                                             filename, mod_info)
            elif mod_type == imp.PKG_DIRECTORY:
                loader = _PackageDirLoader(mod_name, file,
                                           filename, mod_info)
            elif mod_type == imp.C_EXTENSION:
                loader = _FileSystemLoader(mod_name, file,
                                           filename, mod_info)
            else:
                loader = _BasicLoader(mod_name, file,
                                      filename, mod_info)
            return loader


    class _BasicLoader(object):
        """PEP 302 loader wrapper for top level import machinery"""
        def __init__(self, mod_name, file, filename, mod_info):
            self.mod_name = mod_name
            self.file = file
            self.filename = filename
            self.mod_info = mod_info

        def _fix_name(self, mod_name):
            if mod_name is None:
                mod_name = self.mod_name
            elif mod_name != self.mod_name:
                raise ImportError("Loader for module %s cannot handle "
                                  "module %s" % (self.mod_name, mod_name))
            return mod_name

        def load_module(self, mod_name=None):
            mod_name = self._fix_name(mod_name)
            mod = imp.load_module(mod_name, self.file,
                                  self.filename, self.mod_info)
            mod.__loader__ = self  # for introspection
            return mod

        def get_code(self, mod_name=None):
            return None

        def get_source(self, mod_name=None):
            return None

        def is_package(self, mod_name=None):
            return False

        def close(self):
            if self.file:
                self.file.close()

        def __del__(self):
            self.close()


    class _FileSystemLoader(_BasicLoader):
        """PEP 302 loader wrapper for filesystem based imports"""
        def get_code(self, mod_name=None):
            mod_name = self._fix_name(mod_name)
            return self._get_code(mod_name)

        def get_data(self, pathname):
            return open(pathname, "rb").read()

        def get_filename(self, mod_name=None):
            mod_name = self._fix_name(mod_name)
            return self._get_filename(mod_name)

        def get_source(self, mod_name=None):
            mod_name = self._fix_name(mod_name)
            return self._get_source(mod_name)

        def is_package(self, mod_name=None):
            mod_name = self._fix_name(mod_name)
            return self._is_package(mod_name)

        def _get_code(self, mod_name):
            return None

        def _get_filename(self, mod_name):
            return self.filename

        def _get_source(self, mod_name):
            return None

        def _is_package(self, mod_name):
            return False

    class _PackageDirLoader(_FileSystemLoader):
        """PEP 302 loader wrapper for PKG_DIRECTORY directories"""
        def _is_package(self, mod_name):
            return True


    class _SourceFileLoader(_FileSystemLoader):
        """PEP 302 loader wrapper for PY_SOURCE modules"""
        def _get_code(self, mod_name):
            return compile(self._get_source(mod_name),
                           self.filename, 'exec')

        def _get_source(self, mod_name):
            f = self.file
            f.seek(0)
            return f.read()


    class _CompiledFileLoader(_FileSystemLoader):
        """PEP 302 loader wrapper for PY_COMPILED modules"""
        def _get_code(self, mod_name):
            f = self.file
            f.seek(0)
            return _read_compiled_file(f)


    def _get_importer(path_item):
        """Retrieve a PEP 302 importer for the given path item

        The returned importer is cached in sys.path_importer_cache
        if it was newly created by a path hook.

        If there is no importer, a wrapper around the basic import
        machinery is returned. This wrapper is never inserted into
        the importer cache (None is inserted instead).

        The cache (or part of it) can be cleared manually if a
        rescan of sys.path_hooks is necessary.
        """
        try:
            importer = sys.path_importer_cache[path_item]
        except KeyError:
            for path_hook in sys.path_hooks:
                try:
                    importer = path_hook(path_item)
                    break
                except ImportError:
                    pass
            else:
                importer = None
            sys.path_importer_cache[path_item] = importer
        if importer is None:
            try:
                importer = _FileSystemImporter(path_item)
            except ImportError:
                pass
        return importer


    def _get_path_loader(mod_name, path=None):
        """Retrieve a PEP 302 loader using a path importer"""
        if path is None:
            path = sys.path
            absolute_loader = _AbsoluteImporter().find_module(mod_name)
            if isinstance(absolute_loader, _FileSystemLoader):
                # Found in filesystem, so scan path hooks
                # before accepting this one as the right one
                loader = None
            else:
                # Not found in filesystem, so use top-level loader
                loader = absolute_loader
        else:
            loader = absolute_loader = None
        if loader is None:
            for path_item in path:
                importer = _get_importer(path_item)
                if importer is not None:
                    loader = importer.find_module(mod_name)
                    if loader is not None:
                        # Found a loader for our module
                        break
            else:
                # No path hook found, so accept the top level loader
                loader = absolute_loader
        return loader

    def _get_package(pkg_name):
        """Retrieve a named package"""
        pkg = __import__(pkg_name)
        sub_pkg_names = pkg_name.split(".")
        for sub_pkg in sub_pkg_names[1:]:
            pkg = getattr(pkg, sub_pkg)
        return pkg

    def _get_loader(mod_name, path=None):
        """Retrieve a PEP 302 loader for the given module or package

        If the module or package is accessible via the normal import
        mechanism, a wrapper around the relevant part of that machinery
        is returned.

        Non PEP 302 mechanisms (e.g. the Windows registry) used by the
        standard import machinery to find files in alternative locations
        are partially supported, but are searched AFTER sys.path. Normally,
        these locations are searched BEFORE sys.path, preventing sys.path
        entries from shadowing them.
        For this to cause a visible difference in behaviour, there must
        be a module or package name that is accessible via both sys.path
        and one of the non PEP 302 file system mechanisms. In this case,
        the emulation will find the former version, while the builtin
        import mechanism will find the latter.
        Items of the following types can be affected by this discrepancy:
            imp.C_EXTENSION
            imp.PY_SOURCE
            imp.PY_COMPILED
            imp.PKG_DIRECTORY
        """
        try:
            loader = sys.modules[mod_name].__loader__
        except (KeyError, AttributeError):
            loader = None
        if loader is None:
            imp.acquire_lock()
            try:
                # Module not in sys.modules, or uses an unhooked loader
                parts = mod_name.rsplit(".", 1)
                if len(parts) == 2:
                    # Sub package, so use parent package's path
                    pkg_name, sub_name = parts
                    if pkg_name and pkg_name[0] != '.':
                        if path is not None:
                            raise ImportError("Path argument must be None "
                                            "for a dotted module name")
                        pkg = _get_package(pkg_name)
                        try:
                            path = pkg.__path__
                        except AttributeError:
                            raise ImportError(pkg_name +
                                            " is not a package")
                    else:
                        raise ImportError("Relative import syntax is not "
                                          "supported by _get_loader()")
                else:
                    # Top level module, so stick with default path
                    sub_name = mod_name

                for importer in sys.meta_path:
                    loader = importer.find_module(mod_name, path)
                    if loader is not None:
                        # Found a metahook to handle the module
                        break
                else:
                    # Handling via the standard path mechanism
                    loader = _get_path_loader(mod_name, path)
            finally:
                imp.release_lock()
        return loader


# This helper is needed due to a missing component in the PEP 302
# loader protocol (specifically, "get_filename" is non-standard)
def _get_filename(loader, mod_name):
    try:
        get_filename = loader.get_filename
    except AttributeError:
        return None
    else:
        return get_filename(mod_name)

# ------------------------------------------------------------
# Done with the import machinery emulation, on with the code!

def _run_code(code, run_globals, init_globals,
              mod_name, mod_fname, mod_loader):
    """Helper for _run_module_code"""
    if init_globals is not None:
        run_globals.update(init_globals)
    run_globals.update(__name__ = mod_name,
                       __file__ = mod_fname,
                       __loader__ = mod_loader)
    exec code in run_globals
    return run_globals

def _run_module_code(code, init_globals=None,
                    mod_name=None, mod_fname=None,
                    mod_loader=None, alter_sys=False):
    """Helper for run_module"""
    # Set up the top level namespace dictionary
    if alter_sys:
        # Modify sys.argv[0] and sys.module[mod_name]
        temp_module = imp.new_module(mod_name)
        mod_globals = temp_module.__dict__
        saved_argv0 = sys.argv[0]
        restore_module = mod_name in sys.modules
        if restore_module:
            saved_module = sys.modules[mod_name]
        sys.argv[0] = mod_fname
        sys.modules[mod_name] = temp_module
        try:
            _run_code(code, mod_globals, init_globals,
                      mod_name, mod_fname, mod_loader)
        finally:
            sys.argv[0] = saved_argv0
        if restore_module:
            sys.modules[mod_name] = saved_module
        else:
            del sys.modules[mod_name]
        # Copy the globals of the temporary module, as they
        # may be cleared when the temporary module goes away
        return mod_globals.copy()
    else:
        # Leave the sys module alone
        return _run_code(code, {}, init_globals,
                         mod_name, mod_fname, mod_loader)


def run_module(mod_name, init_globals=None,
                         run_name=None, alter_sys=False):
    """Execute a module's code without importing it

       Returns the resulting top level namespace dictionary
    """
    loader = _get_loader(mod_name)
    if loader is None:
        raise ImportError("No module named " + mod_name)
    code = loader.get_code(mod_name)
    if code is None:
        raise ImportError("No code object available for " + mod_name)
    filename = _get_filename(loader, mod_name)
    if run_name is None:
        run_name = mod_name
    return _run_module_code(code, init_globals, run_name,
                            filename, loader, alter_sys)


if __name__ == "__main__":
    # Run the module specified as the next command line argument
    if len(sys.argv) < 2:
        print >> sys.stderr, "No module specified for execution"
    else:
        del sys.argv[0] # Make the requested module sys.argv[0]
        run_module(sys.argv[0], run_name="__main__", alter_sys=True)