diff --git a/CHANGELOG.rst b/CHANGELOG.rst index dd31eb7..8a8fb35 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -3,6 +3,15 @@ Changelog Versions follow `Semantic Versioning `_ (``..``). +v1.8.2 (TBC) +------------------- + +Bug Fixes +^^^^^^^^^ + +* Fixed error when parsing a class with no constructor. + + v1.8.1 (2021-04-24) ------------------- diff --git a/autoapi/mappers/python/mapper.py b/autoapi/mappers/python/mapper.py index 655e5c8..686e9cd 100644 --- a/autoapi/mappers/python/mapper.py +++ b/autoapi/mappers/python/mapper.py @@ -380,6 +380,15 @@ class PythonSphinxMapper(SphinxMapperBase): ) 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) if lines and "autodoc-process-docstring" in self.app.events.events: self.app.emit( @@ -388,12 +397,6 @@ class PythonSphinxMapper(SphinxMapperBase): obj.docstring = "\n".join(lines) 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 if self.app.config.autoapi_member_order == "alphabetical": obj.children.sort(key=operator.attrgetter("name")) @@ -405,14 +408,25 @@ class PythonSphinxMapper(SphinxMapperBase): def _record_typehints(self, obj): if isinstance( obj, (PythonClass, PythonFunction, PythonMethod) - ) and not obj.obj.get("overloads"): + ) and not obj.overloads: 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: obj_annotations[name] = annotation - return_annotation = obj.obj.get("return_annotation") - if return_annotation: + return_annotation = obj_data["return_annotation"] + if include_return_annotation and return_annotation: obj_annotations["return"] = return_annotation self.app.env.autoapi_annotations[obj.id] = obj_annotations diff --git a/autoapi/mappers/python/objects.py b/autoapi/mappers/python/objects.py index 17a6301..310f8e1 100644 --- a/autoapi/mappers/python/objects.py +++ b/autoapi/mappers/python/objects.py @@ -1,3 +1,4 @@ +import functools from typing import Optional import sphinx.util.logging @@ -174,6 +175,10 @@ class PythonFunction(PythonPythonMapper): autodoc_typehints == "description" and not obj["overloads"] ) 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 """The type annotation for the return type of this function. @@ -199,18 +204,6 @@ class PythonFunction(PythonPythonMapper): :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): """The representation of a method.""" @@ -343,8 +336,6 @@ class PythonClass(PythonPythonMapper): def __init__(self, obj, **kwargs): super(PythonClass, self).__init__(obj, **kwargs) - self.args = obj["args"] - self.bases = obj["bases"] """The fully qualified names of all base classes. @@ -357,20 +348,34 @@ class PythonClass(PythonPythonMapper): :type: str """ - args = self._args + args = "" + + if self.constructor: + autodoc_typehints = getattr(self.app.config, "autodoc_typehints", "signature") + show_annotations = autodoc_typehints != "none" and not ( + autodoc_typehints == "description" and not self.constructor.overloads + ) + 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) - constructor = self.constructor - if constructor: - args = constructor.args + return args - if args.startswith("self"): - args = args[4:].lstrip(",").lstrip() + @property + def overloads(self): + overloads = [] - return args + 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 + ] - @args.setter - def args(self, value): - self._args = value + return overloads @property def docstring(self): @@ -404,6 +409,7 @@ class PythonClass(PythonPythonMapper): return self._children_of_type("class") @property + @functools.lru_cache() def constructor(self): for child in self.children: if child.short_name == "__init__": diff --git a/autoapi/mappers/python/parser.py b/autoapi/mappers/python/parser.py index abdb4ba..c79d239 100644 --- a/autoapi/mappers/python/parser.py +++ b/autoapi/mappers/python/parser.py @@ -86,22 +86,12 @@ class Parser: if astroid_utils.is_exception(node): 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)) data = { "type": type_, "name": node.name, "full_name": self._get_full_name(node.name), - "args": args, "bases": basenames, "doc": astroid_utils.get_class_docstring(node), "from_line_no": node.fromlineno, diff --git a/autoapi/templates/python/class.rst b/autoapi/templates/python/class.rst index a080ba8..053d8e6 100644 --- a/autoapi/templates/python/class.rst +++ b/autoapi/templates/python/class.rst @@ -1,12 +1,8 @@ {% if obj.display %} .. {{ obj.type }}:: {{ obj.short_name }}{% if obj.args %}({{ obj.args }}){% endif %} -{% if obj.constructor %} - -{% for (args, return_annotation) in obj.constructor.overloads %} - {% if args and args.startswith("self, ") %}{% set args = args[6:] %}{% endif %} +{% for (args, return_annotation) in obj.overloads %} {{ " " * (obj.type | length) }} {{ obj.short_name }}{% if args %}({{ args }}){% endif %} {% endfor %} -{% endif %} {% if obj.bases %} diff --git a/tests/python/py38positionalparams/conf.py b/tests/python/py38positionalparams/conf.py index a3292c7..ca3b9cc 100644 --- a/tests/python/py38positionalparams/conf.py +++ b/tests/python/py38positionalparams/conf.py @@ -13,7 +13,6 @@ exclude_patterns = ["_build"] pygments_style = "sphinx" todo_include_todos = False html_theme = "alabaster" -html_static_path = ["_static"] htmlhelp_basename = "pyexampledoc" extensions = ["sphinx.ext.autodoc", "autoapi.extension"] autoapi_type = "python" diff --git a/tests/python/py3example/conf.py b/tests/python/py3example/conf.py index b4c6a01..1415b0b 100644 --- a/tests/python/py3example/conf.py +++ b/tests/python/py3example/conf.py @@ -13,7 +13,6 @@ exclude_patterns = ["_build"] pygments_style = "sphinx" todo_include_todos = False html_theme = "alabaster" -html_static_path = ["_static"] htmlhelp_basename = "pyexampledoc" extensions = ["sphinx.ext.autodoc", "autoapi.extension"] autoapi_type = "python" diff --git a/tests/python/py3implicitnamespace/conf.py b/tests/python/py3implicitnamespace/conf.py index 53e4789..3cf98b6 100644 --- a/tests/python/py3implicitnamespace/conf.py +++ b/tests/python/py3implicitnamespace/conf.py @@ -13,7 +13,6 @@ exclude_patterns = ["_build"] pygments_style = "sphinx" todo_include_todos = False html_theme = "alabaster" -html_static_path = ["_static"] htmlhelp_basename = "py3implicitnamespacedoc" extensions = ["sphinx.ext.autodoc", "autoapi.extension"] autoapi_type = "python" diff --git a/tests/python/pyannotationcommentsexample/conf.py b/tests/python/pyannotationcommentsexample/conf.py index a3292c7..ca3b9cc 100644 --- a/tests/python/pyannotationcommentsexample/conf.py +++ b/tests/python/pyannotationcommentsexample/conf.py @@ -13,7 +13,6 @@ exclude_patterns = ["_build"] pygments_style = "sphinx" todo_include_todos = False html_theme = "alabaster" -html_static_path = ["_static"] htmlhelp_basename = "pyexampledoc" extensions = ["sphinx.ext.autodoc", "autoapi.extension"] autoapi_type = "python" diff --git a/tests/python/pyautodoc_typehints/conf.py b/tests/python/pyautodoc_typehints/conf.py index 4d34f5a..882bd22 100644 --- a/tests/python/pyautodoc_typehints/conf.py +++ b/tests/python/pyautodoc_typehints/conf.py @@ -13,7 +13,6 @@ exclude_patterns = ["_build"] pygments_style = "sphinx" todo_include_todos = False html_theme = "alabaster" -html_static_path = ["_static"] htmlhelp_basename = "pyexampledoc" extensions = ["sphinx.ext.autodoc", "autoapi.extension", "sphinx.ext.napoleon"] autoapi_type = "python" diff --git a/tests/python/pyexample/conf.py b/tests/python/pyexample/conf.py index a3292c7..ca3b9cc 100644 --- a/tests/python/pyexample/conf.py +++ b/tests/python/pyexample/conf.py @@ -13,7 +13,6 @@ exclude_patterns = ["_build"] pygments_style = "sphinx" todo_include_todos = False html_theme = "alabaster" -html_static_path = ["_static"] htmlhelp_basename = "pyexampledoc" extensions = ["sphinx.ext.autodoc", "autoapi.extension"] autoapi_type = "python" diff --git a/tests/python/pyexample/example/example.py b/tests/python/pyexample/example/example.py index 81a2809..b7c032a 100644 --- a/tests/python/pyexample/example/example.py +++ b/tests/python/pyexample/example/example.py @@ -107,3 +107,7 @@ def decorator_okay(func): class Bar(Foo): def method_okay(self, foo=None, bar=None): pass + + +class ClassWithNoInit: + pass diff --git a/tests/python/pyexample/manualapi.rst b/tests/python/pyexample/manualapi.rst index 78a5e6f..82826b7 100644 --- a/tests/python/pyexample/manualapi.rst +++ b/tests/python/pyexample/manualapi.rst @@ -7,3 +7,4 @@ Autodoc Directives .. autoapidecorator:: example.decorator_okay + :noindex: diff --git a/tests/python/pyiexample/conf.py b/tests/python/pyiexample/conf.py index 2382363..31b9218 100644 --- a/tests/python/pyiexample/conf.py +++ b/tests/python/pyiexample/conf.py @@ -13,7 +13,6 @@ exclude_patterns = ["_build"] pygments_style = "sphinx" todo_include_todos = False html_theme = "alabaster" -html_static_path = ["_static"] htmlhelp_basename = "pyexampledoc" extensions = ["sphinx.ext.autodoc", "autoapi.extension"] autoapi_type = "python" diff --git a/tests/python/pyiexample2/conf.py b/tests/python/pyiexample2/conf.py index 46a98a9..464950c 100644 --- a/tests/python/pyiexample2/conf.py +++ b/tests/python/pyiexample2/conf.py @@ -13,7 +13,6 @@ exclude_patterns = ["_build"] pygments_style = "sphinx" todo_include_todos = False html_theme = "alabaster" -html_static_path = ["_static"] htmlhelp_basename = "pyexampledoc" extensions = ["sphinx.ext.autodoc", "autoapi.extension"] autoapi_type = "python" diff --git a/tests/python/pypackagecomplex/conf.py b/tests/python/pypackagecomplex/conf.py index e96a5a6..2b77167 100644 --- a/tests/python/pypackagecomplex/conf.py +++ b/tests/python/pypackagecomplex/conf.py @@ -13,7 +13,6 @@ exclude_patterns = ["_build"] pygments_style = "sphinx" todo_include_todos = False html_theme = "alabaster" -html_static_path = ["_static"] htmlhelp_basename = "pypackagecomplexdoc" extensions = ["autoapi.extension"] autoapi_type = "python" diff --git a/tests/python/pypackageexample/conf.py b/tests/python/pypackageexample/conf.py index dbb6c3a..ead788f 100644 --- a/tests/python/pypackageexample/conf.py +++ b/tests/python/pypackageexample/conf.py @@ -13,7 +13,6 @@ exclude_patterns = ["_build"] pygments_style = "sphinx" todo_include_todos = False html_theme = "alabaster" -html_static_path = ["_static"] htmlhelp_basename = "pypackageexampledoc" extensions = ["autoapi.extension"] autoapi_type = "python" diff --git a/tests/python/pyskipexample/conf.py b/tests/python/pyskipexample/conf.py index 064f75a..44cbd31 100644 --- a/tests/python/pyskipexample/conf.py +++ b/tests/python/pyskipexample/conf.py @@ -13,7 +13,6 @@ exclude_patterns = ["_build"] pygments_style = "sphinx" todo_include_todos = False html_theme = "alabaster" -html_static_path = ["_static"] htmlhelp_basename = "pyexampledoc" extensions = ["sphinx.ext.autodoc", "autoapi.extension"] autoapi_type = "python" diff --git a/tests/python/test_pyintegration.py b/tests/python/test_pyintegration.py index cc8f0fd..e0d4f00 100644 --- a/tests/python/test_pyintegration.py +++ b/tests/python/test_pyintegration.py @@ -51,7 +51,7 @@ def builder(): class TestSimpleModule: @pytest.fixture(autouse=True, scope="class") def built(self, builder): - builder("pyexample") + builder("pyexample", warningiserror=True, confoverrides={"suppress_warnings": ["app"]}) def test_integration(self): self.check_integration("_build/text/autoapi/example/index.txt")