New Upstream Release - sphinx-autoapi

Ready changes

Summary

Merged new upstream version: 2.1.0 (was: 2.0.0).

Diff

diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index dfdda0a..fd0c92f 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -1,34 +1,27 @@
 name: tests
 
-on: [push, pull_request]
+on:
+  - push
+  - pull_request
 
 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:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
+        with:
+          fetch-depth: 0
       - name: Set up Python ${{ matrix.python-version }}
-        uses: actions/setup-python@v2
+        uses: actions/setup-python@v4
         with:
           python-version: ${{ matrix.python-version }}
       - name: Install dependencies
         run: |
           python -m pip install --upgrade pip setuptools wheel
-          python -m pip install tox
+          python -m pip install tox tox-gh-actions
       - name: Run tests
-        run: tox -e py
-  lint:
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v2
-      - uses: actions/setup-python@v2
-      - name: Install dependencies
-        run: |
-          python -m pip install --upgrade pip setuptools wheel
-          python -m pip install tox
-      - name: Lint
-        run: tox -e formatting,lint
+        run: tox
diff --git a/.gitignore b/.gitignore
index c86c4a9..a6137f2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,3 +16,5 @@ _api_
 .pytest_cache/
 build/
 dist/
+tests/python/pyexample/autoapi/example/index.rst
+tests/python/pymovedconfpy/autoapi/example/index.rst
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 9cc4890..6a1b498 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -3,14 +3,58 @@ Changelog
 
 Versions follow `Semantic Versioning <https://semver.org/>`_ (``<major>.<minor>.<patch>``).
 
