@@ -2018,5 +2018,223 @@ def test_set_bad_filter(self):
20182018 self .assertRaises (ValueError , _testcapi .PyImport_SetLazyImportsFilter , 42 )
20192019
20202020
2021+
2022+ class DictOperationsWithLazyTests (unittest .TestCase ):
2023+ """Tests for dict operations with lazy import values."""
2024+
2025+ def tearDown (self ):
2026+ for key in list (sys .modules .keys ()):
2027+ if key .startswith ('test.test_lazy_import.data' ):
2028+ del sys .modules [key ]
2029+ sys .set_lazy_imports_filter (None )
2030+ sys .set_lazy_imports ("normal" )
2031+
2032+ def test_dict_copy_preserves_lazy (self ):
2033+ """dict.copy() should preserve lazy import proxy objects."""
2034+ sys .set_lazy_imports ("all" )
2035+ from test .test_lazy_import .data .metasyntactic import names
2036+ d = names .__dict__ .copy ()
2037+ self .assertIsInstance (d ["Foo" ], types .LazyImportType )
2038+ self .assertEqual (d ["Metasyntactic" ], "Metasyntactic" )
2039+
2040+ def test_dict_or_preserves_lazy (self ):
2041+ """dict | should keep the winning side's lazy/resolved status."""
2042+ sys .set_lazy_imports ("all" )
2043+ from test .test_lazy_import .data .metasyntactic import names
2044+ lazy = names .__dict__ .copy ()
2045+ resolved = {"Foo" : "resolved" }
2046+ self .assertEqual ((lazy | resolved )["Foo" ], "resolved" )
2047+ self .assertIsInstance ((resolved | lazy )["Foo" ], types .LazyImportType )
2048+
2049+ def test_dict_update_preserves_lazy (self ):
2050+ """dict.update() should transfer lazy import proxy objects."""
2051+ sys .set_lazy_imports ("all" )
2052+ from test .test_lazy_import .data .metasyntactic import names
2053+ target = {}
2054+ target .update (names .__dict__ )
2055+ self .assertIsInstance (target ["Foo" ], types .LazyImportType )
2056+
2057+
2058+ class SubmoduleLazinessTests (unittest .TestCase ):
2059+ """Tests that module-level lazy imports remain lazy until accessed."""
2060+
2061+ def tearDown (self ):
2062+ for key in list (sys .modules .keys ()):
2063+ if key .startswith ('test.test_lazy_import.data' ):
2064+ del sys .modules [key ]
2065+ sys .set_lazy_imports_filter (None )
2066+ sys .set_lazy_imports ("normal" )
2067+
2068+ def test_unaccessed_imports_stay_lazy (self ):
2069+ """Imports in 'all' mode should stay lazy until accessed."""
2070+ sys .set_lazy_imports ("all" )
2071+ from test .test_lazy_import .data .metasyntactic import names
2072+ self .assertIsInstance (names .__dict__ ["Foo" ], types .LazyImportType )
2073+ self .assertNotIn (
2074+ "test.test_lazy_import.data.metasyntactic.foo" , sys .modules
2075+ )
2076+ _ = names .Foo
2077+ self .assertEqual (names .Foo , "Foo" )
2078+ self .assertIn (
2079+ "test.test_lazy_import.data.metasyntactic.foo" , sys .modules
2080+ )
2081+ self .assertIsInstance (names .__dict__ ["Ack" ], types .LazyImportType )
2082+ self .assertNotIn (
2083+ "test.test_lazy_import.data.metasyntactic.foo.ack" , sys .modules
2084+ )
2085+
2086+
2087+ class AttributeSideEffectTests (unittest .TestCase ):
2088+ """Tests that submodule imports don't overwrite parent attributes."""
2089+
2090+ def tearDown (self ):
2091+ for key in list (sys .modules .keys ()):
2092+ if key .startswith ('test.test_lazy_import.data' ):
2093+ del sys .modules [key ]
2094+ sys .set_lazy_imports_filter (None )
2095+ sys .set_lazy_imports ("normal" )
2096+
2097+ def test_version_submodule_does_not_overwrite (self ):
2098+ """A __version__ submodule should not overwrite the parent's
2099+ __version__ attribute imported in __init__.py."""
2100+ import test .test_lazy_import .data .versioned as versioned
2101+ self .assertEqual (versioned .__version__ , "1.0" )
2102+ self .assertEqual (
2103+ versioned .__copyright__ ,
2104+ "Copyright (c) 2001-2022 Python Software Foundation." ,
2105+ )
2106+
2107+
2108+ class ModuleVariableNameCollisionTests (unittest .TestCase ):
2109+ """Tests for name collision between a submodule and a variable."""
2110+
2111+ def tearDown (self ):
2112+ for key in list (sys .modules .keys ()):
2113+ if key .startswith ('test.test_lazy_import.data' ):
2114+ del sys .modules [key ]
2115+ sys .set_lazy_imports_filter (None )
2116+ sys .set_lazy_imports ("normal" )
2117+
2118+ def test_variable_after_import_wins (self ):
2119+ """Variable assigned after import should overwrite the submodule."""
2120+ from test .test_lazy_import .data import module_same_name_var_order1
2121+ self .assertEqual (module_same_name_var_order1 .bar , "Blah" )
2122+
2123+ def test_import_after_variable_wins (self ):
2124+ """Import after variable assignment should overwrite the variable."""
2125+ from test .test_lazy_import .data import module_same_name_var_order2
2126+ bar_mod = sys .modules [
2127+ "test.test_lazy_import.data.module_same_name_var_order2.bar"
2128+ ]
2129+ self .assertIs (module_same_name_var_order2 .bar , bar_mod )
2130+
2131+
2132+ class DeletedModuleReimportTests (unittest .TestCase ):
2133+ """Tests for reimporting after module deletion from sys.modules."""
2134+
2135+ def tearDown (self ):
2136+ for key in list (sys .modules .keys ()):
2137+ if key .startswith ('test.test_lazy_import.data' ):
2138+ del sys .modules [key ]
2139+ sys .set_lazy_imports_filter (None )
2140+ sys .set_lazy_imports ("normal" )
2141+
2142+ def test_reimport_creates_new_module (self ):
2143+ """Deleting and reimporting should create a new module object."""
2144+ import test .test_lazy_import .data .metasyntactic .foo
2145+ import test .test_lazy_import .data .metasyntactic .foo .bar .baz
2146+
2147+ first_bar = test .test_lazy_import .data .metasyntactic .foo .bar
2148+
2149+ del sys .modules [
2150+ "test.test_lazy_import.data.metasyntactic.foo.bar"
2151+ ]
2152+
2153+ import test .test_lazy_import .data .metasyntactic .foo .bar .thud
2154+
2155+ second_bar = test .test_lazy_import .data .metasyntactic .foo .bar
2156+
2157+ self .assertIsNot (first_bar , second_bar )
2158+ self .assertIn ("baz" , dir (first_bar ))
2159+ self .assertNotIn ("thud" , dir (first_bar ))
2160+ self .assertIn ("thud" , dir (second_bar ))
2161+ self .assertNotIn ("baz" , dir (second_bar ))
2162+
2163+
2164+ @support .requires_subprocess ()
2165+ class CircularImportLazyTests (unittest .TestCase ):
2166+ """Tests that lazy imports can break circular import patterns."""
2167+
2168+ def test_succeeds_with_lazy (self ):
2169+ """Same-level circular imports should succeed with lazy mode."""
2170+ proc = assert_python_ok (
2171+ "-X" , "lazy_imports=all" , "-c" ,
2172+ "import test.test_lazy_import.data.circular_import_pkg.main;"
2173+ "print('OK')" ,
2174+ )
2175+ self .assertIn (b"OK" , proc .out )
2176+
2177+ def test_fails_without_lazy (self ):
2178+ """Same-level circular imports should fail without lazy mode."""
2179+ result = subprocess .run (
2180+ [sys .executable , "-X" , "lazy_imports=none" , "-c" ,
2181+ "import test.test_lazy_import.data.circular_import_pkg.main" ],
2182+ capture_output = True , text = True ,
2183+ )
2184+ self .assertNotEqual (result .returncode , 0 )
2185+ self .assertIn ("ImportError" , result .stderr )
2186+
2187+
2188+ @support .requires_subprocess ()
2189+ class DictMutationDuringLoadTests (unittest .TestCase ):
2190+ """Tests that module dict mutation during loading doesn't crash."""
2191+
2192+ def test_dict_mutation_during_import (self ):
2193+ """Iterating a module dict while lazy imports resolve should not
2194+ crash even if the dict is mutated during iteration."""
2195+ with tempfile .TemporaryDirectory () as tmpdir :
2196+ pkg = os .path .join (tmpdir , "dcwl_pkg" )
2197+ os .makedirs (pkg )
2198+
2199+ with open (os .path .join (pkg , "__init__.py" ), "w" ) as f :
2200+ f .write (textwrap .dedent ("""\
2201+ from .elements import elements_function
2202+
2203+ def __go(lcls):
2204+ global __all__
2205+ __all__ = sorted(
2206+ name
2207+ for name, obj in lcls.items()
2208+ if not name.startswith("_")
2209+ )
2210+
2211+ __go(locals())
2212+ """ ))
2213+
2214+ with open (os .path .join (pkg , "elements.py" ), "w" ) as f :
2215+ f .write (textwrap .dedent ("""\
2216+ from .elements_sub import elements_sub_function
2217+
2218+ def elements_function():
2219+ pass
2220+ """ ))
2221+
2222+ with open (os .path .join (pkg , "elements_sub.py" ), "w" ) as f :
2223+ f .write ("def elements_sub_function(): pass\n " )
2224+
2225+ env = os .environ .copy ()
2226+ env ["PYTHONPATH" ] = tmpdir
2227+ result = subprocess .run (
2228+ [sys .executable , "-X" , "lazy_imports=all" ,
2229+ "-c" , "import dcwl_pkg; print('OK')" ],
2230+ capture_output = True , text = True , env = env ,
2231+ )
2232+ self .assertEqual (
2233+ result .returncode , 0 ,
2234+ f"stdout: { result .stdout } , stderr: { result .stderr } " ,
2235+ )
2236+ self .assertIn ("OK" , result .stdout )
2237+
2238+
20212239if __name__ == '__main__' :
20222240 unittest .main ()
0 commit comments