From 7003b9dd424874bbf8a03c7bd799bfadba8596fa Mon Sep 17 00:00:00 2001
From: =?utf8?q?Vladim=C3=ADr=20Vondru=C5=A1?=
Date: Sun, 15 Sep 2019 21:34:22 +0200
Subject: [PATCH] documentation/python, m.sphinx: support documenting raised
exceptions.
---
doc/documentation/python.rst | 23 +++++-
doc/plugins/sphinx.rst | 23 +++---
documentation/python.py | 24 ++++++-
.../templates/python/details-function.html | 17 ++++-
.../templates/python/details-property.html | 15 ++++
.../test_python/content/content.Class.html | 63 ++++++++++++++++
.../test_python/content/content.html | 31 ++++++++
.../test_python/content/content/__init__.py | 13 ++++
documentation/test_python/content/docs.rst | 14 ++++
.../test_python/inspect_type_links/docs.rst | 8 +++
.../inspect_type_links.Foo.html | 72 +++++++++++++++++++
.../inspect_type_links.html | 25 ++++++-
.../inspect_type_links/__init__.py | 4 ++
documentation/test_python/test_inspect.py | 1 +
plugins/m/sphinx.py | 25 ++++++-
15 files changed, 341 insertions(+), 17 deletions(-)
create mode 100644 documentation/test_python/inspect_type_links/inspect_type_links.Foo.html
diff --git a/doc/documentation/python.rst b/doc/documentation/python.rst
index f539bbdd..50b7630f 100644
--- a/doc/documentation/python.rst
+++ b/doc/documentation/python.rst
@@ -1257,6 +1257,8 @@ Property Description
cross-linked types
:py:`function.params` List of function parameters. See below for
details.
+:py:`function.exceptions` List of exceptions raised by this function.
+ See below for details.
:py:`function.has_complex_params` Set to :py:`True` if the parameter list
should be wrapped on several lines for
better readability (for example when it
@@ -1276,8 +1278,8 @@ Property Description
:py:`False` otherwise.
=================================== ===========================================
-The :py:`func.params` is a list of function parameters and their description.
-Each item has the following properties:
+The :py:`function.params` is a list of function parameters and their
+description. Each item has the following properties:
.. class:: m-table m-fullwidth
@@ -1298,6 +1300,19 @@ In some cases (for example in case of native APIs), the parameters can't be
introspected. In that case, the parameter list is a single entry with ``name``
set to :py:`"..."` and the rest being empty.
+The :py:`function.exceptions` is a list of exceptions types and descriptions.
+Each item has the following properties:
+
+.. class:: m-table m-fullwidth
+
+=========================== ===================================================
+Property Description
+=========================== ===================================================
+:py:`exception.type` Exception type
+:py:`exception.type_link` Like :py:`exception`, but with a cross-linked type
+:py:`exception.content` Detailed documentation
+=========================== ===================================================
+
`Property properties`_
``````````````````````
@@ -1313,6 +1328,10 @@ Property Description
cross-linked types
:py:`property.summary` Doc summary
:py:`property.content` Detailed documentation, if any
+:py:`property.exceptions` List of exceptions raised when accessing
+ this property. Same as
+ :py:`function.exceptions` described in
+ `function properties`_.
:py:`property.is_gettable` If the property is gettable
:py:`property.is_settable` If the property is settable
:py:`property.is_deletable` If the property is deletable with :py:`del`
diff --git a/doc/plugins/sphinx.rst b/doc/plugins/sphinx.rst
index 00486ec1..2c8d534b 100644
--- a/doc/plugins/sphinx.rst
+++ b/doc/plugins/sphinx.rst
@@ -421,11 +421,14 @@ conveniently directly in the :rst:`.. py:enum::` directive via
------------
The :rst:`.. py:function::` directive supports additional options ---
-:py:`:param :` for documenting parameters and :py:`:return:` for
-documenting the return value. It's allowed to have either none or all
-parameters documented (the ``self`` parameter can be omitted), having them
-documented only partially or documenting parameters that are not present in the
-function signature will cause a warning. Example:
+:rst:`:param name:` for documenting parameters, :rst:`:raise name:` for
+documenting raised exceptions and :rst:`:return:` for documenting the return
+value. It's allowed to have either none or all parameters documented (the
+``self`` parameter can be omitted), having them documented only partially or
+documenting parameters that are not present in the function signature will
+cause a warning. Documenting one parameter multiple times causes a warning, on
+the other hand listing one exception multiple times is a valid use case.
+Example:
.. code:: rst
@@ -435,6 +438,7 @@ function signature will cause a warning. Example:
:param value: Corresponding value
:param overwrite_existing: Overwrite existing value if already present
in the container
+ :raise ValueError: If the key type is not hashable
:return: The inserted tuple or the existing
key/value pair in case :p:`overwrite_existing` is not set
@@ -470,10 +474,11 @@ Example:
`Properties`_
-------------
-Use :rst:`.. py:property::` for documenting properties. This directive doesn't
-support any additional options besides :rst:`:summary:`. For convenience,
-properties that have just a summary can be also documented directly in the
-enclosing :rst:`.. py:class::` directive `as shown above <#classes>`__.
+Use :rst:`.. py:property::` for documenting properties. This directive supports
+the :rst:`:raise name:` option similarly as for `functions`_, plus the usual
+:rst:`:summary:`. For convenience, properties that have just a summary can be
+also documented directly in the enclosing :rst:`.. py:class::` directive
+`as shown above <#classes>`__.
.. code:: rst
diff --git a/documentation/python.py b/documentation/python.py
index ad21e8db..ece774ee 100755
--- a/documentation/python.py
+++ b/documentation/python.py
@@ -1524,7 +1524,7 @@ def extract_function_doc(state: State, parent, entry: Empty) -> List[Any]:
overloads = [out]
- # Common path for parameter / return value docs and search
+ # Common path for parameter / exception / return value docs and search
path_str = '.'.join(entry.path)
for out in overloads:
signature = '({})'.format(', '.join(['{}: {}'.format(param.name, param.type) if param.type else param.name for param in out.params]))
@@ -1568,6 +1568,16 @@ def extract_function_doc(state: State, parent, entry: Empty) -> List[Any]:
if name not in used_params:
logging.warning("%s%s documents parameter %s, which isn't in the signature", path_str, signature, name)
+ if function_docs.get('raise'):
+ out.exceptions = []
+ for type_, content in function_docs['raise']:
+ exception = Empty()
+ exception.type = type_
+ exception.type_link = make_name_link(state, entry.path, type_)
+ exception.content = render_inline_rst(state, content)
+ out.exceptions += [exception]
+ out.has_details = True
+
if function_docs.get('return'):
try:
out.return_value = render_inline_rst(state, function_docs['return'])
@@ -1739,6 +1749,18 @@ def extract_property_doc(state: State, parent, entry: Empty):
for hook in state.hooks_post_scope:
hook(type=entry.type, path=entry.path)
+ # Exception docs, if any
+ exception_docs = state.property_docs.get('.'.join(entry.path), {}).get('raise')
+ if exception_docs:
+ out.exceptions = []
+ for type_, content in exception_docs:
+ exception = Empty()
+ exception.type = type_
+ exception.type_link = make_name_link(state, entry.path, type_)
+ exception.content = render_inline_rst(state, content)
+ out.exceptions += [exception]
+ out.has_details = True
+
if not state.config['SEARCH_DISABLED']:
result = Empty()
result.flags = ResultFlag.from_type(ResultFlag.NONE, EntryType.PROPERTY)
diff --git a/documentation/templates/python/details-function.html b/documentation/templates/python/details-function.html
index 1bce6420..25440508 100644
--- a/documentation/templates/python/details-function.html
+++ b/documentation/templates/python/details-function.html
@@ -6,7 +6,7 @@
{% if function.summary %}
{{ function.summary }}
{% endif %}
- {% if function.has_param_details or function.return_value %}
+ {% if function.has_param_details or function.exceptions or function.return_value %}
This one documents raised exceptions in an (otherwise unneeded) detail
+view
+
+
+
Exceptions
+
+
+
+
ValueError
+
This thing fires
+
+
+
ValueError
+
This same thing fires also for this reason
+
+
+
RuntimeError
+
This another thing fires too
+
+
+
+
def content.foo_with_details(a, b)
diff --git a/documentation/test_python/content/content/__init__.py b/documentation/test_python/content/content/__init__.py
index ace7afb6..e555cf0f 100644
--- a/documentation/test_python/content/content/__init__.py
+++ b/documentation/test_python/content/content/__init__.py
@@ -26,6 +26,9 @@ class Class:
def method_param_docs(self, a, b):
"""This method gets its params except self documented"""
+ def method_param_exception_return_docs(self, a, b):
+ """This one documents params and raised exceptions"""
+
@property
def a_property(self):
"""This summary is not shown either"""
@@ -38,6 +41,11 @@ class Class:
def annotated_property(self) -> float:
"""This is an annotated property"""
+ @property
+ def property_exception_docs(self):
+ """This one documents raised exceptions in an (otherwise unneeded)
+ detail view"""
+
DATA_WITH_DETAILS: str = 'this blows'
class ClassWithSummary:
@@ -102,6 +110,11 @@ def full_docstring(a, b) -> str:
Like this.
"""
+def exception_docs():
+ """This one documents raised exceptions in an (otherwise unneeded) detail
+ view
+ """
+
# This should check we handle reST parsing errors in external docs gracefully.
# Will probably look extra weird in the output tho, but that's okay -- it's an
# error after all.
diff --git a/documentation/test_python/content/docs.rst b/documentation/test_python/content/docs.rst
index fd02d935..21065c3f 100644
--- a/documentation/test_python/content/docs.rst
+++ b/documentation/test_python/content/docs.rst
@@ -59,6 +59,12 @@
The ``self`` isn't documented and thus also not included in the list.
+.. py:function:: content.Class.method_param_exception_return_docs
+ :param a: The first parameter
+ :param b: The second parameter
+ :raise AttributeError: If you do bad things to it
+ :return: If you don't do bad things to it
+
.. py:property:: content.Class.a_property
:summary: This overwrites the docstring for :ref:`a_property`, but doesn't
add any detailed block.
@@ -72,6 +78,9 @@
Annotated property, using summary from the docstring.
+.. py:property:: content.Class.property_exception_docs
+ :raise AttributeError: If you do bad things to it
+
.. py:data:: content.Class.DATA_WITH_DETAILS
Detailed docs for :ref:`DATA_WITH_DETAILS` in a class to check
@@ -136,6 +145,11 @@ Doing :p:`this` here is not good either.
:param a: First parameter
:param b: Second
+.. py:function:: content.exception_docs
+ :raise ValueError: This thing fires
+ :raise ValueError: This *same* thing fires *also* for this reason
+ :raise RuntimeError: This another thing fires too
+
.. py:data:: content.CONSTANT
:summary: This is finally a docstring for :ref:`CONSTANT`
diff --git a/documentation/test_python/inspect_type_links/docs.rst b/documentation/test_python/inspect_type_links/docs.rst
index 4548d45b..015f93e6 100644
--- a/documentation/test_python/inspect_type_links/docs.rst
+++ b/documentation/test_python/inspect_type_links/docs.rst
@@ -7,6 +7,14 @@
function we need to say :ref:`inspect_type_links.open()`. If it would be
the other way around, there would be no simple way to link to builtins.
+.. py:function:: inspect_type_links.open
+ :raise ValueError: If this is not a can, crosslinking to :ref:`ValueError`
+ of course.
+
+.. py:property:: inspect_type_links.Foo.prop
+ :raise SystemError: If you look at it wrong, crosslinking to
+ :ref:`SystemError` of course.
+
.. py:module:: inspect_type_links.first
:ref:`Foo`, :ref:`first.Foo` and :ref:`inspect_type_links.first.Foo` should
diff --git a/documentation/test_python/inspect_type_links/inspect_type_links.Foo.html b/documentation/test_python/inspect_type_links/inspect_type_links.Foo.html
new file mode 100644
index 00000000..a1f90474
--- /dev/null
+++ b/documentation/test_python/inspect_type_links/inspect_type_links.Foo.html
@@ -0,0 +1,72 @@
+
+
+
+
+ inspect_type_links.Foo | My Python Project
+
+
+
+
+
+
+
+
If you look at it wrong, crosslinking to
+SystemError of course.
+
+
+
+
+
+
+
+
+
+
+
diff --git a/documentation/test_python/inspect_type_links/inspect_type_links.html b/documentation/test_python/inspect_type_links/inspect_type_links.html
index 16df2d11..994ac451 100644
--- a/documentation/test_python/inspect_type_links/inspect_type_links.html
+++ b/documentation/test_python/inspect_type_links/inspect_type_links.html
@@ -61,12 +61,33 @@ the other way around, there would be no simple way to link to builtins.
If this is not a can, crosslinking to ValueError
+of course.
+
+
+
+
+
diff --git a/documentation/test_python/inspect_type_links/inspect_type_links/__init__.py b/documentation/test_python/inspect_type_links/inspect_type_links/__init__.py
index b248126e..590c8b9a 100644
--- a/documentation/test_python/inspect_type_links/inspect_type_links/__init__.py
+++ b/documentation/test_python/inspect_type_links/inspect_type_links/__init__.py
@@ -3,6 +3,10 @@ from . import first, second
class Foo:
"""A class in the root module"""
+ @property
+ def prop(self):
+ """Here just to test the raise option"""
+
class Bar:
"""Another class in the root module"""
diff --git a/documentation/test_python/test_inspect.py b/documentation/test_python/test_inspect.py
index 4ba365f9..57599dba 100644
--- a/documentation/test_python/test_inspect.py
+++ b/documentation/test_python/test_inspect.py
@@ -175,6 +175,7 @@ class TypeLinks(BaseInspectTestCase):
self.assertEqual(*self.actual_expected_contents('index.html'))
self.assertEqual(*self.actual_expected_contents('inspect_type_links.html'))
+ self.assertEqual(*self.actual_expected_contents('inspect_type_links.Foo.html'))
self.assertEqual(*self.actual_expected_contents('inspect_type_links.first.html'))
self.assertEqual(*self.actual_expected_contents('inspect_type_links.first.Foo.html'))
self.assertEqual(*self.actual_expected_contents('inspect_type_links.first.Foo.Foo.html'))
diff --git a/plugins/m/sphinx.py b/plugins/m/sphinx.py
index 3446581e..ba7db45e 100755
--- a/plugins/m/sphinx.py
+++ b/plugins/m/sphinx.py
@@ -173,6 +173,7 @@ class PyFunction(rst.Directive):
required_arguments = 1
option_spec = {'summary': directives.unchanged,
'param': directives_unchanged_list,
+ 'raise': directives_unchanged_list,
'return': directives.unchanged}
def run(self):
@@ -182,12 +183,21 @@ class PyFunction(rst.Directive):
for name, content in self.options.get('param', []):
if name in params: raise KeyError("duplicate param {}".format(name))
params[name] = content
+ # Check that exceptions are parsed properly. This will blow up if the
+ # exception name is not specified. Unlike function params not turning
+ # these into a dict since a single type can be raised for multiple
+ # reasons.
+ raises = []
+ for name, content in self.options.get('raise', []):
+ raises += [(name, content)]
output = function_doc_output.setdefault(self.arguments[0], {})
if self.options.get('summary'):
output['summary'] = self.options['summary']
if params:
output['params'] = params
+ if raises:
+ output['raise'] = raises
if self.options.get('return'):
output['return'] = self.options['return']
if self.content:
@@ -198,10 +208,21 @@ class PyProperty(rst.Directive):
final_argument_whitespace = True
has_content = True
required_arguments = 1
- option_spec = {'summary': directives.unchanged}
+ option_spec = {'summary': directives.unchanged,
+ 'raise': directives_unchanged_list}
def run(self):
+ # Check that exceptions are parsed properly. This will blow up if the
+ # exception name is not specified. Unlike function params not turning
+ # these into a dict since a single type can be raised for multiple
+ # reasons.
+ raises = []
+ for name, content in self.options.get('raise', []):
+ raises += [(name, content)]
+
output = property_doc_output.setdefault(self.arguments[0], {})
+ if raises:
+ output['raise'] = raises
if self.options.get('summary'):
output['summary'] = self.options['summary']
if self.content:
@@ -539,6 +560,8 @@ def merge_inventories(name_map, **kwargs):
# TODO: this will blow up if the above loop is never entered (which is
# unlikely) as EntryType is defined there
(EntryType.CLASS, 'py:class'),
+ # Otherwise we can't link to standard exceptions from :raise:
+ (EntryType.CLASS, 'py:exception'), # TODO: special type for these?
(EntryType.DATA, 'py:data'), # typing.Tuple or typing.Any is data
# Those are custom to m.css, not in Sphinx
(EntryType.ENUM, 'py:enum'),
--
2.30.2