+.. towncrier release notes start
+
+v2.1.0 (2023-03-28)
+-------------------
+
+Deprecations and Removals
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+- Support for documenting languages other than Python is deprecated. (#248)
+- Removed the option to have autoapi generate toctree entries for domain objects.
+  Domain objects are now added to the toctree by Sphinx.
+  Dropped support for sphinx < 5.2.0. (#369)
+
+
+Misc
+^^^^
+
+- Added basic type checking.
+- Integrated towncrier into the release workflow.
+
+
+v2.0.1 (2023-01-16)
+-------------------
+
+Features
+^^^^^^^^
+- Can turn off the addition of documented objects to the TOC tree.
+- Added support for Python 3.11.
+
+Bug Fixes
+^^^^^^^^^
+- `#330 <https://github.com/readthedocs/sphinx-autoapi/issues/330>`: (Python)
+  Render tuple values as tuples, not lists.
+- `#341 <https://github.com/readthedocs/sphinx-autoapi/issues/341>`: (Python)
+  Fix module level assignments to class attributes being documented as
+  module level attributes.
+- (Python) Fix "bysource" sort order showing items in alphabetical order.
+- (Python) Use the correct directives for a variable type and value.
+
+Trivial/Internal Changes
+^^^^^^^^^^^^^^^^^^^^^^^^
+- Removed some autogenerated test data from the repository.
+
+
 v2.0.0 (2022-09-27)
 -------------------
 
 Breaking Changes
 ^^^^^^^^^^^^^^^^
 
-* Dropped support for Sphinx <4.
-* `#352 <https://github.com/readthedocs/sphinx-autoapi/issues/352>`: (Python)
+- Dropped support for Sphinx <4.
+- `#352 <https://github.com/readthedocs/sphinx-autoapi/issues/352>`: (Python)
   Properties are rendered with the ``property`` directive,
   fixing support for Sphinx 5.2.
   A new ``PythonPythonMapper`` object (``PythonProperty``) has been created
@@ -20,8 +64,8 @@ Breaking Changes
 
 Trivial/Internal Changes
 ^^^^^^^^^^^^^^^^^^^^^^^^
-* Use https links where possible in documentation.
-* Pass correct argument types to ``status_iterator``.
+- Use https links where possible in documentation.
+- Pass correct argument types to ``status_iterator``.
 
 
 V1.9.0 (2022-07-25)
@@ -30,33 +74,33 @@ V1.9.0 (2022-07-25)
 Breaking Changes
 ^^^^^^^^^^^^^^^^
 
-* Dropped support for Python 3.6.
+- Dropped support for Python 3.6.
 
 Features
 ^^^^^^^^
 
-* Added support for Python 3.10.
-* `#222 <https://github.com/readthedocs/sphinx-autoapi/issues/222>`:
+- Added support for Python 3.10.
+- `#222 <https://github.com/readthedocs/sphinx-autoapi/issues/222>`:
   Marked extension as parallel read safe.
 
 Bug Fixes
 ^^^^^^^^^
-* `#324 <https://github.com/readthedocs/sphinx-autoapi/issues/324>`: (Python)
+- `#324 <https://github.com/readthedocs/sphinx-autoapi/issues/324>`: (Python)
   Fail elegantly when no source files are found.
-* (Python) Stop calling ``autodoc-process-docstring`` when docstring is empty.
+- (Python) Stop calling ``autodoc-process-docstring`` when docstring is empty.
   Works around https://github.com/sphinx-doc/sphinx/issues/10701.
-* `#318 <https://github.com/readthedocs/sphinx-autoapi/issues/318>`: (Python)
+- `#318 <https://github.com/readthedocs/sphinx-autoapi/issues/318>`: (Python)
   Fixed misaligned argument types on methods/classmethods when using type comments.
-* `#278 <https://github.com/readthedocs/sphinx-autoapi/issues/278>`: (Python)
+- `#278 <https://github.com/readthedocs/sphinx-autoapi/issues/278>`: (Python)
   Limit signatures to 60 characters in summaries.
-* Fix keyerror when using markdown sources.
-* `#328 <https://github.com/readthedocs/sphinx-autoapi/issues/328>`: (Python)
+- Fix keyerror when using markdown sources.
+- `#328 <https://github.com/readthedocs/sphinx-autoapi/issues/328>`: (Python)
   Fix kw-only marker getting ignored if first in the signature.
 
 Trivial/Internal Changes
 ^^^^^^^^^^^^^^^^^^^^^^^^
-* Fixed tests in Sphinx 5.
-* Fixed many typos throughout the documentation.
+- Fixed tests in Sphinx 5.
+- Fixed many typos throughout the documentation.
 
 
 v1.8.4 (2021-08-16)
@@ -64,7 +108,7 @@ v1.8.4 (2021-08-16)
 
 Bug Fixes
 ^^^^^^^^^
-* `#301 <https://github.com/readthedocs/sphinx-autoapi/issues/301>`: (Python)
+- `#301 <https://github.com/readthedocs/sphinx-autoapi/issues/301>`: (Python)
   Fixed compatibility with astroid 2.7+.
 
 
@@ -73,13 +117,13 @@ v1.8.3 (2021-07-31)
 
 Bug Fixes
 ^^^^^^^^^
-* `#299 <https://github.com/readthedocs/sphinx-autoapi/issues/299>`: (Python)
+- `#299 <https://github.com/readthedocs/sphinx-autoapi/issues/299>`: (Python)
   Fixed incorrect indentation in generated documentation when a class with no
   constructor has a summary line spanning multiple lines.
 
 Trivial/Internal Changes
 ^^^^^^^^^^^^^^^^^^^^^^^^
-* Fixed broken link to Jinja objects.inv.
+- Fixed broken link to Jinja objects.inv.
 
 
 v1.8.2 (2021-07-26)
@@ -88,14 +132,14 @@ v1.8.2 (2021-07-26)
 Bug Fixes
 ^^^^^^^^^
 
-* Fixed error when parsing a class with no constructor.
-* `#293 <https://github.com/readthedocs/sphinx-autoapi/issues/293>`:
+- Fixed error when parsing a class with no constructor.
+- `#293 <https://github.com/readthedocs/sphinx-autoapi/issues/293>`:
   Fixed failure to build out of source conf.py files.
   Configuration values using relative values are now relative to the source directory
   instead of relative to the conf.py file.
-* `#289 <https://github.com/readthedocs/sphinx-autoapi/issues/289>`: (Python)
+- `#289 <https://github.com/readthedocs/sphinx-autoapi/issues/289>`: (Python)
   Fixed AttributeError using inheritance diagrams on a module with plain imports.
-* `#292 <https://github.com/readthedocs/sphinx-autoapi/issues/292>`:
+- `#292 <https://github.com/readthedocs/sphinx-autoapi/issues/292>`:
   Explicitly use the domain for generated directives.
 
 
@@ -105,7 +149,7 @@ v1.8.1 (2021-04-24)
 Bug Fixes
 ^^^^^^^^^
 
-* `#273 <https://github.com/readthedocs/sphinx-autoapi/issues/273>`:
+- `#273 <https://github.com/readthedocs/sphinx-autoapi/issues/273>`:
   Fixed type annotations being shown for only a single module.
 
 
@@ -115,38 +159,38 @@ v1.8.0 (2021-04-12)
 Features
 ^^^^^^^^
 
-* Expandable value for multi-line string attributes.
-* `#265 <https://github.com/readthedocs/sphinx-autoapi/issues/265>`:
+- Expandable value for multi-line string attributes.
+- `#265 <https://github.com/readthedocs/sphinx-autoapi/issues/265>`:
   Can resolve the qualified paths of parameters to generics.
-* `#275 <https://github.com/readthedocs/sphinx-autoapi/issues/275>`:
+- `#275 <https://github.com/readthedocs/sphinx-autoapi/issues/275>`:
   Warnings have been categorised and can be suppressed through ``suppress_warnings``.
-* `#280 <https://github.com/readthedocs/sphinx-autoapi/issues/280>`:
+- `#280 <https://github.com/readthedocs/sphinx-autoapi/issues/280>`:
   Data attributes are documented in module summaries.
 
 Bug Fixes
 ^^^^^^^^^
 
-* `#273 <https://github.com/readthedocs/sphinx-autoapi/issues/273>`:
+- `#273 <https://github.com/readthedocs/sphinx-autoapi/issues/273>`:
   Fixed setting ``autodoc_typehints`` to ``none`` or ``description``
   not turning off signature type hints.
   ``autodoc_typehints`` integration is considered experimental until
   the extension properly supports overload functions.
-* `#261 <https://github.com/readthedocs/sphinx-autoapi/issues/261>`:
+- `#261 <https://github.com/readthedocs/sphinx-autoapi/issues/261>`:
   Fixed data annotations causing pickle or deepcopy errors.
-* Documentation can be generated when multiple source directories
+- Documentation can be generated when multiple source directories
   share a single ``conf.py`` file.
 
 Trivial/Internal Changes
 ^^^^^^^^^^^^^^^^^^^^^^^^
 
-* Fixed ``DeprecationWarning`` for invalid escape sequence ``\s`` in tests.
-* Fixed ``FutureWarning`` for ``Node.traverse()`` becoming an iterator instead of list.
-* New example implementation of ``autoapi-skip-member`` Sphinx event.
-* Can run tests with tox 4.
-* Updated packaging to use PEP-517.
-* All unittest style tests have been converted to pytest style tests.
-* An exception raised by docfx is raised directly instead of wrapping it.
-* Started using Github Actions for continuous integration.
+- Fixed ``DeprecationWarning`` for invalid escape sequence ``\s`` in tests.
+- Fixed ``FutureWarning`` for ``Node.traverse()`` becoming an iterator instead of list.
+- New example implementation of ``autoapi-skip-member`` Sphinx event.
+- Can run tests with tox 4.
+- Updated packaging to use PEP-517.
+- All unittest style tests have been converted to pytest style tests.
+- An exception raised by docfx is raised directly instead of wrapping it.
+- Started using Github Actions for continuous integration.
 
 
 V1.7.0 (2021-01-31)
@@ -155,17 +199,17 @@ V1.7.0 (2021-01-31)
 Features
 ^^^^^^^^
 
-* The fully qualified path of objects are included type annotations
+- The fully qualified path of objects are included type annotations
   so that Sphinx can link to them.
-* Added support for Sphinx 3.3. and 3.4.
-* `#240 <https://github.com/readthedocs/sphinx-autoapi/issues/240>`:
+- Added support for Sphinx 3.3. and 3.4.
+- `#240 <https://github.com/readthedocs/sphinx-autoapi/issues/240>`:
   The docstrings of ``object.__init__``, ``object.__new__``,
   ``type.__init__``, and ``type.__new__`` are not inherited.
 
 Bug Fixes
 ^^^^^^^^^
 
-* `#260 <https://github.com/readthedocs/sphinx-autoapi/issues/260>`:
+- `#260 <https://github.com/readthedocs/sphinx-autoapi/issues/260>`:
   The overload signatures of ``__init__`` methods are documented.
 
 
@@ -175,33 +219,33 @@ V1.6.0 (2021-01-20)
 Breaking Changes
 ^^^^^^^^^^^^^^^^
 
-* Dropped support for Python 2 and Sphinx 1.x/2.x.
+- Dropped support for Python 2 and Sphinx 1.x/2.x.
   Python 2 source code can still be parsed.
 
 Features
 ^^^^^^^^
 
-* (Python) Added support for using type hints as parameter types and return types
+- (Python) Added support for using type hints as parameter types and return types
   via the ``sphinx.ext.autodoc.typehints`` extension.
-* `#191 <https://github.com/readthedocs/sphinx-autoapi/issues/191>`:
+- `#191 <https://github.com/readthedocs/sphinx-autoapi/issues/191>`:
   Basic incremental build support is enabled ``autoapi_keep_files`` is enabled.
   Providing none of the source files have changed,
   AutoAPI will skip parsing the source code and regenerating the API documentation.
-* `#200 <https://github.com/readthedocs/sphinx-autoapi/issues/200>`:
+- `#200 <https://github.com/readthedocs/sphinx-autoapi/issues/200>`:
   Can pass a callback that edits the Jinja Environment object before
   template rendering begins.
   This allows custom filters, tests, and globals to be added to the environment.
-* Added support for Python 3.9.
+- Added support for Python 3.9.
 
 Bug Fixes
 ^^^^^^^^^
 
-* `#246 <https://github.com/readthedocs/sphinx-autoapi/issues/246>`: (Python)
+- `#246 <https://github.com/readthedocs/sphinx-autoapi/issues/246>`: (Python)
   Fixed TypeError when parsing a class that inherits from ``type``.
-* `#244 <https://github.com/readthedocs/sphinx-autoapi/issues/244>`:
+- `#244 <https://github.com/readthedocs/sphinx-autoapi/issues/244>`:
   Fixed an unnecessary deprecation warning being raised when running
   sphinx-build from the same directory as conf.py.
-* (Python) Fixed properties documented by Autodoc directives getting documented as methods.
+- (Python) Fixed properties documented by Autodoc directives getting documented as methods.
 
 
 V1.5.1 (2020-10-01)
@@ -210,7 +254,7 @@ V1.5.1 (2020-10-01)
 Bug Fixes
 ^^^^^^^^^
 
-* Fixed AttributeError when generating an inheritance diagram for a module.
+- Fixed AttributeError when generating an inheritance diagram for a module.
 
 
 V1.5.0 (2020-08-31)
@@ -221,28 +265,28 @@ This will be the last minor version to support Python 2 and Sphinx 1.x/2.x.
 Features
 ^^^^^^^^
 
-* `#222 <https://github.com/readthedocs/sphinx-autoapi/issues/222>`:
+- `#222 <https://github.com/readthedocs/sphinx-autoapi/issues/222>`:
   Declare the extension as parallel unsafe.
-* `#217 <https://github.com/readthedocs/sphinx-autoapi/issues/217>`: (Python)
+- `#217 <https://github.com/readthedocs/sphinx-autoapi/issues/217>`: (Python)
   All overload signatures are documented.
-* `#243 <https://github.com/readthedocs/sphinx-autoapi/issues/243>`:
+- `#243 <https://github.com/readthedocs/sphinx-autoapi/issues/243>`:
   Files are found in order of preference according to ``autoapi_file_patterns``.
-* Added support for Sphinx 3.2.
+- Added support for Sphinx 3.2.
 
 Bug Fixes
 ^^^^^^^^^
 
-* `#219 <https://github.com/readthedocs/sphinx-autoapi/issues/219>`: (Python)
+- `#219 <https://github.com/readthedocs/sphinx-autoapi/issues/219>`: (Python)
   Fixed return types not showing for methods.
-* (Python) Fixed incorrect formatting of properties on generated method directives.
-* Fixed every toctree entry getting added as a new list.
-* `#234 <https://github.com/readthedocs/sphinx-autoapi/issues/234>`:
+- (Python) Fixed incorrect formatting of properties on generated method directives.
+- Fixed every toctree entry getting added as a new list.
+- `#234 <https://github.com/readthedocs/sphinx-autoapi/issues/234>`:
   Fixed only some entries getting added to the toctree.
 
 Trivial/Internal Changes
 ^^^^^^^^^^^^^^^^^^^^^^^^
 
-* autoapisummary directive inherits from autosummary for future stability.
+- autoapisummary directive inherits from autosummary for future stability.
 
 
 v1.4.0 (2020-06-07)
@@ -251,16 +295,16 @@ v1.4.0 (2020-06-07)
 Features
 ^^^^^^^^
 
-* `#197 <https://github.com/readthedocs/sphinx-autoapi/issues/197>`: Added
+- `#197 <https://github.com/readthedocs/sphinx-autoapi/issues/197>`: Added
   ``autoapi.__version__`` and ``autoapi.__version_info__`` attributes
   for accessing version information.
-* `#201 <https://github.com/readthedocs/sphinx-autoapi/issues/201>`: (Python)
+- `#201 <https://github.com/readthedocs/sphinx-autoapi/issues/201>`: (Python)
   Added the ``autoapi_member_order`` option to allow the order that members
   are documented to be configurable.
-* `#203 <https://github.com/readthedocs/sphinx-autoapi/issues/203>`: (Python)
+- `#203 <https://github.com/readthedocs/sphinx-autoapi/issues/203>`: (Python)
   A class without a docstring inherits one from its parent.
   A methods without a docstring inherits one from the method that it overrides.
-* `#204 <https://github.com/readthedocs/sphinx-autoapi/issues/204>`: (Python)
+- `#204 <https://github.com/readthedocs/sphinx-autoapi/issues/204>`: (Python)
   Added the ``imported-members`` AutoAPI option to be able to enable or disable
   documenting objects imported from the same top-level package or module
   without needing to override templates.
@@ -268,22 +312,22 @@ Features
 Bug Fixes
 ^^^^^^^^^
 
-* `#198 <https://github.com/readthedocs/sphinx-autoapi/issues/198>`:
+- `#198 <https://github.com/readthedocs/sphinx-autoapi/issues/198>`:
   Documentation describes the required layout for template override directories.
-* `#195 <https://github.com/readthedocs/sphinx-autoapi/issues/195>`: (Python)
+- `#195 <https://github.com/readthedocs/sphinx-autoapi/issues/195>`: (Python)
   Fixed incorrect formatting when ``show-inheritance-diagram``
   and ``private-members`` are turned on.
-* `#193 <https://github.com/readthedocs/sphinx-autoapi/issues/193>` and
+- `#193 <https://github.com/readthedocs/sphinx-autoapi/issues/193>` and
   `#208 <https://github.com/readthedocs/sphinx-autoapi/issues/208>`: (Python)
   Inheritance diagrams can follow imports to find classes to document.
-* `#213 <https://github.com/readthedocs/sphinx-autoapi/issues/213>`: (Python)
+- `#213 <https://github.com/readthedocs/sphinx-autoapi/issues/213>`: (Python)
   Fixed module summary never showing.
 
 Trivial/Internal Changes
 ^^^^^^^^^^^^^^^^^^^^^^^^
 
-* black shows diffs by default
-* `#207 <https://github.com/readthedocs/sphinx-autoapi/issues/207>`:
+- black shows diffs by default
+- `#207 <https://github.com/readthedocs/sphinx-autoapi/issues/207>`:
   Fixed a typo in the code of the golang tutorial.
 
 
@@ -293,45 +337,45 @@ v1.3.0 (2020-04-05)
 Breaking Changes
 ^^^^^^^^^^^^^^^^
 
-* Dropped support for Python 3.4 and 3.5.
+- Dropped support for Python 3.4 and 3.5.
 
 Features
 ^^^^^^^^
 
-* `#151 <https://github.com/readthedocs/sphinx-autoapi/issues/151>`: (Python)
+- `#151 <https://github.com/readthedocs/sphinx-autoapi/issues/151>`: (Python)
   Added the ``autoapi_python_use_implicit_namespaces`` option to allow
   AutoAPI to search for implicit namespace packages.
-* Added support for Sphinx 2.2 and 2.3.
-* Added support for Python 3.8.
-* `#140 <https://github.com/readthedocs/sphinx-autoapi/issues/140>`: (Python)
+- Added support for Sphinx 2.2 and 2.3.
+- Added support for Python 3.8.
+- `#140 <https://github.com/readthedocs/sphinx-autoapi/issues/140>`: (Python)
   Added the ``autoapi-inheritance-diagram`` directive to create
   inheritance diagrams without importing modules.
   Enable the ``show-inheritance-diagram`` AutoAPI option to
   turn the diagrams on in generated documentation.
-* `#183 <https://github.com/readthedocs/sphinx-autoapi/issues/183>`: (Python)
+- `#183 <https://github.com/readthedocs/sphinx-autoapi/issues/183>`: (Python)
   Added the ``show-inheritance`` AutoAPI option to be able to enable or disable
   the display of a list of base classes in generated documentation about a class.
   Added the ``inherited-members`` AutoAPI option to be able to enable or disable
   the display of members inherited from a base class
   in generated documentation about a class.
-* The ``autoapi_include_summaries`` option has been replaced with the
+- The ``autoapi_include_summaries`` option has been replaced with the
   ``show-module-summary`` AutoAPI option.
   ``autoapi_include_summaries`` will stop working in the next major version.
-* Added support for Sphinx 2.4 and 3.0
+- Added support for Sphinx 2.4 and 3.0
 
 Bug Fixes
 ^^^^^^^^^
 
-* `#186 <https://github.com/readthedocs/sphinx-autoapi/issues/186>`: (Python)
+- `#186 <https://github.com/readthedocs/sphinx-autoapi/issues/186>`: (Python)
   Fixed an exception when there are too many argument type annotations
   in a type comment.
-* (Python) args and kwargs type annotations can be read from
+- (Python) args and kwargs type annotations can be read from
   the function type comment.
 
 Trivial/Internal Changes
 ^^^^^^^^^^^^^^^^^^^^^^^^
 
-* Tests are now included in the sdist.
+- Tests are now included in the sdist.
 
 
 v1.2.1 (2019-10-09)
@@ -340,7 +384,7 @@ v1.2.1 (2019-10-09)
 Bug Fixes
 ^^^^^^^^^
 
-* (Python) "Invalid desc node" warning no longer raised for autodoc-style
+- (Python) "Invalid desc node" warning no longer raised for autodoc-style
   directives.
 
 
@@ -350,21 +394,21 @@ v1.2.0 (2019-10-05)
 Features
 ^^^^^^^^
 
-* (Python) Can read per argument type comments with astroid > 2.2.5.
-* (Python) Added autoapidecorator directive with Sphinx >= 2.0.
-* (Python) Can use autodoc_docstring_signature with Autodoc-style directives.
-* (Python) Added autoapi-skip-member event.
-* Made it more clear which file causes an error, when an error occurs.
-* Sphinx language domains are now optional dependencies.
+- (Python) Can read per argument type comments with astroid > 2.2.5.
+- (Python) Added autoapidecorator directive with Sphinx >= 2.0.
+- (Python) Can use autodoc_docstring_signature with Autodoc-style directives.
+- (Python) Added autoapi-skip-member event.
+- Made it more clear which file causes an error, when an error occurs.
+- Sphinx language domains are now optional dependencies.
 
 Bug Fixes
 ^^^^^^^^^
 
-* (Python) Forward reference annotations are no longer rendered as strings.
-* (Python) autoapifunction directive no longer documents async functions as
+- (Python) Forward reference annotations are no longer rendered as strings.
+- (Python) autoapifunction directive no longer documents async functions as
   a normal function.
-* (Python) Fixed unicode decode errors in some Python 3 situations.
-* Documentation more accurately describes what configuration accepts
+- (Python) Fixed unicode decode errors in some Python 3 situations.
+- Documentation more accurately describes what configuration accepts
   relative paths and where they are relative to.
 
 
@@ -374,19 +418,19 @@ v1.1.0 (2019-06-23)
 Features
 ^^^^^^^^
 
-* (Python) Can override ignoring local imports in modules by using __all__.
+- (Python) Can override ignoring local imports in modules by using __all__.
 
 Bug Fixes
 ^^^^^^^^^
 
-* (Python) Fixed incorrect formatting of functions and methods.
-* Added support for Sphinx 2.1.
+- (Python) Fixed incorrect formatting of functions and methods.
+- Added support for Sphinx 2.1.
 
 Trivial/Internal Changes
 ^^^^^^^^^^^^^^^^^^^^^^^^
 
-* Fixed some dead links in the README.
-* Fixed lint virtualenv.
+- Fixed some dead links in the README.
+- Fixed lint virtualenv.
 
 
 v1.0.0 (2019-04-24)
@@ -395,30 +439,30 @@ v1.0.0 (2019-04-24)
 Features
 ^^^^^^^^
 
-* `#100 <https://github.com/readthedocs/sphinx-autoapi/issues/100>`: (Python)
+- `#100 <https://github.com/readthedocs/sphinx-autoapi/issues/100>`: (Python)
   Added support for documenting C extensions via ``.pyi`` stub files.
-* Added support for Sphinx 2.0.
-* Toned down the API reference index page.
-* (Go) Patterns configured in ``autoapi_ignore`` are passed to godocjson.
-* New and improved documentation.
-* No longer need to set ``autoapi_add_toctree_entry`` to False when ``autoapi_generate_api_docs`` is False.
-* `#139 <https://github.com/readthedocs/sphinx-autoapi/issues/139>`
+- Added support for Sphinx 2.0.
+- Toned down the API reference index page.
+- (Go) Patterns configured in ``autoapi_ignore`` are passed to godocjson.
+- New and improved documentation.
+- No longer need to set ``autoapi_add_toctree_entry`` to False when ``autoapi_generate_api_docs`` is False.
+- `#139 <https://github.com/readthedocs/sphinx-autoapi/issues/139>`
   Added support for basic type annotations in documentation generation and autodoc-style directives.
 
 Bug Fixes
 ^^^^^^^^^
 
-* `#159 <https://github.com/readthedocs/sphinx-autoapi/issues/159>`: (Python)
+- `#159 <https://github.com/readthedocs/sphinx-autoapi/issues/159>`: (Python)
   Fixed ``UnicodeDecodeError`` on Python 2 when a documenting an attribute that contains binary data.
-* (Python) Fixed private submodules displaying when ``private-members`` is turned off.
-* Templates no longer produce excessive whitespace.
-* (Python) Fixed an error when giving an invalid object to an autodoc-style directive.
+- (Python) Fixed private submodules displaying when ``private-members`` is turned off.
+- Templates no longer produce excessive whitespace.
+- (Python) Fixed an error when giving an invalid object to an autodoc-style directive.
 
 Trivial/Internal Changes
 ^^^^^^^^^^^^^^^^^^^^^^^^
 
-* No longer pin the version of black.
-* Added missing test environments to travis.
+- No longer pin the version of black.
+- Added missing test environments to travis.
 
 
 v0.7.1 (2019-02-04)
@@ -427,7 +471,7 @@ v0.7.1 (2019-02-04)
 Bug Fixes
 ^^^^^^^^^
 
-* (Python) Fixed a false warning when importing a local module.
+- (Python) Fixed a false warning when importing a local module.
 
 
 v0.7.0 (2019-01-30)
@@ -436,17 +480,17 @@ v0.7.0 (2019-01-30)
 Breaking Changes
 ^^^^^^^^^^^^^^^^
 
-* Dropped support for Sphinx<1.6.
+- Dropped support for Sphinx<1.6.
 
 Features
 ^^^^^^^^
 
-* Added debug messages about what AutoAPI is doing.
+- Added debug messages about what AutoAPI is doing.
 
 Bug Fixes
 ^^^^^^^^^
 
-* `#156 <https://github.com/readthedocs/sphinx-autoapi/issues/156>`: (Python) Made import resolution more stable.
+- `#156 <https://github.com/readthedocs/sphinx-autoapi/issues/156>`: (Python) Made import resolution more stable.
 
     Also capable of giving more detailed warnings.
 
@@ -454,10 +498,10 @@ Bug Fixes
 Trivial/Internal Changes
 ^^^^^^^^^^^^^^^^^^^^^^^^
 
-* Code is now formatted using black.
-* Removed references to old css and js files.
-* Replaced usage of deprecated Sphinx features.
-* Reorganised tests to be more pytest-like.
+- Code is now formatted using black.
+- Removed references to old css and js files.
+- Replaced usage of deprecated Sphinx features.
+- Reorganised tests to be more pytest-like.
 
 
 v0.6.2 (2018-11-15)
@@ -466,7 +510,7 @@ v0.6.2 (2018-11-15)
 Bug Fixes
 ^^^^^^^^^
 
-* (Python) Fixed some import chains failing to resolve depending on resolution order.
+- (Python) Fixed some import chains failing to resolve depending on resolution order.
 
 
 v0.6.1 (2018-11-14)
@@ -475,18 +519,18 @@ v0.6.1 (2018-11-14)
 Bug Fixes
 ^^^^^^^^^
 
-* (Python) Fixed unicode decoding on Python 3.7.
-* (Python) Fixed autodoc directives not documenting anything in submodules or subpackages.
-* (Python) Fixed error parsing files with unicode docstrings.
-* (Python) Fixed error when documenting something that's imported in more than one place.
+- (Python) Fixed unicode decoding on Python 3.7.
+- (Python) Fixed autodoc directives not documenting anything in submodules or subpackages.
+- (Python) Fixed error parsing files with unicode docstrings.
+- (Python) Fixed error when documenting something that's imported in more than one place.
 
 
 Trivial/Internal Changes
 ^^^^^^^^^^^^^^^^^^^^^^^^
 
-* (Python) Added Python 3.7 testing.
-* Started testing against stable version of Sphinx 1.8.
-* Fixed all "no title" warnings during tests.
+- (Python) Added Python 3.7 testing.
+- Started testing against stable version of Sphinx 1.8.
+- Fixed all "no title" warnings during tests.
 
 
 v0.6.0 (2018-08-20)
@@ -495,51 +539,51 @@ v0.6.0 (2018-08-20)
 Breaking Changes
 ^^^^^^^^^^^^^^^^
 
-* `#152 <https://github.com/readthedocs/sphinx-autoapi/issues/152>`: Removed the ``autoapi_add_api_root_toctree`` option.
+- `#152 <https://github.com/readthedocs/sphinx-autoapi/issues/152>`: Removed the ``autoapi_add_api_root_toctree`` option.
 
     This has been replaced with the ``autoapi_add_toctree_entry`` option.
 
-* `#25 <https://github.com/readthedocs/sphinx-autoapi/issues/25>`: Removed distutils support.
-* Removed redundant ``package_dir`` and ``package_data`` options.
+- `#25 <https://github.com/readthedocs/sphinx-autoapi/issues/25>`: Removed distutils support.
+- Removed redundant ``package_dir`` and ``package_data`` options.
 
 Features
 ^^^^^^^^
 
-* (Python) Added viewcode support for imported members.
-* `#146 <https://github.com/readthedocs/sphinx-autoapi/issues/146>`: (Python) No longer documents ``__init__()`` attributes without a docstring.
-* `#153 <https://github.com/readthedocs/sphinx-autoapi/issues/153>`: (Python) Can document a public python API.
-* `#111 <https://github.com/readthedocs/sphinx-autoapi/issues/111>`: (Python) Can opt to write manual documentation through new autodoc-style directives.
-* `#152 <https://github.com/readthedocs/sphinx-autoapi/issues/152>`: Made it easier to remove default index page.
+- (Python) Added viewcode support for imported members.
+- `#146 <https://github.com/readthedocs/sphinx-autoapi/issues/146>`: (Python) No longer documents ``__init__()`` attributes without a docstring.
+- `#153 <https://github.com/readthedocs/sphinx-autoapi/issues/153>`: (Python) Can document a public python API.
+- `#111 <https://github.com/readthedocs/sphinx-autoapi/issues/111>`: (Python) Can opt to write manual documentation through new autodoc-style directives.
+- `#152 <https://github.com/readthedocs/sphinx-autoapi/issues/152>`: Made it easier to remove default index page.
 
     Also removed autoapi_add_api_root_toctree config option
 
-* `#150 <https://github.com/readthedocs/sphinx-autoapi/issues/150>`: (Python) ``private-members`` also controls private subpackages and submodules.
-* (Python) Added support for static and class methods.
-* (Python) Methods include ``self`` in their arguments.
+- `#150 <https://github.com/readthedocs/sphinx-autoapi/issues/150>`: (Python) ``private-members`` also controls private subpackages and submodules.
+- (Python) Added support for static and class methods.
+- (Python) Methods include ``self`` in their arguments.
 
     This more closely matches autodoc behaviour.
 
-* `#145 <https://github.com/readthedocs/sphinx-autoapi/issues/145>`: (Python) Added support for detecting Python exceptions.
-* (Python) Can control how __init__ docstring is displayed.
-* (Python) Added support for viewcode.
-* (Python) Source files no longer need to be in ``sys.path``.
+- `#145 <https://github.com/readthedocs/sphinx-autoapi/issues/145>`: (Python) Added support for detecting Python exceptions.
+- (Python) Can control how __init__ docstring is displayed.
+- (Python) Added support for viewcode.
+- (Python) Source files no longer need to be in ``sys.path``.
 
 Bug Fixes
 ^^^^^^^^^
 
-* (Python) Fixed linking to builtin bases.
-* (Python) Fixed properties being documented more than once when set in ``__init__()``.
-* (Python) Fixed nested classes not getting displayed.
-* `#148 <https://github.com/readthedocs/sphinx-autoapi/issues/148>`: (Python) Fixed astroid 2.0 compatibility.
-* (Python) Fixed filtered classes and attributes getting displayed.
-* (Python) Fixed incorrect display of long lists.
-* `#125 <https://github.com/readthedocs/sphinx-autoapi/issues/125>`: (Javascript) Fixed running incorrect jsdoc command on Windows.
-* `#125 <https://github.com/readthedocs/sphinx-autoapi/issues/125>`: (Python) Support specifying package directories in ``autoapi_dirs``.
+- (Python) Fixed linking to builtin bases.
+- (Python) Fixed properties being documented more than once when set in ``__init__()``.
+- (Python) Fixed nested classes not getting displayed.
+- `#148 <https://github.com/readthedocs/sphinx-autoapi/issues/148>`: (Python) Fixed astroid 2.0 compatibility.
+- (Python) Fixed filtered classes and attributes getting displayed.
+- (Python) Fixed incorrect display of long lists.
+- `#125 <https://github.com/readthedocs/sphinx-autoapi/issues/125>`: (Javascript) Fixed running incorrect jsdoc command on Windows.
+- `#125 <https://github.com/readthedocs/sphinx-autoapi/issues/125>`: (Python) Support specifying package directories in ``autoapi_dirs``.
 
 Trivial/Internal Changes
 ^^^^^^^^^^^^^^^^^^^^^^^^
 
-* Added Sphinx 1.7 and 1.8.0b1 testing.
-* `#120 <https://github.com/readthedocs/sphinx-autoapi/issues/120>`: Updated documentation to remove outdated references.
-* Removed old testing dependencies.
-* `#143 <https://github.com/readthedocs/sphinx-autoapi/issues/143>`: Removed unnecessary wheel dependency.
+- Added Sphinx 1.7 and 1.8.0b1 testing.
+- `#120 <https://github.com/readthedocs/sphinx-autoapi/issues/120>`: Updated documentation to remove outdated references.
+- Removed old testing dependencies.
+- `#143 <https://github.com/readthedocs/sphinx-autoapi/issues/143>`: Removed unnecessary wheel dependency.
diff --git a/README.rst b/README.rst
index efc122a..2d0c264 100644
--- a/README.rst
+++ b/README.rst
@@ -31,6 +31,10 @@ AutoAPI finds and generates documentation by parsing source code.
 Language Support
 ----------------
 
+.. warning::
+
+    Support for all languages other than Python will be removed in the next major version!
+
 ==========  ======  ==========================================================
 Language    Status  Parser
 ==========  ======  ==========================================================
@@ -123,6 +127,16 @@ You can even get black to format changes automatically when you commit using `pr
     pip install pre-commit
     pre-commit install
 
+Release Notes
+~~~~~~~~~~~~~
+
+Release notes are managed through `towncrier <https://towncrier.readthedocs.io/en/stable/index.html>`_.
+When making a pull request you will need to create a news fragment to document your change:
+
+.. code-block:: bash
+
+    tox -e release_notes -- create --help
+
 Versioning
 ----------
 
diff --git a/autoapi/__init__.py b/autoapi/__init__.py
index fab86b8..25d001f 100644
--- a/autoapi/__init__.py
+++ b/autoapi/__init__.py
@@ -3,5 +3,5 @@
 from .extension import setup
 
 
-__version__ = "2.0.0"
-__version_info__ = (2, 0, 0)
+__version__ = "2.1.0"
+__version_info__ = (2, 1, 0)
diff --git a/autoapi/backends.py b/autoapi/backends.py
index 3282eb4..6a56963 100644
--- a/autoapi/backends.py
+++ b/autoapi/backends.py
@@ -1,3 +1,5 @@
+from typing import Dict, Sequence, Tuple
+
 from .mappers import (
     DotNetSphinxMapper,
     PythonSphinxMapper,
@@ -30,9 +32,9 @@ LANGUAGE_MAPPERS = {
 
 #: describes backend requirements in form
 #: {'backend name': (('1st package name in pypi', '1st package import name'), ...)}
-LANGUAGE_REQUIREMENTS = {
+LANGUAGE_REQUIREMENTS: Dict[str, Sequence[Tuple[str, str]]] = {
     "python": (),
     "javascript": (),
     "go": (("sphinxcontrib-golangdomain", "sphinxcontrib.golangdomain"),),
     "dotnet": (("sphinxcontrib-dotnetdomain", "sphinxcontrib.dotnetdomain"),),
-}  # type: Dict[str, Sequence[Tuple[str, str]]]
+}
diff --git a/autoapi/directives.py b/autoapi/directives.py
index 6a222dc..717cf5f 100644
--- a/autoapi/directives.py
+++ b/autoapi/directives.py
@@ -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 = ""
 
@@ -53,7 +53,6 @@ class NestedParse(Directive):  # pylint: disable=too-few-public-methods
     required_arguments = 0
     optional_arguments = 0
     final_argument_whitespace = False
-    option_spec = {}
 
     def run(self):
         node = nodes.container()
diff --git a/autoapi/documenters.py b/autoapi/documenters.py
index ed37e7a..f8ed706 100644
--- a/autoapi/documenters.py
+++ b/autoapi/documenters.py
@@ -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):
@@ -79,7 +79,7 @@ class AutoapiDocumenter(autodoc.Documenter):
         elif not self.options.inherited_members:
             children = (child for child in children if not child[1].inherited)
 
-        return False, sorted(children)
+        return False, children
 
 
 class _AutoapiDocstringSignatureMixin:  # pylint: disable=too-few-public-methods
@@ -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):
diff --git a/autoapi/extension.py b/autoapi/extension.py
index fece211..d18ed08 100644
--- a/autoapi/extension.py
+++ b/autoapi/extension.py
@@ -1,6 +1,4 @@
-# -*- coding: utf-8 -*-
-"""
-Sphinx Auto-API Top-level Extension.
+"""Sphinx Auto-API Top-level Extension.
 
 This extension allows you to automagically generate API documentation from your project.
 """
@@ -8,10 +6,11 @@ import io
 import os
 import shutil
 import sys
+from typing import Dict, Tuple
 import warnings
 
 import sphinx
-from sphinx.util.console import darkgreen, bold
+from sphinx.util.console import colorize
 from sphinx.addnodes import toctree
 from sphinx.errors import ExtensionError
 import sphinx.util.logging
@@ -27,7 +26,6 @@ from .backends import (
 from .directives import AutoapiSummary, NestedParse
 from .inheritance_diagrams import AutoapiInheritanceDiagram
 from .settings import API_ROOT
-from .toctree import add_domain_to_toctree
 
 LOGGER = sphinx.util.logging.getLogger(__name__)
 
@@ -40,11 +38,8 @@ _DEFAULT_OPTIONS = [
     "special-members",
     "imported-members",
 ]
-_VIEWCODE_CACHE = {}
-"""Caches a module's parse results for use in viewcode.
-
-:type: dict(str, tuple)
-"""
+_VIEWCODE_CACHE: Dict[str, Tuple[str, Dict]] = {}
+"""Caches a module's parse results for use in viewcode."""
 
 
 class RemovedInAutoAPI2Warning(DeprecationWarning):
@@ -74,13 +69,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 +91,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 +104,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
-                    ]
-                ),
-            )
+            f"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]
@@ -176,7 +160,10 @@ def build_finished(app, exception):
             os.path.join(app.srcdir, app.config.autoapi_root)
         )
         if app.verbosity > 1:
