-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
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
Autosummary additional links for inherited methods and attributes #11712
base: master
Are you sure you want to change the base?
Changes from all commits
e62a37c
4e71d5b
6a2b58a
2f4d125
bee1d6b
4302a97
092f706
063d33f
b8dff3f
697be33
76c0194
192ad95
e9edee7
b7a0049
0901005
de412e0
4f3aa01
8e62aed
71c1729
89adbd9
2d56014
bff342f
13895a9
7f06fcb
8ea3cba
219cc23
3199fe0
9884579
5421b26
a34b648
6f3fbc3
9037a22
224076b
065a1a0
58ff1e3
3c9b0bb
336d3e3
ceb2b99
479ff7d
4987598
a3adf19
d6abcbe
73827e2
242f21a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -346,15 +346,44 @@ def generate_autosummary_content( | |
ns['modules'] = imported_modules + modules | ||
ns['all_modules'] = all_imported_modules + all_modules | ||
elif doc.objtype == 'class': | ||
ns['members'] = dir(obj) | ||
ns['inherited_members'] = set(dir(obj)) - set(obj.__dict__.keys()) | ||
ns['members'] = list(_get_class_members(obj)) | ||
obj_classinfo = _get_class_members(obj, False) | ||
obj_local = [j for j, k in obj_classinfo.items() if k == obj] | ||
|
||
ns['inherited_members'] = [ | ||
x for x in dict.fromkeys(ns['members']) if x not in obj_local | ||
] | ||
|
||
ns['methods'], ns['all_methods'] = _get_members( | ||
doc, app, obj, {'method'}, include_public={'__init__'} | ||
) | ||
ns['attributes'], ns['all_attributes'] = _get_members( | ||
doc, app, obj, {'attribute', 'property'} | ||
) | ||
|
||
method_string = [m.split('.')[-1] for m in ns['methods']] | ||
attr_string = [m.split('.')[-1] for m in ns['attributes']] | ||
|
||
# Find the first link for this attribute in higher classes | ||
ns['inherited_qualnames'] = [] | ||
ns['inherited_methods'] = [] | ||
ns['inherited_attributes'] = [] | ||
|
||
inherited_set = set(ns['inherited_members']) | ||
for cl in obj.__mro__: | ||
classinfo = _get_class_members(cl, False) | ||
local = [j for j, k in classinfo.items() if k == cl] | ||
|
||
for i in local: | ||
if i in inherited_set: | ||
cl_str = f'{cl.__module__}.{cl.__name__}.{i}' | ||
ns['inherited_qualnames'].append(cl_str) | ||
if i in method_string: | ||
ns['inherited_methods'].append(cl_str) | ||
elif i in attr_string: | ||
ns['inherited_attributes'].append(cl_str) | ||
inherited_set.remove(i) | ||
|
||
if modname is None or qualname is None: | ||
modname, qualname = _split_full_qualified_name(name) | ||
|
||
|
@@ -398,9 +427,12 @@ def _skip_member(app: Sphinx, obj: Any, name: str, objtype: str) -> bool: | |
return False | ||
|
||
|
||
def _get_class_members(obj: Any) -> dict[str, Any]: | ||
def _get_class_members(obj: Any, return_object: bool = True) -> dict[str, Any]: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would actually flip the boolean and use And whether we change the boolean or not, I think it would be good to make it keyword-only just so that we know what we do when we call the function (we don't need to go to its implementation). |
||
members = sphinx.ext.autodoc.importer.get_class_members(obj, None, safe_getattr) | ||
return {name: member.object for name, member in members.items()} | ||
if return_object: | ||
return {name: member.object for name, member in members.items()} | ||
else: | ||
return {name: member.class_ for name, member in members.items()} | ||
|
||
|
||
def _get_module_members(app: Sphinx, obj: Any) -> dict[str, Any]: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
import sys | ||
|
||
class Parent: | ||
|
||
relation = "Family" | ||
|
||
def __init__(self): | ||
self.name = "Andrew" | ||
self.age = 35 | ||
|
||
def get_name(self): | ||
return self.name | ||
|
||
def get_age(self): | ||
return f"Parent class - {self.age}" | ||
|
||
# Test mangled name behaviour | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we have a normal private attribute/method (no mangled, just simple underscore)? |
||
__get_age = get_age # private copy of original get_age method | ||
__private_parent_attribute = "Private_parent" | ||
|
||
|
||
class Child(Parent): | ||
|
||
def __init__(self): | ||
self.name = "Bobby" | ||
self.age = 15 | ||
|
||
def get_name(self): | ||
return self.name | ||
|
||
def get_age(self): | ||
return f"Child class - {self.age}" | ||
|
||
@staticmethod | ||
def addition(a, b): | ||
return a + b | ||
|
||
|
||
class Baby(Child): | ||
|
||
# Test a private attribute | ||
__private_baby_name = "Private1234" | ||
|
||
def __init__(self): | ||
self.name = "Charlie" | ||
self.age = 2 | ||
|
||
def get_age(self): | ||
return f"Baby class - {self.age}" | ||
|
||
class BabyInnerClass(): | ||
baby_inner_attribute = "An attribute of an inner class" | ||
|
||
|
||
class Job: | ||
|
||
def __init__(self): | ||
self.salary = 10 | ||
|
||
def get_salary(self): | ||
return self.salary | ||
|
||
|
||
class Architect(Job): | ||
|
||
def __init__(self): | ||
self.salary = 20 | ||
|
||
def get_age(self): | ||
return f"Architect age - {self.age}" | ||
|
||
# Test a class that inherits multiple classes | ||
class Jerry(Architect, Child): | ||
|
||
def __init__(self): | ||
self.name = "Jerry" | ||
self.age = 25 | ||
|
||
|
||
__all__ = ["Parent", "Child", "Baby", "Job", "Architect", "Jerry"] | ||
|
||
# The following is used to process the expected builtin members for different | ||
# versions of Python. The base list is for v3.11 (the latest testing when added) | ||
# and new builtin attributes/methods in later versions can be appended below. | ||
Comment on lines
+82
to
+84
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we have those values declared in the test instead? the test roots should be only contains the code we "test" (so this file should be like a real one on which you run autodoc). |
||
|
||
members_3_11 = ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', | ||
'__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', | ||
'__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', | ||
'__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', | ||
'__sizeof__', '__str__', '__subclasshook__', '__weakref__'] | ||
|
||
attr_3_11 = ['__annotations__', '__dict__', '__doc__', '__module__', '__weakref__'] | ||
|
||
|
||
def concat_and_sort(list1, list2): | ||
return sorted(list1 + list2) | ||
|
||
|
||
built_in_attr = attr_3_11 | ||
built_in_members = members_3_11 | ||
# The second test has an extra builtin member | ||
built_in_members2 = ['__annotations__', *members_3_11] | ||
|
||
if sys.version_info[:2] >= (3, 13): | ||
add_3_13 = ['__firstlineno__', '__static_attributes__'] | ||
built_in_attr = concat_and_sort(built_in_attr, add_3_13) | ||
built_in_members = concat_and_sort(built_in_members, add_3_13) | ||
built_in_members2 = concat_and_sort(built_in_members2, add_3_13) | ||
|
||
if sys.version_info[:2] >= (3, 14): | ||
built_in_attr = concat_and_sort(built_in_attr, ['__annotate__']) | ||
built_in_members2 = concat_and_sort(built_in_members2, ['__annotate__']) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just reconfirm, but this was already the case that it returned private attributes right? since we are doing
members = dir(self)
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, the existing attributes members and private members already included private ones. Therefore there shouldn't be any change here, just clarifying this in the docs.