Skip to content

Commit a23e2ff

Browse files
committed
Render Python properties with the property directive
Fixes #352.
1 parent d91c7aa commit a23e2ff

File tree

19 files changed

+530
-45
lines changed

19 files changed

+530
-45
lines changed

CHANGELOG.rst

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,21 @@ Changelog
33

44
Versions follow `Semantic Versioning <https://semver.org/>`_ (``<major>.<minor>.<patch>``).
55

6-
v1.9.1 (TBC)
6+
v2.0.0 (TBC)
77
------------
88

9+
Breaking Changes
10+
^^^^^^^^^^^^^^^^
11+
12+
* Dropped support for Sphinx <4.
13+
* `#352 <https://github.com/readthedocs/sphinx-autoapi/issues/352>`: (Python)
14+
Properties are rendered with the ``property`` directive,
15+
fixing support for Sphinx 5.2.
16+
A new ``PythonPythonMapper`` object (``PythonProperty``) has been created
17+
to support this change. This object can be passed to templates, filters,
18+
and hooks.
19+
A new ``property.rst`` template has also been created to support this change.
20+
921
Trivial/Internal Changes
1022
^^^^^^^^^^^^^^^^^^^^^^^^
1123
* Use https links where possible in documentation.

autoapi/documenters.py

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
PythonFunction,
77
PythonClass,
88
PythonMethod,
9+
PythonProperty,
910
PythonData,
1011
PythonAttribute,
1112
PythonException,
@@ -192,8 +193,11 @@ def import_object(self):
192193

193194
if result:
194195
self.parent = self._method_parent
195-
if self.object.method_type != "method":
196-
# document class and static members before ordinary ones
196+
if "staticmethod" in self.object.properties:
197+
# document static members before ordinary ones
198+
self.member_order = self.member_order - 2
199+
elif "classmethod" in self.object.properties:
200+
# document class members before ordinary ones but after static ones
197201
self.member_order = self.member_order - 1
198202

199203
return result
@@ -212,22 +216,28 @@ def add_directive_header(self, sig):
212216
self.add_line(" :{}:".format(property_type), sourcename)
213217

214218

215-
class AutoapiPropertyDocumenter(
216-
AutoapiMethodDocumenter, AutoapiDocumenter, autodoc.PropertyDocumenter
217-
):
219+
class AutoapiPropertyDocumenter(AutoapiDocumenter, autodoc.PropertyDocumenter):
218220
objtype = "apiproperty"
219-
directivetype = "method"
220-
# Always prefer AutoapiDocumenters
221-
priority = autodoc.MethodDocumenter.priority * 100 + 100 + 1
221+
directivetype = "property"
222+
priority = autodoc.PropertyDocumenter.priority * 100 + 100
222223

223224
@classmethod
224225
def can_document_member(cls, member, membername, isattr, parent):
225-
return isinstance(member, PythonMethod) and "property" in member.properties
226+
return isinstance(member, PythonProperty)
226227

227228
def add_directive_header(self, sig):
228-
super(AutoapiPropertyDocumenter, self).add_directive_header(sig)
229+
autodoc.ClassLevelDocumenter.add_directive_header(self, sig)
230+
229231
sourcename = self.get_sourcename()
230-
self.add_line(" :property:", sourcename)
232+
if self.options.annotation and self.options.annotation is not autodoc.SUPPRESS:
233+
self.add_line(" :type: %s" % self.options.annotation, sourcename)
234+
235+
for property_type in (
236+
"abstractmethod",
237+
"classmethod",
238+
):
239+
if property_type in self.object.properties:
240+
self.add_line(" :{}:".format(property_type), sourcename)
231241

232242

233243
class AutoapiDataDocumenter(AutoapiDocumenter, autodoc.DataDocumenter):

autoapi/mappers/python/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
PythonModule,
66
PythonMethod,
77
PythonPackage,
8+
PythonProperty,
89
PythonAttribute,
910
PythonData,
1011
PythonException,

autoapi/mappers/python/mapper.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
PythonModule,
2020
PythonMethod,
2121
PythonPackage,
22+
PythonProperty,
2223
PythonAttribute,
2324
PythonData,
2425
PythonException,
@@ -237,12 +238,12 @@ class PythonSphinxMapper(SphinxMapperBase):
237238
PythonModule,
238239
PythonMethod,
239240
PythonPackage,
241+
PythonProperty,
240242
PythonAttribute,
241243
PythonData,
242244
PythonException,
243245
)
244246
}
245-
_OBJ_MAP["property"] = PythonMethod
246247