-            LOGGER.info(bold("[AutoAPI] ") + darkgreen("Cleaning generated .rst files"))
+            LOGGER.info(
+                colorize("bold", "[AutoAPI] ")
+                + colorize("darkgreen", "Cleaning generated .rst files")
+            )
         shutil.rmtree(normalized_root)
 
         sphinx_mapper = LANGUAGE_MAPPERS[app.config.autoapi_type]
@@ -195,13 +182,11 @@ def doctree_read(app, doctree):
     Inject AutoAPI into the TOC Tree dynamically.
     """
 
-    add_domain_to_toctree(app, doctree, app.env.docname)
-
     if app.env.docname == "index":
         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
@@ -219,11 +204,11 @@ 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)
-            message_prefix = bold("[AutoAPI] ")
-            message = darkgreen(
-                "Adding AutoAPI TOCTree [{0}] to index.rst".format(toc_entry)
+            nodes[-1]["entries"].append((None, f"{app.config.autoapi_root}/index"))
+            nodes[-1]["includefiles"].append(f"{app.config.autoapi_root}/index")
+            message_prefix = colorize("bold", "[AutoAPI] ")
+            message = colorize(
+                "darkgreen", f"Adding AutoAPI TOCTree [{toc_entry}] to index.rst"
             )
             LOGGER.info(message_prefix + message)
 
@@ -258,11 +243,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"], encoding="utf-8")
+
+    with stream as in_f:
+        source = in_f.read()
 
     result = (source, locations)
     _VIEWCODE_CACHE[modname] = result
@@ -270,7 +256,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
diff --git a/autoapi/inheritance_diagrams.py b/autoapi/inheritance_diagrams.py
index 0f3d935..02db48c 100644
--- a/autoapi/inheritance_diagrams.py
+++ b/autoapi/inheritance_diagrams.py
@@ -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
diff --git a/autoapi/mappers/base.py b/autoapi/mappers/base.py
index 3f7eb32..8efc9ed 100644
--- a/autoapi/mappers/base.py
+++ b/autoapi/mappers/base.py
@@ -6,7 +6,7 @@ import re
 from jinja2 import Environment, FileSystemLoader, TemplateNotFound
 import sphinx
 import sphinx.util
-from sphinx.util.console import darkgreen, bold
+from sphinx.util.console import colorize
 from sphinx.util.osutil import ensuredir
 import sphinx.util.logging
 import unidecode
@@ -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):
@@ -215,7 +211,10 @@ class SphinxMapperBase:
         """
         paths = list(self.find_files(patterns=patterns, dirs=dirs, ignore=ignore))
         for path in sphinx.util.status_iterator(
-            paths, bold("[AutoAPI] Reading files... "), "darkgreen", len(paths)
+            paths,
+            colorize("bold", "[AutoAPI] Reading files... "),
+            "darkgreen",
+            len(paths),
         ):
             data = self.read_file(path=path)
             if data:
