17db96d56Sopenharmony_ci"""runpy.py - locating and running Python code using the module namespace
27db96d56Sopenharmony_ci
37db96d56Sopenharmony_ciProvides support for locating and running Python scripts using the Python
47db96d56Sopenharmony_cimodule namespace instead of the native filesystem.
57db96d56Sopenharmony_ci
67db96d56Sopenharmony_ciThis allows Python code to play nicely with non-filesystem based PEP 302
77db96d56Sopenharmony_ciimporters when locating support scripts as well as when importing modules.
87db96d56Sopenharmony_ci"""
97db96d56Sopenharmony_ci# Written by Nick Coghlan <ncoghlan at gmail.com>
107db96d56Sopenharmony_ci#    to implement PEP 338 (Executing Modules as Scripts)
117db96d56Sopenharmony_ci
127db96d56Sopenharmony_ci
137db96d56Sopenharmony_ciimport sys
147db96d56Sopenharmony_ciimport importlib.machinery # importlib first so we can test #15386 via -m
157db96d56Sopenharmony_ciimport importlib.util
167db96d56Sopenharmony_ciimport io
177db96d56Sopenharmony_ciimport os
187db96d56Sopenharmony_ci
197db96d56Sopenharmony_ci__all__ = [
207db96d56Sopenharmony_ci    "run_module", "run_path",
217db96d56Sopenharmony_ci]
227db96d56Sopenharmony_ci
237db96d56Sopenharmony_ci# avoid 'import types' just for ModuleType
247db96d56Sopenharmony_ciModuleType = type(sys)
257db96d56Sopenharmony_ci
267db96d56Sopenharmony_ciclass _TempModule(object):
277db96d56Sopenharmony_ci    """Temporarily replace a module in sys.modules with an empty namespace"""
287db96d56Sopenharmony_ci    def __init__(self, mod_name):
297db96d56Sopenharmony_ci        self.mod_name = mod_name
307db96d56Sopenharmony_ci        self.module = ModuleType(mod_name)
317db96d56Sopenharmony_ci        self._saved_module = []
327db96d56Sopenharmony_ci
337db96d56Sopenharmony_ci    def __enter__(self):
347db96d56Sopenharmony_ci        mod_name = self.mod_name
357db96d56Sopenharmony_ci        try:
367db96d56Sopenharmony_ci            self._saved_module.append(sys.modules[mod_name])
377db96d56Sopenharmony_ci        except KeyError:
387db96d56Sopenharmony_ci            pass
397db96d56Sopenharmony_ci        sys.modules[mod_name] = self.module
407db96d56Sopenharmony_ci        return self
417db96d56Sopenharmony_ci
427db96d56Sopenharmony_ci    def __exit__(self, *args):
437db96d56Sopenharmony_ci        if self._saved_module:
447db96d56Sopenharmony_ci            sys.modules[self.mod_name] = self._saved_module[0]
457db96d56Sopenharmony_ci        else:
467db96d56Sopenharmony_ci            del sys.modules[self.mod_name]
477db96d56Sopenharmony_ci        self._saved_module = []
487db96d56Sopenharmony_ci
497db96d56Sopenharmony_ciclass _ModifiedArgv0(object):
507db96d56Sopenharmony_ci    def __init__(self, value):
517db96d56Sopenharmony_ci        self.value = value
527db96d56Sopenharmony_ci        self._saved_value = self._sentinel = object()
537db96d56Sopenharmony_ci
547db96d56Sopenharmony_ci    def __enter__(self):
557db96d56Sopenharmony_ci        if self._saved_value is not self._sentinel:
567db96d56Sopenharmony_ci            raise RuntimeError("Already preserving saved value")
577db96d56Sopenharmony_ci        self._saved_value = sys.argv[0]
587db96d56Sopenharmony_ci        sys.argv[0] = self.value
597db96d56Sopenharmony_ci
607db96d56Sopenharmony_ci    def __exit__(self, *args):
617db96d56Sopenharmony_ci        self.value = self._sentinel
627db96d56Sopenharmony_ci        sys.argv[0] = self._saved_value
637db96d56Sopenharmony_ci
647db96d56Sopenharmony_ci# TODO: Replace these helpers with importlib._bootstrap_external functions.
657db96d56Sopenharmony_cidef _run_code(code, run_globals, init_globals=None,
667db96d56Sopenharmony_ci              mod_name=None, mod_spec=None,
677db96d56Sopenharmony_ci              pkg_name=None, script_name=None):
687db96d56Sopenharmony_ci    """Helper to run code in nominated namespace"""
697db96d56Sopenharmony_ci    if init_globals is not None:
707db96d56Sopenharmony_ci        run_globals.update(init_globals)
717db96d56Sopenharmony_ci    if mod_spec is None:
727db96d56Sopenharmony_ci        loader = None
737db96d56Sopenharmony_ci        fname = script_name
747db96d56Sopenharmony_ci        cached = None
757db96d56Sopenharmony_ci    else:
767db96d56Sopenharmony_ci        loader = mod_spec.loader
777db96d56Sopenharmony_ci        fname = mod_spec.origin
787db96d56Sopenharmony_ci        cached = mod_spec.cached
797db96d56Sopenharmony_ci        if pkg_name is None:
807db96d56Sopenharmony_ci            pkg_name = mod_spec.parent
817db96d56Sopenharmony_ci    run_globals.update(__name__ = mod_name,
827db96d56Sopenharmony_ci                       __file__ = fname,
837db96d56Sopenharmony_ci                       __cached__ = cached,
847db96d56Sopenharmony_ci                       __doc__ = None,
857db96d56Sopenharmony_ci                       __loader__ = loader,
867db96d56Sopenharmony_ci                       __package__ = pkg_name,
877db96d56Sopenharmony_ci                       __spec__ = mod_spec)
887db96d56Sopenharmony_ci    exec(code, run_globals)
897db96d56Sopenharmony_ci    return run_globals
907db96d56Sopenharmony_ci
917db96d56Sopenharmony_cidef _run_module_code(code, init_globals=None,
927db96d56Sopenharmony_ci                    mod_name=None, mod_spec=None,
937db96d56Sopenharmony_ci                    pkg_name=None, script_name=None):
947db96d56Sopenharmony_ci    """Helper to run code in new namespace with sys modified"""
957db96d56Sopenharmony_ci    fname = script_name if mod_spec is None else mod_spec.origin
967db96d56Sopenharmony_ci    with _TempModule(mod_name) as temp_module, _ModifiedArgv0(fname):
977db96d56Sopenharmony_ci        mod_globals = temp_module.module.__dict__
987db96d56Sopenharmony_ci        _run_code(code, mod_globals, init_globals,
997db96d56Sopenharmony_ci                  mod_name, mod_spec, pkg_name, script_name)
1007db96d56Sopenharmony_ci    # Copy the globals of the temporary module, as they
1017db96d56Sopenharmony_ci    # may be cleared when the temporary module goes away
1027db96d56Sopenharmony_ci    return mod_globals.copy()
1037db96d56Sopenharmony_ci
1047db96d56Sopenharmony_ci# Helper to get the full name, spec and code for a module
1057db96d56Sopenharmony_cidef _get_module_details(mod_name, error=ImportError):
1067db96d56Sopenharmony_ci    if mod_name.startswith("."):
1077db96d56Sopenharmony_ci        raise error("Relative module names not supported")
1087db96d56Sopenharmony_ci    pkg_name, _, _ = mod_name.rpartition(".")
1097db96d56Sopenharmony_ci    if pkg_name:
1107db96d56Sopenharmony_ci        # Try importing the parent to avoid catching initialization errors
1117db96d56Sopenharmony_ci        try:
1127db96d56Sopenharmony_ci            __import__(pkg_name)
1137db96d56Sopenharmony_ci        except ImportError as e:
1147db96d56Sopenharmony_ci            # If the parent or higher ancestor package is missing, let the
1157db96d56Sopenharmony_ci            # error be raised by find_spec() below and then be caught. But do
1167db96d56Sopenharmony_ci            # not allow other errors to be caught.
1177db96d56Sopenharmony_ci            if e.name is None or (e.name != pkg_name and
1187db96d56Sopenharmony_ci                    not pkg_name.startswith(e.name + ".")):
1197db96d56Sopenharmony_ci                raise
1207db96d56Sopenharmony_ci        # Warn if the module has already been imported under its normal name
1217db96d56Sopenharmony_ci        existing = sys.modules.get(mod_name)
1227db96d56Sopenharmony_ci        if existing is not None and not hasattr(existing, "__path__"):
1237db96d56Sopenharmony_ci            from warnings import warn
1247db96d56Sopenharmony_ci            msg = "{mod_name!r} found in sys.modules after import of " \
1257db96d56Sopenharmony_ci                "package {pkg_name!r}, but prior to execution of " \
1267db96d56Sopenharmony_ci                "{mod_name!r}; this may result in unpredictable " \
1277db96d56Sopenharmony_ci                "behaviour".format(mod_name=mod_name, pkg_name=pkg_name)
1287db96d56Sopenharmony_ci            warn(RuntimeWarning(msg))
1297db96d56Sopenharmony_ci
1307db96d56Sopenharmony_ci    try:
1317db96d56Sopenharmony_ci        spec = importlib.util.find_spec(mod_name)
1327db96d56Sopenharmony_ci    except (ImportError, AttributeError, TypeError, ValueError) as ex:
1337db96d56Sopenharmony_ci        # This hack fixes an impedance mismatch between pkgutil and
1347db96d56Sopenharmony_ci        # importlib, where the latter raises other errors for cases where
1357db96d56Sopenharmony_ci        # pkgutil previously raised ImportError
1367db96d56Sopenharmony_ci        msg = "Error while finding module specification for {!r} ({}: {})"
1377db96d56Sopenharmony_ci        if mod_name.endswith(".py"):
1387db96d56Sopenharmony_ci            msg += (f". Try using '{mod_name[:-3]}' instead of "
1397db96d56Sopenharmony_ci                    f"'{mod_name}' as the module name.")
1407db96d56Sopenharmony_ci        raise error(msg.format(mod_name, type(ex).__name__, ex)) from ex
1417db96d56Sopenharmony_ci    if spec is None:
1427db96d56Sopenharmony_ci        raise error("No module named %s" % mod_name)
1437db96d56Sopenharmony_ci    if spec.submodule_search_locations is not None:
1447db96d56Sopenharmony_ci        if mod_name == "__main__" or mod_name.endswith(".__main__"):
1457db96d56Sopenharmony_ci            raise error("Cannot use package as __main__ module")
1467db96d56Sopenharmony_ci        try:
1477db96d56Sopenharmony_ci            pkg_main_name = mod_name + ".__main__"
1487db96d56Sopenharmony_ci            return _get_module_details(pkg_main_name, error)
1497db96d56Sopenharmony_ci        except error as e:
1507db96d56Sopenharmony_ci            if mod_name not in sys.modules:
1517db96d56Sopenharmony_ci                raise  # No module loaded; being a package is irrelevant
1527db96d56Sopenharmony_ci            raise error(("%s; %r is a package and cannot " +
1537db96d56Sopenharmony_ci                               "be directly executed") %(e, mod_name))
1547db96d56Sopenharmony_ci    loader = spec.loader
1557db96d56Sopenharmony_ci    if loader is None:
1567db96d56Sopenharmony_ci        raise error("%r is a namespace package and cannot be executed"
1577db96d56Sopenharmony_ci                                                                 % mod_name)
1587db96d56Sopenharmony_ci    try:
1597db96d56Sopenharmony_ci        code = loader.get_code(mod_name)
1607db96d56Sopenharmony_ci    except ImportError as e:
1617db96d56Sopenharmony_ci        raise error(format(e)) from e
1627db96d56Sopenharmony_ci    if code is None:
1637db96d56Sopenharmony_ci        raise error("No code object available for %s" % mod_name)
1647db96d56Sopenharmony_ci    return mod_name, spec, code
1657db96d56Sopenharmony_ci
1667db96d56Sopenharmony_ciclass _Error(Exception):
1677db96d56Sopenharmony_ci    """Error that _run_module_as_main() should report without a traceback"""
1687db96d56Sopenharmony_ci
1697db96d56Sopenharmony_ci# XXX ncoghlan: Should this be documented and made public?
1707db96d56Sopenharmony_ci# (Current thoughts: don't repeat the mistake that lead to its
1717db96d56Sopenharmony_ci# creation when run_module() no longer met the needs of
1727db96d56Sopenharmony_ci# mainmodule.c, but couldn't be changed because it was public)
1737db96d56Sopenharmony_cidef _run_module_as_main(mod_name, alter_argv=True):
1747db96d56Sopenharmony_ci    """Runs the designated module in the __main__ namespace
1757db96d56Sopenharmony_ci
1767db96d56Sopenharmony_ci       Note that the executed module will have full access to the
1777db96d56Sopenharmony_ci       __main__ namespace. If this is not desirable, the run_module()
1787db96d56Sopenharmony_ci       function should be used to run the module code in a fresh namespace.
1797db96d56Sopenharmony_ci
1807db96d56Sopenharmony_ci       At the very least, these variables in __main__ will be overwritten:
1817db96d56Sopenharmony_ci           __name__
1827db96d56Sopenharmony_ci           __file__
1837db96d56Sopenharmony_ci           __cached__
1847db96d56Sopenharmony_ci           __loader__
1857db96d56Sopenharmony_ci           __package__
1867db96d56Sopenharmony_ci    """
1877db96d56Sopenharmony_ci    try:
1887db96d56Sopenharmony_ci        if alter_argv or mod_name != "__main__": # i.e. -m switch
1897db96d56Sopenharmony_ci            mod_name, mod_spec, code = _get_module_details(mod_name, _Error)
1907db96d56Sopenharmony_ci        else:          # i.e. directory or zipfile execution
1917db96d56Sopenharmony_ci            mod_name, mod_spec, code = _get_main_module_details(_Error)
1927db96d56Sopenharmony_ci    except _Error as exc:
1937db96d56Sopenharmony_ci        msg = "%s: %s" % (sys.executable, exc)
1947db96d56Sopenharmony_ci        sys.exit(msg)
1957db96d56Sopenharmony_ci    main_globals = sys.modules["__main__"].__dict__
1967db96d56Sopenharmony_ci    if alter_argv:
1977db96d56Sopenharmony_ci        sys.argv[0] = mod_spec.origin
1987db96d56Sopenharmony_ci    return _run_code(code, main_globals, None,
1997db96d56Sopenharmony_ci                     "__main__", mod_spec)
2007db96d56Sopenharmony_ci
2017db96d56Sopenharmony_cidef run_module(mod_name, init_globals=None,
2027db96d56Sopenharmony_ci               run_name=None, alter_sys=False):
2037db96d56Sopenharmony_ci    """Execute a module's code without importing it.
2047db96d56Sopenharmony_ci
2057db96d56Sopenharmony_ci       mod_name -- an absolute module name or package name.
2067db96d56Sopenharmony_ci
2077db96d56Sopenharmony_ci       Optional arguments:
2087db96d56Sopenharmony_ci       init_globals -- dictionary used to pre-populate the module’s
2097db96d56Sopenharmony_ci       globals dictionary before the code is executed.
2107db96d56Sopenharmony_ci
2117db96d56Sopenharmony_ci       run_name -- if not None, this will be used for setting __name__;
2127db96d56Sopenharmony_ci       otherwise, __name__ will be set to mod_name + '__main__' if the
2137db96d56Sopenharmony_ci       named module is a package and to just mod_name otherwise.
2147db96d56Sopenharmony_ci
2157db96d56Sopenharmony_ci       alter_sys -- if True, sys.argv[0] is updated with the value of
2167db96d56Sopenharmony_ci       __file__ and sys.modules[__name__] is updated with a temporary
2177db96d56Sopenharmony_ci       module object for the module being executed. Both are
2187db96d56Sopenharmony_ci       restored to their original values before the function returns.
2197db96d56Sopenharmony_ci
2207db96d56Sopenharmony_ci       Returns the resulting module globals dictionary.
2217db96d56Sopenharmony_ci    """
2227db96d56Sopenharmony_ci    mod_name, mod_spec, code = _get_module_details(mod_name)
2237db96d56Sopenharmony_ci    if run_name is None:
2247db96d56Sopenharmony_ci        run_name = mod_name
2257db96d56Sopenharmony_ci    if alter_sys:
2267db96d56Sopenharmony_ci        return _run_module_code(code, init_globals, run_name, mod_spec)
2277db96d56Sopenharmony_ci    else:
2287db96d56Sopenharmony_ci        # Leave the sys module alone
2297db96d56Sopenharmony_ci        return _run_code(code, {}, init_globals, run_name, mod_spec)
2307db96d56Sopenharmony_ci
2317db96d56Sopenharmony_cidef _get_main_module_details(error=ImportError):
2327db96d56Sopenharmony_ci    # Helper that gives a nicer error message when attempting to
2337db96d56Sopenharmony_ci    # execute a zipfile or directory by invoking __main__.py
2347db96d56Sopenharmony_ci    # Also moves the standard __main__ out of the way so that the
2357db96d56Sopenharmony_ci    # preexisting __loader__ entry doesn't cause issues
2367db96d56Sopenharmony_ci    main_name = "__main__"
2377db96d56Sopenharmony_ci    saved_main = sys.modules[main_name]
2387db96d56Sopenharmony_ci    del sys.modules[main_name]
2397db96d56Sopenharmony_ci    try:
2407db96d56Sopenharmony_ci        return _get_module_details(main_name)
2417db96d56Sopenharmony_ci    except ImportError as exc:
2427db96d56Sopenharmony_ci        if main_name in str(exc):
2437db96d56Sopenharmony_ci            raise error("can't find %r module in %r" %
2447db96d56Sopenharmony_ci                              (main_name, sys.path[0])) from exc
2457db96d56Sopenharmony_ci        raise
2467db96d56Sopenharmony_ci    finally:
2477db96d56Sopenharmony_ci        sys.modules[main_name] = saved_main
2487db96d56Sopenharmony_ci
2497db96d56Sopenharmony_ci
2507db96d56Sopenharmony_cidef _get_code_from_file(run_name, fname):
2517db96d56Sopenharmony_ci    # Check for a compiled file first
2527db96d56Sopenharmony_ci    from pkgutil import read_code
2537db96d56Sopenharmony_ci    decoded_path = os.path.abspath(os.fsdecode(fname))
2547db96d56Sopenharmony_ci    with io.open_code(decoded_path) as f:
2557db96d56Sopenharmony_ci        code = read_code(f)
2567db96d56Sopenharmony_ci    if code is None:
2577db96d56Sopenharmony_ci        # That didn't work, so try it as normal source code
2587db96d56Sopenharmony_ci        with io.open_code(decoded_path) as f:
2597db96d56Sopenharmony_ci            code = compile(f.read(), fname, 'exec')
2607db96d56Sopenharmony_ci    return code, fname
2617db96d56Sopenharmony_ci
2627db96d56Sopenharmony_cidef run_path(path_name, init_globals=None, run_name=None):
2637db96d56Sopenharmony_ci    """Execute code located at the specified filesystem location.
2647db96d56Sopenharmony_ci
2657db96d56Sopenharmony_ci       path_name -- filesystem location of a Python script, zipfile,
2667db96d56Sopenharmony_ci       or directory containing a top level __main__.py script.
2677db96d56Sopenharmony_ci
2687db96d56Sopenharmony_ci       Optional arguments:
2697db96d56Sopenharmony_ci       init_globals -- dictionary used to pre-populate the module’s
2707db96d56Sopenharmony_ci       globals dictionary before the code is executed.
2717db96d56Sopenharmony_ci
2727db96d56Sopenharmony_ci       run_name -- if not None, this will be used to set __name__;
2737db96d56Sopenharmony_ci       otherwise, '<run_path>' will be used for __name__.
2747db96d56Sopenharmony_ci
2757db96d56Sopenharmony_ci       Returns the resulting module globals dictionary.
2767db96d56Sopenharmony_ci    """
2777db96d56Sopenharmony_ci    if run_name is None:
2787db96d56Sopenharmony_ci        run_name = "<run_path>"
2797db96d56Sopenharmony_ci    pkg_name = run_name.rpartition(".")[0]
2807db96d56Sopenharmony_ci    from pkgutil import get_importer
2817db96d56Sopenharmony_ci    importer = get_importer(path_name)
2827db96d56Sopenharmony_ci    # Trying to avoid importing imp so as to not consume the deprecation warning.
2837db96d56Sopenharmony_ci    is_NullImporter = False
2847db96d56Sopenharmony_ci    if type(importer).__module__ == 'imp':
2857db96d56Sopenharmony_ci        if type(importer).__name__ == 'NullImporter':
2867db96d56Sopenharmony_ci            is_NullImporter = True
2877db96d56Sopenharmony_ci    if isinstance(importer, type(None)) or is_NullImporter:
2887db96d56Sopenharmony_ci        # Not a valid sys.path entry, so run the code directly
2897db96d56Sopenharmony_ci        # execfile() doesn't help as we want to allow compiled files
2907db96d56Sopenharmony_ci        code, fname = _get_code_from_file(run_name, path_name)
2917db96d56Sopenharmony_ci        return _run_module_code(code, init_globals, run_name,
2927db96d56Sopenharmony_ci                                pkg_name=pkg_name, script_name=fname)
2937db96d56Sopenharmony_ci    else:
2947db96d56Sopenharmony_ci        # Finder is defined for path, so add it to
2957db96d56Sopenharmony_ci        # the start of sys.path
2967db96d56Sopenharmony_ci        sys.path.insert(0, path_name)
2977db96d56Sopenharmony_ci        try:
2987db96d56Sopenharmony_ci            # Here's where things are a little different from the run_module
2997db96d56Sopenharmony_ci            # case. There, we only had to replace the module in sys while the
3007db96d56Sopenharmony_ci            # code was running and doing so was somewhat optional. Here, we
3017db96d56Sopenharmony_ci            # have no choice and we have to remove it even while we read the
3027db96d56Sopenharmony_ci            # code. If we don't do this, a __loader__ attribute in the
3037db96d56Sopenharmony_ci            # existing __main__ module may prevent location of the new module.
3047db96d56Sopenharmony_ci            mod_name, mod_spec, code = _get_main_module_details()
3057db96d56Sopenharmony_ci            with _TempModule(run_name) as temp_module, \
3067db96d56Sopenharmony_ci                 _ModifiedArgv0(path_name):
3077db96d56Sopenharmony_ci                mod_globals = temp_module.module.__dict__
3087db96d56Sopenharmony_ci                return _run_code(code, mod_globals, init_globals,
3097db96d56Sopenharmony_ci                                    run_name, mod_spec, pkg_name).copy()
3107db96d56Sopenharmony_ci        finally:
3117db96d56Sopenharmony_ci            try:
3127db96d56Sopenharmony_ci                sys.path.remove(path_name)
3137db96d56Sopenharmony_ci            except ValueError:
3147db96d56Sopenharmony_ci                pass
3157db96d56Sopenharmony_ci
3167db96d56Sopenharmony_ci
3177db96d56Sopenharmony_ciif __name__ == "__main__":
3187db96d56Sopenharmony_ci    # Run the module specified as the next command line argument
3197db96d56Sopenharmony_ci    if len(sys.argv) < 2:
3207db96d56Sopenharmony_ci        print("No module specified for execution", file=sys.stderr)
3217db96d56Sopenharmony_ci    else:
3227db96d56Sopenharmony_ci        del sys.argv[0] # Make the requested module sys.argv[0]
3237db96d56Sopenharmony_ci        _run_module_as_main(sys.argv[0])
324