mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-23 22:14:18 +02:00
184 lines
5.7 KiB
Python
184 lines
5.7 KiB
Python
"""Docket-specific dependency injection for FastMCP background tasks.
|
|
|
|
Moved out of ``fastmcp.server.dependencies`` during the SEP-1686 -> SEP-2663
|
|
migration. These helpers are all docket-touching: the ``require_docket``
|
|
install-hint, the docket/worker ContextVars, and the ``CurrentDocket`` /
|
|
``CurrentWorker`` dependencies. Everything here is wire-agnostic engine plumbing
|
|
that ``TasksExtension`` drives.
|
|
|
|
The generic ``is_docket_available`` probe stays in ``fastmcp.server.dependencies``
|
|
(core's ``Context``/``Progress`` still use it) and is re-exported here for the
|
|
tasks package's callers.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib.metadata
|
|
from contextvars import ContextVar
|
|
from types import TracebackType
|
|
from typing import TYPE_CHECKING, cast
|
|
|
|
from uncalled_for import Dependency
|
|
|
|
from fastmcp.server.dependencies import (
|
|
_MIN_DOCKET_VERSION,
|
|
get_server,
|
|
is_docket_available,
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
from docket import Docket
|
|
from docket.worker import Worker
|
|
|
|
__all__ = [
|
|
"CurrentDocket",
|
|
"CurrentWorker",
|
|
"is_docket_available",
|
|
"require_docket",
|
|
]
|
|
|
|
|
|
_current_docket: ContextVar[Docket | None] = ContextVar("docket", default=None)
|
|
_current_worker: ContextVar[Worker | None] = ContextVar("worker", default=None)
|
|
|
|
|
|
def require_docket(feature: str) -> None:
|
|
"""Raise ImportError with install instructions if docket not available.
|
|
|
|
Args:
|
|
feature: Description of what requires docket (e.g., "`task=True`",
|
|
"CurrentDocket()"). Will be included in the error message.
|
|
"""
|
|
if is_docket_available():
|
|
return
|
|
|
|
try:
|
|
installed = importlib.metadata.version("pydocket")
|
|
except importlib.metadata.PackageNotFoundError:
|
|
installed = None
|
|
|
|
if installed is None:
|
|
detail = (
|
|
"FastMCP background tasks require the `tasks` extra. "
|
|
"Install with: pip install 'fastmcp[tasks]'."
|
|
)
|
|
else:
|
|
detail = (
|
|
f"FastMCP background tasks require pydocket>={_MIN_DOCKET_VERSION}, "
|
|
f"but pydocket {installed} is installed (likely pulled in by another "
|
|
f"package). Upgrade with: pip install -U 'pydocket>={_MIN_DOCKET_VERSION}'."
|
|
)
|
|
|
|
raise ImportError(f"{detail} (Triggered by {feature})")
|
|
|
|
|
|
class _CurrentDocket(Dependency["Docket"]):
|
|
"""Async context manager for Docket dependency."""
|
|
|
|
async def __aenter__(self) -> Docket:
|
|
require_docket("CurrentDocket()")
|
|
# Check server instance first, fall back to ContextVar for mounted children
|
|
# whose parent owns the Docket
|
|
try:
|
|
docket = get_server()._docket
|
|
except RuntimeError:
|
|
docket = None
|
|
if docket is None:
|
|
docket = _current_docket.get()
|
|
if docket is None:
|
|
raise RuntimeError(
|
|
"No Docket instance found. Docket is only initialized when there are "
|
|
"task-enabled components (task=True). Add task=True to a component "
|
|
"to enable Docket infrastructure."
|
|
)
|
|
return docket
|
|
|
|
async def __aexit__(
|
|
self,
|
|
exc_type: type[BaseException] | None,
|
|
exc_value: BaseException | None,
|
|
traceback: TracebackType | None,
|
|
) -> None:
|
|
pass
|
|
|
|
|
|
def CurrentDocket() -> Docket:
|
|
"""Get the current Docket instance managed by FastMCP.
|
|
|
|
This dependency provides access to the Docket instance that FastMCP
|
|
automatically creates for background task scheduling.
|
|
|
|
Returns:
|
|
A dependency that resolves to the active Docket instance
|
|
|
|
Raises:
|
|
RuntimeError: If not within a FastMCP server context
|
|
ImportError: If fastmcp[tasks] not installed
|
|
|
|
Example:
|
|
```python
|
|
from fastmcp_tasks.dependencies import CurrentDocket
|
|
|
|
@mcp.tool()
|
|
async def schedule_task(docket: Docket = CurrentDocket()) -> str:
|
|
await docket.add(some_function)(arg1, arg2)
|
|
return "Scheduled"
|
|
```
|
|
"""
|
|
require_docket("CurrentDocket()")
|
|
return cast("Docket", _CurrentDocket())
|
|
|
|
|
|
class _CurrentWorker(Dependency["Worker"]):
|
|
"""Async context manager for Worker dependency."""
|
|
|
|
async def __aenter__(self) -> Worker:
|
|
require_docket("CurrentWorker()")
|
|
# Check server instance first, fall back to ContextVar for mounted children
|
|
try:
|
|
worker = get_server()._worker
|
|
except RuntimeError:
|
|
worker = None
|
|
if worker is None:
|
|
worker = _current_worker.get()
|
|
if worker is None:
|
|
raise RuntimeError(
|
|
"No Worker instance found. Worker is only initialized when there are "
|
|
"task-enabled components (task=True). Add task=True to a component "
|
|
"to enable Docket infrastructure."
|
|
)
|
|
return worker
|
|
|
|
async def __aexit__(
|
|
self,
|
|
exc_type: type[BaseException] | None,
|
|
exc_value: BaseException | None,
|
|
traceback: TracebackType | None,
|
|
) -> None:
|
|
pass
|
|
|
|
|
|
def CurrentWorker() -> Worker:
|
|
"""Get the current Docket Worker instance managed by FastMCP.
|
|
|
|
This dependency provides access to the Worker instance that FastMCP
|
|
automatically creates for background task processing.
|
|
|
|
Returns:
|
|
A dependency that resolves to the active Worker instance
|
|
|
|
Raises:
|
|
RuntimeError: If not within a FastMCP server context
|
|
ImportError: If fastmcp[tasks] not installed
|
|
|
|
Example:
|
|
```python
|
|
from fastmcp_tasks.dependencies import CurrentWorker
|
|
|
|
@mcp.tool()
|
|
async def check_worker_status(worker: Worker = CurrentWorker()) -> str:
|
|
return f"Worker: {worker.name}"
|
|
```
|
|
"""
|
|
require_docket("CurrentWorker()")
|
|
return cast("Worker", _CurrentWorker())
|