Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .ruff.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ extend-exclude = [
"tests/roots/test-pycode/cp_1251_coded.py", # Not UTF-8
]

[per-file-target-version]
"tests/roots/test-ext-autodoc/target/pep695.py" = "py312"

[format]
preview = true
quote-style = "single"
Expand Down
1 change: 1 addition & 0 deletions AUTHORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ Contributors
* Martin Larralde -- additional napoleon admonitions
* Martin Liška -- option directive and role improvements
* Martin Mahner -- nature theme
* Martin Matouš -- initial support for PEP 695
* Matthew Fernandez -- todo extension fix
* Matthew Woodcraft -- text output improvements
* Matthias Geier -- style improvements
Expand Down
2 changes: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ Features added
Patch by Adam Turner.
* #13805: LaTeX: add support for ``fontawesome7`` package.
Patch by Jean-François B.
* #13508: Initial support for :pep:`695` type aliases.
Patch by Martin Matouš, Jeremy Maitin-Shepard, and Adam Turner.

Bugs fixed
----------
Expand Down
32 changes: 32 additions & 0 deletions doc/usage/extensions/autodoc.rst
Original file line number Diff line number Diff line change
Expand Up @@ -966,6 +966,38 @@ Automatically document attributes or data
``:no-value:`` has no effect.


Automatically document type aliases
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. rst:directive:: autotype

.. versionadded:: 8.3

Document a :pep:`695` type alias (the :keyword:`type` statement).
By default, the directive only inserts the docstring of the alias itself:

The directive can also contain content of its own,
which will be inserted into the resulting non-auto directive source
after the docstring (but before any automatic member documentation).

Therefore, you can also mix automatic and non-automatic member documentation.

.. rubric:: Options

.. rst:directive:option:: no-index
:type:

Do not generate an index entry for the documented class
or any auto-documented members.

.. rst:directive:option:: no-index-entry
:type:

Do not generate an index entry for the documented class
or any auto-documented members.
Unlike ``:no-index:``, cross-references are still created.


Configuration
-------------

Expand Down
2 changes: 1 addition & 1 deletion sphinx/domains/python/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -742,7 +742,7 @@ class PythonDomain(Domain):
'staticmethod': ObjType(_('static method'), 'meth', 'obj'),
'attribute': ObjType(_('attribute'), 'attr', 'obj'),
'property': ObjType(_('property'), 'attr', '_prop', 'obj'),
'type': ObjType(_('type alias'), 'type', 'obj'),
'type': ObjType(_('type alias'), 'type', 'class', 'obj'),
'module': ObjType(_('module'), 'mod', 'obj'),
}

Expand Down
2 changes: 2 additions & 0 deletions sphinx/ext/autodoc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
ModuleDocumenter,
ModuleLevelDocumenter,
PropertyDocumenter,
TypeAliasDocumenter,
autodoc_attrgetter,
py_ext_sig_re,
)
Expand Down Expand Up @@ -114,6 +115,7 @@ def setup(app: Sphinx) -> ExtensionMetadata:
app.add_autodocumenter(MethodDocumenter)
app.add_autodocumenter(AttributeDocumenter)
app.add_autodocumenter(PropertyDocumenter)
app.add_autodocumenter(TypeAliasDocumenter)

app.add_config_value(
'autoclass_content',
Expand Down
24 changes: 23 additions & 1 deletion sphinx/ext/autodoc/_documenters.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
safe_getattr,
stringify_signature,
)
from sphinx.util.typing import restify, stringify_annotation
from sphinx.util.typing import AnyTypeAliasType, restify, stringify_annotation

if TYPE_CHECKING:
from collections.abc import Callable, Iterator, Sequence
Expand All @@ -58,6 +58,7 @@
_FunctionDefProperties,
_ItemProperties,
_ModuleProperties,
_TypeStatementProperties,
)
from sphinx.ext.autodoc.directive import DocumenterBridge
from sphinx.registry import SphinxComponentRegistry
Expand Down Expand Up @@ -1800,6 +1801,27 @@ def _get_property_getter(self) -> Callable[..., Any] | None:
return None


class TypeAliasDocumenter(Documenter):
"""Specialized Documenter subclass for type aliases."""

props: _TypeStatementProperties

objtype = 'type'
member_order = 70
option_spec: ClassVar[OptionSpec] = {
'no-index': bool_option,
'no-index-entry': bool_option,
'annotation': annotation_option,
'no-value': bool_option,
}

