fix(loaders): render clean signatures, drop unresolvable aliases from models

- Stringify Object.signature() instead of str()-ing the bound method,
  which produced "<bound method Class.signature of ...>" reprs
- Skip alias members that cannot resolve (stdlib/third-party imports)
  while preserving resolvable package re-exports; return None for empty
  signatures (classes without __init__ args)
- Add MCP renderer regression tests for signature cleanliness, alias
  filtering, and package re-export preservation
This commit is contained in:
2026-09-15 16:53:08 +05:30
parent cd031ed5de
commit 4ec67c86c6
51 changed files with 941 additions and 2287 deletions

View File

@@ -12,6 +12,8 @@ into doc-forge documentation models.
Notes:
- All analysis is static; analyzed modules are never executed.
- Private members (names starting with `_`) are skipped during conversion.
- Imported aliases that cannot be resolved (stdlib/third-party names) are
skipped; aliases that resolve within the documented project are kept.
---
"""
@@ -232,6 +234,8 @@ class GriffeLoader:
for name, member in obj.members.items():
if name.startswith("_"):
continue
if not self._is_resolvable_alias(member):
continue
module.add_object(self._convert_object(member))
@@ -268,6 +272,8 @@ class GriffeLoader:
for name, member in obj.members.items():
if name.startswith("_"):
continue
if not self._is_resolvable_alias(member):
continue
doc_obj.add_member(self._convert_object(member))
except AliasResolutionError:
pass
@@ -299,17 +305,53 @@ class GriffeLoader:
"""
Safely extract the signature of a Griffe object.
Griffe exposes signatures as callables (``Object.signature``); the
method is invoked and stringified to produce a clean, stable signature.
Aliases that point outside the loaded project fail resolution and are
reported as `None`.
Args:
obj (Object):
Griffe object to inspect.
Returns:
str | None:
String representation of the object's signature if available, otherwise `None`.
Clean string representation of the object's signature if
available, otherwise `None`.
"""
try:
if hasattr(obj, "signature") and obj.signature:
return str(obj.signature)
except AliasResolutionError:
signature = getattr(obj, "signature", None)
if signature is None:
return None
if callable(signature):
signature = signature()
if not signature:
return None
return str(signature).strip() or None
except Exception:
return None
return None
def _is_resolvable_alias(self, obj: Object) -> bool:
"""
Report whether an object is a resolvable part of the documented API.
Aliases that cannot be resolved (stdlib or third-party imports) are
treated as noise and excluded from the model. Non-alias objects are
always resolvable.
Args:
obj (Object):
Griffe object to test.
Returns:
bool:
True if the object should be kept, False if it is an
unresolvable alias.
"""
if obj.kind.value != "alias":
return True
try:
_ = obj.canonical_path
except AliasResolutionError:
return False
return True