Skip to content

Commit 7319f89

Browse files
authored
Add overloads to docs (#56)
1 parent 39935ee commit 7319f89

File tree

17 files changed

+466
-309
lines changed

17 files changed

+466
-309
lines changed

.pre-commit-config.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,7 @@ repos:
3434
- zarr
3535
- h5py
3636
- anndata
37+
- types-docutils
38+
- sphinx
3739
ci:
3840
skip: [mypy] # too big

docs/conf.py

Lines changed: 71 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@
66
from datetime import UTC, datetime
77
from importlib.metadata import metadata
88
from pathlib import Path
9+
from typing import TYPE_CHECKING
10+
11+
12+
if TYPE_CHECKING:
13+
from docutils.nodes import TextElement, reference
14+
from sphinx.addnodes import pending_xref
15+
from sphinx.application import Sphinx
16+
from sphinx.environment import BuildEnvironment
917

1018

1119
HERE = Path(__file__).parent
@@ -47,6 +55,7 @@
4755
napoleon_numpy_docstring = True
4856
todo_include_todos = False
4957
intersphinx_mapping = dict(
58+
anndata=("https://anndata.readthedocs.io/en/stable/", None),
5059
cupy=("https://docs.cupy.dev/en/stable/", None),
5160
dask=("https://docs.dask.org/en/stable/", None),
5261
h5py=("https://docs.h5py.org/en/stable/", None),
@@ -55,47 +64,14 @@
5564
scipy=("https://docs.scipy.org/doc/scipy/", None),
5665
zarr=("https://zarr.readthedocs.io/en/stable/", None),
5766
)
58-
# Try overriding type paths
59-
qualname_overrides = autodoc_type_aliases = {
60-
"np.bool": ("py:data", "numpy.bool"),
61-
"np.dtype": "numpy.dtype",
62-
"np.number": "numpy.number",
63-
"np.integer": "numpy.integer",
64-
"np.floating": "numpy.floating",
65-
"np.random.Generator": "numpy.random.Generator",
66-
"ArrayLike": "numpy.typing.ArrayLike",
67-
"DTypeLike": "numpy.typing.DTypeLike",
68-
"NDArray": "numpy.typing.NDArray",
69-
"_pytest.fixtures.FixtureRequest": "pytest.FixtureRequest",
70-
**{
71-
k: v
72-
for k_plain, v in {
73-
"CSBase": "scipy.sparse.spmatrix",
74-
"CupyArray": "cupy.ndarray",
75-
"CupySparseMatrix": "cupyx.scipy.sparse.spmatrix",
76-
"DaskArray": "dask.array.Array",
77-
"H5Dataset": "h5py.Dataset",
78-
"ZarrArray": "zarr.Array",
79-
}.items()
80-
for k in (k_plain, f"types.{k_plain}")
81-
},
82-
}
83-
# If that doesn’t work, ignore them
84-
nitpick_ignore = {
85-
("py:class", "fast_array_utils.types.T_co"),
67+
nitpick_ignore = [
8668
("py:class", "Arr"),
69+
("py:class", "ToDType"),
8770
("py:class", "testing.fast_array_utils._array_type.Arr"),
8871
("py:class", "testing.fast_array_utils._array_type.Inner"),
8972
("py:class", "_DTypeLikeFloat32"),
9073
("py:class", "_DTypeLikeFloat64"),
91-
# sphinx bugs, should be covered by `autodoc_type_aliases` above
92-
("py:class", "Array"),
93-
("py:class", "ArrayLike"),
94-
("py:class", "DTypeLike"),
95-
("py:class", "NDArray"),
96-
("py:class", "np.bool"),
97-
("py:class", "_pytest.fixtures.FixtureRequest"),
98-
}
74+
]
9975

10076
# Options for HTML output
10177
html_theme = "furo"
@@ -104,3 +80,62 @@
10480
source_branch="main",
10581
source_directory="docs/",
10682
)
83+
84+
_np_nocls = {"float64": "attr"}
85+
_optional_types = {
86+
"CupyArray": "cupy.ndarray",
87+
"CupySparseMatrix": "cupyx.scipy.sparse.spmatrix",
88+
"DaskArray": "dask.array.Array",
89+
"H5Dataset": "h5py.Dataset",
90+
"ZarrArray": "zarr.Array",
91+
}
92+
93+
94+
def find_type_alias(name: str) -> tuple[str, str] | tuple[None, None]:
95+
"""Find a type alias."""
96+
import numpy.typing as npt
97+
98+
from fast_array_utils import types, typing
99+
100+
if name in typing.__all__:
101+
return "data", f"fast_array_utils.typing.{name}"
102+
if name.startswith("types.") and name[6:] in types.__all__:
103+
if path := _optional_types.get(name[6:]):
104+
return "class", path
105+
return "data", f"fast_array_utils.{name}"
106+
if name.startswith("np."):
107+
return _np_nocls.get(name[3:], "class"), f"numpy.{name[3:]}"
108+
if name in npt.__all__:
109+
return "data", f"numpy.typing.{name}"
110+
return None, None
111+
112+
113+
def resolve_type_aliases(
114+
app: Sphinx, env: BuildEnvironment, node: pending_xref, contnode: TextElement
115+
) -> reference | None:
116+
"""Resolve :class: references to our type aliases as :attr: instead."""
117+
if (node["refdomain"], node["reftype"]) != ("py", "class"):
118+
return None
119+
typ, target = find_type_alias(node["reftarget"])
120+
if typ is None or target is None:
121+
return None
122+
if target.startswith("fast_array_utils."):
123+
ref = env.get_domain("py").resolve_xref(
124+
env, node["refdoc"], app.builder, typ, target, node, contnode
125+
)
126+
else:
127+
from sphinx.ext.intersphinx import resolve_reference_any_inventory
128+
129+
node["reftype"] = typ
130+
node["reftarget"] = target
131+
ref = resolve_reference_any_inventory(
132+
env=env, honor_disabled_refs=False, node=node, contnode=contnode
133+
)
134+
if ref is None:
135+
msg = f"Could not resolve {typ} {target} (from {node['reftarget']})"
136+
raise AssertionError(msg)
137+
return ref
138+
139+
140+
def setup(app: Sphinx) -> None: # noqa: D103
141+
app.connect("missing-reference", resolve_type_aliases, priority=800)

