:py:`CLASS_INDEX_EXPAND_INNER` Whether to expand inner classes in the
class index. If not set, :py:`False` is
used.
-:py:`PYBIND11_COMPATIBILITY` Enable some additional tricks for better
+:py:`PYBIND11_COMPATIBILITY: bool` Enable some additional tricks for better
compatibility with pybind11. If not set,
:py:`False` is used. See
`pybind11 compatibility`_ for more
information.
+:py:`ATTRS_COMPATIBILITY: bool` Enable some additional tricks for better
+ compatibility with attrs. If not set,
+ :py:`False` is used. See
+ `attrs compatibility`_ for more
+ information.
:py:`SEARCH_DISABLED: bool` Disable search functionality. If this
option is set, no search data is compiled
and the rendered HTML does not contain
:gh:`pybind/pybind11#1160`). Support for this feature is not done on the
script side yet.
+`attrs compatibility`_
+======================
+
+If a codebase is using the `attrs <https://www.attrs.org/>`_ package and the
+:py:`ATTRS_COMPATIBILITY` option is enabled, the script is able to extract the
+(otherwise inaccessible by normal means) information about attributes defined
+using :py:`attr.ib()` or via the :py:`@attr.s(auto_attribs=True)` decorator.
+Note that attributes of classes using :py:`@attr.s(slots=True)` are visible
+even without the compatibility enabled.
+
+In all cases, there's no possibility of adding in-source docstrings for any of
+these and you need to supply the documentation with the :rst:`.. py:property::`
+directive as described in `External documentation content`_.
+
+Additionally, various dunder methods that say just "*Automatically created by
+attrs.*" in their docstring are implicitly hidden from the output if this
+option is enabled. In order to show them again, override the docstring to
+something meaningful.
+
`Command-line options`_
=======================
'CLASS_INDEX_EXPAND_INNER': False,
'PYBIND11_COMPATIBILITY': False,
+ 'ATTRS_COMPATIBILITY': False,
'SEARCH_DISABLED': False,
'SEARCH_DOWNLOAD_BINARY': False,
('__weakref__', "list of weak references to the object (if defined)")
])
+_automatically_created_by_attrs = """
+ Automatically created by attrs.
+ """
+_automatically_created_by_attrs_even_more_indented = """
+ Automatically created by attrs.
+ """
+_filtered_attrs_functions = set([
+ ('__ne__', """
+ Check equality and either forward a NotImplemented or return the result
+ negated.
+ """),
+ ('__lt__', _automatically_created_by_attrs),
+ ('__le__', _automatically_created_by_attrs),
+ ('__gt__', _automatically_created_by_attrs),
+ ('__ge__', _automatically_created_by_attrs),
+ ('__repr__', _automatically_created_by_attrs),
+ ('__getstate__', _automatically_created_by_attrs_even_more_indented),
+ ('__setstate__', _automatically_created_by_attrs_even_more_indented)
+])
+
def crawl_enum(state: State, path: List[str], enum_, parent_url):
enum_entry = Empty()
enum_entry.type = EntryType.ENUM
# Filter out underscored methods (but not dunder methods such
# as __init__)
if name.startswith('_') and not (name.startswith('__') and name.endswith('__')): continue
- # Filter out dunder methods that don't have their own docs
- if name.startswith('__') and (name, object.__doc__) in _filtered_builtin_functions: continue
+ # Filter out dunder methods that ...
+ if name.startswith('__'):
+ # ... don't have their own docs
+ if (name, object.__doc__) in _filtered_builtin_functions: continue
+ # ... or are auto-generated by attrs
+ if state.config['ATTRS_COMPATIBILITY']:
+ if (name, object.__doc__) in _filtered_attrs_functions: continue
+ # Unfortunately the __eq__ doesn't have a docstring,
+ # try to match it just from the param names
+ if name == '__eq__' and object.__doc__ is None:
+ try:
+ signature = inspect.signature(object)
+ if 'self' in signature.parameters and 'other' in signature.parameters:
+ continue
+ except ValueError: # pragma: no cover
+ pass
elif type == EntryType.PROPERTY:
if (name, object.__doc__) in _filtered_builtin_properties: continue
if name.startswith('_'): continue # TODO: are there any dunder props?
class_entry.members += [name]
+ # If attrs compatibility is enabled, look for more properties in hidden
+ # places.
+ if state.config['ATTRS_COMPATIBILITY'] and hasattr(class_, '__attrs_attrs__'):
+ for attrib in class_.__attrs_attrs__:
+ if attrib.name.startswith('_'): continue
+
+ # In some cases, the attribute can be present also among class
+ # data (for example when using slots). Prefer the info provided by
+ # attrs (instead of `continue`) as it can provide type annotation
+ # also when the native annotation isn't used
+ if attrib.name not in class_entry.members:
+ class_entry.members += [attrib.name]
+
+ subpath = path + [attrib.name]
+
+ entry = Empty()
+ entry.type = EntryType.PROPERTY
+ entry.object = attrib
+ entry.path = subpath
+ entry.url = '{}#{}'.format(class_entry.url, state.config['ID_FORMATTER'](EntryType.PROPERTY, subpath[-1:]))
+ state.name_map['.'.join(subpath)] = entry
+
# Add itself to the name map
state.name_map['.'.join(path)] = class_entry
return overloads
def extract_property_doc(state: State, parent, entry: Empty):
- assert inspect.isdatadescriptor(entry.object)
-
out = Empty()
out.name = entry.path[-1]
out.id = state.config['ID_FORMATTER'](EntryType.PROPERTY, entry.path[-1:])
+ # If this is a property hammered out of attrs, we parse it differently
+ if state.config['ATTRS_COMPATIBILITY'] and type(entry.object).__name__ == 'Attribute' and type(entry.object).__module__ == 'attr._make':
+ # TODO: are there readonly attrs?
+ out.is_gettable = True
+ out.is_settable = True
+ out.is_deletable = True
+ out.type, out.type_link = extract_annotation(state, entry.path, entry.object.type)
+
+ # Call all scope enter hooks before rendering the docs
+ for hook in state.hooks_pre_scope:
+ hook(type=entry.type, path=entry.path)
+
+ # Unfortunately we can't get any docstring for these
+ out.summary, out.content = extract_docs(state, state.property_docs, entry.type, entry.path, '')
+
+ # Call all scope exit hooks after rendering the docs
+ for hook in state.hooks_post_scope:
+ hook(type=entry.type, path=entry.path)
+
+ out.has_details = bool(out.content)
+
+ return out
+
+ # Otherwise we expect a sane thing
+ assert inspect.isdatadescriptor(entry.object)
+
# If this is a slot, there won't be any fget / fset / fdel. Assume they're
# gettable and settable (couldn't find any way to make them *inspectably*
# readonly, all solutions involved throwing from __setattr__()) and
.. py:data:: inspect_attrs.MySlotClass.annotated
:summary: This is a float slot.
+.. py:data:: inspect_attrs.MyClass.plain_data
+ :summary: This is plain data, not handled by attrs
+
.. py:function:: inspect_attrs.MyClass.__init__
:summary: External docs for the init
:param annotated: The first argument
:param unannotated: This gets the default of four
:param complex_annotation: Yes, a list
+ :param complex_annotation_in_attr: Annotated using ``attr.ib(type=)``,
+ should be shown as well
:param hidden_property: Interesting, but I don't care.
The :p:`hidden_property` isn't shown in the output as it's prefixed with
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8" />
+ <title>inspect_attrs.MyClass | My Python Project</title>
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+ <link rel="stylesheet" href="m-dark+documentation.compiled.css" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+ <div class="m-container">
+ <div class="m-row">
+ <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Python Project</a>
+ </div>
+ </div>
+</nav></header>
+<main><article>
+ <div class="m-container m-container-inflatable">
+ <div class="m-row">
+ <div class="m-col-l-10 m-push-l-1">
+ <h1>
+ <span class="m-breadcrumb"><a href="inspect_attrs.html">inspect_attrs</a>.<wbr/></span>MyClass <span class="m-thin">class</span>
+ </h1>
+ <p>A class with attr-defined properties</p>
+ <div class="m-block m-default">
+ <h3>Contents</h3>
+ <ul>
+ <li>
+ Reference
+ <ul>
+ <li><a href="#dunder-methods">Special methods</a></li>
+ <li><a href="#properties">Properties</a></li>
+ <li><a href="#data">Data</a></li>
+ </ul>
+ </li>
+ </ul>
+ </div>
+ <section id="dunder-methods">
+ <h2><a href="#dunder-methods">Special methods</a></h2>
+ <dl class="m-doc">
+ <dt>
+ <span class="m-doc-wrap-bumper">def <a href="#__init__" class="m-doc">__init__</a>(</span><span class="m-doc-wrap">self,
+ annotated: float,
+ unannotated = 4,
+ complex_annotation: typing.List[typing.Tuple[int, float]] = [],
+ complex_annotation_in_attr: typing.List[typing.Tuple[int, float]] = [],
+ hidden_property: float = 3) -> None</span>
+ </dt>
+ <dd>External docs for the init</dd>
+ </dl>
+ </section>
+ <section id="properties">
+ <h2><a href="#properties">Properties</a></h2>
+ <dl class="m-doc">
+ <dt id="annotated">
+ <a href="#annotated" class="m-doc-self">annotated</a>: float <span class="m-label m-flat m-success">get set del</span>
+ </dt>
+ <dd></dd>
+ <dt id="unannotated">
+ <a href="#unannotated" class="m-doc-self">unannotated</a> <span class="m-label m-flat m-success">get set del</span>
+ </dt>
+ <dd>External docs for this property</dd>
+ <dt id="complex_annotation">
+ <a href="#complex_annotation" class="m-doc-self">complex_annotation</a>: typing.List[typing.Tuple[int, float]] <span class="m-label m-flat m-success">get set del</span>
+ </dt>
+ <dd></dd>
+ <dt id="complex_annotation_in_attr">
+ <a href="#complex_annotation_in_attr" class="m-doc-self">complex_annotation_in_attr</a>: typing.List[typing.Tuple[int, float]] <span class="m-label m-flat m-success">get set del</span>
+ </dt>
+ <dd></dd>
+ </dl>
+ </section>
+ <section id="data">
+ <h2><a href="#data">Data</a></h2>
+ <dl class="m-doc">
+ <dt id="plain_data">
+ <a href="#plain_data" class="m-doc-self">plain_data</a>: float = 35
+ </dt>
+ <dd>This is plain data, not handled by attrs</dd>
+ </dl>
+ </section>
+ <section>
+ <h2>Method documentation</h2>
+ <section class="m-doc-details" id="__init__"><div>
+ <h3>
+ <span class="m-doc-wrap-bumper">def inspect_attrs.<wbr />MyClass.<wbr /></span><span class="m-doc-wrap"><span class="m-doc-wrap-bumper"><a href="#__init__" class="m-doc-self">__init__</a>(</span><span class="m-doc-wrap">self,
+ annotated: float,
+ unannotated = 4,
+ complex_annotation: typing.List[typing.Tuple[int, float]] = [],
+ complex_annotation_in_attr: typing.List[typing.Tuple[int, float]] = [],
+ hidden_property: float = 3) -> None</span></span>
+ </h3>
+ <p>External docs for the init</p>
+ <table class="m-table m-fullwidth m-flat">
+ <thead>
+ <tr><th colspan="2">Parameters</th></tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td style="width: 1%">annotated</td>
+ <td>The first argument</td>
+ </tr>
+ <tr>
+ <td>unannotated</td>
+ <td>This gets the default of four</td>
+ </tr>
+ <tr>
+ <td>complex_annotation</td>
+ <td>Yes, a list</td>
+ </tr>
+ <tr>
+ <td>complex_annotation_in_attr</td>
+ <td>Annotated using <code>attr.ib(type=)</code>,
+should be shown as well</td>
+ </tr>
+ <tr>
+ <td>hidden_property</td>
+ <td>Interesting, but I don't care.</td>
+ </tr>
+ </tbody>
+ </table>
+<p>The <code>hidden_property</code> isn't shown in the output as it's prefixed with
+an underscore.</p>
+ </div></section>
+ </section>
+ </div>
+ </div>
+ </div>
+</article></main>
+</body>
+</html>
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8" />
+ <title>inspect_attrs.MyClassAutoAttribs | My Python Project</title>
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+ <link rel="stylesheet" href="m-dark+documentation.compiled.css" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+ <div class="m-container">
+ <div class="m-row">
+ <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Python Project</a>
+ </div>
+ </div>
+</nav></header>
+<main><article>
+ <div class="m-container m-container-inflatable">
+ <div class="m-row">
+ <div class="m-col-l-10 m-push-l-1">
+ <h1>
+ <span class="m-breadcrumb"><a href="inspect_attrs.html">inspect_attrs</a>.<wbr/></span>MyClassAutoAttribs <span class="m-thin">class</span>
+ </h1>
+ <p>A class with automatic attr-defined properties</p>
+ <div class="m-block m-default">
+ <h3>Contents</h3>
+ <ul>
+ <li>
+ Reference
+ <ul>
+ <li><a href="#dunder-methods">Special methods</a></li>
+ <li><a href="#properties">Properties</a></li>
+ <li><a href="#data">Data</a></li>
+ </ul>
+ </li>
+ </ul>
+ </div>
+ <section id="dunder-methods">
+ <h2><a href="#dunder-methods">Special methods</a></h2>
+ <dl class="m-doc">
+ <dt id="__init__">
+ <span class="m-doc-wrap-bumper">def <a href="#__init__" class="m-doc-self">__init__</a>(</span><span class="m-doc-wrap">self,
+ annotated: float,
+ complex_annotation: typing.List[typing.Tuple[int, float]] = []) -> None</span>
+ </dt>
+ <dd></dd>
+ </dl>
+ </section>
+ <section id="properties">
+ <h2><a href="#properties">Properties</a></h2>
+ <dl class="m-doc">
+ <dt id="annotated">
+ <a href="#annotated" class="m-doc-self">annotated</a>: float <span class="m-label m-flat m-success">get set del</span>
+ </dt>
+ <dd></dd>
+ <dt id="complex_annotation">
+ <a href="#complex_annotation" class="m-doc-self">complex_annotation</a>: typing.List[typing.Tuple[int, float]] <span class="m-label m-flat m-success">get set del</span>
+ </dt>
+ <dd>This is complex.</dd>
+ </dl>
+ </section>
+ <section id="data">
+ <h2><a href="#data">Data</a></h2>
+ <dl class="m-doc">
+ <dt id="unannotated">
+ <a href="#unannotated" class="m-doc-self">unannotated</a> = 4
+ </dt>
+ <dd></dd>
+ </dl>
+ </section>
+ </div>
+ </div>
+ </div>
+</article></main>
+</body>
+</html>
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="UTF-8" />
+ <title>inspect_attrs.MySlotClass | My Python Project</title>
+ <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,400i,600,600i%7CSource+Code+Pro:400,400i,600" />
+ <link rel="stylesheet" href="m-dark+documentation.compiled.css" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+</head>
+<body>
+<header><nav id="navigation">
+ <div class="m-container">
+ <div class="m-row">
+ <a href="index.html" id="m-navbar-brand" class="m-col-t-8 m-col-m-none m-left-m">My Python Project</a>
+ </div>
+ </div>
+</nav></header>
+<main><article>
+ <div class="m-container m-container-inflatable">
+ <div class="m-row">
+ <div class="m-col-l-10 m-push-l-1">
+ <h1>
+ <span class="m-breadcrumb"><a href="inspect_attrs.html">inspect_attrs</a>.<wbr/></span>MySlotClass <span class="m-thin">class</span>
+ </h1>
+ <p>A class with attr-defined slots</p>
+ <div class="m-block m-default">
+ <h3>Contents</h3>
+ <ul>
+ <li>
+ Reference
+ <ul>
+ <li><a href="#dunder-methods">Special methods</a></li>
+ <li><a href="#properties">Properties</a></li>
+ </ul>
+ </li>
+ </ul>
+ </div>
+ <section id="dunder-methods">
+ <h2><a href="#dunder-methods">Special methods</a></h2>
+ <dl class="m-doc">
+ <dt id="__init__">
+ <span class="m-doc-wrap-bumper">def <a href="#__init__" class="m-doc-self">__init__</a>(</span><span class="m-doc-wrap">self,
+ annotated: float,
+ complex_annotation: typing.List[typing.Tuple[int, float]] = [],
+ complex_annotation_in_attr: typing.List[typing.Tuple[int, float]] = []) -> None</span>
+ </dt>
+ <dd></dd>
+ </dl>
+ </section>
+ <section id="properties">
+ <h2><a href="#properties">Properties</a></h2>
+ <dl class="m-doc">
+ <dt id="annotated">
+ <a href="#annotated" class="m-doc-self">annotated</a>: float <span class="m-label m-flat m-success">get set del</span>
+ </dt>
+ <dd></dd>
+ <dt id="complex_annotation">
+ <a href="#complex_annotation" class="m-doc-self">complex_annotation</a>: typing.List[typing.Tuple[int, float]] <span class="m-label m-flat m-success">get set del</span>
+ </dt>
+ <dd></dd>
+ <dt id="complex_annotation_in_attr">
+ <a href="#complex_annotation_in_attr" class="m-doc-self">complex_annotation_in_attr</a>: typing.List[typing.Tuple[int, float]] <span class="m-label m-flat m-success">get set del</span>
+ </dt>
+ <dd></dd>
+ </dl>
+ </section>
+ </div>
+ </div>
+ </div>
+</article></main>
+</body>
+</html>
annotated: float = attr.ib()
unannotated = attr.ib(4)
complex_annotation: List[Tuple[int, float]] = attr.ib(default=[])
+ complex_annotation_in_attr = attr.ib(default=[], type=List[Tuple[int, float]])
+
+ # This is just data
+ plain_data: float = 35
# Shouldn't be shown
_hidden_property: float = attr.ib(3)
unannotated = 4
complex_annotation: List[Tuple[int, float]] = []
-@attr.s(auto_attribs=True, slots=True)
+@attr.s(slots=True)
class MySlotClass:
"""A class with attr-defined slots"""
- annotated: float
- complex_annotation: List[Tuple[int, float]] = []
+ annotated: float = attr.ib()
+ complex_annotation: List[Tuple[int, float]] = attr.ib(default=[])
+ complex_annotation_in_attr = attr.ib(default=[], type=List[Tuple[int, float]])
self.assertEqual(*self.actual_expected_contents('content.docstring_summary.html'))
self.assertEqual(*self.actual_expected_contents('content.Class.html'))
self.assertEqual(*self.actual_expected_contents('content.ClassWithSummary.html'))
+ self.assertEqual(*self.actual_expected_contents('content.ClassWithSlots.html'))
class ParseDocstrings(BaseInspectTestCase):
def test(self):
page std:doc 2 page.html -
""".lstrip())
# Yes, above it should say A documentation page, but it doesn't
+
+try:
+ import attr
+except ImportError:
+ attr = None
+class Attrs(BaseInspectTestCase):
+ @unittest.skipUnless(attr, "the attr package was not found")
+ def test(self):
+ self.run_python({
+ 'PLUGINS': ['m.sphinx'],
+ 'INPUT_DOCS': ['docs.rst'],
+ 'ATTRS_COMPATIBILITY': True
+ })
+ self.assertEqual(*self.actual_expected_contents('inspect_attrs.MyClass.html'))
+ self.assertEqual(*self.actual_expected_contents('inspect_attrs.MyClassAutoAttribs.html'))
+ self.assertEqual(*self.actual_expected_contents('inspect_attrs.MySlotClass.html'))