Added support for Python 3.11

pull/367/head
Ashley Whetter 1 year ago
parent 1556b17ca8
commit 94b255aecc

@ -6,7 +6,7 @@ jobs:
test:
strategy:
matrix:
python-version: [3.7, 3.8, 3.9, '3.10']
python-version: [3.7, 3.8, 3.9, '3.10', 3.11]
platform: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.platform }}
steps:
@ -25,7 +25,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- name: Set up Python 3.11
uses: actions/setup-python@v2
with:
python-version: 3.11
- name: Install dependencies
run: |
python -m pip install --upgrade pip setuptools wheel

@ -9,6 +9,7 @@ v2.0.1 (TBC)
Features
^^^^^^^^
* Can turn off the addition of documented objects to the TOC tree.
* Added support for Python 3.11.
Bug Fixes
^^^^^^^^^

@ -24,9 +24,9 @@ class AutoapiSummary(Autosummary): # pylint: disable=too-few-public-methods
if obj.overloads:
sig = "(\u2026)"
else:
sig = "({})".format(obj.args)
sig = f"({obj.args})"
if obj.return_annotation is not None:
sig += " \u2192 {}".format(obj.return_annotation)
sig += f" \u2192 {obj.return_annotation}"
else:
sig = ""

@ -12,7 +12,7 @@ from .mappers.python import (
PythonException,
)
# pylint: disable=attribute-defined-outside-init,no-self-use,unused-argument
# pylint: disable=attribute-defined-outside-init,unused-argument
class AutoapiDocumenter(autodoc.Documenter):
@ -93,7 +93,7 @@ class _AutoapiDocstringSignatureMixin: # pylint: disable=too-few-public-methods
if self.retann is None:
self.retann = self.object.return_annotation
return super(_AutoapiDocstringSignatureMixin, self).format_signature(**kwargs)
return super().format_signature(**kwargs)
class AutoapiFunctionDocumenter(
@ -130,7 +130,7 @@ class AutoapiDecoratorDocumenter(
if self.args is None:
self.args = self.format_args(**kwargs)
return super(AutoapiDecoratorDocumenter, self).format_signature(**kwargs)
return super().format_signature(**kwargs)
def format_args(self, **kwargs):
to_format = self.object.args
@ -170,8 +170,8 @@ class AutoapiClassDocumenter(
# TODO: Change sphinx to allow overriding of getting base names
if self.object.bases:
bases = [":class:`{}`".format(base) for base in self.object.bases]
self.add_line(" " + "Bases: {}".format(", ".join(bases)), sourcename)
bases = ", ".join(f":class:`{base}`" for base in self.object.bases)
self.add_line(f" Bases: {bases}", sourcename)
class AutoapiMethodDocumenter(
@ -189,7 +189,7 @@ class AutoapiMethodDocumenter(
return "(" + self.object.args + ")"
def import_object(self):
result = super(AutoapiMethodDocumenter, self).import_object()
result = super().import_object()
if result:
self.parent = self._method_parent
@ -213,7 +213,7 @@ class AutoapiMethodDocumenter(
"staticmethod",
):
if property_type in self.object.properties:
self.add_line(" :{}:".format(property_type), sourcename)
self.add_line(f" :{property_type}:", sourcename)
class AutoapiPropertyDocumenter(AutoapiDocumenter, autodoc.PropertyDocumenter):
@ -230,14 +230,14 @@ class AutoapiPropertyDocumenter(AutoapiDocumenter, autodoc.PropertyDocumenter):
sourcename = self.get_sourcename()
if self.options.annotation and self.options.annotation is not autodoc.SUPPRESS:
self.add_line(" :type: %s" % self.options.annotation, sourcename)
self.add_line(f" :type: {self.options.annotation}", sourcename)
for property_type in (
"abstractmethod",
"classmethod",
):
if property_type in self.object.properties:
self.add_line(" :{}:".format(property_type), sourcename)
self.add_line(f" :{property_type}:", sourcename)
class AutoapiDataDocumenter(AutoapiDocumenter, autodoc.DataDocumenter):
@ -255,13 +255,11 @@ class AutoapiDataDocumenter(AutoapiDocumenter, autodoc.DataDocumenter):
if not self.options.annotation:
# TODO: Change sphinx to allow overriding of object description
if self.object.value is not None:
self.add_line(
" :annotation: = {}".format(self.object.value), sourcename
)
self.add_line(f" :annotation: = {self.object.value}", sourcename)
elif self.options.annotation is autodoc.SUPPRESS:
pass
else:
self.add_line(" :annotation: %s" % self.options.annotation, sourcename)
self.add_line(f" :annotation: {self.options.annotation}", sourcename)
class AutoapiAttributeDocumenter(AutoapiDocumenter, autodoc.AttributeDocumenter):
@ -280,13 +278,11 @@ class AutoapiAttributeDocumenter(AutoapiDocumenter, autodoc.AttributeDocumenter)
if not self.options.annotation:
# TODO: Change sphinx to allow overriding of object description
if self.object.value is not None:
self.add_line(
" :annotation: = {}".format(self.object.value), sourcename
)
self.add_line(f" :annotation: = {self.object.value}", sourcename)
elif self.options.annotation is autodoc.SUPPRESS:
pass
else:
self.add_line(" :annotation: %s" % self.options.annotation, sourcename)
self.add_line(f" :annotation: {self.options.annotation}", sourcename)
class AutoapiModuleDocumenter(AutoapiDocumenter, autodoc.ModuleDocumenter):

@ -74,13 +74,9 @@ def run_autoapi(app): # pylint: disable=too-many-branches
Load AutoAPI data from the filesystem.
"""
if app.config.autoapi_type not in LANGUAGE_MAPPERS:
allowed = ", ".join(f'"{api_type}"' for api_type in sorted(LANGUAGE_MAPPERS))
raise ExtensionError(
"Invalid autoapi_type setting, "
"following values is allowed: {}".format(
", ".join(
'"{}"'.format(api_type) for api_type in sorted(LANGUAGE_MAPPERS)
)
)
f"Invalid autoapi_type setting, following values are allowed: {allowed}"
)
if not app.config.autoapi_dirs:
@ -100,8 +96,8 @@ def run_autoapi(app): # pylint: disable=too-many-branches
for _dir in normalised_dirs:
if not os.path.exists(_dir):
raise ExtensionError(
"AutoAPI Directory `{dir}` not found. "
"Please check your `autoapi_dirs` setting.".format(dir=_dir)
f"AutoAPI Directory `{_dir}` not found. "
"Please check your `autoapi_dirs` setting."
)
normalized_root = os.path.normpath(
@ -113,20 +109,13 @@ def run_autoapi(app): # pylint: disable=too-many-branches
import_name in sys.modules
for _, import_name in LANGUAGE_REQUIREMENTS[app.config.autoapi_type]
):
packages = ", ".join(
f'{import_name} (available as "{pkg_name}" on PyPI)'
for pkg_name, import_name in LANGUAGE_REQUIREMENTS[app.config.autoapi_type]
)
raise ExtensionError(
"AutoAPI of type `{type}` requires following "
"packages to be installed and included in extensions list: "
"{packages}".format(
type=app.config.autoapi_type,
packages=", ".join(
'{import_name} (available as "{pkg_name}" on PyPI)'.format(
pkg_name=pkg_name, import_name=import_name
)
for pkg_name, import_name in LANGUAGE_REQUIREMENTS[
app.config.autoapi_type
]
),
)
"AutoAPI of type `{app.config.autoapi_type}` requires following "
f"packages to be installed and included in extensions list: {packages}"
)
sphinx_mapper = LANGUAGE_MAPPERS[app.config.autoapi_type]
@ -202,7 +191,7 @@ def doctree_read(app, doctree):
all_docs = set()
insert = True
nodes = list(doctree.traverse(toctree))
toc_entry = "%s/index" % app.config.autoapi_root
toc_entry = f"{app.config.autoapi_root}/index"
add_entry = (
nodes
and app.config.autoapi_generate_api_docs
@ -220,12 +209,10 @@ def doctree_read(app, doctree):
insert = False
if insert and app.config.autoapi_add_toctree_entry:
# Insert AutoAPI index
nodes[-1]["entries"].append((None, "%s/index" % app.config.autoapi_root))
nodes[-1]["includefiles"].append("%s/index" % app.config.autoapi_root)
nodes[-1]["entries"].append((None, f"{app.config.autoapi_root}/index"))
nodes[-1]["includefiles"].append(f"{app.config.autoapi_root}/index")
message_prefix = bold("[AutoAPI] ")
message = darkgreen(
"Adding AutoAPI TOCTree [{0}] to index.rst".format(toc_entry)
)
message = darkgreen(f"Adding AutoAPI TOCTree [{toc_entry}] to index.rst")
LOGGER.info(message_prefix + message)
@ -259,11 +246,12 @@ def viewcode_find(app, modname):
stack.extend((full_name + ".", gchild) for gchild in children)
if module.obj["encoding"]:
source = io.open(
module.obj["file_path"], encoding=module.obj["encoding"]
).read()
stream = io.open(module.obj["file_path"], encoding=module.obj["encoding"])
else:
source = open(module.obj["file_path"]).read()
stream = open(module.obj["file_path"])
with stream as in_f:
source = in_f.read()
result = (source, locations)
_VIEWCODE_CACHE[modname] = result
@ -271,7 +259,7 @@ def viewcode_find(app, modname):
def viewcode_follow_imported(app, modname, attribute):
fullname = "{}.{}".format(modname, attribute)
fullname = f"{modname}.{attribute}"
all_objects = app.env.autoapi_all_objects
if fullname not in all_objects:
return None

@ -35,9 +35,7 @@ def _import_class(name, currmodule):
if not target:
raise sphinx.ext.inheritance_diagram.InheritanceException(
"Could not import class or module {} specified for inheritance diagram".format(
name
)
f"Could not import class or module {name} specified for inheritance diagram"
)
if isinstance(target, astroid.ClassDef):
@ -51,7 +49,7 @@ def _import_class(name, currmodule):
return classes
raise sphinx.ext.inheritance_diagram.InheritanceException(
"{} specified for inheritance diagram is not a class or module".format(name)
f"{name} specified for inheritance diagram is not a class or module"
)
@ -128,6 +126,6 @@ class AutoapiInheritanceDiagram(sphinx.ext.inheritance_diagram.InheritanceDiagra
old_graph = sphinx.ext.inheritance_diagram.InheritanceGraph
sphinx.ext.inheritance_diagram.InheritanceGraph = _AutoapiInheritanceGraph
try:
return super(AutoapiInheritanceDiagram, self).run()
return super().run()
finally:
sphinx.ext.inheritance_diagram.InheritanceGraph = old_graph

@ -74,14 +74,10 @@ class PythonMapperBase:
ctx = {}
try:
template = self.jinja_env.get_template(
"{language}/{type}.rst".format(language=self.language, type=self.type)
)
template = self.jinja_env.get_template(f"{self.language}/{self.type}.rst")
except TemplateNotFound:
# Use a try/except here so we fallback to language specific defaults, over base defaults
template = self.jinja_env.get_template(
"base/{type}.rst".format(type=self.type)
)
template = self.jinja_env.get_template(f"base/{self.type}.rst")
ctx.update(**self.get_context_data())
ctx.update(**kwargs)
@ -104,10 +100,10 @@ class PythonMapperBase:
"""Object sorting comparison"""
if isinstance(other, PythonMapperBase):
return self.id < other.id
return super(PythonMapperBase, self).__lt__(other)
return super().__lt__(other)
def __str__(self):
return "<{cls} {id}>".format(cls=self.__class__.__name__, id=self.id)
return f"<{self.__class__.__name__} {self.id}>"
@property
def short_name(self):
@ -253,7 +249,7 @@ class SphinxMapperBase:
):
LOGGER.info(
bold("[AutoAPI] ")
+ darkgreen("Ignoring %s/%s" % (root, filename))
+ darkgreen(f"Ignoring {root}/{filename}")
)
skip = True

@ -46,7 +46,7 @@ class GoSphinxMapper(SphinxMapperBase):
_ignore = kwargs.get("ignore")
if _ignore:
parser_command.extend(["-e", "{0}".format("|".join(_ignore))])
parser_command.extend(["-e", "|".join(_ignore)])
parser_command.append(path)
@ -55,13 +55,13 @@ class GoSphinxMapper(SphinxMapperBase):
return parsed_data
except IOError:
LOGGER.warning(
"Error reading file: {0}".format(path),
f"Error reading file: {path}",
type="autoapi",
subtype="not_readable",
)
except TypeError:
LOGGER.warning(
"Error reading file: {0}".format(path),
f"Error reading file: {path}",
type="autoapi",
subtype="not_readable",
)
@ -86,13 +86,13 @@ class GoSphinxMapper(SphinxMapperBase):
try:
# Contextual type data from children recursion
if _type:
LOGGER.debug("Forcing Go Type %s" % _type)
LOGGER.debug(f"Forcing Go Type {_type}")
cls = obj_map[_type]
else:
cls = obj_map[data["type"]]
except KeyError:
# this warning intentionally has no (sub-)type
LOGGER.warning("Unknown type: %s" % data)
LOGGER.warning(f"Unknown type: {data}")
else:
if cls.inverted_names and "names" in data:
# Handle types that have reversed names parameter
@ -128,7 +128,7 @@ class GoPythonMapper(PythonMapperBase):
inverted_names = False
def __init__(self, obj, **kwargs):
super(GoPythonMapper, self).__init__(obj, **kwargs)
super().__init__(obj, **kwargs)
self.name = obj.get("name") or obj.get("packageName")
self.id = self.name
@ -149,7 +149,7 @@ class GoPythonMapper(PythonMapperBase):
self.bugs = obj.get("bugs", [])
def __str__(self):
return "<{cls} {id}>".format(cls=self.__class__.__name__, id=self.id)
return f"<{self.__class__.__name__} {self.id}>"
@property
def short_name(self):
@ -186,7 +186,7 @@ class GoMethod(GoPythonMapper):
ref_directive = "meth"
def __init__(self, obj, **kwargs):
super(GoMethod, self).__init__(obj, **kwargs)
super().__init__(obj, **kwargs)
self.receiver = obj.get("recv")

@ -35,13 +35,13 @@ class JavaScriptSphinxMapper(SphinxMapperBase):
return parsed_data
except IOError:
LOGGER.warning(
"Error reading file: {0}".format(path),
f"Error reading file: {path}",
type="autoapi",
subtype="not_readable",
)
except TypeError:
LOGGER.warning(
"Error reading file: {0}".format(path),
f"Error reading file: {path}",
type="autoapi",
subtype="not_readable",
)
@ -80,7 +80,7 @@ class JavaScriptSphinxMapper(SphinxMapperBase):
cls = obj_map[data["kind"]]
except (KeyError, TypeError):
# this warning intentionally has no (sub-)type
LOGGER.warning("Unknown type: %s" % data)
LOGGER.warning(f"Unknown type: {data}")
else:
# Recurse for children
obj = cls(data, jinja_env=self.jinja_env, app=self.app)
@ -103,7 +103,7 @@ class JavaScriptPythonMapper(PythonMapperBase):
so we try and keep standard naming to keep templates more re-usable.
"""
super(JavaScriptPythonMapper, self).__init__(obj, **kwargs)
super().__init__(obj, **kwargs)
self.name = obj.get("name")
self.id = self.name

@ -58,7 +58,7 @@ def get_full_import_name(import_from, name):
import_from.modname, level=import_from.level
)
return "{}.{}".format(module_name, partial_basename)
return f"{module_name}.{partial_basename}"
def resolve_qualname(node, basename):
@ -98,7 +98,7 @@ def resolve_qualname(node, basename):
full_basename = assignment.qname()
break
if isinstance(assignment, astroid.nodes.AssignName):
full_basename = "{}.{}".format(assignment.scope().qname(), assignment.name)
full_basename = f"{assignment.scope().qname()}.{assignment.name}"
if isinstance(node, astroid.nodes.Call):
full_basename = re.sub(r"\(.*\)", "()", full_basename)

@ -51,9 +51,7 @@ def _expand_wildcard_placeholder(original_module, originals_map, placeholder):
continue
if name not in originals_map:
msg = "Invalid __all__ entry {0} in {1}".format(
name, original_module["name"]
)
msg = f"Invalid __all__ entry {name} in {original_module['name']}"
LOGGER.warning(msg, type="autoapi", subtype="python_import_resolution")
continue
@ -115,9 +113,7 @@ def _resolve_module_placeholders(modules, module_name, visit_path, resolved):
continue
if imported_from not in modules:
msg = "Cannot resolve import of unknown module {0} in {1}".format(
imported_from, module_name
)
msg = f"Cannot resolve import of unknown module {imported_from} in {module_name}"
LOGGER.warning(msg, type="autoapi", subtype="python_import_resolution")
module["children"].remove(child)
children.pop(child["name"])
@ -143,9 +139,7 @@ def _resolve_module_placeholders(modules, module_name, visit_path, resolved):
original = originals_map[new_placeholder["name"]]
_resolve_placeholder(new_placeholder, original)
elif original_name not in modules[imported_from][1]:
msg = "Cannot resolve import of {0} in {1}".format(
child["original_path"], module_name
)
msg = f"Cannot resolve import of {child['original_path']} in {module_name}"
LOGGER.warning(msg, type="autoapi", subtype="python_import_resolution")
module["children"].remove(child)
children.pop(child["name"])
@ -246,7 +240,7 @@ class PythonSphinxMapper(SphinxMapperBase):
}
def __init__(self, app, template_dir=None, url_root=None):
super(PythonSphinxMapper, self).__init__(app, template_dir, url_root)
super().__init__(app, template_dir, url_root)
self.jinja_env.filters["link_objs"] = _link_objs
self._use_implicit_namespace = (
@ -325,7 +319,7 @@ class PythonSphinxMapper(SphinxMapperBase):
except (IOError, TypeError, ImportError):
LOGGER.debug("Reason:", exc_info=True)
LOGGER.warning(
"Unable to read file: {0}".format(path),
f"Unable to read file: {path}",
type="autoapi",
subtype="not_readable",
)
@ -347,14 +341,14 @@ class PythonSphinxMapper(SphinxMapperBase):
self._resolve_placeholders()
self.app.env.autoapi_annotations = {}
super(PythonSphinxMapper, self).map(options)
super().map(options)
parents = {obj.name: obj for obj in self.objects.values()}
for obj in self.objects.values():
parent_name = obj.name.rsplit(".", 1)[0]
if parent_name in parents and parent_name != obj.name:
parent = parents[parent_name]
attr = "sub{}s".format(obj.type)
attr = f"sub{obj.type}s"
getattr(parent, attr).append(obj)
for obj in self.objects.values():
@ -373,7 +367,7 @@ class PythonSphinxMapper(SphinxMapperBase):
cls = self._OBJ_MAP[data["type"]]
except KeyError:
# this warning intentionally has no (sub-)type
LOGGER.warning("Unknown type: %s" % data["type"])
LOGGER.warning(f"Unknown type: {data['type']}")
else:
obj = cls(
data,

@ -15,11 +15,11 @@ def _format_args(args_info, include_annotations=True, ignore_self=None):
for i, (prefix, name, annotation, default) in enumerate(args_info):
if i == 0 and ignore_self is not None and name == ignore_self:
continue
formatted = "{}{}{}{}".format(
prefix or "",
name or "",
": {}".format(annotation) if annotation and include_annotations else "",
(" = {}" if annotation else "={}").format(default) if default else "",
formatted = (
(prefix or "")
+ (name or "")
+ (f": {annotation}" if annotation and include_annotations else "")
+ ((" = {}" if annotation else "={}").format(default) if default else "")
)
result.append(formatted)
@ -42,7 +42,7 @@ class PythonPythonMapper(PythonMapperBase):
member_order = 0
def __init__(self, obj, class_content="class", **kwargs):
super(PythonPythonMapper, self).__init__(obj, **kwargs)
super().__init__(obj, **kwargs)
self.name = obj["name"]
self.id = obj.get("full_name", self.name)
@ -172,7 +172,7 @@ class PythonFunction(PythonPythonMapper):
member_order = 30
def __init__(self, obj, **kwargs):
super(PythonFunction, self).__init__(obj, **kwargs)
super().__init__(obj, **kwargs)
autodoc_typehints = getattr(self.app.config, "autodoc_typehints", "signature")
show_annotations = autodoc_typehints != "none" and not (
@ -217,7 +217,7 @@ class PythonMethod(PythonFunction):
member_order = 50
def __init__(self, obj, **kwargs):
super(PythonMethod, self).__init__(obj, **kwargs)
super().__init__(obj, **kwargs)
self.properties = obj["properties"]
"""The properties that describe what type of method this is.
@ -228,7 +228,7 @@ class PythonMethod(PythonFunction):
"""
def _should_skip(self): # type: () -> bool
skip = super(PythonMethod, self)._should_skip() or self.name in (
skip = super()._should_skip() or self.name in (
"__new__",
"__init__",
)
@ -242,7 +242,7 @@ class PythonProperty(PythonPythonMapper):
member_order = 60
def __init__(self, obj, **kwargs):
super(PythonProperty, self).__init__(obj, **kwargs)
super().__init__(obj, **kwargs)
self.annotation = obj["return_annotation"]
"""The type annotation of this property.
@ -265,7 +265,7 @@ class PythonData(PythonPythonMapper):
member_order = 40
def __init__(self, obj, **kwargs):
super(PythonData, self).__init__(obj, **kwargs)
super().__init__(obj, **kwargs)
self.value = obj.get("value")
"""The value of this attribute.
@ -297,7 +297,7 @@ class TopLevelPythonPythonMapper(PythonPythonMapper):
_RENDER_LOG_LEVEL = "VERBOSE"
def __init__(self, obj, **kwargs):
super(TopLevelPythonPythonMapper, self).__init__(obj, **kwargs)
super().__init__(obj, **kwargs)
self.top_level_object = "." not in self.name
"""Whether this object is at the very top level (True) or not (False).
@ -354,7 +354,7 @@ class PythonClass(PythonPythonMapper):
member_order = 20
def __init__(self, obj, **kwargs):
super(PythonClass, self).__init__(obj, **kwargs)
super().__init__(obj, **kwargs)
self.bases = obj["bases"]
"""The fully qualified names of all base classes.
@ -411,7 +411,7 @@ class PythonClass(PythonPythonMapper):
if constructor_docstring:
if self._class_content == "both":
docstring = "{0}\n{1}".format(docstring, constructor_docstring)
docstring = f"{docstring}\n{constructor_docstring}"
else:
docstring = constructor_docstring

@ -4,8 +4,7 @@ load-plugins=
[MESSAGES CONTROL]
disable=bad-continuation,
duplicate-code,
disable=duplicate-code,
fixme,
import-error,
missing-class-docstring,

@ -25,6 +25,7 @@ classifiers =
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Programming Language :: Python :: 3.10
Programming Language :: Python :: 3.11
project_urls =
Documentation = https://sphinx-autoapi.readthedocs.io/en/latest/

@ -2,7 +2,7 @@
isolated_build = true
envlist =
# Keep this in sync with .github/workflows/main.yml
py{37,38,39,310}
py{37,38,39,310,311}
formatting
lint
docs
@ -27,7 +27,7 @@ commands =
[testenv:lint]
skip_install = true
deps =
pylint~=2.4.2
pylint~=2.15.9
commands =
pylint {posargs:autoapi}

Loading…
Cancel
Save