mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Fix #1362: autodoc: Support private class attributes
So far, autodoc treats a "private" class attribute as a mere attribute. But its name is mangled by python interpreter. This make it unmangled name to be documented expectedly.
This commit is contained in:
parent
8c8943f6c0
commit
488a173904
1
CHANGES
1
CHANGES
@ -44,6 +44,7 @@ Bugs fixed
|
||||
parameter having ``inspect._empty`` as its default value
|
||||
* #7901: autodoc: type annotations for overloaded functions are not resolved
|
||||
* #904: autodoc: An instance attribute cause a crash of autofunction directive
|
||||
* #1362: autodoc: ``private-members`` option does not work for class attributes
|
||||
* #7839: autosummary: cannot handle umlauts in function names
|
||||
* #7865: autosummary: Failed to extract summary line when abbreviations found
|
||||
* #7866: autosummary: Failed to extract correct summary line when docstring
|
||||
|
@ -11,7 +11,7 @@
|
||||
import importlib
|
||||
import traceback
|
||||
import warnings
|
||||
from typing import Any, Callable, Dict, List, Mapping, NamedTuple, Tuple
|
||||
from typing import Any, Callable, Dict, List, Mapping, NamedTuple, Optional, Tuple
|
||||
|
||||
from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias
|
||||
from sphinx.pycode import ModuleAnalyzer
|
||||
@ -21,6 +21,36 @@ from sphinx.util.inspect import isclass, isenumclass, safe_getattr
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def mangle(subject: Any, name: str) -> str:
|
||||
"""mangle the given name."""
|
||||
try:
|
||||
if isclass(subject) and name.startswith('__') and not name.endswith('__'):
|
||||
return "_%s%s" % (subject.__name__, name)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
return name
|
||||
|
||||
|
||||
def unmangle(subject: Any, name: str) -> Optional[str]:
|
||||
"""unmangle the given name."""
|
||||
try:
|
||||
if isclass(subject) and not name.endswith('__'):
|
||||
prefix = "_%s__" % subject.__name__
|
||||
if name.startswith(prefix):
|
||||
return name.replace(prefix, "__", 1)
|
||||
else:
|
||||
for cls in subject.__mro__:
|
||||
prefix = "_%s__" % cls.__name__
|
||||
if name.startswith(prefix):
|
||||
# mangled attribute defined in parent class
|
||||
return None
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
return name
|
||||
|
||||
|
||||
def import_module(modname: str, warningiserror: bool = False) -> Any:
|
||||
"""
|
||||
Call importlib.import_module(modname), convert exceptions to ImportError
|
||||
@ -68,7 +98,8 @@ def import_object(modname: str, objpath: List[str], objtype: str = '',
|
||||
for attrname in objpath:
|
||||
parent = obj
|
||||
logger.debug('[autodoc] getattr(_, %r)', attrname)
|
||||
obj = attrgetter(obj, attrname)
|
||||
mangled_name = mangle(obj, attrname)
|
||||
obj = attrgetter(obj, mangled_name)
|
||||
logger.debug('[autodoc] => %r', obj)
|
||||
object_name = attrname
|
||||
return [module, parent, object_name, obj]
|
||||
@ -161,7 +192,8 @@ def get_object_members(subject: Any, objpath: List[str], attrgetter: Callable,
|
||||
try:
|
||||
value = attrgetter(subject, name)
|
||||
directly_defined = name in obj_dict
|
||||
if name not in members:
|
||||
name = unmangle(subject, name)
|
||||
if name and name not in members:
|
||||
members[name] = Attribute(name, directly_defined, value)
|
||||
except AttributeError:
|
||||
continue
|
||||
@ -169,7 +201,8 @@ def get_object_members(subject: Any, objpath: List[str], attrgetter: Callable,
|
||||
# annotation only member (ex. attr: int)
|
||||
if hasattr(subject, '__annotations__') and isinstance(subject.__annotations__, Mapping):
|
||||
for name in subject.__annotations__:
|
||||
if name not in members:
|
||||
name = unmangle(subject, name)
|
||||
if name and name not in members:
|
||||
members[name] = Attribute(name, True, INSTANCEATTR)
|
||||
|
||||
if analyzer:
|
||||
|
11
tests/roots/test-ext-autodoc/target/name_mangling.py
Normal file
11
tests/roots/test-ext-autodoc/target/name_mangling.py
Normal file
@ -0,0 +1,11 @@
|
||||
class Foo:
|
||||
#: name of Foo
|
||||
__name = None
|
||||
__age = None
|
||||
|
||||
|
||||
class Bar(Foo):
|
||||
__address = None
|
||||
|
||||
#: a member having mangled-like name
|
||||
_Baz__email = None
|
@ -1973,3 +1973,48 @@ def test_name_conflict(app):
|
||||
' docstring of target.name_conflict.foo::bar.',
|
||||
'',
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.sphinx('html', testroot='ext-autodoc')
|
||||
def test_name_mangling(app):
|
||||
options = {"members": None,
|
||||
"undoc-members": None,
|
||||
"private-members": None}
|
||||
actual = do_autodoc(app, 'module', 'target.name_mangling', options)
|
||||
assert list(actual) == [
|
||||
'',
|
||||
'.. py:module:: target.name_mangling',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: Bar()',
|
||||
' :module: target.name_mangling',
|
||||
'',
|
||||
'',
|
||||
' .. py:attribute:: Bar._Baz__email',
|
||||
' :module: target.name_mangling',
|
||||
' :value: None',
|
||||
'',
|
||||
' a member having mangled-like name',
|
||||
'',
|
||||
'',
|
||||
' .. py:attribute:: Bar.__address',
|
||||
' :module: target.name_mangling',
|
||||
' :value: None',
|
||||
'',
|
||||
'',
|
||||
'.. py:class:: Foo()',
|
||||
' :module: target.name_mangling',
|
||||
'',
|
||||
'',
|
||||
' .. py:attribute:: Foo.__age',
|
||||
' :module: target.name_mangling',
|
||||
' :value: None',
|
||||
'',
|
||||
'',
|
||||
' .. py:attribute:: Foo.__name',
|
||||
' :module: target.name_mangling',
|
||||
' :value: None',
|
||||
'',
|
||||
' name of Foo',
|
||||
'',
|
||||
]
|
||||
|
Loading…
Reference in New Issue
Block a user