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
|
parameter having ``inspect._empty`` as its default value
|
||||||
* #7901: autodoc: type annotations for overloaded functions are not resolved
|
* #7901: autodoc: type annotations for overloaded functions are not resolved
|
||||||
* #904: autodoc: An instance attribute cause a crash of autofunction directive
|
* #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
|
* #7839: autosummary: cannot handle umlauts in function names
|
||||||
* #7865: autosummary: Failed to extract summary line when abbreviations found
|
* #7865: autosummary: Failed to extract summary line when abbreviations found
|
||||||
* #7866: autosummary: Failed to extract correct summary line when docstring
|
* #7866: autosummary: Failed to extract correct summary line when docstring
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
import importlib
|
import importlib
|
||||||
import traceback
|
import traceback
|
||||||
import warnings
|
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.deprecation import RemovedInSphinx40Warning, deprecated_alias
|
||||||
from sphinx.pycode import ModuleAnalyzer
|
from sphinx.pycode import ModuleAnalyzer
|
||||||
@ -21,6 +21,36 @@ from sphinx.util.inspect import isclass, isenumclass, safe_getattr
|
|||||||
logger = logging.getLogger(__name__)
|
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:
|
def import_module(modname: str, warningiserror: bool = False) -> Any:
|
||||||
"""
|
"""
|
||||||
Call importlib.import_module(modname), convert exceptions to ImportError
|
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:
|
for attrname in objpath:
|
||||||
parent = obj
|
parent = obj
|
||||||
logger.debug('[autodoc] getattr(_, %r)', attrname)
|
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)
|
logger.debug('[autodoc] => %r', obj)
|
||||||
object_name = attrname
|
object_name = attrname
|
||||||
return [module, parent, object_name, obj]
|
return [module, parent, object_name, obj]
|
||||||
@ -161,7 +192,8 @@ def get_object_members(subject: Any, objpath: List[str], attrgetter: Callable,
|
|||||||
try:
|
try:
|
||||||
value = attrgetter(subject, name)
|
value = attrgetter(subject, name)
|
||||||
directly_defined = name in obj_dict
|
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)
|
members[name] = Attribute(name, directly_defined, value)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
continue
|
continue
|
||||||
@ -169,7 +201,8 @@ def get_object_members(subject: Any, objpath: List[str], attrgetter: Callable,
|
|||||||
# annotation only member (ex. attr: int)
|
# annotation only member (ex. attr: int)
|
||||||
if hasattr(subject, '__annotations__') and isinstance(subject.__annotations__, Mapping):
|
if hasattr(subject, '__annotations__') and isinstance(subject.__annotations__, Mapping):
|
||||||
for name in subject.__annotations__:
|
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)
|
members[name] = Attribute(name, True, INSTANCEATTR)
|
||||||
|
|
||||||
if analyzer:
|
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.',
|
' 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