diff --git a/Lib/test/test_lazy_import/__init__.py b/Lib/test/test_lazy_import/__init__.py index 1298d532b91b97c..76fc312e5ed3c65 100644 --- a/Lib/test/test_lazy_import/__init__.py +++ b/Lib/test/test_lazy_import/__init__.py @@ -2109,5 +2109,135 @@ def test_set_bad_filter(self): self.assertRaises(ValueError, _testcapi.PyImport_SetLazyImportsFilter, 42) +class SubmoduleLazinessTests(unittest.TestCase): + """Tests that module-level lazy imports remain lazy until accessed.""" + + def tearDown(self): + for key in list(sys.modules.keys()): + if key.startswith('test.test_lazy_import.data'): + del sys.modules[key] + sys.set_lazy_imports_filter(None) + sys.set_lazy_imports("normal") + + def test_unaccessed_imports_stay_lazy(self): + """Imports in 'all' mode should stay lazy until accessed.""" + sys.set_lazy_imports("all") + from test.test_lazy_import.data.metasyntactic import names + self.assertIsInstance(names.__dict__["Foo"], types.LazyImportType) + self.assertNotIn( + "test.test_lazy_import.data.metasyntactic.foo", sys.modules + ) + _ = names.Foo + self.assertEqual(names.Foo, "Foo") + self.assertIn( + "test.test_lazy_import.data.metasyntactic.foo", sys.modules + ) + self.assertIsInstance(names.__dict__["Ack"], types.LazyImportType) + self.assertNotIn( + "test.test_lazy_import.data.metasyntactic.foo.ack", sys.modules + ) + + +class AttributeSideEffectTests(unittest.TestCase): + """Tests that submodule imports don't overwrite parent attributes.""" + + def tearDown(self): + for key in list(sys.modules.keys()): + if key.startswith('test.test_lazy_import.data'): + del sys.modules[key] + sys.set_lazy_imports_filter(None) + sys.set_lazy_imports("normal") + + def test_version_submodule_does_not_overwrite(self): + """A __version__ submodule should not overwrite the parent's + __version__ attribute imported in __init__.py.""" + import test.test_lazy_import.data.versioned as versioned + self.assertEqual(versioned.__version__, "1.0") + self.assertEqual( + versioned.__copyright__, + "Copyright (c) 2001-2022 Python Software Foundation.", + ) + + +class ModuleVariableNameCollisionTests(unittest.TestCase): + """Tests for name collision between a submodule and a variable.""" + + def tearDown(self): + for key in list(sys.modules.keys()): + if key.startswith('test.test_lazy_import.data'): + del sys.modules[key] + sys.set_lazy_imports_filter(None) + sys.set_lazy_imports("normal") + + def test_variable_after_import_wins(self): + """Variable assigned after import should overwrite the submodule.""" + from test.test_lazy_import.data import module_same_name_var_order1 + self.assertEqual(module_same_name_var_order1.bar, "Blah") + + def test_import_after_variable_wins(self): + """Import after variable assignment should overwrite the variable.""" + from test.test_lazy_import.data import module_same_name_var_order2 + bar_mod = sys.modules[ + "test.test_lazy_import.data.module_same_name_var_order2.bar" + ] + self.assertIs(module_same_name_var_order2.bar, bar_mod) + + +class DeletedModuleReimportTests(unittest.TestCase): + """Tests for reimporting after module deletion from sys.modules.""" + + def tearDown(self): + for key in list(sys.modules.keys()): + if key.startswith('test.test_lazy_import.data'): + del sys.modules[key] + sys.set_lazy_imports_filter(None) + sys.set_lazy_imports("normal") + + def test_reimport_creates_new_module(self): + """Deleting and reimporting should create a new module object.""" + import test.test_lazy_import.data.metasyntactic.foo + import test.test_lazy_import.data.metasyntactic.foo.bar.baz + + first_bar = test.test_lazy_import.data.metasyntactic.foo.bar + + del sys.modules[ + "test.test_lazy_import.data.metasyntactic.foo.bar" + ] + + import test.test_lazy_import.data.metasyntactic.foo.bar.thud + + second_bar = test.test_lazy_import.data.metasyntactic.foo.bar + + self.assertIsNot(first_bar, second_bar) + self.assertIn("baz", dir(first_bar)) + self.assertNotIn("thud", dir(first_bar)) + self.assertIn("thud", dir(second_bar)) + self.assertNotIn("baz", dir(second_bar)) + + +@support.requires_subprocess() +class CircularImportLazyTests(unittest.TestCase): + """Tests that lazy imports can break circular import patterns.""" + + def test_succeeds_with_lazy(self): + """Same-level circular imports should succeed with lazy mode.""" + proc = assert_python_ok( + "-X", "lazy_imports=all", "-c", + "import test.test_lazy_import.data.circular_import_pkg.main;" + "print('OK')", + ) + self.assertIn(b"OK", proc.out) + + def test_fails_without_lazy(self): + """Same-level circular imports should fail without lazy mode.""" + result = subprocess.run( + [sys.executable, "-c", + "import test.test_lazy_import.data.circular_import_pkg.main"], + capture_output=True, text=True, + ) + self.assertNotEqual(result.returncode, 0) + self.assertIn("ImportError", result.stderr) + + if __name__ == '__main__': unittest.main() diff --git a/Lib/test/test_lazy_import/data/circular_import_pkg/__init__.py b/Lib/test/test_lazy_import/data/circular_import_pkg/__init__.py new file mode 100644 index 000000000000000..e69de29bb2d1d64 diff --git a/Lib/test/test_lazy_import/data/circular_import_pkg/main.py b/Lib/test/test_lazy_import/data/circular_import_pkg/main.py new file mode 100644 index 000000000000000..0b897b377e28a66 --- /dev/null +++ b/Lib/test/test_lazy_import/data/circular_import_pkg/main.py @@ -0,0 +1,2 @@ +from .x import X2 +X2() diff --git a/Lib/test/test_lazy_import/data/circular_import_pkg/x.py b/Lib/test/test_lazy_import/data/circular_import_pkg/x.py new file mode 100644 index 000000000000000..b44d5185c9e70fc --- /dev/null +++ b/Lib/test/test_lazy_import/data/circular_import_pkg/x.py @@ -0,0 +1,7 @@ +def X1(): + return "X" + +from .y import Y1 + +def X2(): + return Y1() diff --git a/Lib/test/test_lazy_import/data/circular_import_pkg/y.py b/Lib/test/test_lazy_import/data/circular_import_pkg/y.py new file mode 100644 index 000000000000000..d2385f7e67c01fd --- /dev/null +++ b/Lib/test/test_lazy_import/data/circular_import_pkg/y.py @@ -0,0 +1,7 @@ +def Y1(): + return "Y" + +from .x import X2 + +def Y2(): + return X2() diff --git a/Lib/test/test_lazy_import/data/metasyntactic/__init__.py b/Lib/test/test_lazy_import/data/metasyntactic/__init__.py new file mode 100644 index 000000000000000..e69de29bb2d1d64 diff --git a/Lib/test/test_lazy_import/data/metasyntactic/foo/__init__.py b/Lib/test/test_lazy_import/data/metasyntactic/foo/__init__.py new file mode 100644 index 000000000000000..632a9fb87ac0fd7 --- /dev/null +++ b/Lib/test/test_lazy_import/data/metasyntactic/foo/__init__.py @@ -0,0 +1 @@ +Foo = "Foo" diff --git a/Lib/test/test_lazy_import/data/metasyntactic/foo/ack/__init__.py b/Lib/test/test_lazy_import/data/metasyntactic/foo/ack/__init__.py new file mode 100644 index 000000000000000..c4c249f3b651536 --- /dev/null +++ b/Lib/test/test_lazy_import/data/metasyntactic/foo/ack/__init__.py @@ -0,0 +1 @@ +Ack = "Ack" diff --git a/Lib/test/test_lazy_import/data/metasyntactic/foo/bar/__init__.py b/Lib/test/test_lazy_import/data/metasyntactic/foo/bar/__init__.py new file mode 100644 index 000000000000000..03faa550258f97f --- /dev/null +++ b/Lib/test/test_lazy_import/data/metasyntactic/foo/bar/__init__.py @@ -0,0 +1 @@ +Bar = "Bar" diff --git a/Lib/test/test_lazy_import/data/metasyntactic/foo/bar/baz/__init__.py b/Lib/test/test_lazy_import/data/metasyntactic/foo/bar/baz/__init__.py new file mode 100644 index 000000000000000..62d8366b4404d6c --- /dev/null +++ b/Lib/test/test_lazy_import/data/metasyntactic/foo/bar/baz/__init__.py @@ -0,0 +1 @@ +Baz = "Baz" diff --git a/Lib/test/test_lazy_import/data/metasyntactic/foo/bar/baz/qux/__init__.py b/Lib/test/test_lazy_import/data/metasyntactic/foo/bar/baz/qux/__init__.py new file mode 100644 index 000000000000000..12389d76a28539c --- /dev/null +++ b/Lib/test/test_lazy_import/data/metasyntactic/foo/bar/baz/qux/__init__.py @@ -0,0 +1 @@ +Qux = "Qux" diff --git a/Lib/test/test_lazy_import/data/metasyntactic/foo/bar/thud/__init__.py b/Lib/test/test_lazy_import/data/metasyntactic/foo/bar/thud/__init__.py new file mode 100644 index 000000000000000..a4dd8ab0fae17ee --- /dev/null +++ b/Lib/test/test_lazy_import/data/metasyntactic/foo/bar/thud/__init__.py @@ -0,0 +1 @@ +Thud = "Thud" diff --git a/Lib/test/test_lazy_import/data/metasyntactic/names.py b/Lib/test/test_lazy_import/data/metasyntactic/names.py new file mode 100644 index 000000000000000..b2edf13e850389a --- /dev/null +++ b/Lib/test/test_lazy_import/data/metasyntactic/names.py @@ -0,0 +1,9 @@ +from .foo import Foo +from .foo.ack import Ack +from .foo.bar import Bar +from .foo.bar.baz import Baz +from .foo.bar.thud import Thud +from .waldo import Waldo +from .waldo.fred import Fred +from .plugh import Plugh +Metasyntactic = "Metasyntactic" diff --git a/Lib/test/test_lazy_import/data/metasyntactic/plugh/__init__.py b/Lib/test/test_lazy_import/data/metasyntactic/plugh/__init__.py new file mode 100644 index 000000000000000..5db053204fc17ce --- /dev/null +++ b/Lib/test/test_lazy_import/data/metasyntactic/plugh/__init__.py @@ -0,0 +1 @@ +Plugh = "Plugh" diff --git a/Lib/test/test_lazy_import/data/metasyntactic/waldo/__init__.py b/Lib/test/test_lazy_import/data/metasyntactic/waldo/__init__.py new file mode 100644 index 000000000000000..39f5a1b18385733 --- /dev/null +++ b/Lib/test/test_lazy_import/data/metasyntactic/waldo/__init__.py @@ -0,0 +1 @@ +Waldo = "Waldo" diff --git a/Lib/test/test_lazy_import/data/metasyntactic/waldo/fred/__init__.py b/Lib/test/test_lazy_import/data/metasyntactic/waldo/fred/__init__.py new file mode 100644 index 000000000000000..8c9dce1b3cfb9d1 --- /dev/null +++ b/Lib/test/test_lazy_import/data/metasyntactic/waldo/fred/__init__.py @@ -0,0 +1 @@ +Fred = "Fred" diff --git a/Lib/test/test_lazy_import/data/module_same_name_var_order1/__init__.py b/Lib/test/test_lazy_import/data/module_same_name_var_order1/__init__.py new file mode 100644 index 000000000000000..b815b2a7d6fedf1 --- /dev/null +++ b/Lib/test/test_lazy_import/data/module_same_name_var_order1/__init__.py @@ -0,0 +1,3 @@ +from .bar import Bar +bar = "Blah" +Bar diff --git a/Lib/test/test_lazy_import/data/module_same_name_var_order1/bar.py b/Lib/test/test_lazy_import/data/module_same_name_var_order1/bar.py new file mode 100644 index 000000000000000..03faa550258f97f --- /dev/null +++ b/Lib/test/test_lazy_import/data/module_same_name_var_order1/bar.py @@ -0,0 +1 @@ +Bar = "Bar" diff --git a/Lib/test/test_lazy_import/data/module_same_name_var_order2/__init__.py b/Lib/test/test_lazy_import/data/module_same_name_var_order2/__init__.py new file mode 100644 index 000000000000000..008d199c73e534d --- /dev/null +++ b/Lib/test/test_lazy_import/data/module_same_name_var_order2/__init__.py @@ -0,0 +1,3 @@ +bar = "Blah" +from .bar import Bar +Bar diff --git a/Lib/test/test_lazy_import/data/module_same_name_var_order2/bar.py b/Lib/test/test_lazy_import/data/module_same_name_var_order2/bar.py new file mode 100644 index 000000000000000..03faa550258f97f --- /dev/null +++ b/Lib/test/test_lazy_import/data/module_same_name_var_order2/bar.py @@ -0,0 +1 @@ +Bar = "Bar" diff --git a/Lib/test/test_lazy_import/data/versioned/__init__.py b/Lib/test/test_lazy_import/data/versioned/__init__.py new file mode 100644 index 000000000000000..be5bb3f8e607185 --- /dev/null +++ b/Lib/test/test_lazy_import/data/versioned/__init__.py @@ -0,0 +1,2 @@ +from .__version__ import __version__ +from .__version__ import __copyright__ diff --git a/Lib/test/test_lazy_import/data/versioned/__version__.py b/Lib/test/test_lazy_import/data/versioned/__version__.py new file mode 100644 index 000000000000000..37a104387354e28 --- /dev/null +++ b/Lib/test/test_lazy_import/data/versioned/__version__.py @@ -0,0 +1,2 @@ +__version__ = "1.0" +__copyright__ = "Copyright (c) 2001-2022 Python Software Foundation." diff --git a/Makefile.pre.in b/Makefile.pre.in index 9435bf534fb5121..5b3bccd786101dd 100644 --- a/Makefile.pre.in +++ b/Makefile.pre.in @@ -2671,6 +2671,20 @@ TESTSUBDIRS= idlelib/idle_test \ test/test_lazy_import/data \ test/test_lazy_import/data/pkg \ test/test_lazy_import/data/badsyntax \ + test/test_lazy_import/data/circular_import_pkg \ + test/test_lazy_import/data/metasyntactic \ + test/test_lazy_import/data/metasyntactic/foo \ + test/test_lazy_import/data/metasyntactic/foo/ack \ + test/test_lazy_import/data/metasyntactic/foo/bar \ + test/test_lazy_import/data/metasyntactic/foo/bar/baz \ + test/test_lazy_import/data/metasyntactic/foo/bar/baz/qux \ + test/test_lazy_import/data/metasyntactic/foo/bar/thud \ + test/test_lazy_import/data/metasyntactic/plugh \ + test/test_lazy_import/data/metasyntactic/waldo \ + test/test_lazy_import/data/metasyntactic/waldo/fred \ + test/test_lazy_import/data/module_same_name_var_order1 \ + test/test_lazy_import/data/module_same_name_var_order2 \ + test/test_lazy_import/data/versioned \ test/test_module \ test/test_multiprocessing_fork \ test/test_multiprocessing_forkserver \