docs/index.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,18 @@
1010
.. automodule:: fast_array_utils
1111
:members:
1212

13+
``fast_array_utils.types``
14+
--------------------------
15+
16+
.. automodule:: fast_array_utils.types
17+
:members: CSBase, CSDataset
18+
19+
``fast_array_utils.typing``
20+
---------------------------
21+
22+
.. automodule:: fast_array_utils.typing
23+
:members:
24+
1325
``fast_array_utils.conv``
1426
-------------------------
1527

src/fast_array_utils/conv/__init__.py

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,59 @@
33

44
from __future__ import annotations
55

6-
from ._to_dense import to_dense
6+
from typing import TYPE_CHECKING, overload
7+
8+
from ..typing import CpuArray, DiskArray, GpuArray # noqa: TC001
9+
from ._to_dense import to_dense_
10+
11+
12+
if TYPE_CHECKING:
13+
from typing import Any, Literal
14+
15+
from numpy.typing import NDArray
16+
17+
from .. import types
718

819

920
__all__ = ["to_dense"]
21+
22+
23+
@overload
24+
def to_dense(
25+
x: CpuArray | DiskArray | types.CSDataset, /, *, to_memory: bool = False
26+
) -> NDArray[Any]: ...
27+
28+
29+
@overload
30+
def to_dense(x: types.DaskArray, /, *, to_memory: Literal[False] = False) -> types.DaskArray: ...
31+
@overload
32+
def to_dense(x: types.DaskArray, /, *, to_memory: Literal[True]) -> NDArray[Any]: ...
33+
34+
35+
@overload
36+
def to_dense(x: GpuArray, /, *, to_memory: Literal[False] = False) -> types.CupyArray: ...
37+
@overload
38+
def to_dense(x: GpuArray, /, *, to_memory: Literal[True]) -> NDArray[Any]: ...
39+
40+
41+
def to_dense(
42+
x: CpuArray | GpuArray | DiskArray | types.CSDataset | types.DaskArray,
43+
/,
44+
*,
45+
to_memory: bool = False,
46+
) -> NDArray[Any] | types.DaskArray | types.CupyArray:
47+
"""Convert x to a dense array.
48+
49+
Parameters
50+
----------
51+
x
52+
Input object to be converted.
53+
to_memory
54+
Also load data into memory (resulting in a :class:`numpy.ndarray`).
55+
56+
Returns
57+
-------
58+
Dense form of ``x``
59+
60+
"""
61+
return to_dense_(x, to_memory=to_memory)

src/fast_array_utils/conv/_to_dense.py

Lines changed: 15 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -2,106 +2,61 @@
22
from __future__ import annotations
33

44
from functools import singledispatch
5-
from typing import TYPE_CHECKING, cast, overload
5+
from typing import TYPE_CHECKING, cast
66

77
import numpy as np
88

99
from .. import types
10+
from ..typing import CpuArray, DiskArray, GpuArray # noqa: TC001
1011

1112

