You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
KeyedRateLimitRef.key_fn is typed Callable[[dict[str, object]], str], and the dict it receives is the raw job-row payload, not the actor's validated payload model. In the consumer, the limiter is resolved with payload=job.payload — the JSON straight off the row — and the payload is validated against payload_type further down the same dispatch, after admission control has already run.
For a library this strict about typing everywhere else, that's a surprising seam, and it has consequences beyond ergonomics:
Pydantic defaults don't apply. A key field with a default that wasn't present in the serialized payload is simply missing from the dict, even though the attribute is populated on the model the handler receives. key_fn and the handler then disagree about the same field.
Aliases don't apply. With Field(alias="providerId"), the model exposes provider_id while the raw dict has providerId. key_fn has to know the wire name, so the routing key is coupled to serialization rather than to the model.
Failures surface in the wrong layer. If the field is absent or the wrong shape, the KeyError/TypeError is raised inside rate-limit resolution rather than as a payload-validation error, so an invalid payload looks like a limiter fault.
No type checking at the point it matters most.payload["provider"] is exactly the kind of expression a rename should break at check time and doesn't.
Today I keep the keyed field mandatory, never aliased, never defaulted, and always a plain scalar — an invariant that nothing enforces and that a future model refactor will quietly violate.
What I'd rather have is key_fn receiving the validated model:
The validated payload is constructed on the same dispatch anyway, so the data is available; the question is ordering, not availability. (Same argument applies to KeyedReservationRef.key_fn, which shares the signature.)
Open questions:
Is limits-before-validation deliberate — don't pay model_validate for a job you might not admit, or don't let a malformed payload consume a token? Both are defensible, and if that's the intent then moving validation earlier is the wrong fix.
If the ordering has to stay, would a typed opt-in work — the ref declaring the payload type so key_fn gets a validated model, falling back to the raw dict otherwise? That keeps the fast path and gives correctness to callers who want it.
KeyedRateLimitRef.key_fnis typedCallable[[dict[str, object]], str], and the dict it receives is the raw job-row payload, not the actor's validated payload model. In the consumer, the limiter is resolved withpayload=job.payload— the JSON straight off the row — and the payload is validated againstpayload_typefurther down the same dispatch, after admission control has already run.For a library this strict about typing everywhere else, that's a surprising seam, and it has consequences beyond ergonomics:
key_fnand the handler then disagree about the same field.Field(alias="providerId"), the model exposesprovider_idwhile the raw dict hasproviderId.key_fnhas to know the wire name, so the routing key is coupled to serialization rather than to the model.KeyError/TypeErroris raised inside rate-limit resolution rather than as a payload-validation error, so an invalid payload looks like a limiter fault.payload["provider"]is exactly the kind of expression a rename should break at check time and doesn't.Today I keep the keyed field mandatory, never aliased, never defaulted, and always a plain scalar — an invariant that nothing enforces and that a future model refactor will quietly violate.
What I'd rather have is
key_fnreceiving the validated model:The validated payload is constructed on the same dispatch anyway, so the data is available; the question is ordering, not availability. (Same argument applies to
KeyedReservationRef.key_fn, which shares the signature.)Open questions:
model_validatefor a job you might not admit, or don't let a malformed payload consume a token? Both are defensible, and if that's the intent then moving validation earlier is the wrong fix.key_fngets a validated model, falling back to the raw dict otherwise? That keeps the fast path and gives correctness to callers who want it.key_fnsees the pre-validation wire form, and that defaults and aliases therefore do not apply, would at least make the trap visible. Keyed rate-limit follow-ups: accept str subclasses from key_fn; surface keyed limits in the admin UI #32 tightened the return side ofkey_fn; this is the argument side.