@@ -252,8 +251,10 @@ class SphinxMapperBase:
                                 os.path.join(root, filename), ignore_pattern
                             ):
                                 LOGGER.info(
-                                    bold("[AutoAPI] ")
-                                    + darkgreen("Ignoring %s/%s" % (root, filename))
+                                    colorize("bold", "[AutoAPI] ")
+                                    + colorize(
+                                        "darkgreen", f"Ignoring {root}/{filename}"
+                                    )
                                 )
                                 skip = True
 
@@ -294,7 +295,7 @@ class SphinxMapperBase:
         """Trigger find of serialized sources and build objects"""
         for _, data in sphinx.util.status_iterator(
             self.paths.items(),
-            bold("[AutoAPI] ") + "Mapping Data... ",
+            colorize("bold", "[AutoAPI] ") + "Mapping Data... ",
             length=len(self.paths),
             stringify_func=(lambda x: x[0]),
         ):
@@ -312,7 +313,7 @@ class SphinxMapperBase:
     def output_rst(self, root, source_suffix):
         for _, obj in sphinx.util.status_iterator(
             self.objects.items(),
-            bold("[AutoAPI] ") + "Rendering Data... ",
+            colorize("bold", "[AutoAPI] ") + "Rendering Data... ",
             length=len(self.objects),
             verbosity=1,
             stringify_func=(lambda x: x[0]),
@@ -323,7 +324,7 @@ class SphinxMapperBase:
 
             detail_dir = obj.include_dir(root=root)
             ensuredir(detail_dir)
-            path = os.path.join(detail_dir, "%s%s" % ("index", source_suffix))
+            path = os.path.join(detail_dir, f"index{source_suffix}")
             with open(path, "wb+") as detail_file:
                 detail_file.write(rst.encode("utf-8"))
 
diff --git a/autoapi/mappers/dotnet.py b/autoapi/mappers/dotnet.py
index cd2b868..c93e97d 100644
--- a/autoapi/mappers/dotnet.py
+++ b/autoapi/mappers/dotnet.py
@@ -1,14 +1,15 @@
+from collections import defaultdict
 import re
 import os
 import subprocess
 import traceback
 import shutil
-from collections import defaultdict
+from typing import Dict
 import unidecode
 
 import yaml
 from sphinx.util.osutil import ensuredir
-from sphinx.util.console import darkgreen, bold
+from sphinx.util.console import colorize
 import sphinx.util.logging
 from sphinx.errors import ExtensionError
 
@@ -56,7 +57,7 @@ class DotNetSphinxMapper(SphinxMapperBase):
     :param app: Sphinx application passed in as part of the extension
     """
 
-    top_namespaces = {}
+    top_namespaces: Dict[str, "DotNetNamespace"] = {}
 
     DOCFX_OUTPUT_PATH = "_api"
 
@@ -69,7 +70,9 @@ class DotNetSphinxMapper(SphinxMapperBase):
         the canonical source before the default patterns.  Fallback to default
         pattern matches if no ``docfx.json`` files are found.
         """
-        LOGGER.info(bold("[AutoAPI] ") + darkgreen("Loading Data"))
+        LOGGER.info(
+            colorize("bold", "[AutoAPI] ") + colorize("darkgreen", "Loading Data")
+        )
         all_files = set()
         if not self.app.config.autoapi_file_patterns:
             all_files = set(
@@ -83,8 +86,9 @@ class DotNetSphinxMapper(SphinxMapperBase):
         if all_files:
             command = ["docfx", "metadata", "--raw", "--force"]
             command.extend(all_files)
-            proc = subprocess.Popen(
-                " ".join(command),
+            proc = subprocess.run(
+                command,
+                check=False,
                 stdout=subprocess.PIPE,
                 stderr=subprocess.PIPE,
                 shell=True,
@@ -100,9 +104,8 @@ class DotNetSphinxMapper(SphinxMapperBase):
                     if key in os.environ
                 ),
             )
-            _, error_output = proc.communicate()
-            if error_output:
-                LOGGER.warning(error_output, type="autoapi", subtype="not_readable")
+            if proc.stderr:
+                LOGGER.warning(proc.stderr, type="autoapi", subtype="not_readable")
         # We now have yaml files
         for xdoc_path in self.find_files(
             patterns=["*.yml"], dirs=[self.DOCFX_OUTPUT_PATH], ignore=ignore
@@ -119,18 +122,18 @@ class DotNetSphinxMapper(SphinxMapperBase):
         :param path: Path of file to read
         """
         try:
-            with open(path, "r") as handle:
+            with open(path, "r", encoding="utf-8") as handle:
                 parsed_data = yaml.safe_load(handle)
                 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",
             )
@@ -141,7 +144,7 @@ class DotNetSphinxMapper(SphinxMapperBase):
         """Trigger find of serialized sources and build objects"""
         for _, data in sphinx.util.status_iterator(
             self.paths.items(),
-            bold("[AutoAPI] ") + "Mapping Data... ",
+            colorize("bold", "[AutoAPI] ") + "Mapping Data... ",
             length=len(self.paths),
             stringify_func=(lambda x: x[0]),
         ):
@@ -173,7 +176,7 @@ class DotNetSphinxMapper(SphinxMapperBase):
             cls = obj_map[data["type"].lower()]
         except KeyError:
             # this warning intentionally has no (sub-)type
-            LOGGER.warning("Unknown type: %s" % data)
+            LOGGER.warning(f"Unknown type: {data}")
         else:
             obj = cls(
                 data, jinja_env=self.jinja_env, app=self.app, options=options, **kwargs
@@ -242,7 +245,7 @@ class DotNetSphinxMapper(SphinxMapperBase):
 
         for _, obj in sphinx.util.status_iterator(
             self.objects.items(),
-            bold("[AutoAPI] ") + "Rendering Data... ",
+            colorize("bold", "[AutoAPI] ") + "Rendering Data... ",
             length=len(self.objects),
             stringify_func=(lambda x: x[0]),
         ):
@@ -255,7 +258,7 @@ class DotNetSphinxMapper(SphinxMapperBase):
 
             detail_dir = os.path.join(root, obj.pathname)
             ensuredir(detail_dir)
-            path = os.path.join(detail_dir, "%s%s" % ("index", source_suffix))
+            path = os.path.join(detail_dir, f"index{source_suffix}")
             with open(path, "wb") as detail_file:
                 detail_file.write(rst.encode("utf-8"))
 
@@ -270,7 +273,10 @@ class DotNetSphinxMapper(SphinxMapperBase):
     @staticmethod
     def build_finished(app, _):
         if app.verbosity > 1:
-            LOGGER.info(bold("[AutoAPI] ") + darkgreen("Cleaning generated .yml files"))
+            LOGGER.info(
+                colorize("bold", "[AutoAPI] ")
+                + colorize("darkgreen", "Cleaning generated .yml files")
+            )
         if os.path.exists(DotNetSphinxMapper.DOCFX_OUTPUT_PATH):
             shutil.rmtree(DotNetSphinxMapper.DOCFX_OUTPUT_PATH)
 
@@ -291,7 +297,7 @@ class DotNetPythonMapper(PythonMapperBase):
             for obj in kwargs.pop("references", [])
             if "uid" in obj
         )
-        super(DotNetPythonMapper, self).__init__(obj, **kwargs)
+        super().__init__(obj, **kwargs)
 
         # Always exist
         self.id = obj.get("uid", obj.get("id"))
@@ -350,7 +356,7 @@ class DotNetPythonMapper(PythonMapperBase):
         ]
 
     def __str__(self):
-        return "<{cls} {id}>".format(cls=self.__class__.__name__, id=self.id)
+        return f"<{self.__class__.__name__} {self.id}>"
 
     @property
     def pathname(self):
@@ -383,7 +389,7 @@ class DotNetPythonMapper(PythonMapperBase):
         try:
             repo = self.source["remote"]["repo"].replace(".git", "")
             path = self.path
-            return "{repo}/blob/master/{path}".format(repo=repo, path=path)
+            return f"{repo}/blob/master/{path}"
         except KeyError:
             return ""
 
@@ -466,11 +472,11 @@ class DotNetPythonMapper(PythonMapperBase):
                 if ref[1] == ":" and ref[0] in DOC_COMMENT_IDENTITIES:
                     reftype = DOC_COMMENT_IDENTITIES[ref[:1]]
                     ref = ref[2:]
-                    replacement = ":{reftype}:`{ref}`".format(reftype=reftype, ref=ref)
+                    replacement = f":{reftype}:`{ref}`"
                 elif ref[:2] == "!:":
                     replacement = ref[2:]
                 else:
-                    replacement = ":any:`{ref}`".format(ref=ref)
+                    replacement = f":any:`{ref}`"
 
                 # Escape following text
                 text_end = text[found.end() :]
@@ -529,7 +535,7 @@ class DotNetPythonMapper(PythonMapperBase):
             elif part.get("name") == ">":
                 parts.append("}")
             elif "fullName" in part and "uid" in part:
-                parts.append("{fullName}<{uid}>".format(**part))
+                parts.append(f"{part['fullName']}<{part['uid']}>")
             elif "uid" in part:
                 parts.append(part["uid"])
             elif "fullName" in part:
diff --git a/autoapi/mappers/go.py b/autoapi/mappers/go.py
index df25bf4..c72e557 100644
--- a/autoapi/mappers/go.py
+++ b/autoapi/mappers/go.py
@@ -1,7 +1,7 @@
 import json
 import subprocess
 
-from sphinx.util.console import bold
+from sphinx.util.console import colorize
 import sphinx.util.logging
 
 from .base import PythonMapperBase, SphinxMapperBase
@@ -24,7 +24,7 @@ class GoSphinxMapper(SphinxMapperBase):
 
         """
         for _dir in sphinx.util.status_iterator(
-            dirs, bold("[AutoAPI] Loading Data "), "darkgreen", len(dirs)
+            dirs, colorize("bold", "[AutoAPI] Loading Data "), "darkgreen", len(dirs)
         ):
             data = self.read_file(_dir, ignore=ignore)
             if data:
@@ -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
@@ -123,12 +123,11 @@ class GoSphinxMapper(SphinxMapperBase):
 
 
 class GoPythonMapper(PythonMapperBase):
-
     language = "go"
     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 +148,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 +185,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")
 
 
diff --git a/autoapi/mappers/javascript.py b/autoapi/mappers/javascript.py
index 6d9c5d7..4e6ccc2 100644
--- a/autoapi/mappers/javascript.py
+++ b/autoapi/mappers/javascript.py
@@ -2,7 +2,7 @@ import json
 import subprocess
 import os
 
-from sphinx.util.console import bold
+from sphinx.util.console import colorize
 import sphinx.util.logging
 
 from .base import PythonMapperBase, SphinxMapperBase
@@ -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",
             )
@@ -52,7 +52,7 @@ class JavaScriptSphinxMapper(SphinxMapperBase):
         """Trigger find of serialized sources and build objects"""
         for _, data in sphinx.util.status_iterator(
             self.paths.items(),
-            bold("[AutoAPI] ") + "Mapping Data... ",
+            colorize("bold", "[AutoAPI] ") + "Mapping Data... ",
             length=len(self.paths),
             stringify_func=(lambda x: x[0]),
         ):
@@ -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)
@@ -92,7 +92,6 @@ class JavaScriptSphinxMapper(SphinxMapperBase):
 
 
 class JavaScriptPythonMapper(PythonMapperBase):
-
     language = "javascript"
 
     def __init__(self, obj, **kwargs):
@@ -103,7 +102,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
 
diff --git a/autoapi/mappers/python/astroid_utils.py b/autoapi/mappers/python/astroid_utils.py
index 8fe8588..c2c2e6e 100644
--- a/autoapi/mappers/python/astroid_utils.py
+++ b/autoapi/mappers/python/astroid_utils.py
@@ -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)
@@ -139,6 +139,9 @@ def _get_const_values(node):
                 break
         else:
             value = new_value
