Skip to content

Commit 86e8aec

Browse files
committed
tryout for pylance
1 parent 78522fb commit 86e8aec

12 files changed

Lines changed: 197 additions & 52 deletions

File tree

docs/conf.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
# Import statements
1515
import datetime
16+
import inspect
1617
import logging
1718
import os
1819
import re
@@ -637,5 +638,20 @@ def _replace_snippet(match):
637638
pass
638639

639640

641+
def process_signature(
642+
app, what, name, obj, options, signature, return_annotation
643+
):
644+
"""Use compact signatures marked by UltraPlot only in generated docs."""
645+
marked = getattr(obj, "__ultraplot_doc_signature__", None)
646+
if marked is None and inspect.ismethod(obj):
647+
marked = getattr(obj.__func__, "__ultraplot_doc_signature__", None)
648+
if marked is None and inspect.isclass(obj):
649+
marked = getattr(obj.__init__, "__ultraplot_doc_signature__", None)
650+
if marked is not None:
651+
return marked, return_annotation
652+
return signature, return_annotation
653+
654+
640655
def setup(app):
641656
app.connect("autodoc-process-docstring", process_docstring)
657+
app.connect("autodoc-process-signature", process_signature)

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ dynamic = ["version"]
4949
packages = {find = {exclude=["docs*", "baseline*", "logo*"]}}
5050
include-package-data = true
5151

52+
[tool.setuptools.package-data]
53+
ultraplot = ["py.typed"]
54+
5255
[tool.setuptools_scm]
5356
write_to = "ultraplot/_version.py"
5457
write_to_template = "__version__ = '{version}'\n"

ultraplot/axes/cartesian.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import functools
88
import inspect
99
from dataclasses import dataclass, field
10-
from typing import Any, Dict, Optional, Tuple, Union
10+
from typing import Any, Callable, Dict, Optional, Tuple, TypeVar, Union, cast
1111

1212
import matplotlib.axis as maxis
1313
import matplotlib.dates as mdates
@@ -40,6 +40,8 @@
4040

4141
__all__ = ["CartesianAxes"]
4242

43+
_F = TypeVar("_F", bound=Callable[..., Any])
44+
4345

4446
# Tuple of date converters
4547
DATE_CONVERTERS = (mdates.DateConverter,)
@@ -1860,7 +1862,7 @@ def get_tightbbox(self, renderer, *args, **kwargs):
18601862
return super().get_tightbbox(renderer, *args, **kwargs)
18611863

18621864

1863-
def _capture_explicit_format_keys(func):
1865+
def _capture_explicit_format_keys(func: _F) -> _F:
18641866
"""
18651867
Preserve raw keyword names before Python binds them to the format signature.
18661868
"""
@@ -1870,7 +1872,7 @@ def wrapper(self, *args, **kwargs):
18701872
kwargs.setdefault("_explicit_format_keys", set(kwargs))
18711873
return func(self, *args, **kwargs)
18721874

1873-
return wrapper
1875+
return cast(_F, wrapper)
18741876

18751877

18761878
# tmp

ultraplot/figure.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import inspect
88
import os
99
from contextlib import ExitStack
10+
from typing import Callable, TypeVar, cast
1011

1112
try:
1213
from typing import Any, Iterable, List, Optional, Tuple, Union
@@ -50,6 +51,8 @@
5051
"Figure",
5152
]
5253

54+
_F = TypeVar("_F", bound=Callable[..., Any])
55+
5356

5457
def _any_not_none(*values):
5558
"""Return whether at least one value is not ``None``."""
@@ -695,7 +698,7 @@ def _draw_context():
695698
return canvas
696699

697700

698-
def _clear_border_cache(func):
701+
def _clear_border_cache(func: _F) -> _F:
699702
"""
700703
Decorator that clears the border cache after function execution.
701704
"""
@@ -707,7 +710,7 @@ def wrapper(self, *args, **kwargs):
707710
delattr(self, "_cached_border_axes")
708711
return result
709712

710-
return wrapper
713+
return cast(_F, wrapper)
711714

712715

713716
class Figure(mfigure.Figure):
@@ -3387,28 +3390,28 @@ def add_axes(self, rect, **kwargs):
33873390

33883391
@docstring._concatenate_inherited
33893392
@docstring._snippet_manager
3390-
def add_subplot(self, *args, **kwargs):
3393+
def add_subplot(self, *args, **kwargs) -> paxes.Axes:
33913394
"""
33923395
%(figure.subplot)s
33933396
"""
33943397
return self._add_subplot(*args, **kwargs)
33953398

33963399
@docstring._snippet_manager
3397-
def subplot(self, *args, **kwargs): # shorthand
3400+
def subplot(self, *args, **kwargs) -> paxes.Axes: # shorthand
33983401
"""
33993402
%(figure.subplot)s
34003403
"""
34013404
return self._add_subplot(*args, **kwargs)
34023405

