Skip to content

Commit be1963c

Browse files
committed
refactor: Be more consistent regarding not overriding submodules with aliases
Our documentation states that a submodule will always get precedence over an alias of the same name. But the code only avoided replacing a submodule when the alias was pointing to the module. With this change, we avoid replacing the submodule with *any* alias of the same name, not just aliases that point to the submodule. The reason we do this is that Griffe suffers from a limitation where a module cannot store both a submodule and anoter member of the same name. We previously decided not to lift this limitation, because we consider this kind of name-shadowing bad practice. We document it in our "Recommendations / Python code" page.
1 parent fbad6f5 commit be1963c

File tree

2 files changed

+24
-8
lines changed

2 files changed

+24
-8
lines changed

src/_griffe/loader.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -397,19 +397,16 @@ def expand_wildcards(
397397
endlineno=alias_endlineno,
398398
parent=obj, # type: ignore[arg-type]
399399
)
400-
# Special case: we avoid overwriting a submodule with an alias pointing to it.
401-
# Griffe suffers from this design flaw where an object cannot store both
400+
# Special case: we avoid overwriting a submodule with an alias.
401+
# Griffe suffers from this limitation where an object cannot store both
402402
# a submodule and a member of the same name, while this poses (almost) no issue in Python.
403-
# We at least prevent this case where a submodule is overwritten by an imported version of itself.
403+
# We always give precedence to the submodule.
404+
# See the "avoid member-submodule name shadowing" section in the "Python code" docs page.
404405
if already_present:
405406
prev_member = obj.get_member(new_member.name)
406407
with suppress(AliasResolutionError, CyclicAliasError):
407408
if prev_member.is_module:
408-
if prev_member.is_alias:
409-
prev_member = prev_member.final_target
410-
if alias.final_target is prev_member:
411-
# Alias named after the module it targets: skip to avoid cyclic aliases.
412-
continue
409+
continue
413410

414411
# Everything went right (supposedly), we add the alias as a member of the current object.
415412
obj.set_member(new_member.name, alias)

tests/test_loader.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,3 +489,22 @@ def test_not_calling_package_loaded_hook_on_something_else_than_package() -> Non
489489
alias: Alias = loader.load("pkg.L") # type: ignore[assignment]
490490
assert alias.is_alias
491491
assert not alias.resolved
492+
493+
494+
def test_not_overriding_module_with_alias_from_wildcard_import() -> None:
495+
"""Do not override a submodule with an imported object with the same name."""
496+
with temporary_visited_package(
497+
"pkg",
498+
{
499+
"__init__.py": "",
500+
"a/__init__.py": "from .m import *",
501+
"a/m.py": "def m(): pass",
502+
"b/__init__.py": "from .m import *",
503+
"b/m.py": "from pkg.a.m import m",
504+
},
505+
resolve_aliases=True,
506+
) as pkg:
507+
assert pkg["a.m"].is_module
508+
assert pkg["a.m.m"].is_function
509+
assert pkg["b.m"].is_module
510+
assert pkg["b.m.m"].is_alias

0 commit comments

Comments
 (0)