+
+        if isinstance(node, astroid.nodes.Tuple):
+            value = tuple(new_value)
     elif isinstance(node, astroid.nodes.Const):
         value = node.value
 
@@ -462,7 +465,8 @@ def _iter_args(args, annotations, defaults):
 
         name = arg.name
         if isinstance(arg, astroid.Tuple):
-            name = "({})".format(", ".join(x.name for x in arg.elts))
+            argument_names = ", ".join(x.name for x in arg.elts)
+            name = f"({argument_names})"
 
         yield (name, format_annotation(annotation), default)
 
diff --git a/autoapi/mappers/python/mapper.py b/autoapi/mappers/python/mapper.py
index 3b87e4e..664a846 100644
--- a/autoapi/mappers/python/mapper.py
+++ b/autoapi/mappers/python/mapper.py
@@ -7,7 +7,7 @@ import re
 import sphinx.environment
 from sphinx.errors import ExtensionError
 import sphinx.util
-from sphinx.util.console import bold
+from sphinx.util.console import colorize
 import sphinx.util.docstrings
 import sphinx.util.logging
 
@@ -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
 
@@ -106,18 +104,15 @@ def _resolve_module_placeholders(modules, module_name, visit_path, resolved):
 
         imported_from, original_name = child["original_path"].rsplit(".", 1)
         if imported_from in visit_path:
-            msg = "Cannot resolve cyclic import: {0}, {1}".format(
-                ", ".join(visit_path), imported_from
-            )
+            visit_str = ", ".join(visit_path)
+            msg = f"Cannot resolve cyclic import: {visit_str}, {imported_from}"
             LOGGER.warning(msg, type="autoapi", subtype="python_import_resolution")
             module["children"].remove(child)
             children.pop(child["name"])
             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 +138,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 +239,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 = (
@@ -299,7 +292,7 @@ class PythonSphinxMapper(SphinxMapperBase):
 
         for dir_root, path in sphinx.util.status_iterator(
             dir_root_files,
-            bold("[AutoAPI] Reading files... "),
+            colorize("bold", "[AutoAPI] Reading files... "),
             length=len(dir_root_files),
             stringify_func=(lambda x: x[1]),
         ):
@@ -325,7 +318,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 +340,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 +366,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,
diff --git a/autoapi/mappers/python/objects.py b/autoapi/mappers/python/objects.py
index 999d0ee..96a15f9 100644
--- a/autoapi/mappers/python/objects.py
+++ b/autoapi/mappers/python/objects.py
@@ -1,5 +1,5 @@
 import functools
-from typing import Optional
+from typing import List, Optional
 
 import sphinx.util.logging
 
@@ -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)
 
