Skip to content

Commit 246ed45

Browse files
authored
feat: Improved typing for static type checkers
1 parent 42f2e5c commit 246ed45

File tree

17 files changed

+396
-236
lines changed

17 files changed

+396
-236
lines changed

.github/actions/code-style/action.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,7 @@ runs:
1313
- name: MyPy
1414
shell: bash
1515
run: uv run mypy ./
16+
17+
- name: Pyright
18+
shell: bash
19+
run: uv run pyright

Makefile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
before_commit: lint mypy pytest
1+
before_commit: lint mypy pyright pytest
22

33
install:
44
uv sync
@@ -14,5 +14,8 @@ lint:
1414
mypy:
1515
uv run mypy ./
1616

17+
pyright:
18+
uv run pyright
19+
1720
pytest:
1821
uv run pytest

injection/__init__.pyi

Lines changed: 29 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -151,13 +151,13 @@ class Module:
151151
@property
152152
def is_locked(self) -> bool: ...
153153
@overload
154-
def inject[**P, T](
154+
def inject[T](
155155
self,
156-
wrapped: Callable[P, T],
156+
wrapped: T,
157157
/,
158158
*,
159159
threadsafe: bool | None = ...,
160-
) -> Callable[P, T]:
160+
) -> T:
161161
"""
162162
Decorator applicable to a class or function. Inject function dependencies using
163163
parameter type annotations. If applied to a class, the dependencies resolved
@@ -166,72 +166,31 @@ class Module:
166166
With `threadsafe=True`, the injection logic is wrapped in a `threading.RLock`.
167167
"""
168168

169-
@overload
170-
def inject[T](
171-
self,
172-
wrapped: type[T],
173-
/,
174-
*,
175-
threadsafe: bool | None = ...,
176-
) -> type[T]: ...
177169
@overload
178170
def inject(
179171
self,
180172
wrapped: None = ...,
181173
/,
182174
*,
183175
threadsafe: bool | None = ...,
184-
) -> _Decorator[Callable[..., Any] | type]: ...
176+
) -> _Decorator: ... # type: ignore[type-arg]
185177
@overload
186-
def injectable[**P, T](
178+
def injectable[T](
187179
self,
188-
wrapped: Callable[P, T],
180+
wrapped: T,
189181
/,
190182
*,
191-
cls: _InjectableFactory[T] = ...,
183+
cls: _InjectableFactory[Any] = ...,
192184
inject: bool = ...,
193-
on: _TypeInfo[T] = ...,
185+
on: _TypeInfo[Any] = ...,
194186
mode: Mode | ModeStr = ...,
195-
) -> Callable[P, T]:
187+
) -> T:
196188
"""
197189
Decorator applicable to a class or function. It is used to indicate how the
198190
injectable will be constructed. At injection time, a new instance will be
199191
injected each time.
200192
"""
201193

202-
@overload
203-
def injectable[**P, T]( # type: ignore[overload-overlap]
204-
self,
205-
wrapped: Callable[P, Awaitable[T]],
206-
/,
207-
*,
208-
cls: _InjectableFactory[T] = ...,
209-
inject: bool = ...,
210-
on: _TypeInfo[T] = ...,
211-
mode: Mode | ModeStr = ...,
212-
) -> Callable[P, Awaitable[T]]: ...
213-
@overload
214-
def injectable[T](
215-
self,
216-
wrapped: type[T],
217-
/,
218-
*,
219-
cls: _InjectableFactory[T] = ...,
220-
inject: bool = ...,
221-
on: _TypeInfo[T] = ...,
222-
mode: Mode | ModeStr = ...,
223-
) -> type[T]: ...
224-
@overload
225-
def injectable[T](
226-
self,
227-
wrapped: None = ...,
228-
/,
229-
*,
230-
cls: _InjectableFactory[T] = ...,
231-
inject: bool = ...,
232-
on: _TypeInfo[T],
233-
mode: Mode | ModeStr = ...,
234-
) -> _Decorator[Callable[..., T] | Callable[..., Awaitable[T]] | type[T]]: ...
235194
@overload
236195
def injectable(
237196
self,
@@ -240,99 +199,52 @@ class Module:
240199
*,
241200
cls: _InjectableFactory[Any] = ...,
242201
inject: bool = ...,
243-
on: tuple[()] = ...,
202+
on: _TypeInfo[Any] = ...,
244203
mode: Mode | ModeStr = ...,
245-
) -> _Decorator[Callable[..., Any] | type]: ...
204+
) -> _Decorator: ... # type: ignore[type-arg]
246205
@overload
247-
def singleton[**P, T](
206+
def singleton[T](
248207
self,
249-
wrapped: Callable[P, T],
208+
wrapped: T,
250209
/,
251210
*,
252211
inject: bool = ...,
253-
on: _TypeInfo[T] = ...,
212+
on: _TypeInfo[Any] = ...,
254213
mode: Mode | ModeStr = ...,
255-
) -> Callable[P, T]:
214+
) -> T:
256215
"""
257216
Decorator applicable to a class or function. It is used to indicate how the
258217
singleton will be constructed. At injection time, the injected instance will
259218
always be the same.
260219
"""
261220

