Dependency Injection¶
Dependency Injection (DI) is a design pattern that helps write loosely coupled, easily testable, and extensible code. Instead of handlers creating the objects (dependencies) they need themselves, they receive them from outside.
Argenta uses the dishka library to implement DI, which allows you to declaratively declare dependencies directly in your handler signatures. You can read more about DI, IoC, and the API for creating providers in the official dishka documentation.
Main Idea¶
Imagine your handler needs access to a database to work. Instead of importing and initializing the connection inside the function, you simply declare it as an argument with a type annotation:
Note
argenta.di.FromDishka is an alias for dishka.FromDishka, and they are fully interchangeable.
Usage example:
1from sqlite3 import Connection
2from argenta import Response, Router
3from argenta.di import FromDishka
4
5router = Router()
6
7@router.command("connect")
8def connect_handler(response: Response, connection: FromDishka[Connection]):
9 connection.execute("...")
Argenta with dishka will resolve the dependency by type Connection and inject it. But before using the dependency, it must be declared in a provider:
Usage example:
1import sqlite3
2from sqlite3 import Connection
3from typing import Iterable
4
5from dishka import Provider, Scope, provide
6
7
8class ConnectionProvider(Provider):
9 @provide(scope=Scope.REQUEST)
10 def new_connection(self) -> Iterable[Connection]:
11 conn = sqlite3.connect(":memory:")
12 yield conn
13 conn.close()
After creating the provider, it must be registered in the orchestrator.
Note
Providers are registered in Orchestrator, not in App, because the orchestrator is responsible for configuring the DI container at the application level. You can pass a list of multiple providers through the custom_providers parameter.
Usage example:
1from argenta import Orchestrator
2
3orchestrator = Orchestrator(custom_providers=[ConnectionProvider()])
How Does It Work?¶
At the implementations of DI in Argenta are providers and a container.
Provider (Provider) is a “recipe” that explains how to create and configure a particular dependency (for example, a database connection, API client, or any other service).
Container (IoC Container) is a “factory” that stores all recipes (providers) and creates and provides ready dependencies on request.
Built-in Providers¶
Argenta comes with a built-in provider that gives access to important system dependencies without additional configuration. For example, you can get the ArgSpace object, which contains the command-line arguments passed when the application was launched.
Usage example:
1from argenta import Response, Router
2from argenta.di import FromDishka
3from argenta.orchestrator.argparser import ArgSpace
4
5router = Router()
6
7@router.command("info")
8def connect_handler(response: Response, argspace: FromDishka[ArgSpace]):
9 print(argspace.get_by_name("type"))
Data Exchange Between Handlers¶
In addition to DI, handlers can exchange data within a session through a context object. In Argenta, this role is performed by the DataBridge object.
Each handler can write data to it, as well as read, update, and delete data.
See also
You can read more about this in the DataBridge section.