@@ -41,14 +41,14 @@ class PythonPythonMapper(PythonMapperBase):
     is_callable = False
     member_order = 0
 
-    def __init__(self, obj, class_content="class", **kwargs):
-        super(PythonPythonMapper, self).__init__(obj, **kwargs)
+    def __init__(self, obj, class_content="class", **kwargs) -> None:
+        super().__init__(obj, **kwargs)
 
         self.name = obj["name"]
         self.id = obj.get("full_name", self.name)
 
         # Optional
-        self.children = []
+        self.children: List[PythonPythonMapper] = []
         self._docstring = obj["doc"]
         self._docstring_resolved = False
         self.imported = "original_path" in obj
@@ -61,7 +61,7 @@ class PythonPythonMapper(PythonMapperBase):
         # For later
         self._class_content = class_content
 
-        self._display_cache = None  # type: Optional[bool]
+        self._display_cache: Optional[bool] = None
 
     @property
     def docstring(self):
@@ -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
 
diff --git a/autoapi/mappers/python/parser.py b/autoapi/mappers/python/parser.py
index 41d1ba5..4f717c9 100644
--- a/autoapi/mappers/python/parser.py
+++ b/autoapi/mappers/python/parser.py
@@ -49,9 +49,20 @@ class Parser:
         )
 
     def parse_annassign(self, node):
-        return self.parse_assign(node)
+        # Don't document module level assignments to class attributes
+        if isinstance(node.target, astroid.nodes.AssignAttr):
+            return []
+
+        return self._parse_assign(node)
 
     def parse_assign(self, node):
