Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: get_object dynamic supports methods set by assignments #305

Merged
merged 1 commit into from
Nov 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 44 additions & 14 deletions quartodoc/autosummary.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,23 +283,12 @@ def dynamic_alias(

canonical_path = None
crnt_part = mod
prev_part = _NoParent
for ii, attr_name in enumerate(splits):
# fetch attribute ----
try:
prev_part = crnt_part
crnt_part = getattr(crnt_part, attr_name)
if not isinstance(crnt_part, ModuleType) and not canonical_path:
if inspect.isclass(crnt_part) or inspect.isfunction(crnt_part):
_mod = getattr(crnt_part, "__module__", None)

if _mod is None:
canonical_path = path
else:
canonical_path = _mod + ":" + ".".join(splits[ii:])
else:
canonical_path = path
elif isinstance(crnt_part, ModuleType) and ii == (len(splits) - 1):
# final object is module
canonical_path = crnt_part.__name__

except AttributeError:
# Fetching the attribute can fail if it is purely a type hint,
# and has no value. This can be an issue if you have added a
Expand All @@ -319,6 +308,22 @@ def dynamic_alias(
f"No attribute named `{attr_name}` in the path `{path}`."
)

# update canonical_path ----
# this is our belief about where the final object lives (ie. its submodule)
try:
_qualname = ".".join(splits[ii:])
_is_final = ii == (len(splits) - 1)
new_canonical_path = _canonical_path(
crnt_part, _qualname, _is_final, prev_part
)
except AttributeError:
new_canonical_path = None

if new_canonical_path is not None:
# Note that previously we kept the first valid canonical path,
# but now keep the last.
canonical_path = new_canonical_path

if canonical_path is None:
raise ValueError(f"Cannot find canonical path for `{path}`")

Expand Down Expand Up @@ -351,6 +356,31 @@ def dynamic_alias(
return dc.Alias(attr_name, obj, parent=parent)


class _NoParent:
"""Represent the absence of a parent object."""


def _canonical_path(crnt_part: object, qualname: str, is_final=False, parent=_NoParent):
if not isinstance(crnt_part, ModuleType):
# classes and functions ----
if inspect.isclass(crnt_part) or inspect.isfunction(crnt_part):
_mod = getattr(crnt_part, "__module__", None)

if _mod is None:
return None
else:
# we can use the object's actual __qualname__ here, which correctly
# reports the path for e.g. methods on a class
return _mod + ":" + crnt_part.__qualname__
elif parent is not _NoParent and isinstance(parent, ModuleType):
return parent.__name__ + ":" + qualname
else:
return None
elif isinstance(crnt_part, ModuleType) and is_final:
# final object is module
return crnt_part.__name__


def _is_valueless(obj: dc.Object):
if isinstance(obj, dc.Attribute):
if "class-attribute" in obj.labels and obj.value is None:
Expand Down
4 changes: 4 additions & 0 deletions quartodoc/tests/example_alias_target.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,7 @@

def alias_target():
"""An alias target"""


class AClass:
some_method = nested_alias_target
15 changes: 15 additions & 0 deletions quartodoc/tests/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,18 @@ def test_get_object_dynamic_module_attr_class_instance():

assert obj.path == "quartodoc.tests.example_dynamic.some_instance"
assert obj.docstring.value == "Dynamic instance doc"


def test_get_object_dynamic_class_method_assigned():
# method is assigned to class using
# some_method = some_function
obj = get_object(
"quartodoc.tests.example_alias_target:AClass.some_method", dynamic=True
)

assert isinstance(obj, dc.Alias)
assert isinstance(obj.target, dc.Function)
assert (
obj.target.path
== "quartodoc.tests.example_alias_target__nested.nested_alias_target"
)
Loading