262-
@overload
263-
def singleton[**P, T]( # type: ignore[overload-overlap]
264-
self,
265-
wrapped: Callable[P, Awaitable[T]],
266-
/,
267-
*,
268-
inject: bool = ...,
269-
on: _TypeInfo[T] = ...,
270-
mode: Mode | ModeStr = ...,
271-
) -> Callable[P, Awaitable[T]]: ...
272-
@overload
273-
def singleton[T](
274-
self,
275-
wrapped: type[T],
276-
/,
277-
*,
278-
inject: bool = ...,
279-
on: _TypeInfo[T] = ...,
280-
mode: Mode | ModeStr = ...,
281-
) -> type[T]: ...
282-
@overload
283-
def singleton[T](
284-
self,
285-
wrapped: None = ...,
286-
/,
287-
*,
288-
inject: bool = ...,
289-
on: _TypeInfo[T],
290-
mode: Mode | ModeStr = ...,
291-
) -> _Decorator[Callable[..., T] | Callable[..., Awaitable[T]] | type[T]]: ...
292221
@overload
293222
def singleton(
294223
self,
295224
wrapped: None = ...,
296225
/,
297226
*,
298227
inject: bool = ...,
299-
on: tuple[()] = ...,
228+
on: _TypeInfo[Any] = ...,
300229
mode: Mode | ModeStr = ...,
301-
) -> _Decorator[Callable[..., Any] | type]: ...
302-
@overload
303-
def scoped[T](
230+
) -> _Decorator: ... # type: ignore[type-arg]
231+
def scoped(
304232
self,
305233
scope_name: str,
306234
/,
307235
*,
308236
inject: bool = ...,
309-
on: _TypeInfo[T],
237+
on: _TypeInfo[Any] = ...,
310238
mode: Mode | ModeStr = ...,
311-
) -> _Decorator[
312-
Callable[..., T]
313-
| Callable[..., Awaitable[T]]
314-
| Callable[..., AsyncIterator[T]]
315-
| Callable[..., Iterator[T]]
316-
| type[T]
317-
]:
239+
) -> _Decorator: # type: ignore[type-arg]
318240
"""
319241
Decorator applicable to a class or function or generator function. It is used
320242
to indicate how the scoped instance will be constructed. At injection time, the
321243
injected instance is retrieved from the scope.
322244
"""
323245

324246
@overload
325-
def scoped(
326-
self,
327-
scope_name: str,
328-
/,
329-
*,
330-
inject: bool = ...,
331-
on: tuple[()] = ...,
332-
mode: Mode | ModeStr = ...,
333-
) -> _Decorator[Callable[..., Any] | type]: ...
334-
@overload
335-
def should_be_injectable[T](self, wrapped: type[T], /) -> type[T]:
247+
def should_be_injectable[T](self, wrapped: T, /) -> T:
336248
"""
337249
Decorator applicable to a class. It is used to specify whether an injectable
338250
should be registered. Raise an exception at injection time if the class isn't
@@ -344,62 +256,35 @@ class Module:
344256
self,
345257
wrapped: None = ...,
346258
/,
347-
) -> _Decorator[type]: ...
259+
) -> _Decorator: ... # type: ignore[type-arg]
348260
@overload
349-
def constant[**P, T](
261+
def constant[T](
350262
self,
351-
wrapped: Callable[P, T],
263+
wrapped: T,
352264
/,
353265
*,
354-
on: _TypeInfo[T] = ...,
266+
on: _TypeInfo[Any] = ...,
355267
mode: Mode | ModeStr = ...,
356-
) -> Callable[P, T]:
268+
) -> T:
357269
"""
358270
Decorator applicable to a class or function. It is used to indicate how the
359271
constant is constructed. At injection time, the injected instance will always
360272
be the same. Unlike `@singleton`, dependencies will not be resolved.
361273
"""
362274

363-
@overload
364-
def constant[**P, T]( # type: ignore[overload-overlap]
365-
self,
366-
wrapped: Callable[P, Awaitable[T]],
367-
/,
368-
*,
369-
on: _TypeInfo[T] = ...,
370-
mode: Mode | ModeStr = ...,
371-
) -> Callable[P, Awaitable[T]]: ...
372-
@overload
373-
def constant[T](
374-
self,
375-
wrapped: type[T],
376-
/,
377-
*,
378-
on: _TypeInfo[T] = ...,
379-
mode: Mode | ModeStr = ...,
380-
) -> type[T]: ...
381-
@overload
382-
def constant[T](
383-
self,
384-
wrapped: None = ...,
385-
/,
386-
*,
387-
on: _TypeInfo[T],
388-
mode: Mode | ModeStr = ...,
389-
) -> _Decorator[Callable[..., T] | Callable[..., Awaitable[T]] | type[T]]: ...
390275
@overload
391276
def constant(
392277
self,
393278
wrapped: None = ...,
394279
/,
395280
*,
396-
on: tuple[()] = ...,
281+
on: _TypeInfo[Any] = ...,
397282
mode: Mode | ModeStr = ...,
398-
) -> _Decorator[Callable[..., Any] | type]: ...
283+
) -> _Decorator: ... # type: ignore[type-arg]
399284
def set_constant[T](
400285
self,
401286
instance: T,
402-
on: _TypeInfo[T] = ...,
287+
on: _TypeInfo[Any] = ...,
403288
*,
404289
alias: bool = ...,
405290
mode: Mode | ModeStr = ...,

injection/_core/asfunction.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
from collections.abc import Callable
22
from functools import wraps
33
from inspect import iscoroutinefunction
4-
from typing import Any
4+
from typing import Any, Protocol
55

66
from injection._core.common.asynchronous import Caller
77
from injection._core.module import Module, mod
88

9-
type AsFunctionWrappedType[**P, T] = type[Callable[P, T]]
9+
10+
class _AsFunctionCallable[**P, T](Protocol):
11+
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T: ...
12+
13+
14+
type AsFunctionWrappedType[**P, T] = type[_AsFunctionCallable[P, T]]
1015

1116

1217
def asfunction[**P, T](

pyproject.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ bench = [
1111
dev = [
1212
"hatch",
1313
"mypy",
14+
"pyright",
1415
"ruff",
1516
]
1617
doc = [
@@ -76,7 +77,7 @@ exclude_lines = [
7677
]
7778

7879
[tool.coverage.run]
79-
omit = ["bench.py"]
80+
omit = ["bench.py", "typing_checks/*"]
8081

8182
[tool.hatch.build]
8283
skip-excluded-dirs = true
@@ -108,6 +109,9 @@ init_forbid_extra = true
108109
init_typed = true
109110
warn_required_dynamic_aliases = true
110111

112+
[tool.pyright]
113+
include = ["typing_checks/"]
114+
111115
[tool.pytest]
112116
python_files = ["test_*.py"]
113117
addopts = ["--tb", "short", "--cov", "./", "--cov-report", "term-missing:skip-covered"]

typing_checks/__init__.py

Whitespace-only changes.

typing_checks/decorators/__init__.py

Whitespace-only changes.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from typing import NamedTuple
2+
3+
from injection import asfunction
4+
5+
6+
class A: ...
7+
8+
9+
class B: ...
10+
11+
12+
# TODO: idk why mypy check fail here
13+
14+
15+
@asfunction
16+
class FunctionA(NamedTuple):
17+
a: A
18+
b: B
19+
20+
def __call__(self, foo: str) -> None: ...
21+
22+
23+
FunctionA("foo") # type: ignore[arg-type, call-arg]
24+
25+
26+
@asfunction()
27+
class FunctionB(NamedTuple):
28+
a: A
29+
b: B
30+
31+
def __call__(self, bar: str) -> None: ...
32+
33+
34+
FunctionB("bar") # type: ignore[arg-type, call-arg]

0 commit comments

Comments
 (0)