Replace vendored DI with uncalled-for (#3301)

* Replace vendored DI with uncalled-for

FastMCP vendored a minimal DI engine extracted from Docket (~164 lines)
with try/except fallback patterns everywhere. The `uncalled-for` package
is a clean, typed extraction of this same system, and since Docket will
also depend on it (chrisguidry/docket#353), `uncalled_for.Dependency`
becomes the single canonical base class.

This deletes the `_vendor/docket_di/` directory, replaces all the
try/except import patterns with direct `uncalled_for` imports, and
updates the `Dependency.execution` → `current_execution` ContextVar
references to match the Docket branch. The `Progress` class now
delegates to an internal impl and returns `self` from `__aenter__`
(matching Docket's pattern) so that ty's generic resolution works
without `type: ignore` suppressions.

Temporarily points pydocket at the `use-uncalled-for` branch so both
sides can be validated together in CI.

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Re-export Dependency from fastmcp.dependencies

Internal code like azure.py should import from the fastmcp namespace
rather than reaching into uncalled_for directly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Import Dependency from fastmcp namespace in tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add generic type parameters to Dependency subclasses

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Mention uncalled-for in DI docs

The DI engine now comes from uncalled-for, so the docs should credit
it alongside Docket. Also updates the Docket docs link to docket.lol.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Point docket dependency at main

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Bump uncalled-for pin to >=0.2.0

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix uncalled-for imports for 0.2.0 API changes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Support Shared() dependencies without docket

Enters a SharedContext at server lifetime so that Shared() dependencies
from uncalled-for resolve once and are cached across tool/resource/prompt
calls. When running with docket, the Worker already handles this; this
covers the non-docket path and direct call_tool() usage.

Also re-exports Shared from fastmcp.dependencies.

Closes #3251

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Bump docket lockfile to latest main

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Remove duplicate test classes from rebase conflict resolution

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Point docket dependency at pydocket>=0.18.0 release

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Pair SharedContext __aenter__ with __aexit__ in Context lifecycle

The old `_ensure_shared_context` on the server called `__aenter__()` on a
lazy `SharedContext` but never `__aexit__()`, leaking the exit stack and
its resources. Moved the SharedContext management into Context's own
enter/exit so it's properly paired: when docket is available the lifespan
handles it, otherwise Context creates and cleans up a per-request one.

Updated Shared() tests to use Client (which runs the lifespan) rather
than calling server methods directly, since cross-request sharing
requires a lifespan.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Hoist SharedContext import to module level

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Chris Guidry 2026-03-02 16:35:15 -05:00 committed by GitHub
commit 547daf7a36
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 589 additions and 575 deletions

View file

@ -10,7 +10,7 @@ import { VersionBadge } from "/snippets/version-badge.mdx";
FastMCP uses dependency injection to provide runtime values to your tools, resources, and prompts. Instead of passing context through every layer of your code, you declare what you need as parameter defaults—FastMCP resolves them automatically when your function runs.
The dependency injection system is powered by [Docket](https://github.com/chrisguidry/docket). Core DI features like `Depends()` and `CurrentContext()` work without installing Docket. For background tasks and advanced task-related dependencies, install `fastmcp[tasks]`. For comprehensive coverage of dependency patterns, see the [Docket dependency documentation](https://chrisguidry.github.io/docket/dependencies/).
The dependency injection system is powered by [Docket](https://github.com/chrisguidry/docket) and its dependency system [uncalled-for](https://github.com/chrisguidry/uncalled-for). Core DI features like `Depends()` and `CurrentContext()` work without installing Docket. For background tasks and advanced task-related dependencies, install `fastmcp[tasks]`. For comprehensive coverage of dependency patterns, see the [Docket dependency documentation](https://docket.lol/en/latest/dependency-injection/).
<Note>
Dependency parameters are automatically excluded from the MCP schema—clients never see them as callable parameters. This separation keeps your function signatures clean while giving you access to the runtime context you need.

View file

@ -23,6 +23,7 @@ dependencies = [
"websockets>=15.0.1",
"jsonschema-path>=0.3.4",
"jsonref>=1.1.0",
"uncalled-for>=0.2.0",
"watchfiles>=1.0.0",
]
@ -56,7 +57,7 @@ apps = ["prefab-ui>=0.6.0"]
azure = ["azure-identity>=1.16.0"]
code-mode = ["pydantic-monty>=0.0.7"]
openai = ["openai>=1.102.0"]
tasks = ["pydocket>=0.17.2"]
tasks = ["pydocket>=0.18.0"]
[dependency-groups]
dev = [

View file

@ -1 +0,0 @@
"""Vendored third-party code for FastMCP."""

View file

@ -1,7 +0,0 @@
# Vendored Docket DI
This is a minimal vendored copy of the dependency injection engine from [Docket](https://github.com/chrisguidry/docket), pending its release as a standalone library.
When `fastmcp[tasks]` is installed, FastMCP uses Docket's DI classes directly for `isinstance` compatibility in worker contexts. This vendored version is only used when Docket is not installed, allowing basic `Depends()` functionality without the full Docket dependency.
Once the DI component is released separately, this vendored copy will be removed.

View file

@ -1,163 +0,0 @@
"""Vendored dependency injection engine from Docket.
This is a minimal subset of docket.dependencies for FastMCP's DI system.
When docket is installed, FastMCP uses docket's classes directly for
isinstance compatibility. This vendored version is only used when docket
is not installed.
Original source: https://github.com/chrisguidry/docket
License: MIT
"""
from __future__ import annotations
import abc
import inspect
from contextlib import AsyncExitStack
from contextvars import ContextVar
from collections.abc import Awaitable, Callable
from contextlib import AbstractAsyncContextManager, AbstractContextManager
from typing import (
Any,
Generic,
TypeVar,
cast,
)
R = TypeVar("R")
# Cached signature lookup (simplified from docket.execution.get_signature)
_signature_cache: dict[Callable[..., Any], inspect.Signature] = {}
def get_signature(function: Callable[..., Any]) -> inspect.Signature:
"""Get cached signature for a function."""
if function in _signature_cache:
return _signature_cache[function]
signature_attr = getattr(function, "__signature__", None)
if isinstance(signature_attr, inspect.Signature):
_signature_cache[function] = signature_attr
return signature_attr
signature = inspect.signature(function)
_signature_cache[function] = signature
return signature
class Dependency(abc.ABC):
"""Base class for all dependencies.
Subclasses must implement __aenter__ to provide the dependency value.
The __aexit__ method is optional for cleanup.
"""
single: bool = False
@abc.abstractmethod
async def __aenter__(self) -> Any: ...
async def __aexit__(self, *args: object) -> None: # noqa: B027
pass
DependencyFunction = Callable[..., Any]
_parameter_cache: dict[Callable[..., Any], dict[str, Dependency]] = {}
def get_dependency_parameters(
function: Callable[..., Any],
) -> dict[str, Dependency]:
"""Find parameters with Dependency defaults."""
if function in _parameter_cache:
return _parameter_cache[function]
dependencies: dict[str, Dependency] = {}
signature = get_signature(function)
for parameter, param in signature.parameters.items():
if not isinstance(param.default, Dependency):
continue
dependencies[parameter] = param.default
_parameter_cache[function] = dependencies
return dependencies
class _Depends(Dependency, Generic[R]):
"""Wrapper for user-defined dependency functions."""
dependency: DependencyFunction
cache: ContextVar[dict[DependencyFunction, Any]] = ContextVar("cache")
stack: ContextVar[AsyncExitStack] = ContextVar("stack")
def __init__(self, dependency: DependencyFunction) -> None:
self.dependency = dependency
async def _resolve_parameters(self, function: DependencyFunction) -> dict[str, Any]:
stack = self.stack.get()
arguments: dict[str, Any] = {}
parameters = get_dependency_parameters(function)
for parameter, dependency in parameters.items():
arguments[parameter] = await stack.enter_async_context(dependency)
return arguments
async def __aenter__(self) -> R:
cache = self.cache.get()
if self.dependency in cache:
return cache[self.dependency]
stack = self.stack.get()
arguments = await self._resolve_parameters(self.dependency)
raw_value = self.dependency(**arguments)
# Handle different return types
resolved_value: R
if isinstance(raw_value, AbstractAsyncContextManager):
resolved_value = await stack.enter_async_context(raw_value)
elif isinstance(raw_value, AbstractContextManager):
resolved_value = stack.enter_context(raw_value)
elif inspect.iscoroutine(raw_value) or isinstance(raw_value, Awaitable):
resolved_value = await cast(Awaitable[R], raw_value)
else:
resolved_value = cast(R, raw_value)
cache[self.dependency] = resolved_value
return resolved_value
def Depends(dependency: DependencyFunction) -> Any:
"""Include a user-defined function as a dependency.
Dependencies may be:
- Synchronous functions returning a value
- Asynchronous functions returning a value (awaitable)
- Synchronous context managers (using @contextmanager)
- Asynchronous context managers (using @asynccontextmanager)
Example:
```python
def get_config() -> dict:
return {"api_url": "https://api.example.com"}
@mcp.tool
def my_tool(config: dict = Depends(get_config)) -> str:
return config["api_url"]
```
"""
return cast(Any, _Depends(dependency))
__all__ = [
"Dependency",
"Depends",
"_Depends",
"get_dependency_parameters",
"get_signature",
]

View file

@ -1,6 +1,6 @@
import inspect
from collections.abc import Awaitable, Callable
from typing import TypeAlias, cast
from typing import TypeAlias
import mcp.types
import pydantic
@ -65,7 +65,7 @@ def _create_roots_callback_from_fn(
try:
roots = fn(context)
if inspect.isawaitable(roots):
roots = cast(RootsList, await roots)
roots = await roots
return mcp.types.ListRootsResult(roots=convert_roots_list(roots))
except Exception as e:
return mcp.types.ErrorData(

View file

@ -51,7 +51,7 @@ def create_sampling_callback(
try:
result = sampling_handler(params.messages, params, context)
if inspect.isawaitable(result):
result = cast(SamplingHandlerResult, await result)
result = await result
if isinstance(result, str):
result = CreateMessageResult(

View file

@ -1,20 +1,14 @@
"""Dependency injection exports for FastMCP.
This module re-exports dependency injection symbols from Docket and FastMCP
to provide a clean, centralized import location for all dependency-related
functionality.
This module re-exports dependency injection symbols to provide a clean,
centralized import location for all dependency-related functionality.
DI features (Depends, CurrentContext, CurrentFastMCP) work without pydocket
using a vendored DI engine. Only task-related dependencies (CurrentDocket,
using the uncalled-for DI engine. Only task-related dependencies (CurrentDocket,
CurrentWorker) and background task execution require fastmcp[tasks].
"""
# Try docket first for isinstance compatibility, fall back to vendored
try:
from docket import Depends
except ImportError:
from fastmcp._vendor.docket_di import Depends
from uncalled_for import Dependency, Depends, Shared
from fastmcp.server.dependencies import (
CurrentAccessToken,
@ -37,8 +31,10 @@ __all__ = [
"CurrentHeaders",
"CurrentRequest",
"CurrentWorker",
"Dependency",
"Depends",
"Progress",
"ProgressLike",
"Shared",
"TokenClaim",
]

View file

@ -13,6 +13,7 @@ from typing import TYPE_CHECKING, Any, cast
import httpx
from key_value.aio.protocols import AsyncKeyValue
from fastmcp.dependencies import Dependency
from fastmcp.server.auth.oauth_proxy import OAuthProxy
from fastmcp.server.auth.providers.jwt import JWTVerifier
from fastmcp.utilities.auth import decode_jwt_payload, parse_scopes
@ -627,12 +628,6 @@ class AzureJWTVerifier(JWTVerifier):
# --- Dependency injection support ---
# These require fastmcp[azure] extra for azure-identity
# Check if DI engine is available
try:
from docket.dependencies import Dependency
except ImportError:
from fastmcp._vendor.docket_di import Dependency
def _require_azure_identity(feature: str) -> None:
"""Raise ImportError with install instructions if azure-identity is not available."""
@ -645,7 +640,7 @@ def _require_azure_identity(feature: str) -> None:
) from e
class _EntraOBOToken(Dependency): # type: ignore[misc]
class _EntraOBOToken(Dependency[str]):
"""Dependency that performs OBO token exchange for Microsoft Entra.
Uses azure.identity's OnBehalfOfCredential for async-native OBO,

View file

@ -24,6 +24,7 @@ from mcp.types import Resource as SDKResource
from pydantic.networks import AnyUrl
from starlette.requests import Request
from typing_extensions import TypeVar
from uncalled_for import SharedContext
from fastmcp.resources.resource import ResourceResult
from fastmcp.server.elicitation import (
@ -266,6 +267,7 @@ class Context:
_current_docket,
_current_server,
_current_worker,
is_docket_available,
)
self._server_token = _current_server.set(weakref.ref(self.fastmcp))
@ -274,32 +276,41 @@ class Context:
# This ensures ContextVars work even in ASGI environments (Lambda, FastAPI mount)
# where lifespan ContextVars don't propagate to request handlers.
server = self.fastmcp
if server._docket is not None:
self._docket_token = _current_docket.set(server._docket)
if server._worker is not None:
self._worker_token = _current_worker.set(server._worker)
if is_docket_available():
if server._docket is not None:
self._docket_token = _current_docket.set(server._docket)
if server._worker is not None:
self._worker_token = _current_worker.set(server._worker)
else:
# Without docket, the lifespan won't provide a SharedContext,
# so create one scoped to this Context for Shared() dependencies.
self._shared_context = SharedContext()
await self._shared_context.__aenter__()
return self
async def __aexit__(self, exc_type, exc_val, exc_tb) -> None:
"""Exit the context manager and reset the most recent token."""
# Reset server/docket/worker tokens
from fastmcp.server.dependencies import (
_current_docket,
_current_server,
_current_worker,
)
# Mirror __aenter__: clean up docket/worker tokens or SharedContext
if hasattr(self, "_worker_token"):
_current_worker.reset(self._worker_token)
delattr(self, "_worker_token")
del self._worker_token
if hasattr(self, "_docket_token"):
_current_docket.reset(self._docket_token)
delattr(self, "_docket_token")
del self._docket_token
if hasattr(self, "_shared_context"):
await self._shared_context.__aexit__(exc_type, exc_val, exc_tb)
del self._shared_context
if hasattr(self, "_server_token"):
_current_server.reset(self._server_token)
delattr(self, "_server_token")
del self._server_token
# Reset context token
if self._tokens:
@ -406,10 +417,9 @@ class Context:
return
try:
from docket.dependencies import Dependency
from docket.dependencies import current_execution
# Get current execution from worker context
execution = Dependency.execution.get()
execution = current_execution.get()
# Update progress in Redis using Docket's progress API.
# Docket only exposes increment() (relative), so we compute

View file

@ -1,7 +1,7 @@
"""Dependency injection for FastMCP.
DI features (Depends, CurrentContext, CurrentFastMCP) work without pydocket
using a vendored DI engine. Only task-related dependencies (CurrentDocket,
using the uncalled-for DI engine. Only task-related dependencies (CurrentDocket,
CurrentWorker) and background task execution require fastmcp[tasks].
"""
@ -28,6 +28,8 @@ from mcp.server.auth.provider import (
)
from mcp.server.lowlevel.server import request_ctx
from starlette.requests import Request
from uncalled_for import Dependency, get_dependency_parameters
from uncalled_for.resolution import _Depends
from fastmcp.exceptions import FastMCPError
from fastmcp.server.auth import AccessToken
@ -104,10 +106,10 @@ def get_task_context() -> TaskContextInfo | None:
if not is_docket_available():
return None
from docket.dependencies import Dependency as DocketDependency
from docket.dependencies import current_execution
try:
execution = DocketDependency.execution.get()
execution = current_execution.get()
# Parse the task key: {session_id}:{task_id}:{task_type}:{component}
from fastmcp.server.tasks.keys import parse_task_key
@ -208,24 +210,7 @@ def require_docket(feature: str) -> None:
)
# --- Dependency injection imports ---
# Try docket first for isinstance compatibility in worker context,
# fall back to vendored DI engine when docket is not installed.
try:
from docket.dependencies import (
Dependency,
_Depends,
get_dependency_parameters,
)
except ImportError:
from fastmcp._vendor.docket_di import (
Dependency,
_Depends,
get_dependency_parameters,
)
# Import Progress separately to avoid breaking DI fallback if Progress is missing
# Import Progress separately — it's docket-specific, not part of uncalled-for
try:
from docket.dependencies import Progress as DocketProgress
except ImportError:
@ -366,31 +351,15 @@ def _clear_signature_caches(fn: Callable[..., Any]) -> None:
Called after modifying a function's signature to ensure downstream
code sees the updated signature.
"""
# Clear vendored DI caches
from fastmcp._vendor.docket_di import _parameter_cache, _signature_cache
from uncalled_for.introspection import _parameter_cache, _signature_cache
_signature_cache.pop(fn, None)
_parameter_cache.pop(fn, None)
# Also clear for __func__ if it's a method
if inspect.ismethod(fn):
_signature_cache.pop(fn.__func__, None)
_parameter_cache.pop(fn.__func__, None)
# Try to clear docket caches if docket is installed
if is_docket_available():
try:
from docket.dependencies import _parameter_cache as docket_param_cache
from docket.execution import _signature_cache as docket_sig_cache
docket_sig_cache.pop(fn, None)
docket_param_cache.pop(fn, None)
if inspect.ismethod(fn):
docket_sig_cache.pop(fn.__func__, None)
docket_param_cache.pop(fn.__func__, None)
except (ImportError, AttributeError):
pass # Cache access not available in this docket version
def get_context() -> Context:
"""Get the current FastMCP Context instance directly."""
@ -828,7 +797,7 @@ async def _restore_task_origin_request_id(session_id: str, task_id: str) -> str
return None
class _CurrentContext(Dependency): # type: ignore[misc]
class _CurrentContext(Dependency["Context"]):
"""Async context manager for Context dependency.
In foreground (request) mode: returns the active context from _current_context.
@ -893,7 +862,7 @@ class _CurrentContext(Dependency): # type: ignore[misc]
self._context = None
class _OptionalCurrentContext(Dependency): # type: ignore[misc]
class _OptionalCurrentContext(Dependency["Context | None"]):
"""Context dependency that degrades to None when no context is active.
This is implemented as a wrapper (composition), not a subclass of
@ -951,7 +920,7 @@ def OptionalCurrentContext() -> Context | None:
return cast("Context | None", _OptionalCurrentContext())
class _CurrentDocket(Dependency): # type: ignore[misc]
class _CurrentDocket(Dependency["Docket"]):
"""Async context manager for Docket dependency."""
async def __aenter__(self) -> Docket:
@ -996,7 +965,7 @@ def CurrentDocket() -> Docket:
return cast("Docket", _CurrentDocket())
class _CurrentWorker(Dependency): # type: ignore[misc]
class _CurrentWorker(Dependency["Worker"]):
"""Async context manager for Worker dependency."""
async def __aenter__(self) -> Worker:
@ -1040,7 +1009,7 @@ def CurrentWorker() -> Worker:
return cast("Worker", _CurrentWorker())
class _CurrentFastMCP(Dependency): # type: ignore[misc]
class _CurrentFastMCP(Dependency["FastMCP"]):
"""Async context manager for FastMCP server dependency."""
async def __aenter__(self) -> FastMCP:
@ -1081,7 +1050,7 @@ def CurrentFastMCP() -> FastMCP:
return cast(FastMCP, _CurrentFastMCP())
class _CurrentRequest(Dependency): # type: ignore[misc]
class _CurrentRequest(Dependency[Request]):
"""Async context manager for HTTP Request dependency."""
async def __aenter__(self) -> Request:
@ -1117,7 +1086,7 @@ def CurrentRequest() -> Request:
return cast(Request, _CurrentRequest())
class _CurrentHeaders(Dependency): # type: ignore[misc]
class _CurrentHeaders(Dependency[dict[str, str]]):
"""Async context manager for HTTP Headers dependency."""
async def __aenter__(self) -> dict[str, str]:
@ -1241,7 +1210,7 @@ class InMemoryProgress:
self._message = message
class Progress(Dependency): # type: ignore[misc]
class Progress(Dependency["Progress"]):
"""FastMCP Progress dependency that works in both server and worker contexts.
Handles three execution modes:
@ -1254,39 +1223,67 @@ class Progress(Dependency): # type: ignore[misc]
is installed.
"""
async def __aenter__(self) -> ProgressLike:
# Check if we're in a FastMCP server context
_impl: ProgressLike | None = None
async def __aenter__(self) -> Progress:
server_ref = _current_server.get()
if server_ref is None or server_ref() is None:
raise RuntimeError("Progress dependency requires a FastMCP server context.")
# If pydocket is installed, try to use Docket's progress
if is_docket_available():
from docket.dependencies import Progress as DocketProgress
# Try to get execution from Docket worker context
try:
docket_progress = DocketProgress()
return await docket_progress.__aenter__()
self._impl = await docket_progress.__aenter__()
return self
except LookupError:
# Not in worker context - fall through to in-memory progress
pass
# Return in-memory progress for immediate execution
# This is used when:
# 1. pydocket is not installed
# 2. Docket is not running (no task-enabled components)
# 3. In server context (not worker context)
return InMemoryProgress()
self._impl = InMemoryProgress()
return self
async def __aexit__(self, *args: object) -> None:
pass
self._impl = None
@property
def current(self) -> int | None:
"""Current progress value."""
assert self._impl is not None, "Progress must be used as a dependency"
return self._impl.current
@property
def total(self) -> int:
"""Total/target progress value."""
assert self._impl is not None, "Progress must be used as a dependency"
return self._impl.total
@property
def message(self) -> str | None:
"""Current progress message."""
assert self._impl is not None, "Progress must be used as a dependency"
return self._impl.message
async def set_total(self, total: int) -> None:
"""Set the total/target value for progress tracking."""
assert self._impl is not None, "Progress must be used as a dependency"
await self._impl.set_total(total)
async def increment(self, amount: int = 1) -> None:
"""Atomically increment the current progress value."""
assert self._impl is not None, "Progress must be used as a dependency"
await self._impl.increment(amount)
async def set_message(self, message: str | None) -> None:
"""Update the progress status message."""
assert self._impl is not None, "Progress must be used as a dependency"
await self._impl.set_message(message)
# --- Access Token dependency ---
class _CurrentAccessToken(Dependency): # type: ignore[misc]
class _CurrentAccessToken(Dependency[AccessToken]):
"""Async context manager for AccessToken dependency."""
_access_token_cv_token: Token[AccessToken | None] | None = None
@ -1346,7 +1343,7 @@ def CurrentAccessToken() -> AccessToken:
# --- Token Claim dependency ---
class _TokenClaim(Dependency): # type: ignore[misc]
class _TokenClaim(Dependency[str]):
"""Dependency that extracts a specific claim from the access token."""
def __init__(self, claim_name: str):

View file

@ -8,6 +8,8 @@ from collections.abc import AsyncIterator
from contextlib import AsyncExitStack, asynccontextmanager, suppress
from typing import TYPE_CHECKING, Any
from uncalled_for import SharedContext
import fastmcp
from fastmcp.utilities.logging import get_logger
@ -48,9 +50,11 @@ class LifespanMixin:
server_token = _current_server.set(weakref.ref(self))
try:
# If docket is not available, skip task infrastructure
# If docket is not available, skip task infrastructure but still
# set up SharedContext so Shared() dependencies work.
if not is_docket_available():
yield
async with SharedContext():
yield
return
# Collect task-enabled components at startup with all transforms applied.
@ -64,9 +68,11 @@ class LifespanMixin:
raise
task_components = []
# If no task-enabled components, skip Docket infrastructure entirely
# If no task-enabled components, skip Docket infrastructure but still
# set up SharedContext so Shared() dependencies work.
if not task_components:
yield
async with SharedContext():
yield
return
# Docket is available AND there are task-enabled components

View file

@ -225,9 +225,7 @@ async def call_sampling_handler(
)
if inspect.isawaitable(result):
result = cast(
str | CreateMessageResult | CreateMessageResultWithTools, await result
)
result = await result
# Convert string to CreateMessageResult
if isinstance(result, str):

View file

@ -683,11 +683,7 @@ class TestAzureOBOIntegration:
def test_entra_obo_token_is_dependency_instance(self):
"""Test that EntraOBOToken is a Dependency instance."""
try:
from docket.dependencies import Dependency
except ImportError:
from fastmcp._vendor.docket_di import Dependency
from fastmcp.dependencies import Dependency
from fastmcp.server.auth.providers.azure import _EntraOBOToken
dep = _EntraOBOToken(["scope"])

View file

@ -25,7 +25,7 @@ class InitializationMiddleware(Middleware):
self.client_info = None
self.session_data = {}
async def on_initialize( # type: ignore[override]
async def on_initialize(
self,
context: MiddlewareContext[mt.InitializeRequest],
call_next: CallNext[mt.InitializeRequest, None],
@ -63,7 +63,7 @@ class ClientDetectionMiddleware(Middleware):
self.tools_modified = False
self.initialization_called = False
async def on_initialize( # type: ignore[override]
async def on_initialize(
self,
context: MiddlewareContext[mt.InitializeRequest],
call_next: CallNext[mt.InitializeRequest, None],
@ -77,7 +77,7 @@ class ClientDetectionMiddleware(Middleware):
return await call_next(context)
async def on_list_tools( # type: ignore[override]
async def on_list_tools(
self,
context: MiddlewareContext[mt.ListToolsRequest],
call_next: CallNext[mt.ListToolsRequest, list],
@ -109,7 +109,7 @@ async def test_simple_initialization_hook():
super().__init__()
self.called = False
async def on_initialize( # type: ignore[override]
async def on_initialize(
self,
context: MiddlewareContext[mt.InitializeRequest],
call_next: CallNext[mt.InitializeRequest, None],
@ -301,7 +301,7 @@ async def test_middleware_mcp_error_during_initialization():
server = FastMCP("TestServer")
class ErrorThrowingMiddleware(Middleware):
async def on_initialize( # type: ignore[override]
async def on_initialize(
self,
context: MiddlewareContext[mt.InitializeRequest],
call_next: CallNext[mt.InitializeRequest, None],
@ -327,7 +327,7 @@ async def test_middleware_mcp_error_before_call_next():
server = FastMCP("TestServer")
class EarlyErrorMiddleware(Middleware):
async def on_initialize( # type: ignore[override]
async def on_initialize(
self,
context: MiddlewareContext[mt.InitializeRequest],
call_next: CallNext[mt.InitializeRequest, None],

View file

@ -229,7 +229,7 @@ async def test_dependency_context_managers_cleaned_up_in_background():
cleanup_called.append("exit")
@mcp.tool(task=True)
async def use_connection(name: str, conn: str = Depends(tracked_connection)) -> str: # type: ignore[assignment]
async def use_connection(name: str, conn: str = Depends(tracked_connection)) -> str:
assert conn == "connection"
assert "enter" in cleanup_called
assert "exit" not in cleanup_called # Still open during execution

View file

@ -8,7 +8,7 @@ from mcp.types import TextContent, TextResourceContents
from fastmcp import FastMCP
from fastmcp.client import Client
from fastmcp.dependencies import CurrentContext, Depends
from fastmcp.dependencies import CurrentContext, Depends, Shared
from fastmcp.server.context import Context
HUZZAH = "huzzah!"
@ -67,7 +67,7 @@ async def test_depends_with_async_function(mcp: FastMCP):
return 42
@mcp.tool()
async def greet_user(name: str, user_id: int = Depends(get_user_id)) -> str: # type: ignore[assignment]
async def greet_user(name: str, user_id: int = Depends(get_user_id)) -> str:
return f"Hello {name}, your ID is {user_id}"
result = await mcp.call_tool("greet_user", {"name": "Alice"})
@ -89,7 +89,7 @@ async def test_depends_with_async_context_manager(mcp: FastMCP):
cleanup_called = True
@mcp.tool()
async def query_db(sql: str, db: str = Depends(get_database)) -> str: # type: ignore[assignment]
async def query_db(sql: str, db: str = Depends(get_database)) -> str:
return f"Executing '{sql}' on {db}"
result = await mcp.call_tool("query_db", {"sql": "SELECT * FROM users"})
@ -198,7 +198,7 @@ async def test_sync_tool_with_async_dependency(mcp: FastMCP):
return "loaded_config"
@mcp.tool()
def process_data(value: int, config: str = Depends(fetch_config)) -> str: # type: ignore[assignment]
def process_data(value: int, config: str = Depends(fetch_config)) -> str:
return f"Processing {value} with {config}"
result = await mcp.call_tool("process_data", {"value": 100})
@ -376,7 +376,7 @@ async def test_async_tool_context_manager_stays_open(mcp: FastMCP):
@mcp.tool()
async def query_data(
query: str,
connection: Connection = Depends(get_connection), # type: ignore[assignment]
connection: Connection = Depends(get_connection),
) -> str:
assert connection.is_open
return f"open={connection.is_open}"
@ -390,7 +390,7 @@ async def test_async_resource_context_manager_stays_open(mcp: FastMCP):
"""Test that context manager dependencies stay open during async resource execution."""
@mcp.resource("data://config")
async def load_config(connection: Connection = Depends(get_connection)) -> str: # type: ignore[assignment]
async def load_config(connection: Connection = Depends(get_connection)) -> str:
assert connection.is_open
return f"open={connection.is_open}"
@ -404,7 +404,7 @@ async def test_async_resource_template_context_manager_stays_open(mcp: FastMCP):
@mcp.resource("user://{user_id}")
async def get_user(
user_id: str,
connection: Connection = Depends(get_connection), # type: ignore[assignment]
connection: Connection = Depends(get_connection),
) -> str:
assert connection.is_open
return f"open={connection.is_open},user={user_id}"
@ -420,7 +420,7 @@ async def test_async_prompt_context_manager_stays_open(mcp: FastMCP):
@mcp.prompt()
async def research_prompt(
topic: str,
connection: Connection = Depends(get_connection), # type: ignore[assignment]
connection: Connection = Depends(get_connection),
) -> str:
assert connection.is_open
return f"open={connection.is_open},topic={topic}"
@ -463,7 +463,7 @@ async def test_connection_dependency_excluded_from_tool_schema(mcp: FastMCP):
@mcp.tool()
async def with_connection(
name: str,
connection: Connection = Depends(get_connection), # type: ignore[assignment]
connection: Connection = Depends(get_connection),
) -> str:
return name
@ -489,7 +489,7 @@ async def test_sync_tool_context_manager_stays_open(mcp: FastMCP):
@mcp.tool()
async def query_sync(
query: str,
connection: Connection = Depends(get_sync_connection), # type: ignore[assignment]
connection: Connection = Depends(get_sync_connection),
) -> str:
assert connection.is_open
return f"open={connection.is_open}"
@ -513,7 +513,7 @@ async def test_sync_resource_context_manager_stays_open(mcp: FastMCP):
conn.is_open = False
@mcp.resource("data://sync")
async def load_sync(connection: Connection = Depends(get_sync_connection)) -> str: # type: ignore[assignment]
async def load_sync(connection: Connection = Depends(get_sync_connection)) -> str:
assert connection.is_open
return f"open={connection.is_open}"
@ -537,7 +537,7 @@ async def test_sync_resource_template_context_manager_stays_open(mcp: FastMCP):
@mcp.resource("item://{item_id}")
async def get_item(
item_id: str,
connection: Connection = Depends(get_sync_connection), # type: ignore[assignment]
connection: Connection = Depends(get_sync_connection),
) -> str:
assert connection.is_open
return f"open={connection.is_open},item={item_id}"
@ -563,7 +563,7 @@ async def test_sync_prompt_context_manager_stays_open(mcp: FastMCP):
@mcp.prompt()
async def sync_prompt(
topic: str,
connection: Connection = Depends(get_sync_connection), # type: ignore[assignment]
connection: Connection = Depends(get_sync_connection),
) -> str:
assert connection.is_open
return f"open={connection.is_open},topic={topic}"
@ -746,3 +746,352 @@ async def test_validation_error_propagates_from_dependency(mcp: FastMCP):
assert result.is_error
assert isinstance(result.content[0], TextContent)
assert result.content[0].text == "Invalid input format"
class TestDependencyInjection:
"""Tests for the uncalled-for DI engine."""
def test_is_docket_available(self):
"""Test is_docket_available returns True when docket is installed."""
from fastmcp.server.dependencies import is_docket_available
assert is_docket_available() is True
def test_require_docket_passes_when_installed(self):
"""Test require_docket doesn't raise when docket is installed."""
from fastmcp.server.dependencies import require_docket
require_docket("test feature")
def test_dependency_class_exists(self):
"""Test Dependency and Depends are importable from fastmcp."""
from fastmcp.dependencies import Dependency, Depends
assert Dependency is not None
assert Depends is not None
def test_depends_works(self):
"""Test Depends() creates proper dependency wrapper."""
from uncalled_for.resolution import _Depends
from fastmcp.dependencies import Depends
def get_value() -> str:
return "test_value"
dep = Depends(get_value)
assert isinstance(dep, _Depends)
assert dep.factory is get_value
async def test_depends_import_from_fastmcp(self):
"""Test that Depends can be imported from fastmcp.dependencies."""
from fastmcp.dependencies import Depends
def get_config() -> dict:
return {"key": "value"}
dep = Depends(get_config)
assert dep is not None
def test_get_dependency_parameters(self):
"""Test get_dependency_parameters finds dependency defaults."""
from uncalled_for import get_dependency_parameters
from uncalled_for.resolution import _Depends
from fastmcp.dependencies import Depends
def get_db() -> str:
return "database"
def my_func(name: str, db: str = Depends(get_db)) -> str:
return f"{name}: {db}"
deps = get_dependency_parameters(my_func)
assert "db" in deps
db_dep = deps["db"]
assert isinstance(db_dep, _Depends)
assert db_dep.factory is get_db
class TestAuthDependencies:
"""Tests for authentication dependencies (CurrentAccessToken, TokenClaim)."""
def test_current_access_token_is_importable(self):
"""Test that CurrentAccessToken can be imported."""
from fastmcp.server.dependencies import CurrentAccessToken
assert CurrentAccessToken is not None
def test_token_claim_is_importable(self):
"""Test that TokenClaim can be imported."""
from fastmcp.server.dependencies import TokenClaim
assert TokenClaim is not None
def test_current_access_token_is_dependency(self):
"""Test that CurrentAccessToken is a Dependency instance."""
from fastmcp.dependencies import Dependency
from fastmcp.server.dependencies import _CurrentAccessToken
dep = _CurrentAccessToken()
assert isinstance(dep, Dependency)
def test_token_claim_creates_dependency(self):
"""Test that TokenClaim creates a Dependency instance."""
from fastmcp.dependencies import Dependency
from fastmcp.server.dependencies import TokenClaim, _TokenClaim
dep = TokenClaim("oid")
assert isinstance(dep, _TokenClaim)
assert isinstance(dep, Dependency)
assert dep.claim_name == "oid"
async def test_current_access_token_raises_without_token(self):
"""Test that CurrentAccessToken raises when no token is available."""
from fastmcp.server.dependencies import _CurrentAccessToken
dep = _CurrentAccessToken()
with pytest.raises(RuntimeError, match="No access token found"):
await dep.__aenter__()
async def test_token_claim_raises_without_token(self):
"""Test that TokenClaim raises when no token is available."""
from fastmcp.server.dependencies import _TokenClaim
dep = _TokenClaim("oid")
with pytest.raises(RuntimeError, match="No access token available"):
await dep.__aenter__()
async def test_current_access_token_excluded_from_tool_schema(self, mcp: FastMCP):
"""Test that CurrentAccessToken dependency is excluded from tool schema."""
import mcp.types as mcp_types
from fastmcp.server.auth import AccessToken
from fastmcp.server.dependencies import CurrentAccessToken
@mcp.tool()
async def tool_with_token(
name: str,
token: AccessToken = CurrentAccessToken(),
) -> str:
return name
result = await mcp._list_tools_mcp(mcp_types.ListToolsRequest())
tool = next(t for t in result.tools if t.name == "tool_with_token")
assert "name" in tool.inputSchema["properties"]
assert "token" not in tool.inputSchema["properties"]
async def test_token_claim_excluded_from_tool_schema(self, mcp: FastMCP):
"""Test that TokenClaim dependency is excluded from tool schema."""
import mcp.types as mcp_types
from fastmcp.server.dependencies import TokenClaim
@mcp.tool()
async def tool_with_claim(
name: str,
user_id: str = TokenClaim("oid"),
) -> str:
return name
result = await mcp._list_tools_mcp(mcp_types.ListToolsRequest())
tool = next(t for t in result.tools if t.name == "tool_with_claim")
assert "name" in tool.inputSchema["properties"]
assert "user_id" not in tool.inputSchema["properties"]
def test_current_access_token_exported_from_all(self):
"""Test that CurrentAccessToken is exported from __all__."""
from fastmcp.server import dependencies
assert "CurrentAccessToken" in dependencies.__all__
def test_token_claim_exported_from_all(self):
"""Test that TokenClaim is exported from __all__."""
from fastmcp.server import dependencies
assert "TokenClaim" in dependencies.__all__
class TestSharedDependencies:
"""Tests for Shared() dependencies that resolve once and are reused."""
async def test_shared_sync_function(self, mcp: FastMCP):
"""Shared dependency from a sync function resolves and is reused."""
call_count = 0
def get_config() -> dict[str, str]:
nonlocal call_count
call_count += 1
return {"key": "value"}
@mcp.tool()
async def tool_a(config: dict[str, str] = Shared(get_config)) -> str:
return config["key"]
@mcp.tool()
async def tool_b(config: dict[str, str] = Shared(get_config)) -> str:
return config["key"]
async with Client(mcp) as client:
result_a = await client.call_tool("tool_a", {})
result_b = await client.call_tool("tool_b", {})
assert result_a.content[0].text == "value"
assert result_b.content[0].text == "value"
assert call_count == 1
async def test_shared_async_function(self, mcp: FastMCP):
"""Shared dependency from an async function resolves and is reused."""
call_count = 0
async def get_session() -> str:
nonlocal call_count
call_count += 1
return "session-abc"
@mcp.tool()
async def tool_a(session: str = Shared(get_session)) -> str:
return session
@mcp.tool()
async def tool_b(session: str = Shared(get_session)) -> str:
return session
async with Client(mcp) as client:
result_a = await client.call_tool("tool_a", {})
result_b = await client.call_tool("tool_b", {})
assert result_a.content[0].text == "session-abc"
assert result_b.content[0].text == "session-abc"
assert call_count == 1
async def test_shared_async_context_manager(self, mcp: FastMCP):
"""Shared dependency from an async context manager stays open across calls."""
enter_count = 0
@asynccontextmanager
async def get_connection():
nonlocal enter_count
enter_count += 1
conn = Connection()
async with conn:
yield conn
@mcp.tool()
async def tool_a(conn: Connection = Shared(get_connection)) -> bool:
return conn.is_open
@mcp.tool()
async def tool_b(conn: Connection = Shared(get_connection)) -> bool:
return conn.is_open
async with Client(mcp) as client:
result_a = await client.call_tool("tool_a", {})
result_b = await client.call_tool("tool_b", {})
assert result_a.content[0].text == "true"
assert result_b.content[0].text == "true"
assert enter_count == 1
async def test_shared_with_depends(self, mcp: FastMCP):
"""Shared and Depends can coexist in the same tool."""
shared_calls = 0
depends_calls = 0
def get_config() -> str:
nonlocal shared_calls
shared_calls += 1
return "shared-config"
def get_request_id() -> str:
nonlocal depends_calls
depends_calls += 1
return "request-123"
@mcp.tool()
async def my_tool(
config: str = Shared(get_config),
request_id: str = Depends(get_request_id),
) -> str:
return f"{config}/{request_id}"
async with Client(mcp) as client:
result1 = await client.call_tool("my_tool", {})
result2 = await client.call_tool("my_tool", {})
assert result1.content[0].text == "shared-config/request-123"
assert result2.content[0].text == "shared-config/request-123"
assert shared_calls == 1
assert depends_calls == 2
async def test_shared_excluded_from_schema(self, mcp: FastMCP):
"""Shared dependencies are not exposed in the tool schema."""
def get_db() -> str:
return "db"
@mcp.tool()
async def my_tool(name: str, db: str = Shared(get_db)) -> str:
return name
result = await mcp._list_tools_mcp(mcp_types.ListToolsRequest())
tool = next(t for t in result.tools if t.name == "my_tool")
assert "name" in tool.inputSchema["properties"]
assert "db" not in tool.inputSchema["properties"]
async def test_shared_in_resource(self, mcp: FastMCP):
"""Shared dependencies work in resource functions."""
call_count = 0
def get_config() -> str:
nonlocal call_count
call_count += 1
return "resource-config"
@mcp.resource("test://config")
async def config_resource(config: str = Shared(get_config)) -> str:
return config
async with Client(mcp) as client:
result = await client.read_resource("test://config")
assert result[0].text == "resource-config"
result = await client.read_resource("test://config")
assert result[0].text == "resource-config"
assert call_count == 1
async def test_shared_in_prompt(self, mcp: FastMCP):
"""Shared dependencies work in prompt functions."""
call_count = 0
def get_system_prompt() -> str:
nonlocal call_count
call_count += 1
return "You are a helpful assistant."
@mcp.prompt()
async def my_prompt(topic: str, system: str = Shared(get_system_prompt)) -> str:
return f"{system} Talk about {topic}."
async with Client(mcp) as client:
result = await client.get_prompt("my_prompt", {"topic": "dogs"})
assert (
"You are a helpful assistant. Talk about dogs."
in result.messages[0].content.text
)
result = await client.get_prompt("my_prompt", {"topic": "cats"})
assert (
"You are a helpful assistant. Talk about cats."
in result.messages[0].content.text
)
assert call_count == 1

View file

@ -7,7 +7,7 @@ import pytest
from fastmcp import FastMCP
from fastmcp.client import Client
from fastmcp.dependencies import CurrentContext, Depends
from fastmcp.dependencies import CurrentContext
from fastmcp.server.context import Context
@ -284,179 +284,3 @@ class TestTransformContextAnnotations:
result = await client.get_prompt("prompt_with_ctx", {"topic": "AI"})
assert "Write about AI" in result.messages[0].content.text
assert "session:" in result.messages[0].content.text
class TestVendoredDI:
"""Tests for vendored DI when docket is not installed."""
def test_is_docket_available(self):
"""Test is_docket_available returns True when docket is installed."""
from fastmcp.server.dependencies import is_docket_available
# In dev environment, docket should be available
assert is_docket_available() is True
def test_require_docket_passes_when_installed(self):
"""Test require_docket doesn't raise when docket is installed."""
from fastmcp.server.dependencies import require_docket
# Should not raise
require_docket("test feature")
def test_vendored_dependency_class_exists(self):
"""Test vendored Dependency class is importable."""
from fastmcp._vendor.docket_di import Dependency, Depends
assert Dependency is not None
assert Depends is not None
def test_vendored_depends_works(self):
"""Test vendored Depends() creates proper dependency wrapper."""
from fastmcp._vendor.docket_di import Depends, _Depends
def get_value() -> str:
return "test_value"
dep = Depends(get_value)
assert isinstance(dep, _Depends)
assert dep.dependency is get_value
async def test_depends_import_fallback(self):
"""Test that Depends can be imported from fastmcp.dependencies."""
# This tests the import path, not the actual fallback behavior
# since docket is always installed in dev
def get_config() -> dict:
return {"key": "value"}
dep = Depends(get_config)
# Should work regardless of whether docket or vendored is used
assert dep is not None
def test_vendored_get_dependency_parameters(self):
"""Test vendored get_dependency_parameters finds dependency defaults."""
from fastmcp._vendor.docket_di import (
Depends,
_Depends,
get_dependency_parameters,
)
def get_db() -> str:
return "database"
def my_func(name: str, db: str = Depends(get_db)) -> str:
return f"{name}: {db}"
deps = get_dependency_parameters(my_func)
assert "db" in deps
db_dep = deps["db"]
assert isinstance(db_dep, _Depends)
assert db_dep.dependency is get_db
class TestAuthDependencies:
"""Tests for authentication dependencies (CurrentAccessToken, TokenClaim)."""
def test_current_access_token_is_importable(self):
"""Test that CurrentAccessToken can be imported."""
from fastmcp.server.dependencies import CurrentAccessToken
assert CurrentAccessToken is not None
def test_token_claim_is_importable(self):
"""Test that TokenClaim can be imported."""
from fastmcp.server.dependencies import TokenClaim
assert TokenClaim is not None
def test_current_access_token_is_dependency(self):
"""Test that CurrentAccessToken is a Dependency instance."""
# Import the Dependency class the same way the code does
# (docket if available, vendored otherwise)
try:
from docket.dependencies import Dependency
except ImportError:
from fastmcp._vendor.docket_di import Dependency
from fastmcp.server.dependencies import _CurrentAccessToken
dep = _CurrentAccessToken()
assert isinstance(dep, Dependency)
def test_token_claim_creates_dependency(self):
"""Test that TokenClaim creates a Dependency instance."""
# Import the Dependency class the same way the code does
try:
from docket.dependencies import Dependency
except ImportError:
from fastmcp._vendor.docket_di import Dependency
from fastmcp.server.dependencies import TokenClaim, _TokenClaim
dep = TokenClaim("oid")
assert isinstance(dep, _TokenClaim)
assert isinstance(dep, Dependency)
assert dep.claim_name == "oid"
async def test_current_access_token_raises_without_token(self):
"""Test that CurrentAccessToken raises when no token is available."""
from fastmcp.server.dependencies import _CurrentAccessToken
dep = _CurrentAccessToken()
with pytest.raises(RuntimeError, match="No access token found"):
await dep.__aenter__()
async def test_token_claim_raises_without_token(self):
"""Test that TokenClaim raises when no token is available."""
from fastmcp.server.dependencies import _TokenClaim
dep = _TokenClaim("oid")
with pytest.raises(RuntimeError, match="No access token available"):
await dep.__aenter__()
async def test_current_access_token_excluded_from_tool_schema(self, mcp: FastMCP):
"""Test that CurrentAccessToken dependency is excluded from tool schema."""
from fastmcp.server.auth import AccessToken
from fastmcp.server.dependencies import CurrentAccessToken
@mcp.tool()
async def tool_with_token(
name: str,
token: AccessToken = CurrentAccessToken(),
) -> str:
return name
result = await mcp._list_tools_mcp(mcp_types.ListToolsRequest())
tool = next(t for t in result.tools if t.name == "tool_with_token")
assert "name" in tool.inputSchema["properties"]
assert "token" not in tool.inputSchema["properties"]
async def test_token_claim_excluded_from_tool_schema(self, mcp: FastMCP):
"""Test that TokenClaim dependency is excluded from tool schema."""
from fastmcp.server.dependencies import TokenClaim
@mcp.tool()
async def tool_with_claim(
name: str,
user_id: str = TokenClaim("oid"),
) -> str:
return name
result = await mcp._list_tools_mcp(mcp_types.ListToolsRequest())
tool = next(t for t in result.tools if t.name == "tool_with_claim")
assert "name" in tool.inputSchema["properties"]
assert "user_id" not in tool.inputSchema["properties"]
def test_current_access_token_exported_from_all(self):
"""Test that CurrentAccessToken is exported from __all__."""
from fastmcp.server import dependencies
assert "CurrentAccessToken" in dependencies.__all__
def test_token_claim_exported_from_all(self):
"""Test that TokenClaim is exported from __all__."""
from fastmcp.server import dependencies
assert "TokenClaim" in dependencies.__all__

209
uv.lock generated
View file

@ -39,7 +39,7 @@ wheels = [
[[package]]
name = "anthropic"
version = "0.84.0"
version = "0.79.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
@ -51,9 +51,9 @@ dependencies = [
{ name = "sniffio" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/04/ea/0869d6df9ef83dcf393aeefc12dd81677d091c6ffc86f783e51cf44062f2/anthropic-0.84.0.tar.gz", hash = "sha256:72f5f90e5aebe62dca316cb013629cfa24996b0f5a4593b8c3d712bc03c43c37", size = 539457, upload-time = "2026-02-25T05:22:38.54Z" }
sdist = { url = "https://files.pythonhosted.org/packages/15/b1/91aea3f8fd180d01d133d931a167a78a3737b3fd39ccef2ae8d6619c24fd/anthropic-0.79.0.tar.gz", hash = "sha256:8707aafb3b1176ed6c13e2b1c9fb3efddce90d17aee5d8b83a86c70dcdcca871", size = 509825, upload-time = "2026-02-07T18:06:18.388Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/64/ca/218fa25002a332c0aa149ba18ffc0543175998b1f65de63f6d106689a345/anthropic-0.84.0-py3-none-any.whl", hash = "sha256:861c4c50f91ca45f942e091d83b60530ad6d4f98733bfe648065364da05d29e7", size = 455156, upload-time = "2026-02-25T05:22:40.468Z" },
{ url = "https://files.pythonhosted.org/packages/95/b2/cc0b8e874a18d7da50b0fda8c99e4ac123f23bf47b471827c5f6f3e4a767/anthropic-0.79.0-py3-none-any.whl", hash = "sha256:04cbd473b6bbda4ca2e41dd670fe2f829a911530f01697d0a1e37321eb75f3cf", size = 405918, upload-time = "2026-02-07T18:06:20.246Z" },
]
[[package]]
@ -111,15 +111,15 @@ wheels = [
[[package]]
name = "azure-core"
version = "1.38.2"
version = "1.38.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "requests" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/00/fe/5c7710bc611a4070d06ba801de9a935cc87c3d4b689c644958047bdf2cba/azure_core-1.38.2.tar.gz", hash = "sha256:67562857cb979217e48dc60980243b61ea115b77326fa93d83b729e7ff0482e7", size = 363734, upload-time = "2026-02-18T19:33:05.6Z" }
sdist = { url = "https://files.pythonhosted.org/packages/53/9b/23893febea484ad8183112c9419b5eb904773adb871492b5fa8ff7b21e09/azure_core-1.38.1.tar.gz", hash = "sha256:9317db1d838e39877eb94a2240ce92fa607db68adf821817b723f0d679facbf6", size = 363323, upload-time = "2026-02-11T02:03:06.051Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/42/23/6371a551800d3812d6019cd813acd985f9fac0fedc1290129211a73da4ae/azure_core-1.38.2-py3-none-any.whl", hash = "sha256:074806c75cf239ea284a33a66827695ef7aeddac0b4e19dda266a93e4665ead9", size = 217957, upload-time = "2026-02-18T19:33:07.696Z" },
{ url = "https://files.pythonhosted.org/packages/db/88/aaea2ad269ce70b446660371286272c1f6ba66541a7f6f635baf8b0db726/azure_core-1.38.1-py3-none-any.whl", hash = "sha256:69f08ee3d55136071b7100de5b198994fc1c5f89d2b91f2f43156d20fcf200a4", size = 217930, upload-time = "2026-02-11T02:03:07.548Z" },
]
[[package]]
@ -195,11 +195,11 @@ wheels = [
[[package]]
name = "certifi"
version = "2026.2.25"
version = "2026.1.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" }
sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" },
{ url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" },
]
[[package]]
@ -596,7 +596,7 @@ wheels = [
[[package]]
name = "cyclopts"
version = "4.6.0"
version = "4.5.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "attrs" },
@ -606,9 +606,9 @@ dependencies = [
{ name = "tomli", marker = "python_full_version < '3.11'" },
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/49/5c/88a4068c660a096bbe87efc5b7c190080c9e86919c36ec5f092cb08d852f/cyclopts-4.6.0.tar.gz", hash = "sha256:483c4704b953ea6da742e8de15972f405d2e748d19a848a4d61595e8e5360ee5", size = 162724, upload-time = "2026-02-23T15:44:49.286Z" }
sdist = { url = "https://files.pythonhosted.org/packages/a5/16/06e35c217334930ff7c476ce1c8e74ed786fa3ef6742e59a1458e2412290/cyclopts-4.5.3.tar.gz", hash = "sha256:35fa70971204c450d9668646a6ca372eb5fa3070fbe8dd51c5b4b31e65198f2d", size = 162437, upload-time = "2026-02-16T15:07:11.96Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/8f/eb/1e8337755a70dc7d7ff10a73dc8f20e9352c9ad6c2256ed863ac95cd3539/cyclopts-4.6.0-py3-none-any.whl", hash = "sha256:0a891cb55bfd79a3cdce024db8987b33316aba11071e5258c21ac12a640ba9f2", size = 200518, upload-time = "2026-02-23T15:44:47.854Z" },
{ url = "https://files.pythonhosted.org/packages/3a/1f/d8bce383a90d8a6a11033327777afa4d4d611ec11869284adb6f48152906/cyclopts-4.5.3-py3-none-any.whl", hash = "sha256:50af3085bb15d4a6f2582dd383dad5e4ba6a0d4d4c64ee63326d881a752a6919", size = 200231, upload-time = "2026-02-16T15:07:13.045Z" },
]
[[package]]
@ -710,16 +710,16 @@ wheels = [
[[package]]
name = "fakeredis"
version = "2.34.1"
version = "2.34.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "redis" },
{ name = "sortedcontainers" },
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/11/40/fd09efa66205eb32253d2b2ebc63537281384d2040f0a88bcd2289e120e4/fakeredis-2.34.1.tar.gz", hash = "sha256:4ff55606982972eecce3ab410e03d746c11fe5deda6381d913641fbd8865ea9b", size = 177315, upload-time = "2026-02-25T13:17:51.315Z" }
sdist = { url = "https://files.pythonhosted.org/packages/d8/44/c403963727d707e03f49a417712b0a23e853d33ae50729679040b6cfe281/fakeredis-2.34.0.tar.gz", hash = "sha256:72bc51a7ab39bedf5004f0cf1b5206822619c1be8c2657fd878d1f4250256c57", size = 177156, upload-time = "2026-02-16T15:56:34.318Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/49/b5/82f89307d0d769cd9bf46a54fb9136be08e4e57c5570ae421db4c9a2ba62/fakeredis-2.34.1-py3-none-any.whl", hash = "sha256:0107ec99d48913e7eec2a5e3e2403d1bd5f8aa6489d1a634571b975289c48f12", size = 122160, upload-time = "2026-02-25T13:17:49.701Z" },
{ url = "https://files.pythonhosted.org/packages/1a/8e/af19c00753c432355f9b76cec3ab0842578de43ba575e82735b18c1b3ec9/fakeredis-2.34.0-py3-none-any.whl", hash = "sha256:bc45d362c6cc3a537f8287372d8ea532538dfbe7f5d635d0905d7b3464ec51d2", size = 122063, upload-time = "2026-02-16T15:56:21.227Z" },
]
[package.optional-dependencies]
@ -742,7 +742,7 @@ wheels = [
[[package]]
name = "fastapi"
version = "0.133.1"
version = "0.129.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "annotated-doc" },
@ -751,9 +751,9 @@ dependencies = [
{ name = "typing-extensions" },
{ name = "typing-inspection" },
]
sdist = { url = "https://files.pythonhosted.org/packages/22/6f/0eafed8349eea1fa462238b54a624c8b408cd1ba2795c8e64aa6c34f8ab7/fastapi-0.133.1.tar.gz", hash = "sha256:ed152a45912f102592976fde6cbce7dae1a8a1053da94202e51dd35d184fadd6", size = 378741, upload-time = "2026-02-25T18:18:17.398Z" }
sdist = { url = "https://files.pythonhosted.org/packages/48/47/75f6bea02e797abff1bca968d5997793898032d9923c1935ae2efdece642/fastapi-0.129.0.tar.gz", hash = "sha256:61315cebd2e65df5f97ec298c888f9de30430dd0612d59d6480beafbc10655af", size = 375450, upload-time = "2026-02-12T13:54:52.541Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d2/c9/a175a7779f3599dfa4adfc97a6ce0e157237b3d7941538604aadaf97bfb6/fastapi-0.133.1-py3-none-any.whl", hash = "sha256:658f34ba334605b1617a65adf2ea6461901bdb9af3a3080d63ff791ecf7dc2e2", size = 109029, upload-time = "2026-02-25T18:18:18.578Z" },
{ url = "https://files.pythonhosted.org/packages/9e/dd/d0ee25348ac58245ee9f90b6f3cbb666bf01f69be7e0911f9851bddbda16/fastapi-0.129.0-py3-none-any.whl", hash = "sha256:b4946880e48f462692b31c083be0432275cbfb6e2274566b1be91479cc1a84ec", size = 102950, upload-time = "2026-02-12T13:54:54.528Z" },
]
[[package]]
@ -777,6 +777,7 @@ dependencies = [
{ name = "python-dotenv" },
{ name = "pyyaml" },
{ name = "rich" },
{ name = "uncalled-for" },
{ name = "uvicorn" },
{ name = "watchfiles" },
{ name = "websockets" },
@ -852,11 +853,12 @@ requires-dist = [
{ name = "py-key-value-aio", extras = ["filetree", "keyring", "memory"], specifier = ">=0.4.4,<0.5.0" },
{ name = "pydantic", extras = ["email"], specifier = ">=2.11.7" },
{ name = "pydantic-monty", marker = "extra == 'code-mode'", specifier = ">=0.0.7" },
{ name = "pydocket", marker = "extra == 'tasks'", specifier = ">=0.17.2" },
{ name = "pydocket", marker = "extra == 'tasks'", specifier = ">=0.18.0" },
{ name = "pyperclip", specifier = ">=1.9.0" },
{ name = "python-dotenv", specifier = ">=1.1.0" },
{ name = "pyyaml", specifier = ">=6.0,<7.0" },
{ name = "rich", specifier = ">=13.9.4" },
{ name = "uncalled-for", specifier = ">=0.2.0" },
{ name = "uvicorn", specifier = ">=0.35" },
{ name = "watchfiles", specifier = ">=1.0.0" },
{ name = "websockets", specifier = ">=15.0.1" },
@ -1043,7 +1045,7 @@ wheels = [
[[package]]
name = "inline-snapshot"
version = "0.32.3"
version = "0.32.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "asttokens" },
@ -1053,9 +1055,9 @@ dependencies = [
{ name = "tomli", marker = "python_full_version < '3.11'" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c7/66/9e1a4d3341e8301a98f6987afd7c7053441b4c07360b1fa4fca7279683f9/inline_snapshot-0.32.3.tar.gz", hash = "sha256:c606d2551de08a293d29bfcd1df2edde9d478f57c83f063b4642f45906b2d748", size = 2625275, upload-time = "2026-02-24T07:46:54.851Z" }
sdist = { url = "https://files.pythonhosted.org/packages/41/74/19294067d5b5b78144eab2aacec85b998c2d2ea6b3d24eefd9a90255d7aa/inline_snapshot-0.32.0.tar.gz", hash = "sha256:57fa3df325284d0d14def5dab9ac5da89e383f085bea9a7be51fdeab65e59ced", size = 2623331, upload-time = "2026-02-13T19:51:54.469Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c5/af/38e4c5192ab8dbcc73dee07bc0f76718e4a8612783f0e573374588228c1c/inline_snapshot-0.32.3-py3-none-any.whl", hash = "sha256:3925c75fadfcbd190f3c77218a485d27d8af46b952bfe64410b482dd9dbeb24f", size = 84668, upload-time = "2026-02-24T07:46:53.029Z" },
{ url = "https://files.pythonhosted.org/packages/97/25/0e84a6322e5fdb1bf67870b2269151449f4894987b26c78718918dd64ea6/inline_snapshot-0.32.0-py3-none-any.whl", hash = "sha256:b522ae2c891f666e80213c5f9677ec6fd4a2a7d334ab9d6ce745675bec6a40f0", size = 84087, upload-time = "2026-02-13T19:51:52.604Z" },
]
[package.optional-dependencies]
@ -1306,16 +1308,17 @@ wheels = [
[[package]]
name = "jsonschema-path"
version = "0.4.2"
version = "0.3.4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pathable" },
{ name = "pyyaml" },
{ name = "referencing" },
{ name = "requests" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b3/da/1ebeb1c0ff579c330e200e8b06e6200653e3d0758136d8bd86762d63e7de/jsonschema_path-0.4.2.tar.gz", hash = "sha256:5f5ff183150030ea24bb51cf1ddac9bf5dbf030272e2792a7ffe8262f7eea2a5", size = 13417, upload-time = "2026-02-23T16:21:36.602Z" }
sdist = { url = "https://files.pythonhosted.org/packages/6e/45/41ebc679c2a4fced6a722f624c18d658dee42612b83ea24c1caf7c0eb3a8/jsonschema_path-0.3.4.tar.gz", hash = "sha256:8365356039f16cc65fddffafda5f58766e34bebab7d6d105616ab52bc4297001", size = 11159, upload-time = "2025-01-24T14:33:16.547Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e8/10/96f8fe82137979fcd1e46fff243ce7d80cd03b9e1cee8f22476ce780f38c/jsonschema_path-0.4.2-py3-none-any.whl", hash = "sha256:9c3d88e727cc4f1a88e51dbbed4211dbcd815d27799d2685efd904435c3d39e7", size = 16702, upload-time = "2026-02-23T16:21:35.119Z" },
{ url = "https://files.pythonhosted.org/packages/cb/58/3485da8cb93d2f393bce453adeef16896751f14ba3e2024bc21dc9597646/jsonschema_path-0.3.4-py3-none-any.whl", hash = "sha256:f502191fdc2b22050f9a81c9237be9d27145b9001c55842bece5e94e382e52f8", size = 14810, upload-time = "2025-01-24T14:33:14.652Z" },
]
[[package]]
@ -1506,16 +1509,16 @@ wheels = [
[[package]]
name = "msal"
version = "1.35.0"
version = "1.34.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cryptography" },
{ name = "pyjwt", extra = ["crypto"] },
{ name = "requests" },
]
sdist = { url = "https://files.pythonhosted.org/packages/95/ec/52e6c9ad90ad7eb3035f5e511123e89d1ecc7617f0c94653264848623c12/msal-1.35.0.tar.gz", hash = "sha256:76ab7513dbdac88d76abdc6a50110f082b7ed3ff1080aca938c53fc88bc75b51", size = 164057, upload-time = "2026-02-24T10:58:28.415Z" }
sdist = { url = "https://files.pythonhosted.org/packages/cf/0e/c857c46d653e104019a84f22d4494f2119b4fe9f896c92b4b864b3b045cc/msal-1.34.0.tar.gz", hash = "sha256:76ba83b716ea5a6d75b0279c0ac353a0e05b820ca1f6682c0eb7f45190c43c2f", size = 153961, upload-time = "2025-09-22T23:05:48.989Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/56/26/5463e615de18ad8b80d75d14c612ef3c866fcc07c1c52e8eac7948984214/msal-1.35.0-py3-none-any.whl", hash = "sha256:baf268172d2b736e5d409689424d2f321b4142cab231b4b96594c86762e7e01d", size = 120082, upload-time = "2026-02-24T10:58:27.219Z" },
{ url = "https://files.pythonhosted.org/packages/c2/dc/18d48843499e278538890dc709e9ee3dea8375f8be8e82682851df1b48b5/msal-1.34.0-py3-none-any.whl", hash = "sha256:f669b1644e4950115da7a176441b0e13ec2975c29528d8b9e81316023676d6e1", size = 116987, upload-time = "2025-09-22T23:05:47.294Z" },
]
[[package]]
@ -1532,7 +1535,7 @@ wheels = [
[[package]]
name = "openai"
version = "2.24.0"
version = "2.21.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
@ -1544,9 +1547,9 @@ dependencies = [
{ name = "tqdm" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/55/13/17e87641b89b74552ed408a92b231283786523edddc95f3545809fab673c/openai-2.24.0.tar.gz", hash = "sha256:1e5769f540dbd01cb33bc4716a23e67b9d695161a734aff9c5f925e2bf99a673", size = 658717, upload-time = "2026-02-24T20:02:07.958Z" }
sdist = { url = "https://files.pythonhosted.org/packages/92/e5/3d197a0947a166649f566706d7a4c8f7fe38f1fa7b24c9bcffe4c7591d44/openai-2.21.0.tar.gz", hash = "sha256:81b48ce4b8bbb2cc3af02047ceb19561f7b1dc0d4e52d1de7f02abfd15aa59b7", size = 644374, upload-time = "2026-02-14T00:12:01.577Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c9/30/844dc675ee6902579b8eef01ed23917cc9319a1c9c0c14ec6e39340c96d0/openai-2.24.0-py3-none-any.whl", hash = "sha256:fed30480d7d6c884303287bde864980a4b137b60553ffbcf9ab4a233b7a73d94", size = 1120122, upload-time = "2026-02-24T20:02:05.669Z" },
{ url = "https://files.pythonhosted.org/packages/cc/56/0a89092a453bb2c676d66abee44f863e742b2110d4dbb1dbcca3f7e5fc33/openai-2.21.0-py3-none-any.whl", hash = "sha256:0bc1c775e5b1536c294eded39ee08f8407656537ccc71b1004104fe1602e267c", size = 1103065, upload-time = "2026-02-14T00:11:59.603Z" },
]
[[package]]
@ -1663,24 +1666,24 @@ wheels = [
[[package]]
name = "pathable"
version = "0.5.0"
version = "0.4.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/72/55/b748445cb4ea6b125626f15379be7c96d1035d4fa3e8fee362fa92298abf/pathable-0.5.0.tar.gz", hash = "sha256:d81938348a1cacb525e7c75166270644782c0fb9c8cecc16be033e71427e0ef1", size = 16655, upload-time = "2026-02-20T08:47:00.748Z" }
sdist = { url = "https://files.pythonhosted.org/packages/67/93/8f2c2075b180c12c1e9f6a09d1a985bc2036906b13dff1d8917e395f2048/pathable-0.4.4.tar.gz", hash = "sha256:6905a3cd17804edfac7875b5f6c9142a218c7caef78693c2dbbbfbac186d88b2", size = 8124, upload-time = "2025-01-10T18:43:13.247Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/52/96/5a770e5c461462575474468e5af931cff9de036e7c2b4fea23c1c58d2cbe/pathable-0.5.0-py3-none-any.whl", hash = "sha256:646e3d09491a6351a0c82632a09c02cdf70a252e73196b36d8a15ba0a114f0a6", size = 16867, upload-time = "2026-02-20T08:46:59.536Z" },
{ url = "https://files.pythonhosted.org/packages/7d/eb/b6260b31b1a96386c0a880edebe26f89669098acea8e0318bff6adb378fd/pathable-0.4.4-py3-none-any.whl", hash = "sha256:5ae9e94793b6ef5a4cbe0a7ce9dbbefc1eec38df253763fd0aeeacf2762dbbc2", size = 9592, upload-time = "2025-01-10T18:43:11.88Z" },
]
[[package]]
name = "pdbpp"
version = "0.12.1"
version = "0.12.0.post1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "fancycompleter" },
{ name = "pygments" },
]
sdist = { url = "https://files.pythonhosted.org/packages/95/8d/dbc2c26f9c947e3d6df82d3ee7711fb52a92000177cea22fd3e428dc0429/pdbpp-0.12.1.tar.gz", hash = "sha256:932cc5963760105e33607f44a1a3b83d28096d7dbf055b77b527c5c70a1dafef", size = 77358, upload-time = "2026-02-23T14:23:44.993Z" }
sdist = { url = "https://files.pythonhosted.org/packages/5b/23/0bd339679059fb289e0752671b5eac63a472742d1de0035e13a27429e500/pdbpp-0.12.0.post1.tar.gz", hash = "sha256:cace9951d7414fe651141d240ab20e4509c8f97d20f7b142386d1bd1bb182d18", size = 75765, upload-time = "2026-01-19T10:56:55.026Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/05/4e/0703722c46447fa03c9425386b3ef3e90254a7c1eb5da654c3c33b210317/pdbpp-0.12.1-py3-none-any.whl", hash = "sha256:3828809519439f468c9475c4c2cbb3899f2f5ba40e79c207d90eabe4c2373f5e", size = 30659, upload-time = "2026-02-23T14:23:43.66Z" },
{ url = "https://files.pythonhosted.org/packages/28/f2/20fa391af27da4ef421832fb7d554748786efcc3b0da69a51e2ca66043d0/pdbpp-0.12.0.post1-py3-none-any.whl", hash = "sha256:8c62acd6adaa02f620d7d9cff689208a28019b6c05e6b894318f313e61fb1dc7", size = 30661, upload-time = "2026-01-19T10:56:53.638Z" },
]
[[package]]
@ -2078,21 +2081,21 @@ wheels = [
[[package]]
name = "pydantic-settings"
version = "2.13.1"
version = "2.13.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pydantic" },
{ name = "python-dotenv" },
{ name = "typing-inspection" },
]
sdist = { url = "https://files.pythonhosted.org/packages/52/6d/fffca34caecc4a3f97bda81b2098da5e8ab7efc9a66e819074a11955d87e/pydantic_settings-2.13.1.tar.gz", hash = "sha256:b4c11847b15237fb0171e1462bf540e294affb9b86db4d9aa5c01730bdbe4025", size = 223826, upload-time = "2026-02-19T13:45:08.055Z" }
sdist = { url = "https://files.pythonhosted.org/packages/96/a1/ae859ffac5a3338a66b74c5e29e244fd3a3cc483c89feaf9f56c39898d75/pydantic_settings-2.13.0.tar.gz", hash = "sha256:95d875514610e8595672800a5c40b073e99e4aae467fa7c8f9c263061ea2e1fe", size = 222450, upload-time = "2026-02-15T12:11:23.476Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/00/4b/ccc026168948fec4f7555b9164c724cf4125eac006e176541483d2c959be/pydantic_settings-2.13.1-py3-none-any.whl", hash = "sha256:d56fd801823dbeae7f0975e1f8c8e25c258eb75d278ea7abb5d9cebb01b56237", size = 58929, upload-time = "2026-02-19T13:45:06.034Z" },
{ url = "https://files.pythonhosted.org/packages/b0/1a/dd1b9d7e627486cf8e7523d09b70010e05a4bc41414f4ae6ce184cf0afb6/pydantic_settings-2.13.0-py3-none-any.whl", hash = "sha256:d67b576fff39cd086b595441bf9c75d4193ca9c0ed643b90360694d0f1240246", size = 58429, upload-time = "2026-02-15T12:11:22.133Z" },
]
[[package]]
name = "pydocket"
version = "0.17.9"
version = "0.18.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cloudpickle" },
@ -2109,10 +2112,11 @@ dependencies = [
{ name = "typer" },
{ name = "typing-extensions" },
{ name = "tzdata", marker = "sys_platform == 'win32'" },
{ name = "uncalled-for" },
]
sdist = { url = "https://files.pythonhosted.org/packages/99/e9/08c8642607b1b4b4f92798c04da625d763ad2b585ced7d91cc593d301ed3/pydocket-0.17.9.tar.gz", hash = "sha256:4b98b9951303fba2b77649969539d501500cd0b0e5accc27e03b16c25a76f3e6", size = 348534, upload-time = "2026-02-20T20:53:42.868Z" }
sdist = { url = "https://files.pythonhosted.org/packages/d2/da/5f76e42214c76402e1a2b4b59610211635c1068cab85509c78f1ca49a385/pydocket-0.18.0.tar.gz", hash = "sha256:cd5b6e7386331ca05a0163401f392b08b07e61342b5333c3ece6a7ca5435f984", size = 354637, upload-time = "2026-03-02T16:22:17.356Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ad/79/886e4db80730935f87176657aadf22f51ec9952d36ae34df9d257a9ca93d/pydocket-0.17.9-py3-none-any.whl", hash = "sha256:3f48f40d6250a33c70622b0d6c3841ed23feb3997f8e4440acd5073cd43fa044", size = 94908, upload-time = "2026-02-20T20:53:41.509Z" },
{ url = "https://files.pythonhosted.org/packages/04/57/ac0d47cd3550d859138647c2c4fbd53a2db05db8729433eaa6128e9964ba/pydocket-0.18.0-py3-none-any.whl", hash = "sha256:d995d9a3c88af0402fda640c18e1b51561041b9e3af1a92dce2fdc6c8f6c7090", size = 98848, upload-time = "2026-03-02T16:22:15.792Z" },
]
[[package]]
@ -2269,16 +2273,16 @@ wheels = [
[[package]]
name = "pytest-env"
version = "1.5.0"
version = "1.3.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pytest" },
{ name = "python-dotenv" },
{ name = "tomli", marker = "python_full_version < '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/e6/56/a931c6f6194917ff44be41b8586e2ffd13a18fa70fb28d9800a4695befa5/pytest_env-1.5.0.tar.gz", hash = "sha256:db8994b9ce170f135a37acc09ac753a6fc697d15e691b576ed8d8ca261c40246", size = 15271, upload-time = "2026-02-17T18:31:39.095Z" }
sdist = { url = "https://files.pythonhosted.org/packages/43/ad/dd32e4614fb68ad980c949fd4299f8c6a8d4874e24ec8d222c056efb4741/pytest_env-1.3.2.tar.gz", hash = "sha256:f091a2c6a8eb91befcae2b4c1bd2905a51f33bc1c6567707b7feed4e51b76b47", size = 12009, upload-time = "2026-02-11T22:09:49.168Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/61/af/99b52a8524983bfece35e51e65a0b517b22920c023e57855c95e744e19e4/pytest_env-1.5.0-py3-none-any.whl", hash = "sha256:89a15686ac837c9cd009a8a2d52bd55865e2f23c82094247915dae4540c87161", size = 10122, upload-time = "2026-02-17T18:31:37.496Z" },
{ url = "https://files.pythonhosted.org/packages/e8/ad/d793670b26f4fb82e974dbff20d05782ebb23490b08987976cdc62d854bb/pytest_env-1.3.2-py3-none-any.whl", hash = "sha256:e8626b776a035112a8ad58fcc9e04926868c58f15225de484de7c8af4b4b526c", size = 7864, upload-time = "2026-02-11T22:09:47.775Z" },
]
[[package]]
@ -2497,28 +2501,28 @@ wheels = [
[[package]]
name = "redis"
version = "7.2.1"
version = "7.2.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "async-timeout", marker = "python_full_version < '3.11.3'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/e9/31/1476f206482dd9bc53fdbbe9f6fbd5e05d153f18e54667ce839df331f2e6/redis-7.2.1.tar.gz", hash = "sha256:6163c1a47ee2d9d01221d8456bc1c75ab953cbda18cfbc15e7140e9ba16ca3a5", size = 4906735, upload-time = "2026-02-25T20:05:18.171Z" }
sdist = { url = "https://files.pythonhosted.org/packages/9f/32/6fac13a11e73e1bc67a2ae821a72bfe4c2d8c4c48f0267e4a952be0f1bae/redis-7.2.0.tar.gz", hash = "sha256:4dd5bf4bd4ae80510267f14185a15cba2a38666b941aff68cccf0256b51c1f26", size = 4901247, upload-time = "2026-02-16T17:16:22.797Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ca/98/1dd1a5c060916cf21d15e67b7d6a7078e26e2605d5c37cbc9f4f5454c478/redis-7.2.1-py3-none-any.whl", hash = "sha256:49e231fbc8df2001436ae5252b3f0f3dc930430239bfeb6da4c7ee92b16e5d33", size = 396057, upload-time = "2026-02-25T20:05:16.533Z" },
{ url = "https://files.pythonhosted.org/packages/86/cf/f6180b67f99688d83e15c84c5beda831d1d341e95872d224f87ccafafe61/redis-7.2.0-py3-none-any.whl", hash = "sha256:01f591f8598e483f1842d429e8ae3a820804566f1c73dca1b80e23af9fba0497", size = 394898, upload-time = "2026-02-16T17:16:20.693Z" },
]
[[package]]
name = "referencing"
version = "0.37.0"
version = "0.36.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "attrs" },
{ name = "rpds-py" },
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" }
sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744, upload-time = "2025-01-25T08:48:16.138Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" },
{ url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775, upload-time = "2025-01-25T08:48:14.241Z" },
]
[[package]]
@ -2538,15 +2542,15 @@ wheels = [
[[package]]
name = "rich"
version = "14.3.3"
version = "14.3.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "markdown-it-py" },
{ name = "pygments" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b3/c6/f3b320c27991c46f43ee9d856302c70dc2d0fb2dba4842ff739d5f46b393/rich-14.3.3.tar.gz", hash = "sha256:b8daa0b9e4eef54dd8cf7c86c03713f53241884e814f4e2f5fb342fe520f639b", size = 230582, upload-time = "2026-02-19T17:23:12.474Z" }
sdist = { url = "https://files.pythonhosted.org/packages/74/99/a4cab2acbb884f80e558b0771e97e21e939c5dfb460f488d19df485e8298/rich-14.3.2.tar.gz", hash = "sha256:e712f11c1a562a11843306f5ed999475f09ac31ffb64281f73ab29ffdda8b3b8", size = 230143, upload-time = "2026-02-01T16:20:47.908Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/14/25/b208c5683343959b670dc001595f2f3737e051da617f66c31f7c4fa93abc/rich-14.3.3-py3-none-any.whl", hash = "sha256:793431c1f8619afa7d3b52b2cdec859562b950ea0d4b6b505397612db8d5362d", size = 310458, upload-time = "2026-02-19T17:23:13.732Z" },
{ url = "https://files.pythonhosted.org/packages/ef/45/615f5babd880b4bd7d405cc0dc348234c5ffb6ed1ea33e152ede08b2072d/rich-14.3.2-py3-none-any.whl", hash = "sha256:08e67c3e90884651da3239ea668222d19bea7b589149d8014a21c633420dbb69", size = 309963, upload-time = "2026-02-01T16:20:46.078Z" },
]
[[package]]
@ -2686,27 +2690,27 @@ wheels = [
[[package]]
name = "ruff"
version = "0.15.3"
version = "0.15.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/3c/3b/20d9a0bc954d51b63f20cf710cf506bfe675d1e6138139342dd5ccc90326/ruff-0.15.3.tar.gz", hash = "sha256:78757853320d8ddb9da24e614ef69a37bcbcfd477e5a6435681188d4bce4eaa1", size = 4569031, upload-time = "2026-02-26T15:39:38.015Z" }
sdist = { url = "https://files.pythonhosted.org/packages/04/dc/4e6ac71b511b141cf626357a3946679abeba4cf67bc7cc5a17920f31e10d/ruff-0.15.1.tar.gz", hash = "sha256:c590fe13fb57c97141ae975c03a1aedb3d3156030cabd740d6ff0b0d601e203f", size = 4540855, upload-time = "2026-02-12T23:09:09.998Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ed/00/c544ab1d70f86dc50a2f2a8e1262e5af5025897ccd820415f559f9f2f63f/ruff-0.15.3-py3-none-linux_armv6l.whl", hash = "sha256:f7df0fd6f889a8d8de2ddb48a9eb55150954400f2157ea15b21a2f49ecaaf988", size = 10444066, upload-time = "2026-02-26T15:39:47.708Z" },
{ url = "https://files.pythonhosted.org/packages/fb/15/9dee3f4e891261adbd690f8c6f075418a7cd76e845601b00a0da2ae2ad6e/ruff-0.15.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:0198b5445197d443c3bbf2cc358f4bd477fb3951e3c7f2babc13e9bb490614a8", size = 10853125, upload-time = "2026-02-26T15:40:18.943Z" },
{ url = "https://files.pythonhosted.org/packages/88/ba/fc5aeda852c89faf821d36c951df866117342e88439e1b1e1e762a07b7fd/ruff-0.15.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:adf95b5be57b25fbbbc07cd68d37414bee8729e807ad0217219558027186967e", size = 10180833, upload-time = "2026-02-26T15:40:13.282Z" },
{ url = "https://files.pythonhosted.org/packages/06/87/e2f80a39164476fac4d45752a0d4721d6645f40b7f851e48add12af9947e/ruff-0.15.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b56dbd9cd86489ccbad96bb58fa4c958342b5510fdeb60ea13d9d3566bd845c", size = 10536806, upload-time = "2026-02-26T15:40:24.129Z" },
{ url = "https://files.pythonhosted.org/packages/fd/89/2e5bf0ed30ea3778460ea4d8cc6cb4d88ba96d9732d2c0cc33349cd65196/ruff-0.15.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c6f263ce511871955d8c5401b62c7e863988ea4d0527aa0a3b1b7ecff4d4abc4", size = 10276093, upload-time = "2026-02-26T15:39:44.654Z" },
{ url = "https://files.pythonhosted.org/packages/82/cb/318206d778c7f42917ca7b0f9436cf27652d1731fe434d3c9990c4a611fa/ruff-0.15.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e90fa1bed82ffede5768232b9bd23212c547ab7cd74c752007ecade1d895ee1a", size = 11051593, upload-time = "2026-02-26T15:39:35.157Z" },
{ url = "https://files.pythonhosted.org/packages/58/8f/65ee4c1b88e49dd4c0a3fc43e81832536c7942f0c702b6f3d25db0f95d6c/ruff-0.15.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e9d53760b7061ddbe5ea9e25381332c607fc14c40bde78f8a25392a93a68d74", size = 11885820, upload-time = "2026-02-26T15:39:59.504Z" },
{ url = "https://files.pythonhosted.org/packages/db/04/d4261f6729ad9a356bc6e3223ba297acf3b66118cef4795b4a8953b255ff/ruff-0.15.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ec90e3b78c56c4acca4264d371dd48e29215ecb673cc2fa3c4b799b72050e491", size = 11340583, upload-time = "2026-02-26T15:39:50.781Z" },
{ url = "https://files.pythonhosted.org/packages/24/84/490f38b2bc104e0fdc9496c2a66a48fb2d24a01de46ba0c60c4f6c4d4590/ruff-0.15.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7ce448fd395f822e34c8f6f7dfcd84b6726340082950858f92c4daa6baf8915", size = 11160701, upload-time = "2026-02-26T15:40:02.447Z" },
{ url = "https://files.pythonhosted.org/packages/ad/25/eae9cb7b6c28b425ed8cbe797da89c78146071102181ba74c4cdfd06bbeb/ruff-0.15.3-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:14f7d763962d385f75b9b3b57fcc5661c56c20d8b1ddc9f5c881b5fa0ba499fa", size = 11111482, upload-time = "2026-02-26T15:39:56.462Z" },
{ url = "https://files.pythonhosted.org/packages/95/18/16d0b5ef143cb9e52724f18cbccb4b3c5cd4d4e2debbd95e2be3aeb64c9e/ruff-0.15.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b57084e3a3d65418d376c7023711c37cce023cd2fb038a76ba15ee21f3c2c2ee", size = 10497151, upload-time = "2026-02-26T15:40:10.64Z" },
{ url = "https://files.pythonhosted.org/packages/bf/b4/1829314241ddba07c54a742ab387da343fe56a0267a6b6498f3e2ae99821/ruff-0.15.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d567523ff7dcf3112b0f71231d18c3506dd06943359476ee64dea0f9c8f63976", size = 10281955, upload-time = "2026-02-26T15:40:16.033Z" },
{ url = "https://files.pythonhosted.org/packages/d7/93/80a4ec4bd3cf58ca9b49dccf2bd232b520db14184912fb7e0eb6f3ecc484/ruff-0.15.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:4223088d255bf31a50b6640445b39f668164d64c23e5fa403edfb1e0b11122e5", size = 10766613, upload-time = "2026-02-26T15:40:21.55Z" },
{ url = "https://files.pythonhosted.org/packages/da/92/fe016b862295dc57499997e7f2edc58119469b210f4f03ccb763fa65f130/ruff-0.15.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:32399ddae088970b2db6efd8d3f49981375cb828075359b6c088ed1fe63d64e1", size = 11262113, upload-time = "2026-02-26T15:39:41.5Z" },
{ url = "https://files.pythonhosted.org/packages/42/b1/77dcd05940388d9ba3de03ac4b8b598826d57935728071e1be9f2ef5b714/ruff-0.15.3-py3-none-win32.whl", hash = "sha256:1f1eb95ff614351e3a89a862b6d94e6c42c170e61916e1f20facd6c38477f5f3", size = 10509423, upload-time = "2026-02-26T15:40:05.217Z" },
{ url = "https://files.pythonhosted.org/packages/29/d5/76aab0fabbd54e8c77d02fcff2494906ba85b539d22aa9b7124f7100f008/ruff-0.15.3-py3-none-win_amd64.whl", hash = "sha256:2b22dffe5f5e1e537097aa5208684f069e495f980379c4491b1cfb198a444d0c", size = 11637739, upload-time = "2026-02-26T15:39:53.951Z" },
{ url = "https://files.pythonhosted.org/packages/f2/61/9b4e3682dfd26054321e1b2fdb67a51361dd6ec2fb63f2b50d711f8832ae/ruff-0.15.3-py3-none-win_arm64.whl", hash = "sha256:82443c14d694d4cbd9e598ede27ef5d6f08389ccad91c933be775ea2f4e66f76", size = 10957794, upload-time = "2026-02-26T15:40:08.045Z" },
{ url = "https://files.pythonhosted.org/packages/23/bf/e6e4324238c17f9d9120a9d60aa99a7daaa21204c07fcd84e2ef03bb5fd1/ruff-0.15.1-py3-none-linux_armv6l.whl", hash = "sha256:b101ed7cf4615bda6ffe65bdb59f964e9f4a0d3f85cbf0e54f0ab76d7b90228a", size = 10367819, upload-time = "2026-02-12T23:09:03.598Z" },
{ url = "https://files.pythonhosted.org/packages/b3/ea/c8f89d32e7912269d38c58f3649e453ac32c528f93bb7f4219258be2e7ed/ruff-0.15.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:939c995e9277e63ea632cc8d3fae17aa758526f49a9a850d2e7e758bfef46602", size = 10798618, upload-time = "2026-02-12T23:09:22.928Z" },
{ url = "https://files.pythonhosted.org/packages/5e/0f/1d0d88bc862624247d82c20c10d4c0f6bb2f346559d8af281674cf327f15/ruff-0.15.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1d83466455fdefe60b8d9c8df81d3c1bbb2115cede53549d3b522ce2bc703899", size = 10148518, upload-time = "2026-02-12T23:08:58.339Z" },
{ url = "https://files.pythonhosted.org/packages/f5/c8/291c49cefaa4a9248e986256df2ade7add79388fe179e0691be06fae6f37/ruff-0.15.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9457e3c3291024866222b96108ab2d8265b477e5b1534c7ddb1810904858d16", size = 10518811, upload-time = "2026-02-12T23:09:31.865Z" },
{ url = "https://files.pythonhosted.org/packages/c3/1a/f5707440e5ae43ffa5365cac8bbb91e9665f4a883f560893829cf16a606b/ruff-0.15.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:92c92b003e9d4f7fbd33b1867bb15a1b785b1735069108dfc23821ba045b29bc", size = 10196169, upload-time = "2026-02-12T23:09:17.306Z" },
{ url = "https://files.pythonhosted.org/packages/2a/ff/26ddc8c4da04c8fd3ee65a89c9fb99eaa5c30394269d424461467be2271f/ruff-0.15.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fe5c41ab43e3a06778844c586251eb5a510f67125427625f9eb2b9526535779", size = 10990491, upload-time = "2026-02-12T23:09:25.503Z" },
{ url = "https://files.pythonhosted.org/packages/fc/00/50920cb385b89413f7cdb4bb9bc8fc59c1b0f30028d8bccc294189a54955/ruff-0.15.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66a6dd6df4d80dc382c6484f8ce1bcceb55c32e9f27a8b94c32f6c7331bf14fb", size = 11843280, upload-time = "2026-02-12T23:09:19.88Z" },
{ url = "https://files.pythonhosted.org/packages/5d/6d/2f5cad8380caf5632a15460c323ae326f1e1a2b5b90a6ee7519017a017ca/ruff-0.15.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a4a42cbb8af0bda9bcd7606b064d7c0bc311a88d141d02f78920be6acb5aa83", size = 11274336, upload-time = "2026-02-12T23:09:14.907Z" },
{ url = "https://files.pythonhosted.org/packages/a3/1d/5f56cae1d6c40b8a318513599b35ea4b075d7dc1cd1d04449578c29d1d75/ruff-0.15.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ab064052c31dddada35079901592dfba2e05f5b1e43af3954aafcbc1096a5b2", size = 11137288, upload-time = "2026-02-12T23:09:07.475Z" },
{ url = "https://files.pythonhosted.org/packages/cd/20/6f8d7d8f768c93b0382b33b9306b3b999918816da46537d5a61635514635/ruff-0.15.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:5631c940fe9fe91f817a4c2ea4e81f47bee3ca4aa646134a24374f3c19ad9454", size = 11070681, upload-time = "2026-02-12T23:08:55.43Z" },
{ url = "https://files.pythonhosted.org/packages/9a/67/d640ac76069f64cdea59dba02af2e00b1fa30e2103c7f8d049c0cff4cafd/ruff-0.15.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:68138a4ba184b4691ccdc39f7795c66b3c68160c586519e7e8444cf5a53e1b4c", size = 10486401, upload-time = "2026-02-12T23:09:27.927Z" },
{ url = "https://files.pythonhosted.org/packages/65/3d/e1429f64a3ff89297497916b88c32a5cc88eeca7e9c787072d0e7f1d3e1e/ruff-0.15.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:518f9af03bfc33c03bdb4cb63fabc935341bb7f54af500f92ac309ecfbba6330", size = 10197452, upload-time = "2026-02-12T23:09:12.147Z" },
{ url = "https://files.pythonhosted.org/packages/78/83/e2c3bade17dad63bf1e1c2ffaf11490603b760be149e1419b07049b36ef2/ruff-0.15.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:da79f4d6a826caaea95de0237a67e33b81e6ec2e25fc7e1993a4015dffca7c61", size = 10693900, upload-time = "2026-02-12T23:09:34.418Z" },
{ url = "https://files.pythonhosted.org/packages/a1/27/fdc0e11a813e6338e0706e8b39bb7a1d61ea5b36873b351acee7e524a72a/ruff-0.15.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3dd86dccb83cd7d4dcfac303ffc277e6048600dfc22e38158afa208e8bf94a1f", size = 11227302, upload-time = "2026-02-12T23:09:36.536Z" },
{ url = "https://files.pythonhosted.org/packages/f6/58/ac864a75067dcbd3b95be5ab4eb2b601d7fbc3d3d736a27e391a4f92a5c1/ruff-0.15.1-py3-none-win32.whl", hash = "sha256:660975d9cb49b5d5278b12b03bb9951d554543a90b74ed5d366b20e2c57c2098", size = 10462555, upload-time = "2026-02-12T23:09:29.899Z" },
{ url = "https://files.pythonhosted.org/packages/e0/5e/d4ccc8a27ecdb78116feac4935dfc39d1304536f4296168f91ed3ec00cd2/ruff-0.15.1-py3-none-win_amd64.whl", hash = "sha256:c820fef9dd5d4172a6570e5721704a96c6679b80cf7be41659ed439653f62336", size = 11599956, upload-time = "2026-02-12T23:09:01.157Z" },
{ url = "https://files.pythonhosted.org/packages/2a/07/5bda6a85b220c64c65686bc85bd0bbb23b29c62b3a9f9433fa55f17cda93/ruff-0.15.1-py3-none-win_arm64.whl", hash = "sha256:5ff7d5f0f88567850f45081fac8f4ec212be8d0b963e385c3f7d0d2eb4899416", size = 10874604, upload-time = "2026-02-12T23:09:05.515Z" },
]
[[package]]
@ -2888,31 +2892,31 @@ wheels = [
[[package]]
name = "ty"
version = "0.0.19"
version = "0.0.17"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/84/5e/da108b9eeb392e02ff0478a34e9651490b36af295881cb56575b83f0cc3a/ty-0.0.19.tar.gz", hash = "sha256:ee3d9ed4cb586e77f6efe3d0fe5a855673ca438a3d533a27598e1d3502a2948a", size = 5220026, upload-time = "2026-02-26T12:13:15.215Z" }
sdist = { url = "https://files.pythonhosted.org/packages/66/c3/41ae6346443eedb65b96761abfab890a48ce2aa5a8a27af69c5c5d99064d/ty-0.0.17.tar.gz", hash = "sha256:847ed6c120913e280bf9b54d8eaa7a1049708acb8824ad234e71498e8ad09f97", size = 5167209, upload-time = "2026-02-13T13:26:36.835Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5a/31/fd8c6067abb275bea11523d21ecf64e1d870b1ce80cac529cf6636df1471/ty-0.0.19-py3-none-linux_armv6l.whl", hash = "sha256:29bed05d34c8a7597567b8e327c53c1aed4a07dcfbe6c81e6d60c7444936ad77", size = 10268470, upload-time = "2026-02-26T12:13:42.881Z" },
{ url = "https://files.pythonhosted.org/packages/15/de/16a11bbf7d98c75849fc41f5d008b89bb5d080a4b10dc8ea851ee2bd371b/ty-0.0.19-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:79140870c688c97ec68e723c28935ddef9d91a76d48c68e665fe7c851e628b8a", size = 10098562, upload-time = "2026-02-26T12:13:31.618Z" },
{ url = "https://files.pythonhosted.org/packages/e7/4f/086d6ff6686eadf903913c45b53ab96694b62bbfee1d8cf3e55a9b5aa4b2/ty-0.0.19-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6e9c1f9cfa6a26f7881d14d75cf963af743f6c4189e6aa3e3b4056a65f22e730", size = 9604073, upload-time = "2026-02-26T12:13:24.645Z" },
{ url = "https://files.pythonhosted.org/packages/95/13/888a6b6c7ed4a880fee91bec997f775153ce86215ee4c56b868516314734/ty-0.0.19-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbca43b050edf1db2e64ae7b79add233c2aea2855b8a876081bbd032edcd0610", size = 10106295, upload-time = "2026-02-26T12:13:40.584Z" },
{ url = "https://files.pythonhosted.org/packages/cb/e8/05a372cae8da482de73b8246fb43236bf11e24ac28c879804568108759db/ty-0.0.19-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8acaa88ab1955ca6b15a0ccc274011c4961377fe65c3948e5d2b212f2517b87c", size = 10098234, upload-time = "2026-02-26T12:13:33.725Z" },
{ url = "https://files.pythonhosted.org/packages/c5/f1/5b0958e9e9576e7662192fe689bbb3dc88e631a4e073db3047793a547d58/ty-0.0.19-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a901b6a6dd9d17d5b3b2e7bafc3057294e88da3f5de507347316687d7f191a1", size = 10607218, upload-time = "2026-02-26T12:13:17.576Z" },
{ url = "https://files.pythonhosted.org/packages/fb/ab/358c78b77844f58ff5aca368550ab16c719f1ab0ec892ceb1114d7500f4e/ty-0.0.19-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8deafdaaaee65fd121c66064da74a922d8501be4a2d50049c71eab521a23eff7", size = 11160593, upload-time = "2026-02-26T12:13:36.008Z" },
{ url = "https://files.pythonhosted.org/packages/95/59/827fc346d66a59fe48e9689a5ceb67dbbd5b4de2e8d4625371af39a2e8b7/ty-0.0.19-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:035e56071af280897441018f74f921b97d53aec0856f8af85f4f949df8eda07d", size = 10822392, upload-time = "2026-02-26T12:13:29.415Z" },
{ url = "https://files.pythonhosted.org/packages/81/f9/3bbfbbe35478de9bcd63848f4bc9bffda72278dd9732dbad3efc3978432e/ty-0.0.19-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abdf5885130393ce74501dba792f48ce0a515756ec81c33a4b324bdf3509df6e", size = 10707139, upload-time = "2026-02-26T12:13:20.148Z" },
{ url = "https://files.pythonhosted.org/packages/12/9e/597023b183ec4ade83a36a0cea5c103f3bffa34f70813d46386c61447fb8/ty-0.0.19-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:877e89005c8f9d1dbff5ad14cbac9f35c528406fde38926f9b44f24830de8d6a", size = 10096933, upload-time = "2026-02-26T12:13:45.266Z" },
{ url = "https://files.pythonhosted.org/packages/1e/76/d0d2f6e674db2a17c8efa5e26682b9dfa8d34774705f35902a7b45ebd3bd/ty-0.0.19-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:39bd1da051c1e4d316efaf79dbed313255633f7c6ad6e24d29f4d9c6ffaf4de6", size = 10109547, upload-time = "2026-02-26T12:13:22.17Z" },
{ url = "https://files.pythonhosted.org/packages/a4/b0/76026c06b852a3aa4fdb5bd329fdc2175aaf3c64a3fafece9cc4df167cee/ty-0.0.19-py3-none-musllinux_1_2_i686.whl", hash = "sha256:87df8415a6c9cb27b8f1382fcdc6052e59f5b9f50f78bc14663197eb5c8d3699", size = 10289110, upload-time = "2026-02-26T12:13:38.29Z" },
{ url = "https://files.pythonhosted.org/packages/14/6c/f3b3a189816b4f079b20fe5d0d7ee38e38a472f53cc6770bb6571147e3de/ty-0.0.19-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:89b6bb23c332ed5c38dd859eb5793f887abcc936f681a40d4ea68e35eac1af33", size = 10796479, upload-time = "2026-02-26T12:13:10.992Z" },
{ url = "https://files.pythonhosted.org/packages/3d/18/caee33d1ce9dd50bd94c26cde7cda4f6971e22e474e7d72a5c86d745ad58/ty-0.0.19-py3-none-win32.whl", hash = "sha256:19b33df3aa7af7b1a9eaa4e1175c3b4dec0f5f2e140243e3492c8355c37418f3", size = 9677215, upload-time = "2026-02-26T12:13:08.519Z" },
{ url = "https://files.pythonhosted.org/packages/81/41/18fc0771d0b1da7d7cc2fc9af278d3122b754fe8b521a748734f4e16ecfd/ty-0.0.19-py3-none-win_amd64.whl", hash = "sha256:b9052c61464cdd76bc8e6796f2588c08700f25d0dcbc225bb165e390ea9d96a4", size = 10651252, upload-time = "2026-02-26T12:13:13.035Z" },
{ url = "https://files.pythonhosted.org/packages/8b/8c/26f7ce8863eb54510082747b3dfb1046ba24f16fc11de18c0e5feb36ff18/ty-0.0.19-py3-none-win_arm64.whl", hash = "sha256:9329804b66dcbae8e7af916ef4963221ed53b8ec7d09b0793591c5ae8a0f3270", size = 10093195, upload-time = "2026-02-26T12:13:26.816Z" },
{ url = "https://files.pythonhosted.org/packages/c0/01/0ef15c22a1c54b0f728ceff3f62d478dbf8b0dcf8ff7b80b954f79584f3e/ty-0.0.17-py3-none-linux_armv6l.whl", hash = "sha256:64a9a16555cc8867d35c2647c2f1afbd3cae55f68fd95283a574d1bb04fe93e0", size = 10192793, upload-time = "2026-02-13T13:27:13.943Z" },
{ url = "https://files.pythonhosted.org/packages/0f/2c/f4c322d9cded56edc016b1092c14b95cf58c8a33b4787316ea752bb9418e/ty-0.0.17-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:eb2dbd8acd5c5a55f4af0d479523e7c7265a88542efe73ed3d696eb1ba7b6454", size = 10051977, upload-time = "2026-02-13T13:26:57.741Z" },
{ url = "https://files.pythonhosted.org/packages/4c/a5/43746c1ff81e784f5fc303afc61fe5bcd85d0fcf3ef65cb2cef78c7486c7/ty-0.0.17-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f18f5fd927bc628deb9ea2df40f06b5f79c5ccf355db732025a3e8e7152801f6", size = 9564639, upload-time = "2026-02-13T13:26:42.781Z" },
{ url = "https://files.pythonhosted.org/packages/d6/b8/280b04e14a9c0474af574f929fba2398b5e1c123c1e7735893b4cd73d13c/ty-0.0.17-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5383814d1d7a5cc53b3b07661856bab04bb2aac7a677c8d33c55169acdaa83df", size = 10061204, upload-time = "2026-02-13T13:27:00.152Z" },
{ url = "https://files.pythonhosted.org/packages/2a/d7/493e1607d8dfe48288d8a768a2adc38ee27ef50e57f0af41ff273987cda0/ty-0.0.17-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c20423b8744b484f93e7bf2ef8a9724bca2657873593f9f41d08bd9f83444c9", size = 10013116, upload-time = "2026-02-13T13:26:34.543Z" },
{ url = "https://files.pythonhosted.org/packages/80/ef/22f3ed401520afac90dbdf1f9b8b7755d85b0d5c35c1cb35cf5bd11b59c2/ty-0.0.17-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6f5b1aba97db9af86517b911674b02f5bc310750485dc47603a105bd0e83ddd", size = 10533623, upload-time = "2026-02-13T13:26:31.449Z" },
{ url = "https://files.pythonhosted.org/packages/75/ce/744b15279a11ac7138832e3a55595706b4a8a209c9f878e3ab8e571d9032/ty-0.0.17-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:488bce1a9bea80b851a97cd34c4d2ffcd69593d6c3f54a72ae02e5c6e47f3d0c", size = 11069750, upload-time = "2026-02-13T13:26:48.638Z" },
{ url = "https://files.pythonhosted.org/packages/f2/be/1133c91f15a0e00d466c24f80df486d630d95d1b2af63296941f7473812f/ty-0.0.17-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8df66b91ec84239420985ec215e7f7549bfda2ac036a3b3c065f119d1c06825a", size = 10870862, upload-time = "2026-02-13T13:26:54.715Z" },
{ url = "https://files.pythonhosted.org/packages/3e/4a/a2ed209ef215b62b2d3246e07e833081e07d913adf7e0448fc204be443d6/ty-0.0.17-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:002139e807c53002790dfefe6e2f45ab0e04012e76db3d7c8286f96ec121af8f", size = 10628118, upload-time = "2026-02-13T13:26:45.439Z" },
{ url = "https://files.pythonhosted.org/packages/b3/0c/87476004cb5228e9719b98afffad82c3ef1f84334bde8527bcacba7b18cb/ty-0.0.17-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6c4e01f05ce82e5d489ab3900ca0899a56c4ccb52659453780c83e5b19e2b64c", size = 10038185, upload-time = "2026-02-13T13:27:02.693Z" },
{ url = "https://files.pythonhosted.org/packages/46/4b/98f0b3ba9aef53c1f0305519536967a4aa793a69ed72677b0a625c5313ac/ty-0.0.17-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:2b226dd1e99c0d2152d218c7e440150d1a47ce3c431871f0efa073bbf899e881", size = 10047644, upload-time = "2026-02-13T13:27:05.474Z" },
{ url = "https://files.pythonhosted.org/packages/93/e0/06737bb80aa1a9103b8651d2eb691a7e53f1ed54111152be25f4a02745db/ty-0.0.17-py3-none-musllinux_1_2_i686.whl", hash = "sha256:8b11f1da7859e0ad69e84b3c5ef9a7b055ceed376a432fad44231bdfc48061c2", size = 10231140, upload-time = "2026-02-13T13:27:10.844Z" },
{ url = "https://files.pythonhosted.org/packages/7c/79/e2a606bd8852383ba9abfdd578f4a227bd18504145381a10a5f886b4e751/ty-0.0.17-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:c04e196809ff570559054d3e011425fd7c04161529eb551b3625654e5f2434cb", size = 10718344, upload-time = "2026-02-13T13:26:51.66Z" },
{ url = "https://files.pythonhosted.org/packages/c5/2d/2663984ac11de6d78f74432b8b14ba64d170b45194312852b7543cf7fd56/ty-0.0.17-py3-none-win32.whl", hash = "sha256:305b6ed150b2740d00a817b193373d21f0767e10f94ac47abfc3b2e5a5aec809", size = 9672932, upload-time = "2026-02-13T13:27:08.522Z" },
{ url = "https://files.pythonhosted.org/packages/de/b5/39be78f30b31ee9f5a585969930c7248354db90494ff5e3d0756560fb731/ty-0.0.17-py3-none-win_amd64.whl", hash = "sha256:531828267527aee7a63e972f54e5eee21d9281b72baf18e5c2850c6b862add83", size = 10542138, upload-time = "2026-02-13T13:27:17.084Z" },
{ url = "https://files.pythonhosted.org/packages/40/b7/f875c729c5d0079640c75bad2c7e5d43edc90f16ba242f28a11966df8f65/ty-0.0.17-py3-none-win_arm64.whl", hash = "sha256:de9810234c0c8d75073457e10a84825b9cd72e6629826b7f01c7a0b266ae25b1", size = 10023068, upload-time = "2026-02-13T13:26:39.637Z" },
]
[[package]]
name = "typer"
version = "0.24.1"
version = "0.23.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "annotated-doc" },
@ -2920,9 +2924,9 @@ dependencies = [
{ name = "rich" },
{ name = "shellingham" },
]
sdist = { url = "https://files.pythonhosted.org/packages/f5/24/cb09efec5cc954f7f9b930bf8279447d24618bb6758d4f6adf2574c41780/typer-0.24.1.tar.gz", hash = "sha256:e39b4732d65fbdcde189ae76cf7cd48aeae72919dea1fdfc16593be016256b45", size = 118613, upload-time = "2026-02-21T16:54:40.609Z" }
sdist = { url = "https://files.pythonhosted.org/packages/d3/ae/93d16574e66dfe4c2284ffdaca4b0320ade32858cb2cc586c8dd79f127c5/typer-0.23.2.tar.gz", hash = "sha256:a99706a08e54f1aef8bb6a8611503808188a4092808e86addff1828a208af0de", size = 120162, upload-time = "2026-02-16T18:52:40.354Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4a/91/48db081e7a63bb37284f9fbcefda7c44c277b18b0e13fbc36ea2335b71e6/typer-0.24.1-py3-none-any.whl", hash = "sha256:112c1f0ce578bfb4cab9ffdabc68f031416ebcc216536611ba21f04e9aa84c9e", size = 56085, upload-time = "2026-02-21T16:54:41.616Z" },
{ url = "https://files.pythonhosted.org/packages/14/2c/dee705c427875402200fe779eb8a3c00ccb349471172c41178336e9599cc/typer-0.23.2-py3-none-any.whl", hash = "sha256:e9c8dc380f82450b3c851a9b9d5a0edf95d1d6456ae70c517d8b06a50c7a9978", size = 56834, upload-time = "2026-02-16T18:52:39.308Z" },
]
[[package]]
@ -2955,6 +2959,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" },
]
[[package]]
name = "uncalled-for"
version = "0.2.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/02/7c/b5b7d8136f872e3f13b0584e576886de0489d7213a12de6bebf29ff6ebfc/uncalled_for-0.2.0.tar.gz", hash = "sha256:b4f8fdbcec328c5a113807d653e041c5094473dd4afa7c34599ace69ccb7e69f", size = 49488, upload-time = "2026-02-27T17:40:58.137Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ff/7f/4320d9ce3be404e6310b915c3629fe27bf1e2f438a1a7a3cb0396e32e9a9/uncalled_for-0.2.0-py3-none-any.whl", hash = "sha256:2c0bd338faff5f930918f79e7eb9ff48290df2cb05fcc0b40a7f334e55d4d85f", size = 11351, upload-time = "2026-02-27T17:40:56.804Z" },
]
[[package]]
name = "urllib3"
version = "2.6.3"
@ -2966,16 +2979,16 @@ wheels = [
[[package]]
name = "uvicorn"
version = "0.41.0"
version = "0.40.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click" },
{ name = "h11" },
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/32/ce/eeb58ae4ac36fe09e3842eb02e0eb676bf2c53ae062b98f1b2531673efdd/uvicorn-0.41.0.tar.gz", hash = "sha256:09d11cf7008da33113824ee5a1c6422d89fbc2ff476540d69a34c87fab8b571a", size = 82633, upload-time = "2026-02-16T23:07:24.1Z" }
sdist = { url = "https://files.pythonhosted.org/packages/c3/d1/8f3c683c9561a4e6689dd3b1d345c815f10f86acd044ee1fb9a4dcd0b8c5/uvicorn-0.40.0.tar.gz", hash = "sha256:839676675e87e73694518b5574fd0f24c9d97b46bea16df7b8c05ea1a51071ea", size = 81761, upload-time = "2025-12-21T14:16:22.45Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/83/e4/d04a086285c20886c0daad0e026f250869201013d18f81d9ff5eada73a88/uvicorn-0.41.0-py3-none-any.whl", hash = "sha256:29e35b1d2c36a04b9e180d4007ede3bcb32a85fbdfd6c6aeb3f26839de088187", size = 68783, upload-time = "2026-02-16T23:07:22.357Z" },
{ url = "https://files.pythonhosted.org/packages/3d/d8/2083a1daa7439a66f3a48589a57d576aa117726762618f6bb09fe3798796/uvicorn-0.40.0-py3-none-any.whl", hash = "sha256:c6c8f55bc8bf13eb6fa9ff87ad62308bbbc33d0b67f84293151efe87e0d5f2ee", size = 68502, upload-time = "2025-12-21T14:16:21.041Z" },
]
[[package]]