@@ -1126,6 +1126,139 @@ def test_safe_exists(tmp_path: Path) -> None:
11261126 assert safe_exists (p ) is False
11271127
11281128
1129+ def test_import_sets_module_as_attribute (pytester : Pytester ) -> None :
1130+ """Unittest test for #12194."""
1131+ pytester .path .joinpath ("foo/bar/baz" ).mkdir (parents = True )
1132+ pytester .path .joinpath ("foo/__init__.py" ).touch ()
1133+ pytester .path .joinpath ("foo/bar/__init__.py" ).touch ()
1134+ pytester .path .joinpath ("foo/bar/baz/__init__.py" ).touch ()
1135+ pytester .syspathinsert ()
1136+
1137+ # Import foo.bar.baz and ensure parent modules also ended up imported.
1138+ baz = import_path (
1139+ pytester .path .joinpath ("foo/bar/baz/__init__.py" ),
1140+ mode = ImportMode .importlib ,
1141+ root = pytester .path ,
1142+ consider_namespace_packages = False ,
1143+ )
1144+ assert baz .__name__ == "foo.bar.baz"
1145+ foo = sys .modules ["foo" ]
1146+ assert foo .__name__ == "foo"
1147+ bar = sys .modules ["foo.bar" ]
1148+ assert bar .__name__ == "foo.bar"
1149+
1150+ # Check parent modules have an attribute pointing to their children.
1151+ assert bar .baz is baz
1152+ assert foo .bar is bar
1153+
1154+ # Ensure we returned the "foo.bar" module cached in sys.modules.
1155+ bar_2 = import_path (
1156+ pytester .path .joinpath ("foo/bar/__init__.py" ),
1157+ mode = ImportMode .importlib ,
1158+ root = pytester .path ,
1159+ consider_namespace_packages = False ,
1160+ )
1161+ assert bar_2 is bar
1162+
1163+
1164+ def test_import_sets_module_as_attribute_without_init_files (pytester : Pytester ) -> None :
1165+ """Similar to test_import_sets_module_as_attribute, but without __init__.py files."""
1166+ pytester .path .joinpath ("foo/bar" ).mkdir (parents = True )
1167+ pytester .path .joinpath ("foo/bar/baz.py" ).touch ()
1168+ pytester .syspathinsert ()
1169+
1170+ # Import foo.bar.baz and ensure parent modules also ended up imported.
1171+ baz = import_path (
1172+ pytester .path .joinpath ("foo/bar/baz.py" ),
1173+ mode = ImportMode .importlib ,
1174+ root = pytester .path ,
1175+ consider_namespace_packages = False ,
1176+ )
1177+ assert baz .__name__ == "foo.bar.baz"
1178+ foo = sys .modules ["foo" ]
1179+ assert foo .__name__ == "foo"
1180+ bar = sys .modules ["foo.bar" ]
1181+ assert bar .__name__ == "foo.bar"
1182+
1183+ # Check parent modules have an attribute pointing to their children.
1184+ assert bar .baz is baz
1185+ assert foo .bar is bar
1186+
1187+ # Ensure we returned the "foo.bar.baz" module cached in sys.modules.
1188+ baz_2 = import_path (
1189+ pytester .path .joinpath ("foo/bar/baz.py" ),
1190+ mode = ImportMode .importlib ,
1191+ root = pytester .path ,
1192+ consider_namespace_packages = False ,
1193+ )
1194+ assert baz_2 is baz
1195+
1196+
1197+ def test_import_sets_module_as_attribute_regression (pytester : Pytester ) -> None :
1198+ """Regression test for #12194."""
1199+ pytester .path .joinpath ("foo/bar/baz" ).mkdir (parents = True )
1200+ pytester .path .joinpath ("foo/__init__.py" ).touch ()
1201+ pytester .path .joinpath ("foo/bar/__init__.py" ).touch ()
1202+ pytester .path .joinpath ("foo/bar/baz/__init__.py" ).touch ()
1203+ f = pytester .makepyfile (
1204+ """
1205+ import foo
1206+ from foo.bar import baz
1207+ foo.bar.baz
1208+
1209+ def test_foo() -> None:
1210+ pass
1211+ """
1212+ )
1213+
1214+ pytester .syspathinsert ()
1215+ result = pytester .runpython (f )
1216+ assert result .ret == 0
1217+
1218+ result = pytester .runpytest ("--import-mode=importlib" , "--doctest-modules" )
1219+ assert result .ret == 0
1220+
1221+
1222+ def test_import_submodule_not_namespace (pytester : Pytester ) -> None :
1223+ """
1224+ Regression test for importing a submodule 'foo.bar' while there is a 'bar' directory
1225+ reachable from sys.path -- ensuring the top-level module does not end up imported as a namespace
1226+ package.
1227+
1228+ #12194
1229+ https://github.com/pytest-dev/pytest/pull/12208#issuecomment-2056458432
1230+ """
1231+ pytester .syspathinsert ()
1232+ # Create package 'foo' with a submodule 'bar'.
1233+ pytester .path .joinpath ("foo" ).mkdir ()
1234+ foo_path = pytester .path .joinpath ("foo/__init__.py" )
1235+ foo_path .touch ()
1236+ bar_path = pytester .path .joinpath ("foo/bar.py" )
1237+ bar_path .touch ()
1238+ # Create top-level directory in `sys.path` with the same name as that submodule.
1239+ pytester .path .joinpath ("bar" ).mkdir ()
1240+
1241+ # Import `foo`, then `foo.bar`, and check they were imported from the correct location.
1242+ foo = import_path (
1243+ foo_path ,
1244+ mode = ImportMode .importlib ,
1245+ root = pytester .path ,
1246+ consider_namespace_packages = False ,
1247+ )
1248+ bar = import_path (
1249+ bar_path ,
1250+ mode = ImportMode .importlib ,
1251+ root = pytester .path ,
1252+ consider_namespace_packages = False ,
1253+ )
1254+ assert foo .__name__ == "foo"
1255+ assert bar .__name__ == "foo.bar"
1256+ assert foo .__file__ is not None
1257+ assert bar .__file__ is not None
1258+ assert Path (foo .__file__ ) == foo_path
1259+ assert Path (bar .__file__ ) == bar_path
1260+
1261+
11291262class TestNamespacePackages :
11301263 """Test import_path support when importing from properly namespace packages."""
11311264
0 commit comments