+        # Don't document module level assignments to class attributes
+        if any(isinstance(target, astroid.nodes.AssignAttr) for target in node.targets):
+            return []
+
+        return self._parse_assign(node)
+
+    def _parse_assign(self, node):
         doc = ""
         doc_node = node.next_sibling()
         if isinstance(doc_node, astroid.nodes.Expr) and isinstance(
@@ -187,7 +198,7 @@ class Parser:
         if node.name == "__init__":
             for child in node.get_children():
                 if isinstance(child, (astroid.nodes.Assign, astroid.nodes.AnnAssign)):
-                    child_data = self.parse_assign(child)
+                    child_data = self._parse_assign(child)
                     result.extend(data for data in child_data if data["doc"])
 
         return result
diff --git a/autoapi/templates/python/data.rst b/autoapi/templates/python/data.rst
index 89417f1..3d12b2d 100644
--- a/autoapi/templates/python/data.rst
+++ b/autoapi/templates/python/data.rst
@@ -1,31 +1,36 @@
 {% if obj.display %}
 .. py:{{ obj.type }}:: {{ obj.name }}
-   {%+ if obj.value is not none or obj.annotation is not none -%}
-   :annotation:
-        {%- if obj.annotation %} :{{ obj.annotation }}
-        {%- endif %}
-        {%- if obj.value is not none %} = {%
-            if obj.value is string and obj.value.splitlines()|count > 1 -%}
+   {%- if obj.annotation is not none %}
+
+   :type: {%- if obj.annotation %} {{ obj.annotation }}{%- endif %}
+
+   {%- endif %}
+
+   {%- if obj.value is not none %}
+
+   :value: {% if obj.value is string and obj.value.splitlines()|count > 1 -%}
                 Multiline-String
 
     .. raw:: html
 
         <details><summary>Show Value</summary>
 
-    .. code-block:: text
-        :linenos:
+    .. code-block:: python
 
-        {{ obj.value|indent(width=8) }}
+        """{{ obj.value|indent(width=8,blank=true) }}"""
 
     .. raw:: html
 
         </details>
 
             {%- else -%}
+              {%- if obj.value is string -%}
+                {{ "%r" % obj.value|string|truncate(100) }}
+              {%- else -%}
                 {{ obj.value|string|truncate(100) }}
+              {%- endif -%}
             {%- endif %}
-        {%- endif %}
-    {% endif %}
+   {%- endif %}
 
 
    {{ obj.docstring|indent(3) }}
diff --git a/autoapi/toctree.py b/autoapi/toctree.py
deleted file mode 100644
index 9c9e059..0000000
--- a/autoapi/toctree.py
+++ /dev/null
@@ -1,164 +0,0 @@
-"""
-A small Sphinx extension that adds Domain objects (eg. Python Classes & Methods) to the TOC Tree.
-
-It dynamically adds them to the already rendered ``app.env.tocs`` dict on the Sphinx environment.
-Traditionally this only contains Section's,
-we then nest our Domain references inside the already existing Sections.
-"""
-
-from docutils import nodes
-from sphinx import addnodes
-import sphinx.util.logging
-
-LOGGER = sphinx.util.logging.getLogger(__name__)
-
-
-def _build_toc_node(docname, anchor="anchor", text="test text", bullet=False):
-    """
-    Create the node structure that Sphinx expects for TOC Tree entries.
-
-    The ``bullet`` argument wraps it in a ``nodes.bullet_list``,
-    which is how you nest TOC Tree entries.
-    """
-    reference = nodes.reference(
-        "",
-        "",
-        internal=True,
-        refuri=docname,
-        anchorname="#" + anchor,
-        *[nodes.Text(text, text)]
-    )
-    para = addnodes.compact_paragraph("", "", reference)
-    ret_list = nodes.list_item("", para)
-    return nodes.bullet_list("", ret_list) if bullet else ret_list
-
-
-def _traverse_parent(node, objtypes):
-    """
-    Traverse up the node's parents until you hit the ``objtypes`` referenced.
-
-    Can either be a single type,
-    or a tuple of types.
-    """
-    curr_node = node.parent
-    while curr_node is not None:
-        if isinstance(curr_node, objtypes):
-            return curr_node
-        curr_node = curr_node.parent
-    return None
-
-
-def _find_toc_node(toc, ref_id, objtype):
-    """
-    Find the actual TOC node for a ref_id.
-
-    Depends on the object type:
-    * Section - First section (refuri) or 2nd+ level section (anchorname)
-    * Desc - Just use the anchor name
-    """
-    for check_node in toc.traverse(nodes.reference):
-        if objtype == nodes.section and (
-            check_node.attributes["refuri"] == ref_id
-            or check_node.attributes["anchorname"] == "#" + ref_id
-        ):
-            return check_node
-        if (
-            objtype == addnodes.desc
-            and check_node.attributes["anchorname"] == "#" + ref_id
-        ):
-            return check_node
-    return None
-
-
-def _get_toc_reference(node, toc, docname):
-    """
-    Logic that understands maps a specific node to it's part of the toctree.
-
-    It takes a specific incoming ``node``,
-    and returns the actual TOC Tree node that is said reference.
-    """
-    if isinstance(node, nodes.section) and isinstance(node.parent, nodes.document):
-        # Top Level Section header
-        ref_id = docname
-        toc_reference = _find_toc_node(toc, ref_id, nodes.section)
-    elif isinstance(node, nodes.section):
-        # Nested Section header
-        ref_id = node.attributes["ids"][0]
-        toc_reference = _find_toc_node(toc, ref_id, nodes.section)
-    else:
-        # Desc node
-        try:
-            ref_id = node.children[0].attributes["ids"][0]
-            toc_reference = _find_toc_node(toc, ref_id, addnodes.desc)
-        except (KeyError, IndexError):
-            LOGGER.warning(
-                "Invalid desc node",
-                exc_info=True,
-                type="autoapi",
-                subtype="toc_reference",
-            )
-            toc_reference = None
-
-    return toc_reference
-
-
-def add_domain_to_toctree(app, doctree, docname):
-    """
-    Add domain objects to the toctree dynamically.
-
-    This should be attached to the ``doctree-resolved`` event.
-    This works by:
-
-    * Finding each domain node (addnodes.desc)
-    * Figuring out it's parent that will be in the toctree
-         (nodes.section, or a previously added addnodes.desc)
-    * Finding that parent in the TOC Tree based on it's ID
-    * Taking that element in the TOC Tree,
-      and finding it's parent that is a TOC Listing (nodes.bullet_list)
-    * Adding the new TOC element for our specific node as a child of that nodes.bullet_list
-        * This checks that bullet_list's last child,
-        and checks that it is also a nodes.bullet_list,
-        effectively nesting it under that element
-    """
-    toc = app.env.tocs[docname]
-    for desc_node in doctree.traverse(addnodes.desc):
-        try:
-            ref_id = desc_node.children[0].attributes["ids"][0]
-        except (KeyError, IndexError):
-            # autodoc-style directives already add nodes to the toc.
-            continue
-        try:
-            # Python domain object
-            ref_text = desc_node[0].attributes["fullname"].split(".")[-1].split("(")[0]
-        except (KeyError, IndexError):
-            # TODO[eric]: Support other Domains and ways of accessing this data
-            # Use `astext` for other types of domain objects
-            ref_text = desc_node[0].astext().split(".")[-1].split("(")[0]
-        # This is the actual object that will exist in the TOC Tree
-        # Sections by default, and other Desc nodes that we've previously placed.
-        parent_node = _traverse_parent(
-            node=desc_node, objtypes=(addnodes.desc, nodes.section)
-        )
-
-        if parent_node:
-            toc_reference = _get_toc_reference(parent_node, toc, docname)
-            if toc_reference:
-                # Get the last child of our parent's bullet list, this is where "we" live.
-                toc_insertion_point = _traverse_parent(
-                    toc_reference, nodes.bullet_list
-                )[-1]
-                # Ensure we're added another bullet list so that we nest inside the parent,
-                # not next to it
-                if len(toc_insertion_point) > 1 and isinstance(
-                    toc_insertion_point[1], nodes.bullet_list
-                ):
-                    to_add = _build_toc_node(docname, anchor=ref_id, text=ref_text)
-                    toc_insertion_point = toc_insertion_point[1]
-                else:
-                    to_add = _build_toc_node(
-                        docname,
-                        anchor=ref_id,
-                        text=ref_text,
-                        bullet=True,
-                    )
-                toc_insertion_point.append(to_add)
diff --git a/debian/changelog b/debian/changelog
index 07eafdd..8acec20 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+sphinx-autoapi (2.1.0-1) UNRELEASED; urgency=low
+
+  * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk>  Thu, 04 May 2023 14:23:55 -0000
+
 sphinx-autoapi (2.0.0-1) unstable; urgency=medium
 
   * Team upload.
diff --git a/debian/patches/0001-Use-local-object-inventory-files-for-sphinx.patch b/debian/patches/0001-Use-local-object-inventory-files-for-sphinx.patch
index b3241b0..fc50208 100644
--- a/debian/patches/0001-Use-local-object-inventory-files-for-sphinx.patch
+++ b/debian/patches/0001-Use-local-object-inventory-files-for-sphinx.patch
@@ -15,10 +15,10 @@ Forwarded: not-needed
  docs/conf.py | 25 ++++++++++++++++++++-----
  1 file changed, 20 insertions(+), 5 deletions(-)
 
-diff --git a/docs/conf.py b/docs/conf.py
-index 48e91ad..459d476 100644
---- a/docs/conf.py
-+++ b/docs/conf.py
+Index: sphinx-autoapi.git/docs/conf.py
+===================================================================
+--- sphinx-autoapi.git.orig/docs/conf.py
++++ sphinx-autoapi.git/docs/conf.py
 @@ -12,6 +12,9 @@
  # All configuration values have a default; values that are commented out
  # serve to show the default.
diff --git a/docs/changes/.gitkeep b/docs/changes/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/docs/maintenance/release-process.rst b/docs/maintenance/release-process.rst
index e7fa2a1..b80cffc 100644
--- a/docs/maintenance/release-process.rst
+++ b/docs/maintenance/release-process.rst
@@ -15,10 +15,9 @@ Preparation
 -----------
 
 1. Update the version numbers in ``autoapi/__init__.py``.
-2. Add any missing changelog entries.
-3. Put the version number and release date into the changelog.
-4. Commit and push the changes.
-5. Check that the tests passed on github.
+2. Run ``tox -e release_notes -- build``
+3. Commit and push the changes.
+4. Check that the tests passed on github.
 
 Release
 -------
diff --git a/docs/reference/config.rst b/docs/reference/config.rst
index f8a5b47..5df123e 100644
--- a/docs/reference/config.rst
+++ b/docs/reference/config.rst
@@ -30,6 +30,10 @@ Configuration Options
    .NET        ``'dotnet'``
    ==========  ================
 
+   .. warning::
+
+      Support for all languages other than Python will be removed in the next major version!
+
 .. confval:: autoapi_template_dir
 
    Default: ``''``
diff --git a/docs/tutorials.rst b/docs/tutorials.rst
index 4738d57..2aa4901 100644
--- a/docs/tutorials.rst
+++ b/docs/tutorials.rst
@@ -20,6 +20,10 @@ Javascript   ``pip install sphinx-autoapi``
 .NET         ``pip install sphinx-autoapi[dotnet]``
 ==========   ======================================
 
+.. warning::
+
+    Support for all languages other than Python will be removed in the next major version!
+
 Depending on which language you are trying to document,
 each language has a different set of steps for finishing the setup of AutoAPI:
 
diff --git a/pylintrc b/pylintrc
index 5414f3f..9de8ed6 100644
--- a/pylintrc
+++ b/pylintrc
@@ -4,8 +4,7 @@ load-plugins=
 
 [MESSAGES CONTROL]
 
-disable=bad-continuation,
-      duplicate-code,
+disable=duplicate-code,
       fixme,
       import-error,
       missing-class-docstring,
diff --git a/pyproject.toml b/pyproject.toml
index ead8162..b871705 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,3 +1,18 @@
 [build-system]
 requires = ["setuptools>=46.4.0", "wheel"]
 build-backend = "setuptools.build_meta"
+
+[[tool.mypy.overrides]]
+module = "astroid.*"
+ignore_missing_imports = true
+
+[[tool.mypy.overrides]]
+module = "autoapi.documenters"
+ignore_errors = true
+
+[tool.towncrier]
+directory = "docs/changes"
+filename = "CHANGELOG.rst"
+package = "autoapi"
+title_format = "v{version} ({project_date})"
+underlines = ["-", "^", "\""]
diff --git a/setup.cfg b/setup.cfg
index db508f7..f933f47 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -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/
 
@@ -36,7 +37,7 @@ install_requires =
     astroid>=2.7
     Jinja2
     PyYAML
-    sphinx>=4.0
+    sphinx>=5.2.0
     unidecode
 
 [options.extras_require]
diff --git a/tests/python/py3example/example/example.py b/tests/python/py3example/example/example.py
index 1e4c3bd..816d72e 100644
--- a/tests/python/py3example/example/example.py
+++ b/tests/python/py3example/example/example.py
@@ -12,9 +12,12 @@ from example2 import B
 T = TypeVar("T")
 U = TypeVar("U")
 
-software = "sphinx"
+software = "sphin'x"
+more_software = 'sphinx"autoapi'
+interesting_string = "interesting\"fun'\\'string"
+
+code_snippet = """The following is some code:
 
-code_snippet = """
 # -*- coding: utf-8 -*-
 from __future__ import absolute_import, division, print_function, unicode_literals
 # from future.builtins.disabled import *
diff --git a/tests/python/pyexample/autoapi/example/index.rst b/tests/python/pyexample/autoapi/example/index.rst
deleted file mode 100644
index 2d6946b..0000000
--- a/tests/python/pyexample/autoapi/example/index.rst
+++ /dev/null
@@ -1,185 +0,0 @@
-:py:mod:`example`
-=================
-
-.. py:module:: example
-
-.. autoapi-nested-parse::
-
-   Example module
-
-   This is a description
-
-
-
-Module Contents
----------------
-
-Classes
-~~~~~~~
-
-.. autoapisummary::
-
-   example.Foo
-   example.Bar
-   example.ClassWithNoInit
-   example.One
-   example.MultilineOne
-   example.Two
-
-
-
-Functions
-~~~~~~~~~
-
-.. autoapisummary::
-
-   example.decorator_okay
-   example.fn_with_long_sig
-
-
-
-.. py:class:: Foo(attr)
-
-   Bases: :py:obj:`object`
-
-   This is using custom filters.
-   .. py:class:: Meta
-
-      Bases: :py:obj:`object`
-
-      This is using custom filters.
-      .. py:method:: foo()
-         :classmethod:
-
-         The foo class method
-
-
-
-   .. py:property:: property_simple
-      :type: int
-
-      This property should parse okay.
-
-
-   .. py:attribute:: class_var
-      :annotation: = 42
-
-      
-
-   .. py:attribute:: another_class_var
-      :annotation: = 42
-
-      Another class var docstring
-
-
-   .. py:attribute:: attr2
-      
-
-      This is the docstring of an instance attribute.
-
-      :type: str
-
-
-   .. py:method:: method_okay(foo=None, bar=None)
-
-      This method should parse okay
-
-
-   .. py:method:: method_multiline(foo=None, bar=None, baz=None)
-
-      This is on multiple lines, but should parse okay too
-
-      pydocstyle gives us lines of source. Test if this means that multiline
-      definitions are covered in the way we're anticipating here
-
-
-   .. py:method:: method_tricky(foo=None, bar=dict(foo=1, bar=2))
-
-      This will likely fail our argument testing
-
-      We parse naively on commas, so the nested dictionary will throw this off
-
-
-   .. py:method:: method_sphinx_docs(foo, bar=0)
-
-      This method is documented with sphinx style docstrings.
-
-      :param foo: The first argument.
-      :type foo: int
-
-      :param int bar: The second argument.
-
-      :returns: The sum of foo and bar.
-      :rtype: int
-
-
-   .. py:method:: method_google_docs(foo, bar=0)
-
-      This method is documented with google style docstrings.
-
-      Args:
-          foo (int): The first argument.
-          bar (int): The second argument.
-
-      Returns:
-          int: The sum of foo and bar.
-
-
-   .. py:method:: method_sphinx_unicode()
-
-      This docstring uses unicodé.
-
-      :returns: A string.
-      :rtype: str
-
-
-   .. py:method:: method_google_unicode()
-
-      This docstring uses unicodé.
-
-      Returns:
-          str: A string.
-
-
-
-.. py:function:: decorator_okay(func)
-
-   This decorator should parse okay.
-
-
-.. py:class:: Bar(attr)
-
-   Bases: :py:obj:`Foo`
-
-   This is using custom filters.
-   .. py:method:: method_okay(foo=None, bar=None)
-
-      This method should parse okay
-
-
-
-.. py:class:: ClassWithNoInit
-
-   This is using custom filters.
-
-.. py:class:: One
-
-   This is using custom filters.
-
-.. py:class:: MultilineOne
-
-   Bases: :py:obj:`One`
-
-   This is using custom filters.
-
-.. py:class:: Two
-
-   Bases: :py:obj:`One`
-
-   This is using custom filters.
-
-.. py:function:: fn_with_long_sig(this, *, function=None, has=True, quite=True, a, long, signature, many, keyword, arguments)
-
-   A function with a long signature.
-
-
diff --git a/tests/python/pyexample/example/example.py b/tests/python/pyexample/example/example.py
index 74cda18..2572731 100644
--- a/tests/python/pyexample/example/example.py
+++ b/tests/python/pyexample/example/example.py
@@ -4,6 +4,11 @@
 This is a description
 """
 
+A_TUPLE = ("a", "b")
+"""A tuple to be rendered as a tuple."""
+A_LIST = ["c", "d"]
+"""A list to be rendered as a list."""
+
 
 class Foo(object):
     """Can we parse arguments from the class docstring?
@@ -100,6 +105,9 @@ class Foo(object):
         return "google"
 
 
+Foo.bar = "dinglebop"
+
+
 def decorator_okay(func):
     """This decorator should parse okay."""
 
diff --git a/tests/python/pymovedconfpy/autoapi/example/index.rst b/tests/python/pymovedconfpy/autoapi/example/index.rst
deleted file mode 100644
index 0896e07..0000000
--- a/tests/python/pymovedconfpy/autoapi/example/index.rst
+++ /dev/null
@@ -1,207 +0,0 @@
-:py:mod:`example`
-=================
-
-.. py:module:: example
-
-.. autoapi-nested-parse::
-
-   Example module
-
-   This is a description
-
-
-
-Module Contents
----------------
-
-Classes
-~~~~~~~
-
-.. autoapisummary::
-
-   example.Foo
-   example.Bar
-   example.ClassWithNoInit
-   example.One
-   example.MultilineOne
-   example.Two
-
-
-
-Functions
-~~~~~~~~~
-
-.. autoapisummary::
-
-   example.decorator_okay
-   example.fn_with_long_sig
-
-
-
-.. py:class:: Foo(attr)
-
-   Bases: :py:obj:`object`
-
-   Can we parse arguments from the class docstring?
-
-   :param attr: Set an attribute.
-   :type attr: str
-
-   Constructor docstring
-
-   .. py:class:: Meta
-
-      Bases: :py:obj:`object`
-
-      A nested class just to test things out
-
-      .. py:method:: foo()
-         :classmethod:
-
-         The foo class method
-
-
-
-   .. py:property:: property_simple
-      :type: int
-
-      This property should parse okay.
-
-
-   .. py:attribute:: class_var
-      :annotation: = 42
-
-      
-
-   .. py:attribute:: another_class_var
-      :annotation: = 42
-
-      Another class var docstring
-
-
-   .. py:attribute:: attr2
-      
-
-      This is the docstring of an instance attribute.
-
-      :type: str
-
-
-   .. py:method:: method_okay(foo=None, bar=None)
-
-      This method should parse okay
-
-
-   .. py:method:: method_multiline(foo=None, bar=None, baz=None)
-
-      This is on multiple lines, but should parse okay too
-
-      pydocstyle gives us lines of source. Test if this means that multiline
-      definitions are covered in the way we're anticipating here
-
-
-   .. py:method:: method_tricky(foo=None, bar=dict(foo=1, bar=2))
-
-      This will likely fail our argument testing
-
-      We parse naively on commas, so the nested dictionary will throw this off
-
-
-   .. py:method:: method_sphinx_docs(foo, bar=0)
-
-      This method is documented with sphinx style docstrings.
-
-      :param foo: The first argument.
-      :type foo: int
-
-      :param int bar: The second argument.
-
-      :returns: The sum of foo and bar.
-      :rtype: int
-
-
-   .. py:method:: method_google_docs(foo, bar=0)
-
-      This method is documented with google style docstrings.
-
-      Args:
-          foo (int): The first argument.
-          bar (int): The second argument.
-
-      Returns:
-          int: The sum of foo and bar.
-
-
-   .. py:method:: method_sphinx_unicode()
-
-      This docstring uses unicodé.
-
-      :returns: A string.
-      :rtype: str
-
-
-   .. py:method:: method_google_unicode()
-
-      This docstring uses unicodé.
-
-      Returns:
-          str: A string.
-
-
-
-.. py:function:: decorator_okay(func)
-
-   This decorator should parse okay.
-
-
-.. py:class:: Bar(attr)
-
-   Bases: :py:obj:`Foo`
-
-   Can we parse arguments from the class docstring?
-
-   :param attr: Set an attribute.
-   :type attr: str
-
-   Constructor docstring
-
-   .. py:method:: method_okay(foo=None, bar=None)
-
-      This method should parse okay
-
-
-
-.. py:class:: ClassWithNoInit
-
-
-.. py:class:: One
-
-   One.
-
-   One __init__.
-
-
-.. py:class:: MultilineOne
-
-   Bases: :py:obj:`One`
-
-   This is a naughty summary line
-   that exists on two lines.
-
-   One __init__.
-
-
-.. py:class:: Two
-
-   Bases: :py:obj:`One`
-
-   Two.
-
-   One __init__.
-
-
-.. py:function:: fn_with_long_sig(this, *, function=None, has=True, quite=True, a, long, signature, many, keyword, arguments)
-
-   A function with a long signature.
-
-
diff --git a/tests/python/pypackagecomplex/complex/foo.py b/tests/python/pypackagecomplex/complex/foo.py
index 09f7fb5..da60ce6 100644
--- a/tests/python/pypackagecomplex/complex/foo.py
+++ b/tests/python/pypackagecomplex/complex/foo.py
@@ -10,7 +10,6 @@ __all__ = ["PublicClass", "Foo"]
 
 
 class Foo(object):
-
     class_var = 42  #: Class var docstring
 
     another_class_var = 42
diff --git a/tests/python/pypackageexample/example/foo.py b/tests/python/pypackageexample/example/foo.py
index 6233add..5523d52 100644
--- a/tests/python/pypackageexample/example/foo.py
+++ b/tests/python/pypackageexample/example/foo.py
@@ -5,7 +5,6 @@ This is a description
 
 
 class Foo(object):
-
     class_var = 42  #: Class var docstring
 
     another_class_var = 42
diff --git a/tests/python/test_pyintegration.py b/tests/python/test_pyintegration.py
index 0cc8b98..e5702cb 100644
--- a/tests/python/test_pyintegration.py
+++ b/tests/python/test_pyintegration.py
@@ -4,22 +4,21 @@ import pathlib
 import re
 import shutil
 import sys
-from unittest.mock import patch, Mock, call
+from unittest.mock import Mock, call, patch
 
+import autoapi.settings
 import pytest
 import sphinx
-from sphinx.application import Sphinx
-from sphinx.errors import ExtensionError
 import sphinx.util.logging
-
 from autoapi.mappers.python import (
-    PythonModule,
-    PythonFunction,
     PythonClass,
     PythonData,
+    PythonFunction,
     PythonMethod,
+    PythonModule,
 )
-import autoapi.settings
+from sphinx.application import Sphinx
+from sphinx.errors import ExtensionError
 
 
 def rebuild(confoverrides=None, confdir=".", **kwargs):
@@ -30,7 +29,7 @@ def rebuild(confoverrides=None, confdir=".", **kwargs):
         doctreedir="_build/.doctrees",
         buildername="text",
         confoverrides=confoverrides,
-        **kwargs
+        **kwargs,
     )
     app.build()
 
