Skip to content

Commit

Permalink
Dust off get_type_dependency
Browse files Browse the repository at this point in the history
  • Loading branch information
hypergonial committed Jan 26, 2024
1 parent 808cf03 commit 0f7f7f9
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 5 deletions.
33 changes: 29 additions & 4 deletions arc/abc/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@


T = t.TypeVar("T")
DefaultT = t.TypeVar("DefaultT")
P = t.ParamSpec("P")

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -1280,20 +1281,44 @@ async def cmd(ctx: arc.GatewayContext, dep: MyDependency = arc.inject()) -> None
self._injector.set_type_dependency(type_, instance)
return self

def get_type_dependency(self, type_: type[T]) -> T | hikari.UndefinedType:
@t.overload
def get_type_dependency(self, type_: type[T]) -> T:
...

@t.overload
def get_type_dependency(self, type_: type[T], *, default: DefaultT) -> T | DefaultT:
...

def get_type_dependency(
self, type_: type[T], *, default: DefaultT | hikari.UndefinedType = hikari.UNDEFINED
) -> T | DefaultT:
"""Get a type dependency for this client.
Parameters
----------
type_ : type[T]
The type of the dependency.
default : DefaultT
The default value to return if the dependency does not exist.
If not provided, this will raise an exception if the dependency does not exist.
Returns
-------
T | hikari.UndefinedType
The instance of the dependency, if it exists.
T | DefaultT
The instance of the dependency, if it exists, otherwise `default`.
Raises
------
KeyError
If the dependency does not exist and `default` was not provided.
"""
return self._injector.get_type_dependency(type_, default=hikari.UNDEFINED)
if default is hikari.UNDEFINED:
value = self._injector.get_type_dependency(type_)
if isinstance(value, alluka.abc.Undefined):
raise KeyError(f"Could not resolve dependency of type {type_}.")
return value
else:
return self._injector.get_type_dependency(type_, default=default)

@t.overload
def inject_dependencies(self, func: t.Callable[P, T]) -> t.Callable[P, T]:
Expand Down
21 changes: 20 additions & 1 deletion docs/guides/dependency_injection.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,26 @@ If you're trying to inject a function that already has decorators on it, the [`@

This means you can inject dependencies into [hooks](./hooks.md), [error handlers](./error_handling.md), [loops](./loops.md), or literally any ordinary Python function. The sky is the limit!

## The benefits of dependency injection
### Getting dependencies without injection

In some cases it may not be convenient to use [`@Client.inject_dependencies`][arc.abc.client.Client.inject_dependencies], so for this reason, the client exposes a lower-level method of getting the dependencies directly, in the form of [`Client.get_type_dependency`][arc.abc.client.Client.get_type_dependency].

This method takes the type of the dependency, and an optional default as parameters, and returns the dependency, if one exists:

```py
def compare_counter(value: int) -> None:
db = client.get_type_dependency(MyDatabase)

if value > db.value:
print("Value is bigger!")
else:
print("Counter is bigger or equal!")
```

!!! note
This function practically serves the exact same purpose as previous snippet, with the difference that a `db` cannot be passed to the function to override the injected default, reducing it's flexibility.

## Why dependency injection?

Dependency injection **separates the concern** of constructing an object from using them, therefore it is possible to **loosely couple** the logic and state of your program. One benefit of this approach is that you can separate the actual implementations from the abstract types that functions may consume.

Expand Down

0 comments on commit 0f7f7f9

Please sign in to comment.