py domain: Add py:property directive to describe a property (refs: #7068)

This commit is contained in:
Takeshi KOMIYA 2020-03-12 00:54:39 +09:00
parent 3693ffe232
commit 204f86f736
5 changed files with 103 additions and 3 deletions

View File

@ -66,6 +66,7 @@ Features added
the location where the object is defined
* #7199: py domain: Add :confval:`python_use_unqualified_type_names` to suppress
the module name of the python reference if it can be resolved (experimental)
* #7068: py domain: Add :rst:dir:`py:property` directive to describe a property
* #7784: i18n: The alt text for image is translated by default (without
:confval:`gettext_additional_targets` setting)
* #2018: html: :confval:`html_favicon` and :confval:`html_logo` now accept URL

View File

@ -316,6 +316,22 @@ The following directives are provided for module and class contents:
.. versionadded:: 4.0
.. rst:directive:: .. py:property:: name
Describes an object property.
.. versionadded:: 4.0
.. rubric:: options
.. rst:directive:option:: abstractmethod
:type: no value
Indicate the property is abstract.
.. rst:directive:option:: type: type of the property
:type: text
.. rst:directive:: .. py:method:: name(parameters)
Describes an object method. The parameters should not include the ``self``
@ -368,6 +384,10 @@ The following directives are provided for module and class contents:
.. versionadded:: 2.1
.. deprecated:: 4.0
Use :rst:dir:`py:property` instead.
.. rst:directive:option:: staticmethod
:type: no value
@ -584,6 +604,8 @@ a matching identifier is found:
Reference a data attribute of an object.
.. note:: The role is also able to refer to property.
.. rst:role:: py:exc
Reference an exception. A dotted name may be used.

View File

@ -825,6 +825,46 @@ class PyAttribute(PyObject):
return _('%s (%s attribute)') % (attrname, clsname)
class PyProperty(PyObject):
"""Description of an attribute."""
option_spec = PyObject.option_spec.copy()
option_spec.update({
'abstractmethod': directives.flag,
'type': directives.unchanged,
})
def handle_signature(self, sig: str, signode: desc_signature) -> Tuple[str, str]:
fullname, prefix = super().handle_signature(sig, signode)
typ = self.options.get('type')
if typ:
signode += addnodes.desc_annotation(typ, ': ' + typ)
return fullname, prefix
def get_signature_prefix(self, sig: str) -> str:
prefix = ['property']
if 'abstractmethod' in self.options:
prefix.insert(0, 'abstract')
return ' '.join(prefix) + ' '
def get_index_text(self, modname: str, name_cls: Tuple[str, str]) -> str:
name, cls = name_cls
try:
clsname, attrname = name.rsplit('.', 1)
if modname and self.env.config.add_module_names:
clsname = '.'.join([modname, clsname])
except ValueError:
if modname:
return _('%s (in module %s)') % (name, modname)
else:
return name
return _('%s (%s property)') % (attrname, clsname)
class PyDecoratorMixin:
"""
Mixin for decorator directives.
@ -1056,6 +1096,7 @@ class PythonDomain(Domain):
'classmethod': ObjType(_('class method'), 'meth', 'obj'),
'staticmethod': ObjType(_('static method'), 'meth', 'obj'),
'attribute': ObjType(_('attribute'), 'attr', 'obj'),
'property': ObjType(_('property'), 'attr', '_prop', 'obj'),
'module': ObjType(_('module'), 'mod', 'obj'),
} # type: Dict[str, ObjType]
@ -1068,6 +1109,7 @@ class PythonDomain(Domain):
'classmethod': PyClassMethod,
'staticmethod': PyStaticMethod,
'attribute': PyAttribute,
'property': PyProperty,
'module': PyModule,
'currentmodule': PyCurrentModule,
'decorator': PyDecoratorFunction,
@ -1205,8 +1247,17 @@ class PythonDomain(Domain):
type, searchmode)
if not matches and type == 'attr':
# fallback to meth (for property)
# fallback to meth (for property; Sphinx-2.4.x)
# this ensures that `:attr:` role continues to refer to the old property entry
# that defined by ``method`` directive in old reST files.
matches = self.find_obj(env, modname, clsname, target, 'meth', searchmode)
if not matches and type == 'meth':
# fallback to attr (for property)
# this ensures that `:meth:` in the old reST files can refer to the property
# entry that defined by ``property`` directive.
#
# Note: _prop is a secret role only for internal look-up.
matches = self.find_obj(env, modname, clsname, target, '_prop', searchmode)
if not matches:
return None

View File

@ -18,8 +18,7 @@ module
* Link to :py:meth:`module_a.submodule.ModTopLevel.mod_child_1`
.. py:method:: ModTopLevel.prop
:property:
.. py:property:: ModTopLevel.prop
* Link to :py:attr:`prop attribute <.prop>`
* Link to :py:meth:`prop method <.prop>`

View File

@ -201,6 +201,10 @@ def test_resolve_xref_for_properties(app, status, warning):
' title="module_a.submodule.ModTopLevel.prop">'
'<code class="xref py py-meth docutils literal notranslate"><span class="pre">'
'prop</span> <span class="pre">method</span></code></a>' in content)
assert ('Link to <a class="reference internal" href="#module_a.submodule.ModTopLevel.prop"'
' title="module_a.submodule.ModTopLevel.prop">'
'<code class="xref py py-attr docutils literal notranslate"><span class="pre">'
'prop</span> <span class="pre">attribute</span></code></a>' in content)
@pytest.mark.sphinx('dummy', testroot='domain-py')
@ -798,6 +802,29 @@ def test_pyattribute(app):
assert domain.objects['Class.attr'] == ('index', 'Class.attr', 'attribute', False)
def test_pyproperty(app):
text = (".. py:class:: Class\n"
"\n"
" .. py:property:: prop\n"
" :abstractmethod:\n"
" :type: str\n")
domain = app.env.get_domain('py')
doctree = restructuredtext.parse(app, text)
assert_node(doctree, (addnodes.index,
[desc, ([desc_signature, ([desc_annotation, "class "],
[desc_name, "Class"])],
[desc_content, (addnodes.index,
desc)])]))
assert_node(doctree[1][1][0], addnodes.index,
entries=[('single', 'prop (Class property)', 'Class.prop', '', None)])
assert_node(doctree[1][1][1], ([desc_signature, ([desc_annotation, "abstract property "],
[desc_name, "prop"],
[desc_annotation, ": str"])],
[desc_content, ()]))
assert 'Class.prop' in domain.objects
assert domain.objects['Class.prop'] == ('index', 'Class.prop', 'property', False)
def test_pydecorator_signature(app):
text = ".. py:decorator:: deco"
domain = app.env.get_domain('py')