34033406
@docstring._snippet_manager
3404-
def add_subplots(self, *args, **kwargs):
3407+
def add_subplots(self, *args, **kwargs) -> pgridspec.SubplotGrid:
34053408
"""
34063409
%(figure.subplots)s
34073410
"""
34083411
return self._add_subplots(*args, **kwargs)
34093412

34103413
@docstring._snippet_manager
3411-
def subplots(self, *args, **kwargs):
3414+
def subplots(self, *args, **kwargs) -> pgridspec.SubplotGrid:
34123415
"""
34133416
%(figure.subplots)s
34143417
"""

ultraplot/gridspec.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from collections.abc import MutableSequence
1010
from functools import wraps
1111
from numbers import Integral
12-
from typing import List, Optional, Tuple, Union
12+
from typing import Callable, List, Optional, Tuple, TypeVar, Union, cast, overload
1313

1414
import matplotlib.axes as maxes
1515
import matplotlib.gridspec as mgridspec
@@ -122,8 +122,23 @@ def _dummy_method(*args):
122122
return _dummy_method
123123

124124

125-
def _apply_to_all(func=None, *, doc_key=None):
126-
def decorator(f):
125+
_F = TypeVar("_F", bound=Callable[..., object])
126+
127+
128+
@overload
129+
def _apply_to_all(func: _F, *, doc_key: Optional[str] = None) -> _F: ...
130+
131+
132+
@overload
133+
def _apply_to_all(
134+
func: None = None, *, doc_key: Optional[str] = None
135+
) -> Callable[[_F], _F]: ...
136+
137+
138+
def _apply_to_all(
139+
func: Optional[_F] = None, *, doc_key: Optional[str] = None
140+
) -> Union[_F, Callable[[_F], _F]]:
141+
def decorator(f: _F) -> _F:
127142
@wraps(f)
128143
def wrapper(self, *args, **kwargs):
129144
objs = self._apply_command(f.__name__, *args, **kwargs)
@@ -158,7 +173,7 @@ def wrapper(self, *args, **kwargs):
158173

159174
wrapper.__doc__ = doc
160175

161-
return wrapper
176+
return cast(_F, wrapper)
162177

163178
if func is not None:
164179
return decorator(func)
@@ -2051,7 +2066,7 @@ def _validate_item(self, items, scalar=False):
20512066
return items
20522067

20532068
@docstring._snippet_manager
2054-
def format(self, **kwargs):
2069+
def format(self, **kwargs) -> None:
20552070
"""
20562071
Call the ``format`` command for the `~SubplotGrid.figure`
20572072
and every axes in the grid.

ultraplot/internals/docstring.py

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -23,43 +23,48 @@
2323
# ... print(*_iter_doc(uplt))
2424
import inspect
2525
import re
26+
from typing import Any, Callable, TypeVar, cast, overload
2627

2728
from . import ic # noqa: F401
2829

30+
_F = TypeVar("_F", bound=Callable[..., Any])
31+
_T = TypeVar("_T")
2932

30-
def _obfuscate_kwargs(func):
33+
34+
def _obfuscate_kwargs(func: _F) -> _F:
3135
"""
32-
Obfuscate keyword args.
36+
Mark keyword arguments as compact in generated API documentation.
3337
"""
3438
return _obfuscate_signature(func, lambda **kwargs: None)
3539

3640

37-
def _obfuscate_params(func):
41+
def _obfuscate_params(func: _F) -> _F:
3842
"""
39-
Obfuscate all parameters.
43+
Mark all parameters as compact in generated API documentation.
4044
"""
4145
return _obfuscate_signature(func, lambda *args, **kwargs: None)
4246

4347

44-
def _obfuscate_signature(func, dummy):
48+
def _obfuscate_signature(func: _F, dummy: Callable[..., Any]) -> _F:
4549
"""
46-
Obfuscate a misleading or incomplete call signature.
47-
Instead users should inspect the parameter table.
50+
Mark a misleading or incomplete signature as compact in generated docs.
51+
52+
The callable's actual signature remains available to Python and language
53+
servers; Sphinx reads the marker below when rendering API headings.
4854
"""
49-
# Obfuscate signature by converting to *args **kwargs. Note this does
50-
# not change behavior of function! Copy parameters from a dummy function
51-
# because I'm too lazy to figure out inspect.Parameters API
52-
# See: https://stackoverflow.com/a/33112180/4970632
53-
sig = inspect.signature(func)
54-
sig_repl = inspect.signature(dummy)
55-
func.__signature__ = sig.replace(parameters=tuple(sig_repl.parameters.values()))
55+
# Keep the compact signature available to documentation tooling without
56+
# changing the callable's runtime signature. Sphinx uses this marker to
57+
# avoid filling API headings with inherited or dynamically routed options.
58+
setattr(func, "__ultraplot_doc_signature__", str(inspect.signature(dummy)))
5659
return func
5760

5861

59-
def _concatenate_inherited(func, prepend_summary=False):
62+
def _concatenate_inherited(
63+
func: _F, prepend_summary: bool = False
64+
) -> _F:
6065
"""
6166
Concatenate docstrings from a matplotlib axes method with a ultraplot
62-
axes method and obfuscate the call signature.
67+
axes method and mark its generated-documentation signature as compact.
6368
"""
6469
import matplotlib.axes as maxes
6570
import matplotlib.figure as mfigure
@@ -102,7 +107,7 @@ def _concatenate_inherited(func, prepend_summary=False):
102107
"""
103108