247248
def __init__(self, app, template_dir=None, url_root=None):
248249
super(PythonSphinxMapper, self).__init__(app, template_dir, url_root)
@@ -421,7 +422,7 @@ def _record_typehints(self, obj):
421422
if (
422423
isinstance(obj, (PythonClass, PythonFunction, PythonMethod))
423424
and not obj.overloads
424-
):
425+
) or isinstance(obj, PythonProperty):
425426
obj_annotations = {}
426427

427428
include_return_annotation = True

autoapi/mappers/python/objects.py

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ class PythonFunction(PythonPythonMapper):
169169

170170
type = "function"
171171
is_callable = True
172-
member_order = 40
172+
member_order = 30
173173

174174
def __init__(self, obj, **kwargs):
175175
super(PythonFunction, self).__init__(obj, **kwargs)
@@ -219,13 +219,6 @@ class PythonMethod(PythonFunction):
219219
def __init__(self, obj, **kwargs):
220220
super(PythonMethod, self).__init__(obj, **kwargs)
221221

222-
self.method_type = obj.get("method_type")
223-
"""The type of method that this object represents.
224-
225-
This can be one of: method, staticmethod, or classmethod.
226-
227-
:type: str
228-
"""
229222
self.properties = obj["properties"]
230223
"""The properties that describe what type of method this is.
231224
@@ -242,11 +235,34 @@ def _should_skip(self): # type: () -> bool
242235
return self._ask_ignore(skip)
243236

244237

238+
class PythonProperty(PythonPythonMapper):
239+
"""The representation of a property on a class."""
240+
241+
type = "property"
242+
member_order = 60
243+
244+
def __init__(self, obj, **kwargs):
245+
super(PythonProperty, self).__init__(obj, **kwargs)
246+
247+
self.annotation = obj["return_annotation"]
248+
"""The type annotation of this property.
249+
250+
:type: str or None
251+
"""
252+
self.properties = obj["properties"]
253+
"""The properties that describe what type of property this is.
254+
255+
Can be any of: abstractmethod, classmethod
256+
257+
:type: list(str)
258+
"""
259+
260+
245261
class PythonData(PythonPythonMapper):
246262
"""Global, module level data."""
247263

248264
type = "data"
249-
member_order = 10
265+
member_order = 40
250266

251267
def __init__(self, obj, **kwargs):
252268
super(PythonData, self).__init__(obj, **kwargs)
@@ -272,7 +288,7 @@ class PythonAttribute(PythonData):
272288
"""An object/class level attribute."""
273289

274290
type = "attribute"
275-
member_order = 10
291+
member_order = 60
276292

277293

278294
class TopLevelPythonPythonMapper(PythonPythonMapper):
@@ -335,7 +351,7 @@ class PythonClass(PythonPythonMapper):
335351
"""The representation of a class."""
336352

337353
type = "class"
338-
member_order = 30
354+
member_order = 20
339355

340356
def __init__(self, obj, **kwargs):
341357
super(PythonClass, self).__init__(obj, **kwargs)
@@ -409,6 +425,10 @@ def docstring(self, value):
409425
def methods(self):
410426
return self._children_of_type("method")
411427

428+
@property
429+
def properties(self):
430+
return self._children_of_type("property")
431+
412432
@property
413433
def attributes(self):
414434
return self._children_of_type("attribute")
@@ -446,4 +466,4 @@ class PythonException(PythonClass):
446466
"""The representation of an exception class."""
447467

448468
type = "exception"
449-
member_order = 20
469+
member_order = 10

autoapi/mappers/python/parser.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -150,18 +150,23 @@ def parse_functiondef(self, node): # pylint: disable=too-many-branches
150150

151151
if node.type == "function":
152152
type_ = "function"
153+
154+
if isinstance(node, astroid.AsyncFunctionDef):
155+
properties.append("async")
153156
elif astroid_utils.is_decorated_with_property(node):
154157
type_ = "property"
155-
properties.append("property")
158+
if node.type == "classmethod":
159+
properties.append(node.type)
160+
if node.is_abstract(pass_is_abstract=False):
161+
properties.append("abstractmethod")
156162
else:
157163
# "__new__" method is implicit classmethod
158164
if node.type in ("staticmethod", "classmethod") and node.name != "__new__":
159165
properties.append(node.type)
160166
if node.is_abstract(pass_is_abstract=False):
161167
properties.append("abstractmethod")
162-
163-
if isinstance(node, astroid.AsyncFunctionDef):
164-
properties.append("async")
168+
if isinstance(node, astroid.AsyncFunctionDef):
169+
properties.append("async")
165170

166171
data = {
167172
"type": type_,
@@ -177,9 +182,6 @@ def parse_functiondef(self, node): # pylint: disable=too-many-branches
177182
"overloads": [],
178183
}
179184

180-
if type_ in ("method", "property"):
181-
data["method_type"] = node.type
182-
183185
result = [data]
184186

185187
if node.name == "__init__":

autoapi/templates/python/class.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@
3232
{{ klass.render()|indent(3) }}
3333
{% endfor %}
3434
{% if "inherited-members" in autoapi_options %}
35+
{% set visible_properties = obj.properties|selectattr("display")|list %}
36+
{% else %}
37+
{% set visible_properties = obj.properties|rejectattr("inherited")|selectattr("display")|list %}
38+
{% endif %}
39+
{% for property in visible_properties %}
40+
{{ property.render()|indent(3) }}
41+
{% endfor %}
42+
{% if "inherited-members" in autoapi_options %}
3543
{% set visible_attributes = obj.attributes|selectattr("display")|list %}
3644
{% else %}
3745
{% set visible_attributes = obj.attributes|rejectattr("inherited")|selectattr("display")|list %}

autoapi/templates/python/function.rst

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,11 @@
55
{{ obj.short_name }}({{ args }}){% if return_annotation is not none %} -> {{ return_annotation }}{% endif %}
66

77
{% endfor %}
8-
{% if sphinx_version >= (2, 1) %}
98
{% for property in obj.properties %}
109
:{{ property }}:
1110
{% endfor %}
12-
{% endif %}
1311

1412
{% if obj.docstring %}
1513
{{ obj.docstring|indent(3) }}
16-
{% else %}
1714
{% endif %}
1815
{% endif %}

autoapi/templates/python/method.rst

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
{%- if obj.display %}
2-
{% if sphinx_version >= (2, 1) %}
32
.. py:method:: {{ obj.short_name }}({{ obj.args }}){% if obj.return_annotation is not none %} -> {{ obj.return_annotation }}{% endif %}
43
54
{% for (args, return_annotation) in obj.overloads %}
@@ -14,13 +13,6 @@
1413
{% else %}
1514

1615
{% endif %}
17-
{% else %}
18-
.. py:{{ obj.method_type }}:: {{ obj.short_name }}({{ obj.args }})
19-
{% for (args, return_annotation) in obj.overloads %}
20-
{{ " " * (obj.method_type | length) }} {{ obj.short_name }}({{ args }})
21-
{% endfor %}
22-
23-
{% endif %}
2416
{% if obj.docstring %}
2517
{{ obj.docstring|indent(3) }}
2618
{% endif %}

autoapi/templates/python/property.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{%- if obj.display %}
2+
.. py:property:: {{ obj.short_name }}
3+
{% if obj.annotation %}
4+
:type: {{ obj.annotation }}
5+
{% endif %}
6+
{% if obj.properties %}
7+
{% for property in obj.properties %}
8+
:{{ property }}:
9+
{% endfor %}
10+
{% endif %}
11+
12+
{% if obj.docstring %}
13+
{{ obj.docstring|indent(3) }}
14+
{% endif %}
15+
{% endif %}

docs/reference/templates.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ Python
7373
:members:
7474
:show-inheritance:
7575

76+
.. autoapiclass:: autoapi.mappers.python.objects.PythonProperty
77+
:members:
78+
:show-inheritance:
79+
7680
.. autoapiclass:: autoapi.mappers.python.objects.PythonData
7781
:members:
7882
:show-inheritance:

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ install_requires =
3636
astroid>=2.7
3737
Jinja2
3838
PyYAML
39-
sphinx>=3.0
39+
sphinx>=4.0
4040
unidecode
4141

4242
[options.extras_require]

0 commit comments

Comments
 (0)