Actor¶
The @actor decorator, ActorRef, and related types.
actor ¶
Typed actor decorator and registration handle.
The :func:actor decorator is the user-facing entry point for
registering an async handler with the worker. It introspects the
handler's signature, validates it, and returns an :class:ActorRef
parameterized on the handler's payload (P) and return (R)
types.
Handler signatures follow the FastAPI principle — declare what you need. The decorator accepts any of these shapes:
async def fn(payload: P) -> R— payload only.def fn(payload: P) -> R— payload only (sync).async def fn(payload: P, ctx: JobContext[P]) -> R— payload + context.async def fn(payload: P, *, db: DbSession, http: HttpClient) -> R— payload + DI deps.async def fn(payload: P, ctx: JobContext[P], *, db: DbSession) -> R— all three.
The first parameter is always the validated payload (P: BaseModel).
The optional second positional parameter is the typed
:class:JobContext. Any further parameters are dependency-injection
requests: their names and annotations are captured on
:attr:ActorRef.dependencies and resolved by the worker's DI pass at
dispatch time. Whether deps arrive as keyword-only (*, db: ...) or
positional is up to the handler — the dispatcher always passes them
as keyword arguments.
Sync functions (plain def) are accepted and dispatched to
:func:asyncio.to_thread, freeing the event loop for other work.
Sync actors must cooperate with cancellation by polling
:meth:JobContext.should_abort. LOOP-scoped DI dependencies (e.g.
asyncpg.Connection) are not thread-safe and should not be used
from sync actors.
The ref carries a :class:pydantic.TypeAdapter for R so the
:class:~taskq.client._handle.JobHandle can round-trip the actor's
return value through the JSONB result column without losing the
type parameter.
Decoration-time validation is strict: the decorator validates annotations
on payload, ctx, and DI parameters. Sync (def) and async (async def)
handlers are both accepted.
__all__
module-attribute
¶
ActorFn ¶
Actor handler that takes only a payload.
The dispatcher injects nothing beyond the validated payload model. Use this shape for actors that don't need cancellation cooperation, attempt counters, or other context fields.
ActorFnWithCtx ¶
Actor handler that takes a payload and a typed :class:JobContext.
Declare ctx: JobContext[YourPayload] as the second parameter to
opt into context injection. The dispatcher constructs the context per
attempt, populates it with the validated payload and a fresh
:class:asyncio.Event for cooperative cancellation, then passes it
to the handler. Handlers that don't declare ctx skip this work.
ActorHandler ¶
Bases: Protocol
Most-general actor signature: payload first, then ctx and/or DI deps.
Pyright infers P_ from the first positional parameter (the
payload model) and R_ from the awaited return type, regardless
of how many additional ctx / DI parameters the handler
declares. This lets the :func:actor decorator preserve full
payload-and-result inference for FastAPI-style handlers like::
async def my_actor(
payload: OrderPayload,
ctx: JobContext[OrderPayload],
*,
db: DbSession,
http: HttpClient,
) -> OrderResult: ...
Without ActorHandler, callers would have to choose between
:data:ActorFn (payload only) and :data:ActorFnWithCtx (payload
+ ctx), neither of which describes the DI case.
ActorRef ¶
ActorRef(
*,
name: str,
queue: str,
fn: Callable[..., object],
is_sync: bool = False,
wants_ctx: bool,
dependencies: dict[str, type[object]],
payload_type: type[P],
result_adapter: TypeAdapter[R],
retry: RetryPolicy,
result_ttl: timedelta | None,
singleton: bool = False,
max_concurrent: int | None = None,
max_pending: int | None = None,
metadata: dict[str, object] | None = None,
unique_for: timedelta | None = None,
unique_states: tuple[JobStatus, ...] = (
"pending",
"scheduled",
"running",
),
start_to_close: timedelta | None = None,
rate_limits: list[str] | None = None,
reservations: list[str | KeyedReservationRef]
| None = None,
non_retryable_exceptions: tuple[
type[BaseException], ...
] = (),
retry_classifier: RetryClassifierHook | None = None,
on_retry_exhausted: OnRetryExhausted | None = None,
on_retry_exhausted_timeout: float = 3.0,
on_success: OnSuccess | None = None,
on_success_timeout: float = 3.0,
priority: int = 0,
)
Typed reference to a registered actor.
Created by the :func:actor decorator. Not callable directly via
the queue path — enqueue jobs by passing this ref to
:meth:JobsClient.enqueue(ref, payload) <taskq.client.JobsClient.enqueue>.
Direct in-process invocation (await my_actor(payload, ...)) is
available for tests and simulators.
The two type parameters carry the actor's payload and result types end-to-end:
payload_typere-validates raw dispatch-time payloads back toP(via :meth:pydantic.BaseModel.model_validate).result_adapterround-trips actor returns through the JSONBresultcolumn (dump_python(mode="json")on the worker side,validate_pythonon the client side).
Attributes:
| Name | Type | Description |
|---|---|---|
max_concurrent |
Fleet-wide concurrency cap for this actor.
max_concurrent may transiently exceed configured value by up to (num_active_producers - 1) * max_concurrent per actor under heavy contention (or (num_producers - 1) * limit_n when limit_n < max_concurrent). For strict correctness, use ConcurrencyReservation. |
|
metadata |
Arbitrary key-value metadata stored in
|
Both are stored as instance fields rather than class metadata so
pyright can infer P and R from a constructor call without
relying on phantom-type tricks.
wants_ctx records whether the handler declared a
:class:JobContext parameter; the dispatcher passes ctx only
when set. dependencies maps each DI parameter name to its
annotated type — the worker's DI pass resolves these at dispatch
time and passes them as keyword arguments to the handler. The DI
resolver itself is an erasure boundary (see
the resolver operates on the registered provider graph at runtime): user-declared annotations on
DI parameters are captured here as type[object] because the
resolver operates on the registered provider graph at runtime.
Source code in src/taskq/actor.py
__slots__
class-attribute
instance-attribute
¶
__slots__ = (
"_fn",
"dependencies",
"is_sync",
"max_concurrent",
"max_pending",
"metadata",
"name",
"non_retryable_exceptions",
"on_retry_exhausted",
"on_retry_exhausted_timeout",
"on_success",
"on_success_timeout",
"payload_type",
"priority",
"queue",
"rate_limits",
"reservations",
"result_adapter",
"result_ttl",
"retry",
"retry_classifier",
"singleton",
"start_to_close",
"unique_for",
"unique_states",
"wants_ctx",
)
on_retry_exhausted_timeout
instance-attribute
¶
fn
property
¶
Return the underlying handler.
May be sync (def) or async (async def). Direct
invocation through :meth:__call__ is the safer path because
it handles both shapes and enforces the declared signature.
__call__
async
¶
Direct invocation — bypasses enqueue, runs the handler in-process.
Pass a :class:JobContext only when the registered handler
declared one (when :attr:wants_ctx is True); calling with
a context against a no-ctx handler raises :class:TypeError,
and calling without a context against a ctx handler also
raises :class:TypeError.
deps mirrors the keyword arguments the worker's
dependency-injection pass supplies in production. Tests may
pass them explicitly; the call site is responsible for
matching the names recorded in :attr:dependencies. Missing
dependencies surface as a runtime TypeError from Python's
argument binding.
Production callers go through :meth:JobsClient.enqueue.
Source code in src/taskq/actor.py
actor ¶
actor(
*,
name: str | None = None,
queue: str = "default",
retry: RetryPolicy | None = None,
result_ttl: timedelta | None = None,
singleton: bool = False,
max_concurrent: int | None = None,
max_pending: int | None = None,
metadata: dict[str, object] | None = None,
unique_for: timedelta | None = None,
unique_states: tuple[JobStatus, ...] = (
"pending",
"scheduled",
"running",
),
start_to_close: timedelta | None = None,
rate_limits: list[str] | None = None,
reservations: list[str | KeyedReservationRef]
| None = None,
non_retryable_exceptions: tuple[
type[BaseException], ...
] = (),
retry_classifier: RetryClassifierHook | None = None,
on_retry_exhausted: OnRetryExhausted | None = None,
on_retry_exhausted_timeout: float = 3.0,
on_success: OnSuccess | None = None,
on_success_timeout: float = 3.0,
priority: int = 0,
) -> Callable[[Callable[..., object]], ActorRef[P, R]]
actor(
fn: Callable[..., object] | None = None,
/,
*,
name: str | None = None,
queue: str = "default",
retry: RetryPolicy | None = None,
result_ttl: timedelta | None = None,
singleton: bool = False,
max_concurrent: int | None = None,
max_pending: int | None = None,
metadata: dict[str, object] | None = None,
unique_for: timedelta | None = None,
unique_states: tuple[JobStatus, ...] = (
"pending",
"scheduled",
"running",
),
start_to_close: timedelta | None = None,
rate_limits: list[str] | None = None,
reservations: list[str | KeyedReservationRef]
| None = None,
non_retryable_exceptions: tuple[
type[BaseException], ...
] = (),
retry_classifier: RetryClassifierHook | None = None,
on_retry_exhausted: OnRetryExhausted | None = None,
on_retry_exhausted_timeout: float = 3.0,
on_success: OnSuccess | None = None,
on_success_timeout: float = 3.0,
priority: int = 0,
) -> (
ActorRef[P, R]
| Callable[[ActorHandler[P, R]], ActorRef[P, R]]
)
Register an async handler as a typed :class:ActorRef.
All shapes are supported — declare what you need::
# Payload only.
@actor
async def my_actor(payload: MyPayload) -> MyResult:
...
# Payload + typed context.
@actor
async def my_actor(payload: MyPayload, ctx: JobContext[MyPayload]) -> MyResult:
...
# Payload + DI deps (FastAPI-style).
@actor
async def my_actor(
payload: MyPayload,
*,
db: DbSession,
http: HttpClient,
) -> MyResult:
...
# Payload + ctx + DI deps.
@actor(queue="priority")
async def my_actor(
payload: MyPayload,
ctx: JobContext[MyPayload],
*,
db: DbSession,
) -> MyResult:
...
Pyright infers P from the first positional parameter and R
from the return annotation, then propagates them through
ActorRef[P, R] to :meth:JobsClient.enqueue and
:class:JobHandle[R].
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
max_concurrent
|
int | None
|
Fleet-wide concurrency cap for this actor.
max_concurrent may transiently exceed configured value by up to (num_active_producers - 1) * max_concurrent per actor under heavy contention (or (num_producers - 1) * limit_n when limit_n < max_concurrent). For strict correctness, use ConcurrencyReservation. |
None
|
max_pending
|
int | None
|
Queue-depth backpressure cap for this actor.
|
None
|
singleton
|
bool
|
Enforce at most one active job of this actor across the
fleet ( |
False
|
metadata
|
dict[str, object] | None
|
Arbitrary key-value metadata stored in
|
None
|
unique_states
|
tuple[JobStatus, ...]
|
The set of job statuses to consider "active" for
|
('pending', 'scheduled', 'running')
|
Source code in src/taskq/actor.py
356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 | |