Profile
Back to NewsBack
GitHub Trending 31 min
Reader Mode
tox-dev/sphinx-autodoc-typehints: Type hints support for the Sphinx autodoc extension

tox-dev/sphinx-autodoc-typehints: Type hints support for the Sphinx autodoc extension

13 hours ago

sphinx-autodoc-typehints

PyPI</a> Supported Python versions</a> Downloads</a> check</a>

This Sphinx extension reads your Python type hints and automatically adds type information to your generated documentation -- so you write types once in code and they appear in your docs without duplication.

Features:

  • Adds parameter and return types from annotations into docstrings
  • Resolves types from TYPE_CHECKING blocks and
.pyi stub files
  • Renders @overload signatures in docstrings
  • Extracts types from attrs and
dataclass classes
  • Shows default parameter values alongside types
  • Types the keys of **kwargs: Unpack[SomeTypedDict] as keyword arguments
  • Shows the type parameters of generic classes, functions and type aliases in their signatures
  • Shows the message of @deprecated objects as a deprecation notice
  • Links the base class member an @override method replaces
  • Shows the type a property's setter accepts when it differs from the type the getter returns
  • Controls union display style (Union[X, Y] vs X | Y)
  • Automatically fixes cross-references for stdlib types whose runtime module differs from their documented path
  • Supports custom type formatters and module name rewriting
  • Extracts descriptions from [Annotated[T, Doc(...)]](https://typing-extensions.readthedocs.io/en/latest/#Doc)
metadata and with numpydoc
  • Reads PEP 484 type comments when a function has no annotations
  • Types instance attributes annotated in __init__ for their :ivar: fields
Sphinx has a built-in autodoc_typehints setting (since v2.1) that can move type hints between signatures and descriptions. This extension replaces that with the features above. See Avoid duplicate types with built-in Sphinx.

- Avoid duplicate types with built-in Sphinx - Use with Google or NumPy docstring style - Use with numpydoc - Control return type display - Change how union types look - Show default parameter values - Document **kwargs typed with a TypedDict - Document TypedDict classes - Control overload signature display - Show deprecations from @deprecated - Link what an @override method overrides - Show the type a property setter accepts - Keep type hints in function signatures - Handle circular imports - Resolve types from TYPE_CHECKING blocks - Show types for attrs or dataclass fields - Type attributes documented with :ivar: or :cvar: - Write a custom type formatter - Document a type alias without expanding it - Add types for C extensions or packages without annotations - Use type comments - Document generators - List parameters once with autoclass_content = "both" - Style the injected types - Fix cross-reference links for stdlib types - Fix cross-reference links for renamed modules - Suppress warnings - Configuration options - Typing constructs - Warning categories - How it works - How return type options interact

Installation

pip install sphinx-autodoc-typehints

Then add the extension to your conf.py:

extensions = ["sphinx.ext.autodoc", "sphinx_autodoc_typehints"]

It needs Python 3.12 or later and Sphinx 9.1 or later, and it is safe for parallel builds (sphinx-build -j).

Quick start

Instead of writing types in your docstrings, write them as Python type hints. The extension picks them up and adds them to your Sphinx output:

# Before: types repeated in docstrings
def format_unit(value, unit):
    """
    Format a value with its unit.

:param float value: a numeric value :param str unit: the unit (kg, m, etc.) :rtype: str """ return f"{value} {unit}"

After: types only in annotations, docs generated automatically

def format_unit(value: float, unit: str) -> str: """ Format a value with its unit.

:param value: a numeric value :param unit: the unit (kg, m, etc.) """ return f"{value} {unit}"

The extension adds the type information to your docs during the Sphinx build. See an example at the pyproject-api docs.

How-to guides

Avoid duplicate types with built-in Sphinx

With Sphinx's own autodoc_typehints set to "description" or "both", Sphinx adds the parameters a docstring leaves out with its own plain, unlinked types (y (str)) next to the ones this extension writes. Set it to "none" to let this extension handle everything:

autodoc_typehints = "none"

Use with Google or NumPy docstring style

If you use sphinx.ext.napoleon for Google-style or NumPy-style docstrings, load it before this extension:

extensions = [
    "sphinx.ext.autodoc",
    "sphinx.ext.napoleon",
    "sphinx_autodoc_typehints",
]

Napoleon and this extension share one return type by default: napoleon's :rtype: gives way to the annotation. With napoleon_use_rtype = False, napoleon puts the docstring's type in the Returns text instead, so set typehints_use_rtype = False too, or the type shows twice:

napoleon_use_rtype = False  # sphinx.ext.napoleon setting
typehints_use_rtype = False

A type written in the docstring's Returns section then stays as written, in place of the annotation's.

See napoleon_use_rtype in the Sphinx docs.

With napoleon_use_param or napoleon_use_keyword set to False, napoleon lists the parameters, or the keyword arguments, in one field each, and the types go in parentheses after each name, where napoleon puts a type written in the docstring. A type written in the docstring is kept. With numpydoc, an Other Parameters section renders this way too, under its own heading.

The extension supplies the types napoleon would otherwise read from the annotations for parameters and for the attributes in an Attributes section: a class's, annotated in the class body or where __init__ assigns them, and a module's, annotated at module level, whether napoleon writes the section as :ivar: fields or, with napoleon_use_ivar off, as .. attribute:: entries. These render like any other field, linked and with their arguments, while a type written in the docstring, as in count (int): ..., stays as written. A keyword argument napoleon writes as a :keyword: field gets a :paramtype: next to it, and a :kwtype: napoleon wrote from the docstring counts as an existing type.

Use with numpydoc

The numpydoc extension works too; load it before this extension:

extensions = [
    "sphinx.ext.autodoc",
    "numpydoc",
    "sphinx_autodoc_typehints",
]

The Parameters, Other Parameters, Returns, Yields and Raises sections numpydoc produces are turned into field lists first, so the types from the annotations go in as they do for Sphinx-style docstrings. An entry naming several parameters, a, b : int, is split into one per name, and a type written in the docstring is kept for a parameter without an annotation.

Control return type display

By default, return types appear as a separate block in your docs. You can change this:

# Don't show return types at all
typehints_document_rtype = False

Don't show "None" return types, but show all others

typehints_document_rtype_none = False

Show the return type inline with the return description

instead of as a separate block

typehints_use_rtype = False

Change how union types look

A union written with the pipe syntax, str | int, renders that way. Before Python 3.14, a pipe next to a typing construct, as in Literal["a"] | None or Self | None, builds the same object Optional[...] does, so it renders like one unless always_use_bars_union is set. One written as Union[str, int] or Optional[str] renders as written by default; to show those with pipes too:

always_use_bars_union = True

On Python 3.14+, the pipe syntax is always used regardless of this setting.

By default, Optional[Union[A, B]] is simplified to Union[A, B, None]. To keep the Optional wrapper:

simplify_optional_unions = False

With this set to False, a Union[...] or Optional[...] containing None displays as Optional. A union written with pipes, such as int | str | None, keeps them. The option only matters before Python 3.14, where every union shows with pipes.

Show default parameter values

To include default values in your docs, set typehints_defaults to one of three styles:

# "param (int, default: 1) -- description"
typehints_defaults = "comma"

"param (int (default: 1)) -- description"

typehints_defaults = "braces"

"param (int) -- description (default: 1)" (at end of text)

typehints_defaults = "braces-after"

None and False leave defaults out, and any other value stops the build with a configuration error. A parameter typed only in its docstring, by a :type: field, as :param int x: or in napoleon's combined Parameters field, gets the default next to that type the same way. With "braces-after", the default closes the last plain paragraph of the description. A list, a directive such as .. note::, or a literal block at the end of the description stays as written, and a paragraph introducing a literal block keeps its :: last.

A dataclass field declared with field(default_factory=list), or an attrs one with attrs.field(factory=list), gets a fresh value on each call, and its default reads as that call, list(), in both the signature and the parameter list. An attrs factory taking the instance reads as make(self). A lambda, or any other factory without a name to look up, keeps the placeholder, since its source is not reliably recoverable from a generated __init__.

Document **kwargs typed with a TypedDict

PEP 692 types **kwargs key by key through a TypedDict. The extension types each key documented as a keyword argument, the way it types a parameter:

class Options(TypedDict, total=False):
    verbose: Required[bool]
    level: int

def configure(**kwargs: Unpack[Options]) -> None: """ Configure the run.

:param kwargs: the options :keyword verbose: print every step :keyword level: how much to print """

renders verbose as bool and level as NotRequired[int], since a caller may leave it out, and a ReadOnly key keeps its ReadOnly[...]. A key described through Annotated[..., Doc(...)], or every key with always_document_param_types = True, gets a keyword entry of its own.

Document TypedDict classes

A TypedDict documented with :members: and :undoc-members: lists its keys as attributes. A key that may be left out shows as NotRequired[...], also when the class's total=False makes it so rather than its annotation, and one a caller must pass shows its type alone, also when written as Required[...]. A PEP 705 read-only key keeps its ReadOnly[...], inside the NotRequired[...] as the typing spec nests them, so rating: ReadOnly[NotRequired[float]] shows NotRequired[ReadOnly[float]]. The items a PEP 728 TypedDict accepts beyond its keys show as an Extra items field:

class Counts(TypedDict, extra_items=int):
    total: int

shows Extra items: int, and a closed=True class, or one inheriting from it, shows Extra items: Never, its equivalent. A docstring with its own :Extra items: field keeps it.

Control overload signature display

When a function has @overload signatures, they are rendered automatically in the docstring, with their types cross-referenced like parameter types and without the self or cls parameter of a method. Each reads like the overload's signature, with its / and * markers, the stars of args and *kwargs, and its defaults as written, and an overload taking nothing shows as (). A class lists the overloads of its __init__ or __new__. To disable this globally:

typehints_document_overloads = False

To disable overloads for a single function while keeping them everywhere else, add :no-overloads: to the docstring:

@overload
def f(x: int) -> str: ...
@overload
def f(x: str) -> bool: ...
def f(x):
    """:no-overloads:

f accepts int or str, see docs for details. """

The :no-overloads: directive is stripped from the rendered output, also when typehints_document_overloads is off.

The overloads go right after the summary paragraph, so autosummary still picks the summary up. A function documented through its overloads gets no separate return type, since each overload states its own. An exception class lists its constructor overloads like any other class, and a static method keeps its first parameter.

With typehints_use_signature = True the overloads replace the signature instead, one line each, as plain autodoc shows them, and the Overloads field goes away:

mod.f(x: int) -> str
mod.f(x: str) -> bool

Every line shows its return type, as that is what tells the overloads apart, except for a class, whose lines list its constructor's parameters. A default an overload writes as ... stays ..., here and in the Overloads field: it says the parameter is optional, while the implementation's default may be one the overload's type rules out, as True for flag: Literal[False] = .... Plain autodoc shows the implementation's default instead. Only the first line is the link target of the object. A signature written in the directive, .. autofunction:: f(x), shows as written, with the overloads in the Overloads field, while :no-overloads: keeps the signature of the implementation.

Show deprecations from @deprecated

A function, method or class decorated with @warnings.deprecated (or the typing_extensions backport) gets a deprecation notice below its summary, styled like Sphinx's deprecated directive:

@deprecated("Use fresh() instead.")
def stale(value: int) -> int:
    """Do the stale thing."""

renders Deprecated: Use fresh() instead. The message shows as written, without markup, as it is the text the object warns with at runtime. A docstring with a .. deprecated:: directive of its own, which can name the version, gets no second notice, and a subclass of a deprecated class is not marked.

Link what an @override method overrides

A method or property marked with @typing.override gets an Overrides field that links the member it replaces, found in the nearest base class that defines it:

class Child(Base):
    @override
    def run(self, count: int) -> str:
        """Run again."""

shows Overrides: Base.run. A method marked @override that no base class defines gets no field. Listed under a subclass with :inherited-members:, the method still links what it overrides in its own class's bases, and the link goes through typehints_fixup_module_name like any other.

Show the type a property setter accepts

Autodoc puts the type a property's getter returns in its header, and nothing tells the reader what they may assign. When the setter's value parameter has a different annotation, the property gets a Setter accepts field with it:

class Box:
    @property
    def size(self) -> int:
        """The size."""

@size.setter def size(self, new: int | str) -> None: ...

shows property size: int followed by Setter accepts: int | str. A property without a setter, such as a functools.cached_property, a setter annotated with the getter's type, or one without an annotation gets no field, and a docstring with a :Setter accepts: field of its own keeps it.

Keep type hints in function signatures

By default, type hints are removed from function signatures and shown in the parameter list below. To keep them visible in the signature line:

typehints_use_signature = True  # show parameter types in signature
typehints_use_signature_return = True  # show return type in signature

The kept types read the same as the fields below them: typehints_formatter, typehints_fixup_module_name, typehints_fully_qualified and documented type aliases apply to both. They are written as Python writes them, so a NewType shows by its name, UserId rather than the field's UserId (int), a type variable no signature declares shows as ~module.T, and the markup a formatter returns becomes the name it refers to, so ` :class:bool turns into bool, which Sphinx then links as it links any annotation.

A function with @overload signatures shows those in its signature, one line each, in place of the implementation's, see Control overload signature display.

Handle circular imports

When two modules need to reference each other's types, you'll get circular import errors. Fix this by using from __future__ import annotations, which makes all type hints strings that are resolved later:

from __future__ import annotations

import othermodule

def process(item: othermodule.OtherClass) -> None: ...

Resolve types from TYPE_CHECKING blocks

This extension automatically imports types from TYPE_CHECKING blocks at doc-build time. Any module-level guard counts, whether it names TYPE_CHECKING directly or through a module alias such as t.TYPE_CHECKING, including the else branch of if not TYPE_CHECKING: and tests that combine it with and or or. Annotations read the names as a type checker does, so with else: ArrayLike = Any they document the guarded ArrayLike. The module itself keeps what it binds at runtime: the fallback, or the function behind @overload stubs, stays for your code and autodoc during the build, and the block only adds the names the module lacks. When the block imports a dependency your docs environment lacks, the extension mocks it the way autodoc_mock_imports would, so its types still render as cross-references, in a pipe union such as Thing | None too; install it when you want those references to resolve. That includes a relative import of a sibling module that is absent. An import statement raises a warning when it fails for another reason, such as a dependency you have installed but that fails to import. A guarded statement that only a type checker accepts, and that imports nothing, is skipped silently, and the names it would define still work in annotations. Fix the environment, or suppress the warning:

suppress_warnings = ["sphinx_autodoc_typehints.guarded_import"]

Show types for attrs or dataclass fields

The extension backfills annotations from attrs field metadata automatically. For dataclasses, annotations are read from the class body. Make sure the class is documented with .. autoclass:: and both :members: and :undoc-members:, since a field without a docstring is otherwise skipped.

Type attributes documented with :ivar: or :cvar:

An attribute annotated in the class body, or where __init__ assigns it, gets a :vartype: for its :ivar:, :cvar: or :var: field:

class Job:
    """
    A job.

:ivar retries: how often it ran again """

def __init__(self) -> None: self.retries: int = 0

renders retries as int, as does retries: int in the class body, the way a __slots__ class declares it. A ClassVar[...] or Final[...] shows the type it holds, and where both declare an attribute the __init__ annotation wins. Assignments inside if, try and with blocks count, ones in nested functions do not, and an :ivar int retries: or an existing :vartype retries: stays as written.

Write a custom type formatter

To control exactly how a type appears in your docs, provide a formatter function. It receives the type annotation and the Sphinx config, and returns RST markup (or None to use the default rendering):

def my_formatter(annotation, config):
    if annotation is bool:
        return ":class:bool"
    return None

typehints_formatter = my_formatter

With typehints_use_signature or typehints_use_signature_return, the formatter also decides the types kept in the signature, where its markup becomes the name it refers to. A value that is not callable stops the build with a configuration error.

To always show the full module path for types (e.g., collections.OrderedDict instead of OrderedDict):

typehints_fully_qualified = True

It follows Sphinx's autodoc_typehints_format unless set, so autodoc_typehints_format = "fully-qualified" turns it on.

Document a type alias without expanding it

The extension preserves alias names only when they have a .. py:type:: directive in your docs. Without that entry, the alias is expanded to its underlying type. Add a documentation entry for the alias, and it will render as a clickable link instead. The entry has to name the alias where the annotation looks it up: .. py:type:: pkg.Vector serves the functions and methods of pkg, while an alias documented in a submodule such as pkg.compat does not.

A plain assignment such as Count = int evaluates to the very type it names, so without from __future__ import annotations the extension reads the function's source to tell limit: Count from limit: int. Only the parameters written with the alias name render as the alias, and functions without source, such as generated ones, show the underlying type. The extension picks between the alias and its underlying type once every document is read, so the .. py:type:: entry may come after the function, on its page or in another document, and -j does not change the result. A signature kept by typehints_use_signature is written while documents are still being read, so it names the alias only when its entry was read first.

An alias set in autodoc_type_aliases renders by its name. It links to the target it names when the docs document that target, as a type, class or data entry; otherwise the name shows as plain text, so the value may also be a display string. The extension decides once every document is read, so the result does not depend on the order documents are read in or on -j.

A recursive alias such as type Json = dict[str, Json] | list[Json] | str renders its self-reference as a link to the alias name. A generic alias used with arguments, Pairs[int], expands with the arguments put in, or shows by name when the arguments do not fit its parameters.

Add types for C extensions or packages without annotations

The extension reads .pyi stub files automatically. Place a .pyi file next to the .so/.pyd file (or as __init__.pyi in the package directory) with the type annotations, and they'll be picked up. Beyond that:

  • A function a package re-exports from a native submodule, such as pkg._native.parse exposed as pkg.parse, takes its
types from the package's __init__.pyi, and a native submodule without a __file__, as PyO3 builds them, finds its stub too.
  • The @overload variants of a stub combine into a union, unless the stub also gives the implementation's signature.
  • if sys.version_info and if sys.platform branches are followed for the running interpreter.
  • A name that exists only in the stub, including a stub TypeAlias or type alias, renders as a cross-reference.
  • A module the stub imports that isn't installed, import numpy.typing as npt in a docs environment without numpy, is
mocked as autodoc_mock_imports would, so npt.NDArray still renders as a cross-reference.
  • A C attribute (a getset or member descriptor) gets its type from the stub's @property or class annotation.
  • Editing a stub rereads the pages documenting what it describes on the next incremental build.
  • Looking for a stub reads a package's attributes without running its PEP 562
__getattr__, so a package that imports on first access, or renames what it hands out as anyio does, stays as autodoc found it.

Use type comments

A function without annotations can carry its types in a PEP 484 type comment, which the extension reads:

def scale(value, factor):
    # type: (float, int) -> float
    """
    Scale a value.

:param value: the value :param factor: the factor """

The per-argument form, a # type: comment after each parameter, works too, and self or cls may be left out of the comment. A comment that does not parse raises a sphinx_autodoc_typehints.comment warning.

Document generators

A function annotated to return a Generator, Iterator, AsyncGenerator or AsyncIterator whose docstring has a Yields section (:Yields:, :yields: or :yield:, as napoleon and numpydoc write it) gets no return type, since the Yields section already describes what it produces. Its entry gets the yielded type instead, Yields: int -- the numbers for Iterator[int], unless it states one. Wrapping the annotation in Annotated changes none of this.

A generator that also hands back a value with return, Generator[int, None, bool], and documents it in a Returns section gets that value's type, bool, as its return type; with napoleon, as here:

def count() -> Generator[int, None, bool]:
    """
    Yields:
        the numbers

Returns: whether it finished """

List parameters once with autoclass_content = "both"

With autoclass_content = "both", or the :class-doc-from: both option of an autoclass directive, which overrides it for that class, Sphinx shows the class and the constructor docstrings together. A parameter either of them documents gets its type there, and always_document_param_types adds the remaining ones only once, under the constructor docstring. So do a Doc() description, the keys of **kwargs: Unpack[...] and the constructor's overloads, while a @deprecated notice heads the class docstring.

Style the injected types

Every type the extension adds is wrapped in an inline element with the CSS class sphinx_autodoc_typehints-type, which a theme or a custom stylesheet can target.

Fix cross-reference links for stdlib types

Many Python stdlib classes have a __module__ that points to an internal module rather than their public documented path. For example, threading.local reports its module as _thread._local, and concurrent.futures.Executor as concurrent.futures._base.Executor. This produces broken cross-reference links because the extension builds links from __module__.__qualname__.

When intersphinx is enabled, the extension automatically fixes this. After intersphinx loads its inventories, it builds a reverse mapping from runtime paths to documented paths and applies it during annotation formatting. No configuration is needed — just make sure intersphinx is loaded:

extensions = [
    "sphinx.ext.autodoc",
    "sphinx.ext.intersphinx",
    "sphinx_autodoc_typehints",
]

intersphinx_mapping = { "python": ("https://docs.python.org/3", None), }

With this, a type hint like threading.local correctly links to threading.local in the Python docs instead of producing a broken _thread._local reference.

Fix cross-reference links for renamed modules

Some third-party libraries expose types under a different module path than where they're documented. For example, GTK types live at gi.repository.Gtk.Window in Python, but their docs list them as Gtk.Window. This causes broken intersphinx links.

Use typehints_fixup_module_name to rewrite the module path before links are generated:

def fixup_module_name(module: str) -> str:
    if module.startswith("gi.repository."):
        return module.removeprefix("gi.repository.")
    return module

typehints_fixup_module_name = fixup_module_name

Editing the function re-reads the documents on the next incremental build, as changing any other option does. The extension tells by comparing the function's source, so a change only to a helper it calls or to data it reads goes unseen; run sphinx-build -E after such a change. A functools.partial or a callable instance has no source to compare, so every build re-reads every document; assign a plain function to keep incremental builds incremental.

Suppress warnings

To silence all warnings from this extension:

suppress_warnings = ["sphinx_autodoc_typehints"]

To suppress only specific warning types, see Warning categories for the full list.

Reference

Configuration options

| Option | Default | Description | | -------------------------------- | ---------------------------------- | --------------------------------------------------------------------------------------------------------- | | typehints_document_rtype | True | Show the return type in docs. | | typehints_document_rtype_none | True | Show return type when it's None. | | typehints_document_overloads | True | Show @overload signatures in docs. Use :no-overloads: in a docstring for per-function control. | | typehints_use_rtype | True | Show return type as a separate block. When False, it's inlined with the return description. | | always_use_bars_union | False | Show Union[X, Y] and Optional[X] with bars too; X \| Y always has them. Always on for Python 3.14+. | | simplify_optional_unions | True | Flatten Optional[Union[A, B]] to Union[A, B, None]. | | typehints_defaults | None | Show default values: "comma", "braces", or "braces-after". | | typehints_use_signature | False | Keep parameter types in the function signature. | | typehints_use_signature_return | False | Keep the return type in the function signature. | | typehints_fully_qualified | follows autodoc_typehints_format | Show full module path for types (e.g., module.Class not Class), in signatures too. | | always_document_param_types | False | Add types even for parameters that don't have a :param: entry in the docstring. | | typehints_formatter | None | A function (annotation, Config) -> str \| None for custom type rendering. | | typehints_fixup_module_name | None | A function (str) -> str to rewrite module paths before generating cross-reference links. |

Typing constructs

Some annotations render differently from how they are written:

| Written | Rendered | | -------------------------------------------------------- | -------------------------------------------------------------------------------------- | | args: Ts, tuple[*Ts] | Unpack[TypeVarTuple(Ts)], tuple[Unpack[TypeVarTuple(Ts)]] | | args: tuple[int, ...] | Unpack[tuple[int, ...]] | | def fT: int, class Box(Generic[T]) | T, with the bound in the signature | | T = TypeVar("T", bound=int) in a non-generic signature | TypeVar(T, bound=int) | | P.args, P.kwargs for P = ParamSpec("P") | ParamSpecArgs(P), ParamSpecKwargs(P), as written when the signature declares **P | | UserId = NewType("UserId", int) | UserId (int) | | Annotated[int, ...] | int, with a Doc(...) in the metadata becoming the description | | Literal[Color.RED] | Literal[Color.RED], linking the enum member | | tuple[()] | tuple[()], the empty tuple | | typing_extensions.Self | Self, linking to typing.Self where typing has it | | MISSING = sentinel("MISSING") (PEP 661) | MISSING, linking it as module data |

With Sphinx's python_display_short_literal_types on, Literal["a", "b"] renders as 'a' | 'b'.

The table covers the types the extension writes into fields and signatures, and into the header of data, an attribute or a property autodoc documents, so count: Annotated[int, ...] shows as count: int there too. A header written by hand, such as .. py:data:: mod.x with a :type: option, keeps the type as written.

Warning categories

All warnings can be suppressed via Sphinx's suppress_warnings in conf.py:

| Category | When it's raised | | --------------------------------------------- | --------------------------------------------------------------------------------------------------------- | | sphinx_autodoc_typehints | Catch-all for every warning from this extension. | | sphinx_autodoc_typehints.comment | A type comment (# type: ...) couldn't be parsed, or lists fewer types than the function has parameters. | | sphinx_autodoc_typehints.forward_reference | An annotation names something undefined or isn't valid Python, on 3.14 unevaluated too; shown as written. | | sphinx_autodoc_typehints.guarded_import | An import in a TYPE_CHECKING block failed for a reason other than an absent dependency. | | sphinx_autodoc_typehints.local_function | A method of a class defined inside a function can't be processed; wrap wrappers with functools.wraps. | | sphinx_autodoc_typehints.multiple_ast_nodes | The source of an unannotated function didn't parse to one definition, so its type comment can't be read. |

Explanation

How it works

During the Sphinx build, this extension hooks into two autodoc events. First, it strips type annotations from function signatures (so they don't appear twice), unless typehints_use_signature or typehints_use_signature_return keeps them. Then, it reads the annotations and adds type information into the docstring -- parameter types go next to each :param: entry, and the return type becomes an :rtype: entry.

A signature you write yourself is left as written when it names other parameters than the object takes: one in the directive, .. autofunction:: mod.f(x, y=3), or on the docstring's first line with autodoc_docstring_signature. One with the same parameters, differing only in defaults or types, is replaced by the object's own, and so is one made of only args and *kwargs, which is what autodoc falls back to when it can't see a class's parameters. A class takes the parameters autodoc reads for it: those of its metaclass's __call__ ahead of __new__ and __init__, unless that __call__ only passes args and *kwargs on.

Autodoc writes the type after the name of data, an attribute or a property itself, evaluating the annotation before any event runs, so a name imported under TYPE_CHECKING resolved there only when documenting something else had imported it first. The extension renders that type again once the header is built, the way it renders a field's type, with the type aliases, typehints_fully_qualified and the TYPE_CHECKING imports applied. The header therefore reads like the field, not like plain autodoc: RATE: decimal.Decimal shows as RATE: Decimal unless typehints_fully_qualified is on, and a class from a module in autodoc_mock_imports shows by its name, as it does in a field.

A class whose subscription hands back the class itself, as SQLAlchemy's TypeEngine does, evaluates TypeEngine[_T] to a bare TypeEngine. The extension evaluates such an annotation again from its source, keeping the arguments, so the header, the signature, the parameter, return and :ivar: fields and the overloads all show TypeEngine[_T], with TypeEngine still linked. The __init__ a dataclass or attrs class generates has no source of its own, so its parameters take the annotations its fields write. A name the branches of an if annotate differently keeps the bare class, as the source does not tell which branch ran.

Autodoc leaves the type parameters of a generic object out of its signature. The extension adds them after the name, with bounds, constraints and defaults, the way PEP 695 writes them: def firstT: Sized, class Stack[T] and type Pairs[K] = dict[K, K]. A class made generic by subclassing Generic[T] shows its parameters the same way. A type parameter a signature declares this way renders by name where it is used, as items (T) or args (P.args), while a type variable no signature declares keeps its full TypeVar(T, bound=int) form. An overload declaring its own, def getS: str -> list[S], renders them by name in its entry of the Overloads field. A signature written by hand, such as .. py:function:: first(items), shows only the type parameters it writes.

A member inherited from a generic base and documented under a subclass that binds the base's type parameters, as :inherited-members: lists it or .. automethod:: mod.Child.get names it, shows the arguments the subclass gives them. With class Child(Base[int]), Base.get(self) -> T documents as returning int under Child, while Base.get keeps T. The binding follows the bases through every level, so class B(A[list[U]]) and class C(B[int]) turn A's T into list[int] under C, and it covers the parameters and return type of methods and of the constructor a class documents, the overloads of either, the header of an attribute or property, so value: T reads value: int under Child, and the :vartype: a class-body annotation gives an :ivar:. A TypedDict binds the same way: class IntOptions(Options[int]) shows the keys it inherits with int, as does **kwargs: Unpack[Options[int]]. A type parameter the subclass leaves unbound, class Child(Base), stays as it is, and one it passes on, class ChildU, renders by name as a parameter the subclass declares.

Only parameters that already have a :param: line in the docstring get type information added, with two exceptions: a parameter whose annotation carries a Doc(...) description gets a :param: line of its own, and the entries of napoleon's combined parameter field get their types in place. Set always_document_param_types = True to add types for all parameters, even undocumented ones; the added lines follow the signature's order, next to the documented parameters, and go into napoleon's combined Parameters or Keyword Arguments field when the docstring lists the others there.

Parameters are recognized under :param, :parameter, :arg, :argument, :keyword, :kwarg and :kwparam, naming args and kwargs bare or with escaped stars, args or \args. Only fields that start a line count: one indented into a literal block, a code block or a directive such as a note is left as written. A type written in the field, :param int x:, and a :type x: field give way to the annotation, and a parameter without one keeps its docstring type. A @typing.no_type_check function still gets its types, decorated functions are documented through __wrapped__, a class or function stating its signature in __signature__, as a pydantic model does, through that signature, a functools.partial or partialmethod through the function it calls with the bound arguments left out, a callable instance documented as a function or method through its __call__, a data or attribute entry holding a callable gets no parameter or return types since it documents a value, and an annotation that fails to evaluate shows as written, whatever its own code raises, with a warning only when it names something undefined or isn't valid Python.

How return type options interact

The return type options combine as follows:

  • Both defaults (typehints_document_rtype = True, typehints_use_rtype = True) -- return type appears as a
separate :rtype: block below the description.
  • Inline mode (typehints_document_rtype = True, typehints_use_rtype = False) -- return type is appended to the
:return: text. If there's no :return: entry, it falls back to a separate block. A :return: entry that already states its type, such as napoleon's int -- the value, is left as it is, while a description that only contains a --, such as the value -- or None when missing, still gets the type. Next to an :rtype: field, which the annotation replaces, the :return: text always counts as a description.
  • Disabled (typehints_document_rtype = False) -- no return type is added from the annotation, regardless of other
settings; an :rtype: written in the docstring stays as written.
  • Skip None (typehints_document_rtype_none = False) -- hides None return types specifically, other return types
still appear. Annotated[None, Doc(...)] loses its type too, but keeps its description.

Classes and constructors get no return type, and a property shows its type in its header instead. An :rtype: written in the docstring, over one line or several, gives way to the annotation. The separate block goes after the parameter fields, before the first directive or other block that follows them, or at the end. A return annotation carrying a Doc(...) description adds a :return:` entry when the docstring has none.

Chat with me