Commit 999a336a authored by Nick Coghlan's avatar Nick Coghlan

Make full module name available as __module_name__ even when __name__ is set...

Make full module name available as __module_name__ even when __name__ is set to something else (like '__main__')
parent 0f8b31a2
...@@ -35,16 +35,19 @@ The supplied dictionary will not be modified. If any of the special ...@@ -35,16 +35,19 @@ The supplied dictionary will not be modified. If any of the special
global variables below are defined in the supplied dictionary, those global variables below are defined in the supplied dictionary, those
definitions are overridden by the \code{run_module} function. definitions are overridden by the \code{run_module} function.
The special global variables \code{__name__}, \code{__file__}, The special global variables \code{__name__}, \code{__module_name__},
\code{__loader__} and \code{__builtins__} are set in the globals \code{__file__}, \code{__loader__} and \code{__builtins__} are
dictionary before the module code is executed. set in the globals dictionary before the module code is executed.
\code{__name__} is set to \var{run_name} if this optional argument is \code{__name__} is set to \var{run_name} if this optional argument is
supplied, and the \var{mod_name} argument otherwise. supplied, and the \var{mod_name} argument otherwise.
\code{__module_name__} is always set to \var{mod_name} (this allows
modules to use imports relative to their package name).
\code{__loader__} is set to the PEP 302 module loader used to retrieve \code{__loader__} is set to the PEP 302 module loader used to retrieve
the code for the module (This loader may be a wrapper around the the code for the module (This will not be defined if the module was
standard import mechanism). found using the standard import mechanism).
\code{__file__} is set to the name provided by the module loader. If \code{__file__} is set to the name provided by the module loader. If
the loader does not make filename information available, this the loader does not make filename information available, this
...@@ -55,10 +58,12 @@ the top level namespace of the \module{__builtin__} module. ...@@ -55,10 +58,12 @@ the top level namespace of the \module{__builtin__} module.
If the argument \var{alter_sys} is supplied and evaluates to If the argument \var{alter_sys} is supplied and evaluates to
\code{True}, then \code{sys.argv[0]} is updated with the value of \code{True}, then \code{sys.argv[0]} is updated with the value of
\code{__file__} and \code{sys.modules[__name__]} is updated with a \code{__file__} and \code{sys.modules[mod_name]} is updated with a
temporary module object for the module being executed. Both temporary module object for the module being executed. Both
\code{sys.argv[0]} and \code{sys.modules[__name__]} are restored to \code{sys.argv[0]} and \code{sys.modules[mod_name]} are restored to
their original values before the function returns. their original values before the function returns. If \var{run_name}
differs from \var{mod_name} entries are made in \code{sys.modules}
for both names.
Note that this manipulation of \module{sys} is not thread-safe. Other Note that this manipulation of \module{sys} is not thread-safe. Other
threads may see the partially initialised module, as well as the threads may see the partially initialised module, as well as the
......
...@@ -21,18 +21,19 @@ __all__ = [ ...@@ -21,18 +21,19 @@ __all__ = [
] ]
def _run_code(code, run_globals, init_globals, def _run_code(code, run_globals, init_globals, run_name,
mod_name, mod_fname, mod_loader): mod_name, mod_fname, mod_loader):
"""Helper for _run_module_code""" """Helper for _run_module_code"""
if init_globals is not None: if init_globals is not None:
run_globals.update(init_globals) run_globals.update(init_globals)
run_globals.update(__name__ = mod_name, run_globals.update(__name__ = run_name,
__module_name__ = mod_name,
__file__ = mod_fname, __file__ = mod_fname,
__loader__ = mod_loader) __loader__ = mod_loader)
exec code in run_globals exec code in run_globals
return run_globals return run_globals
def _run_module_code(code, init_globals=None, def _run_module_code(code, init_globals=None, run_name=None,
mod_name=None, mod_fname=None, mod_name=None, mod_fname=None,
mod_loader=None, alter_sys=False): mod_loader=None, alter_sys=False):
"""Helper for run_module""" """Helper for run_module"""
...@@ -42,26 +43,33 @@ def _run_module_code(code, init_globals=None, ...@@ -42,26 +43,33 @@ def _run_module_code(code, init_globals=None,
temp_module = imp.new_module(mod_name) temp_module = imp.new_module(mod_name)
mod_globals = temp_module.__dict__ mod_globals = temp_module.__dict__
saved_argv0 = sys.argv[0] saved_argv0 = sys.argv[0]
restore_module = mod_name in sys.modules sentinel = object()
if restore_module: module_mod_name = sys.modules.get(mod_name, sentinel)
saved_module = sys.modules[mod_name] module_run_name = sys.modules.get(run_name, sentinel)
sys.argv[0] = mod_fname sys.argv[0] = mod_fname
sys.modules[mod_name] = temp_module sys.modules[mod_name] = temp_module
if run_name != mod_name:
sys.modules[run_name] = temp_module
try: try:
_run_code(code, mod_globals, init_globals, _run_code(code, mod_globals, init_globals, run_name,
mod_name, mod_fname, mod_loader) mod_name, mod_fname, mod_loader)
finally: finally:
sys.argv[0] = saved_argv0 sys.argv[0] = saved_argv0
if restore_module: if module_mod_name is not sentinel:
sys.modules[mod_name] = saved_module sys.modules[mod_name] = module_mod_name
else: else:
del sys.modules[mod_name] del sys.modules[mod_name]
if run_name != mod_name:
if module_run_name is not sentinel:
sys.modules[run_name] = module_run_name
else:
del sys.modules[run_name]
# Copy the globals of the temporary module, as they # Copy the globals of the temporary module, as they
# may be cleared when the temporary module goes away # may be cleared when the temporary module goes away
return mod_globals.copy() return mod_globals.copy()
else: else:
# Leave the sys module alone # Leave the sys module alone
return _run_code(code, {}, init_globals, return _run_code(code, {}, init_globals, run_name,
mod_name, mod_fname, mod_loader) mod_name, mod_fname, mod_loader)
...@@ -92,7 +100,7 @@ def run_module(mod_name, init_globals=None, ...@@ -92,7 +100,7 @@ def run_module(mod_name, init_globals=None,
if run_name is None: if run_name is None:
run_name = mod_name run_name = mod_name
return _run_module_code(code, init_globals, run_name, return _run_module_code(code, init_globals, run_name,
filename, loader, alter_sys) mod_name, filename, loader, alter_sys)
if __name__ == "__main__": if __name__ == "__main__":
......
...@@ -23,6 +23,8 @@ class RunModuleCodeTest(unittest.TestCase): ...@@ -23,6 +23,8 @@ class RunModuleCodeTest(unittest.TestCase):
"run_argv0 = sys.argv[0]\n" "run_argv0 = sys.argv[0]\n"
"if __name__ in sys.modules:\n" "if __name__ in sys.modules:\n"
" run_name = sys.modules[__name__].__name__\n" " run_name = sys.modules[__name__].__name__\n"
"if __module_name__ in sys.modules:\n"
" mod_name = sys.modules[__module_name__].__module_name__\n"
"# Check nested operation\n" "# Check nested operation\n"
"import runpy\n" "import runpy\n"
"nested = runpy._run_module_code('x=1\\n', mod_name='<run>',\n" "nested = runpy._run_module_code('x=1\\n', mod_name='<run>',\n"
...@@ -32,14 +34,16 @@ class RunModuleCodeTest(unittest.TestCase): ...@@ -32,14 +34,16 @@ class RunModuleCodeTest(unittest.TestCase):
def test_run_module_code(self): def test_run_module_code(self):
initial = object() initial = object()
name = "<Nonsense>" run_name = "<Nonsense>"
mod_name = "<ModuleNonsense>"
file = "Some other nonsense" file = "Some other nonsense"
loader = "Now you're just being silly" loader = "Now you're just being silly"
d1 = dict(initial=initial) d1 = dict(initial=initial)
saved_argv0 = sys.argv[0] saved_argv0 = sys.argv[0]
d2 = _run_module_code(self.test_source, d2 = _run_module_code(self.test_source,
d1, d1,
name, run_name,
mod_name,
file, file,
loader, loader,
True) True)
...@@ -47,19 +51,23 @@ class RunModuleCodeTest(unittest.TestCase): ...@@ -47,19 +51,23 @@ class RunModuleCodeTest(unittest.TestCase):
self.failUnless(d2["initial"] is initial) self.failUnless(d2["initial"] is initial)
self.failUnless(d2["result"] == self.expected_result) self.failUnless(d2["result"] == self.expected_result)
self.failUnless(d2["nested"]["x"] == 1) self.failUnless(d2["nested"]["x"] == 1)
self.failUnless(d2["__name__"] is name) self.failUnless(d2["__name__"] is run_name)
self.failUnless(d2["run_name"] is name) self.failUnless(d2["run_name"] is run_name)
self.failUnless(d2["__module_name__"] is mod_name)
self.failUnless(d2["mod_name"] is mod_name)
self.failUnless(d2["__file__"] is file) self.failUnless(d2["__file__"] is file)
self.failUnless(d2["run_argv0"] is file) self.failUnless(d2["run_argv0"] is file)
self.failUnless(d2["__loader__"] is loader) self.failUnless(d2["__loader__"] is loader)
self.failUnless(sys.argv[0] is saved_argv0) self.failUnless(sys.argv[0] is saved_argv0)
self.failUnless(name not in sys.modules) self.failUnless(mod_name not in sys.modules)
self.failUnless(run_name not in sys.modules)
def test_run_module_code_defaults(self): def test_run_module_code_defaults(self):
saved_argv0 = sys.argv[0] saved_argv0 = sys.argv[0]
d = _run_module_code(self.test_source) d = _run_module_code(self.test_source)
self.failUnless(d["result"] == self.expected_result) self.failUnless(d["result"] == self.expected_result)
self.failUnless(d["__name__"] is None) self.failUnless(d["__name__"] is None)
self.failUnless(d["__module_name__"] is None)
self.failUnless(d["__file__"] is None) self.failUnless(d["__file__"] is None)
self.failUnless(d["__loader__"] is None) self.failUnless(d["__loader__"] is None)
self.failUnless(d["run_argv0"] is saved_argv0) self.failUnless(d["run_argv0"] is saved_argv0)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment