Depends¶
QTasks supports managed dependencies for tasks through the Depends mechanism.
It allows you to declare task parameters so that their values are automatically
created and correctly cleared in the appropriate context.
Example of using Depends¶
from typing import Annotated
from contextlib import asynccontextmanager
from qtasks.plugins.depends import Depends, ScopeEnum
@asynccontextmanager
async def test_dep():
print("Open")
yield 123
print("Close")
@app.task
async def test(dep: Annotated[int, Depends(test_dep, scope=ScopeEnum.TASK)]):
print(dep)
When the task is executed, the output will be as follows:
Open
123
Close
What happens:
- When the task starts, the
test_dep()context opens. - The value
123is passed to the task body. - When the task finishes, the context closes and
Closeis printed.
How Depends works "under the hood"¶
The Depends mechanism is implemented in a built-in worker plugin — (A)syncDependsPlugin.
Below is a simplified description of the steps.
1. Searching for Depends in task parameters¶
When preparing to execute a task, the plugin:
- checks for the presence of
Dependsas an explicit parameter value (dep = Depends(...)); - or searches for it inside
Annotated[..., Depends(...)](takes the last element ofAnnotated).
If Depends is found, the task is marked as having a dependency.
2. Creating a context via (Async)ExitStack¶
AsyncExitStack (or ExitStack for synchronous tasks) is used to manage the
dependency lifecycle:
- The plugin enters the
test_dep()context via the context manager stack. - The value obtained from
yield(in the example —123) is saved as a parameter value.
3. Processing the scope (lifetime of the dependency)¶
The dependency lives in a specific "scope" defined by ScopeEnum:
class ScopeEnum(Enum):
TASK = "task"
WORKER = "worker"
BROKER = "broker"
STORAGE = "storage"
GLOBAL_CONFIG = "global_config"
Logic:
TASK— the context opens when the task starts and closes when it finishes.- The other options (
WORKER,BROKER,STORAGE,GLOBAL_CONFIG) link the context to the component resource.
Technically, this is implemented through triggers:
TASK— closes on thetask_executor_task_closetrigger;- the rest — on component completion triggers:
"{component}_stop".
4. Task argument substitution¶
After receiving a value from the dependency function, the plugin:
- replaces the task parameter (
dep) with the result oftest_dep(); - the task receives a ready-made value (for example, an open connection, a session object, a number, a config, etc.).
Why Depends is needed¶
Depends solves the problem of managed dependencies:
- creating and closing resources in the right context (at the task or component level);
- no need to manually work with context managers inside the body of tasks;
- a single point of description of dependencies for different tasks.
This is a convenient way to embed a connection to a database, an external service client, a cache, a config, and other resources into QTasks tasks so that their lifecycle is controlled by the framework.