Skip to content

Commit 04e0925

Browse files
Support typing via PyRight (#138)
* fix mixed named and anonymous arguments * configure pyright * reduce old-style types * run pyright in CI * use separate typetest * document typing explicitly
1 parent c1c8e06 commit 04e0925

File tree

7 files changed

+55
-25
lines changed

7 files changed

+55
-25
lines changed

.github/workflows/verification.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
- name: Install dependencies
1919
run: |
2020
python -m pip install --upgrade pip
21-
pip install .[test]
21+
pip install .[test,typetest]
2222
- name: Lint with flake8
2323
run: |
2424
flake8 asyncstdlib unittests
@@ -28,3 +28,5 @@ jobs:
2828
- name: Verify with MyPy
2929
run: |
3030
mypy --pretty
31+
- name: Verify with PyRight
32+
uses: jakebailey/pyright-action@v2

asyncstdlib/_core.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
AsyncGenerator,
1616
Iterable,
1717
AsyncIterable,
18-
Union,
1918
Generic,
2019
Optional,
2120
Awaitable,
@@ -100,7 +99,7 @@ def borrow(iterator: AsyncIterator[T]) -> AsyncGenerator[T, None]:
10099

101100

102101
def awaitify(
103-
function: Union[Callable[..., T], Callable[..., Awaitable[T]]]
102+
function: "Callable[..., Awaitable[T]] | Callable[..., T]",
104103
) -> Callable[..., Awaitable[T]]:
105104
"""Ensure that ``function`` can be used in ``await`` expressions"""
106105
if iscoroutinefunction(function):
@@ -114,16 +113,16 @@ class Awaitify(Generic[T]):
114113

115114
__slots__ = "__wrapped__", "_async_call"
116115

117-
def __init__(self, function: Union[Callable[..., T], Callable[..., Awaitable[T]]]):
116+
def __init__(self, function: "Callable[..., Awaitable[T]] | Callable[..., T]"):
118117
self.__wrapped__ = function
119-
self._async_call: Optional[Callable[..., Awaitable[T]]] = None
118+
self._async_call: "Callable[..., Awaitable[T]] | None" = None
120119

121120
def __call__(self, *args: Any, **kwargs: Any) -> Awaitable[T]:
122121
if (async_call := self._async_call) is None:
123122
value = self.__wrapped__(*args, **kwargs)
124123
if isinstance(value, Awaitable):
125124
self._async_call = self.__wrapped__ # type: ignore
126-
return value
125+
return value # pyright: ignore
127126
else:
128127
self._async_call = force_async(self.__wrapped__) # type: ignore
129128
return await_value(value)

asyncstdlib/builtins.pyi

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,37 +77,43 @@ def zip(
7777
def map(
7878
function: Callable[[T1], Awaitable[R]],
7979
__it1: AnyIterable[T1],
80+
/,
8081
) -> AsyncIterator[R]: ...
8182
@overload
8283
def map(
8384
function: Callable[[T1], R],
8485
__it1: AnyIterable[T1],
86+
/,
8587
) -> AsyncIterator[R]: ...
8688
@overload
8789
def map(
8890
function: Callable[[T1, T2], Awaitable[R]],
8991
__it1: AnyIterable[T1],
9092
__it2: AnyIterable[T2],
93+
/,
9194
) -> AsyncIterator[R]: ...
9295
@overload
9396
def map(
9497
function: Callable[[T1, T2], R],
9598
__it1: AnyIterable[T1],
9699
__it2: AnyIterable[T2],
100+
/,
97101
) -> AsyncIterator[R]: ...
98102
@overload
99103
def map(
100104
function: Callable[[T1, T2, T3], Awaitable[R]],
101105
__it1: AnyIterable[T1],
102106
__it2: AnyIterable[T2],
103107
__it3: AnyIterable[T3],
108+
/,
104109
) -> AsyncIterator[R]: ...
105110
@overload
106111
def map(
107112
function: Callable[[T1, T2, T3], R],
108113
__it1: AnyIterable[T1],
109114
__it2: AnyIterable[T2],
110115
__it3: AnyIterable[T3],
116+
/,
111117
) -> AsyncIterator[R]: ...
112118
@overload
113119
def map(
@@ -116,6 +122,7 @@ def map(
116122
__it2: AnyIterable[T2],
117123
__it3: AnyIterable[T3],
118124
__it4: AnyIterable[T4],
125+
/,
119126
) -> AsyncIterator[R]: ...
120127
@overload
121128
def map(
@@ -124,6 +131,7 @@ def map(
124131
__it2: AnyIterable[T2],
125132
__it3: AnyIterable[T3],
126133
__it4: AnyIterable[T4],
134+
/,
127135
) -> AsyncIterator[R]: ...
128136
@overload
129137
def map(
@@ -133,6 +141,7 @@ def map(
133141
__it3: AnyIterable[T3],
134142
__it4: AnyIterable[T4],
135143
__it5: AnyIterable[T5],
144+
/,
136145
) -> AsyncIterator[R]: ...
137146
@overload
138147
def map(
@@ -142,6 +151,7 @@ def map(
142151
__it3: AnyIterable[T3],
143152
__it4: AnyIterable[T4],
144153
__it5: AnyIterable[T5],
154+
/,
145155
) -> AsyncIterator[R]: ...
146156
@overload
147157
def map(
@@ -151,6 +161,7 @@ def map(
151161
__it3: AnyIterable[Any],
152162
__it4: AnyIterable[Any],
153163
__it5: AnyIterable[Any],
164+
/,
154165
*iterable: AnyIterable[Any],
155166
) -> AsyncIterator[R]: ...
156167
@overload
@@ -161,6 +172,7 @@ def map(
161172
__it3: AnyIterable[Any],
162173
__it4: AnyIterable[Any],
163174
__it5: AnyIterable[Any],
175+
/,
164176
*iterable: AnyIterable[Any],
165177
) -> AsyncIterator[R]: ...
166178
@overload

asyncstdlib/heapq.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
from typing import (
33
Generic,
44
AsyncIterator,
5-
Tuple,
6-
List,
75
Optional,
86
Callable,
97
Any,
@@ -53,21 +51,21 @@ def __init__(
5351
@classmethod
5452
def from_iters(
5553
cls,
56-
iterables: Tuple[AnyIterable[T], ...],
54+
iterables: "tuple[AnyIterable[T], ...]",
5755
reverse: bool,
5856
key: Callable[[T], Awaitable[LT]],
5957
) -> "AsyncIterator[_KeyIter[LT]]": ...
6058

6159
@overload
6260
@classmethod
6361
def from_iters(
64-
cls, iterables: Tuple[AnyIterable[LT], ...], reverse: bool, key: None
62+
cls, iterables: "tuple[AnyIterable[LT], ...]", reverse: bool, key: None
6563
) -> "AsyncIterator[_KeyIter[LT]]": ...
6664

6765
@classmethod
6866
async def from_iters(
6967
cls,
70-
iterables: Tuple[AnyIterable[Any], ...],
68+
iterables: "tuple[AnyIterable[Any], ...]",
7169
reverse: bool,
7270
key: Optional[Callable[[Any], Any]],
7371
) -> "AsyncIterator[_KeyIter[Any]]":
@@ -124,10 +122,10 @@ async def merge(
124122
"""
125123
a_key = awaitify(key) if key is not None else None
126124
# sortable iterators with (reverse) position to ensure stable sort for ties
127-
iter_heap: List[Tuple[_KeyIter[Any], int]] = [
125+
iter_heap: "list[tuple[_KeyIter[Any], int]]" = [
128126
(itr, idx if not reverse else -idx)
129127
async for idx, itr in a_enumerate(
130-
_KeyIter.from_iters(iterables, reverse, a_key)
128+
_KeyIter[Any].from_iters(iterables, reverse, a_key)
131129
)
132130
]
133131
try:
@@ -175,7 +173,7 @@ async def _largest(
175173
n: int,
176174
key: Callable[[T], Awaitable[LT]],
177175
reverse: bool,
178-
) -> List[T]:
176+
) -> "list[T]":
179177
ordered: Callable[[LT], LT] = ReverseLT if reverse else lambda x: x # type: ignore
180178
async with ScopedIter(iterable) as iterator:
181179
# assign an ordering to items to solve ties
@@ -207,7 +205,7 @@ async def nlargest(
207205
iterable: AnyIterable[T],
208206
n: int,
209207
key: Optional[Callable[[Any], Awaitable[Any]]] = None,
210-
) -> List[T]:
208+
) -> "list[T]":
211209
"""
212210
Return a sorted list of the ``n`` largest elements from the (async) iterable
213211
@@ -229,7 +227,7 @@ async def nsmallest(
229227
iterable: AnyIterable[T],
230228
n: int,
231229
key: Optional[Callable[[Any], Awaitable[Any]]] = None,
232-
) -> List[T]:
230+
) -> "list[T]":
233231
"""
234232
Return a sorted list of the ``n`` smallest elements from the (async) iterable
235233

asyncstdlib/itertools.pyi

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,9 @@ from typing import (
88
Iterable,
99
Callable,
1010
TypeVar,
11-
Self,
1211
overload,
1312
)
14-
from typing_extensions import Literal
13+
from typing_extensions import Literal, Self
1514

1615
from ._typing import AnyIterable, ADD, T, T1, T2, T3, T4, T5
1716

docs/source/contributing.rst

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@ where you can report bugs, request improvements or propose changes.
88

99
- For bug reports and feature requests simply `open a new issue`_
1010
and fill in the appropriate template.
11-
- Even for content submissions it is highly recommended to make sure an issue
12-
exists - this allows you to get early feedback and document the development.
11+
- Even for content submissions make sure `an issue exists`_ - this
12+
allows you to get early feedback and document the development.
1313
You can use whatever tooling you like to create the content,
1414
but the next sections give a rough outline on how to proceed.
1515

1616
.. _asyncstdlib GitHub repository: https://github.com/maxfischer2781/asyncstdlib
1717
.. _open a new issue: https://github.com/maxfischer2781/asyncstdlib/issues/new/choose
18+
.. _an issue exists: https://github.com/maxfischer2781/asyncstdlib/issues
1819

1920
Submitting Content
2021
==================
@@ -32,26 +33,34 @@ the extras ``test`` and ``doc``, respectively.
3233
.. note::
3334

3435
Ideally you develop with the repository checked out locally and a separate `Python venv`_.
35-
If you have the venv active and the current working directory is the repository root,
36-
simply run `python -m pip install -e '.[test,doc]'` to install all dependencies.
36+
If you have the venv active and are at the repository root,
37+
run ``python -m pip install -e '.[test,typetest,doc]'`` to install all dependencies.
3738

3839
.. _`GitHub Fork and Pull Request`: https://guides.github.com/activities/forking/
3940
.. _`Python venv`: https://docs.python.org/3/library/venv.html
4041

4142
Testing Code
4243
------------
4344

44-
Code is verified locally using the tools `flake8`, `black`, `pytest` and `mypy`.
45-
If you do not have your own preferences we recommend the following order:
45+
Code can be verified locally using the tools `flake8`, `black`, `pytest`, `pyright` and `mypy`.
46+
You should always verify that the basic checks pass:
4647

4748
.. code:: bash
4849
4950
python -m black asyncstdlib unittests
5051
python -m flake8 asyncstdlib unittests
5152
python -m pytest
53+
54+
This runs tests from simplest to most advanced and should allow a quick development cycle.
55+
56+
In many cases you can rely on your IDE for type checking.
57+
For major typing related changes, run the full type checking:
58+
59+
.. code:: bash
60+
5261
python -m mypy --pretty
62+
python -m pyright
5363
54-
This runs tests from simplest to most advanced and should allow you quick development.
5564
Note that some additional checks are run on GitHub to check test coverage and code health.
5665

5766
Building Docs

pyproject.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ test = [
3636
"pytest-cov",
3737
"flake8-2020",
3838
"mypy; implementation_name=='cpython'",
39+
]
40+
typetest = [
41+
"mypy; implementation_name=='cpython'",
42+
"pyright",
3943
"typing-extensions",
4044
]
4145
doc = ["sphinx", "sphinxcontrib-trio"]
@@ -64,6 +68,13 @@ warn_return_any = true
6468
no_implicit_reexport = true
6569
strict_equality = true
6670

71+
[tool.pyright]
72+
include = ["asyncstdlib", "typetests"]
73+
typeCheckingMode = "strict"
74+
pythonPlatform = "All"
75+
pythonVersion = "3.8"
76+
verboseOutput = true
77+
6778
[tool.pytest.ini_options]
6879
testpaths = [
6980
"unittests",

0 commit comments

Comments
 (0)