104109
# Return docstring
105-
# NOTE: Also obfuscate parameters to avoid partial coverage of call signatures
110+
# Keep generated API headings compact to avoid showing partial call signatures.
106111
func.__doc__ = inspect.cleandoc(doc)
107112
func = _obfuscate_params(func)
108113
return func
@@ -143,17 +148,24 @@ def __missing__(self, key):
143148
return dict.__getitem__(self, key)
144149
raise KeyError(key)
145150

146-
def __call__(self, obj):
151+
@overload
152+
def __call__(self, obj: str) -> str: ...
153+
154+
@overload
155+
def __call__(self, obj: _T) -> _T: ...
156+
157+
def __call__(self, obj: _T | str) -> _T | str:
147158
"""
148159
Add snippets to the string or object using ``%(name)s`` substitution. Here
149160
``%(name)s`` is used rather than ``.format`` to support invalid identifiers.
150161
"""
151162
if isinstance(obj, str):
152163
obj %= self # add snippets to a string
153164
else:
154-
obj.__doc__ = inspect.getdoc(obj) # also dedents the docstring
155-
if obj.__doc__:
156-
obj.__doc__ %= self # insert snippets after dedent
165+
documented = cast(Any, obj)
166+
documented.__doc__ = inspect.getdoc(documented) # also dedents the docstring
167+
if documented.__doc__:
168+
documented.__doc__ %= self # insert snippets after dedent
157169
return obj
158170

159171
def __setitem__(self, key, value):

ultraplot/internals/inputs.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import functools
77
import sys
8+
from typing import Any, Callable, TypeVar, cast
89

910
import numpy as np
1011
import numpy.ma as ma
@@ -21,6 +22,8 @@
2122
except ModuleNotFoundError:
2223
Triangulation = object
2324

25+
_F = TypeVar("_F", bound=Callable[..., Any])
26+
2427

2528
# Constants
2629
BASEMAP_FUNCS = ( # default latlon=True
@@ -289,13 +292,15 @@ def _parse_triangulation_inputs(*args, **kwargs):
289292
return triangulation, z, args[1:], kwargs
290293

291294

292-
def _parse_triangulation_with_preprocess(*keys, keywords=None, allow_extra=True):
295+
def _parse_triangulation_with_preprocess(
296+
*keys, keywords=None, allow_extra=True
297+
) -> Callable[[_F], _F]:
293298
"""
294299
Combines _parse_triangulation with _preprocess_or_redirect for backwards compatibility.
295300
"""
296301

297-
def _decorator(func):
298-
def triangulation_wrapper(self, *args, **kwargs):
302+
def _decorator(func: _F) -> _F:
303+
def triangulation_wrapper(self, *args, **kwargs) -> Any:
299304
triangulation, z, remaining_args, updated_kwargs = (
300305
_parse_triangulation_inputs(*args, **kwargs)
301306
)
@@ -318,14 +323,14 @@ def _tri_cartopy_default(args, kwargs):
318323

319324
# Finally make sure all other metadata is correct
320325
functools.update_wrapper(final_wrapper, func)
321-
return final_wrapper
326+
return cast(_F, final_wrapper)
322327

323328
return _decorator
324329

325330

326331
def _preprocess_or_redirect(
327332
*keys, keywords=None, allow_extra=True, cartopy_default_transform=True
328-
):
333+
) -> Callable[[_F], _F]:
329334
"""
330335
Redirect internal plotting calls to native matplotlib methods. Also convert
331336
keyword args to positional and pass arguments through 'data' dictionary.
@@ -336,12 +341,12 @@ def _preprocess_or_redirect(
336341
if isinstance(keywords, str):
337342
keywords = (keywords,)
338343

339-
def _decorator(func):
344+
def _decorator(func: _F) -> _F:
340345
name = func.__name__
341346
from . import _kwargs_to_args
342347

343348
@functools.wraps(func)
344-
def _preprocess_or_redirect(self, *args, **kwargs):
349+
def _preprocess_or_redirect(self, *args, **kwargs) -> Any:
345350
if getattr(self, "_internal_call", None):
346351
# Redirect internal matplotlib call to native function
347352
from ..axes import PlotAxes
@@ -404,7 +409,7 @@ def _preprocess_or_redirect(self, *args, **kwargs):
404409
# Call main function
405410
return func(self, *args, **kwargs) # call unbound method
406411

407-
return _preprocess_or_redirect
412+
return cast(_F, _preprocess_or_redirect)
408413

409414
return _decorator
410415

0 commit comments

Comments
 (0)