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
3 changes: 0 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ class Printer:
self.history.append(message)
print(message)


@injectable
class Service:
def __init__(self, printer: Printer):
Expand All @@ -46,12 +45,10 @@ class Service:
def hello(self):
self.printer.print("Hello world!")


@inject
def main(service: Service):
service.hello()


if __name__ == "__main__":
main()
```
Expand Down
15 changes: 15 additions & 0 deletions documentation/integrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,18 @@ app = Application(
services=InjectionServices(custom_module),
)
```

## [FastAPI](https://github.com/fastapi/fastapi)

Exemple:

```python
from fastapi import FastAPI
from injection.integrations.fastapi import Inject

app = FastAPI()

@app.get("/")
async def my_endpoint(service: MyService = Inject(MyService)):
...
```
14 changes: 11 additions & 3 deletions injection/_core/hook.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from collections.abc import Callable, Generator, Iterator
from dataclasses import dataclass, field
from inspect import isclass, isgeneratorfunction
from typing import Self
from typing import Any, Self

from injection.exceptions import HookError

Expand Down Expand Up @@ -48,11 +48,11 @@ def __apply_function(
handler: Callable[P, T],
function: HookFunction[P, T],
) -> Callable[P, T]:
if not isgeneratorfunction(function):
if not cls.__is_generator_function(function):
return function # type: ignore[return-value]

def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
hook = function(*args, **kwargs)
hook: HookGenerator[T] = function(*args, **kwargs) # type: ignore[assignment]

try:
next(hook)
Expand Down Expand Up @@ -87,6 +87,14 @@ def __apply_stack(

return handler

@staticmethod
def __is_generator_function(obj: Any) -> bool:
for o in obj, getattr(obj, "__call__", None):
if isgeneratorfunction(o):
return True

return False


def apply_hooks[**P, T](
handler: Callable[P, T],
Expand Down
11 changes: 11 additions & 0 deletions injection/integrations/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from importlib.util import find_spec
from typing import Literal

__all__ = ("_is_installed",)


def _is_installed(package: str, needed_for: object, /) -> Literal[True]:
if find_spec(package) is None:
raise RuntimeError(f"To use `{needed_for}`, {package} must be installed.")

return True
8 changes: 2 additions & 6 deletions injection/integrations/blacksheep.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
from typing import Any, override

from injection import Module, mod
from injection.integrations import _is_installed

__all__ = ("InjectionServices",)


try:
import blacksheep # noqa: F401
except ImportError as exc: # pragma: no cover
raise ImportError(f"To use `{__name__}`, blacksheep must be installed.") from exc
else:
if _is_installed("blacksheep", __name__):
from rodi import ContainerProtocol


Expand Down
37 changes: 37 additions & 0 deletions injection/integrations/fastapi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from collections.abc import Callable
from typing import Any

from injection import Module, mod
from injection.exceptions import InjectionError
from injection.integrations import _is_installed

__all__ = ("Inject",)

if _is_installed("fastapi", __name__):
from fastapi import Depends


def Inject[T](cls: type[T] | Any, /, module: Module | None = None) -> Any: # noqa: N802
"""
Declare a FastAPI dependency with `python-injection`.
"""

dependency: InjectionDependency[T] = InjectionDependency(cls, module or mod())
return Depends(dependency)


class InjectionDependency[T]:
__slots__ = ("__call__",)

__call__: Callable[[], T]

def __init__(self, cls: type[T] | Any, module: Module):
lazy_instance = module.get_lazy_instance(cls)
self.__call__ = lambda: self.__ensure(~lazy_instance, cls)

@staticmethod
def __ensure[_T](instance: _T | None, cls: type[_T] | Any) -> _T:
if instance is None:
raise InjectionError(f"`{cls}` is an unknown dependency.")

return instance
2 changes: 1 addition & 1 deletion injection/testing/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ test_constant = _.constant
test_injectable = _.injectable
test_singleton = _.singleton

def load_test_profile(*additional_names: str) -> ContextManager[None]:
def load_test_profile(*other_profile_names: str) -> ContextManager[None]:
"""
Context manager or decorator for temporary use test module.
"""
Loading