The `asyncio.iscoroutinefunction` call is deprecated in Python 3.14
and slated for removal in 3.16. Since `is_coroutine_function` already
unwraps `functools.partial` layers before checking, the asyncio
fallback is redundant on all supported Python versions — it can never
return True when `inspect.iscoroutinefunction` returned False on the
unwrapped function.
Fixes#3765
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* fix: resolve OpenAPI 3.x server variables in _create_default_client
When an OpenAPI spec defines server variables (e.g. `https://{region}.api.example.com/v1`),
the default values are now substituted before constructing the httpx client base URL.
Previously, the URL was used as-is, causing all requests to fail for specs that use
server variable templating.
Fixes#1681
* fix: use str.replace instead of format_map for server variable substitution
format_map applies Python string formatting rules, so variable names
like {api.version} would be treated as attribute access and raise errors.
Literal token replacement handles all valid OpenAPI variable names safely.
* Unify background task context forwarding and fix concurrent dependency bugs
We've been getting a steady trickle of edge-case reports around background tasks
and contextual dependencies over the last few months (#3654, #3656, #3569). Each
one pointed at a different symptom, but they all traced back to the same area:
the way context is negotiated between the "frontend" server and Docket workers
was grown piecemeal, with each new piece of context (access tokens, HTTP headers,
origin request IDs) getting its own Redis key, its own restore function, and its
own ContextVar. This made it hard to reason about what state was available where,
and the shared-instance Dependency pattern made concurrent tasks stomp on each
other's cleanup state.
This takes a step back and reworks the whole thing as a single unified system:
- Dependency subclasses (_CurrentContext, Progress, _CurrentAccessToken, etc.)
are now stateless factories — __aenter__ returns a fresh per-invocation
object, so concurrent tasks never share mutable state. Fixes#3654, #3656.
- The three individual context-snapshot Redis keys (access_token, http_headers,
origin_request_id) are collapsed into a single TaskContextSnapshot stored as
one JSON key per task. The three _restore_task_* functions and two ContextVars
they populated are gone.
- Sync functions like get_http_request() and get_access_token() now find the
snapshot transparently in background tasks via a 3-tier sync fallback:
ContextVar (set by _CurrentContext for functions with deps) → in-memory dict
(same-process workers) → sync Redis GET (out-of-process workers). No function
wrapping needed.
- The _wrap_for_task_http_headers hack is deleted. FunctionTool registers its
raw function with Docket so Docket sees and resolves ALL dependencies,
including Docket-native ones like Retry and Timeout.
- ProxyTool.from_mcp_tool() now propagates execution.taskSupport metadata from
remote tools. Fixes#3569.
- Removed redundant _current_docket/_current_worker ContextVar management from
Context.__aenter__/__aexit__ (they're only set in the lifespan now).
Closes#3654Closes#3656Closes#3569🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Address code review feedback
- _OptionalCurrentContext: guard __aexit__ against cleaning up contexts it
didn't create (check is_background_task before delegating)
- Narrow except clauses in snapshot loading (OSError, JSONDecodeError, etc.
instead of bare Exception)
- Fix docstrings on register_with_docket for resources/prompts/templates
- Simplify Progress: read ExecutionProgress directly from current_execution
instead of creating and manually entering a DocketProgress wrapper
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Use pop-on-access transfer buffer instead of bounded LRU cache for snapshots
The in-memory snapshot dict is a transfer mechanism, not a cache. Entries go
in at submission and come out at the worker's first access. Using pop instead
of get means the dict only holds entries during the brief submission-to-execution
window, bounded by task concurrency (~10) rather than a 10,000-entry LRU limit.
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Drop in-memory transfer buffer, use sync Redis for all backends
Instead of maintaining an in-memory dict to bridge the async/sync gap, use
a sync Redis client directly. For memory:// backends (fakeredis), shares the
same FakeServer instance via docket._redis.get_memory_server() so data written
by the async Docket client is visible to sync reads. For real Redis, creates a
standard sync connection. No in-process state to manage at all.
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Move snapshot operations to TaskContextSnapshot methods
capture(), from_json(), to_json(), save() are now classmethod/instance methods
on the dataclass instead of free functions. Deduplicates JSON parsing that was
copy-pasted between the async and sync load paths.
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Trim implementation details from register_with_docket docstrings
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Clarify docket lookup comment in submit_to_docket
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Restore docket/worker ContextVar bridge in Context.__aenter__
Servers that own the Docket (the parent) re-set _current_docket/_current_worker
from their instance attributes when entering a Context. Mounted children skip
this (their _docket is None), so they inherit the parent's value. This is needed
for ASGI deployments where ContextVars set during the lifespan don't propagate
to request handlers.
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Key snapshot cache by task_id to prevent cross-task context leakage
Docket workers may reuse the same asyncio context for sequential tasks.
The ContextVar cache now stores (task_id, snapshot) tuples so stale entries
from previous tasks are automatically ignored.
🤖 Generated with Claude Code
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>