mirror of https://github.com/python/cpython
bpo-45020: Add -X frozen_modules=[on|off] to explicitly control use of frozen modules. (gh-28320)
Currently we freeze several modules into the runtime. For each of these modules it is essential to bootstrapping the runtime that they be frozen. Any other stdlib module that we later freeze into the runtime is not essential. We can just as well import from the .py file. This PR lets users explicitly choose which should be used, with the new "-X frozen_modules=[on|off]" CLI flag. The default is "off" for now. https://bugs.python.org/issue45020
This commit is contained in:
parent
1aaa859497
commit
a65c86889e
|
@ -480,6 +480,12 @@ Miscellaneous options
|
||||||
objects and pyc files are desired as well as supressing the extra visual
|
objects and pyc files are desired as well as supressing the extra visual
|
||||||
location indicators when the interpreter displays tracebacks. See also
|
location indicators when the interpreter displays tracebacks. See also
|
||||||
:envvar:`PYTHONNODEBUGRANGES`.
|
:envvar:`PYTHONNODEBUGRANGES`.
|
||||||
|
* ``-X frozen_modules`` determines whether or not frozen modules are
|
||||||
|
ignored by the import machinery. A value of "on" means they get
|
||||||
|
imported and "off" means they are ignored. The default is "on"
|
||||||
|
for non-debug builds (the normal case) and "off" for debug builds.
|
||||||
|
Note that the "importlib_bootstrap" and "importlib_bootstrap_external"
|
||||||
|
frozen modules are always used, even if this flag is set to "off".
|
||||||
|
|
||||||
It also allows passing arbitrary values and retrieving them through the
|
It also allows passing arbitrary values and retrieving them through the
|
||||||
:data:`sys._xoptions` dictionary.
|
:data:`sys._xoptions` dictionary.
|
||||||
|
@ -518,6 +524,9 @@ Miscellaneous options
|
||||||
.. versionadded:: 3.11
|
.. versionadded:: 3.11
|
||||||
The ``-X no_debug_ranges`` option.
|
The ``-X no_debug_ranges`` option.
|
||||||
|
|
||||||
|
.. versionadded:: 3.11
|
||||||
|
The ``-X frozen_modules`` option.
|
||||||
|
|
||||||
|
|
||||||
Options you shouldn't use
|
Options you shouldn't use
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
|
@ -172,6 +172,7 @@ typedef struct PyConfig {
|
||||||
int legacy_windows_stdio;
|
int legacy_windows_stdio;
|
||||||
#endif
|
#endif
|
||||||
wchar_t *check_hash_pycs_mode;
|
wchar_t *check_hash_pycs_mode;
|
||||||
|
int use_frozen_modules;
|
||||||
|
|
||||||
/* --- Path configuration inputs ------------ */
|
/* --- Path configuration inputs ------------ */
|
||||||
int pathconfig_warnings;
|
int pathconfig_warnings;
|
||||||
|
|
|
@ -155,6 +155,7 @@ extern PyStatus _PyConfig_Copy(
|
||||||
extern PyStatus _PyConfig_InitPathConfig(
|
extern PyStatus _PyConfig_InitPathConfig(
|
||||||
PyConfig *config,
|
PyConfig *config,
|
||||||
int compute_path_config);
|
int compute_path_config);
|
||||||
|
extern PyStatus _PyConfig_InitImportConfig(PyConfig *config);
|
||||||
extern PyStatus _PyConfig_Read(PyConfig *config, int compute_path_config);
|
extern PyStatus _PyConfig_Read(PyConfig *config, int compute_path_config);
|
||||||
extern PyStatus _PyConfig_Write(const PyConfig *config,
|
extern PyStatus _PyConfig_Write(const PyConfig *config,
|
||||||
struct pyruntimestate *runtime);
|
struct pyruntimestate *runtime);
|
||||||
|
|
|
@ -246,6 +246,9 @@ struct _is {
|
||||||
PyObject *builtins;
|
PyObject *builtins;
|
||||||
// importlib module
|
// importlib module
|
||||||
PyObject *importlib;
|
PyObject *importlib;
|
||||||
|
// override for config->use_frozen_modules (for tests)
|
||||||
|
// (-1: "off", 1: "on", 0: no override)
|
||||||
|
int override_frozen_modules;
|
||||||
|
|
||||||
/* Used in Modules/_threadmodule.c. */
|
/* Used in Modules/_threadmodule.c. */
|
||||||
long num_threads;
|
long num_threads;
|
||||||
|
|
|
@ -72,14 +72,16 @@ class PythonValuesTestCase(unittest.TestCase):
|
||||||
self.assertGreater(abs(entry.size), 10)
|
self.assertGreater(abs(entry.size), 10)
|
||||||
self.assertTrue([entry.code[i] for i in range(abs(entry.size))])
|
self.assertTrue([entry.code[i] for i in range(abs(entry.size))])
|
||||||
# Check the module's package-ness.
|
# Check the module's package-ness.
|
||||||
spec = importlib.util.find_spec(modname)
|
with import_helper.frozen_modules():
|
||||||
|
spec = importlib.util.find_spec(modname)
|
||||||
if entry.size < 0:
|
if entry.size < 0:
|
||||||
# It's a package.
|
# It's a package.
|
||||||
self.assertIsNotNone(spec.submodule_search_locations)
|
self.assertIsNotNone(spec.submodule_search_locations)
|
||||||
else:
|
else:
|
||||||
self.assertIsNone(spec.submodule_search_locations)
|
self.assertIsNone(spec.submodule_search_locations)
|
||||||
|
|
||||||
expected = imp._frozen_module_names()
|
with import_helper.frozen_modules():
|
||||||
|
expected = imp._frozen_module_names()
|
||||||
self.maxDiff = None
|
self.maxDiff = None
|
||||||
self.assertEqual(modules, expected, "PyImport_FrozenModules example "
|
self.assertEqual(modules, expected, "PyImport_FrozenModules example "
|
||||||
"in Doc/library/ctypes.rst may be out of date")
|
"in Doc/library/ctypes.rst may be out of date")
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import contextlib
|
import contextlib
|
||||||
|
import _imp
|
||||||
import importlib
|
import importlib
|
||||||
import importlib.util
|
import importlib.util
|
||||||
import os
|
import os
|
||||||
|
@ -109,7 +110,24 @@ def _save_and_block_module(name, orig_modules):
|
||||||
return saved
|
return saved
|
||||||
|
|
||||||
|
|
||||||
def import_fresh_module(name, fresh=(), blocked=(), deprecated=False):
|
@contextlib.contextmanager
|
||||||
|
def frozen_modules(enabled=True):
|
||||||
|
"""Force frozen modules to be used (or not).
|
||||||
|
|
||||||
|
This only applies to modules that haven't been imported yet.
|
||||||
|
Also, some essential modules will always be imported frozen.
|
||||||
|
"""
|
||||||
|
_imp._override_frozen_modules_for_tests(1 if enabled else -1)
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
finally:
|
||||||
|
_imp._override_frozen_modules_for_tests(0)
|
||||||
|
|
||||||
|
|
||||||
|
def import_fresh_module(name, fresh=(), blocked=(), *,
|
||||||
|
deprecated=False,
|
||||||
|
usefrozen=False,
|
||||||
|
):
|
||||||
"""Import and return a module, deliberately bypassing sys.modules.
|
"""Import and return a module, deliberately bypassing sys.modules.
|
||||||
|
|
||||||
This function imports and returns a fresh copy of the named Python module
|
This function imports and returns a fresh copy of the named Python module
|
||||||
|
@ -133,6 +151,9 @@ def import_fresh_module(name, fresh=(), blocked=(), deprecated=False):
|
||||||
|
|
||||||
This function will raise ImportError if the named module cannot be
|
This function will raise ImportError if the named module cannot be
|
||||||
imported.
|
imported.
|
||||||
|
|
||||||
|
If "usefrozen" is False (the default) then the frozen importer is
|
||||||
|
disabled (except for essential modules like importlib._bootstrap).
|
||||||
"""
|
"""
|
||||||
# NOTE: test_heapq, test_json and test_warnings include extra sanity checks
|
# NOTE: test_heapq, test_json and test_warnings include extra sanity checks
|
||||||
# to make sure that this utility function is working as expected
|
# to make sure that this utility function is working as expected
|
||||||
|
@ -148,7 +169,8 @@ def import_fresh_module(name, fresh=(), blocked=(), deprecated=False):
|
||||||
for blocked_name in blocked:
|
for blocked_name in blocked:
|
||||||
if not _save_and_block_module(blocked_name, orig_modules):
|
if not _save_and_block_module(blocked_name, orig_modules):
|
||||||
names_to_remove.append(blocked_name)
|
names_to_remove.append(blocked_name)
|
||||||
fresh_module = importlib.import_module(name)
|
with frozen_modules(usefrozen):
|
||||||
|
fresh_module = importlib.import_module(name)
|
||||||
except ImportError:
|
except ImportError:
|
||||||
fresh_module = None
|
fresh_module = None
|
||||||
finally:
|
finally:
|
||||||
|
@ -169,9 +191,12 @@ class CleanImport(object):
|
||||||
|
|
||||||
with CleanImport("foo"):
|
with CleanImport("foo"):
|
||||||
importlib.import_module("foo") # new reference
|
importlib.import_module("foo") # new reference
|
||||||
|
|
||||||
|
If "usefrozen" is False (the default) then the frozen importer is
|
||||||
|
disabled (except for essential modules like importlib._bootstrap).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *module_names):
|
def __init__(self, *module_names, usefrozen=False):
|
||||||
self.original_modules = sys.modules.copy()
|
self.original_modules = sys.modules.copy()
|
||||||
for module_name in module_names:
|
for module_name in module_names:
|
||||||
if module_name in sys.modules:
|
if module_name in sys.modules:
|
||||||
|
@ -183,12 +208,15 @@ class CleanImport(object):
|
||||||
if module.__name__ != module_name:
|
if module.__name__ != module_name:
|
||||||
del sys.modules[module.__name__]
|
del sys.modules[module.__name__]
|
||||||
del sys.modules[module_name]
|
del sys.modules[module_name]
|
||||||
|
self._frozen_modules = frozen_modules(usefrozen)
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
|
self._frozen_modules.__enter__()
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __exit__(self, *ignore_exc):
|
def __exit__(self, *ignore_exc):
|
||||||
sys.modules.update(self.original_modules)
|
sys.modules.update(self.original_modules)
|
||||||
|
self._frozen_modules.__exit__(*ignore_exc)
|
||||||
|
|
||||||
|
|
||||||
class DirsOnSysPath(object):
|
class DirsOnSysPath(object):
|
||||||
|
|
|
@ -599,6 +599,10 @@ class EnvironmentVarGuard(collections.abc.MutableMapping):
|
||||||
def unset(self, envvar):
|
def unset(self, envvar):
|
||||||
del self[envvar]
|
del self[envvar]
|
||||||
|
|
||||||
|
def copy(self):
|
||||||
|
# We do what os.environ.copy() does.
|
||||||
|
return dict(self)
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ import re
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
import sysconfig
|
||||||
import tempfile
|
import tempfile
|
||||||
import textwrap
|
import textwrap
|
||||||
|
|
||||||
|
@ -426,6 +427,7 @@ class InitConfigTests(EmbeddingTestsMixin, unittest.TestCase):
|
||||||
'pathconfig_warnings': 1,
|
'pathconfig_warnings': 1,
|
||||||
'_init_main': 1,
|
'_init_main': 1,
|
||||||
'_isolated_interpreter': 0,
|
'_isolated_interpreter': 0,
|
||||||
|
'use_frozen_modules': False,
|
||||||
}
|
}
|
||||||
if MS_WINDOWS:
|
if MS_WINDOWS:
|
||||||
CONFIG_COMPAT.update({
|
CONFIG_COMPAT.update({
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import unittest
|
import unittest
|
||||||
from test.support import captured_stdout
|
from test.support import captured_stdout, import_helper
|
||||||
|
|
||||||
|
|
||||||
class TestFrozen(unittest.TestCase):
|
class TestFrozen(unittest.TestCase):
|
||||||
|
@ -20,8 +20,9 @@ class TestFrozen(unittest.TestCase):
|
||||||
name = '__hello__'
|
name = '__hello__'
|
||||||
if name in sys.modules:
|
if name in sys.modules:
|
||||||
del sys.modules[name]
|
del sys.modules[name]
|
||||||
with captured_stdout() as out:
|
with import_helper.frozen_modules():
|
||||||
import __hello__
|
with captured_stdout() as out:
|
||||||
|
import __hello__
|
||||||
self.assertEqual(out.getvalue(), 'Hello world!\n')
|
self.assertEqual(out.getvalue(), 'Hello world!\n')
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,8 @@ machinery = util.import_importlib('importlib.machinery')
|
||||||
import unittest
|
import unittest
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
|
from test.support import import_helper
|
||||||
|
|
||||||
|
|
||||||
class FindSpecTests(abc.FinderTests):
|
class FindSpecTests(abc.FinderTests):
|
||||||
|
|
||||||
|
@ -13,7 +15,8 @@ class FindSpecTests(abc.FinderTests):
|
||||||
|
|
||||||
def find(self, name, path=None):
|
def find(self, name, path=None):
|
||||||
finder = self.machinery.FrozenImporter
|
finder = self.machinery.FrozenImporter
|
||||||
return finder.find_spec(name, path)
|
with import_helper.frozen_modules():
|
||||||
|
return finder.find_spec(name, path)
|
||||||
|
|
||||||
def test_module(self):
|
def test_module(self):
|
||||||
name = '__hello__'
|
name = '__hello__'
|
||||||
|
@ -52,7 +55,8 @@ class FinderTests(abc.FinderTests):
|
||||||
finder = self.machinery.FrozenImporter
|
finder = self.machinery.FrozenImporter
|
||||||
with warnings.catch_warnings():
|
with warnings.catch_warnings():
|
||||||
warnings.simplefilter("ignore", DeprecationWarning)
|
warnings.simplefilter("ignore", DeprecationWarning)
|
||||||
return finder.find_module(name, path)
|
with import_helper.frozen_modules():
|
||||||
|
return finder.find_module(name, path)
|
||||||
|
|
||||||
def test_module(self):
|
def test_module(self):
|
||||||
name = '__hello__'
|
name = '__hello__'
|
||||||
|
|
|
@ -3,27 +3,54 @@ from .. import util
|
||||||
|
|
||||||
machinery = util.import_importlib('importlib.machinery')
|
machinery = util.import_importlib('importlib.machinery')
|
||||||
|
|
||||||
from test.support import captured_stdout
|
from test.support import captured_stdout, import_helper
|
||||||
|
import contextlib
|
||||||
import types
|
import types
|
||||||
import unittest
|
import unittest
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def deprecated():
|
||||||
|
with warnings.catch_warnings():
|
||||||
|
warnings.simplefilter('ignore', DeprecationWarning)
|
||||||
|
yield
|
||||||
|
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def fresh(name, *, oldapi=False):
|
||||||
|
with util.uncache(name):
|
||||||
|
with import_helper.frozen_modules():
|
||||||
|
with captured_stdout() as stdout:
|
||||||
|
if oldapi:
|
||||||
|
with deprecated():
|
||||||
|
yield stdout
|
||||||
|
else:
|
||||||
|
yield stdout
|
||||||
|
|
||||||
|
|
||||||
class ExecModuleTests(abc.LoaderTests):
|
class ExecModuleTests(abc.LoaderTests):
|
||||||
|
|
||||||
def exec_module(self, name):
|
def exec_module(self, name):
|
||||||
with util.uncache(name), captured_stdout() as stdout:
|
with import_helper.frozen_modules():
|
||||||
spec = self.machinery.ModuleSpec(
|
is_package = self.machinery.FrozenImporter.is_package(name)
|
||||||
name, self.machinery.FrozenImporter, origin='frozen',
|
spec = self.machinery.ModuleSpec(
|
||||||
is_package=self.machinery.FrozenImporter.is_package(name))
|
name,
|
||||||
module = types.ModuleType(name)
|
self.machinery.FrozenImporter,
|
||||||
module.__spec__ = spec
|
origin='frozen',
|
||||||
assert not hasattr(module, 'initialized')
|
is_package=is_package,
|
||||||
|
)
|
||||||
|
module = types.ModuleType(name)
|
||||||
|
module.__spec__ = spec
|
||||||
|
assert not hasattr(module, 'initialized')
|
||||||
|
|
||||||
|
with fresh(name) as stdout:
|
||||||
self.machinery.FrozenImporter.exec_module(module)
|
self.machinery.FrozenImporter.exec_module(module)
|
||||||
self.assertTrue(module.initialized)
|
|
||||||
self.assertTrue(hasattr(module, '__spec__'))
|
self.assertTrue(module.initialized)
|
||||||
self.assertEqual(module.__spec__.origin, 'frozen')
|
self.assertTrue(hasattr(module, '__spec__'))
|
||||||
return module, stdout.getvalue()
|
self.assertEqual(module.__spec__.origin, 'frozen')
|
||||||
|
return module, stdout.getvalue()
|
||||||
|
|
||||||
def test_module(self):
|
def test_module(self):
|
||||||
name = '__hello__'
|
name = '__hello__'
|
||||||
|
@ -50,20 +77,19 @@ class ExecModuleTests(abc.LoaderTests):
|
||||||
name = '__phello__.spam'
|
name = '__phello__.spam'
|
||||||
with util.uncache('__phello__'):
|
with util.uncache('__phello__'):
|
||||||
module, output = self.exec_module(name)
|
module, output = self.exec_module(name)
|
||||||
check = {'__name__': name}
|
check = {'__name__': name}
|
||||||
for attr, value in check.items():
|
for attr, value in check.items():
|
||||||
attr_value = getattr(module, attr)
|
attr_value = getattr(module, attr)
|
||||||
self.assertEqual(attr_value, value,
|
self.assertEqual(attr_value, value,
|
||||||
'for {name}.{attr}, {given} != {expected!r}'.format(
|
'for {name}.{attr}, {given} != {expected!r}'.format(
|
||||||
name=name, attr=attr, given=attr_value,
|
name=name, attr=attr, given=attr_value,
|
||||||
expected=value))
|
expected=value))
|
||||||
self.assertEqual(output, 'Hello world!\n')
|
self.assertEqual(output, 'Hello world!\n')
|
||||||
|
|
||||||
def test_module_repr(self):
|
def test_module_repr(self):
|
||||||
name = '__hello__'
|
name = '__hello__'
|
||||||
module, output = self.exec_module(name)
|
module, output = self.exec_module(name)
|
||||||
with warnings.catch_warnings():
|
with deprecated():
|
||||||
warnings.simplefilter('ignore', DeprecationWarning)
|
|
||||||
repr_str = self.machinery.FrozenImporter.module_repr(module)
|
repr_str = self.machinery.FrozenImporter.module_repr(module)
|
||||||
self.assertEqual(repr_str,
|
self.assertEqual(repr_str,
|
||||||
"<module '__hello__' (frozen)>")
|
"<module '__hello__' (frozen)>")
|
||||||
|
@ -78,7 +104,8 @@ class ExecModuleTests(abc.LoaderTests):
|
||||||
test_state_after_failure = None
|
test_state_after_failure = None
|
||||||
|
|
||||||
def test_unloadable(self):
|
def test_unloadable(self):
|
||||||
assert self.machinery.FrozenImporter.find_spec('_not_real') is None
|
with import_helper.frozen_modules():
|
||||||
|
assert self.machinery.FrozenImporter.find_spec('_not_real') is None
|
||||||
with self.assertRaises(ImportError) as cm:
|
with self.assertRaises(ImportError) as cm:
|
||||||
self.exec_module('_not_real')
|
self.exec_module('_not_real')
|
||||||
self.assertEqual(cm.exception.name, '_not_real')
|
self.assertEqual(cm.exception.name, '_not_real')
|
||||||
|
@ -91,84 +118,76 @@ class ExecModuleTests(abc.LoaderTests):
|
||||||
|
|
||||||
class LoaderTests(abc.LoaderTests):
|
class LoaderTests(abc.LoaderTests):
|
||||||
|
|
||||||
|
def load_module(self, name):
|
||||||
|
with fresh(name, oldapi=True) as stdout:
|
||||||
|
module = self.machinery.FrozenImporter.load_module(name)
|
||||||
|
return module, stdout
|
||||||
|
|
||||||
def test_module(self):
|
def test_module(self):
|
||||||
with util.uncache('__hello__'), captured_stdout() as stdout:
|
module, stdout = self.load_module('__hello__')
|
||||||
with warnings.catch_warnings():
|
check = {'__name__': '__hello__',
|
||||||
warnings.simplefilter('ignore', DeprecationWarning)
|
'__package__': '',
|
||||||
module = self.machinery.FrozenImporter.load_module('__hello__')
|
'__loader__': self.machinery.FrozenImporter,
|
||||||
check = {'__name__': '__hello__',
|
}
|
||||||
'__package__': '',
|
for attr, value in check.items():
|
||||||
'__loader__': self.machinery.FrozenImporter,
|
self.assertEqual(getattr(module, attr), value)
|
||||||
}
|
self.assertEqual(stdout.getvalue(), 'Hello world!\n')
|
||||||
for attr, value in check.items():
|
self.assertFalse(hasattr(module, '__file__'))
|
||||||
self.assertEqual(getattr(module, attr), value)
|
|
||||||
self.assertEqual(stdout.getvalue(), 'Hello world!\n')
|
|
||||||
self.assertFalse(hasattr(module, '__file__'))
|
|
||||||
|
|
||||||
def test_package(self):
|
def test_package(self):
|
||||||
with util.uncache('__phello__'), captured_stdout() as stdout:
|
module, stdout = self.load_module('__phello__')
|
||||||
with warnings.catch_warnings():
|
check = {'__name__': '__phello__',
|
||||||
warnings.simplefilter('ignore', DeprecationWarning)
|
'__package__': '__phello__',
|
||||||
module = self.machinery.FrozenImporter.load_module('__phello__')
|
'__path__': [],
|
||||||
check = {'__name__': '__phello__',
|
'__loader__': self.machinery.FrozenImporter,
|
||||||
'__package__': '__phello__',
|
}
|
||||||
'__path__': [],
|
for attr, value in check.items():
|
||||||
'__loader__': self.machinery.FrozenImporter,
|
attr_value = getattr(module, attr)
|
||||||
}
|
self.assertEqual(attr_value, value,
|
||||||
for attr, value in check.items():
|
"for __phello__.%s, %r != %r" %
|
||||||
attr_value = getattr(module, attr)
|
(attr, attr_value, value))
|
||||||
self.assertEqual(attr_value, value,
|
self.assertEqual(stdout.getvalue(), 'Hello world!\n')
|
||||||
"for __phello__.%s, %r != %r" %
|
self.assertFalse(hasattr(module, '__file__'))
|
||||||
(attr, attr_value, value))
|
|
||||||
self.assertEqual(stdout.getvalue(), 'Hello world!\n')
|
|
||||||
self.assertFalse(hasattr(module, '__file__'))
|
|
||||||
|
|
||||||
def test_lacking_parent(self):
|
def test_lacking_parent(self):
|
||||||
with util.uncache('__phello__', '__phello__.spam'), \
|
with util.uncache('__phello__'):
|
||||||
captured_stdout() as stdout:
|
module, stdout = self.load_module('__phello__.spam')
|
||||||
with warnings.catch_warnings():
|
check = {'__name__': '__phello__.spam',
|
||||||
warnings.simplefilter('ignore', DeprecationWarning)
|
'__package__': '__phello__',
|
||||||
module = self.machinery.FrozenImporter.load_module('__phello__.spam')
|
'__loader__': self.machinery.FrozenImporter,
|
||||||
check = {'__name__': '__phello__.spam',
|
}
|
||||||
'__package__': '__phello__',
|
for attr, value in check.items():
|
||||||
'__loader__': self.machinery.FrozenImporter,
|
attr_value = getattr(module, attr)
|
||||||
}
|
self.assertEqual(attr_value, value,
|
||||||
for attr, value in check.items():
|
"for __phello__.spam.%s, %r != %r" %
|
||||||
attr_value = getattr(module, attr)
|
(attr, attr_value, value))
|
||||||
self.assertEqual(attr_value, value,
|
self.assertEqual(stdout.getvalue(), 'Hello world!\n')
|
||||||
"for __phello__.spam.%s, %r != %r" %
|
self.assertFalse(hasattr(module, '__file__'))
|
||||||
(attr, attr_value, value))
|
|
||||||
self.assertEqual(stdout.getvalue(), 'Hello world!\n')
|
|
||||||
self.assertFalse(hasattr(module, '__file__'))
|
|
||||||
|
|
||||||
def test_module_reuse(self):
|
def test_module_reuse(self):
|
||||||
with util.uncache('__hello__'), captured_stdout() as stdout:
|
with fresh('__hello__', oldapi=True) as stdout:
|
||||||
with warnings.catch_warnings():
|
module1 = self.machinery.FrozenImporter.load_module('__hello__')
|
||||||
warnings.simplefilter('ignore', DeprecationWarning)
|
module2 = self.machinery.FrozenImporter.load_module('__hello__')
|
||||||
module1 = self.machinery.FrozenImporter.load_module('__hello__')
|
self.assertIs(module1, module2)
|
||||||
module2 = self.machinery.FrozenImporter.load_module('__hello__')
|
self.assertEqual(stdout.getvalue(),
|
||||||
self.assertIs(module1, module2)
|
'Hello world!\nHello world!\n')
|
||||||
self.assertEqual(stdout.getvalue(),
|
|
||||||
'Hello world!\nHello world!\n')
|
|
||||||
|
|
||||||
def test_module_repr(self):
|
def test_module_repr(self):
|
||||||
with util.uncache('__hello__'), captured_stdout():
|
with fresh('__hello__', oldapi=True) as stdout:
|
||||||
with warnings.catch_warnings():
|
module = self.machinery.FrozenImporter.load_module('__hello__')
|
||||||
warnings.simplefilter('ignore', DeprecationWarning)
|
repr_str = self.machinery.FrozenImporter.module_repr(module)
|
||||||
module = self.machinery.FrozenImporter.load_module('__hello__')
|
self.assertEqual(repr_str,
|
||||||
repr_str = self.machinery.FrozenImporter.module_repr(module)
|
"<module '__hello__' (frozen)>")
|
||||||
self.assertEqual(repr_str,
|
|
||||||
"<module '__hello__' (frozen)>")
|
|
||||||
|
|
||||||
# No way to trigger an error in a frozen module.
|
# No way to trigger an error in a frozen module.
|
||||||
test_state_after_failure = None
|
test_state_after_failure = None
|
||||||
|
|
||||||
def test_unloadable(self):
|
def test_unloadable(self):
|
||||||
with warnings.catch_warnings():
|
with import_helper.frozen_modules():
|
||||||
warnings.simplefilter("ignore", DeprecationWarning)
|
with deprecated():
|
||||||
assert self.machinery.FrozenImporter.find_module('_not_real') is None
|
assert self.machinery.FrozenImporter.find_module('_not_real') is None
|
||||||
with self.assertRaises(ImportError) as cm:
|
with self.assertRaises(ImportError) as cm:
|
||||||
self.machinery.FrozenImporter.load_module('_not_real')
|
self.load_module('_not_real')
|
||||||
self.assertEqual(cm.exception.name, '_not_real')
|
self.assertEqual(cm.exception.name, '_not_real')
|
||||||
|
|
||||||
|
|
||||||
|
@ -185,15 +204,17 @@ class InspectLoaderTests:
|
||||||
# Make sure that the code object is good.
|
# Make sure that the code object is good.
|
||||||
name = '__hello__'
|
name = '__hello__'
|
||||||
with captured_stdout() as stdout:
|
with captured_stdout() as stdout:
|
||||||
code = self.machinery.FrozenImporter.get_code(name)
|
with import_helper.frozen_modules():
|
||||||
mod = types.ModuleType(name)
|
code = self.machinery.FrozenImporter.get_code(name)
|
||||||
exec(code, mod.__dict__)
|
mod = types.ModuleType(name)
|
||||||
self.assertTrue(hasattr(mod, 'initialized'))
|
exec(code, mod.__dict__)
|
||||||
self.assertEqual(stdout.getvalue(), 'Hello world!\n')
|
self.assertTrue(hasattr(mod, 'initialized'))
|
||||||
|
self.assertEqual(stdout.getvalue(), 'Hello world!\n')
|
||||||
|
|
||||||
def test_get_source(self):
|
def test_get_source(self):
|
||||||
# Should always return None.
|
# Should always return None.
|
||||||
result = self.machinery.FrozenImporter.get_source('__hello__')
|
with import_helper.frozen_modules():
|
||||||
|
result = self.machinery.FrozenImporter.get_source('__hello__')
|
||||||
self.assertIsNone(result)
|
self.assertIsNone(result)
|
||||||
|
|
||||||
def test_is_package(self):
|
def test_is_package(self):
|
||||||
|
@ -201,7 +222,8 @@ class InspectLoaderTests:
|
||||||
test_for = (('__hello__', False), ('__phello__', True),
|
test_for = (('__hello__', False), ('__phello__', True),
|
||||||
('__phello__.spam', False))
|
('__phello__.spam', False))
|
||||||
for name, is_package in test_for:
|
for name, is_package in test_for:
|
||||||
result = self.machinery.FrozenImporter.is_package(name)
|
with import_helper.frozen_modules():
|
||||||
|
result = self.machinery.FrozenImporter.is_package(name)
|
||||||
self.assertEqual(bool(result), is_package)
|
self.assertEqual(bool(result), is_package)
|
||||||
|
|
||||||
def test_failure(self):
|
def test_failure(self):
|
||||||
|
@ -209,7 +231,8 @@ class InspectLoaderTests:
|
||||||
for meth_name in ('get_code', 'get_source', 'is_package'):
|
for meth_name in ('get_code', 'get_source', 'is_package'):
|
||||||
method = getattr(self.machinery.FrozenImporter, meth_name)
|
method = getattr(self.machinery.FrozenImporter, meth_name)
|
||||||
with self.assertRaises(ImportError) as cm:
|
with self.assertRaises(ImportError) as cm:
|
||||||
method('importlib')
|
with import_helper.frozen_modules():
|
||||||
|
method('importlib')
|
||||||
self.assertEqual(cm.exception.name, 'importlib')
|
self.assertEqual(cm.exception.name, 'importlib')
|
||||||
|
|
||||||
(Frozen_ILTests,
|
(Frozen_ILTests,
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
Add a new command line option, "-X frozen_modules=[on|off]" to opt out
|
||||||
|
of (or into) using optional frozen modules. This defaults to "on" (or
|
||||||
|
"off" if it's a debug build).
|
|
@ -315,6 +315,37 @@ _imp__frozen_module_names(PyObject *module, PyObject *Py_UNUSED(ignored))
|
||||||
return _imp__frozen_module_names_impl(module);
|
return _imp__frozen_module_names_impl(module);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyDoc_STRVAR(_imp__override_frozen_modules_for_tests__doc__,
|
||||||
|
"_override_frozen_modules_for_tests($module, override, /)\n"
|
||||||
|
"--\n"
|
||||||
|
"\n"
|
||||||
|
"(internal-only) Override PyConfig.use_frozen_modules.\n"
|
||||||
|
"\n"
|
||||||
|
"(-1: \"off\", 1: \"on\", 0: no override)\n"
|
||||||
|
"See frozen_modules() in Lib/test/support/import_helper.py.");
|
||||||
|
|
||||||
|
#define _IMP__OVERRIDE_FROZEN_MODULES_FOR_TESTS_METHODDEF \
|
||||||
|
{"_override_frozen_modules_for_tests", (PyCFunction)_imp__override_frozen_modules_for_tests, METH_O, _imp__override_frozen_modules_for_tests__doc__},
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
_imp__override_frozen_modules_for_tests_impl(PyObject *module, int override);
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
_imp__override_frozen_modules_for_tests(PyObject *module, PyObject *arg)
|
||||||
|
{
|
||||||
|
PyObject *return_value = NULL;
|
||||||
|
int override;
|
||||||
|
|
||||||
|
override = _PyLong_AsInt(arg);
|
||||||
|
if (override == -1 && PyErr_Occurred()) {
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
return_value = _imp__override_frozen_modules_for_tests_impl(module, override);
|
||||||
|
|
||||||
|
exit:
|
||||||
|
return return_value;
|
||||||
|
}
|
||||||
|
|
||||||
#if defined(HAVE_DYNAMIC_LOADING)
|
#if defined(HAVE_DYNAMIC_LOADING)
|
||||||
|
|
||||||
PyDoc_STRVAR(_imp_create_dynamic__doc__,
|
PyDoc_STRVAR(_imp_create_dynamic__doc__,
|
||||||
|
@ -467,4 +498,4 @@ exit:
|
||||||
#ifndef _IMP_EXEC_DYNAMIC_METHODDEF
|
#ifndef _IMP_EXEC_DYNAMIC_METHODDEF
|
||||||
#define _IMP_EXEC_DYNAMIC_METHODDEF
|
#define _IMP_EXEC_DYNAMIC_METHODDEF
|
||||||
#endif /* !defined(_IMP_EXEC_DYNAMIC_METHODDEF) */
|
#endif /* !defined(_IMP_EXEC_DYNAMIC_METHODDEF) */
|
||||||
/*[clinic end generated code: output=0ab3fa7c5808bba4 input=a9049054013a1b77]*/
|
/*[clinic end generated code: output=96038c277119d6e3 input=a9049054013a1b77]*/
|
||||||
|
|
|
@ -1050,17 +1050,58 @@ _imp_create_builtin(PyObject *module, PyObject *spec)
|
||||||
|
|
||||||
/* Frozen modules */
|
/* Frozen modules */
|
||||||
|
|
||||||
|
static bool
|
||||||
|
is_essential_frozen_module(const char *name)
|
||||||
|
{
|
||||||
|
/* These modules are necessary to bootstrap the import system. */
|
||||||
|
if (strcmp(name, "_frozen_importlib") == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (strcmp(name, "_frozen_importlib_external") == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (strcmp(name, "zipimport") == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
/* This doesn't otherwise have anywhere to find the module.
|
||||||
|
See frozenmain.c. */
|
||||||
|
if (strcmp(name, "__main__") == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
use_frozen(void)
|
||||||
|
{
|
||||||
|
PyInterpreterState *interp = _PyInterpreterState_GET();
|
||||||
|
int override = interp->override_frozen_modules;
|
||||||
|
if (override > 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (override < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return interp->config.use_frozen_modules;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static PyObject *
|
static PyObject *
|
||||||
list_frozen_module_names(bool force)
|
list_frozen_module_names()
|
||||||
{
|
{
|
||||||
PyObject *names = PyList_New(0);
|
PyObject *names = PyList_New(0);
|
||||||
if (names == NULL) {
|
if (names == NULL) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
bool enabled = use_frozen();
|
||||||
for (const struct _frozen *p = PyImport_FrozenModules; ; p++) {
|
for (const struct _frozen *p = PyImport_FrozenModules; ; p++) {
|
||||||
if (p->name == NULL) {
|
if (p->name == NULL) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (!enabled && !is_essential_frozen_module(p->name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
PyObject *name = PyUnicode_FromString(p->name);
|
PyObject *name = PyUnicode_FromString(p->name);
|
||||||
if (name == NULL) {
|
if (name == NULL) {
|
||||||
Py_DECREF(names);
|
Py_DECREF(names);
|
||||||
|
@ -1077,18 +1118,27 @@ list_frozen_module_names(bool force)
|
||||||
}
|
}
|
||||||
|
|
||||||
static const struct _frozen *
|
static const struct _frozen *
|
||||||
find_frozen(PyObject *name)
|
find_frozen(PyObject *modname)
|
||||||
{
|
{
|
||||||
const struct _frozen *p;
|
if (modname == NULL) {
|
||||||
|
|
||||||
if (name == NULL)
|
|
||||||
return NULL;
|
return NULL;
|
||||||
|
}
|
||||||
|
const char *name = PyUnicode_AsUTF8(modname);
|
||||||
|
if (name == NULL) {
|
||||||
|
PyErr_Clear();
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (!use_frozen() && !is_essential_frozen_module(name)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
const struct _frozen *p;
|
||||||
for (p = PyImport_FrozenModules; ; p++) {
|
for (p = PyImport_FrozenModules; ; p++) {
|
||||||
if (p->name == NULL)
|
if (p->name == NULL) {
|
||||||
return NULL;
|
return NULL;
|
||||||
if (_PyUnicode_EqualToASCIIString(name, p->name))
|
}
|
||||||
|
if (strcmp(name, p->name) == 0) {
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
@ -1991,7 +2041,28 @@ static PyObject *
|
||||||
_imp__frozen_module_names_impl(PyObject *module)
|
_imp__frozen_module_names_impl(PyObject *module)
|
||||||
/*[clinic end generated code: output=80609ef6256310a8 input=76237fbfa94460d2]*/
|
/*[clinic end generated code: output=80609ef6256310a8 input=76237fbfa94460d2]*/
|
||||||
{
|
{
|
||||||
return list_frozen_module_names(true);
|
return list_frozen_module_names();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*[clinic input]
|
||||||
|
_imp._override_frozen_modules_for_tests
|
||||||
|
|
||||||
|
override: int
|
||||||
|
/
|
||||||
|
|
||||||
|
(internal-only) Override PyConfig.use_frozen_modules.
|
||||||
|
|
||||||
|
(-1: "off", 1: "on", 0: no override)
|
||||||
|
See frozen_modules() in Lib/test/support/import_helper.py.
|
||||||
|
[clinic start generated code]*/
|
||||||
|
|
||||||
|
static PyObject *
|
||||||
|
_imp__override_frozen_modules_for_tests_impl(PyObject *module, int override)
|
||||||
|
/*[clinic end generated code: output=36d5cb1594160811 input=8f1f95a3ef21aec3]*/
|
||||||
|
{
|
||||||
|
PyInterpreterState *interp = _PyInterpreterState_GET();
|
||||||
|
interp->override_frozen_modules = override;
|
||||||
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Common implementation for _imp.exec_dynamic and _imp.exec_builtin */
|
/* Common implementation for _imp.exec_dynamic and _imp.exec_builtin */
|
||||||
|
@ -2155,6 +2226,7 @@ static PyMethodDef imp_methods[] = {
|
||||||
_IMP_IS_BUILTIN_METHODDEF
|
_IMP_IS_BUILTIN_METHODDEF
|
||||||
_IMP_IS_FROZEN_METHODDEF
|
_IMP_IS_FROZEN_METHODDEF
|
||||||
_IMP__FROZEN_MODULE_NAMES_METHODDEF
|
_IMP__FROZEN_MODULE_NAMES_METHODDEF
|
||||||
|
_IMP__OVERRIDE_FROZEN_MODULES_FOR_TESTS_METHODDEF
|
||||||
_IMP_CREATE_DYNAMIC_METHODDEF
|
_IMP_CREATE_DYNAMIC_METHODDEF
|
||||||
_IMP_EXEC_DYNAMIC_METHODDEF
|
_IMP_EXEC_DYNAMIC_METHODDEF
|
||||||
_IMP_EXEC_BUILTIN_METHODDEF
|
_IMP_EXEC_BUILTIN_METHODDEF
|
||||||
|
|
|
@ -100,6 +100,8 @@ static const char usage_3[] = "\
|
||||||
instruction in code objects. This is useful when smaller code objects and pyc \n\
|
instruction in code objects. This is useful when smaller code objects and pyc \n\
|
||||||
files are desired as well as supressing the extra visual location indicators \n\
|
files are desired as well as supressing the extra visual location indicators \n\
|
||||||
when the interpreter displays tracebacks.\n\
|
when the interpreter displays tracebacks.\n\
|
||||||
|
-X frozen_modules=[on|off]: whether or not frozen modules should be used.\n\
|
||||||
|
The default is \"on\" (or \"off\" if you are running a local build).\n\
|
||||||
\n\
|
\n\
|
||||||
--check-hash-based-pycs always|default|never:\n\
|
--check-hash-based-pycs always|default|never:\n\
|
||||||
control how Python invalidates hash-based .pyc files\n\
|
control how Python invalidates hash-based .pyc files\n\
|
||||||
|
@ -949,6 +951,7 @@ _PyConfig_Copy(PyConfig *config, const PyConfig *config2)
|
||||||
COPY_ATTR(pathconfig_warnings);
|
COPY_ATTR(pathconfig_warnings);
|
||||||
COPY_ATTR(_init_main);
|
COPY_ATTR(_init_main);
|
||||||
COPY_ATTR(_isolated_interpreter);
|
COPY_ATTR(_isolated_interpreter);
|
||||||
|
COPY_ATTR(use_frozen_modules);
|
||||||
COPY_WSTRLIST(orig_argv);
|
COPY_WSTRLIST(orig_argv);
|
||||||
|
|
||||||
#undef COPY_ATTR
|
#undef COPY_ATTR
|
||||||
|
@ -1052,6 +1055,7 @@ _PyConfig_AsDict(const PyConfig *config)
|
||||||
SET_ITEM_INT(_init_main);
|
SET_ITEM_INT(_init_main);
|
||||||
SET_ITEM_INT(_isolated_interpreter);
|
SET_ITEM_INT(_isolated_interpreter);
|
||||||
SET_ITEM_WSTRLIST(orig_argv);
|
SET_ITEM_WSTRLIST(orig_argv);
|
||||||
|
SET_ITEM_INT(use_frozen_modules);
|
||||||
|
|
||||||
return dict;
|
return dict;
|
||||||
|
|
||||||
|
@ -1334,6 +1338,7 @@ _PyConfig_FromDict(PyConfig *config, PyObject *dict)
|
||||||
GET_UINT(_install_importlib);
|
GET_UINT(_install_importlib);
|
||||||
GET_UINT(_init_main);
|
GET_UINT(_init_main);
|
||||||
GET_UINT(_isolated_interpreter);
|
GET_UINT(_isolated_interpreter);
|
||||||
|
GET_UINT(use_frozen_modules);
|
||||||
|
|
||||||
#undef CHECK_VALUE
|
#undef CHECK_VALUE
|
||||||
#undef GET_UINT
|
#undef GET_UINT
|
||||||
|
@ -1590,6 +1595,17 @@ config_get_xoption(const PyConfig *config, wchar_t *name)
|
||||||
return _Py_get_xoption(&config->xoptions, name);
|
return _Py_get_xoption(&config->xoptions, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const wchar_t*
|
||||||
|
config_get_xoption_value(const PyConfig *config, wchar_t *name)
|
||||||
|
{
|
||||||
|
const wchar_t *xoption = config_get_xoption(config, name);
|
||||||
|
if (xoption == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
const wchar_t *sep = wcschr(xoption, L'=');
|
||||||
|
return sep ? sep + 1 : L"";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static PyStatus
|
static PyStatus
|
||||||
config_init_home(PyConfig *config)
|
config_init_home(PyConfig *config)
|
||||||
|
@ -2065,6 +2081,48 @@ config_init_fs_encoding(PyConfig *config, const PyPreConfig *preconfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static PyStatus
|
||||||
|
config_init_import(PyConfig *config, int compute_path_config)
|
||||||
|
{
|
||||||
|
PyStatus status;
|
||||||
|
|
||||||
|
status = _PyConfig_InitPathConfig(config, compute_path_config);
|
||||||
|
if (_PyStatus_EXCEPTION(status)) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -X frozen_modules=[on|off] */
|
||||||
|
const wchar_t *value = config_get_xoption_value(config, L"frozen_modules");
|
||||||
|
if (value == NULL) {
|
||||||
|
// For now we always default to "off".
|
||||||
|
// In the near future we will be factoring in PGO and in-development.
|
||||||
|
config->use_frozen_modules = 0;
|
||||||
|
}
|
||||||
|
else if (wcscmp(value, L"on") == 0) {
|
||||||
|
config->use_frozen_modules = 1;
|
||||||
|
}
|
||||||
|
else if (wcscmp(value, L"off") == 0) {
|
||||||
|
config->use_frozen_modules = 0;
|
||||||
|
}
|
||||||
|
else if (wcslen(value) == 0) {
|
||||||
|
// "-X frozen_modules" and "-X frozen_modules=" both imply "on".
|
||||||
|
config->use_frozen_modules = 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return PyStatus_Error("bad value for option -X frozen_modules "
|
||||||
|
"(expected \"on\" or \"off\")");
|
||||||
|
}
|
||||||
|
|
||||||
|
return _PyStatus_OK();
|
||||||
|
}
|
||||||
|
|
||||||
|
PyStatus
|
||||||
|
_PyConfig_InitImportConfig(PyConfig *config)
|
||||||
|
{
|
||||||
|
return config_init_import(config, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static PyStatus
|
static PyStatus
|
||||||
config_read(PyConfig *config, int compute_path_config)
|
config_read(PyConfig *config, int compute_path_config)
|
||||||
{
|
{
|
||||||
|
@ -2111,7 +2169,7 @@ config_read(PyConfig *config, int compute_path_config)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config->_install_importlib) {
|
if (config->_install_importlib) {
|
||||||
status = _PyConfig_InitPathConfig(config, compute_path_config);
|
status = config_init_import(config, compute_path_config);
|
||||||
if (_PyStatus_EXCEPTION(status)) {
|
if (_PyStatus_EXCEPTION(status)) {
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1078,8 +1078,8 @@ init_interp_main(PyThreadState *tstate)
|
||||||
return _PyStatus_OK();
|
return _PyStatus_OK();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute the path configuration
|
// Initialize the import-related configuration.
|
||||||
status = _PyConfig_InitPathConfig(&interp->config, 1);
|
status = _PyConfig_InitImportConfig(&interp->config);
|
||||||
if (_PyStatus_EXCEPTION(status)) {
|
if (_PyStatus_EXCEPTION(status)) {
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue