Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/actions/code-style/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,7 @@ runs:
- name: MyPy
shell: bash
run: uv run mypy ./

- name: Pyright
shell: bash
run: uv run pyright
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
before_commit: lint mypy pytest
before_commit: lint mypy pyright pytest

install:
uv sync
Expand All @@ -14,5 +14,8 @@ lint:
mypy:
uv run mypy ./

pyright:
uv run pyright

pytest:
uv run pytest
173 changes: 29 additions & 144 deletions injection/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -151,13 +151,13 @@ class Module:
@property
def is_locked(self) -> bool: ...
@overload
def inject[**P, T](
def inject[T](
self,
wrapped: Callable[P, T],
wrapped: T,
/,
*,
threadsafe: bool | None = ...,
) -> Callable[P, T]:
) -> T:
"""
Decorator applicable to a class or function. Inject function dependencies using
parameter type annotations. If applied to a class, the dependencies resolved
Expand All @@ -166,72 +166,31 @@ class Module:
With `threadsafe=True`, the injection logic is wrapped in a `threading.RLock`.
"""

@overload
def inject[T](
self,
wrapped: type[T],
/,
*,
threadsafe: bool | None = ...,
) -> type[T]: ...
@overload
def inject(
self,
wrapped: None = ...,
/,
*,
threadsafe: bool | None = ...,
) -> _Decorator[Callable[..., Any] | type]: ...
) -> _Decorator: ... # type: ignore[type-arg]
@overload
def injectable[**P, T](
def injectable[T](
self,
wrapped: Callable[P, T],
wrapped: T,
/,
*,
cls: _InjectableFactory[T] = ...,
cls: _InjectableFactory[Any] = ...,
inject: bool = ...,
on: _TypeInfo[T] = ...,
on: _TypeInfo[Any] = ...,
mode: Mode | ModeStr = ...,
) -> Callable[P, T]:
) -> T:
"""
Decorator applicable to a class or function. It is used to indicate how the
injectable will be constructed. At injection time, a new instance will be
injected each time.
"""

@overload
def injectable[**P, T]( # type: ignore[overload-overlap]
self,
wrapped: Callable[P, Awaitable[T]],
/,
*,
cls: _InjectableFactory[T] = ...,
inject: bool = ...,
on: _TypeInfo[T] = ...,
mode: Mode | ModeStr = ...,
) -> Callable[P, Awaitable[T]]: ...
@overload
def injectable[T](
self,
wrapped: type[T],
/,
*,
cls: _InjectableFactory[T] = ...,
inject: bool = ...,
on: _TypeInfo[T] = ...,
mode: Mode | ModeStr = ...,
) -> type[T]: ...
@overload
def injectable[T](
self,
wrapped: None = ...,
/,
*,
cls: _InjectableFactory[T] = ...,
inject: bool = ...,
on: _TypeInfo[T],
mode: Mode | ModeStr = ...,
) -> _Decorator[Callable[..., T] | Callable[..., Awaitable[T]] | type[T]]: ...
@overload
def injectable(
self,
Expand All @@ -240,99 +199,52 @@ class Module:
*,
cls: _InjectableFactory[Any] = ...,
inject: bool = ...,
on: tuple[()] = ...,
on: _TypeInfo[Any] = ...,
mode: Mode | ModeStr = ...,
) -> _Decorator[Callable[..., Any] | type]: ...
) -> _Decorator: ... # type: ignore[type-arg]
@overload
def singleton[**P, T](
def singleton[T](
self,
wrapped: Callable[P, T],
wrapped: T,
/,
*,
inject: bool = ...,
on: _TypeInfo[T] = ...,
on: _TypeInfo[Any] = ...,
mode: Mode | ModeStr = ...,
) -> Callable[P, T]:
) -> T:
"""
Decorator applicable to a class or function. It is used to indicate how the
singleton will be constructed. At injection time, the injected instance will
always be the same.
"""

@overload
def singleton[**P, T]( # type: ignore[overload-overlap]
self,
wrapped: Callable[P, Awaitable[T]],
/,
*,
inject: bool = ...,
on: _TypeInfo[T] = ...,
mode: Mode | ModeStr = ...,
) -> Callable[P, Awaitable[T]]: ...
@overload
def singleton[T](
self,
wrapped: type[T],
/,
*,
inject: bool = ...,
on: _TypeInfo[T] = ...,
mode: Mode | ModeStr = ...,
) -> type[T]: ...
@overload
def singleton[T](
self,
wrapped: None = ...,
/,
*,
inject: bool = ...,
on: _TypeInfo[T],
mode: Mode | ModeStr = ...,
) -> _Decorator[Callable[..., T] | Callable[..., Awaitable[T]] | type[T]]: ...
@overload
def singleton(
self,
wrapped: None = ...,
/,
*,
inject: bool = ...,
on: tuple[()] = ...,
on: _TypeInfo[Any] = ...,
mode: Mode | ModeStr = ...,
) -> _Decorator[Callable[..., Any] | type]: ...
@overload
def scoped[T](
) -> _Decorator: ... # type: ignore[type-arg]
def scoped(
self,
scope_name: str,
/,
*,
inject: bool = ...,
on: _TypeInfo[T],
on: _TypeInfo[Any] = ...,
mode: Mode | ModeStr = ...,
) -> _Decorator[
Callable[..., T]
| Callable[..., Awaitable[T]]
| Callable[..., AsyncIterator[T]]
| Callable[..., Iterator[T]]
| type[T]
]:
) -> _Decorator: # type: ignore[type-arg]
"""
Decorator applicable to a class or function or generator function. It is used
to indicate how the scoped instance will be constructed. At injection time, the
injected instance is retrieved from the scope.
"""

@overload
def scoped(
self,
scope_name: str,
/,
*,
inject: bool = ...,
on: tuple[()] = ...,
mode: Mode | ModeStr = ...,
) -> _Decorator[Callable[..., Any] | type]: ...
@overload
def should_be_injectable[T](self, wrapped: type[T], /) -> type[T]:
def should_be_injectable[T](self, wrapped: T, /) -> T:
"""
Decorator applicable to a class. It is used to specify whether an injectable
should be registered. Raise an exception at injection time if the class isn't
Expand All @@ -344,62 +256,35 @@ class Module:
self,
wrapped: None = ...,
/,
) -> _Decorator[type]: ...
) -> _Decorator: ... # type: ignore[type-arg]
@overload
def constant[**P, T](
def constant[T](
self,
wrapped: Callable[P, T],
wrapped: T,
/,
*,
on: _TypeInfo[T] = ...,
on: _TypeInfo[Any] = ...,
mode: Mode | ModeStr = ...,
) -> Callable[P, T]:
) -> T:
"""
Decorator applicable to a class or function. It is used to indicate how the
constant is constructed. At injection time, the injected instance will always
be the same. Unlike `@singleton`, dependencies will not be resolved.
"""

@overload
def constant[**P, T]( # type: ignore[overload-overlap]
self,
wrapped: Callable[P, Awaitable[T]],
/,
*,
on: _TypeInfo[T] = ...,
mode: Mode | ModeStr = ...,
) -> Callable[P, Awaitable[T]]: ...
@overload
def constant[T](
self,
wrapped: type[T],
/,
*,
on: _TypeInfo[T] = ...,
mode: Mode | ModeStr = ...,
) -> type[T]: ...
@overload
def constant[T](
self,
wrapped: None = ...,
/,
*,
on: _TypeInfo[T],
mode: Mode | ModeStr = ...,
) -> _Decorator[Callable[..., T] | Callable[..., Awaitable[T]] | type[T]]: ...
@overload
def constant(
self,
wrapped: None = ...,
/,
*,
on: tuple[()] = ...,
on: _TypeInfo[Any] = ...,
mode: Mode | ModeStr = ...,
) -> _Decorator[Callable[..., Any] | type]: ...
) -> _Decorator: ... # type: ignore[type-arg]
def set_constant[T](
self,
instance: T,
on: _TypeInfo[T] = ...,
on: _TypeInfo[Any] = ...,
*,
alias: bool = ...,
mode: Mode | ModeStr = ...,
Expand Down
9 changes: 7 additions & 2 deletions injection/_core/asfunction.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
from collections.abc import Callable
from functools import wraps
from inspect import iscoroutinefunction
from typing import Any
from typing import Any, Protocol

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

type AsFunctionWrappedType[**P, T] = type[Callable[P, T]]

class _AsFunctionCallable[**P, T](Protocol):
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T: ...


type AsFunctionWrappedType[**P, T] = type[_AsFunctionCallable[P, T]]


def asfunction[**P, T](
Expand Down
6 changes: 5 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ bench = [
dev = [
"hatch",
"mypy",
"pyright",
"ruff",
]
doc = [
Expand Down Expand Up @@ -76,7 +77,7 @@ exclude_lines = [
]

[tool.coverage.run]
omit = ["bench.py"]
omit = ["bench.py", "typing_checks/*"]

[tool.hatch.build]
skip-excluded-dirs = true
Expand Down Expand Up @@ -108,6 +109,9 @@ init_forbid_extra = true
init_typed = true
warn_required_dynamic_aliases = true

[tool.pyright]
include = ["typing_checks/"]

[tool.pytest]
python_files = ["test_*.py"]
addopts = ["--tb", "short", "--cov", "./", "--cov-report", "term-missing:skip-covered"]
Expand Down
Empty file added typing_checks/__init__.py
Empty file.
Empty file.
34 changes: 34 additions & 0 deletions typing_checks/decorators/asfunction.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from typing import NamedTuple

from injection import asfunction


class A: ...


class B: ...


# TODO: idk why mypy check fail here


@asfunction
class FunctionA(NamedTuple):
a: A
b: B

def __call__(self, foo: str) -> None: ...


FunctionA("foo") # type: ignore[arg-type, call-arg]


@asfunction()
class FunctionB(NamedTuple):
a: A
b: B

def __call__(self, bar: str) -> None: ...


FunctionB("bar") # type: ignore[arg-type, call-arg]
Loading