From 4f9521d91905f1c7673c9c1b9894490bdf438208 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 2 Jun 2023 13:48:26 -0600 Subject: [PATCH 1/7] Fix the function doc string. --- Lib/importlib/util.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/Lib/importlib/util.py b/Lib/importlib/util.py index b1d9271f8e47ca..e5cc6f2d10d092 100644 --- a/Lib/importlib/util.py +++ b/Lib/importlib/util.py @@ -117,11 +117,19 @@ def find_spec(name, package=None): # dependencies. Thus we use a class. class allowing_all_extensions: - """A context manager that lets users skip the compatibility check. + """A context manager that can temporarily skip the compatibility check. + + NOTE: This function is meant to accommodate an unusual case; one + which is likely to eventually go away. There's is a pretty good + chance this is not what you were looking for. + + WARNING: Using this function to disable the check can lead to + unexpected behavior and even crashes. It should only be used during + extension module development. Normally, extensions that do not support multiple interpreters may not be imported in a subinterpreter. That implies modules - that do not implement multi-phase init. + that do not implement multi-phase init or that explicitly of out. Likewise for modules import in a subinterpeter with its own GIL when the extension does not support a per-interpreter GIL. This @@ -130,6 +138,10 @@ class allowing_all_extensions: In both cases, this context manager may be used to temporarily disable the check for compatible extension modules. + + You can get the same effect as this function by implementing the + basic interface of multi-phase init (PEP 489) and lying about + support for mulitple interpreters (or per-interpreter GIL). """ def __init__(self, disable_check=True): From 04b36cf0f3f5a0149d4e8aa0ec79685400147701 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 2 Jun 2023 14:06:49 -0600 Subject: [PATCH 2/7] Set the default for disable_check to False. --- Lib/importlib/util.py | 6 +++++- Lib/test/test_importlib/test_util.py | 15 ++++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/Lib/importlib/util.py b/Lib/importlib/util.py index e5cc6f2d10d092..6cfaab37ff3a49 100644 --- a/Lib/importlib/util.py +++ b/Lib/importlib/util.py @@ -127,6 +127,10 @@ class allowing_all_extensions: unexpected behavior and even crashes. It should only be used during extension module development. + If "disable_check" is True then the compatibility check will not + happen while the context manager is active. Otherwise (and by + default) the check *will* happen. + Normally, extensions that do not support multiple interpreters may not be imported in a subinterpreter. That implies modules that do not implement multi-phase init or that explicitly of out. @@ -144,7 +148,7 @@ class allowing_all_extensions: support for mulitple interpreters (or per-interpreter GIL). """ - def __init__(self, disable_check=True): + def __init__(self, disable_check=False): self.disable_check = disable_check def __enter__(self): diff --git a/Lib/test/test_importlib/test_util.py b/Lib/test/test_importlib/test_util.py index 0be504925ecc6a..deed9b5f6652a8 100644 --- a/Lib/test/test_importlib/test_util.py +++ b/Lib/test/test_importlib/test_util.py @@ -679,7 +679,7 @@ def run_with_shared_gil(self, script): def test_single_phase_init_module(self): script = textwrap.dedent(''' import importlib.util - with importlib.util.allowing_all_extensions(): + with importlib.util.allowing_all_extensions(True): import _testsinglephase ''') with self.subTest('check disabled, shared GIL'): @@ -699,6 +699,15 @@ def test_single_phase_init_module(self): with self.assertRaises(ImportError): self.run_with_own_gil(script) + script = textwrap.dedent(f''' + import importlib.util + with importlib.util.allowing_all_extensions(): + import _testsinglephase + ''') + with self.subTest('check enabled (default)'): + with self.assertRaises(ImportError): + self.run_with_shared_gil(script) + @unittest.skipIf(_testmultiphase is None, "test requires _testmultiphase module") def test_incomplete_multi_phase_init_module(self): prescript = textwrap.dedent(f''' @@ -714,7 +723,7 @@ def test_incomplete_multi_phase_init_module(self): script = prescript + textwrap.dedent(''' import importlib.util - with importlib.util.allowing_all_extensions(): + with importlib.util.allowing_all_extensions(True): module = module_from_spec(spec) loader.exec_module(module) ''') @@ -739,7 +748,7 @@ def test_incomplete_multi_phase_init_module(self): def test_complete_multi_phase_init_module(self): script = textwrap.dedent(''' import importlib.util - with importlib.util.allowing_all_extensions(): + with importlib.util.allowing_all_extensions(True): import _testmultiphase ''') with self.subTest('check disabled, shared GIL'): From d459defaca1ea7319cf299b7df93f4aebe001623 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 2 Jun 2023 14:08:32 -0600 Subject: [PATCH 3/7] Change the function name. --- Lib/importlib/util.py | 2 +- Lib/test/test_importlib/test_util.py | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Lib/importlib/util.py b/Lib/importlib/util.py index 6cfaab37ff3a49..cc4a10b1b3c330 100644 --- a/Lib/importlib/util.py +++ b/Lib/importlib/util.py @@ -116,7 +116,7 @@ def find_spec(name, package=None): # is imported by runpy, which means we want to avoid any unnecessary # dependencies. Thus we use a class. -class allowing_all_extensions: +class _incompatible_extension_module_restrictions: """A context manager that can temporarily skip the compatibility check. NOTE: This function is meant to accommodate an unusual case; one diff --git a/Lib/test/test_importlib/test_util.py b/Lib/test/test_importlib/test_util.py index deed9b5f6652a8..be7aee61b198f4 100644 --- a/Lib/test/test_importlib/test_util.py +++ b/Lib/test/test_importlib/test_util.py @@ -653,7 +653,7 @@ def test_magic_number(self): @unittest.skipIf(_interpreters is None, 'subinterpreters required') -class AllowingAllExtensionsTests(unittest.TestCase): +class IncompatibleExtensionModuleRestrictionsTests(unittest.TestCase): ERROR = re.compile("^: module (.*) does not support loading in subinterpreters") @@ -679,7 +679,7 @@ def run_with_shared_gil(self, script): def test_single_phase_init_module(self): script = textwrap.dedent(''' import importlib.util - with importlib.util.allowing_all_extensions(True): + with importlib.util._incompatible_extension_module_restrictions(True): import _testsinglephase ''') with self.subTest('check disabled, shared GIL'): @@ -689,7 +689,7 @@ def test_single_phase_init_module(self): script = textwrap.dedent(f''' import importlib.util - with importlib.util.allowing_all_extensions(False): + with importlib.util._incompatible_extension_module_restrictions(False): import _testsinglephase ''') with self.subTest('check enabled, shared GIL'): @@ -701,7 +701,7 @@ def test_single_phase_init_module(self): script = textwrap.dedent(f''' import importlib.util - with importlib.util.allowing_all_extensions(): + with importlib.util._incompatible_extension_module_restrictions(): import _testsinglephase ''') with self.subTest('check enabled (default)'): @@ -723,7 +723,7 @@ def test_incomplete_multi_phase_init_module(self): script = prescript + textwrap.dedent(''' import importlib.util - with importlib.util.allowing_all_extensions(True): + with importlib.util._incompatible_extension_module_restrictions(True): module = module_from_spec(spec) loader.exec_module(module) ''') @@ -734,7 +734,7 @@ def test_incomplete_multi_phase_init_module(self): script = prescript + textwrap.dedent(''' import importlib.util - with importlib.util.allowing_all_extensions(False): + with importlib.util._incompatible_extension_module_restrictions(False): module = module_from_spec(spec) loader.exec_module(module) ''') @@ -748,7 +748,7 @@ def test_incomplete_multi_phase_init_module(self): def test_complete_multi_phase_init_module(self): script = textwrap.dedent(''' import importlib.util - with importlib.util.allowing_all_extensions(True): + with importlib.util._incompatible_extension_module_restrictions(True): import _testmultiphase ''') with self.subTest('check disabled, shared GIL'): @@ -758,7 +758,7 @@ def test_complete_multi_phase_init_module(self): script = textwrap.dedent(f''' import importlib.util - with importlib.util.allowing_all_extensions(False): + with importlib.util._incompatible_extension_module_restrictions(False): import _testmultiphase ''') with self.subTest('check enabled, shared GIL'): From 27296cb096695112c24dd6fbfaf749dc6aeb8bab Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 2 Jun 2023 14:23:48 -0600 Subject: [PATCH 4/7] Add a NEWS entry. --- .../Library/2023-06-02-14-23-41.gh-issue-104310.UamCOB.rst | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2023-06-02-14-23-41.gh-issue-104310.UamCOB.rst diff --git a/Misc/NEWS.d/next/Library/2023-06-02-14-23-41.gh-issue-104310.UamCOB.rst b/Misc/NEWS.d/next/Library/2023-06-02-14-23-41.gh-issue-104310.UamCOB.rst new file mode 100644 index 00000000000000..a6cc2d999db2d2 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-06-02-14-23-41.gh-issue-104310.UamCOB.rst @@ -0,0 +1,7 @@ +In the beta 1 release we added a utility function for extension module +authors, to use when testing their module for support in multiple +interpreters or under a per-interpreter GIL. The name of that function has +changed from ``allowing_all_extensions`` to +``_incompatible_extension_module_restrictions``. The default for the +"disable_check" argment has change from ``True`` to ``False``, to better +match the new function name. From 01fdb4da32647da0b4e8df63409e481570c6799e Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 2 Jun 2023 15:25:08 -0600 Subject: [PATCH 5/7] Update Misc/NEWS.d/next/Library/2023-06-02-14-23-41.gh-issue-104310.UamCOB.rst Co-authored-by: Kirill Podoprigora --- .../next/Library/2023-06-02-14-23-41.gh-issue-104310.UamCOB.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2023-06-02-14-23-41.gh-issue-104310.UamCOB.rst b/Misc/NEWS.d/next/Library/2023-06-02-14-23-41.gh-issue-104310.UamCOB.rst index a6cc2d999db2d2..461a3a25fe1b43 100644 --- a/Misc/NEWS.d/next/Library/2023-06-02-14-23-41.gh-issue-104310.UamCOB.rst +++ b/Misc/NEWS.d/next/Library/2023-06-02-14-23-41.gh-issue-104310.UamCOB.rst @@ -3,5 +3,5 @@ authors, to use when testing their module for support in multiple interpreters or under a per-interpreter GIL. The name of that function has changed from ``allowing_all_extensions`` to ``_incompatible_extension_module_restrictions``. The default for the -"disable_check" argment has change from ``True`` to ``False``, to better +"disable_check" argument has change from ``True`` to ``False``, to better match the new function name. From bec03a22a652bf4168b454b360287e5299f6eba7 Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Fri, 2 Jun 2023 15:59:54 -0600 Subject: [PATCH 6/7] Get rid of the default argument. --- Lib/importlib/util.py | 8 ++++---- Lib/test/test_importlib/test_util.py | 9 --------- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/Lib/importlib/util.py b/Lib/importlib/util.py index cc4a10b1b3c330..7d4a26f2ed72e2 100644 --- a/Lib/importlib/util.py +++ b/Lib/importlib/util.py @@ -128,8 +128,8 @@ class _incompatible_extension_module_restrictions: extension module development. If "disable_check" is True then the compatibility check will not - happen while the context manager is active. Otherwise (and by - default) the check *will* happen. + happen while the context manager is active. Otherwise the check + *will* happen. Normally, extensions that do not support multiple interpreters may not be imported in a subinterpreter. That implies modules @@ -148,8 +148,8 @@ class _incompatible_extension_module_restrictions: support for mulitple interpreters (or per-interpreter GIL). """ - def __init__(self, disable_check=False): - self.disable_check = disable_check + def __init__(self, disable_check): + self.disable_check = bool(disable_check) def __enter__(self): self.old = _imp._override_multi_interp_extensions_check(self.override) diff --git a/Lib/test/test_importlib/test_util.py b/Lib/test/test_importlib/test_util.py index be7aee61b198f4..9e5a1b1f991100 100644 --- a/Lib/test/test_importlib/test_util.py +++ b/Lib/test/test_importlib/test_util.py @@ -699,15 +699,6 @@ def test_single_phase_init_module(self): with self.assertRaises(ImportError): self.run_with_own_gil(script) - script = textwrap.dedent(f''' - import importlib.util - with importlib.util._incompatible_extension_module_restrictions(): - import _testsinglephase - ''') - with self.subTest('check enabled (default)'): - with self.assertRaises(ImportError): - self.run_with_shared_gil(script) - @unittest.skipIf(_testmultiphase is None, "test requires _testmultiphase module") def test_incomplete_multi_phase_init_module(self): prescript = textwrap.dedent(f''' From 760238e0ac8de8b3895a3eac1f0e3e4d9995c25c Mon Sep 17 00:00:00 2001 From: Eric Snow Date: Thu, 8 Jun 2023 11:49:35 -0600 Subject: [PATCH 7/7] Use a keyword-only parameter. --- Lib/importlib/util.py | 2 +- Lib/test/test_importlib/test_util.py | 24 ++++++++++++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Lib/importlib/util.py b/Lib/importlib/util.py index 7d4a26f2ed72e2..f4d6e82331516f 100644 --- a/Lib/importlib/util.py +++ b/Lib/importlib/util.py @@ -148,7 +148,7 @@ class _incompatible_extension_module_restrictions: support for mulitple interpreters (or per-interpreter GIL). """ - def __init__(self, disable_check): + def __init__(self, *, disable_check): self.disable_check = bool(disable_check) def __enter__(self): diff --git a/Lib/test/test_importlib/test_util.py b/Lib/test/test_importlib/test_util.py index 9e5a1b1f991100..e967adc9451c81 100644 --- a/Lib/test/test_importlib/test_util.py +++ b/Lib/test/test_importlib/test_util.py @@ -678,8 +678,8 @@ def run_with_shared_gil(self, script): @unittest.skipIf(_testsinglephase is None, "test requires _testsinglephase module") def test_single_phase_init_module(self): script = textwrap.dedent(''' - import importlib.util - with importlib.util._incompatible_extension_module_restrictions(True): + from importlib.util import _incompatible_extension_module_restrictions + with _incompatible_extension_module_restrictions(disable_check=True): import _testsinglephase ''') with self.subTest('check disabled, shared GIL'): @@ -688,8 +688,8 @@ def test_single_phase_init_module(self): self.run_with_own_gil(script) script = textwrap.dedent(f''' - import importlib.util - with importlib.util._incompatible_extension_module_restrictions(False): + from importlib.util import _incompatible_extension_module_restrictions + with _incompatible_extension_module_restrictions(disable_check=False): import _testsinglephase ''') with self.subTest('check enabled, shared GIL'): @@ -713,8 +713,8 @@ def test_incomplete_multi_phase_init_module(self): ''') script = prescript + textwrap.dedent(''' - import importlib.util - with importlib.util._incompatible_extension_module_restrictions(True): + from importlib.util import _incompatible_extension_module_restrictions + with _incompatible_extension_module_restrictions(disable_check=True): module = module_from_spec(spec) loader.exec_module(module) ''') @@ -724,8 +724,8 @@ def test_incomplete_multi_phase_init_module(self): self.run_with_own_gil(script) script = prescript + textwrap.dedent(''' - import importlib.util - with importlib.util._incompatible_extension_module_restrictions(False): + from importlib.util import _incompatible_extension_module_restrictions + with _incompatible_extension_module_restrictions(disable_check=False): module = module_from_spec(spec) loader.exec_module(module) ''') @@ -738,8 +738,8 @@ def test_incomplete_multi_phase_init_module(self): @unittest.skipIf(_testmultiphase is None, "test requires _testmultiphase module") def test_complete_multi_phase_init_module(self): script = textwrap.dedent(''' - import importlib.util - with importlib.util._incompatible_extension_module_restrictions(True): + from importlib.util import _incompatible_extension_module_restrictions + with _incompatible_extension_module_restrictions(disable_check=True): import _testmultiphase ''') with self.subTest('check disabled, shared GIL'): @@ -748,8 +748,8 @@ def test_complete_multi_phase_init_module(self): self.run_with_own_gil(script) script = textwrap.dedent(f''' - import importlib.util - with importlib.util._incompatible_extension_module_restrictions(False): + from importlib.util import _incompatible_extension_module_restrictions + with _incompatible_extension_module_restrictions(disable_check=False): import _testmultiphase ''') with self.subTest('check enabled, shared GIL'):