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
15 changes: 7 additions & 8 deletions documentation/integrations/unlisted-framework.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,13 @@ issues.

## If your framework inspects function signatures

If your framework inspects function signatures, things get a bit trickier. This is because you'll need to **perform
dependency injection first, then call the function**, which isn't always compatible with how decorators work on regular
functions.
If your framework inspects function signatures, things get a bit trickier. This is because dependencies can't be present
in function parameters.

To solve this, you can define a class with a `__call__` method (where dependencies are injected), and use the
`asfunction` decorator to turn it into a function.
To solve this, you can define a class with a `call` method (where dependencies are injected when the class is
instantiated), and use the `asfunction` decorator to turn it into a function.

The resulting function will have the same signature as the `__call__` method, but without the `self` parameter.
The resulting function will have the same signature as the `call` method, but without the `self` parameter.

Example:

Expand All @@ -26,10 +25,10 @@ from typing import NamedTuple
from injection import asfunction

@asfunction
class do_something(NamedTuple):
class DoSomething(NamedTuple):
service: MyService

def __call__(self):
def call(self):
self.service.do_work()
```

Expand Down
22 changes: 11 additions & 11 deletions injection/_core/asfunction.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,21 @@
from collections.abc import Callable
from functools import wraps
from inspect import iscoroutinefunction
from types import MethodType
from typing import Any, Protocol, runtime_checkable

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

type AsFunctionWrappedType[**P, T] = type[_Callable[P, T]]
type AsFunctionWrappedType[**P, T] = type[AsFunctionCallable[P, T]]


@runtime_checkable
class _Callable[**P, T](Protocol):
class AsFunctionCallable[**P, T](Protocol):
__slots__ = ()

@abstractmethod
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> T:
def call(self, *args: P.args, **kwargs: P.kwargs) -> T:
raise NotImplementedError


Expand All @@ -29,28 +30,27 @@ def asfunction[**P, T](
module = module or mod()

def decorator(wp: AsFunctionWrappedType[P, T]) -> Callable[P, T]:
get_method = wp.__call__.__get__
method = get_method(NotImplemented)
factory: Caller[..., Callable[P, T]] = module.make_injected_function(
fake_method = MethodType(wp.call, NotImplemented)
factory: Caller[..., AsFunctionCallable[P, T]] = module.make_injected_function(
wp,
threadsafe=threadsafe,
).__inject_metadata__

wrapper: Callable[P, T]

if iscoroutinefunction(method):
if iscoroutinefunction(fake_method):

@wraps(method)
@wraps(fake_method)
async def wrapper(*args: P.args, **kwargs: P.kwargs) -> Any:
self = await factory.acall()
return await get_method(self)(*args, **kwargs)
return await self.call(*args, **kwargs) # type: ignore[misc]

else:

@wraps(method)
@wraps(fake_method)
def wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
self = factory.call()
return get_method(self)(*args, **kwargs)
return self.call(*args, **kwargs)

wrapper.__name__ = wp.__name__
wrapper.__qualname__ = wp.__qualname__
Expand Down
4 changes: 2 additions & 2 deletions tests/core/test_asfunction.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class Dependency: ...
class SyncFunction(NamedTuple):
dependency: Dependency

def __call__(self):
def call(self):
return self.dependency

assert isinstance(SyncFunction(), Dependency)
Expand All @@ -28,7 +28,7 @@ async def dependency_recipe() -> Dependency:
class AsyncFunction(NamedTuple):
dependency: Dependency

async def __call__(self):
async def call(self):
return self.dependency

assert isinstance(await AsyncFunction(), Dependency)