Skip to content

Commit

Permalink
Add support for Python type parameter lists
Browse files Browse the repository at this point in the history
Sphinx has partial support for type parameter lists: they are supported
by the Python domain in signatures, but are not supported by autodoc.

This adds the following support:

- Sphinx Python domain for type parameter fields in docstrings, with
  sphinx.ext.napoleon support as well.

- Support for type parameters as Sphinx objects, with cross-linking, like
  the existing support for function parameters as Sphinx objects.

- Support in apigen for PEP 695 type parameters, and for displaying
  pre-PEP 695 separately-defined TypeVar types as PEP 695 type parameters.

Co-authored-by: Brendan <2bndy5@gmail.com>
  • Loading branch information
jbms and 2bndy5 committed Jul 21, 2024
1 parent 9d011ed commit 640b5c7
Show file tree
Hide file tree
Showing 14 changed files with 920 additions and 87 deletions.
5 changes: 4 additions & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,10 @@ def get_colors(color_t: str):
.. highlight:: json
"""

python_apigen_modules = {"tensorstore_demo": "python_apigen_generated/"}
python_apigen_modules = {
"tensorstore_demo": "python_apigen_generated/",
"type_param_demo": "python_apigen_generated/",
}

python_apigen_default_groups = [
("class:.*", "Classes"),
Expand Down
5 changes: 5 additions & 0 deletions docs/python_apigen_demo.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,8 @@ Some other group
----------------

.. python-apigen-group:: some-other-group

Type parameter demo
-------------------

.. python-apigen-group:: type-param
8 changes: 4 additions & 4 deletions docs/tensorstore_demo/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -735,8 +735,8 @@ def __getitem__(self, *args, **kwargs) -> None:
})
More generally, specifying an ``n``-dimensional `bool` array is equivalent to
specifying ``n`` 1-dimensional index arrays, where the ``i``\ th index array specifies
the ``i``\ th coordinate of the `True` values:
specifying ``n`` 1-dimensional index arrays, where the ``i``\\ th index array specifies
the ``i``\\ th coordinate of the `True` values:
>>> x = ts.array([[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12]]],
... dtype=ts.int32)
Expand Down Expand Up @@ -1049,8 +1049,8 @@ def __getitem__(self, *args, **kwargs) -> None:
:python:`dims[i] = self.labels.index(other.labels[i])`. It is an
error if no such dimension exists.
2. Otherwise, ``i`` is the ``j``\ th unlabeled dimension of :python:`other`
(left to right), and :python:`dims[i] = k`, where ``k`` is the ``j``\ th
2. Otherwise, ``i`` is the ``j``\\ th unlabeled dimension of :python:`other`
(left to right), and :python:`dims[i] = k`, where ``k`` is the ``j``\\ th
unlabeled dimension of :python:`self` (left to right). It is an error
if no such dimension exists.
Expand Down
122 changes: 122 additions & 0 deletions docs/type_param_demo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import collections.abc
from typing import (
TypeVar,
Generic,
overload,
Iterable,
Optional,
KeysView,
ValuesView,
ItemsView,
Iterator,
Union,
)

K = TypeVar("K")
V = TypeVar("V")
T = TypeVar("T")
U = TypeVar("U")


class Map(Generic[K, V]):
"""Maps keys of type :py:param:`.K` to values of type :py:param:`.V`.
Type parameters:
K:
Key type.
V:
Mapped value type.
Group:
type-param
"""

@overload
def __init__(self): ...

@overload
def __init__(self, items: collections.abc.Mapping[K, V]): ...

@overload
def __init__(self, items: Iterable[tuple[K, V]]): ...

def __init__(self, items):
"""Construct from the specified items."""
...

def clear(self):
"""Clear the map."""
...

def keys(self) -> KeysView[K]:
"""Return a dynamic view of the keys."""
...

def items(self) -> ItemsView[K, V]:
"""Return a dynamic view of the items."""
...

def values(self) -> ValuesView[V]:
"""Return a dynamic view of the values."""
...

@overload
def get(self, key: K) -> Optional[V]: ...

@overload
def get(self, key: K, default: V) -> V: ...

@overload
def get(self, key: K, default: T) -> Union[V, T]: ...

def get(self, key: K, default=None):
"""Return the mapped value, or the specified default."""
...

def __len__(self) -> int:
"""Return the number of items in the map."""
...

def __contains__(self, key: K) -> bool:
"""Check if the map contains :py:param:`.key`."""
...

def __getitem__(self, key: K) -> V:
"""Return the value associated with :py:param:`.key`.
Raises:
KeyError: if :py:param:`.key` is not present.
"""
...

def __setitem__(self, key: K, value: V):
"""Set the value associated with the specified key."""
...

def __delitem__(self, key: K):
"""Remove the value associated with the specified key.
Raises:
KeyError: if :py:param:`.key` is not present.
"""
...

def __iter__(self) -> Iterator[K]:
"""Iterate over the keys."""
...


class Derived(Map[int, U], Generic[U]):
"""Map from integer keys to arbitrary values.
Type parameters:
U: Mapped value type.
Group:
type-param
"""

pass


__all__ = ["Map", "Derived"]
5 changes: 4 additions & 1 deletion sphinx_immaterial/apidoc/format_signatures.py
Original file line number Diff line number Diff line change
Expand Up @@ -405,7 +405,10 @@ def _format_with_black(
break
if found_name:
break
if isinstance(orig_child, sphinx.addnodes.desc_type_parameter_list):
if (
isinstance(orig_child, docutils.nodes.Element)
and orig_child.get("sphinx_immaterial_parent_type_parameter_list") is True
):
# This is the parent entity type parameter list added by apigen.
parent_type_param_i = i
break
Expand Down
17 changes: 16 additions & 1 deletion sphinx_immaterial/apidoc/object_description_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,22 @@ def format_object_description_tooltip(
("py:property", {"toc_icon_class": "alias", "toc_icon_text": "P"}),
("py:attribute", {"toc_icon_class": "alias", "toc_icon_text": "A"}),
("py:data", {"toc_icon_class": "alias", "toc_icon_text": "V"}),
("py:parameter", {"toc_icon_class": "sub-data", "toc_icon_text": "p"}),
(
"py:parameter",
{
"toc_icon_class": "sub-data",
"toc_icon_text": "p",
"generate_synopses": "first_sentence",
},
),
(
"py:typeParameter",
{
"toc_icon_class": "alias",
"toc_icon_text": "T",
"generate_synopses": "first_sentence",
},
),
("c:member", {"toc_icon_class": "alias", "toc_icon_text": "V"}),
("c:var", {"toc_icon_class": "alias", "toc_icon_text": "V"}),
("c:function", {"toc_icon_class": "procedure", "toc_icon_text": "F"}),
Expand Down
1 change: 0 additions & 1 deletion sphinx_immaterial/apidoc/object_toc.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ def _make_section_from_field(
source: docutils.nodes.field,
) -> Optional[docutils.nodes.section]:
fieldname = cast(docutils.nodes.field_name, source[0])
# fieldbody = cast(docutils.nodes.field_body, source[1])
ids = fieldname["ids"]
if not ids:
# Not indexed
Expand Down
Loading

0 comments on commit 640b5c7

Please sign in to comment.