1213
if TYPE_CHECKING:
13-
from typing import Any, Literal, TypeAlias
14+
from typing import Any
1415

1516
from numpy.typing import NDArray
1617

17-
MemDiskArray: TypeAlias = (
18-
NDArray[Any] | types.CSBase | types.H5Dataset | types.ZarrArray | types.CSDataset
19-
)
20-
Array: TypeAlias = MemDiskArray | types.CupyArray | types.CupySparseMatrix | types.DaskArray
21-
22-
23-
__all__ = ["to_dense"]
24-
25-
26-
@overload
27-
def to_dense(x: MemDiskArray, /, *, to_memory: bool = False) -> NDArray[Any]: ...
28-
29-
30-
@overload
31-
def to_dense(x: types.DaskArray, /, *, to_memory: Literal[False] = False) -> types.DaskArray: ...
32-
@overload
33-
def to_dense(x: types.DaskArray, /, *, to_memory: Literal[True]) -> NDArray[Any]: ...
34-
35-
36-
@overload
37-
def to_dense(
38-
x: types.CupyArray | types.CupySparseMatrix, /, *, to_memory: Literal[False] = False
39-
) -> types.CupyArray: ...
40-
@overload
41-
def to_dense(
42-
x: types.CupyArray | types.CupySparseMatrix, /, *, to_memory: Literal[True]
43-
) -> NDArray[Any]: ...
44-
45-
46-
def to_dense(
47-
x: Array, /, *, to_memory: bool = False
48-
) -> NDArray[Any] | types.DaskArray | types.CupyArray:
49-
"""Convert x to a dense array.
50-
51-
Parameters
52-
----------
53-
x
54-
Input object to be converted.
55-
to_memory
56-
Also load data into memory (resulting in a :class:`numpy.ndarray`).
57-
58-
Returns
59-
-------
60-
Dense form of ``x``
61-
62-
"""
63-
return _to_dense(x, to_memory=to_memory)
64-
6518

6619
# fallback’s arg0 type has to include types of registered functions
6720
@singledispatch
68-
def _to_dense(
69-
x: Array, /, *, to_memory: bool = False
70-
) -> NDArray[Any] | types.DaskArray | types.CupyArray:
21+
def to_dense_(
22+
x: CpuArray | GpuArray | DiskArray | types.DaskArray, /, *, to_memory: bool = False
23+
) -> NDArray[Any] | types.CupyArray | types.DaskArray:
7124
del to_memory # it already is
7225
return np.asarray(x)
7326

7427

75-
@_to_dense.register(types.CSBase) # type: ignore[call-overload,misc]
28+
@to_dense_.register(types.CSBase) # type: ignore[call-overload,misc]
7629
def _to_dense_cs(x: types.CSBase, /, *, to_memory: bool = False) -> NDArray[Any]:
7730
from . import scipy
7831

7932
del to_memory # it already is
8033
return scipy.to_dense(x)
8134

8235

83-
@_to_dense.register(types.DaskArray)
36+
@to_dense_.register(types.DaskArray)
8437
def _to_dense_dask(
8538
x: types.DaskArray, /, *, to_memory: bool = False
8639
) -> NDArray[Any] | types.DaskArray:
8740
import dask.array as da
8841

42+
from . import to_dense
43+
8944
x = da.map_blocks(to_dense, x)
9045
return x.compute() if to_memory else x # type: ignore[return-value]
9146

9247

93-
@_to_dense.register(types.CSDataset)
48+
@to_dense_.register(types.CSDataset)
9449
def _to_dense_ooc(x: types.CSDataset, /, *, to_memory: bool = False) -> NDArray[Any]:
50+
from . import to_dense
51+
9552
if not to_memory:
9653
msg = "to_memory must be True if x is an CS{R,C}Dataset"
9754
raise ValueError(msg)
9855
# TODO(flying-sheep): why is to_memory of type Any? # noqa: TD003
9956
return to_dense(cast("types.CSBase", x.to_memory()))
10057

10158

102-
@_to_dense.register(types.CupyArray | types.CupySparseMatrix) # type: ignore[call-overload,misc]
103-
def _to_dense_cupy(
104-
x: types.CupyArray | types.CupySparseMatrix, /, *, to_memory: bool = False
105-
) -> NDArray[Any] | types.CupyArray:
59+
@to_dense_.register(GpuArray) # type: ignore[call-overload,misc]
60+
def _to_dense_cupy(x: GpuArray, /, *, to_memory: bool = False) -> NDArray[Any] | types.CupyArray:
10661
x = x.toarray() if isinstance(x, types.CupySparseMatrix) else x
10762
return x.get() if to_memory else x

0 commit comments

Comments
 (0)