mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-23 22:14:18 +02:00
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:
parent
5fb72c7200
commit
547daf7a36
19 changed files with 589 additions and 575 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 = [
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
"""Vendored third-party code for FastMCP."""
|
||||
|
|
@ -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.
|
||||
|
|
@ -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",
|
||||
]
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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"])
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
209
uv.lock
generated
|
|
@ -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]]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue