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
34 changes: 10 additions & 24 deletions docs/adr/0001-no-connection-provider-for-a-task.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,12 @@
# No connection provider for a task invocation

**Decision:** the per-task child container is built with no context, and this integration registers
no connection provider.

`modern-di` defines a connection as the framework object a unit of work carries — the HTTP request
in the web integrations, the incoming message in the broker ones. Those adapters seed it as the
child container's context so a provider can depend on it, and the question asked of every new
integration is which object plays that role. It is asked here because Celery clearly *has*
per-invocation state: a task id, a retry count, message headers.

That state lives on the `Task` instance, one layer above the seam. `inject` wraps the plain task
function, and `@app.task` sits outside `@inject`, so when the wrapper runs it holds the caller's
arguments and nothing else. Reaching the `Task` would mean either requiring `bind=True` on every
injected task or reading Celery's thread-local current task inside the wrapper — coupling this
package to task-instance internals to supply a context object that no provider it exists to serve
has needed. `modern-di-typer` declined the same thing for the same reason: a command, like a task,
is a plain call.

So the unit of work is the task invocation itself: one `Scope.REQUEST` child per call, built with no
context, closed when the call returns or raises. A dependency that needs task metadata takes it as
an ordinary task parameter, passed by the caller.

**Revisit trigger:** a provider that genuinely needs per-invocation Celery state rather than the
arguments the caller passed. At that point the context object has a concrete consumer, and the
coupling it costs is worth paying.
`modern-di` seeds a child container with the framework object a unit of work carries, the HTTP
request in the web integrations, the incoming message in the broker ones, so every integration must
name that object. Celery's per-invocation state, the task id, retry count and message headers, lives
on the `Task` instance, one layer above the seam: `inject` wraps the plain task function and
`@app.task` sits outside it, so the wrapper sees only the caller's arguments. Reaching the `Task`
would mean forcing `bind=True` on every injected task or reading Celery's thread-local current task,
coupling this package to task internals for a context object no provider needs; `modern-di-typer`
declined the same for a command. The unit of work is therefore the invocation: one `Scope.REQUEST`
child per call, built with no context, and a dependency needing task metadata takes it as an ordinary
task parameter.
24 changes: 0 additions & 24 deletions docs/adr/0002-no-functools-wraps-on-the-inject-wrapper.md

This file was deleted.

13 changes: 13 additions & 0 deletions docs/adr/0002-reject-variadics-alongside-fromdi.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# `inject` rejects `*args`/`**kwargs` alongside a `FromDI` parameter

Decorating a function that declares a variadic parameter *and* at least one `FromDI` parameter
raises `TypeError` at decoration time; a function with no `FromDI` parameter is returned unchanged
and may use variadics freely. The wrapper binds the caller's arguments to the visible signature and
calls by name, `func(**bound.arguments, **resolved)`, which is what makes injection insensitive to
where a `FromDI` parameter sits. By-name calling cannot forward a variadic: `Signature.bind` stores
the payload under the literal parameter names, so a task called with three positional arguments
receives one tuple named `args`. Forwarding positionally would surrender order insensitivity for
every task, and special-casing the two names misroutes again once a task declares a real parameter
called `args`. For the same reason the wrapper sets `__name__`, `__qualname__`, `__doc__` and
`__module__` by hand rather than using `functools.wraps`, whose `__wrapped__` points
`inspect.signature` back at the un-rewritten signature.
13 changes: 13 additions & 0 deletions docs/adr/0003-connect-both-worker-signal-pairs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# `setup_di` connects both worker signal pairs

`setup_di` connects the same open/close closures to `worker_process_init`/`worker_process_shutdown`
*and* to `worker_init`/`worker_shutdown`, four connections for two operations, because neither pair
alone covers every pool. The process pair fires only under prefork and solo, once per forked child,
keeping cached resources and finalizers fork-safe; the worker pair fires once in the main process and
is the only pair the gevent, eventlet and threads pools send, since those never fork. Connecting only
the process pair is the bug 3.0.1 fixed: under `modern-di` 3.x's mandatory-open lifecycle the root
container stayed closed under the non-forking pools and every `@inject` task raised. The overlap is
harmless: `open()` is a no-op on an open container, `close_sync()` when nothing was cached.
`weak=False` belongs to the same decision: Celery holds receivers weakly by default and these
closures have no other strong reference, so a weak connection lets them be collected and the handlers
never run.
30 changes: 0 additions & 30 deletions docs/adr/0003-reject-variadics-alongside-fromdi.md

This file was deleted.

30 changes: 0 additions & 30 deletions docs/adr/0004-connect-both-worker-signal-pairs.md

This file was deleted.

2 changes: 1 addition & 1 deletion docs/agents/domain.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Single-context repo:
├── CONTEXT.md
├── docs/adr/
│ ├── 0001-no-connection-provider-for-a-task.md
│ └── 0002-no-functools-wraps-on-the-inject-wrapper.md
│ └── 0002-reject-variadics-alongside-fromdi.md
└── modern_di_celery/
```

Expand Down
Loading