Skip to content

Commit

Permalink
feat: Support merging stubs on wildcard imported objects
Browse files Browse the repository at this point in the history
Issue #116: #116
  • Loading branch information
pawamoy committed Nov 30, 2022
1 parent 9a2a711 commit 0ed9c36
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 17 deletions.
11 changes: 5 additions & 6 deletions src/griffe/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,8 @@ def load_module( # noqa: WPS231
logger.debug(f"Inspecting {module}")
module_name = module # type: ignore[assignment]
top_module = self._inspect_module(module) # type: ignore[arg-type]
return self._store_and_return(module_name, top_module)
self.modules_collection[top_module.path] = top_module
return self.modules_collection[module_name] # type: ignore[index]
raise LoadingError("Cannot load builtin module without inspection")
try: # noqa: WPS503
module_name, package = self.finder.find_spec(module, try_relative_path)
Expand All @@ -151,7 +152,7 @@ def load_module( # noqa: WPS231
except LoadingError as error: # noqa: WPS440
logger.error(str(error))
raise
return self._store_and_return(module_name, top_module)
return self.modules_collection[module_name] # type: ignore[index]

def resolve_aliases( # noqa: WPS231
self,
Expand Down Expand Up @@ -370,15 +371,13 @@ def stats(self) -> dict:
"""
return {**stats(self), **self._time_stats}

def _store_and_return(self, name: str, module: Module) -> Module:
self.modules_collection[module.path] = module
return self.modules_collection[name] # type: ignore[index]

def _load_package(self, package: Package | NamespacePackage, submodules: bool = True) -> Module:
top_module = self._load_module(package.name, package.path, submodules=submodules)
self.modules_collection[top_module.path] = top_module
if isinstance(package, NamespacePackage):
return top_module
if package.stubs:
self.expand_wildcards(top_module)
stubs = self._load_module(package.name, package.stubs, submodules=False)
return merge_stubs(top_module, stubs)
return top_module
Expand Down
20 changes: 10 additions & 10 deletions src/griffe/merger.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from contextlib import suppress
from typing import TYPE_CHECKING

from griffe.exceptions import AliasResolutionError, CyclicAliasError
from griffe.logger import get_logger

if TYPE_CHECKING:
Expand Down Expand Up @@ -55,16 +56,15 @@ def _merge_stubs_members(obj: Module | Class, stubs: Module | Class) -> None: #
for member_name, stub_member in stubs.members.items():
if member_name in obj.members:
obj_member = obj[member_name]
if obj_member.is_alias:
continue
if obj_member.kind is not stub_member.kind:
logger.debug(f"Cannot merge stubs of kind {stub_member.kind} into object of kind {obj_member.kind}")
elif obj_member.is_class:
_merge_class_stubs(obj_member, stub_member) # type: ignore[arg-type]
elif obj_member.is_function:
_merge_function_stubs(obj_member, stub_member) # type: ignore[arg-type]
elif obj_member.is_attribute:
_merge_attribute_stubs(obj_member, stub_member) # type: ignore[arg-type]
with suppress(AliasResolutionError, CyclicAliasError):
if obj_member.kind is not stub_member.kind:
logger.debug(f"Cannot merge stubs of kind {stub_member.kind} into object of kind {obj_member.kind}")
elif obj_member.is_class:
_merge_class_stubs(obj_member, stub_member) # type: ignore[arg-type]
elif obj_member.is_function:
_merge_function_stubs(obj_member, stub_member) # type: ignore[arg-type]
elif obj_member.is_attribute:
_merge_attribute_stubs(obj_member, stub_member) # type: ignore[arg-type]
else:
stub_member.runtime = False
obj[member_name] = stub_member
Expand Down
2 changes: 1 addition & 1 deletion src/griffe/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def __setitem__(self, key: str | Sequence[str], value) -> None: # noqa: WPS231
# when reassigning a module to an existing one,
# try to merge them as one regular and one stubs module
# (implicit support for .pyi modules)
if member.is_module:
if member.is_module and not (member.is_namespace_package or member.is_namespace_subpackage):
with suppress(AliasResolutionError, CyclicAliasError):
if value.is_module:
logger.debug(f"Trying to merge {member.filepath} and {value.filepath}")
Expand Down
30 changes: 30 additions & 0 deletions tests/test_merger.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,33 @@ def f() -> pathlib.Path: ...
)
loader = GriffeLoader(search_paths=[tmp_package.tmpdir])
loader.load_module(tmp_package.name)


def test_merge_stubs_on_wildcard_imported_objects():
"""Assert that stubs can be merged on wildcard imported objects."""
with temporary_pypackage("package", ["mod.py", "__init__.pyi"]) as tmp_package:
tmp_package.path.joinpath("mod.py").write_text(
dedent(
"""
class A:
def hello(value: int | str) -> int | str:
return value
"""
)
)
tmp_package.path.joinpath("__init__.py").write_text("from .mod import *")
tmp_package.path.joinpath("__init__.pyi").write_text(
dedent(
"""
from typing import overload
class A:
@overload
def hello(value: int) -> int: ...
@overload
def hello(value: str) -> str: ...
"""
)
)
loader = GriffeLoader(search_paths=[tmp_package.tmpdir])
module = loader.load_module(tmp_package.name)
assert module["A.hello"].overloads

0 comments on commit 0ed9c36

Please sign in to comment.