@@ -104,6 +103,15 @@ class TestSimpleModule:
         # (autoapi_python_class_content="both") class docstring only once.
         assert example_file.count("One __init__.") == 3
 
+        # Tuples should be rendered as tuples, not lists
+        assert "('a', 'b')" in example_file
+        # Lists should be rendered as lists, not tuples
+        assert "['c', 'd']" in example_file
+
+        # Assigning a class level attribute at the module level
+        # should not get documented as a module level attribute.
+        assert "dinglebop" not in example_file
+
         index_path = "_build/text/index.txt"
         with io.open(index_path, encoding="utf8") as index_handle:
             index_file = index_handle.read()
@@ -251,10 +259,13 @@ class TestPy3Module:
         with io.open(example_path, encoding="utf8") as example_handle:
             example_file = example_handle.read()
 
-        assert "software = sphinx" in example_file
+        assert '''software = "sphin'x"''' in example_file
+        assert """more_software = 'sphinx"autoapi'""" in example_file
+        assert "interesting_string = 'interesting\"fun\\'\\\\\\'string'" in example_file
+
         assert "code_snippet = Multiline-String" in example_file
 
-        assert "max_rating :int = 10" in example_file
+        assert "max_rating: int = 10" in example_file
         assert "is_valid" in example_file
 
         assert "ratings" in example_file
@@ -265,7 +276,10 @@ class TestPy3Module:
         assert "start: int" in example_file
         assert "Iterable[int]" in example_file
 
-        assert "List[Union[str, int]]" in example_file
+        if sys.version_info >= (3, 8):
+            assert "List[str | int]" in example_file
+        else:
+            assert "List[Union[str, int]]" in example_file
 
         assert "not_yet_a: A" in example_file
         assert "imported: example2.B" in example_file
@@ -276,7 +290,7 @@ class TestPy3Module:
 
         assert "instance_var" in example_file
 
-        assert "global_a :A" in example_file
+        assert "global_a: A" in example_file
 
         assert "my_method() -> str" in example_file
 
@@ -345,7 +359,7 @@ class TestAnnotationCommentsModule:
         with io.open(example_path, encoding="utf8") as example_handle:
             example_file = example_handle.read()
 
-        assert "max_rating :int = 10" in example_file
+        assert "max_rating: int = 10" in example_file
 
         assert "ratings" in example_file
         assert "List[int]" in example_file
@@ -357,7 +371,10 @@ class TestAnnotationCommentsModule:
         # assert "end: int" in example_file
         assert "Iterable[int]" in example_file
 
-        assert "List[Union[str, int]]" in example_file
+        if sys.version_info >= (3, 8):
+            assert "List[str | int]" in example_file
+        else:
+            assert "List[Union[str, int]]" in example_file
 
         assert "not_yet_a: A" in example_file
         assert "is_an_a" in example_file
@@ -365,7 +382,7 @@ class TestAnnotationCommentsModule:
 
         assert "instance_var" in example_file
 
-        assert "global_a :A" in example_file
+        assert "global_a: A" in example_file
 
         assert "class example.B(a: str)" in example_file
         assert "method(b: list)" in example_file
@@ -388,18 +405,32 @@ class TestPositionalOnlyArgumentsModule:
 
         assert "f_simple(a, b, /, c, d, *, e, f)" in example_file
 
-        assert (
-            "f_comment(a: int, b: int, /, c: Optional[int], d: Optional[int], *, e: float, f: float)"
-            in example_file
-        )
-        assert (
-            "f_annotation(a: int, b: int, /, c: Optional[int], d: Optional[int], *, e: float, f: float)"
-            in example_file
-        )
-        assert (
-            "f_arg_comment(a: int, b: int, /, c: Optional[int], d: Optional[int], *, e: float, f: float)"
-            in example_file
-        )
+        if sys.version_info >= (3, 8):
+            assert (
+                "f_comment(a: int, b: int, /, c: int | None, d: int | None, *, e: float, f: float)"
+                in example_file
+            )
+            assert (
+                "f_annotation(a: int, b: int, /, c: int | None, d: int | None, *, e: float, f: float)"
+                in example_file
+            )
+            assert (
+                "f_arg_comment(a: int, b: int, /, c: int | None, d: int | None, *, e: float, f: float)"
+                in example_file
+            )
+        else:
+            assert (
+                "f_comment(a: int, b: int, /, c: Optional[int], d: Optional[int], *, e: float, f: float)"
+                in example_file
+            )
+            assert (
+                "f_annotation(a: int, b: int, /, c: Optional[int], d: Optional[int], *, e: float, f: float)"
+                in example_file
+            )
+            assert (
+                "f_arg_comment(a: int, b: int, /, c: Optional[int], d: Optional[int], *, e: float, f: float)"
+                in example_file
+            )
         assert "f_no_cd(a: int, b: int, /, *, e: float, f: float)" in example_file
 
 
@@ -427,7 +458,6 @@ class TestSimplePackage:
         builder("pypackageexample")
 
     def test_integration_with_package(self):
-
         example_path = "_build/text/autoapi/example/index.txt"
         with io.open(example_path, encoding="utf8") as example_handle:
             example_file = example_handle.read()
@@ -903,23 +933,23 @@ def test_string_module_attributes(builder):
 
     code_snippet_contents = [
         ".. py:data:: code_snippet",
-        "   :annotation: = Multiline-String",
+        "   :value: Multiline-String",
         "",
         "    .. raw:: html",
         "",
         "        <details><summary>Show Value</summary>",
         "",
-        "    .. code-block:: text",
-        "        :linenos:",
+        "    .. code-block:: python",
         "",
+        '        """The following is some code:',
         "        ",  # <--- Line array monstrosity to preserve these leading spaces
         "        # -*- coding: utf-8 -*-",
         "        from __future__ import absolute_import, division, print_function, unicode_literals",
         "        # from future.builtins.disabled import *",
         "        # from builtins import *",
-        "",
+        "        ",
         """        print("chunky o'block")""",
-        "",
+        '        """',
         "",
         "    .. raw:: html",
         "",
@@ -966,3 +996,29 @@ class TestMdSource:
             warningiserror=True,
             confoverrides={"source_suffix": ["md"]},
         )
+
+
+class TestMemberOrder:
+    @pytest.fixture(autouse=True, scope="class")
+    def built(self, builder):
+        builder(
+            "pyexample",
+            warningiserror=True,
+            confoverrides={
+                "suppress_warnings": ["app"],
+                "autodoc_member_order": "bysource",
+            },
+        )
+
+    def test_line_number_order(self):
+        example_path = "_build/text/manualapi.txt"
+        with io.open(example_path, encoding="utf8") as example_handle:
+            lines = example_handle.readlines()
+
+        method_tricky_pos = lines.index(
+            "   method_tricky(foo=None, bar=dict(foo=1, bar=2))\n"
+        )
+        method_sphinx_docs_pos = lines.index("   method_sphinx_docs(foo, bar=0)\n")
+
+        # method_tricky is defined in the source before method_sphinx_docs
+        assert method_tricky_pos < method_sphinx_docs_pos
diff --git a/tests/test_integration.py b/tests/test_integration.py
index 1f0284d..6ccf3df 100644
--- a/tests/test_integration.py
+++ b/tests/test_integration.py
@@ -116,7 +116,9 @@ class TestTOCTree(LanguageIntegrationTests):
         """
         Test that the example_function gets added to the TOC Tree
         """
-        self._run_test("toctreeexample", "_build/text/index.txt", "* example_function")
+        self._run_test(
+            "toctreeexample", "_build/text/index.txt", '* "example_function()"'
+        )
 
 
 class TestExtensionErrors:
@@ -136,7 +138,7 @@ class TestExtensionErrors:
                 "toctreeexample",
                 {"autoapi_type": "INVALID VALUE"},
                 (
-                    "Invalid autoapi_type setting, following values is "
+                    "Invalid autoapi_type setting, following values are "
                     'allowed: "dotnet", "go", "javascript", "python"'
                 ),
             ),
diff --git a/tox.ini b/tox.ini
index c5c6544..831d881 100644
--- a/tox.ini
+++ b/tox.ini
@@ -2,10 +2,20 @@
 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
+    typecheck
     lint
     docs
+    release_notes
+
+[gh-actions]
+python =
+    3.7: py37
+    3.8: py38
+    3.9: py39
+    3.10: py310
+    3.11: py311, formatting, typecheck, lint, docs, release_notes
 
 [testenv]
 extras =
@@ -27,10 +37,18 @@ commands =
 [testenv:lint]
 skip_install = true
 deps =
-    pylint~=2.4.2
+    pylint~=2.15.9
 commands =
     pylint {posargs:autoapi}
 
+[testenv:typecheck]
+deps =
+    mypy
+    types-docutils
+    types-PyYAML
+commands =
+    mypy {posargs:autoapi}
+
 [testenv:docs]
 extras =
     docs
@@ -38,6 +56,12 @@ changedir = {toxinidir}/docs
 commands =
     sphinx-build -b html -d {envtmpdir}/doctrees . {envtmpdir}/html
 
+[testenv:release_notes]
+deps =
+    towncrier
+commands =
+    towncrier {posargs:check}
+
 [testenv:release]
 skip_install = true
 deps =

More details

Full run details

Historical runs