Fixed error when parsing a class with no constructor

This commit is contained in:
Ashley Whetter 2021-06-21 23:10:52 -07:00
parent 83b1260e67
commit 5faec73073
19 changed files with 70 additions and 61 deletions

View File

@ -3,6 +3,15 @@ Changelog
Versions follow `Semantic Versioning <https://semver.org/>`_ (``<major>.<minor>.<patch>``). Versions follow `Semantic Versioning <https://semver.org/>`_ (``<major>.<minor>.<patch>``).
v1.8.2 (TBC)
-------------------
Bug Fixes
^^^^^^^^^
* Fixed error when parsing a class with no constructor.
v1.8.1 (2021-04-24) v1.8.1 (2021-04-24)
------------------- -------------------

View File

@ -380,6 +380,15 @@ class PythonSphinxMapper(SphinxMapperBase):
) )
obj.url_root = self.url_root obj.url_root = self.url_root
for child_data in data.get("children", []):
for child_obj in self.create_class(
child_data, options=options, **kwargs
):
obj.children.append(child_obj)
# Some objects require children to establish their docstring
# or type annotations (eg classes with inheritance),
# so do this after all children have been created.
lines = sphinx.util.docstrings.prepare_docstring(obj.docstring) lines = sphinx.util.docstrings.prepare_docstring(obj.docstring)
if lines and "autodoc-process-docstring" in self.app.events.events: if lines and "autodoc-process-docstring" in self.app.events.events:
self.app.emit( self.app.emit(
@ -388,12 +397,6 @@ class PythonSphinxMapper(SphinxMapperBase):
obj.docstring = "\n".join(lines) obj.docstring = "\n".join(lines)
self._record_typehints(obj) self._record_typehints(obj)
for child_data in data.get("children", []):
for child_obj in self.create_class(
child_data, options=options, **kwargs
):
obj.children.append(child_obj)
# Parser gives children in source order already # Parser gives children in source order already
if self.app.config.autoapi_member_order == "alphabetical": if self.app.config.autoapi_member_order == "alphabetical":
obj.children.sort(key=operator.attrgetter("name")) obj.children.sort(key=operator.attrgetter("name"))
@ -405,14 +408,25 @@ class PythonSphinxMapper(SphinxMapperBase):
def _record_typehints(self, obj): def _record_typehints(self, obj):
if isinstance( if isinstance(
obj, (PythonClass, PythonFunction, PythonMethod) obj, (PythonClass, PythonFunction, PythonMethod)
) and not obj.obj.get("overloads"): ) and not obj.overloads:
obj_annotations = {} obj_annotations = {}
for _, name, annotation, _ in obj.obj["args"]:
include_return_annotation = True
obj_data = obj.obj
if isinstance(obj, PythonClass):
constructor = obj.constructor
if constructor:
include_return_annotation = False
obj_data = constructor.obj
else:
return
for _, name, annotation, _ in obj_data["args"]:
if name and annotation: if name and annotation:
obj_annotations[name] = annotation obj_annotations[name] = annotation
return_annotation = obj.obj.get("return_annotation") return_annotation = obj_data["return_annotation"]
if return_annotation: if include_return_annotation and return_annotation:
obj_annotations["return"] = return_annotation obj_annotations["return"] = return_annotation
self.app.env.autoapi_annotations[obj.id] = obj_annotations self.app.env.autoapi_annotations[obj.id] = obj_annotations

View File

@ -1,3 +1,4 @@
import functools
from typing import Optional from typing import Optional
import sphinx.util.logging import sphinx.util.logging
@ -174,6 +175,10 @@ class PythonFunction(PythonPythonMapper):
autodoc_typehints == "description" and not obj["overloads"] autodoc_typehints == "description" and not obj["overloads"]
) )
self.args = _format_args(obj["args"], show_annotations) self.args = _format_args(obj["args"], show_annotations)
"""The arguments to this object, formatted as a string.
:type: str
"""
self.return_annotation = obj["return_annotation"] if show_annotations else None self.return_annotation = obj["return_annotation"] if show_annotations else None
"""The type annotation for the return type of this function. """The type annotation for the return type of this function.
@ -199,18 +204,6 @@ class PythonFunction(PythonPythonMapper):
:type: list(tuple(str, str)) :type: list(tuple(str, str))
""" """
@property
def args(self):
"""The arguments to this object, formatted as a string.
:type: str
"""
return self._args
@args.setter
def args(self, value):
self._args = value
class PythonMethod(PythonFunction): class PythonMethod(PythonFunction):
"""The representation of a method.""" """The representation of a method."""
@ -343,8 +336,6 @@ class PythonClass(PythonPythonMapper):
def __init__(self, obj, **kwargs): def __init__(self, obj, **kwargs):
super(PythonClass, self).__init__(obj, **kwargs) super(PythonClass, self).__init__(obj, **kwargs)
self.args = obj["args"]
self.bases = obj["bases"] self.bases = obj["bases"]
"""The fully qualified names of all base classes. """The fully qualified names of all base classes.
@ -357,20 +348,34 @@ class PythonClass(PythonPythonMapper):
:type: str :type: str
""" """
args = self._args args = ""
constructor = self.constructor if self.constructor:
if constructor: autodoc_typehints = getattr(self.app.config, "autodoc_typehints", "signature")
args = constructor.args show_annotations = autodoc_typehints != "none" and not (
autodoc_typehints == "description" and not self.constructor.overloads
if args.startswith("self"): )
args = args[4:].lstrip(",").lstrip() args_data = self.constructor.obj["args"]
if args_data and args_data[0][1] == "self":
args_data = args_data[1:]
args = _format_args(args_data, show_annotations)
return args return args
@args.setter @property
def args(self, value): def overloads(self):
self._args = value overloads = []
if self.constructor:
overload_data = self.constructor.obj["overloads"]
if overload_data and overload_data[0][1] == "self":
overload_data = overload_data[1:]
overloads = [
(_format_args(args), return_annotation)
for args, return_annotation in overload_data
]
return overloads
@property @property
def docstring(self): def docstring(self):
@ -404,6 +409,7 @@ class PythonClass(PythonPythonMapper):
return self._children_of_type("class") return self._children_of_type("class")
@property @property
@functools.lru_cache()
def constructor(self): def constructor(self):
for child in self.children: for child in self.children:
if child.short_name == "__init__": if child.short_name == "__init__":

