Merge pull request #7777 from tk0miya/master

merge 3.x to master and Fix mypy violations (with mypy-0.780)
This commit is contained in:
Takeshi KOMIYA 2020-06-04 00:27:03 +09:00 committed by GitHub
commit ba93a41a72
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
86 changed files with 3424 additions and 2094 deletions

View File

@ -1,56 +0,0 @@
environment:
global:
TEST: -v --durations 25
PYTHONFAULTHANDLER: x
PYTHONWARNINGS: all
matrix:
- PYTHON: 37
- PYTHON: 37-x64
install:
- C:\Python%PYTHON%\python.exe -m pip install -U pip setuptools
- C:\Python%PYTHON%\python.exe -m pip install .[test,websupport]
cache:
- '%LOCALAPPDATA%\pip\Cache'
# No automatic build, just run python tests
build: off
# Update build information before testing, no warnings during this step
before_test:
- ps: |
$py_warnings = $env:PYTHONWARNINGS
$env:PYTHONWARNINGS = 'ignore'
Update-AppveyorBuild -Version ((& "C:\Python$($env:PYTHON)\python.exe" -m sphinx --version).Split(' ')[2])
$env:PYTHONWARNINGS = $py_warnings
test_script:
- ps: |
Push-Location tests
$test_ignore = $env:TEST_IGNORE
if (-not $test_ignore) { $test_ignore = '' }
$tests = $env:TEST
if (-not $tests) { $tests = '' }
& "C:\Python$($env:PYTHON)\python.exe" -m pytest $test_ignore.Split(' ') --junitxml .junit.xml $tests.Split(' ')
Pop-Location
if ($LastExitCode -eq 1) { Write-Host "Test Failures Occurred, leaving for test result parsing"; exit $LastExitCode }
elseif ($LastExitCode -ne 0) { Write-Host "Other Error Occurred, aborting"; exit $LastExitCode }
after_test:
- ps: (New-Object System.Net.WebClient).UploadFile("https://ci.appveyor.com/api/testresults/junit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path (Join-Path tests .junit.xml)))
after_build:
# Remove old or huge cache files to hopefully not exceed the 1GB cache limit.
#
# If the cache limit is reached, the cache will not be updated (of not even
# created in the first run). So this is a trade of between keeping the cache
# current and having a cache at all.
# NB: This is done only `on_success` since the cache in uploaded only on
# success anyway.
- C:\cygwin\bin\find "%LOCALAPPDATA%\pip" -type f -mtime +360 -delete
- C:\cygwin\bin\find "%LOCALAPPDATA%\pip" -type f -size +10M -delete
- C:\cygwin\bin\find "%LOCALAPPDATA%\pip" -empty -delete
# Show size of cache
- C:\cygwin\bin\du -hs "%LOCALAPPDATA%\pip\Cache"

21
.github/workflows/main.yml vendored Normal file
View File

@ -0,0 +1,21 @@
name: CI on Windows
on: [push, pull_request]
jobs:
build:
runs-on: windows-latest
strategy:
matrix:
architecture: [x86, x64]
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v1
with:
architecture: ${{ matrix.architecture }}
- name: Install dependencies
run: pip install -U tox
- name: Run Tox
run: tox -e py

46
CHANGES
View File

@ -41,6 +41,8 @@ Release 3.1.0 (in development)
Dependencies
------------
* #7746: mathjax: Update to 2.7.5
Incompatible changes
--------------------
@ -82,9 +84,14 @@ Features added
* #7487: autodoc: Allow to generate docs for singledispatch functions by
py:autofunction
* #7143: autodoc: Support final classes and methods
* #7384: autodoc: Support signatures defined by ``__new__()``, metaclasses and
builtin base classes
* #2106: autodoc: Support multiple signatures on docstring
* #4422: autodoc: Support GenericAlias in Python 3.7 or above
* #7466: autosummary: headings in generated documents are not translated
* #7490: autosummary: Add ``:caption:`` option to autosummary directive to set a
caption to the toctree
* #7469: autosummary: Support module attributes
* #248, #6040: autosummary: Add ``:recursive:`` option to autosummary directive
to generate stub files recursively
* #4030: autosummary: Add :confval:`autosummary_context` to add template
@ -102,6 +109,7 @@ Features added
* #7541: html theme: Add a "clearer" at the end of the "body"
* #7542: html theme: Make admonition/topic/sidebar scrollable
* #7543: html theme: Add top and bottom margins to tables
* #7695: html theme: Add viewport meta tag for basic theme
* C and C++: allow semicolon in the end of declarations.
* C++, parse parameterized noexcept specifiers.
* #7294: C++, parse expressions with user-defined literals.
@ -110,6 +118,10 @@ Features added
:rst:dir:`py:exception:` and :rst:dir:`py:method:` directives
* #7596: py domain: Change a type annotation for variables to a hyperlink
* #7582: napoleon: a type for attribute are represented like type annotation
* #7734: napoleon: overescaped trailing underscore on attribute
* #7683: Add ``allowed_exceptions`` parameter to ``Sphinx.emit()`` to allow
handlers to raise specified exceptions
* #7295: C++, parse (trailing) requires clauses.
Bugs fixed
----------
@ -131,9 +143,19 @@ Bugs fixed
specified
* #7650: autodoc: undecorated signature is shown for decorated functions
* #7676: autodoc: typo in the default value of autodoc_member_order
* #7676: autodoc: wrong value for :member-order: option is ignored silently
* #7676: autodoc: member-order="bysource" does not work for C module
* #3673: autodoc: member-order="bysource" does not work for a module having
__all__
* #7668: autodoc: wrong retann value is passed to a handler of
autodoc-proccess-signature
* #7711: autodoc: fails with ValueError when processing numpy objects
* #7551: autosummary: a nested class is indexed as non-nested class
* #7661: autosummary: autosummary directive emits warnings twices if failed to
import the target module
* #7685: autosummary: The template variable "members" contains imported members
even if :confval:`autossummary_imported_members` is False
* #7671: autosummary: The location of import failure warning is missing
* #7535: sphinx-autogen: crashes when custom template uses inheritance
* #7536: sphinx-autogen: crashes when template uses i18n feature
* #7653: sphinx-quickstart: Fix multiple directory creation for nested relpath
@ -143,15 +165,20 @@ Bugs fixed
supporting images
* #7610: incorrectly renders consecutive backslashes for docutils-0.16
* #7646: handle errors on event handlers
* #4187: LaTeX: EN DASH disappears from PDF bookmarks in Japanese documents
* #7701: LaTeX: Anonymous indirect hyperlink target causes duplicated labels
* #7756: py domain: The default value for positional only argument is not shown
* C++, fix rendering and xrefs in nested names explicitly starting
in global scope, e.g., ``::A::B``.
* C, fix rendering and xrefs in nested names explicitly starting
in global scope, e.g., ``.A.B``.
* #7763: C and C++, don't crash during display stringification of unary
expressions and fold expressions.
Testing
--------
Release 3.0.4 (in development)
Release 3.0.5 (in development)
==============================
Dependencies
@ -169,14 +196,21 @@ Features added
Bugs fixed
----------
* #7567: autodoc: parametrized types are shown twice for generic types
* #7637: autodoc: system defined TypeVars are shown in Python 3.9
* #7611: md5 fails when OpenSSL FIPS is enabled
* #7626: release package does not contain ``CODE_OF_CONDUCT``
Testing
--------
Release 3.0.4 (released May 27, 2020)
=====================================
Bugs fixed
----------
* #7567: autodoc: parametrized types are shown twice for generic types
* #7637: autodoc: system defined TypeVars are shown in Python 3.9
* #7696: html: Updated jQuery version from 3.4.1 to 3.5.1 for security reasons
* #7611: md5 fails when OpenSSL FIPS is enabled
* #7626: release package does not contain ``CODE_OF_CONDUCT``
Release 3.0.3 (released Apr 26, 2020)
=====================================

View File

@ -1,3 +1,7 @@
======================
Sphinx Code of Conduct
======================
Like the technical community as a whole, the Sphinx team and community is made
up of volunteers from all over the world.
Diversity is a strength, but it can also lead to communication issues and
@ -25,7 +29,7 @@ adhere to.
Not all of us will agree all the time, but disagreement is no excuse for poor
behavior and poor manners. We might all experience some frustration now and
then, but we cannot allow that frustration to turn into a personal attack.
Its important to remember that a community where people feel uncomfortable or
It's important to remember that a community where people feel uncomfortable or
threatened is not a productive one. Members of the Sphinx community should be
respectful when dealing with other members as well as with people outside the
Sphinx community.
@ -57,20 +61,18 @@ adhere to.
* **When we disagree, try to understand why.**
Disagreements, both social and technical, happen all the time and Sphinx is no
exception. It is important that we resolve disagreements and differing views
constructively. Remember that were different. Different people have different
constructively. Remember that we're different. Different people have different
perspectives on issues. Being unable to understand why someone holds a
viewpoint doesnt mean that theyre wrong. Dont forget that it is human to
err and blaming each other doesnt get us anywhere. Instead, focus on helping
viewpoint doesn't mean that they're wrong. Don't forget that it is human to
err and blaming each other doesn't get us anywhere. Instead, focus on helping
to resolve issues and learning from mistakes.
This isnt an exhaustive list of things that you cant do.
Rather, take it in the spirit in which its intended - a guide to make it easier
This isn't an exhaustive list of things that you can't do.
Rather, take it in the spirit in which it's intended - a guide to make it easier
to enrich all of us and the technical communities in which we participate.
This code of conduct applies to all spaces of the Sphinx community.
Attribution
-----------
.. rubric:: Attribution
Original text courtesy of the Speak Up! project:
http://web.archive.org/web/20141109123859/http://speakup.io/coc.html.

View File

@ -1,455 +1,17 @@
.. highlight:: console
Sphinx Developer's Guide
========================
.. topic:: Abstract
This document describes the development process of Sphinx, a documentation
system used by developers to document systems used by other developers to
develop other systems that may also be documented using Sphinx.
.. contents::
:local:
The Sphinx source code is managed using Git and is hosted on GitHub.
git clone git://github.com/sphinx-doc/sphinx
.. rubric:: Community
sphinx-users <sphinx-users@googlegroups.com>
Mailing list for user support.
sphinx-dev <sphinx-dev@googlegroups.com>
Mailing list for development related discussions.
#sphinx-doc on irc.freenode.net
IRC channel for development questions and user support.
Bug Reports and Feature Requests
--------------------------------
If you have encountered a problem with Sphinx or have an idea for a new
feature, please submit it to the `issue tracker`_ on GitHub or discuss it
on the `sphinx-dev`_ mailing list.
For bug reports, please include the output produced during the build process
and also the log file Sphinx creates after it encounters an unhandled
exception. The location of this file should be shown towards the end of the
error message.
Including or providing a link to the source files involved may help us fix the
issue. If possible, try to create a minimal project that produces the error
and post that instead.
.. _`issue tracker`: https://github.com/sphinx-doc/sphinx/issues
.. _`sphinx-dev`: mailto:sphinx-dev@googlegroups.com
======================
Contributing to Sphinx
----------------------
======================
The recommended way for new contributors to submit code to Sphinx is to fork
the repository on GitHub and then submit a pull request after
committing the changes. The pull request will then need to be approved by one
of the core developers before it is merged into the main repository.
Interested in contributing to Sphinx? Hurrah! We welcome all forms of
contribution, including code patches, documentation improvements and bug
reports/feature requests.
#. Check for open issues or open a fresh issue to start a discussion around a
feature idea or a bug.
#. If you feel uncomfortable or uncertain about an issue or your changes, feel
free to email the *sphinx-dev* mailing list.
#. Fork `the repository`_ on GitHub to start making your changes to the
``master`` branch for next MAJOR version, or ``A.x`` branch for next
MINOR version (see `Branch Model`_).
#. Write a test which shows that the bug was fixed or that the feature works
as expected.
#. Send a pull request and bug the maintainer until it gets merged and
published. Make sure to add yourself to AUTHORS_ and the change to
CHANGES_.
Our contributing guide can be found online at:
.. _`the repository`: https://github.com/sphinx-doc/sphinx
.. _AUTHORS: https://github.com/sphinx-doc/sphinx/blob/master/AUTHORS
.. _CHANGES: https://github.com/sphinx-doc/sphinx/blob/master/CHANGES
https://www.sphinx-doc.org/en/master/internals/contributing/
You can also browse it from this repository from
``doc/internals/contributing/``
Getting Started
~~~~~~~~~~~~~~~
These are the basic steps needed to start developing on Sphinx.
#. Create an account on GitHub.
#. Fork the main Sphinx repository (`sphinx-doc/sphinx
<https://github.com/sphinx-doc/sphinx>`_) using the GitHub interface.
#. Clone the forked repository to your machine. ::
git clone https://github.com/USERNAME/sphinx
cd sphinx
#. Checkout the appropriate branch.
Sphinx adopts Semantic Versioning 2.0.0 (refs: https://semver.org/ ).
For changes that preserves backwards-compatibility of API and features,
they should be included in the next MINOR release, use the ``A.x`` branch.
::
git checkout A.x
For incompatible or other substantial changes that should wait until the
next MAJOR release, use the ``master`` branch.
For urgent release, a new PATCH branch must be branched from the newest
release tag (see `Branch Model`_ for detail).
#. Setup a virtual environment.
This is not necessary for unit testing, thanks to ``tox``, but it is
necessary if you wish to run ``sphinx-build`` locally or run unit tests
without the help of ``tox``. ::
virtualenv ~/.venv
. ~/.venv/bin/activate
pip install -e .
#. Create a new working branch. Choose any name you like. ::
git checkout -b feature-xyz
#. Hack, hack, hack.
For tips on working with the code, see the `Coding Guide`_.
#. Test, test, test.
Testing is best done through ``tox``, which provides a number of targets and
allows testing against multiple different Python environments:
* To list all possible targets::
tox -av
* To run unit tests for a specific Python version, such as 3.6::
tox -e py36
* To run unit tests for a specific Python version and turn on deprecation
warnings on so they're shown in the test output::
PYTHONWARNINGS=all tox -e py36
* To run code style and type checks::
tox -e mypy
tox -e flake8
* Arguments to ``pytest`` can be passed via ``tox``, e.g. in order to run a
particular test::
tox -e py36 tests/test_module.py::test_new_feature
* To build the documentation::
tox -e docs
* To build the documentation in multiple formats::
tox -e docs -- -b html,latexpdf
* To run JavaScript tests with `Karma <https://karma-runner.github.io>`_,
execute the following commands (requires `Node.js <https://nodejs.org>`_)::
npm install
npm run test
You can also test by installing dependencies in your local environment. ::
pip install .[test]
New unit tests should be included in the ``tests`` directory where
necessary:
* For bug fixes, first add a test that fails without your changes and passes
after they are applied.
* Tests that need a ``sphinx-build`` run should be integrated in one of the
existing test modules if possible. New tests that to ``@with_app`` and
then ``build_all`` for a few assertions are not good since *the test suite
should not take more than a minute to run*.
#. Please add a bullet point to :file:`CHANGES` if the fix or feature is not
trivial (small doc updates, typo fixes). Then commit::
git commit -m '#42: Add useful new feature that does this.'
GitHub recognizes certain phrases that can be used to automatically
update the issue tracker.
For example::
git commit -m 'Closes #42: Fix invalid markup in docstring of Foo.bar.'
would close issue #42.
#. Push changes in the branch to your forked repository on GitHub. ::
git push origin feature-xyz
#. Submit a pull request from your branch to the respective branch (``master``
or ``A.x``).
#. Wait for a core developer to review your changes.
Translations
~~~~~~~~~~~~
The Sphinx core messages and documentations are translated on `Transifex
<https://www.transifex.com/>`_. Please join `Sphinx project on Transifex
<https://www.transifex.com/sphinx-doc/>`_ and translate them.
Core Developers
~~~~~~~~~~~~~~~
The core developers of Sphinx have write access to the main repository. They
can commit changes, accept/reject pull requests, and manage items on the issue
tracker.
You do not need to be a core developer or have write access to be involved in
the development of Sphinx. You can submit patches or create pull requests
from forked repositories and have a core developer add the changes for you.
The following are some general guidelines for core developers:
* Questionable or extensive changes should be submitted as a pull request
instead of being committed directly to the main repository. The pull
request should be reviewed by another core developer before it is merged.
* Trivial changes can be committed directly but be sure to keep the repository
in a good working state and that all tests pass before pushing your changes.
* When committing code written by someone else, please attribute the original
author in the commit message and any relevant :file:`CHANGES` entry.
Coding Guide
------------
* Try to use the same code style as used in the rest of the project. See the
`Pocoo Styleguide`__ for more information.
__ http://flask.pocoo.org/docs/styleguide/
* For non-trivial changes, please update the :file:`CHANGES` file. If your
changes alter existing behavior, please document this.
* New features should be documented. Include examples and use cases where
appropriate. If possible, include a sample that is displayed in the
generated output.
* When adding a new configuration variable, be sure to document it and update
:file:`sphinx/cmd/quickstart.py` if it's important enough.
* Add appropriate unit tests.
Debugging Tips
~~~~~~~~~~~~~~
* Delete the build cache before building documents if you make changes in the
code by running the command ``make clean`` or using the
:option:`sphinx-build -E` option.
* Use the :option:`sphinx-build -P` option to run ``pdb`` on exceptions.
* Use ``node.pformat()`` and ``node.asdom().toxml()`` to generate a printable
representation of the document structure.
* Set the configuration variable :confval:`keep_warnings` to ``True`` so
warnings will be displayed in the generated output.
* Set the configuration variable :confval:`nitpicky` to ``True`` so that Sphinx
will complain about references without a known target.
* Set the debugging options in the `Docutils configuration file
<http://docutils.sourceforge.net/docs/user/config.html>`_.
* JavaScript stemming algorithms in ``sphinx/search/*.py`` (except ``en.py``)
are generated by this
`modified snowballcode generator <https://github.com/shibukawa/snowball>`_.
Generated `JSX <https://jsx.github.io/>`_ files are
in `this repository <https://github.com/shibukawa/snowball-stemmer.jsx>`_.
You can get the resulting JavaScript files using the following command::
npm install
node_modules/.bin/grunt build # -> dest/*.global.js
Branch Model
------------
Sphinx project uses following branches for developing that conforms to Semantic
Versioning 2.0.0 (refs: https://semver.org/ ).
``master``
Development for MAJOR version.
All changes including incompatible behaviors and public API updates are
allowed.
``A.x`` (ex. ``2.x``)
Where ``A.x`` is the ``MAJOR.MINOR`` release. Used to maintain current
MINOR release. All changes are allowed if the change preserves
backwards-compatibility of API and features.
Only the most recent ``MAJOR.MINOR`` branch is currently retained. When a
new MAJOR version is released, the old ``MAJOR.MINOR`` branch will be
deleted and replaced by an equivalent tag.
``A.B.x`` (ex. ``2.4.x``)
Where ``A.B.x`` is the ``MAJOR.MINOR.PATCH`` release. Only
backwards-compatible bug fixes are allowed. In Sphinx project, PATCH
version is used for urgent bug fix.
``MAJOR.MINOR.PATCH`` branch will be branched from the ``v`` prefixed
release tag (ex. make 2.3.1 that branched from v2.3.0) when a urgent
release is needed. When new PATCH version is released, the branch will be
deleted and replaced by an equivalent tag (ex. v2.3.1).
Deprecating a feature
---------------------
There are a couple reasons that code in Sphinx might be deprecated:
* If a feature has been improved or modified in a backwards-incompatible way,
the old feature or behavior will be deprecated.
* Sometimes Sphinx will include a backport of a Python library that's not
included in a version of Python that Sphinx currently supports. When Sphinx
no longer needs to support the older version of Python that doesn't include
the library, the library will be deprecated in Sphinx.
As the :ref:`deprecation-policy` describes, the first release of Sphinx that
deprecates a feature (``A.B``) should raise a ``RemovedInSphinxXXWarning``
(where ``XX`` is the Sphinx version where the feature will be removed) when the
deprecated feature is invoked. Assuming we have good test coverage, these
warnings are converted to errors when running the test suite with warnings
enabled::
pytest -Wall
Thus, when adding a ``RemovedInSphinxXXWarning`` you need to eliminate or
silence any warnings generated when running the tests.
.. _deprecation-policy:
Deprecation policy
------------------
MAJOR and MINOR releases may deprecate certain features from previous
releases. If a feature is deprecated in a release A.x, it will continue to
work in all A.x.x versions (for all versions of x). It will continue to work
in all B.x.x versions but raise deprecation warnings. Deprecated features
will be removed at the C.0.0. It means the deprecated feature will work during
2 MAJOR releases at least.
So, for example, if we decided to start the deprecation of a function in
Sphinx 2.x:
* Sphinx 2.x will contain a backwards-compatible replica of the function
which will raise a ``RemovedInSphinx40Warning``.
This is a subclass of :exc:`python:PendingDeprecationWarning`, i.e. it
will not get displayed by default.
* Sphinx 3.x will still contain the backwards-compatible replica, but
``RemovedInSphinx40Warning`` will be a subclass of
:exc:`python:DeprecationWarning` then, and gets displayed by default.
* Sphinx 4.0 will remove the feature outright.
Deprecation warnings
~~~~~~~~~~~~~~~~~~~~
Sphinx will enable its ``RemovedInNextVersionWarning`` warnings by default,
if :envvar:`python:PYTHONWARNINGS` is not set.
Therefore you can disable them using:
* ``PYTHONWARNINGS= make html`` (Linux/Mac)
* ``export PYTHONWARNINGS=`` and do ``make html`` (Linux/Mac)
* ``set PYTHONWARNINGS=`` and do ``make html`` (Windows)
But you can also explicitly enable the pending ones using e.g.
``PYTHONWARNINGS=default`` (see the
:ref:`Python docs on configuring warnings <python:describing-warning-filters>`)
for more details.
Unit Testing
------------
Sphinx has been tested with pytest runner. Sphinx developers write unit tests
using pytest notation. Utility functions and pytest fixtures for testing are
provided in ``sphinx.testing``. If you are a developer of Sphinx extensions,
you can write unit tests with using pytest. At this time, ``sphinx.testing``
will help your test implementation.
How to use pytest fixtures that are provided by ``sphinx.testing``?
You can require ``'sphinx.testing.fixtures'`` in your test modules or
``conftest.py`` files like this::
pytest_plugins = 'sphinx.testing.fixtures'
If you want to know more detailed usage, please refer to ``tests/conftest.py``
and other ``test_*.py`` files under ``tests`` directory.
.. note::
Prior to Sphinx - 1.5.2, Sphinx was running the test with nose.
.. versionadded:: 1.6
``sphinx.testing`` as a experimental.
.. versionadded:: 1.8
Sphinx also runs JavaScript tests.
Release procedures
------------------
The release procedures are listed on ``utils/release-checklist``.
Locale Updates
~~~~~~~~~~~~~~
The parts of messages in Sphinx that go into builds are translated into several
locales. The translations are kept as gettext ``.po`` files translated from the
master template :file:`sphinx/locale/sphinx.pot`.
Sphinx uses `Babel <http://babel.pocoo.org/en/latest/>`_ to extract messages
and maintain the catalog files. It is integrated in ``setup.py``:
* Use ``python setup.py extract_messages`` to update the ``.pot`` template.
* Use ``python setup.py update_catalog`` to update all existing language
catalogs in ``sphinx/locale/*/LC_MESSAGES`` with the current messages in the
template file.
* Use ``python setup.py compile_catalog`` to compile the ``.po`` files to binary
``.mo`` files and ``.js`` files.
When an updated ``.po`` file is submitted, run compile_catalog to commit both
the source and the compiled catalogs.
When a new locale is submitted, add a new directory with the ISO 639-1 language
identifier and put ``sphinx.po`` in there. Don't forget to update the possible
values for :confval:`language` in ``doc/usage/configuration.rst``.
The Sphinx core messages can also be translated on `Transifex
<https://www.transifex.com/>`_. There exists a client tool named ``tx`` in the
Python package "transifex_client", which can be used to pull translations in
``.po`` format from Transifex. To do this, go to ``sphinx/locale`` and then run
``tx pull -f -l LANG`` where LANG is an existing language identifier. It is
good practice to run ``python setup.py update_catalog`` afterwards to make sure
the ``.po`` file has the canonical Babel formatting.
Sphinx uses GitHub to host source code, track patches and bugs, and more.
Please make an effort to provide as much possible when filing bugs.

View File

@ -76,7 +76,7 @@ If you wish to install `Sphinx` for development purposes, refer to `the
contributors guide`__.
__ https://pypi.org/project/Sphinx/
__ http://www.sphinx-doc.org/en/master/devguide.html
__ http://www.sphinx-doc.org/en/master/internals/contributing.html
Documentation
=============
@ -110,14 +110,14 @@ For information on running tests locally, refer to `the contributors guide`__.
__ https://travis-ci.org/sphinx-doc/sphinx
__ https://ci.appveyor.com/project/sphinxdoc/sphinx
__ https://circleci.com/gh/sphinx-doc/sphinx
__ http://www.sphinx-doc.org/en/master/devguide.html
__ http://www.sphinx-doc.org/en/master/internals/contributing.html
Contributing
============
Refer to `the contributors guide`__.
__ http://www.sphinx-doc.org/en/master/devguide.html
__ http://www.sphinx-doc.org/en/master/internals/contributing.html
Release signatures
==================

View File

@ -13,3 +13,9 @@ texlive-anyfontsize [platform:rpm]
texlive-ctablestack [platform:rpm]
texlive-gnu-freefont [platform:rpm]
latexmk [platform:rpm]
texlive-latex-recommended [platform:dpkg]
texlive-fonts-recommended [platform:dpkg]
texlive-latex-extra [platform:dpkg]
texlive-luatex [platform:dpkg]
latexmk [platform:dpkg]

View File

@ -116,12 +116,12 @@
this part of the documentation is for you.{%endtrans%}</p>
<ul>
<li>{%trans path=pathto("devguide")%}<a href="{{ path }}">Sphinx Developers Guide</a></li>{%endtrans%}
<li>{%trans path=pathto("authors")%}<a href="{{ path }}">Sphinx Authors</a></li>{%endtrans%}
<li>{%trans path=pathto("internals/contributing")%}<a href="{{ path }}">Sphinx Contributorss Guide</a></li>{%endtrans%}
<li>{%trans path=pathto("internals/authors")%}<a href="{{ path }}">Sphinx Authors</a></li>{%endtrans%}
</ul>
<h2>{%trans%}Code of Conduct{%endtrans%}</h2>
<h2>{%trans%}Code of Conduct{%endtrans%}</h2>
{%trans path=pathto("code_of_conduct")%}Please adhere to our <a href="{{ path }}">Code of Conduct</a>.{%endtrans%}
<p>{%trans path=pathto("code_of_conduct")%}Please adhere to our <a href="{{ path }}">Code of Conduct</a>.{%endtrans%}</p>
{% endblock %}

View File

@ -4,7 +4,8 @@
.. _changes:
Changes in Sphinx
*****************
=========
Changelog
=========
.. include:: ../CHANGES

View File

@ -1,8 +0,0 @@
:tocdepth: 2
.. _code_of_conduct:
Sphinx Code of Conduct
======================
.. include:: ../CODE_OF_CONDUCT

View File

@ -1,4 +1,3 @@
.. _contents:
Sphinx documentation contents
@ -7,35 +6,21 @@ Sphinx documentation contents
.. toctree::
:maxdepth: 2
intro
usage/installation
usage/quickstart
usage/restructuredtext/index
usage/markdown
usage/configuration
usage/builders/index
usage/extensions/index
usage/theming
usage/advanced/intl
usage/advanced/setuptools
usage/advanced/websupport/index
usage/index
development/index
man/index
theming
templating
latex
extdev/index
development/tutorials/index
internals/index
faq
glossary
devguide
changes
examples
authors
code_of_conduct
Indices and tables

View File

@ -14,7 +14,8 @@ Sphinx is a maintained by a group of volunteers. We value every contribution!
* There is also the #sphinx-doc IRC channel on `freenode
<https://freenode.net/>`_.
For more about our development process and methods, see the :doc:`devguide`.
For more about our development process and methods, refer to
:doc:`/internals/index`.
Extensions
==========

13
doc/development/index.rst Normal file
View File

@ -0,0 +1,13 @@
================
Extending Sphinx
================
This guide is aimed at those wishing to develop their own extensions for
Sphinx. Sphinx possesses significant extensibility capabilities including the
ability to hook into almost every point of the build process. If you simply
wish to use Sphinx with existing extensions, refer to :doc:`/usage/index`.
.. toctree::
:maxdepth: 2
tutorials/index

View File

@ -1 +0,0 @@
.. include:: ../CONTRIBUTING.rst

View File

@ -230,11 +230,15 @@ connect handlers to the events. Example:
.. event:: missing-reference (app, env, node, contnode)
Emitted when a cross-reference to a Python module or object cannot be
resolved. If the event handler can resolve the reference, it should return a
Emitted when a cross-reference to an object cannot be resolved.
If the event handler can resolve the reference, it should return a
new docutils node to be inserted in the document tree in place of the node
*node*. Usually this node is a :class:`reference` node containing *contnode*
as a child.
If the handler can not resolve the cross-reference,
it can either return ``None`` to let other handlers try,
or raise :class:`NoUri` to prevent other handlers in trying and suppress
a warning about this cross-reference being unresolved.
:param env: The build environment (``app.builder.env``).
:param node: The :class:`pending_xref` node to be resolved. Its attributes

View File

@ -148,6 +148,43 @@ Google Search
.. _xhtml to reST: http://docutils.sourceforge.net/sandbox/xhtml2rest/xhtml2rest.py
Sphinx vs. Docutils
-------------------
tl;dr: *docutils* converts reStructuredText to multiple output formats. Sphinx
builds upon docutils to allow construction of cross-referenced and indexed
bodies of documentation.
`docutils`__ is a text processing system for converting plain text
documentation into other, richer formats. As noted in the `docutils
documentation`__, docutils uses *readers* to read a document, *parsers* for
parsing plain text formats into an internal tree representation made up of
different types of *nodes*, and *writers* to output this tree in various
document formats. docutils provides parsers for one plain text format -
`reStructuredText`__ - though other, *out-of-tree* parsers have been
implemented including Sphinx's :doc:`Markdown parser </usage/markdown>`. On the
other hand, it provides writers for many different formats including HTML,
LaTeX, man pages, Open Document Format and XML.
docutils exposes all of its functionality through a variety of `front-end
tools`__, such as ``rst2html``, ``rst2odt`` and ``rst2xml``. Crucially though,
all of these tools, and docutils itself, are concerned with individual
documents. They don't support concepts such as cross-referencing, indexing of
documents, or the construction of a document hierarchy (typically manifesting
in a table of contents).
Sphinx builds upon docutils by harnessing docutils' readers and parsers and
providing its own :doc:`/usage/builders/index`. As a result, Sphinx wraps some
of the *writers* provided by docutils. This allows Sphinx to provide many
features that would simply not be possible with docutils, such as those
outlined above.
__ http://docutils.sourceforge.io/
__ http://docutils.sourceforge.io/docs/dev/hacking.html
__ http://docutils.sourceforge.io/rst.html
__ http://docutils.sourceforge.net/docs/user/tools.html
.. _epub-faq:
Epub info

View File

@ -2,8 +2,8 @@
.. _authors:
==============
Sphinx authors
==============
.. include:: ../AUTHORS
.. include:: ../../AUTHORS

View File

@ -0,0 +1,5 @@
:tocdepth: 2
.. _code_of_conduct:
.. include:: ../../CODE_OF_CONDUCT

View File

@ -0,0 +1,309 @@
======================
Contributing to Sphinx
======================
There are many ways you can contribute to Sphinx, be it filing bug reports or
feature requests, writing new documentation or submitting patches for new or
fixed behavior. This guide serves to illustrate how you can get started with
this.
Getting help
------------
The Sphinx community maintains a number of mailing lists and IRC channels.
sphinx-users <sphinx-users@googlegroups.com>
Mailing list for user support.
sphinx-dev <sphinx-dev@googlegroups.com>
Mailing list for development related discussions.
#sphinx-doc on irc.freenode.net
IRC channel for development questions and user support.
Bug Reports and Feature Requests
--------------------------------
If you have encountered a problem with Sphinx or have an idea for a new
feature, please submit it to the `issue tracker`_ on GitHub or discuss it
on the `sphinx-dev`_ mailing list.
For bug reports, please include the output produced during the build process
and also the log file Sphinx creates after it encounters an unhandled
exception. The location of this file should be shown towards the end of the
error message.
Including or providing a link to the source files involved may help us fix the
issue. If possible, try to create a minimal project that produces the error
and post that instead.
.. _`issue tracker`: https://github.com/sphinx-doc/sphinx/issues
.. _`sphinx-dev`: mailto:sphinx-dev@googlegroups.com
Writing code
------------
The Sphinx source code is managed using Git and is hosted on `GitHub`__. The
recommended way for new contributors to submit code to Sphinx is to fork this
repository and submit a pull request after committing changes to their fork.
The pull request will then need to be approved by one of the core developers
before it is merged into the main repository.
.. __: https://github.com/sphinx-doc/sphinx
Getting started
~~~~~~~~~~~~~~~
Before starting on a patch, we recommend checking for open issues or open a
fresh issue to start a discussion around a feature idea or a bug. If you feel
uncomfortable or uncertain about an issue or your changes, feel free to email
the *sphinx-dev* mailing list.
These are the basic steps needed to start developing on Sphinx.
#. Create an account on GitHub.
#. Fork the main Sphinx repository (`sphinx-doc/sphinx
<https://github.com/sphinx-doc/sphinx>`_) using the GitHub interface.
#. Clone the forked repository to your machine. ::
git clone https://github.com/USERNAME/sphinx
cd sphinx
#. Checkout the appropriate branch.
Sphinx adopts Semantic Versioning 2.0.0 (refs: https://semver.org/ ).
For changes that preserves backwards-compatibility of API and features,
they should be included in the next MINOR release, use the ``A.x`` branch.
::
git checkout A.x
For incompatible or other substantial changes that should wait until the
next MAJOR release, use the ``master`` branch.
For urgent release, a new PATCH branch must be branched from the newest
release tag (see :doc:`release-process` for detail).
#. Setup a virtual environment.
This is not necessary for unit testing, thanks to ``tox``, but it is
necessary if you wish to run ``sphinx-build`` locally or run unit tests
without the help of ``tox``::
virtualenv ~/.venv
. ~/.venv/bin/activate
pip install -e .
#. Create a new working branch. Choose any name you like. ::
git checkout -b feature-xyz
#. Hack, hack, hack.
Write your code along with tests that shows that the bug was fixed or that
the feature works as expected.
#. Add a bullet point to :file:`CHANGES` if the fix or feature is not trivial
(small doc updates, typo fixes), then commit::
git commit -m '#42: Add useful new feature that does this.'
GitHub recognizes certain phrases that can be used to automatically
update the issue tracker. For example::
git commit -m 'Closes #42: Fix invalid markup in docstring of Foo.bar.'
would close issue #42.
#. Push changes in the branch to your forked repository on GitHub::
git push origin feature-xyz
#. Submit a pull request from your branch to the respective branch (``master``
or ``A.x``).
#. Wait for a core developer to review your changes.
Coding style
~~~~~~~~~~~~
Please follow these guidelines when writing code for Sphinx:
* Try to use the same code style as used in the rest of the project. See the
`Pocoo Styleguide`__ for more information.
__ http://flask.pocoo.org/docs/styleguide/
* For non-trivial changes, please update the :file:`CHANGES` file. If your
changes alter existing behavior, please document this.
* New features should be documented. Include examples and use cases where
appropriate. If possible, include a sample that is displayed in the
generated output.
* When adding a new configuration variable, be sure to document it and update
:file:`sphinx/cmd/quickstart.py` if it's important enough.
* Add appropriate unit tests.
Style and type checks can be run using ``tox``::
tox -e mypy
tox -e flake8
Unit tests
~~~~~~~~~~
Sphinx is tested using `pytest`__ for Python code and `Karma`__ for JavaScript.
.. __: https://docs.pytest.org/en/latest/
.. __: https://karma-runner.github.io
To run Python unit tests, we recommend using ``tox``, which provides a number
of targets and allows testing against multiple different Python environments:
* To list all possible targets::
tox -av
* To run unit tests for a specific Python version, such as Python 3.6::
tox -e py36
* To run unit tests for a specific Python version and turn on deprecation
warnings on so they're shown in the test output::
PYTHONWARNINGS=all tox -e py36
* Arguments to ``pytest`` can be passed via ``tox``, e.g. in order to run a
particular test::
tox -e py36 tests/test_module.py::test_new_feature
You can also test by installing dependencies in your local environment::
pip install .[test]
To run JavaScript tests, use ``npm``::
npm install
npm run test
New unit tests should be included in the ``tests`` directory where
necessary:
* For bug fixes, first add a test that fails without your changes and passes
after they are applied.
* Tests that need a ``sphinx-build`` run should be integrated in one of the
existing test modules if possible. New tests that to ``@with_app`` and
then ``build_all`` for a few assertions are not good since *the test suite
should not take more than a minute to run*.
.. versionadded:: 1.8
Sphinx also runs JavaScript tests.
.. versionadded:: 1.6
``sphinx.testing`` is added as a experimental.
.. versionchanged:: 1.5.2
Sphinx was switched from nose to pytest.
.. todo:: The below belongs in the developer guide
Utility functions and pytest fixtures for testing are provided in
``sphinx.testing``. If you are a developer of Sphinx extensions, you can write
unit tests with using pytest. At this time, ``sphinx.testing`` will help your
test implementation.
How to use pytest fixtures that are provided by ``sphinx.testing``? You can
require ``'sphinx.testing.fixtures'`` in your test modules or ``conftest.py``
files like this::
pytest_plugins = 'sphinx.testing.fixtures'
If you want to know more detailed usage, please refer to ``tests/conftest.py``
and other ``test_*.py`` files under ``tests`` directory.
Writing documentation
---------------------
.. todo:: Add a more extensive documentation contribution guide.
You can build documentation using ``tox``::
tox -e docs
Translations
~~~~~~~~~~~~
The parts of messages in Sphinx that go into builds are translated into several
locales. The translations are kept as gettext ``.po`` files translated from the
master template :file:`sphinx/locale/sphinx.pot`.
Sphinx uses `Babel <http://babel.pocoo.org/en/latest/>`_ to extract messages
and maintain the catalog files. It is integrated in ``setup.py``:
* Use ``python setup.py extract_messages`` to update the ``.pot`` template.
* Use ``python setup.py update_catalog`` to update all existing language
catalogs in ``sphinx/locale/*/LC_MESSAGES`` with the current messages in the
template file.
* Use ``python setup.py compile_catalog`` to compile the ``.po`` files to binary
``.mo`` files and ``.js`` files.
When an updated ``.po`` file is submitted, run compile_catalog to commit both
the source and the compiled catalogs.
When a new locale is submitted, add a new directory with the ISO 639-1 language
identifier and put ``sphinx.po`` in there. Don't forget to update the possible
values for :confval:`language` in ``doc/usage/configuration.rst``.
The Sphinx core messages can also be translated on `Transifex
<https://www.transifex.com/sphinx-doc/>`_. There ``tx`` client tool, which is
provided by the ``transifex_client`` Python package, can be used to pull
translations in ``.po`` format from Transifex. To do this, go to
``sphinx/locale`` and then run ``tx pull -f -l LANG`` where ``LANG`` is an
existing language identifier. It is good practice to run ``python setup.py
update_catalog`` afterwards to make sure the ``.po`` file has the canonical
Babel formatting.
Debugging tips
--------------
* Delete the build cache before building documents if you make changes in the
code by running the command ``make clean`` or using the
:option:`sphinx-build -E` option.
* Use the :option:`sphinx-build -P` option to run ``pdb`` on exceptions.
* Use ``node.pformat()`` and ``node.asdom().toxml()`` to generate a printable
representation of the document structure.
* Set the configuration variable :confval:`keep_warnings` to ``True`` so
warnings will be displayed in the generated output.
* Set the configuration variable :confval:`nitpicky` to ``True`` so that Sphinx
will complain about references without a known target.
* Set the debugging options in the `Docutils configuration file
<http://docutils.sourceforge.net/docs/user/config.html>`_.
* JavaScript stemming algorithms in ``sphinx/search/*.py`` (except ``en.py``)
are generated by this `modified snowballcode generator
<https://github.com/shibukawa/snowball>`_. Generated `JSX
<https://jsx.github.io/>`_ files are in `this repository
<https://github.com/shibukawa/snowball-stemmer.jsx>`_. You can get the
resulting JavaScript files using the following command::
npm install
node_modules/.bin/grunt build # -> dest/*.global.js

16
doc/internals/index.rst Normal file
View File

@ -0,0 +1,16 @@
================
Sphinx internals
================
This guide contains information about the Sphinx open source project itself.
This is where you can find information about how Sphinx is managed and learn
how to contribute to the project.
.. toctree::
:maxdepth: 2
contributing
release-process
organization
code-of-conduct
authors

View File

@ -0,0 +1,56 @@
==================================
Organization of the Sphinx project
==================================
The guide explains how the Sphinx project is organized.
Core developers
---------------
The core developers of Sphinx have write access to the main repository. They
can commit changes, accept/reject pull requests, and manage items on the issue
tracker.
Guidelines
~~~~~~~~~~
The following are some general guidelines for core developers:
* Questionable or extensive changes should be submitted as a pull request
instead of being committed directly to the main repository. The pull
request should be reviewed by another core developer before it is merged.
* Trivial changes can be committed directly but be sure to keep the repository
in a good working state and that all tests pass before pushing your changes.
* When committing code written by someone else, please attribute the original
author in the commit message and any relevant :file:`CHANGES` entry.
Membership
~~~~~~~~~~
Core membership is predicated on continued active contribution to the project.
In general, prospective cores should demonstrate:
- a good understanding of one of more components of Sphinx
- a history of helpful, constructive contributions
- a willingness to invest time improving Sphinx
Refer to :doc:`contributing` for more information on how you can get started.
Other contributors
------------------
You do not need to be a core developer or have write access to be involved in
the development of Sphinx. You can submit patches or create pull requests
from forked repositories and have a core developer add the changes for you.
Similarly, contributions are not limited to code patches. We also welcome help
triaging bugs, input on design decisions, reviews of existing patches and
documentation improvements. More information can be found in
:doc:`contributing`.
A list of people that have contributed to Sphinx can be found in
:doc:`authors`.

View File

@ -0,0 +1,106 @@
========================
Sphinx's release process
========================
Branch Model
------------
Sphinx project uses following branches for developing that conforms to Semantic
Versioning 2.0.0 (refs: https://semver.org/ ).
``master``
Development for MAJOR version.
All changes including incompatible behaviors and public API updates are
allowed.
``A.x`` (ex. ``2.x``)
Where ``A.x`` is the ``MAJOR.MINOR`` release. Used to maintain current
MINOR release. All changes are allowed if the change preserves
backwards-compatibility of API and features.
Only the most recent ``MAJOR.MINOR`` branch is currently retained. When a
new MAJOR version is released, the old ``MAJOR.MINOR`` branch will be
deleted and replaced by an equivalent tag.
``A.B.x`` (ex. ``2.4.x``)
Where ``A.B.x`` is the ``MAJOR.MINOR.PATCH`` release. Only
backwards-compatible bug fixes are allowed. In Sphinx project, PATCH
version is used for urgent bug fix.
``MAJOR.MINOR.PATCH`` branch will be branched from the ``v`` prefixed
release tag (ex. make 2.3.1 that branched from v2.3.0) when a urgent
release is needed. When new PATCH version is released, the branch will be
deleted and replaced by an equivalent tag (ex. v2.3.1).
Deprecating a feature
---------------------
There are a couple reasons that code in Sphinx might be deprecated:
* If a feature has been improved or modified in a backwards-incompatible way,
the old feature or behavior will be deprecated.
* Sometimes Sphinx will include a backport of a Python library that's not
included in a version of Python that Sphinx currently supports. When Sphinx
no longer needs to support the older version of Python that doesn't include
the library, the library will be deprecated in Sphinx.
As the :ref:`deprecation-policy` describes, the first release of Sphinx that
deprecates a feature (``A.B``) should raise a ``RemovedInSphinxXXWarning``
(where ``XX`` is the Sphinx version where the feature will be removed) when the
deprecated feature is invoked. Assuming we have good test coverage, these
warnings are converted to errors when running the test suite with warnings
enabled::
pytest -Wall
Thus, when adding a ``RemovedInSphinxXXWarning`` you need to eliminate or
silence any warnings generated when running the tests.
.. _deprecation-policy:
Deprecation policy
------------------
MAJOR and MINOR releases may deprecate certain features from previous
releases. If a feature is deprecated in a release A.x, it will continue to
work in all A.x.x versions (for all versions of x). It will continue to work
in all B.x.x versions but raise deprecation warnings. Deprecated features
will be removed at the C.0.0. It means the deprecated feature will work during
2 MAJOR releases at least.
So, for example, if we decided to start the deprecation of a function in
Sphinx 2.x:
* Sphinx 2.x will contain a backwards-compatible replica of the function
which will raise a ``RemovedInSphinx40Warning``.
This is a subclass of :exc:`python:PendingDeprecationWarning`, i.e. it
will not get displayed by default.
* Sphinx 3.x will still contain the backwards-compatible replica, but
``RemovedInSphinx40Warning`` will be a subclass of
:exc:`python:DeprecationWarning` then, and gets displayed by default.
* Sphinx 4.0 will remove the feature outright.
Deprecation warnings
~~~~~~~~~~~~~~~~~~~~
Sphinx will enable its ``RemovedInNextVersionWarning`` warnings by default, if
:envvar:`python:PYTHONWARNINGS` is not set. Therefore you can disable them
using:
* ``PYTHONWARNINGS= make html`` (Linux/Mac)
* ``export PYTHONWARNINGS=`` and do ``make html`` (Linux/Mac)
* ``set PYTHONWARNINGS=`` and do ``make html`` (Windows)
But you can also explicitly enable the pending ones using e.g.
``PYTHONWARNINGS=default`` (see the :ref:`Python docs on configuring warnings
<python:describing-warning-filters>`) for more details.
Release procedures
------------------
The release procedures are listed in ``utils/release-checklist``.

View File

@ -1,71 +0,0 @@
Introduction
============
This is the documentation for the Sphinx documentation builder. Sphinx is a
tool that translates a set of reStructuredText_ source files into various output
formats, automatically producing cross-references, indices, etc. That is, if
you have a directory containing a bunch of reST-formatted documents (and
possibly subdirectories of docs in there as well), Sphinx can generate a
nicely-organized arrangement of HTML files (in some other directory) for easy
browsing and navigation. But from the same source, it can also generate a PDF
file using LaTeX.
The focus is on hand-written documentation, rather than auto-generated API docs.
Though there is support for that kind of documentation as well (which is
intended to be freely mixed with hand-written content), if you need pure API
docs have a look at `Epydoc <http://epydoc.sourceforge.net/>`_, which also
understands reST.
For a great "introduction" to writing docs in general -- the whys and hows, see
also `Write the docs`_, written by Eric Holscher.
.. _rinohtype: https://github.com/brechtm/rinohtype
.. _Write the docs: http://www.writethedocs.org/guide/writing/beginners-guide-to-docs/
Conversion from other systems
-----------------------------
This section is intended to collect helpful hints for those wanting to migrate
to reStructuredText/Sphinx from other documentation systems.
* Gerard Flanagan has written a script to convert pure HTML to reST; it can be
found at the `Python Package Index <https://pypi.org/project/html2rest/>`_.
* For converting the old Python docs to Sphinx, a converter was written which
can be found at `the Python SVN repository
<https://svn.python.org/projects/doctools/converter/>`_. It contains generic
code to convert Python-doc-style LaTeX markup to Sphinx reST.
* Marcin Wojdyr has written a script to convert Docbook to reST with Sphinx
markup; it is at `GitHub <https://github.com/wojdyr/db2rst>`_.
* Christophe de Vienne wrote a tool to convert from Open/LibreOffice documents
to Sphinx: `odt2sphinx <https://pypi.org/project/odt2sphinx/>`_.
* To convert different markups, `Pandoc <https://pandoc.org/>`_ is
a very helpful tool.
Use with other systems
----------------------
See the :ref:`pertinent section in the FAQ list <usingwith>`.
Prerequisites
-------------
Sphinx needs at least **Python 3.6** to run.
It also depends on 3rd party libraries such as docutils_ and jinja2_, but they
are automatically installed when sphinx is installed.
.. _reStructuredText: http://docutils.sourceforge.net/rst.html
.. _docutils: http://docutils.sourceforge.net/
.. _Jinja2: http://jinja.pocoo.org/
Usage
-----
See :doc:`/usage/quickstart` for an introduction. It also contains links to
more advanced sections in this manual for the topics it discusses.

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,7 @@
.. _templating:
==========
Templating
==========
@ -69,9 +70,10 @@ following contents::
By prefixing the name of the overridden template with an exclamation mark,
Sphinx will load the layout template from the underlying HTML theme.
**Important**: If you override a block, call ``{{ super() }}`` somewhere to
render the block's original content in the extended template -- unless you
don't want that content to show up.
.. important::
If you override a block, call ``{{ super() }}`` somewhere to render the
block's original content in the extended template -- unless you don't want
that content to show up.
Working with the builtin templates
@ -85,72 +87,68 @@ Blocks
The following blocks exist in the ``layout.html`` template:
`doctype`
``doctype``
The doctype of the output format. By default this is XHTML 1.0 Transitional
as this is the closest to what Sphinx and Docutils generate and it's a good
idea not to change it unless you want to switch to HTML 5 or a different but
compatible XHTML doctype.
`linktags`
``linktags``
This block adds a couple of ``<link>`` tags to the head section of the
template.
`extrahead`
``extrahead``
This block is empty by default and can be used to add extra contents into
the ``<head>`` tag of the generated HTML file. This is the right place to
add references to JavaScript or extra CSS files.
`relbar1` / `relbar2`
``relbar1``, ``relbar2``
This block contains the *relation bar*, the list of related links (the
parent documents on the left, and the links to index, modules etc. on the
right). `relbar1` appears before the document, `relbar2` after the
right). ``relbar1`` appears before the document, ``relbar2`` after the
document. By default, both blocks are filled; to show the relbar only
before the document, you would override `relbar2` like this::
{% block relbar2 %}{% endblock %}
`rootrellink` / `relbaritems`
Inside the relbar there are three sections: The `rootrellink`, the links
from the documentation and the custom `relbaritems`. The `rootrellink` is a
block that by default contains a list item pointing to the master document
by default, the `relbaritems` is an empty block. If you override them to
add extra links into the bar make sure that they are list items and end with
the :data:`reldelim1`.
``rootrellink``, ``relbaritems``
Inside the relbar there are three sections: The ``rootrellink``, the links
from the documentation and the custom ``relbaritems``. The ``rootrellink``
is a block that by default contains a list item pointing to the master
document by default, the ``relbaritems`` is an empty block. If you
override them to add extra links into the bar make sure that they are list
items and end with the :data:`reldelim1`.
`document`
The contents of the document itself. It contains the block "body" where the
individual content is put by subtemplates like ``page.html``.
``document``
The contents of the document itself. It contains the block "body" where
the individual content is put by subtemplates like ``page.html``.
.. note::
In order for the built-in JavaScript search to show a page preview on
the results page, the document or body content should be wrapped in an
HTML element containing the ``role="main"`` attribute. For example:
.. sourcecode:: html+jinja
HTML element containing the ``role="main"`` attribute. For example::
<div role="main">
{% block document %}{% endblock %}
</div>
`sidebar1` / `sidebar2`
A possible location for a sidebar. `sidebar1` appears before the document
and is empty by default, `sidebar2` after the document and contains the
``sidebar1``, ``sidebar2``
A possible location for a sidebar. ``sidebar1`` appears before the document
and is empty by default, ``sidebar2`` after the document and contains the
default sidebar. If you want to swap the sidebar location override this and
call the `sidebar` helper:
.. sourcecode:: html+jinja
call the ``sidebar`` helper::
{% block sidebar1 %}{{ sidebar() }}{% endblock %}
{% block sidebar2 %}{% endblock %}
(The `sidebar2` location for the sidebar is needed by the ``sphinxdoc.css``
(The ``sidebar2`` location for the sidebar is needed by the ``sphinxdoc.css``
stylesheet, for example.)
`sidebarlogo`
``sidebarlogo``
The logo location within the sidebar. Override this if you want to place
some content at the top of the sidebar.
`footer`
``footer``
The block for the footer div. If you want a custom footer or markup before
or after it, override this one.
@ -159,23 +157,23 @@ list of custom sidebars in the :confval:`html_sidebars` config value. Their use
is deprecated in favor of separate sidebar templates, which can be included via
:confval:`html_sidebars`.
`sidebartoc`
``sidebartoc``
The table of contents within the sidebar.
.. deprecated:: 1.0
`sidebarrel`
``sidebarrel``
The relation links (previous, next document) within the sidebar.
.. deprecated:: 1.0
`sidebarsourcelink`
``sidebarsourcelink``
The "Show source" link within the sidebar (normally only shown if this is
enabled by :confval:`html_show_sourcelink`).
.. deprecated:: 1.0
`sidebarsearch`
``sidebarsearch``
The search box within the sidebar. Override this if you want to place some
content at the bottom of the sidebar.
@ -392,7 +390,6 @@ are in HTML form), these variables are also available:
<a href="{{ next.link|e }}">{{ next.title }}</a>
{% endif %}
.. data:: page_source_suffix
The suffix of the file that was rendered. Since we support a list of
@ -424,14 +421,19 @@ are in HTML form), these variables are also available:
A callable yielding the global TOC tree containing the current page, rendered
as HTML bullet lists. Optional keyword arguments:
* ``collapse`` (``True`` by default): if true, all TOC entries that are not
ancestors of the current page are collapsed
``collapse``
If true, all TOC entries that are not ancestors of the current page are
collapsed.
``True`` by default.
* ``maxdepth`` (defaults to the max depth selected in the toctree directive):
the maximum depth of the tree; set it to ``-1`` to allow unlimited depth
``maxdepth``
The maximum depth of the tree. Set it to ``-1`` to allow unlimited depth.
Defaults to the max depth selected in the toctree directive.
* ``titles_only`` (``False`` by default): if true, put only toplevel document
titles in the tree
``titles_only``
If true, put only top-level document titles in the tree.
``False`` by default.
* ``includehidden`` (``False`` by default): if true, the TOC tree will also
contain hidden entries.
``includehidden``
If true, the ToC tree will also contain hidden entries.
``False`` by default.

View File

@ -574,7 +574,7 @@ General configuration
A dictionary of options that modify how the lexer specified by
:confval:`highlight_language` generates highlighted source code. These are
lexer-specific; for the options understood by each, see the
`Pygments documentation <https://pygments.org/docs/lexers.html>`_.
`Pygments documentation <https://pygments.org/docs/lexers>`_.
.. versionadded:: 1.3

View File

@ -454,7 +454,14 @@ There are also config values that you can set:
looks like a signature, use the line as the signature and remove it from the
docstring content.
If the signature line ends with backslash, autodoc considers the function has
multiple signatures and look at the next line of the docstring. It is useful
for overloaded function.
.. versionadded:: 1.1
.. versionchanged:: 3.1
Support overloaded signatures
.. confval:: autodoc_mock_imports

View File

@ -285,8 +285,12 @@ The following variables available in the templates:
.. data:: attributes
List containing names of "public" attributes in the class. Only available
for classes.
List containing names of "public" attributes in the class/module. Only
available for classes and modules.
.. versionchanged:: 3.1
Attributes of modules are supported.
.. data:: modules

23
doc/usage/index.rst Normal file
View File

@ -0,0 +1,23 @@
============
Using Sphinx
============
This guide serves to demonstrate how one can get started with Sphinx and covers
everything from installing Sphinx and configuring your first Sphinx project to
using some of the advanced features Sphinx provides out-of-the-box. If you are
looking for guidance on extending Sphinx, refer to :doc:`/development/index`.
.. toctree::
:maxdepth: 2
quickstart
installation
restructuredtext/index
markdown
configuration
builders/index
extensions/index
theming
advanced/intl
advanced/setuptools
advanced/websupport/index

View File

@ -12,9 +12,13 @@ Installing Sphinx
Overview
--------
Sphinx is written in `Python`__ and supports Python 3.6+.
Sphinx is written in `Python`__ and supports Python 3.6+. It builds upon the
shoulders of many third-party libraries such as `Docutils`__ and `Jinja`__,
which are installed when Sphinx is installed.
__ https://docs.python-guide.org/
__ https://docutils.sourceforge.io/
__ https://jinja.palletsprojects.com/
Linux

View File

@ -2,21 +2,38 @@
Getting Started
===============
Once Sphinx is :doc:`installed </usage/installation>`, you can proceed with
setting up your first Sphinx project. To ease the process of getting started,
Sphinx provides a tool, :program:`sphinx-quickstart`, which will generate a
documentation source directory and populate it with some defaults. We're going
to use the :program:`sphinx-quickstart` tool here, though its use is by no means
necessary.
Sphinx is a *documentation generator* or a tool that translates a set of plain
text source files into various output formats, automatically producing
cross-references, indices, etc. That is, if you have a directory containing a
bunch of :doc:`/usage/restructuredtext/index` or :doc:`/usage/markdown`
documents, Sphinx can generate a series of HTML files, a PDF file (via LaTeX),
man pages and much more.
Sphinx focuses on documentation, in particular handwritten documentation,
however, Sphinx can also be used to generate blogs, homepages and even books.
Much of Sphinx's power comes from the richness of its default plain-text markup
format, :doc:`reStructuredText </usage/restructuredtext/index>`, along with
it's :doc:`significant extensibility capabilities </development/index>`.
The goal of this document is to give you a quick taste of what Sphinx it is and
how you might use it. When you're done here, you can check out the
:doc:`installation guide </usage/installation>` followed by the intro to the
default markup format used by Sphinx, :doc:`reStucturedText
</usage/restructuredtext/index>`.
For a great "introduction" to writing docs in general -- the whys and hows, see
also `Write the docs`__, written by Eric Holscher.
.. __: http://www.writethedocs.org/guide/writing/beginners-guide-to-docs/
Setting up the documentation sources
------------------------------------
The root directory of a Sphinx collection of :term:`reStructuredText` document
sources is called the :term:`source directory`. This directory also contains
the Sphinx configuration file :file:`conf.py`, where you can configure all
aspects of how Sphinx reads your sources and builds your documentation. [#]_
The root directory of a Sphinx collection of plain-text document sources is
called the :term:`source directory`. This directory also contains the Sphinx
configuration file :file:`conf.py`, where you can configure all aspects of how
Sphinx reads your sources and builds your documentation. [#]_
Sphinx comes with a script called :program:`sphinx-quickstart` that sets up a
source directory and creates a default :file:`conf.py` with the most useful
@ -26,9 +43,6 @@ configuration values from a few questions it asks you. To use this, run:
$ sphinx-quickstart
There is also an automatic "API documentation" generator called
:program:`sphinx-apidoc`; see :doc:`/man/sphinx-apidoc` for details.
Defining document structure
---------------------------

View File

@ -159,7 +159,7 @@ Doctest blocks
Doctest blocks (:duref:`ref <doctest-blocks>`) are interactive Python sessions
cut-and-pasted into docstrings. They do not require the
:ref:`literal blocks <rst-literal-blocks>` syntax. The doctest block must end
with a blank line and should *not* end with with an unused prompt::
with a blank line and should *not* end with an unused prompt::
>>> 1 + 1
2

View File

@ -1,5 +1,7 @@
.. highlight:: python
.. _html-themes:
HTML
====

View File

@ -44,7 +44,7 @@ extras_require = {
'lint': [
'flake8>=3.5.0',
'flake8-import-order',
'mypy>=0.770',
'mypy>=0.780',
'docutils-stubs',
],
'test': [
@ -201,6 +201,7 @@ setup(
'Programming Language :: Python :: 3 :: Only',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: Implementation :: CPython',
'Programming Language :: Python :: Implementation :: PyPy',
'Framework :: Setuptools Plugin',

View File

@ -432,22 +432,32 @@ class Sphinx:
logger.debug('[app] disconnecting event: [id=%s]', listener_id)
self.events.disconnect(listener_id)
def emit(self, event: str, *args: Any) -> List:
def emit(self, event: str, *args: Any,
allowed_exceptions: Tuple["Type[Exception]", ...] = ()) -> List:
"""Emit *event* and pass *arguments* to the callback functions.
Return the return values of all callbacks as a list. Do not emit core
Sphinx events in extensions!
"""
return self.events.emit(event, *args)
def emit_firstresult(self, event: str, *args: Any) -> Any:
.. versionchanged:: 3.1
Added *allowed_exceptions* to specify path-through exceptions
"""
return self.events.emit(event, *args, allowed_exceptions=allowed_exceptions)
def emit_firstresult(self, event: str, *args: Any,
allowed_exceptions: Tuple["Type[Exception]", ...] = ()) -> Any:
"""Emit *event* and pass *arguments* to the callback functions.
Return the result of the first callback that doesn't return ``None``.
.. versionadded:: 0.5
.. versionchanged:: 3.1
Added *allowed_exceptions* to specify path-through exceptions
"""
return self.events.emit_firstresult(event, *args)
return self.events.emit_firstresult(event, *args,
allowed_exceptions=allowed_exceptions)
# registering addon parts
@ -929,12 +939,14 @@ class Sphinx:
if hasattr(self.builder, 'add_css_file'):
self.builder.add_css_file(filename, **kwargs) # type: ignore
def add_latex_package(self, packagename: str, options: str = None) -> None:
def add_latex_package(self, packagename: str, options: str = None,
after_hyperref: bool = False) -> None:
r"""Register a package to include in the LaTeX source code.
Add *packagename* to the list of packages that LaTeX source code will
include. If you provide *options*, it will be taken to `\usepackage`
declaration.
declaration. If you set *after_hyperref* truthy, the package will be
loaded after ``hyperref`` package.
.. code-block:: python
@ -944,8 +956,11 @@ class Sphinx:
# => \usepackage[foo,bar]{mypackage}
.. versionadded:: 1.3
.. versionadded:: 3.1
*after_hyperref* option.
"""
self.registry.add_latex_package(packagename, options)
self.registry.add_latex_package(packagename, options, after_hyperref)
def add_lexer(self, alias: str, lexer: Type[Lexer]) -> None:
"""Register a new lexer for source code.

View File

@ -127,6 +127,7 @@ class LaTeXBuilder(Builder):
self.document_data = [] # type: List[Tuple[str, str, str, str, str, bool]]
self.themes = ThemeFactory(self.app)
self.usepackages = self.app.registry.latex_packages
self.usepackages_after_hyperref = self.app.registry.latex_packages_after_hyperref
texescape.init()
self.init_context()
@ -178,6 +179,7 @@ class LaTeXBuilder(Builder):
# Apply extension settings to context
self.context['packages'] = self.usepackages
self.context['packages_after_hyperref'] = self.usepackages_after_hyperref
# Apply user settings to context
self.context.update(self.config.latex_elements)
@ -456,6 +458,12 @@ def validate_latex_theme_options(app: Sphinx, config: Config) -> None:
config.latex_theme_options.pop(key)
def install_pakcages_for_ja(app: Sphinx) -> None:
"""Install packages for Japanese."""
if app.config.language == 'ja':
app.add_latex_package('pxjahyper', after_hyperref=True)
def default_latex_engine(config: Config) -> str:
""" Better default latex_engine settings for specific languages. """
if config.language == 'ja':
@ -503,6 +511,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
app.add_builder(LaTeXBuilder)
app.connect('config-inited', validate_config_values, priority=800)
app.connect('config-inited', validate_latex_theme_options, priority=800)
app.connect('builder-inited', install_pakcages_for_ja)
app.add_config_value('latex_engine', default_latex_engine, None,
ENUM('pdflatex', 'xelatex', 'lualatex', 'platex', 'uplatex'))

View File

@ -126,7 +126,7 @@ class Config:
'rst_epilog': (None, 'env', [str]),
'rst_prolog': (None, 'env', [str]),
'trim_doctest_flags': (True, 'env', []),
'primary_domain': ('py', 'env', [NoneType]), # type: ignore
'primary_domain': ('py', 'env', [NoneType]),
'needs_sphinx': (None, None, [str]),
'needs_extensions': ({}, None, []),
'manpages_url': (None, 'env', []),

View File

@ -432,9 +432,9 @@ class ASTUnaryOpExpr(ASTExpression):
def _stringify(self, transform: StringifyTransform) -> str:
if self.op[0] in 'cn':
return transform(self.op) + " " + transform(self.expr)
return self.op + " " + transform(self.expr)
else:
return transform(self.op) + transform(self.expr)
return self.op + transform(self.expr)
def describe_signature(self, signode: TextElement, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:

View File

@ -951,12 +951,12 @@ class ASTFoldExpr(ASTExpression):
if self.leftExpr:
res.append(transform(self.leftExpr))
res.append(' ')
res.append(transform(self.op))
res.append(self.op)
res.append(' ')
res.append('...')
if self.rightExpr:
res.append(' ')
res.append(transform(self.op))
res.append(self.op)
res.append(' ')
res.append(transform(self.rightExpr))
res.append(')')
@ -1223,9 +1223,9 @@ class ASTUnaryOpExpr(ASTExpression):
def _stringify(self, transform: StringifyTransform) -> str:
if self.op[0] in 'cn':
return transform(self.op) + " " + transform(self.expr)
return self.op + " " + transform(self.expr)
else:
return transform(self.op) + transform(self.expr)
return self.op + transform(self.expr)
def get_id(self, version: int) -> str:
return _id_operator_unary_v2[self.op] + self.expr.get_id(version)
@ -3538,6 +3538,8 @@ class ASTTemplateIntroduction(ASTBase):
signode += nodes.Text('}')
################################################################################
class ASTTemplateDeclarationPrefix(ASTBase):
def __init__(self,
templates: List[Union[ASTTemplateParams,
@ -3566,18 +3568,35 @@ class ASTTemplateDeclarationPrefix(ASTBase):
t.describe_signature_as_introducer(signode, 'lastIsName', env, symbol, lineSpec)
class ASTRequiresClause(ASTBase):
def __init__(self, expr: ASTExpression) -> None:
self.expr = expr
def _stringify(self, transform: StringifyTransform) -> str:
return 'requires ' + transform(self.expr)
def describe_signature(self, signode: addnodes.desc_signature_line, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
signode += nodes.Text('requires ', 'requires ')
self.expr.describe_signature(signode, mode, env, symbol)
################################################################################
################################################################################
class ASTDeclaration(ASTBase):
def __init__(self, objectType: str, directiveType: str, visibility: str,
templatePrefix: ASTTemplateDeclarationPrefix, declaration: Any,
templatePrefix: ASTTemplateDeclarationPrefix,
requiresClause: ASTRequiresClause, declaration: Any,
trailingRequiresClause: ASTRequiresClause,
semicolon: bool = False) -> None:
self.objectType = objectType
self.directiveType = directiveType
self.visibility = visibility
self.templatePrefix = templatePrefix
self.requiresClause = requiresClause
self.declaration = declaration
self.trailingRequiresClause = trailingRequiresClause
self.semicolon = semicolon
self.symbol = None # type: Symbol
@ -3585,13 +3604,14 @@ class ASTDeclaration(ASTBase):
self.enumeratorScopedSymbol = None # type: Symbol
def clone(self) -> "ASTDeclaration":
if self.templatePrefix:
templatePrefixClone = self.templatePrefix.clone()
else:
templatePrefixClone = None
return ASTDeclaration(self.objectType, self.directiveType,
self.visibility, templatePrefixClone,
self.declaration.clone(), self.semicolon)
templatePrefixClone = self.templatePrefix.clone() if self.templatePrefix else None
requiresClasueClone = self.requiresClause.clone() if self.requiresClause else None
trailingRequiresClasueClone = self.trailingRequiresClause.clone() \
if self.trailingRequiresClause else None
return ASTDeclaration(self.objectType, self.directiveType, self.visibility,
templatePrefixClone, requiresClasueClone,
self.declaration.clone(), trailingRequiresClasueClone,
self.semicolon)
@property
def name(self) -> ASTNestedName:
@ -3619,6 +3639,17 @@ class ASTDeclaration(ASTBase):
res = []
if self.templatePrefix:
res.append(self.templatePrefix.get_id(version))
if self.requiresClause or self.trailingRequiresClause:
if version < 4:
raise NoOldIdError()
res.append('IQ')
if self.requiresClause and self.trailingRequiresClause:
res.append('aa')
if self.requiresClause:
res.append(self.requiresClause.expr.get_id(version))
if self.trailingRequiresClause:
res.append(self.trailingRequiresClause.expr.get_id(version))
res.append('E')
res.append(self.declaration.get_id(version, self.objectType, self.symbol))
return ''.join(res)
@ -3632,7 +3663,13 @@ class ASTDeclaration(ASTBase):
res.append(' ')
if self.templatePrefix:
res.append(transform(self.templatePrefix))
if self.requiresClause:
res.append(transform(self.requiresClause))
res.append(' ')
res.append(transform(self.declaration))
if self.trailingRequiresClause:
res.append(' ')
res.append(transform(self.trailingRequiresClause))
if self.semicolon:
res.append(';')
return ''.join(res)
@ -3653,6 +3690,11 @@ class ASTDeclaration(ASTBase):
self.templatePrefix.describe_signature(signode, mode, env,
symbol=self.symbol,
lineSpec=options.get('tparam-line-spec'))
if self.requiresClause:
reqNode = addnodes.desc_signature_line()
reqNode.sphinx_line_type = 'requiresClause'
signode.append(reqNode)
self.requiresClause.describe_signature(reqNode, 'markType', env, self.symbol)
signode += mainDeclNode
if self.visibility and self.visibility != "public":
mainDeclNode += addnodes.desc_annotation(self.visibility + " ",
@ -3688,8 +3730,16 @@ class ASTDeclaration(ASTBase):
else:
assert False
self.declaration.describe_signature(mainDeclNode, mode, env, self.symbol)
lastDeclNode = mainDeclNode
if self.trailingRequiresClause:
trailingReqNode = addnodes.desc_signature_line()
trailingReqNode.sphinx_line_type = 'trailingRequiresClause'
signode.append(trailingReqNode)
lastDeclNode = trailingReqNode
self.trailingRequiresClause.describe_signature(
trailingReqNode, 'markType', env, self.symbol)
if self.semicolon:
mainDeclNode += nodes.Text(';')
lastDeclNode += nodes.Text(';')
class ASTNamespace(ASTBase):
@ -3808,7 +3858,7 @@ class Symbol:
continue
# only add a declaration if we our self are from a declaration
if self.declaration:
decl = ASTDeclaration('templateParam', None, None, None, tp)
decl = ASTDeclaration('templateParam', None, None, None, None, tp, None)
else:
decl = None
nne = ASTNestedNameElement(tp.get_identifier(), None)
@ -3823,7 +3873,7 @@ class Symbol:
if nn is None:
continue
# (comparing to the template params: we have checked that we are a declaration)
decl = ASTDeclaration('functionParam', None, None, None, fp)
decl = ASTDeclaration('functionParam', None, None, None, None, fp, None)
assert not nn.rooted
assert len(nn.names) == 1
self._add_symbols(nn, [], decl, self.docname)
@ -6297,8 +6347,61 @@ class DefinitionParser(BaseParser):
'Expected ",", or "}".')
return ASTTemplateIntroduction(concept, params)
def _parse_requires_clause(self) -> Optional[ASTRequiresClause]:
# requires-clause -> 'requires' constraint-logical-or-expression
# constraint-logical-or-expression
# -> constraint-logical-and-expression
# | constraint-logical-or-expression '||' constraint-logical-and-expression
# constraint-logical-and-expression
# -> primary-expression
# | constraint-logical-and-expression '&&' primary-expression
self.skip_ws()
if not self.skip_word('requires'):
return None
def parse_and_expr(self: DefinitionParser) -> ASTExpression:
andExprs = []
ops = []
andExprs.append(self._parse_primary_expression())
while True:
self.skip_ws()
oneMore = False
if self.skip_string('&&'):
oneMore = True
ops.append('&&')
elif self.skip_word('and'):
oneMore = True
ops.append('and')
if not oneMore:
break
andExprs.append(self._parse_primary_expression())
if len(andExprs) == 1:
return andExprs[0]
else:
return ASTBinOpExpr(andExprs, ops)
orExprs = []
ops = []
orExprs.append(parse_and_expr(self))
while True:
self.skip_ws()
oneMore = False
if self.skip_string('||'):
oneMore = True
ops.append('||')
elif self.skip_word('or'):
oneMore = True
ops.append('or')
if not oneMore:
break
orExprs.append(parse_and_expr(self))
if len(orExprs) == 1:
return ASTRequiresClause(orExprs[0])
else:
return ASTRequiresClause(ASTBinOpExpr(orExprs, ops))
def _parse_template_declaration_prefix(self, objectType: str
) -> ASTTemplateDeclarationPrefix:
) -> Optional[ASTTemplateDeclarationPrefix]:
templates = [] # type: List[Union[ASTTemplateParams, ASTTemplateIntroduction]]
while 1:
self.skip_ws()
@ -6377,6 +6480,8 @@ class DefinitionParser(BaseParser):
raise Exception('Internal error, unknown directiveType "%s".' % directiveType)
visibility = None
templatePrefix = None
requiresClause = None
trailingRequiresClause = None
declaration = None # type: Any
self.skip_ws()
@ -6385,6 +6490,8 @@ class DefinitionParser(BaseParser):
if objectType in ('type', 'concept', 'member', 'function', 'class'):
templatePrefix = self._parse_template_declaration_prefix(objectType)
if objectType == 'function' and templatePrefix is not None:
requiresClause = self._parse_requires_clause()
if objectType == 'type':
prevErrors = []
@ -6410,6 +6517,8 @@ class DefinitionParser(BaseParser):
declaration = self._parse_type_with_init(named=True, outer='member')
elif objectType == 'function':
declaration = self._parse_type(named=True, outer='function')
if templatePrefix is not None:
trailingRequiresClause = self._parse_requires_clause()
elif objectType == 'class':
declaration = self._parse_class()
elif objectType == 'union':
@ -6427,7 +6536,8 @@ class DefinitionParser(BaseParser):
self.skip_ws()
semicolon = self.skip_string(';')
return ASTDeclaration(objectType, directiveType, visibility,
templatePrefix, declaration, semicolon)
templatePrefix, requiresClause, declaration,
trailingRequiresClause, semicolon)
def parse_namespace_object(self) -> ASTNamespace:
templatePrefix = self._parse_template_declaration_prefix(objectType="namespace")

View File

@ -116,7 +116,8 @@ class PycodeError(Exception):
class NoUri(Exception):
"""Raised by builder.get_relative_uri() if there is no URI available."""
"""Raised by builder.get_relative_uri() or from missing-reference handlers
if there is no URI available."""
pass

View File

@ -12,7 +12,7 @@
from collections import defaultdict
from operator import attrgetter
from typing import Any, Callable, Dict, List, NamedTuple
from typing import Any, Callable, Dict, List, NamedTuple, Tuple, Type
from typing import TYPE_CHECKING
from sphinx.errors import ExtensionError, SphinxError
@ -83,7 +83,8 @@ class EventManager:
if listener.id == listener_id:
listeners.remove(listener)
def emit(self, name: str, *args: Any) -> List:
def emit(self, name: str, *args: Any,
allowed_exceptions: Tuple[Type[Exception], ...] = ()) -> List:
"""Emit a Sphinx event."""
try:
logger.debug('[app] emitting event: %r%s', name, repr(args)[:100])
@ -97,6 +98,9 @@ class EventManager:
for listener in listeners:
try:
results.append(listener.handler(self.app, *args))
except allowed_exceptions:
# pass through the errors specified as *allowed_exceptions*
raise
except SphinxError:
raise
except Exception as exc:
@ -104,12 +108,13 @@ class EventManager:
(listener.handler, name)) from exc
return results
def emit_firstresult(self, name: str, *args: Any) -> Any:
def emit_firstresult(self, name: str, *args: Any,
allowed_exceptions: Tuple[Type[Exception], ...] = ()) -> Any:
"""Emit a Sphinx event and returns first result.
This returns the result of the first handler that doesn't return ``None``.
"""
for result in self.emit(name, *args):
for result in self.emit(name, *args, allowed_exceptions=allowed_exceptions):
if result is not None:
return result
return None

View File

@ -13,9 +13,11 @@
import importlib
import re
import warnings
from inspect import Parameter
from inspect import Parameter, Signature
from types import ModuleType
from typing import Any, Callable, Dict, Iterator, List, Sequence, Set, Tuple, Type, Union
from typing import (
Any, Callable, Dict, Iterator, List, Optional, Sequence, Set, Tuple, Type, Union
)
from typing import TYPE_CHECKING
from docutils.statemachine import StringList
@ -91,6 +93,16 @@ def inherited_members_option(arg: Any) -> Union[object, Set[str]]:
return arg
def member_order_option(arg: Any) -> Optional[str]:
"""Used to convert the :members: option to auto directives."""
if arg is None:
return None
elif arg in ('alphabetical', 'bysource', 'groupwise'):
return arg
else:
raise ValueError(__('invalid value for member-order option: %s') % arg)
SUPPRESS = object()
@ -379,6 +391,17 @@ class Documenter:
# directives of course)
return '.'.join(self.objpath) or self.modname
def _call_format_args(self, **kwargs: Any) -> str:
if kwargs:
try:
return self.format_args(**kwargs)
except TypeError:
# avoid chaining exceptions, by putting nothing here
pass
# retry without arguments for old documenters
return self.format_args()
def format_signature(self, **kwargs: Any) -> str:
"""Format the signature (arguments and return annotation) of the object.
@ -387,21 +410,22 @@ class Documenter:
if self.args is not None:
# signature given explicitly
args = "(%s)" % self.args
retann = self.retann
else:
# try to introspect the signature
try:
try:
args = self.format_args(**kwargs)
except TypeError:
# retry without arguments for old documenters
args = self.format_args()
retann = None
args = self._call_format_args(**kwargs)
if args:
matched = re.match(r'^(\(.*\))\s+->\s+(.*)$', args)
if matched:
args = matched.group(1)
retann = matched.group(2)
except Exception:
logger.warning(__('error while formatting arguments for %s:') %
self.fullname, type='autodoc', exc_info=True)
args = None
retann = self.retann
result = self.env.events.emit_firstresult('autodoc-process-signature',
self.objtype, self.fullname,
self.object, self.options, args, retann)
@ -516,12 +540,12 @@ class Documenter:
else:
logger.warning(__('missing attribute %s in object %s') %
(name, self.fullname), type='autodoc')
return False, sorted(selected)
return False, selected
elif self.options.inherited_members:
return False, sorted((m.name, m.value) for m in members.values())
return False, [(m.name, m.value) for m in members.values()]
else:
return False, sorted((m.name, m.value) for m in members.values()
if m.directly_defined)
return False, [(m.name, m.value) for m in members.values()
if m.directly_defined]
def filter_members(self, members: List[Tuple[str, Any]], want_all: bool
) -> List[Tuple[str, Any, bool]]:
@ -691,20 +715,9 @@ class Documenter:
'.'.join(self.objpath + [mname])
documenter = classes[-1](self.directive, full_mname, self.indent)
memberdocumenters.append((documenter, isattr))
member_order = self.options.member_order or \
self.env.config.autodoc_member_order
if member_order == 'groupwise':
# sort by group; relies on stable sort to keep items in the
# same group sorted alphabetically
memberdocumenters.sort(key=lambda e: e[0].member_order)
elif member_order == 'bysource' and self.analyzer:
# sort by source order, by virtue of the module analyzer
tagorder = self.analyzer.tagorder
def keyfunc(entry: Tuple[Documenter, bool]) -> int:
fullname = entry[0].name.split('::')[1]
return tagorder.get(fullname, len(tagorder))
memberdocumenters.sort(key=keyfunc)
member_order = self.options.member_order or self.env.config.autodoc_member_order
memberdocumenters = self.sort_members(memberdocumenters, member_order)
for documenter, isattr in memberdocumenters:
documenter.generate(
@ -715,6 +728,31 @@ class Documenter:
self.env.temp_data['autodoc:module'] = None
self.env.temp_data['autodoc:class'] = None
def sort_members(self, documenters: List[Tuple["Documenter", bool]],
order: str) -> List[Tuple["Documenter", bool]]:
"""Sort the given member list."""
if order == 'groupwise':
# sort by group; alphabetically within groups
documenters.sort(key=lambda e: (e[0].member_order, e[0].name))
elif order == 'bysource':
if self.analyzer:
# sort by source order, by virtue of the module analyzer
tagorder = self.analyzer.tagorder
def keyfunc(entry: Tuple[Documenter, bool]) -> int:
fullname = entry[0].name.split('::')[1]
return tagorder.get(fullname, len(tagorder))
documenters.sort(key=keyfunc)
else:
# Assume that member discovery order matches source order.
# This is a reasonable assumption in Python 3.6 and up, where
# module.__dict__ is insertion-ordered.
pass
else: # alphabetical
documenters.sort(key=lambda e: e[0].name)
return documenters
def generate(self, more_content: Any = None, real_modname: str = None,
check_module: bool = False, all_members: bool = False) -> None:
"""Generate reST for the object given by *self.name*, and possibly for
@ -812,7 +850,7 @@ class ModuleDocumenter(Documenter):
'noindex': bool_option, 'inherited-members': inherited_members_option,
'show-inheritance': bool_option, 'synopsis': identity,
'platform': identity, 'deprecated': bool_option,
'member-order': identity, 'exclude-members': members_set_option,
'member-order': member_order_option, 'exclude-members': members_set_option,
'private-members': bool_option, 'special-members': members_option,
'imported-members': bool_option, 'ignore-module-all': bool_option
} # type: Dict[str, Callable]
@ -820,6 +858,7 @@ class ModuleDocumenter(Documenter):
def __init__(self, *args: Any) -> None:
super().__init__(*args)
merge_special_members_option(self.options)
self.__all__ = None
@classmethod
def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any
@ -842,6 +881,30 @@ class ModuleDocumenter(Documenter):
type='autodoc')
return ret
def import_object(self) -> Any:
def is_valid_module_all(__all__: Any) -> bool:
"""Check the given *__all__* is valid for a module."""
if (isinstance(__all__, (list, tuple)) and
all(isinstance(e, str) for e in __all__)):
return True
else:
return False
ret = super().import_object()
if not self.options.ignore_module_all:
__all__ = getattr(self.object, '__all__', None)
if is_valid_module_all(__all__):
# valid __all__ found. copy it to self.__all__
self.__all__ = __all__
elif __all__:
# invalid __all__ found.
logger.warning(__('__all__ should be a list of strings, not %r '
'(in module %s) -- ignoring __all__') %
(__all__, self.fullname), type='autodoc')
return ret
def add_directive_header(self, sig: str) -> None:
Documenter.add_directive_header(self, sig)
@ -857,24 +920,12 @@ class ModuleDocumenter(Documenter):
def get_object_members(self, want_all: bool) -> Tuple[bool, List[Tuple[str, Any]]]:
if want_all:
if (self.options.ignore_module_all or not
hasattr(self.object, '__all__')):
if self.__all__:
memberlist = self.__all__
else:
# for implicit module members, check __module__ to avoid
# documenting imported objects
return True, get_module_members(self.object)
else:
memberlist = self.object.__all__
# Sometimes __all__ is broken...
if not isinstance(memberlist, (list, tuple)) or not \
all(isinstance(entry, str) for entry in memberlist):
logger.warning(
__('__all__ should be a list of strings, not %r '
'(in module %s) -- ignoring __all__') %
(memberlist, self.fullname),
type='autodoc'
)
# fall back to all members
return True, get_module_members(self.object)
else:
memberlist = self.options.members or []
ret = []
@ -890,6 +941,25 @@ class ModuleDocumenter(Documenter):
)
return False, ret
def sort_members(self, documenters: List[Tuple["Documenter", bool]],
order: str) -> List[Tuple["Documenter", bool]]:
if order == 'bysource' and self.__all__:
# Sort alphabetically first (for members not listed on the __all__)
documenters.sort(key=lambda e: e[0].name)
# Sort by __all__
def keyfunc(entry: Tuple[Documenter, bool]) -> int:
name = entry[0].name.split('::')[1]
if self.__all__ and name in self.__all__:
return self.__all__.index(name)
else:
return len(self.__all__)
documenters.sort(key=keyfunc)
return documenters
else:
return super().sort_members(documenters, order)
class ModuleLevelDocumenter(Documenter):
"""
@ -961,41 +1031,71 @@ class DocstringSignatureMixin:
Mixin for FunctionDocumenter and MethodDocumenter to provide the
feature of reading the signature from the docstring.
"""
_new_docstrings = None # type: List[List[str]]
_signatures = None # type: List[str]
def _find_signature(self) -> Tuple[str, str]:
# candidates of the object name
valid_names = [self.objpath[-1]] # type: ignore
if isinstance(self, ClassDocumenter):
valid_names.append('__init__')
if hasattr(self.object, '__mro__'):
valid_names.extend(cls.__name__ for cls in self.object.__mro__)
docstrings = self.get_doc()
self._new_docstrings = docstrings[:]
self._signatures = []
result = None
for i, doclines in enumerate(docstrings):
# no lines in docstring, no match
if not doclines:
continue
# match first line of docstring against signature RE
match = py_ext_sig_re.match(doclines[0])
if not match:
continue
exmod, path, base, args, retann = match.groups()
# the base name must match ours
valid_names = [self.objpath[-1]] # type: ignore
if isinstance(self, ClassDocumenter):
valid_names.append('__init__')
if hasattr(self.object, '__mro__'):
valid_names.extend(cls.__name__ for cls in self.object.__mro__)
if base not in valid_names:
continue
# re-prepare docstring to ignore more leading indentation
tab_width = self.directive.state.document.settings.tab_width # type: ignore
self._new_docstrings[i] = prepare_docstring('\n'.join(doclines[1:]),
tabsize=tab_width)
result = args, retann
# don't look any further
break
for j, line in enumerate(doclines):
if not line:
# no lines in docstring, no match
break
if line.endswith('\\'):
multiline = True
line = line.rstrip('\\').rstrip()
else:
multiline = False
# match first line of docstring against signature RE
match = py_ext_sig_re.match(line)
if not match:
continue
exmod, path, base, args, retann = match.groups()
# the base name must match ours
if base not in valid_names:
continue
# re-prepare docstring to ignore more leading indentation
tab_width = self.directive.state.document.settings.tab_width # type: ignore
self._new_docstrings[i] = prepare_docstring('\n'.join(doclines[j + 1:]),
tabsize=tab_width)
if result is None:
# first signature
result = args, retann
else:
# subsequent signatures
self._signatures.append("(%s) -> %s" % (args, retann))
if multiline:
# the signature have multiple signatures on docstring
continue
else:
# don't look any further
break
if result:
# finish the loop when signature found
break
return result
def get_doc(self, ignore: int = None) -> List[List[str]]:
lines = getattr(self, '_new_docstrings', None)
if lines is not None:
return lines
if self._new_docstrings is not None:
return self._new_docstrings
return super().get_doc(ignore) # type: ignore
def format_signature(self, **kwargs: Any) -> str:
@ -1005,7 +1105,11 @@ class DocstringSignatureMixin:
result = self._find_signature()
if result is not None:
self.args, self.retann = result
return super().format_signature(**kwargs) # type: ignore
sig = super().format_signature(**kwargs) # type: ignore
if self._signatures:
return "\n".join([sig] + self._signatures)
else:
return sig
class DocstringStripSignatureMixin(DocstringSignatureMixin):
@ -1087,6 +1191,7 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
documenter = FunctionDocumenter(self.directive, '')
documenter.object = func
documenter.objpath = [None]
sigs.append(documenter.format_signature())
return "\n".join(sigs)
@ -1128,6 +1233,14 @@ class DecoratorDocumenter(FunctionDocumenter):
return None
# Types which have confusing metaclass signatures it would be best not to show.
# These are listed by name, rather than storing the objects themselves, to avoid
# needing to import the modules.
_METACLASS_CALL_BLACKLIST = [
'enum.EnumMeta.__call__',
]
class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: ignore
"""
Specialized Documenter subclass for classes.
@ -1137,7 +1250,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
option_spec = {
'members': members_option, 'undoc-members': bool_option,
'noindex': bool_option, 'inherited-members': inherited_members_option,
'show-inheritance': bool_option, 'member-order': identity,
'show-inheritance': bool_option, 'member-order': member_order_option,
'exclude-members': members_set_option,
'private-members': bool_option, 'special-members': members_option,
} # type: Dict[str, Callable]
@ -1162,27 +1275,83 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
self.doc_as_attr = True
return ret
def _get_signature(self) -> Optional[Signature]:
def get_user_defined_function_or_method(obj: Any, attr: str) -> Any:
""" Get the `attr` function or method from `obj`, if it is user-defined. """
if inspect.is_builtin_class_method(obj, attr):
return None
attr = self.get_attr(obj, attr, None)
if not (inspect.ismethod(attr) or inspect.isfunction(attr)):
return None
return attr
# This sequence is copied from inspect._signature_from_callable.
# ValueError means that no signature could be found, so we keep going.
# First, let's see if it has an overloaded __call__ defined
# in its metaclass
call = get_user_defined_function_or_method(type(self.object), '__call__')
if call is not None:
if "{0.__module__}.{0.__qualname__}".format(call) in _METACLASS_CALL_BLACKLIST:
call = None
if call is not None:
self.env.app.emit('autodoc-before-process-signature', call, True)
try:
return inspect.signature(call, bound_method=True)
except ValueError:
pass
# Now we check if the 'obj' class has a '__new__' method
new = get_user_defined_function_or_method(self.object, '__new__')
if new is not None:
self.env.app.emit('autodoc-before-process-signature', new, True)
try:
return inspect.signature(new, bound_method=True)
except ValueError:
pass
# Finally, we should have at least __init__ implemented
init = get_user_defined_function_or_method(self.object, '__init__')
if init is not None:
self.env.app.emit('autodoc-before-process-signature', init, True)
try:
return inspect.signature(init, bound_method=True)
except ValueError:
pass
# None of the attributes are user-defined, so fall back to let inspect
# handle it.
# We don't know the exact method that inspect.signature will read
# the signature from, so just pass the object itself to our hook.
self.env.app.emit('autodoc-before-process-signature', self.object, False)
try:
return inspect.signature(self.object, bound_method=False)
except ValueError:
pass
# Still no signature: happens e.g. for old-style classes
# with __init__ in C and no `__text_signature__`.
return None
def format_args(self, **kwargs: Any) -> str:
if self.env.config.autodoc_typehints in ('none', 'description'):
kwargs.setdefault('show_annotation', False)
# for classes, the relevant signature is the __init__ method's
initmeth = self.get_attr(self.object, '__init__', None)
# classes without __init__ method, default __init__ or
# __init__ written in C?
if initmeth is None or \
inspect.is_builtin_class_method(self.object, '__init__') or \
not(inspect.ismethod(initmeth) or inspect.isfunction(initmeth)):
return None
try:
self.env.app.emit('autodoc-before-process-signature', initmeth, True)
sig = inspect.signature(initmeth, bound_method=True)
return stringify_signature(sig, show_return_annotation=False, **kwargs)
except TypeError:
# still not possible: happens e.g. for old-style classes
# with __init__ in C
sig = self._get_signature()
except TypeError as exc:
# __signature__ attribute contained junk
logger.warning(__("Failed to get a constructor signature for %s: %s"),
self.fullname, exc)
return None
if sig is None:
return None
return stringify_signature(sig, show_return_annotation=False, **kwargs)
def format_signature(self, **kwargs: Any) -> str:
if self.doc_as_attr:
return ''
@ -1393,6 +1562,30 @@ class DataDeclarationDocumenter(DataDocumenter):
super().add_content(more_content, no_docstring=True)
class GenericAliasDocumenter(DataDocumenter):
"""
Specialized Documenter subclass for GenericAliases.
"""
objtype = 'genericalias'
directivetype = 'data'
priority = DataDocumenter.priority + 1
@classmethod
def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any
) -> bool:
return inspect.isgenericalias(member)
def add_directive_header(self, sig: str) -> None:
self.options.annotation = SUPPRESS # type: ignore
super().add_directive_header(sig)
def add_content(self, more_content: Any, no_docstring: bool = False) -> None:
name = stringify_typehint(self.object)
content = StringList([_('alias of %s') % name], source='')
super().add_content(content)
class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type: ignore
"""
Specialized Documenter subclass for methods (normal, static and class).
@ -1751,6 +1944,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
app.add_autodocumenter(ExceptionDocumenter)
app.add_autodocumenter(DataDocumenter)
app.add_autodocumenter(DataDeclarationDocumenter)
app.add_autodocumenter(GenericAliasDocumenter)
app.add_autodocumenter(FunctionDocumenter)
app.add_autodocumenter(DecoratorDocumenter)
app.add_autodocumenter(MethodDocumenter)
@ -1774,7 +1968,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
app.add_event('autodoc-process-signature')
app.add_event('autodoc-skip-member')
app.connect('config-inited', migrate_autodoc_member_order)
app.connect('config-inited', migrate_autodoc_member_order, priority=800)
app.setup_extension('sphinx.ext.autodoc.type_comment')
app.setup_extension('sphinx.ext.autodoc.typehints')

View File

@ -15,6 +15,7 @@ from docutils import nodes
from docutils.nodes import Node
from sphinx.application import Sphinx
from sphinx.domains.std import StandardDomain
from sphinx.locale import __
from sphinx.util import logging
from sphinx.util.nodes import clean_astext
@ -33,8 +34,7 @@ def get_node_depth(node: Node) -> int:
def register_sections_as_label(app: Sphinx, document: Node) -> None:
labels = app.env.domaindata['std']['labels']
anonlabels = app.env.domaindata['std']['anonlabels']
domain = cast(StandardDomain, app.env.get_domain('std'))
for node in document.traverse(nodes.section):
if (app.config.autosectionlabel_maxdepth and
get_node_depth(node) >= app.config.autosectionlabel_maxdepth):
@ -49,13 +49,13 @@ def register_sections_as_label(app: Sphinx, document: Node) -> None:
name = nodes.fully_normalize_name(ref_name)
sectname = clean_astext(title)
if name in labels:
if name in domain.labels:
logger.warning(__('duplicate label %s, other instance in %s'),
name, app.env.doc2path(labels[name][0]),
name, app.env.doc2path(domain.labels[name][0]),
location=node, type='autosectionlabel', subtype=docname)
anonlabels[name] = docname, labelid
labels[name] = docname, labelid, sectname
domain.anonlabels[name] = docname, labelid
domain.labels[name] = docname, labelid, sectname
def setup(app: Sphinx) -> Dict[str, Any]:

View File

@ -250,14 +250,13 @@ class Autosummary(SphinxDirective):
docname = posixpath.join(tree_prefix, real_name)
docname = posixpath.normpath(posixpath.join(dirname, docname))
if docname not in self.env.found_docs:
location = self.state_machine.get_source_and_line(self.lineno)
if excluded(self.env.doc2path(docname, None)):
msg = __('autosummary references excluded document %r. Ignored.')
else:
msg = __('autosummary: stub file not found %r. '
'Check your autosummary_generate setting.')
logger.warning(msg, real_name, location=location)
logger.warning(msg, real_name, location=self.get_source_info())
continue
docnames.append(docname)
@ -298,7 +297,8 @@ class Autosummary(SphinxDirective):
with mock(self.config.autosummary_mock_imports):
real_name, obj, parent, modname = import_by_name(name, prefixes=prefixes)
except ImportError:
logger.warning(__('autosummary: failed to import %s'), name)
logger.warning(__('autosummary: failed to import %s'), name,
location=self.get_source_info())
continue
self.bridge.result = StringList() # initialize for each documenter
@ -312,11 +312,13 @@ class Autosummary(SphinxDirective):
doccls = get_documenter(self.env.app, obj, parent)
documenter = doccls(self.bridge, full_name)
if not documenter.parse_name():
logger.warning(__('failed to parse name %s'), real_name)
logger.warning(__('failed to parse name %s'), real_name,
location=self.get_source_info())
items.append((display_name, '', '', real_name))
continue
if not documenter.import_object():
logger.warning(__('failed to import object %s'), real_name)
logger.warning(__('failed to import object %s'), real_name,
location=self.get_source_info())
items.append((display_name, '', '', real_name))
continue
if documenter.options.members and not documenter.check_module():

View File

@ -18,6 +18,7 @@
"""
import argparse
import inspect
import locale
import os
import pkgutil
@ -42,6 +43,7 @@ from sphinx.deprecation import RemovedInSphinx50Warning
from sphinx.ext.autodoc import Documenter
from sphinx.ext.autosummary import import_by_name, get_documenter
from sphinx.locale import __
from sphinx.pycode import ModuleAnalyzer, PycodeError
from sphinx.registry import SphinxComponentRegistry
from sphinx.util import logging
from sphinx.util import rst
@ -85,14 +87,14 @@ def setup_documenters(app: Any) -> None:
ModuleDocumenter, ClassDocumenter, ExceptionDocumenter, DataDocumenter,
FunctionDocumenter, MethodDocumenter, AttributeDocumenter,
InstanceAttributeDocumenter, DecoratorDocumenter, PropertyDocumenter,
SlotsAttributeDocumenter, DataDeclarationDocumenter,
SlotsAttributeDocumenter, DataDeclarationDocumenter, GenericAliasDocumenter,
SingledispatchFunctionDocumenter,
)
documenters = [
ModuleDocumenter, ClassDocumenter, ExceptionDocumenter, DataDocumenter,
FunctionDocumenter, MethodDocumenter, AttributeDocumenter,
InstanceAttributeDocumenter, DecoratorDocumenter, PropertyDocumenter,
SlotsAttributeDocumenter, DataDeclarationDocumenter,
SlotsAttributeDocumenter, DataDeclarationDocumenter, GenericAliasDocumenter,
SingledispatchFunctionDocumenter,
] # type: List[Type[Documenter]]
for documenter in documenters:
@ -137,11 +139,11 @@ class AutosummaryRenderer:
if isinstance(app, (Sphinx, DummyApplication)):
if app.translator:
self.env.add_extension("jinja2.ext.i18n")
self.env.install_gettext_translations(app.translator) # type: ignore
self.env.install_gettext_translations(app.translator)
elif isinstance(app, Builder):
if app.app.translator:
self.env.add_extension("jinja2.ext.i18n")
self.env.install_gettext_translations(app.app.translator) # type: ignore
self.env.install_gettext_translations(app.app.translator)
def exists(self, template_name: str) -> bool:
"""Check if template file exists."""
@ -171,6 +173,56 @@ class AutosummaryRenderer:
# -- Generating output ---------------------------------------------------------
class ModuleScanner:
def __init__(self, app: Any, obj: Any) -> None:
self.app = app
self.object = obj
def get_object_type(self, name: str, value: Any) -> str:
return get_documenter(self.app, value, self.object).objtype
def is_skipped(self, name: str, value: Any, objtype: str) -> bool:
try:
return self.app.emit_firstresult('autodoc-skip-member', objtype,
name, value, False, {})
except Exception as exc:
logger.warning(__('autosummary: failed to determine %r to be documented, '
'the following exception was raised:\n%s'),
name, exc, type='autosummary')
return False
def scan(self, imported_members: bool) -> List[str]:
members = []
for name in dir(self.object):
try:
value = safe_getattr(self.object, name)
except AttributeError:
value = None
objtype = self.get_object_type(name, value)
if self.is_skipped(name, value, objtype):
continue
try:
if inspect.ismodule(value):
imported = True
elif safe_getattr(value, '__module__') != self.object.__name__:
imported = True
else:
imported = False
except AttributeError:
imported = False
if imported_members:
# list all members up
members.append(name)
elif imported is False:
# list not-imported members up
members.append(name)
return members
def generate_autosummary_content(name: str, obj: Any, parent: Any,
template: AutosummaryRenderer, template_name: str,
imported_members: bool, app: Any,
@ -214,6 +266,21 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any,
public.append(name)
return public, items
def get_module_attrs(members: Any) -> Tuple[List[str], List[str]]:
"""Find module attributes with docstrings."""
attrs, public = [], []
try:
analyzer = ModuleAnalyzer.for_module(name)
attr_docs = analyzer.find_attr_docs()
for namespace, attr_name in attr_docs:
if namespace == '' and attr_name in members:
attrs.append(attr_name)
if not attr_name.startswith('_'):
public.append(attr_name)
except PycodeError:
pass # give up if ModuleAnalyzer fails to parse code
return public, attrs
def get_modules(obj: Any) -> Tuple[List[str], List[str]]:
items = [] # type: List[str]
for _, modname, ispkg in pkgutil.iter_modules(obj.__path__):
@ -226,13 +293,16 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any,
ns.update(context)
if doc.objtype == 'module':
ns['members'] = dir(obj)
scanner = ModuleScanner(app, obj)
ns['members'] = scanner.scan(imported_members)
ns['functions'], ns['all_functions'] = \
get_members(obj, {'function'}, imported=imported_members)
ns['classes'], ns['all_classes'] = \
get_members(obj, {'class'}, imported=imported_members)
ns['exceptions'], ns['all_exceptions'] = \
get_members(obj, {'exception'}, imported=imported_members)
ns['attributes'], ns['all_attributes'] = \
get_module_attrs(ns['members'])
ispackage = hasattr(obj, '__path__')
if ispackage and recursive:
ns['modules'], ns['all_modules'] = get_modules(obj)

View File

@ -2,6 +2,17 @@
.. automodule:: {{ fullname }}
{% block attributes %}
{% if attributes %}
.. rubric:: Module Attributes
.. autosummary::
{% for item in attributes %}
{{ item }}
{%- endfor %}
{% endif %}
{% endblock %}
{% block functions %}
{% if functions %}
.. rubric:: {{ _('Functions') }}

View File

@ -96,7 +96,7 @@ def setup(app: Sphinx) -> Dict[str, Any]:
# more information for mathjax secure url is here:
# https://docs.mathjax.org/en/latest/start.html#secure-access-to-the-cdn
app.add_config_value('mathjax_path',
'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/latest.js?'
'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/latest.js?'
'config=TeX-AMS-MML_HTMLorMML', 'html')
app.add_config_value('mathjax_options', {}, 'html')
app.add_config_value('mathjax_inline', [r'\(', r'\)'], 'html')

View File

@ -314,7 +314,7 @@ class GoogleDocstring:
return [line[min_indent:] for line in lines]
def _escape_args_and_kwargs(self, name: str) -> str:
if name.endswith('_'):
if name.endswith('_') and getattr(self._config, 'strip_signature_backslash', False):
name = name[:-1] + r'\_'
if name[:2] == '**':

View File

@ -186,7 +186,7 @@ class BuiltinTemplateLoader(TemplateBridge, BaseLoader):
self.environment.globals['accesskey'] = contextfunction(accesskey)
self.environment.globals['idgen'] = idgen
if use_i18n:
self.environment.install_gettext_translations(builder.app.translator) # type: ignore # NOQA
self.environment.install_gettext_translations(builder.app.translator)
def render(self, template: str, context: Dict) -> str: # type: ignore
return self.environment.get_template(template).render(context)

View File

@ -97,6 +97,8 @@ class SphinxComponentRegistry:
#: LaTeX packages; list of package names and its options
self.latex_packages = [] # type: List[Tuple[str, str]]
self.latex_packages_after_hyperref = [] # type: List[Tuple[str, str]]
#: post transforms; list of transforms
self.post_transforms = [] # type: List[Type[Transform]]
@ -362,9 +364,12 @@ class SphinxComponentRegistry:
logger.debug('[app] adding js_file: %r, %r', filename, attributes)
self.js_files.append((filename, attributes))
def add_latex_package(self, name: str, options: str) -> None:
def add_latex_package(self, name: str, options: str, after_hyperref: bool = False) -> None:
logger.debug('[app] adding latex package: %r', name)
self.latex_packages.append((name, options))
if after_hyperref:
self.latex_packages_after_hyperref.append((name, options))
else:
self.latex_packages.append((name, options))
def add_enumerable_node(self, node: "Type[Node]", figtype: str,
title_getter: TitleGetter = None, override: bool = False) -> None:

View File

@ -288,8 +288,8 @@ class IndexBuilder:
frozen.get('envversion') != self.env.version:
raise ValueError('old format')
index2fn = frozen['docnames']
self._filenames = dict(zip(index2fn, frozen['filenames']))
self._titles = dict(zip(index2fn, frozen['titles']))
self._filenames = dict(zip(index2fn, frozen['filenames'])) # type: ignore
self._titles = dict(zip(index2fn, frozen['titles'])) # type: ignore
def load_terms(mapping: Dict[str, Any]) -> Dict[str, Set[str]]:
rv = {}
@ -350,13 +350,13 @@ class IndexBuilder:
def get_terms(self, fn2index: Dict) -> Tuple[Dict[str, List[str]], Dict[str, List[str]]]:
rvs = {}, {} # type: Tuple[Dict[str, List[str]], Dict[str, List[str]]]
for rv, mapping in zip(rvs, (self._mapping, self._title_mapping)):
for k, v in mapping.items():
for k, v in mapping.items(): # type: ignore
if len(v) == 1:
fn, = v
if fn in fn2index:
rv[k] = fn2index[fn]
rv[k] = fn2index[fn] # type: ignore
else:
rv[k] = sorted([fn2index[fn] for fn in v if fn in fn2index])
rv[k] = sorted([fn2index[fn] for fn in v if fn in fn2index]) # type: ignore # NOQA
return rvs
def freeze(self) -> Dict[str, Any]:

View File

@ -46,6 +46,14 @@
<%- endfor %>
<%= hyperref %>
<%- for name, option in packages_after_hyperref %>
<%- if option %>
\usepackage[<%= option %>]{<%= name %>}
<%- else %>
\usepackage{<%= name %>}
<%- endif %>
<%- endfor %>
<%= contentsname %>
\usepackage{sphinxmessages}
<%= tocdepth %>

View File

@ -27,9 +27,9 @@ if "%1" == "help" (
echo. pickle to make pickle files
echo. json to make JSON files
echo. htmlhelp to make HTML files and an HTML help project
echo. qthelp to make HTML files and a qthelp project
echo. qthelp to make HTML files and a Qt help project
echo. devhelp to make HTML files and a Devhelp project
echo. epub to make an epub
echo. epub to make an EPUB
echo. latex to make LaTeX files (you can set PAPER=a4 or PAPER=letter)
echo. latexpdf to make LaTeX files and then PDFs out of them
echo. text to make text files
@ -69,7 +69,7 @@ if errorlevel 9009 (
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
echo.https://sphinx-doc.org/
exit /b 1
)

View File

@ -120,6 +120,7 @@
{%- else %}
<meta http-equiv="Content-Type" content="text/html; charset={{ encoding }}" />
{%- endif %}
<meta name="viewport" content="width=device-width, initial-scale=1.0">
{{- metatags }}
{%- block htmltitle %}
<title>{{ title|striptags|e }}{{ titlesuffix }}</title>

File diff suppressed because one or more lines are too long

View File

@ -14,10 +14,6 @@
<script src="{{ pathto('_static/bizstyle.js', 1) }}"></script>
{%- endblock %}
{# put the sidebar before the body #}
{% block sidebar1 %}{{ sidebar() }}{% endblock %}
{% block sidebar2 %}{% endblock %}
{# doctype override #}
{%- block doctype %}
<!doctype html>

View File

@ -39,6 +39,11 @@ div.document {
-webkit-box-shadow: 2px 2px 5px #000;
}
div.documentwrapper {
float: left;
width: 100%;
}
div.bodywrapper {
margin: 0 0 0 240px;
border-left: 1px solid #ccc;
@ -105,7 +110,6 @@ div.sphinxsidebarwrapper {
}
div.sphinxsidebar {
margin: 0;
padding: 0.5em 12px 12px 12px;
width: {{ theme_sidebarwidth|todim }};
{%- if theme_rightsidebar|tobool %}

View File

@ -1,14 +0,0 @@
{#
sphinxdoc/layout.html
~~~~~~~~~~~~~~~~~~~~~
Sphinx layout template for the sphinxdoc theme.
:copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
#}
{%- extends "basic/layout.html" %}
{# put the sidebar before the body #}
{% block sidebar1 %}{{ sidebar() }}{% endblock %}
{% block sidebar2 %}{% endblock %}

View File

@ -37,6 +37,11 @@ div.document {
background-repeat: repeat-x;
}
div.documentwrapper {
float: left;
width: 100%;
}
div.bodywrapper {
margin: 0 calc({{ theme_sidebarwidth|todim }} + 10px) 0 0;
border-right: 1px solid #ccc;
@ -86,7 +91,6 @@ div.sphinxsidebarwrapper {
}
div.sphinxsidebar {
margin: 0;
padding: 0.5em 15px 15px 0;
width: calc({{ theme_sidebarwidth|todim }} - 20px);
float: right;

View File

@ -92,7 +92,8 @@ class ReferencesResolver(SphinxPostTransform):
# no new node found? try the missing-reference event
if newnode is None:
newnode = self.app.emit_firstresult('missing-reference', self.env,
node, contnode)
node, contnode,
allowed_exceptions=(NoUri,))
# still not found? warn if node wishes to be warned about or
# we are in nit-picky mode
if newnode is None:

View File

@ -35,8 +35,7 @@ def get_terminal_width() -> int:
import termios
import fcntl
import struct
call = fcntl.ioctl(0, termios.TIOCGWINSZ, # type: ignore
struct.pack('hhhh', 0, 0, 0, 0))
call = fcntl.ioctl(0, termios.TIOCGWINSZ, struct.pack('hhhh', 0, 0, 0, 0))
height, width = struct.unpack('hhhh', call)[:2]
terminal_width = width
except Exception:

View File

@ -54,7 +54,7 @@ def get_image_size(filename: str) -> Optional[Tuple[int, int]]:
def guess_mimetype_for_stream(stream: IO, default: Optional[str] = None) -> Optional[str]:
imgtype = imghdr.what(stream) # type: ignore
imgtype = imghdr.what(stream)
if imgtype:
return 'image/' + imgtype
else:

View File

@ -13,6 +13,7 @@ import enum
import inspect
import re
import sys
import types
import typing
import warnings
from functools import partial, partialmethod
@ -304,6 +305,18 @@ def isproperty(obj: Any) -> bool:
return isinstance(obj, property)
def isgenericalias(obj: Any) -> bool:
"""Check if the object is GenericAlias."""
if (hasattr(typing, '_GenericAlias') and # only for py37+
isinstance(obj, typing._GenericAlias)): # type: ignore
return True
elif (hasattr(types, 'GenericAlias') and # only for py39+
isinstance(obj, types.GenericAlias)): # type: ignore
return True
else:
return False
def safe_getattr(obj: Any, name: str, *defargs: Any) -> Any:
"""A getattr() that turns all exceptions into AttributeErrors."""
try:
@ -501,19 +514,34 @@ def signature_from_str(signature: str) -> inspect.Signature:
# parameters
args = definition.args
defaults = list(args.defaults)
params = []
if hasattr(args, "posonlyargs"):
posonlyargs = len(args.posonlyargs) # type: ignore
positionals = posonlyargs + len(args.args)
else:
posonlyargs = 0
positionals = len(args.args)
for _ in range(len(defaults), positionals):
defaults.insert(0, Parameter.empty)
if hasattr(args, "posonlyargs"):
for arg in args.posonlyargs: # type: ignore
for i, arg in enumerate(args.posonlyargs): # type: ignore
if defaults[i] is Parameter.empty:
default = Parameter.empty
else:
default = ast_unparse(defaults[i])
annotation = ast_unparse(arg.annotation) or Parameter.empty
params.append(Parameter(arg.arg, Parameter.POSITIONAL_ONLY,
annotation=annotation))
default=default, annotation=annotation))
for i, arg in enumerate(args.args):
if len(args.args) - i <= len(args.defaults):
default = ast_unparse(args.defaults[-len(args.args) + i])
else:
if defaults[i + posonlyargs] is Parameter.empty:
default = Parameter.empty
else:
default = ast_unparse(defaults[i + posonlyargs])
annotation = ast_unparse(arg.annotation) or Parameter.empty
params.append(Parameter(arg.arg, Parameter.POSITIONAL_OR_KEYWORD,
@ -561,7 +589,7 @@ def getdoc(obj: Any, attrgetter: Callable = safe_getattr,
# This tries to obtain the docstring from super classes.
for basecls in getattr(cls, '__mro__', []):
meth = safe_getattr(basecls, name, None)
if meth:
if meth is not None:
doc = inspect.getdoc(meth)
if doc:
break

View File

@ -25,6 +25,7 @@ from sphinx.util import logging
if TYPE_CHECKING:
from sphinx.builders import Builder
from sphinx.domain import IndexEntry
from sphinx.environment import BuildEnvironment
from sphinx.utils.tags import Tags
@ -303,7 +304,7 @@ def get_prev_node(node: Node) -> Node:
return None
def traverse_translatable_index(doctree: Element) -> Iterable[Tuple[Element, List[str]]]:
def traverse_translatable_index(doctree: Element) -> Iterable[Tuple[Element, List["IndexEntry"]]]: # NOQA
"""Traverse translatable index node from a document tree."""
for node in doctree.traverse(NodeMatcher(addnodes.index, inline=False)): # type: addnodes.index # NOQA
if 'raw_entries' in node:

View File

@ -28,7 +28,7 @@ class BaseRenderer:
def __init__(self, loader: BaseLoader = None) -> None:
self.env = SandboxedEnvironment(loader=loader, extensions=['jinja2.ext.i18n'])
self.env.filters['repr'] = repr
self.env.install_gettext_translations(get_translator()) # type: ignore
self.env.install_gettext_translations(get_translator())
def render(self, template_name: str, context: Dict) -> str:
return self.env.get_template(template_name).render(context)

View File

@ -53,7 +53,7 @@ def stringify(annotation: Any) -> str:
return annotation.__name__
elif not annotation:
return repr(annotation)
elif annotation is NoneType: # type: ignore
elif annotation is NoneType:
return 'None'
elif (getattr(annotation, '__module__', None) == 'builtins' and
hasattr(annotation, '__qualname__')):
@ -91,7 +91,7 @@ def _stringify_py37(annotation: Any) -> str:
if getattr(annotation, '__args__', None):
if qualname == 'Union':
if len(annotation.__args__) > 1 and annotation.__args__[-1] is NoneType: # type: ignore # NOQA
if len(annotation.__args__) > 1 and annotation.__args__[-1] is NoneType:
if len(annotation.__args__) > 2:
args = ', '.join(stringify(a) for a in annotation.__args__[:-1])
return 'Optional[Union[%s]]' % args
@ -159,7 +159,7 @@ def _stringify_py36(annotation: Any) -> str:
annotation.__origin__ is typing.Union):
params = annotation.__args__
if params is not None:
if len(params) > 1 and params[-1] is NoneType: # type: ignore
if len(params) > 1 and params[-1] is NoneType:
if len(params) > 2:
param_str = ", ".join(stringify(p) for p in params[:-1])
return 'Optional[Union[%s]]' % param_str

View File

@ -1428,6 +1428,8 @@ class LaTeXTranslator(SphinxTranslator):
if 'refuri' in node:
return
if 'anonymous' in node:
return
if node.get('refid'):
prev_node = get_prev_node(node)
if isinstance(prev_node, nodes.reference) and node['refid'] == prev_node['refid']:

View File

@ -17,3 +17,9 @@ class C:
class D:
def __init__(self):
"""D(foo, bar, baz)"""
class E:
def __init__(self):
"""E(foo: int, bar: int, baz: int) -> None \\
E(foo: str, bar: str, baz: str) -> None"""

View File

@ -0,0 +1,6 @@
from typing import List, Callable
#: A list of int
T = List[int]
C = Callable[[int], None] # a generic alias not having a doccomment

View File

@ -0,0 +1,25 @@
__all__ = ['baz', 'foo', 'Bar']
def foo():
pass
class Bar:
pass
def baz():
pass
def qux():
pass
class Quux:
pass
def foobar():
pass

View File

@ -37,6 +37,26 @@ def tuple_args(x: Tuple[int, Union[int, str]]) -> Tuple[int, int]:
pass
class NewAnnotation:
def __new__(cls, i: int) -> 'NewAnnotation':
pass
class NewComment:
def __new__(cls, i):
# type: (int) -> NewComment
pass
class _MetaclassWithCall(type):
def __call__(cls, a: int):
pass
class SignatureFromMetaclass(metaclass=_MetaclassWithCall):
pass
def complex_func(arg1, arg2, arg3=None, *args, **kwargs):
# type: (str, List[int], Tuple[int, Union[str, Unknown]], *str, **str) -> None
pass
@ -48,4 +68,3 @@ def missing_attr(c,
):
# type: (...) -> str
return a + (b or "")

View File

@ -1,4 +1,4 @@
from os import * # NOQA
from os import path # NOQA
from typing import Union
@ -17,5 +17,25 @@ class Foo:
pass
def bar(x: Union[int, str], y: int = 1):
class _Baz:
pass
def bar(x: Union[int, str], y: int = 1) -> None:
pass
def _quux():
pass
class Exc(Exception):
pass
class _Exc(Exception):
pass
#: a module-level attribute
qux = 2

View File

@ -11,4 +11,5 @@
autosummary_dummy_module.Foo
autosummary_dummy_module.Foo.Bar
autosummary_dummy_module.bar
autosummary_dummy_module.qux
autosummary_importfail

View File

@ -126,6 +126,15 @@ def test_expressions():
print("Result: ", res)
print("Expected: ", output)
raise DefinitionError("")
displayString = ast.get_display_string()
if res != displayString:
# note: if the expression contains an anon name then this will trigger a falsely
print("")
print("Input: ", expr)
print("Result: ", res)
print("Display: ", displayString)
raise DefinitionError("")
# type expressions
exprCheck('int*')
exprCheck('int *const*')
@ -400,7 +409,7 @@ def test_function_definitions():
output='void f(int arr[static volatile const 42])')
class test_nested_name():
def test_nested_name():
check('struct', '{key}.A', {1: "A"})
check('struct', '{key}.A.B', {1: "A.B"})
check('function', 'void f(.A a)', {1: "f"})

View File

@ -154,6 +154,15 @@ def test_expressions():
print("Input: ", expr)
print("Result: ", res)
raise DefinitionError("")
displayString = ast.get_display_string()
if res != displayString:
# note: if the expression contains an anon name then this will trigger a falsely
print("")
print("Input: ", expr)
print("Result: ", res)
print("Display: ", displayString)
raise DefinitionError("")
# primary
exprCheck('nullptr', 'LDnE')
exprCheck('true', 'L1E')
@ -662,7 +671,7 @@ def test_operators():
check('function', 'void operator[]()', {1: "subscript-operator", 2: "ixv"})
class test_nested_name():
def test_nested_name():
check('class', '{key}::A', {1: "A", 2: "1A"})
check('class', '{key}::A::B', {1: "A::B", 2: "N1A1BE"})
check('function', 'void f(::A a)', {1: "f__A", 2: "1f1A"})
@ -818,6 +827,15 @@ def test_templates():
check('type', 'template<C T = int&> {key}A', {2: 'I_1CE1A'}, key='using')
def test_requires_clauses():
check('function', 'template<typename T> requires A auto f() -> void requires B',
{4: 'I0EIQaa1A1BE1fvv'})
check('function', 'template<typename T> requires A || B or C void f()',
{4: 'I0EIQoo1Aoo1B1CE1fvv'})
check('function', 'template<typename T> requires A && B || C and D void f()',
{4: 'I0EIQooaa1A1Baa1C1DE1fvv'})
def test_template_args():
# from breathe#218
check('function',

View File

@ -8,6 +8,9 @@
:license: BSD, see LICENSE for details.
"""
import pytest
from sphinx.errors import ExtensionError
from sphinx.events import EventManager
@ -22,3 +25,19 @@ def test_event_priority():
events.emit('builder-inited')
assert result == [3, 1, 2, 5, 4]
def test_event_allowed_exceptions():
def raise_error(app):
raise RuntimeError
events = EventManager(object()) # pass an dummy object as an app
events.connect('builder-inited', raise_error, priority=500)
# all errors are conveted to ExtensionError
with pytest.raises(ExtensionError):
events.emit('builder-inited')
# Allow RuntimeError (pass-through)
with pytest.raises(RuntimeError):
events.emit('builder-inited', allowed_exceptions=(RuntimeError,))

View File

@ -1,6 +1,6 @@
"""
test_autodoc
~~~~~~~~~~~~
test_ext_autodoc
~~~~~~~~~~~~~~~~
Test the autodoc extension. This tests mainly the Documenters; the auto
directives are tested in a test source file translated by test_build.
@ -162,7 +162,6 @@ def test_format_signature(app):
pass
assert formatsig('function', 'f', f, None, None) == '(a, b, c=1, **d)'
assert formatsig('function', 'f', f, 'a, b, c, d', None) == '(a, b, c, d)'
assert formatsig('function', 'f', f, None, 'None') == '(a, b, c=1, **d) -> None'
assert formatsig('function', 'g', g, None, None) == r"(a='\n')"
# test for classes
@ -170,21 +169,64 @@ def test_format_signature(app):
pass
class E:
pass
# no signature for classes without __init__
for C in (D, E):
assert formatsig('class', 'D', C, None, None) == ''
def __init__(self):
pass
# an empty init and no init are the same
for C in (D, E):
assert formatsig('class', 'D', C, None, None) == '()'
class SomeMeta(type):
def __call__(cls, a, b=None):
return type.__call__(cls, a, b)
# these three are all equivalent
class F:
def __init__(self, a, b=None):
pass
class FNew:
def __new__(cls, a, b=None):
return super().__new__(cls)
class FMeta(metaclass=SomeMeta):
pass
# and subclasses should always inherit
class G(F):
pass
for C in (F, G):
class GNew(FNew):
pass
class GMeta(FMeta):
pass
# subclasses inherit
for C in (F, FNew, FMeta, G, GNew, GMeta):
assert formatsig('class', 'C', C, None, None) == '(a, b=None)'
assert formatsig('class', 'C', D, 'a, b', 'X') == '(a, b) -> X'
class ListSubclass(list):
pass
# only supported if the python implementation decides to document it
if getattr(list, '__text_signature__', None) is not None:
assert formatsig('class', 'C', ListSubclass, None, None) == '(iterable=(), /)'
else:
assert formatsig('class', 'C', ListSubclass, None, None) == ''
class ExceptionSubclass(Exception):
pass
# Exception has no __text_signature__ at least in Python 3.8
if getattr(Exception, '__text_signature__', None) is None:
assert formatsig('class', 'C', ExceptionSubclass, None, None) == ''
# __init__ have signature at first line of docstring
directive.env.config.autoclass_content = 'both'
@ -247,6 +289,27 @@ def test_format_signature(app):
'(b, c=42, *d, **e)'
def test_autodoc_process_signature_typehints(app):
captured = []
def process_signature(*args):
captured.append(args)
app.connect('autodoc-process-signature', process_signature)
def func(x: int, y: int) -> int:
pass
directive = make_directive_bridge(app.env)
inst = app.registry.documenters['function'](directive, 'func')
inst.fullname = 'func'
inst.object = func
inst.objpath = ['func']
inst.format_signature()
assert captured == [(app, 'function', 'func', func,
directive.genopt, '(x: int, y: int)', 'int')]
def test_get_doc(app):
directive = make_directive_bridge(app.env)
@ -477,14 +540,14 @@ def test_autodoc_members(app):
# default (no-members)
actual = do_autodoc(app, 'class', 'target.inheritance.Base')
assert list(filter(lambda l: '::' in l, actual)) == [
'.. py:class:: Base',
'.. py:class:: Base()',
]
# default ALL-members
options = {"members": None}
actual = do_autodoc(app, 'class', 'target.inheritance.Base', options)
assert list(filter(lambda l: '::' in l, actual)) == [
'.. py:class:: Base',
'.. py:class:: Base()',
' .. py:method:: Base.inheritedclassmeth()',
' .. py:method:: Base.inheritedmeth()',
' .. py:method:: Base.inheritedstaticmeth(cls)'
@ -494,7 +557,7 @@ def test_autodoc_members(app):
options = {"members": "inheritedmeth,inheritedstaticmeth"}
actual = do_autodoc(app, 'class', 'target.inheritance.Base', options)
assert list(filter(lambda l: '::' in l, actual)) == [
'.. py:class:: Base',
'.. py:class:: Base()',
' .. py:method:: Base.inheritedmeth()',
' .. py:method:: Base.inheritedstaticmeth(cls)'
]
@ -506,7 +569,7 @@ def test_autodoc_exclude_members(app):
"exclude-members": "inheritedmeth,inheritedstaticmeth"}
actual = do_autodoc(app, 'class', 'target.inheritance.Base', options)
assert list(filter(lambda l: '::' in l, actual)) == [
'.. py:class:: Base',
'.. py:class:: Base()',
' .. py:method:: Base.inheritedclassmeth()'
]
@ -515,7 +578,7 @@ def test_autodoc_exclude_members(app):
"exclude-members": "inheritedmeth"}
actual = do_autodoc(app, 'class', 'target.inheritance.Base', options)
assert list(filter(lambda l: '::' in l, actual)) == [
'.. py:class:: Base',
'.. py:class:: Base()',
]
@ -659,10 +722,10 @@ def test_autodoc_ignore_module_all(app):
assert list(filter(lambda l: 'class::' in l, actual)) == [
'.. py:class:: Class(arg)',
'.. py:class:: CustomDict',
'.. py:class:: InnerChild',
'.. py:class:: InnerChild()',
'.. py:class:: InstAttCls()',
'.. py:class:: Outer',
' .. py:class:: Outer.Inner',
'.. py:class:: Outer()',
' .. py:class:: Outer.Inner()',
'.. py:class:: StrRepr'
]
@ -683,7 +746,7 @@ def test_autodoc_noindex(app):
actual = do_autodoc(app, 'class', 'target.inheritance.Base', options)
assert list(actual) == [
'',
'.. py:class:: Base',
'.. py:class:: Base()',
' :noindex:',
' :module: target.inheritance',
''
@ -710,13 +773,13 @@ def test_autodoc_inner_class(app):
actual = do_autodoc(app, 'class', 'target.Outer', options)
assert list(actual) == [
'',
'.. py:class:: Outer',
'.. py:class:: Outer()',
' :module: target',
'',
' Foo',
'',
'',
' .. py:class:: Outer.Inner',
' .. py:class:: Outer.Inner()',
' :module: target',
'',
' Foo',
@ -737,7 +800,7 @@ def test_autodoc_inner_class(app):
actual = do_autodoc(app, 'class', 'target.Outer.Inner', options)
assert list(actual) == [
'',
'.. py:class:: Outer.Inner',
'.. py:class:: Outer.Inner()',
' :module: target',
'',
' Foo',
@ -754,7 +817,7 @@ def test_autodoc_inner_class(app):
actual = do_autodoc(app, 'class', 'target.InnerChild', options)
assert list(actual) == [
'',
'.. py:class:: InnerChild',
'.. py:class:: InnerChild()',
' :module: target', '',
' Bases: :class:`target.Outer.Inner`',
'',
@ -798,7 +861,7 @@ def test_autodoc_descriptor(app):
actual = do_autodoc(app, 'class', 'target.descriptor.Class', options)
assert list(actual) == [
'',
'.. py:class:: Class',
'.. py:class:: Class()',
' :module: target.descriptor',
'',
'',
@ -817,21 +880,6 @@ def test_autodoc_descriptor(app):
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_c_module(app):
actual = do_autodoc(app, 'function', 'time.asctime')
assert list(actual) == [
'',
'.. py:function:: asctime([tuple]) -> string',
' :module: time',
'',
" Convert a time tuple to a string, e.g. 'Sat Jun 06 16:26:11 1998'.",
' When the time tuple is not present, current time as returned by localtime()',
' is used.',
'',
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_member_order(app):
# case member-order='bysource'
@ -909,6 +957,40 @@ def test_autodoc_member_order(app):
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_module_member_order(app):
# case member-order='bysource'
options = {"members": 'foo, Bar, baz, qux, Quux, foobar',
'member-order': 'bysource',
"undoc-members": True}
actual = do_autodoc(app, 'module', 'target.sort_by_all', options)
assert list(filter(lambda l: '::' in l, actual)) == [
'.. py:module:: target.sort_by_all',
'.. py:function:: baz()',
'.. py:function:: foo()',
'.. py:class:: Bar()',
'.. py:class:: Quux()',
'.. py:function:: foobar()',
'.. py:function:: qux()',
]
# case member-order='bysource' and ignore-module-all
options = {"members": 'foo, Bar, baz, qux, Quux, foobar',
'member-order': 'bysource',
"undoc-members": True,
"ignore-module-all": True}
actual = do_autodoc(app, 'module', 'target.sort_by_all', options)
assert list(filter(lambda l: '::' in l, actual)) == [
'.. py:module:: target.sort_by_all',
'.. py:function:: foo()',
'.. py:class:: Bar()',
'.. py:function:: baz()',
'.. py:function:: qux()',
'.. py:class:: Quux()',
'.. py:function:: foobar()',
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_module_scope(app):
app.env.temp_data['autodoc:module'] = 'target'
@ -947,7 +1029,7 @@ def test_class_attributes(app):
actual = do_autodoc(app, 'class', 'target.AttCls', options)
assert list(actual) == [
'',
'.. py:class:: AttCls',
'.. py:class:: AttCls()',
' :module: target',
'',
'',
@ -1067,7 +1149,7 @@ def test_slots(app):
' :module: target.slots',
'',
'',
'.. py:class:: Foo',
'.. py:class:: Foo()',
' :module: target.slots',
'',
'',
@ -1083,7 +1165,7 @@ def test_enum_class(app):
actual = do_autodoc(app, 'class', 'target.enum.EnumCls', options)
assert list(actual) == [
'',
'.. py:class:: EnumCls',
'.. py:class:: EnumCls(value)',
' :module: target.enum',
'',
' this is enum class',
@ -1166,96 +1248,6 @@ def test_descriptor_class(app):
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autofunction_for_classes(app):
actual = do_autodoc(app, 'function', 'target.classes.Foo')
assert list(actual) == [
'',
'.. py:function:: Foo()',
' :module: target.classes',
'',
]
actual = do_autodoc(app, 'function', 'target.classes.Bar')
assert list(actual) == [
'',
'.. py:function:: Bar(x, y)',
' :module: target.classes',
'',
]
actual = do_autodoc(app, 'function', 'target.classes.Baz')
assert list(actual) == [
'',
'.. py:function:: Baz(x, y)',
' :module: target.classes',
'',
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autofunction_for_callable(app):
actual = do_autodoc(app, 'function', 'target.callable.function')
assert list(actual) == [
'',
'.. py:function:: function(arg1, arg2, **kwargs)',
' :module: target.callable',
'',
' A callable object that behaves like a function.',
'',
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autofunction_for_method(app):
actual = do_autodoc(app, 'function', 'target.callable.method')
assert list(actual) == [
'',
'.. py:function:: method(arg1, arg2)',
' :module: target.callable',
'',
' docstring of Callable.method().',
'',
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autofunction_for_builtin(app):
actual = do_autodoc(app, 'function', 'os.umask')
assert list(actual) == [
'',
'.. py:function:: umask(mask, /)',
' :module: os',
'',
' Set the current numeric umask and return the previous umask.',
'',
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autofunction_for_methoddescriptor(app):
actual = do_autodoc(app, 'function', 'builtins.int.__add__')
assert list(actual) == [
'',
'.. py:function:: int.__add__(self, value, /)',
' :module: builtins',
'',
' Return self+value.',
'',
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autofunction_for_decorated(app):
actual = do_autodoc(app, 'function', 'target.decorator.foo')
assert list(actual) == [
'',
'.. py:function:: foo()',
' :module: target.decorator',
'',
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_automethod_for_builtin(app):
actual = do_autodoc(app, 'method', 'builtins.int.__add__')
@ -1290,7 +1282,7 @@ def test_abstractmethods(app):
'.. py:module:: target.abstractmethods',
'',
'',
'.. py:class:: Base',
'.. py:class:: Base()',
' :module: target.abstractmethods',
'',
'',
@ -1407,7 +1399,7 @@ def test_coroutine(app):
actual = do_autodoc(app, 'class', 'target.coroutine.AsyncClass', options)
assert list(actual) == [
'',
'.. py:class:: AsyncClass',
'.. py:class:: AsyncClass()',
' :module: target.coroutine',
'',
'',
@ -1449,7 +1441,7 @@ def test_coroutine(app):
def test_partialmethod(app):
expected = [
'',
'.. py:class:: Cell',
'.. py:class:: Cell()',
' :module: target.partialmethod',
'',
' An example for partialmethod.',
@ -1475,24 +1467,11 @@ def test_partialmethod(app):
assert list(actual) == expected
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_wrappedfunction(app):
actual = do_autodoc(app, 'function', 'target.wrappedfunction.slow_function')
assert list(actual) == [
'',
'.. py:function:: slow_function(message, timeout)',
' :module: target.wrappedfunction',
'',
' This function is slow.',
'',
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_partialmethod_undoc_members(app):
expected = [
'',
'.. py:class:: Cell',
'.. py:class:: Cell()',
' :module: target.partialmethod',
'',
' An example for partialmethod.',
@ -1608,6 +1587,37 @@ def test_autodoc_typed_instance_variables(app):
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_GenericAlias(app):
options = {"members": None,
"undoc-members": None}
actual = do_autodoc(app, 'module', 'target.genericalias', options)
if sys.version_info < (3, 7):
assert list(actual) == [
'',
'.. py:module:: target.genericalias',
'',
'',
'.. py:attribute:: T',
' :module: target.genericalias',
'',
' alias of :class:`typing.List`',
]
else:
assert list(actual) == [
'',
'.. py:module:: target.genericalias',
'',
'',
'.. py:data:: T',
' :module: target.genericalias',
'',
' A list of int',
'',
' alias of List[int]',
]
@pytest.mark.skipif(sys.version_info < (3, 9), reason='py39+ is required.')
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_Annotated(app):
@ -1668,22 +1678,6 @@ def test_singledispatch(app):
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_singledispatch_autofunction(app):
options = {}
actual = do_autodoc(app, 'function', 'target.singledispatch.func', options)
assert list(actual) == [
'',
'.. py:function:: func(arg, kwarg=None)',
' func(arg: int, kwarg=None)',
' func(arg: str, kwarg=None)',
' :module: target.singledispatch',
'',
' A function for general use.',
'',
]
@pytest.mark.skipif(sys.version_info < (3, 8),
reason='singledispatchmethod is available since python3.8')
@pytest.mark.sphinx('html', testroot='ext-autodoc')
@ -1695,7 +1689,7 @@ def test_singledispatchmethod(app):
'.. py:module:: target.singledispatchmethod',
'',
'',
'.. py:class:: Foo',
'.. py:class:: Foo()',
' :module: target.singledispatchmethod',
'',
' docstring',
@ -1740,7 +1734,7 @@ def test_cython(app):
'.. py:module:: target.cython',
'',
'',
'.. py:class:: Class',
'.. py:class:: Class()',
' :module: target.cython',
'',
' Docstring.',
@ -1771,7 +1765,7 @@ def test_final(app):
'.. py:module:: target.final',
'',
'',
'.. py:class:: Class',
'.. py:class:: Class()',
' :module: target.final',
' :final:',
'',

View File

@ -0,0 +1,148 @@
"""
test_ext_autodoc_autofunction
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Test the autodoc extension. This tests mainly the Documenters; the auto
directives are tested in a test source file translated by test_build.
:copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS.
:license: BSD, see LICENSE for details.
"""
import pytest
from test_ext_autodoc import do_autodoc
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_classes(app):
actual = do_autodoc(app, 'function', 'target.classes.Foo')
assert list(actual) == [
'',
'.. py:function:: Foo()',
' :module: target.classes',
'',
]
actual = do_autodoc(app, 'function', 'target.classes.Bar')
assert list(actual) == [
'',
'.. py:function:: Bar(x, y)',
' :module: target.classes',
'',
]
actual = do_autodoc(app, 'function', 'target.classes.Baz')
assert list(actual) == [
'',
'.. py:function:: Baz(x, y)',
' :module: target.classes',
'',
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_callable(app):
actual = do_autodoc(app, 'function', 'target.callable.function')
assert list(actual) == [
'',
'.. py:function:: function(arg1, arg2, **kwargs)',
' :module: target.callable',
'',
' A callable object that behaves like a function.',
'',
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_method(app):
actual = do_autodoc(app, 'function', 'target.callable.method')
assert list(actual) == [
'',
'.. py:function:: method(arg1, arg2)',
' :module: target.callable',
'',
' docstring of Callable.method().',
'',
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_builtin_function(app):
actual = do_autodoc(app, 'function', 'os.umask')
assert list(actual) == [
'',
'.. py:function:: umask(mask, /)',
' :module: os',
'',
' Set the current numeric umask and return the previous umask.',
'',
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_methoddescriptor(app):
actual = do_autodoc(app, 'function', 'builtins.int.__add__')
assert list(actual) == [
'',
'.. py:function:: int.__add__(self, value, /)',
' :module: builtins',
'',
' Return self+value.',
'',
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_decorated(app):
actual = do_autodoc(app, 'function', 'target.decorator.foo')
assert list(actual) == [
'',
'.. py:function:: foo()',
' :module: target.decorator',
'',
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_singledispatch(app):
options = {}
actual = do_autodoc(app, 'function', 'target.singledispatch.func', options)
assert list(actual) == [
'',
'.. py:function:: func(arg, kwarg=None)',
' func(arg: int, kwarg=None)',
' func(arg: str, kwarg=None)',
' :module: target.singledispatch',
'',
' A function for general use.',
'',
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_cfunction(app):
actual = do_autodoc(app, 'function', 'time.asctime')
assert list(actual) == [
'',
'.. py:function:: asctime([tuple]) -> string',
' :module: time',
'',
" Convert a time tuple to a string, e.g. 'Sat Jun 06 16:26:11 1998'.",
' When the time tuple is not present, current time as returned by localtime()',
' is used.',
'',
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_wrapped_function(app):
actual = do_autodoc(app, 'function', 'target.wrappedfunction.slow_function')
assert list(actual) == [
'',
'.. py:function:: slow_function(message, timeout)',
' :module: target.wrappedfunction',
'',
' This function is slow.',
'',
]

View File

@ -9,6 +9,7 @@
"""
import platform
import sys
import pytest
@ -27,7 +28,7 @@ def test_autoclass_content_class(app):
'.. py:module:: target.autoclass_content',
'',
'',
'.. py:class:: A',
'.. py:class:: A()',
' :module: target.autoclass_content',
'',
' A class having no __init__, no __new__',
@ -45,13 +46,13 @@ def test_autoclass_content_class(app):
' A class having __init__, no __new__',
'',
'',
'.. py:class:: D',
'.. py:class:: D()',
' :module: target.autoclass_content',
'',
' A class having no __init__, __new__(no docstring)',
'',
'',
'.. py:class:: E',
'.. py:class:: E()',
' :module: target.autoclass_content',
'',
' A class having no __init__, __new__',
@ -87,7 +88,7 @@ def test_autoclass_content_init(app):
'.. py:module:: target.autoclass_content',
'',
'',
'.. py:class:: A',
'.. py:class:: A()',
' :module: target.autoclass_content',
'',
' A class having no __init__, no __new__',
@ -105,13 +106,13 @@ def test_autoclass_content_init(app):
' __init__ docstring',
'',
'',
'.. py:class:: D',
'.. py:class:: D()',
' :module: target.autoclass_content',
'',
' A class having no __init__, __new__(no docstring)',
'',
'',
'.. py:class:: E',
'.. py:class:: E()',
' :module: target.autoclass_content',
'',
' __new__ docstring',
@ -147,7 +148,7 @@ def test_autoclass_content_both(app):
'.. py:module:: target.autoclass_content',
'',
'',
'.. py:class:: A',
'.. py:class:: A()',
' :module: target.autoclass_content',
'',
' A class having no __init__, no __new__',
@ -167,13 +168,13 @@ def test_autoclass_content_both(app):
' __init__ docstring',
'',
'',
'.. py:class:: D',
'.. py:class:: D()',
' :module: target.autoclass_content',
'',
' A class having no __init__, __new__(no docstring)',
'',
'',
'.. py:class:: E',
'.. py:class:: E()',
' :module: target.autoclass_content',
'',
' A class having no __init__, __new__',
@ -237,7 +238,7 @@ def test_autodoc_docstring_signature(app):
actual = do_autodoc(app, 'class', 'target.DocstringSig', options)
assert list(actual) == [
'',
'.. py:class:: DocstringSig',
'.. py:class:: DocstringSig()',
' :module: target',
'',
'',
@ -279,7 +280,7 @@ def test_autodoc_docstring_signature(app):
actual = do_autodoc(app, 'class', 'target.DocstringSig', options)
assert list(actual) == [
'',
'.. py:class:: DocstringSig',
'.. py:class:: DocstringSig()',
' :module: target',
'',
'',
@ -345,6 +346,10 @@ def test_autoclass_content_and_docstring_signature_class(app):
'',
'.. py:class:: D()',
' :module: target.docstring_signature',
'',
'',
'.. py:class:: E()',
' :module: target.docstring_signature',
''
]
@ -374,6 +379,11 @@ def test_autoclass_content_and_docstring_signature_init(app):
'',
'.. py:class:: D(foo, bar, baz)',
' :module: target.docstring_signature',
'',
'',
'.. py:class:: E(foo: int, bar: int, baz: int) -> None',
' E(foo: str, bar: str, baz: str) -> None',
' :module: target.docstring_signature',
''
]
@ -408,6 +418,11 @@ def test_autoclass_content_and_docstring_signature_both(app):
'.. py:class:: D(foo, bar, baz)',
' :module: target.docstring_signature',
'',
'',
'.. py:class:: E(foo: int, bar: int, baz: int) -> None',
' E(foo: str, bar: str, baz: str) -> None',
' :module: target.docstring_signature',
'',
]
@ -435,7 +450,7 @@ def test_mocked_module_imports(app, warning):
'.. py:module:: target.need_mocks',
'',
'',
'.. py:class:: TestAutodoc',
'.. py:class:: TestAutodoc()',
' :module: target.need_mocks',
'',
' TestAutodoc docstring.',
@ -493,6 +508,18 @@ def test_autodoc_typehints_signature(app):
' :module: target.typehints',
'',
'',
'.. py:class:: NewAnnotation(i: int)',
' :module: target.typehints',
'',
'',
'.. py:class:: NewComment(i: int)',
' :module: target.typehints',
'',
'',
'.. py:class:: SignatureFromMetaclass(a: int)',
' :module: target.typehints',
'',
'',
'.. py:function:: complex_func(arg1: str, arg2: List[int], arg3: Tuple[int, '
'Union[str, Unknown]] = None, *args: str, **kwargs: str) -> None',
' :module: target.typehints',
@ -547,6 +574,18 @@ def test_autodoc_typehints_none(app):
' :module: target.typehints',
'',
'',
'.. py:class:: NewAnnotation(i)',
' :module: target.typehints',
'',
'',
'.. py:class:: NewComment(i)',
' :module: target.typehints',
'',
'',
'.. py:class:: SignatureFromMetaclass(a)',
' :module: target.typehints',
'',
'',
'.. py:function:: complex_func(arg1, arg2, arg3=None, *args, **kwargs)',
' :module: target.typehints',
'',

View File

@ -19,7 +19,10 @@ from sphinx import addnodes
from sphinx.ext.autosummary import (
autosummary_table, autosummary_toc, mangle_signature, import_by_name, extract_summary
)
from sphinx.ext.autosummary.generate import AutosummaryEntry, generate_autosummary_docs, main as autogen_main
from sphinx.ext.autosummary.generate import (
AutosummaryEntry, generate_autosummary_content, generate_autosummary_docs,
main as autogen_main
)
from sphinx.testing.util import assert_node, etree_parse
from sphinx.util.docutils import new_document
from sphinx.util.osutil import cd
@ -189,6 +192,83 @@ def test_escaping(app, status, warning):
assert str_content(title) == 'underscore_module_'
@pytest.mark.sphinx(testroot='ext-autosummary')
def test_autosummary_generate_content_for_module(app):
import autosummary_dummy_module
template = Mock()
generate_autosummary_content('autosummary_dummy_module', autosummary_dummy_module, None,
template, None, False, app, False, {})
assert template.render.call_args[0][0] == 'module'
context = template.render.call_args[0][1]
assert context['members'] == ['Exc', 'Foo', '_Baz', '_Exc', '__builtins__',
'__cached__', '__doc__', '__file__', '__name__',
'__package__', '_quux', 'bar', 'qux']
assert context['functions'] == ['bar']
assert context['all_functions'] == ['_quux', 'bar']
assert context['classes'] == ['Foo']
assert context['all_classes'] == ['Foo', '_Baz']
assert context['exceptions'] == ['Exc']
assert context['all_exceptions'] == ['Exc', '_Exc']
assert context['attributes'] == ['qux']
assert context['all_attributes'] == ['qux']
assert context['fullname'] == 'autosummary_dummy_module'
assert context['module'] == 'autosummary_dummy_module'
assert context['objname'] == ''
assert context['name'] == ''
assert context['objtype'] == 'module'
@pytest.mark.sphinx(testroot='ext-autosummary')
def test_autosummary_generate_content_for_module_skipped(app):
import autosummary_dummy_module
template = Mock()
def skip_member(app, what, name, obj, skip, options):
if name in ('Foo', 'bar', 'Exc'):
return True
app.connect('autodoc-skip-member', skip_member)
generate_autosummary_content('autosummary_dummy_module', autosummary_dummy_module, None,
template, None, False, app, False, {})
context = template.render.call_args[0][1]
assert context['members'] == ['_Baz', '_Exc', '__builtins__', '__cached__', '__doc__',
'__file__', '__name__', '__package__', '_quux', 'qux']
assert context['functions'] == []
assert context['classes'] == []
assert context['exceptions'] == []
@pytest.mark.sphinx(testroot='ext-autosummary')
def test_autosummary_generate_content_for_module_imported_members(app):
import autosummary_dummy_module
template = Mock()
generate_autosummary_content('autosummary_dummy_module', autosummary_dummy_module, None,
template, None, True, app, False, {})
assert template.render.call_args[0][0] == 'module'
context = template.render.call_args[0][1]
assert context['members'] == ['Exc', 'Foo', 'Union', '_Baz', '_Exc', '__builtins__',
'__cached__', '__doc__', '__file__', '__loader__',
'__name__', '__package__', '__spec__', '_quux',
'bar', 'path', 'qux']
assert context['functions'] == ['bar']
assert context['all_functions'] == ['_quux', 'bar']
assert context['classes'] == ['Foo']
assert context['all_classes'] == ['Foo', '_Baz']
assert context['exceptions'] == ['Exc']
assert context['all_exceptions'] == ['Exc', '_Exc']
assert context['attributes'] == ['qux']
assert context['all_attributes'] == ['qux']
assert context['fullname'] == 'autosummary_dummy_module'
assert context['module'] == 'autosummary_dummy_module'
assert context['objname'] == ''
assert context['name'] == ''
assert context['objtype'] == 'module'
@pytest.mark.sphinx('dummy', testroot='ext-autosummary')
def test_autosummary_generate(app, status, warning):
app.builder.build_all()
@ -203,16 +283,18 @@ def test_autosummary_generate(app, status, warning):
[autosummary_table, nodes.table, nodes.tgroup, (nodes.colspec,
nodes.colspec,
[nodes.tbody, (nodes.row,
nodes.row,
nodes.row,
nodes.row,
nodes.row)])])
assert_node(doctree[4][0], addnodes.toctree, caption="An autosummary")
assert len(doctree[3][0][0][2]) == 4
assert len(doctree[3][0][0][2]) == 5
assert doctree[3][0][0][2][0].astext() == 'autosummary_dummy_module\n\n'
assert doctree[3][0][0][2][1].astext() == 'autosummary_dummy_module.Foo()\n\n'
assert doctree[3][0][0][2][2].astext() == 'autosummary_dummy_module.Foo.Bar\n\n'
assert doctree[3][0][0][2][2].astext() == 'autosummary_dummy_module.Foo.Bar()\n\n'
assert doctree[3][0][0][2][3].astext() == 'autosummary_dummy_module.bar(x[, y])\n\n'
assert doctree[3][0][0][2][4].astext() == 'autosummary_dummy_module.qux\n\na module-level attribute'
module = (app.srcdir / 'generated' / 'autosummary_dummy_module.rst').read_text()
assert (' .. autosummary::\n'
@ -237,6 +319,11 @@ def test_autosummary_generate(app, status, warning):
'\n'
'.. autoclass:: Foo.Bar\n' in FooBar)
qux = (app.srcdir / 'generated' / 'autosummary_dummy_module.qux.rst').read_text()
assert ('.. currentmodule:: autosummary_dummy_module\n'
'\n'
'.. autodata:: qux' in qux)
@pytest.mark.sphinx('dummy', testroot='ext-autosummary',
confoverrides={'autosummary_generate_overwrite': False})

View File

@ -71,7 +71,7 @@ def test_mathjax_options(app, status, warning):
content = (app.outdir / 'index.html').read_text()
assert ('<script async="async" integrity="sha384-0123456789" '
'src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/latest.js?'
'src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.7/latest.js?'
'config=TeX-AMS-MML_HTMLorMML"></script>' in content)

View File

@ -1394,6 +1394,26 @@ Summary
Attributes
----------
arg_ : type
some description
"""
expected = """
:ivar arg_: some description
:vartype arg_: type
"""
config = Config(napoleon_use_ivar=True)
app = mock.Mock()
actual = str(NumpyDocstring(docstring, config, app, "class"))
self.assertEqual(expected, actual)
def test_underscore_in_attribute_strip_signature_backslash(self):
docstring = """
Attributes
----------
arg_ : type
some description
"""
@ -1404,6 +1424,7 @@ arg_ : type
"""
config = Config(napoleon_use_ivar=True)
config.strip_signature_backslash = True
app = mock.Mock()
actual = str(NumpyDocstring(docstring, config, app, "class"))

View File

@ -58,8 +58,8 @@ def test_project_path2doc(app):
assert project.path2doc('index.foo') is None # unknown extension
assert project.path2doc('index.foo.rst') == 'index.foo'
assert project.path2doc('index') is None
assert project.path2doc('/path/to/index.rst') == PathComparer('/path/to/index')
assert project.path2doc(app.srcdir / '/to/index.rst') == PathComparer('/to/index')
assert project.path2doc('path/to/index.rst') == 'path/to/index'
assert project.path2doc(app.srcdir / 'to/index.rst') == 'to/index'
@pytest.mark.sphinx(srcdir='project_doc2path', testroot='basic')

View File

@ -74,7 +74,7 @@ def test_js_source(app, status, warning):
app.builder.build(['contents'])
v = '3.4.1'
v = '3.5.1'
msg = 'jquery.js version does not match to {v}'.format(v=v)
jquery_min = (app.outdir / '_static' / 'jquery.js').read_text()
assert 'jQuery v{v}'.format(v=v) in jquery_min, msg

View File

@ -29,12 +29,14 @@ def test_signature():
with pytest.raises(TypeError):
inspect.signature('')
# builitin classes
with pytest.raises(ValueError):
inspect.signature(int)
with pytest.raises(ValueError):
inspect.signature(str)
# builtins are supported on a case-by-case basis, depending on whether
# they define __text_signature__
if getattr(list, '__text_signature__', None):
sig = inspect.stringify_signature(inspect.signature(list))
assert sig == '(iterable=(), /)'
else:
with pytest.raises(ValueError):
inspect.signature(list)
# normal function
def func(a, b, c=1, d=2, *e, **f):
@ -333,10 +335,14 @@ def test_signature_from_str_kwonly_args():
@pytest.mark.skipif(sys.version_info < (3, 8),
reason='python-3.8 or above is required')
def test_signature_from_str_positionaly_only_args():
sig = inspect.signature_from_str('(a, /, b)')
assert list(sig.parameters.keys()) == ['a', 'b']
sig = inspect.signature_from_str('(a, b=0, /, c=1)')
assert list(sig.parameters.keys()) == ['a', 'b', 'c']
assert sig.parameters['a'].kind == Parameter.POSITIONAL_ONLY
assert sig.parameters['b'].kind == Parameter.POSITIONAL_OR_KEYWORD
assert sig.parameters['a'].default == Parameter.empty
assert sig.parameters['b'].kind == Parameter.POSITIONAL_ONLY
assert sig.parameters['b'].default == '0'
assert sig.parameters['c'].kind == Parameter.POSITIONAL_OR_KEYWORD
assert sig.parameters['c'].default == '1'
def test_signature_from_str_invalid():
@ -558,6 +564,18 @@ def test_isproperty(app):
assert inspect.isproperty(func) is False # function
@pytest.mark.skipif(sys.version_info < (3, 7), reason='python 3.7+ is required.')
@pytest.mark.sphinx(testroot='ext-autodoc')
def test_isgenericalias(app):
from target.genericalias import C, T
from target.methods import Base
assert inspect.isgenericalias(C) is True
assert inspect.isgenericalias(T) is True
assert inspect.isgenericalias(object()) is False
assert inspect.isgenericalias(Base) is False
def test_unpartial():
def func1(a, b, c):
pass