@classmethod
def can_document_member(
cls: type[Documenter], member: Any, membername: str, isattr: bool, parent: Any
) -> bool:
return isinstance(member, AnyTypeAliasType)


class DocstringSignatureMixin:
"""Retained for compatibility."""

Expand Down
10 changes: 10 additions & 0 deletions sphinx/ext/autodoc/_property_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
'property',
'attribute',
'data',
'type',
]
_AutodocFuncProperty: TypeAlias = Literal[
'abstractmethod',
Expand Down Expand Up @@ -189,3 +190,12 @@ class _AssignStatementProperties(_ItemProperties):
)
_obj_repr_rst: str
_obj_type_annotation: str | None


@dataclasses.dataclass(frozen=False, kw_only=True, slots=True)
class _TypeStatementProperties(_ItemProperties):
obj_type: Literal['type']

_obj___name__: str | None
_obj___qualname__: str | None
_obj___value__: str # The aliased annotation
7 changes: 7 additions & 0 deletions sphinx/ext/autodoc/_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
_AssignStatementProperties,
_ClassDefProperties,
_FunctionDefProperties,
_TypeStatementProperties,
)
from sphinx.ext.autodoc._sentinels import SUPPRESS
from sphinx.locale import _
Expand Down Expand Up @@ -166,6 +167,12 @@ def _directive_header_lines(
):
yield f' :value: {props._obj_repr_rst}'

if props.obj_type == 'type':
assert isinstance(props, _TypeStatementProperties)

if not options.no_value and not props._docstrings_has_hide_value:
yield f' :canonical: {props._obj___value__}'


def _add_content(content: StringList, *, result: StringList, indent: str) -> None:
for line, src in zip(content.data, content.items, strict=True):
Expand Down
31 changes: 30 additions & 1 deletion sphinx/ext/autodoc/importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
_FunctionDefProperties,
_ItemProperties,
_ModuleProperties,
_TypeStatementProperties,
)
from sphinx.ext.autodoc._sentinels import (
RUNTIME_INSTANCE_ATTRIBUTE,
Expand Down Expand Up @@ -774,6 +775,34 @@ def _load_object_by_name(
_obj_repr_rst=inspect.object_description(obj),
_obj_type_annotation=type_annotation,
)
elif objtype == 'type':
obj_module_name = getattr(obj, '__module__', module_name)
if obj_module_name != module_name and module_name.startswith(obj_module_name):
bases = module_name[len(obj_module_name) :].strip('.').split('.')
parts = tuple(bases) + parts
module_name = obj_module_name

if config.autodoc_typehints_format == 'short':
mode = 'smart'
else:
mode = 'fully-qualified-except-typing'
short_literals = config.python_display_short_literal_types
ann = stringify_annotation(
obj.__value__,
mode, # type: ignore[arg-type]
short_literals=short_literals,
)
props = _TypeStatementProperties(
obj_type=objtype,
module_name=module_name,
parts=parts,
docstring_lines=(),
_obj=obj,
_obj___module__=get_attr(obj, '__module__', None),
_obj___name__=getattr(obj, '__name__', None),
_obj___qualname__=getattr(obj, '__qualname__', None),
_obj___value__=ann,
)
else:
props = _ItemProperties(
obj_type=objtype,
Expand Down Expand Up @@ -912,7 +941,7 @@ def _resolve_name(
)
return (path or '') + base, ()

if objtype in {'class', 'exception', 'function', 'decorator', 'data'}:
if objtype in {'class', 'exception', 'function', 'decorator', 'data', 'type'}:
if module_name is not None:
return module_name, (*parents, base)
if path:
Expand Down
4 changes: 3 additions & 1 deletion sphinx/ext/autosummary/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ class AutosummaryEntry(NamedTuple):


def setup_documenters(app: Sphinx) -> None:
from sphinx.ext.autodoc import (
from sphinx.ext.autodoc import ( # type: ignore[attr-defined]
AttributeDocumenter,
ClassDocumenter,
DataDocumenter,
Expand All @@ -117,6 +117,7 @@ def setup_documenters(app: Sphinx) -> None:
MethodDocumenter,
ModuleDocumenter,
PropertyDocumenter,
TypeAliasDocumenter,
)

documenters: list[type[Documenter]] = [
Expand All @@ -129,6 +130,7 @@ def setup_documenters(app: Sphinx) -> None:
AttributeDocumenter,
DecoratorDocumenter,
PropertyDocumenter,
TypeAliasDocumenter,
]
for documenter in documenters:
app.registry.add_documenter(documenter.objtype, documenter)
Expand Down
Loading
Loading