View File

@ -86,22 +86,12 @@ class Parser:
if astroid_utils.is_exception(node): if astroid_utils.is_exception(node):
type_ = "exception" type_ = "exception"
args = []
try:
constructor = node.lookup("__init__")[1]
except IndexError:
pass
else:
if isinstance(constructor, astroid.nodes.FunctionDef):
args = astroid_utils.get_args_info(constructor.args)
basenames = list(astroid_utils.get_full_basenames(node)) basenames = list(astroid_utils.get_full_basenames(node))
data = { data = {
"type": type_, "type": type_,
"name": node.name, "name": node.name,
"full_name": self._get_full_name(node.name), "full_name": self._get_full_name(node.name),
"args": args,
"bases": basenames, "bases": basenames,
"doc": astroid_utils.get_class_docstring(node), "doc": astroid_utils.get_class_docstring(node),
"from_line_no": node.fromlineno, "from_line_no": node.fromlineno,

View File

@ -1,12 +1,8 @@
{% if obj.display %} {% if obj.display %}
.. {{ obj.type }}:: {{ obj.short_name }}{% if obj.args %}({{ obj.args }}){% endif %} .. {{ obj.type }}:: {{ obj.short_name }}{% if obj.args %}({{ obj.args }}){% endif %}
{% if obj.constructor %} {% for (args, return_annotation) in obj.overloads %}
{% for (args, return_annotation) in obj.constructor.overloads %}
{% if args and args.startswith("self, ") %}{% set args = args[6:] %}{% endif %}
{{ " " * (obj.type | length) }} {{ obj.short_name }}{% if args %}({{ args }}){% endif %} {{ " " * (obj.type | length) }} {{ obj.short_name }}{% if args %}({{ args }}){% endif %}
{% endfor %} {% endfor %}
{% endif %}
{% if obj.bases %} {% if obj.bases %}

View File

@ -13,7 +13,6 @@ exclude_patterns = ["_build"]
pygments_style = "sphinx" pygments_style = "sphinx"
todo_include_todos = False todo_include_todos = False
html_theme = "alabaster" html_theme = "alabaster"
html_static_path = ["_static"]
htmlhelp_basename = "pyexampledoc" htmlhelp_basename = "pyexampledoc"
extensions = ["sphinx.ext.autodoc", "autoapi.extension"] extensions = ["sphinx.ext.autodoc", "autoapi.extension"]
autoapi_type = "python" autoapi_type = "python"

View File

@ -13,7 +13,6 @@ exclude_patterns = ["_build"]
pygments_style = "sphinx" pygments_style = "sphinx"
todo_include_todos = False todo_include_todos = False
html_theme = "alabaster" html_theme = "alabaster"
html_static_path = ["_static"]
htmlhelp_basename = "pyexampledoc" htmlhelp_basename = "pyexampledoc"
extensions = ["sphinx.ext.autodoc", "autoapi.extension"] extensions = ["sphinx.ext.autodoc", "autoapi.extension"]
autoapi_type = "python" autoapi_type = "python"

View File

@ -13,7 +13,6 @@ exclude_patterns = ["_build"]
pygments_style = "sphinx" pygments_style = "sphinx"
todo_include_todos = False todo_include_todos = False
html_theme = "alabaster" html_theme = "alabaster"
html_static_path = ["_static"]
htmlhelp_basename = "py3implicitnamespacedoc" htmlhelp_basename = "py3implicitnamespacedoc"
extensions = ["sphinx.ext.autodoc", "autoapi.extension"] extensions = ["sphinx.ext.autodoc", "autoapi.extension"]
autoapi_type = "python" autoapi_type = "python"

View File

@ -13,7 +13,6 @@ exclude_patterns = ["_build"]
pygments_style = "sphinx" pygments_style = "sphinx"
todo_include_todos = False todo_include_todos = False
html_theme = "alabaster" html_theme = "alabaster"
html_static_path = ["_static"]
htmlhelp_basename = "pyexampledoc" htmlhelp_basename = "pyexampledoc"
extensions = ["sphinx.ext.autodoc", "autoapi.extension"] extensions = ["sphinx.ext.autodoc", "autoapi.extension"]
autoapi_type = "python" autoapi_type = "python"

View File

@ -13,7 +13,6 @@ exclude_patterns = ["_build"]
pygments_style = "sphinx" pygments_style = "sphinx"
todo_include_todos = False todo_include_todos = False
html_theme = "alabaster" html_theme = "alabaster"
html_static_path = ["_static"]
htmlhelp_basename = "pyexampledoc" htmlhelp_basename = "pyexampledoc"
extensions = ["sphinx.ext.autodoc", "autoapi.extension", "sphinx.ext.napoleon"] extensions = ["sphinx.ext.autodoc", "autoapi.extension", "sphinx.ext.napoleon"]
autoapi_type = "python" autoapi_type = "python"

View File

@ -13,7 +13,6 @@ exclude_patterns = ["_build"]
pygments_style = "sphinx" pygments_style = "sphinx"
todo_include_todos = False todo_include_todos = False
html_theme = "alabaster" html_theme = "alabaster"
html_static_path = ["_static"]
htmlhelp_basename = "pyexampledoc" htmlhelp_basename = "pyexampledoc"
extensions = ["sphinx.ext.autodoc", "autoapi.extension"] extensions = ["sphinx.ext.autodoc", "autoapi.extension"]
autoapi_type = "python" autoapi_type = "python"

View File

@ -107,3 +107,7 @@ def decorator_okay(func):
class Bar(Foo): class Bar(Foo):
def method_okay(self, foo=None, bar=None): def method_okay(self, foo=None, bar=None):
pass pass
class ClassWithNoInit:
pass

View File

@ -7,3 +7,4 @@ Autodoc Directives
.. autoapidecorator:: example.decorator_okay .. autoapidecorator:: example.decorator_okay
:noindex:

View File

@ -13,7 +13,6 @@ exclude_patterns = ["_build"]
pygments_style = "sphinx" pygments_style = "sphinx"
todo_include_todos = False todo_include_todos = False
html_theme = "alabaster" html_theme = "alabaster"
html_static_path = ["_static"]
htmlhelp_basename = "pyexampledoc" htmlhelp_basename = "pyexampledoc"
extensions = ["sphinx.ext.autodoc", "autoapi.extension"] extensions = ["sphinx.ext.autodoc", "autoapi.extension"]
autoapi_type = "python" autoapi_type = "python"

View File

@ -13,7 +13,6 @@ exclude_patterns = ["_build"]
pygments_style = "sphinx" pygments_style = "sphinx"
todo_include_todos = False todo_include_todos = False
html_theme = "alabaster" html_theme = "alabaster"
html_static_path = ["_static"]
htmlhelp_basename = "pyexampledoc" htmlhelp_basename = "pyexampledoc"
extensions = ["sphinx.ext.autodoc", "autoapi.extension"] extensions = ["sphinx.ext.autodoc", "autoapi.extension"]
autoapi_type = "python" autoapi_type = "python"

View File

@ -13,7 +13,6 @@ exclude_patterns = ["_build"]
pygments_style = "sphinx" pygments_style = "sphinx"
todo_include_todos = False todo_include_todos = False
html_theme = "alabaster" html_theme = "alabaster"
html_static_path = ["_static"]
htmlhelp_basename = "pypackagecomplexdoc" htmlhelp_basename = "pypackagecomplexdoc"
extensions = ["autoapi.extension"] extensions = ["autoapi.extension"]
autoapi_type = "python" autoapi_type = "python"

View File

@ -13,7 +13,6 @@ exclude_patterns = ["_build"]
pygments_style = "sphinx" pygments_style = "sphinx"
todo_include_todos = False todo_include_todos = False
html_theme = "alabaster" html_theme = "alabaster"
html_static_path = ["_static"]
htmlhelp_basename = "pypackageexampledoc" htmlhelp_basename = "pypackageexampledoc"
extensions = ["autoapi.extension"] extensions = ["autoapi.extension"]
autoapi_type = "python" autoapi_type = "python"

View File

@ -13,7 +13,6 @@ exclude_patterns = ["_build"]
pygments_style = "sphinx" pygments_style = "sphinx"
todo_include_todos = False todo_include_todos = False
html_theme = "alabaster" html_theme = "alabaster"
html_static_path = ["_static"]
htmlhelp_basename = "pyexampledoc" htmlhelp_basename = "pyexampledoc"
extensions = ["sphinx.ext.autodoc", "autoapi.extension"] extensions = ["sphinx.ext.autodoc", "autoapi.extension"]
autoapi_type = "python" autoapi_type = "python"

View File

@ -51,7 +51,7 @@ def builder():
class TestSimpleModule: class TestSimpleModule:
@pytest.fixture(autouse=True, scope="class") @pytest.fixture(autouse=True, scope="class")
def built(self, builder): def built(self, builder):
builder("pyexample") builder("pyexample", warningiserror=True, confoverrides={"suppress_warnings": ["app"]})
def test_integration(self): def test_integration(self):
self.check_integration("_build/text/autoapi/example/index.txt") self.check_integration("_build/text/autoapi/example/index.txt")