Fix broken code examples in docs (#3869)

* Fix broken code examples in docs

- Tag error output blocks as ```text instead of ```python (anthropic,
  openai integration docs + v2 mirrors)
- Quote unquoted URL in Descope config example (+ v2 mirror)
- Fix GoogleGenAISamplingHandler → GoogleGenaiSamplingHandler casing
  in sampling docs
- Fix import path: handlers.GoogleGenaiSamplingHandler →
  handlers.google_genai.GoogleGenaiSamplingHandler in v3-features

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix remaining broken doc examples and add skip tags for false positives

- BearerTokenAuth → StaticTokenVerifier in deployment/http.mdx
- providers.oauth → server.auth import in authentication.mdx
- ListToolsNext → updated list_tools API in v3-features.mdx
- OAuthClientProvider → OAuth in v2/storage-backends.mdx
- Add test="skip" for upgrade guides, contrib placeholders, f-string backticks

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Ratchet doc example baselines to zero

All 1444 examples now pass syntax and import checks.

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Add pytest-examples dev dep, fix client_id in StaticTokenVerifier example, commit missed openapi fixes

- Add pytest-examples to dev dependencies (fixes CI ModuleNotFoundError)
- Include required client_id in StaticTokenVerifier token payload
- Commit previously unstaged HTTPRoute import fixes in openapi.mdx

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Update deprecated import paths across docs

- fastmcp.server.openapi → fastmcp.server.providers.openapi
- fastmcp.server.proxy → fastmcp.server.providers.proxy
- fastmcp.server.apps → fastmcp.apps
- Tag upgrade guide "Before" examples with test="skip"

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bill Easton 2026-04-12 11:53:04 -05:00 committed by GitHub
commit 901453902f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 337 additions and 72 deletions

View file

@ -155,11 +155,11 @@ Install the Anthropic handler with `pip install fastmcp[anthropic]`.
```python
from fastmcp import Client
from fastmcp.client.sampling.handlers.google_genai import GoogleGenAISamplingHandler
from fastmcp.client.sampling.handlers.google_genai import GoogleGenaiSamplingHandler
client = Client(
"my_mcp_server.py",
sampling_handler=GoogleGenAISamplingHandler(default_model="gemini-2.0-flash"),
sampling_handler=GoogleGenaiSamplingHandler(default_model="gemini-2.0-flash"),
)
```

View file

@ -660,17 +660,17 @@ FASTMCP_STATELESS_HTTP=true uvicorn app:app --host 0.0.0.0 --port 8000 --workers
Production deployments should never hardcode sensitive information like API keys or authentication tokens. Instead, use environment variables to configure your server at runtime. This keeps your code secure and makes it easy to deploy the same code to different environments with different configurations.
Here's an example using bearer token authentication (though OAuth is recommended for production):
Here's an example using static token authentication for development (OAuth is recommended for production):
```python
import os
from fastmcp import FastMCP
from fastmcp.server.auth import BearerTokenAuth
from fastmcp.server.auth import StaticTokenVerifier
# Read configuration from environment
auth_token = os.environ.get("MCP_AUTH_TOKEN")
if auth_token:
auth = BearerTokenAuth(token=auth_token)
auth = StaticTokenVerifier(tokens={auth_token: {"sub": "admin", "client_id": "cli"}})
mcp = FastMCP("Production Server", auth=auth)
else:
mcp = FastMCP("Production Server")

View file

@ -29,7 +29,7 @@ FastMCP now includes a sampling handler for Google's Gemini models ([#2977](http
```python
from fastmcp import Client
from fastmcp.client.sampling.handlers import GoogleGenaiSamplingHandler
from fastmcp.client.sampling.handlers.google_genai import GoogleGenaiSamplingHandler
from google.genai import Client as GoogleGenaiClient
# Initialize the handler
@ -386,7 +386,7 @@ Support for [MCP Apps](https://modelcontextprotocol.io/specification/2025-06-18/
```python
from fastmcp import FastMCP
from fastmcp.server.apps import AppConfig, ResourceCSP, ResourcePermissions
from fastmcp.apps import AppConfig, ResourceCSP, ResourcePermissions
mcp = FastMCP("My Server")
@ -421,7 +421,7 @@ The `app=` parameter accepts `True` (enable with defaults), an `AppConfig` insta
```python
from fastmcp import Context
from fastmcp.server.apps import AppConfig, UI_EXTENSION_ID
from fastmcp.apps import AppConfig, UI_EXTENSION_ID
@mcp.tool(app=AppConfig(resource_uri="ui://dashboard"))
async def dashboard(ctx: Context) -> dict:
@ -575,15 +575,14 @@ provider.add_transform(ToolTransform({
```python
from collections.abc import Sequence
from fastmcp.server.transforms import Transform, ListToolsNext, GetToolNext
from fastmcp.server.transforms import Transform, GetToolNext
from fastmcp.tools import Tool
class TagFilter(Transform):
def __init__(self, required_tags: set[str]):
self.required_tags = required_tags
async def list_tools(self, call_next: ListToolsNext) -> Sequence[Tool]:
tools = await call_next() # Get tools from downstream
async def list_tools(self, tools: Sequence[Tool]) -> Sequence[Tool]:
return [t for t in tools if t.tags & self.required_tags]
async def get_tool(self, name: str, call_next: GetToolNext) -> Tool | None:

View file

@ -264,7 +264,7 @@ auth = GitHubProvider(
The deprecated WebSocket client transport has been removed. Use `StreamableHttpTransport` instead:
```python
```python test="skip"
# Before
from fastmcp.client.transports import WSTransport
transport = WSTransport("ws://localhost:8000/ws")
@ -357,7 +357,7 @@ main.mount(subserver)
The proxy and OpenAPI modules have moved under `providers` to reflect v3's provider-based architecture:
```python
```python test="skip"
# Deprecated
from fastmcp.server.proxy import FastMCPProxy
from fastmcp.server.openapi import FastMCPOpenAPI
@ -369,7 +369,7 @@ from fastmcp.server.providers.openapi import OpenAPIProvider
`FastMCPOpenAPI` itself is deprecated — use `FastMCP` with an `OpenAPIProvider` instead:
```python
```python test="skip"
# Deprecated
from fastmcp.server.openapi import FastMCPOpenAPI
server = FastMCPOpenAPI(spec, client)
@ -408,7 +408,7 @@ proxy = create_proxy("http://example.com/mcp")
The experimental OpenAPI parser is now standard. Update imports:
```python
```python test="skip"
# Before
from fastmcp.experimental.server.openapi import FastMCPOpenAPI

View file

@ -181,7 +181,7 @@ if __name__ == "__main__":
If you try to call the authenticated server with the same Anthropic code we wrote earlier, you'll get an error indicating that the server rejected the request because it's not authenticated.
```python
```text
Error code: 400 - {
"type": "error",
"error": {

View file

@ -63,8 +63,8 @@ from fastmcp.server.auth.providers.descope import DescopeProvider
# The DescopeProvider automatically discovers Descope endpoints
# and configures JWT token validation
auth_provider = DescopeProvider(
config_url=https://.../.well-known/openid-configuration, # Your MCP Server .well-known URL
base_url=SERVER_URL, # Your server's public URL
config_url="https://.../.well-known/openid-configuration", # Your MCP Server .well-known URL
base_url=SERVER_URL, # Your server's public URL
)
# Create FastMCP server with auth

View file

@ -220,7 +220,7 @@ Because FastMCP's FastAPI integration is based on its [OpenAPI integration](/int
```python
# Assumes the FastAPI app from above is already defined
from fastmcp import FastMCP
from fastmcp.server.openapi import RouteMap, MCPType
from fastmcp.server.providers.openapi import RouteMap, MCPType
# Custom mapping rules
mcp = FastMCP.from_fastapi(

View file

@ -178,8 +178,8 @@ if __name__ == "__main__":
If you try to call the authenticated server with the same OpenAI code we wrote earlier, you'll get an error like this:
```python
pythonAPIStatusError: Error code: 424 - {
```text
APIStatusError: Error code: 424 - {
"error": {
"message": "Error retrieving tool list from MCP server: 'dice_server'. Http status code: 401 (Unauthorized)",
"type": "external_connector_error",

View file

@ -85,7 +85,7 @@ Each `RouteMap` specifies a combination of methods, patterns, and tags, as well
Here is FastMCP's default rule:
```python
from fastmcp.server.openapi import RouteMap, MCPType
from fastmcp.server.providers.openapi import RouteMap, MCPType
DEFAULT_ROUTE_MAPPINGS = [
# All routes become tools
@ -101,7 +101,7 @@ For example, prior to FastMCP 2.8.0, GET requests were automatically mapped to `
```python
from fastmcp import FastMCP
from fastmcp.server.openapi import RouteMap, MCPType
from fastmcp.server.providers.openapi import RouteMap, MCPType
# Restore pre-2.8.0 semantic mapping
semantic_maps = [
@ -124,7 +124,7 @@ Here is a more complete example that uses custom route maps to convert all `GET`
```python
from fastmcp import FastMCP
from fastmcp.server.openapi import RouteMap, MCPType
from fastmcp.server.providers.openapi import RouteMap, MCPType
mcp = FastMCP.from_openapi(
openapi_spec=spec,
@ -164,7 +164,7 @@ You can use this to remove sensitive or internal routes by targeting them specif
```python
from fastmcp import FastMCP
from fastmcp.server.openapi import RouteMap, MCPType
from fastmcp.server.providers.openapi import RouteMap, MCPType
mcp = FastMCP.from_openapi(
openapi_spec=spec,
@ -180,7 +180,7 @@ Or you can use a catch-all rule to exclude everything that your maps don't handl
```python
from fastmcp import FastMCP
from fastmcp.server.openapi import RouteMap, MCPType
from fastmcp.server.providers.openapi import RouteMap, MCPType
mcp = FastMCP.from_openapi(
openapi_spec=spec,
@ -212,7 +212,8 @@ The `route_map_fn` is called on all routes, even those that matched `MCPType.EXC
```python
from fastmcp import FastMCP
from fastmcp.server.openapi import RouteMap, MCPType, HTTPRoute
from fastmcp.server.providers.openapi import RouteMap, MCPType
from fastmcp.utilities.openapi import HTTPRoute
def custom_route_mapper(route: HTTPRoute, mcp_type: MCPType) -> MCPType | None:
"""Advanced route type mapping."""
@ -277,7 +278,7 @@ FastMCP provides several ways to add tags to your MCP components, allowing you t
You can add custom tags to components created from specific routes using the `mcp_tags` parameter in `RouteMap`. These tags will be applied to all components created from routes that match that particular route map.
```python
from fastmcp.server.openapi import RouteMap, MCPType
from fastmcp.server.providers.openapi import RouteMap, MCPType
mcp = FastMCP.from_openapi(
openapi_spec=spec,
@ -368,12 +369,12 @@ Your `mcp_component_fn` is expected to modify the component in-place, not to ret
</Tip>
```python
from fastmcp.server.openapi import (
HTTPRoute,
from fastmcp.server.providers.openapi import (
OpenAPITool,
OpenAPIResource,
OpenAPIResourceTemplate,
)
from fastmcp.utilities.openapi import HTTPRoute
def customize_components(
route: HTTPRoute,

View file

@ -18,7 +18,7 @@ The available modules can be viewed in the [contrib directory](https://github.co
To use a contrib module, import it from the `fastmcp.contrib` package:
```python
```python test="skip"
from fastmcp.contrib import my_module
```

View file

@ -161,7 +161,7 @@ The implementation provides all required OAuth endpoints including authorization
```python
from fastmcp import FastMCP
from fastmcp.server.auth.providers.oauth import MyOAuthProvider
from fastmcp.server.auth import OAuthProvider
auth = MyOAuthProvider(
user_store=your_user_database,

View file

@ -262,7 +262,7 @@ Message(["item1", "item2"])
`PromptResult` gives you explicit control over prompt responses: multiple messages, roles, and metadata at both the message and result level.
```python
```python test="skip"
from fastmcp import FastMCP
from fastmcp.prompts import PromptResult, Message

View file

@ -114,7 +114,7 @@ The decorator supports: `uri` (required), `name`, `title`, `description`, `icons
Mark a function as a prompt template.
```python
```python test="skip"
from fastmcp.prompts import prompt
@prompt

View file

@ -248,7 +248,7 @@ The [FastMCP Client](/clients/client) uses storage for persisting OAuth tokens l
```python
from pathlib import Path
from fastmcp.client.auth import OAuthClientProvider
from fastmcp.client.auth import OAuth
from key_value.aio.stores.filetree import (
FileTreeStore,
FileTreeV1KeySanitizationStrategy,
@ -263,7 +263,7 @@ token_storage = FileTreeStore(
collection_sanitization_strategy=FileTreeV1CollectionSanitizationStrategy(token_dir),
)
oauth_provider = OAuthClientProvider(
oauth_provider = OAuth(
mcp_url="https://your-mcp-server.com/mcp/sse",
token_storage=token_storage
)

View file

@ -152,7 +152,7 @@ Heres how you can add custom route maps to turn `GET` requests into `Resource
```python api_server_with_resources.py {3, 37-42}
import httpx
from fastmcp import FastMCP
from fastmcp.server.openapi import RouteMap, MCPType
from fastmcp.server.providers.openapi import RouteMap, MCPType
# Create an HTTP client for the target API

View file

@ -650,17 +650,17 @@ FASTMCP_STATELESS_HTTP=true uvicorn app:app --host 0.0.0.0 --port 8000 --workers
Production deployments should never hardcode sensitive information like API keys or authentication tokens. Instead, use environment variables to configure your server at runtime. This keeps your code secure and makes it easy to deploy the same code to different environments with different configurations.
Here's an example using bearer token authentication (though OAuth is recommended for production):
Here's an example using static token authentication for development (OAuth is recommended for production):
```python
import os
from fastmcp import FastMCP
from fastmcp.server.auth import BearerTokenAuth
from fastmcp.server.auth import StaticTokenVerifier
# Read configuration from environment
auth_token = os.environ.get("MCP_AUTH_TOKEN")
if auth_token:
auth = BearerTokenAuth(token=auth_token)
auth = StaticTokenVerifier(tokens={auth_token: {"sub": "admin", "client_id": "cli"}})
mcp = FastMCP("Production Server", auth=auth)
else:
mcp = FastMCP("Production Server")

View file

@ -19,11 +19,11 @@ The experimental OpenAPI parser is now the standard implementation. The legacy p
**If you were using the experimental parser:** Update your imports from the experimental module to the standard location:
<CodeGroup>
```python Before
```python test="skip" Before
from fastmcp.experimental.server.openapi import FastMCPOpenAPI, RouteMap, MCPType
```
```python After
```python test="skip" After
from fastmcp.server.openapi import FastMCPOpenAPI, RouteMap, MCPType
```
</CodeGroup>
@ -36,7 +36,7 @@ The following deprecated features have been removed in v2.14.0:
**BearerAuthProvider** (deprecated in v2.11):
<CodeGroup>
```python Before
```python test="skip" Before
from fastmcp.server.auth.providers.bearer import BearerAuthProvider
```
@ -47,7 +47,7 @@ from fastmcp.server.auth.providers.jwt import JWTVerifier
**Context.get_http_request()** (deprecated in v2.2.11):
<CodeGroup>
```python Before
```python test="skip" Before
request = context.get_http_request()
```
@ -59,7 +59,7 @@ request = get_http_request()
**Top-level Image import** (deprecated in v2.8.1):
<CodeGroup>
```python Before
```python test="skip" Before
from fastmcp import Image
```

View file

@ -181,7 +181,7 @@ if __name__ == "__main__":
If you try to call the authenticated server with the same Anthropic code we wrote earlier, you'll get an error indicating that the server rejected the request because it's not authenticated.
```python
```text
Error code: 400 - {
"type": "error",
"error": {

View file

@ -64,8 +64,8 @@ from fastmcp.server.auth.providers.descope import DescopeProvider
# The DescopeProvider automatically discovers Descope endpoints
# and configures JWT token validation
auth_provider = DescopeProvider(
config_url=https://.../.well-known/openid-configuration, # Your MCP Server .well-known URL
base_url=SERVER_URL, # Your server's public URL
config_url="https://.../.well-known/openid-configuration", # Your MCP Server .well-known URL
base_url=SERVER_URL, # Your server's public URL
)
# Create FastMCP server with auth

View file

@ -216,7 +216,7 @@ Because FastMCP's FastAPI integration is based on its [OpenAPI integration](/v2/
```python
# Assumes the FastAPI app from above is already defined
from fastmcp import FastMCP
from fastmcp.server.openapi import RouteMap, MCPType
from fastmcp.server.providers.openapi import RouteMap, MCPType
# Custom mapping rules
mcp = FastMCP.from_fastapi(

View file

@ -178,8 +178,8 @@ if __name__ == "__main__":
If you try to call the authenticated server with the same OpenAI code we wrote earlier, you'll get an error like this:
```python
pythonAPIStatusError: Error code: 424 - {
```text
APIStatusError: Error code: 424 - {
"error": {
"message": "Error retrieving tool list from MCP server: 'dice_server'. Http status code: 401 (Unauthorized)",
"type": "external_connector_error",

View file

@ -81,7 +81,7 @@ Each `RouteMap` specifies a combination of methods, patterns, and tags, as well
Here is FastMCP's default rule:
```python
from fastmcp.server.openapi import RouteMap, MCPType
from fastmcp.server.providers.openapi import RouteMap, MCPType
DEFAULT_ROUTE_MAPPINGS = [
# All routes become tools
@ -97,7 +97,7 @@ For example, prior to FastMCP 2.8.0, GET requests were automatically mapped to `
```python
from fastmcp import FastMCP
from fastmcp.server.openapi import RouteMap, MCPType
from fastmcp.server.providers.openapi import RouteMap, MCPType
# Restore pre-2.8.0 semantic mapping
semantic_maps = [
@ -120,7 +120,7 @@ Here is a more complete example that uses custom route maps to convert all `GET`
```python
from fastmcp import FastMCP
from fastmcp.server.openapi import RouteMap, MCPType
from fastmcp.server.providers.openapi import RouteMap, MCPType
mcp = FastMCP.from_openapi(
openapi_spec=spec,
@ -160,7 +160,7 @@ You can use this to remove sensitive or internal routes by targeting them specif
```python
from fastmcp import FastMCP
from fastmcp.server.openapi import RouteMap, MCPType
from fastmcp.server.providers.openapi import RouteMap, MCPType
mcp = FastMCP.from_openapi(
openapi_spec=spec,
@ -176,7 +176,7 @@ Or you can use a catch-all rule to exclude everything that your maps don't handl
```python
from fastmcp import FastMCP
from fastmcp.server.openapi import RouteMap, MCPType
from fastmcp.server.providers.openapi import RouteMap, MCPType
mcp = FastMCP.from_openapi(
openapi_spec=spec,
@ -208,7 +208,8 @@ The `route_map_fn` is called on all routes, even those that matched `MCPType.EXC
```python
from fastmcp import FastMCP
from fastmcp.server.openapi import RouteMap, MCPType, HTTPRoute
from fastmcp.server.providers.openapi import RouteMap, MCPType
from fastmcp.utilities.openapi import HTTPRoute
def custom_route_mapper(route: HTTPRoute, mcp_type: MCPType) -> MCPType | None:
"""Advanced route type mapping."""
@ -273,7 +274,7 @@ FastMCP provides several ways to add tags to your MCP components, allowing you t
You can add custom tags to components created from specific routes using the `mcp_tags` parameter in `RouteMap`. These tags will be applied to all components created from routes that match that particular route map.
```python
from fastmcp.server.openapi import RouteMap, MCPType
from fastmcp.server.providers.openapi import RouteMap, MCPType
mcp = FastMCP.from_openapi(
openapi_spec=spec,
@ -364,12 +365,12 @@ Your `mcp_component_fn` is expected to modify the component in-place, not to ret
</Tip>
```python
from fastmcp.server.openapi import (
HTTPRoute,
from fastmcp.server.providers.openapi import (
OpenAPITool,
OpenAPIResource,
OpenAPIResourceTemplate,
)
from fastmcp.utilities.openapi import HTTPRoute
def customize_components(
route: HTTPRoute,

View file

@ -18,7 +18,7 @@ The available modules can be viewed in the [contrib directory](https://github.co
To use a contrib module, import it from the `fastmcp.contrib` package:
```python
```python test="skip"
from fastmcp.contrib import my_module
```

View file

@ -161,7 +161,7 @@ The implementation provides all required OAuth endpoints including authorization
```python
from fastmcp import FastMCP
from fastmcp.server.auth.providers.oauth import MyOAuthProvider
from fastmcp.server.auth import OAuthProvider
auth = MyOAuthProvider(
user_store=your_user_database,

View file

@ -52,7 +52,7 @@ The recommended way to create a proxy is using `ProxyClient`, which provides ful
```python
from fastmcp import FastMCP
from fastmcp.server.proxy import ProxyClient
from fastmcp.server.providers.proxy import ProxyClient
# Create a proxy with full MCP feature support
proxy = FastMCP.as_proxy(
@ -86,7 +86,7 @@ FastMCP proxies provide session isolation to ensure safe concurrent operations.
When you pass a disconnected client (which is the normal case), each request gets its own isolated backend session:
```python
from fastmcp.server.proxy import ProxyClient
from fastmcp.server.providers.proxy import ProxyClient
# Each request creates a fresh backend session (recommended)
proxy = FastMCP.as_proxy(ProxyClient("backend_server.py"))
@ -121,7 +121,7 @@ A common use case is bridging transports - exposing a server running on one tran
```python
from fastmcp import FastMCP
from fastmcp.server.proxy import ProxyClient
from fastmcp.server.providers.proxy import ProxyClient
# Bridge remote SSE server to local stdio
remote_proxy = FastMCP.as_proxy(
@ -164,7 +164,7 @@ if __name__ == "__main__":
- **Progress**: Forwards progress notifications during long operations
```python
from fastmcp.server.proxy import ProxyClient
from fastmcp.server.providers.proxy import ProxyClient
# ProxyClient automatically handles all these features
backend = ProxyClient("advanced_backend.py")
@ -304,7 +304,7 @@ Internally, `FastMCP.as_proxy()` uses the `FastMCPProxy` class. You generally do
### Direct Usage
```python
from fastmcp.server.proxy import FastMCPProxy, ProxyClient
from fastmcp.server.providers.proxy import FastMCPProxy, ProxyClient
# Provide a client factory for explicit session control
def create_client():

View file

@ -236,13 +236,13 @@ middleware = ResponseCachingMiddleware(cache_storage=namespaced_store)
The [FastMCP Client](/v2/clients/client) uses storage for persisting OAuth tokens locally. By default, tokens are stored in memory:
```python
from fastmcp.client.auth import OAuthClientProvider
from fastmcp.client.auth import OAuth
from key_value.aio.stores.disk import DiskStore
# Store tokens on disk for persistence across restarts
token_storage = DiskStore(directory="~/.local/share/fastmcp/tokens")
oauth_provider = OAuthClientProvider(
oauth_provider = OAuth(
mcp_url="https://your-mcp-server.com/mcp/sse",
token_storage=token_storage
)

View file

@ -152,7 +152,7 @@ Heres how you can add custom route maps to turn `GET` requests into `Resource
```python api_server_with_resources.py {3, 37-42}
import httpx
from fastmcp import FastMCP
from fastmcp.server.openapi import RouteMap, MCPType
from fastmcp.server.providers.openapi import RouteMap, MCPType
# Create an HTTP client for the target API

View file

@ -89,6 +89,7 @@ dev = [
"prek>=0.2.12",
"loq>=0.1.0a3",
"opentelemetry-exporter-otlp-proto-grpc>=1.39.0",
"pytest-examples>=0.0.18",
]
[project.scripts]
@ -222,4 +223,4 @@ known-first-party = ["fastmcp"]
[tool.codespell]
ignore-words-list = "asend,shttp,te"
ignore-words-list = "asend,shttp,te"

View file

@ -0,0 +1,146 @@
"""Validate Python code examples in FastMCP documentation.
Extracts code blocks from .mdx docs and checks:
1. Syntax every example parses as valid Python
2. FastMCP imports every ``from fastmcp.x import y`` resolves
Supports tags in code fence prefix (for future use):
```python test="skip" skip all checks
```python lint="skip" skip all checks
Run:
uv run pytest tests/docs/test_doc_examples.py -v -s
"""
from __future__ import annotations
import ast
from pathlib import Path
from uuid import uuid4
from pytest_examples import CodeExample
from pytest_examples.find_examples import _extract_code_chunks
DOCS_DIR = Path("docs")
_SKIP_DIRS = {"python-sdk", "public"}
# Snapshot baselines — ratchet DOWN as doc examples are fixed.
MAX_SYNTAX_FAILURES = 0
MAX_IMPORT_FAILURES = 0
def _find_mdx_examples() -> list[CodeExample]:
"""Find Python code examples in .mdx files.
pytest-examples only supports ``.md``; we call its internal
``_extract_code_chunks`` directly so ``.mdx`` works without copying.
"""
examples: list[CodeExample] = []
for mdx_file in sorted(DOCS_DIR.rglob("*.mdx")):
rel = mdx_file.relative_to(DOCS_DIR)
if rel.parts and rel.parts[0] in _SKIP_DIRS:
continue
code = mdx_file.read_text("utf-8")
group = uuid4()
examples.extend(_extract_code_chunks(mdx_file, code, group))
return examples
def _should_skip(example: CodeExample) -> bool:
settings = example.prefix_settings()
return settings.get("lint") == "skip" or settings.get("test") == "skip"
def _check_syntax(example: CodeExample) -> str | None:
"""Return error description if syntax is invalid, else None."""
try:
ast.parse(example.source)
return None
except SyntaxError as e:
rel = Path(example.path).relative_to(DOCS_DIR)
return f"{rel}:{example.start_line}: line {e.lineno}: {e.msg}"
def _check_fastmcp_imports(example: CodeExample) -> list[str]:
"""Return list of broken fastmcp import descriptions."""
try:
tree = ast.parse(example.source)
except SyntaxError:
return []
errors: list[str] = []
rel = Path(example.path).relative_to(DOCS_DIR)
for node in ast.walk(tree):
if isinstance(node, ast.Import):
for alias in node.names:
if alias.name.startswith("fastmcp"):
try:
__import__(alias.name)
except ImportError:
errors.append(
f"{rel}:{example.start_line}: cannot import '{alias.name}'"
)
elif isinstance(node, ast.ImportFrom):
if node.module and node.module.startswith("fastmcp"):
names = [a.name for a in node.names]
try:
mod = __import__(node.module, fromlist=names)
except ImportError:
errors.append(
f"{rel}:{example.start_line}: "
f"cannot import module '{node.module}'"
)
continue
for name in names:
if not hasattr(mod, name):
errors.append(
f"{rel}:{example.start_line}: "
f"'{node.module}' has no '{name}'"
)
return errors
def test_doc_examples_quality():
"""Doc examples should not regress in syntax or import correctness.
Checks every Python code block in ``docs/*.mdx`` (excluding
auto-generated ``python-sdk/`` and ``public/`` directories).
Reports all failures and asserts counts don't exceed known baselines.
"""
examples = _find_mdx_examples()
syntax_failures: list[str] = []
import_failures: list[str] = []
for ex in examples:
if _should_skip(ex):
continue
err = _check_syntax(ex)
if err:
syntax_failures.append(err)
continue
import_failures.extend(_check_fastmcp_imports(ex))
total = len(examples)
print(f"\nDoc examples checked: {total}")
print(f"Syntax failures: {len(syntax_failures)}")
print(f"Import failures: {len(import_failures)}")
if syntax_failures:
print("\nSyntax errors:")
for f in syntax_failures:
print(f" {f}")
if import_failures:
print("\nBroken imports:")
for f in import_failures:
print(f" {f}")
assert len(syntax_failures) <= MAX_SYNTAX_FAILURES, (
f"Syntax failures regressed: {len(syntax_failures)} > {MAX_SYNTAX_FAILURES}"
)
assert len(import_failures) <= MAX_IMPORT_FAILURES, (
f"Import failures regressed: {len(import_failures)} > {MAX_IMPORT_FAILURES}"
)

119
uv.lock generated
View file

@ -1,5 +1,5 @@
version = 1
revision = 3
revision = 2
requires-python = ">=3.10"
resolution-markers = [
"python_full_version >= '3.14'",
@ -167,6 +167,50 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/71/cc/18245721fa7747065ab478316c7fea7c74777d07f37ae60db2e84f8172e8/beartype-0.22.9-py3-none-any.whl", hash = "sha256:d16c9bbc61ea14637596c5f6fbff2ee99cbe3573e46a716401734ef50c3060c2", size = 1333658, upload-time = "2025-12-13T06:50:28.266Z" },
]
[[package]]
name = "black"
version = "26.3.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click" },
{ name = "mypy-extensions" },
{ name = "packaging" },
{ name = "pathspec" },
{ name = "platformdirs" },
{ name = "pytokens" },
{ name = "tomli", marker = "python_full_version < '3.11'" },
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/e1/c5/61175d618685d42b005847464b8fb4743a67b1b8fdb75e50e5a96c31a27a/black-26.3.1.tar.gz", hash = "sha256:2c50f5063a9641c7eed7795014ba37b0f5fa227f3d408b968936e24bc0566b07", size = 666155, upload-time = "2026-03-12T03:36:03.593Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/32/a8/11170031095655d36ebc6664fe0897866f6023892396900eec0e8fdc4299/black-26.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:86a8b5035fce64f5dcd1b794cf8ec4d31fe458cf6ce3986a30deb434df82a1d2", size = 1866562, upload-time = "2026-03-12T03:39:58.639Z" },
{ url = "https://files.pythonhosted.org/packages/69/ce/9e7548d719c3248c6c2abfd555d11169457cbd584d98d179111338423790/black-26.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5602bdb96d52d2d0672f24f6ffe5218795736dd34807fd0fd55ccd6bf206168b", size = 1703623, upload-time = "2026-03-12T03:40:00.347Z" },
{ url = "https://files.pythonhosted.org/packages/7f/0a/8d17d1a9c06f88d3d030d0b1d4373c1551146e252afe4547ed601c0e697f/black-26.3.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6c54a4a82e291a1fee5137371ab488866b7c86a3305af4026bdd4dc78642e1ac", size = 1768388, upload-time = "2026-03-12T03:40:01.765Z" },
{ url = "https://files.pythonhosted.org/packages/52/79/c1ee726e221c863cde5164f925bacf183dfdf0397d4e3f94889439b947b4/black-26.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:6e131579c243c98f35bce64a7e08e87fb2d610544754675d4a0e73a070a5aa3a", size = 1412969, upload-time = "2026-03-12T03:40:03.252Z" },
{ url = "https://files.pythonhosted.org/packages/73/a5/15c01d613f5756f68ed8f6d4ec0a1e24b82b18889fa71affd3d1f7fad058/black-26.3.1-cp310-cp310-win_arm64.whl", hash = "sha256:5ed0ca58586c8d9a487352a96b15272b7fa55d139fc8496b519e78023a8dab0a", size = 1220345, upload-time = "2026-03-12T03:40:04.892Z" },
{ url = "https://files.pythonhosted.org/packages/17/57/5f11c92861f9c92eb9dddf515530bc2d06db843e44bdcf1c83c1427824bc/black-26.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:28ef38aee69e4b12fda8dba75e21f9b4f979b490c8ac0baa7cb505369ac9e1ff", size = 1851987, upload-time = "2026-03-12T03:40:06.248Z" },
{ url = "https://files.pythonhosted.org/packages/54/aa/340a1463660bf6831f9e39646bf774086dbd8ca7fc3cded9d59bbdf4ad0a/black-26.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf9bf162ed91a26f1adba8efda0b573bc6924ec1408a52cc6f82cb73ec2b142c", size = 1689499, upload-time = "2026-03-12T03:40:07.642Z" },
{ url = "https://files.pythonhosted.org/packages/f3/01/b726c93d717d72733da031d2de10b92c9fa4c8d0c67e8a8a372076579279/black-26.3.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:474c27574d6d7037c1bc875a81d9be0a9a4f9ee95e62800dab3cfaadbf75acd5", size = 1754369, upload-time = "2026-03-12T03:40:09.279Z" },
{ url = "https://files.pythonhosted.org/packages/e3/09/61e91881ca291f150cfc9eb7ba19473c2e59df28859a11a88248b5cbbc4d/black-26.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:5e9d0d86df21f2e1677cc4bd090cd0e446278bcbbe49bf3659c308c3e402843e", size = 1413613, upload-time = "2026-03-12T03:40:10.943Z" },
{ url = "https://files.pythonhosted.org/packages/16/73/544f23891b22e7efe4d8f812371ab85b57f6a01b2fc45e3ba2e52ba985b8/black-26.3.1-cp311-cp311-win_arm64.whl", hash = "sha256:9a5e9f45e5d5e1c5b5c29b3bd4265dcc90e8b92cf4534520896ed77f791f4da5", size = 1219719, upload-time = "2026-03-12T03:40:12.597Z" },
{ url = "https://files.pythonhosted.org/packages/dc/f8/da5eae4fc75e78e6dceb60624e1b9662ab00d6b452996046dfa9b8a6025b/black-26.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e6f89631eb88a7302d416594a32faeee9fb8fb848290da9d0a5f2903519fc1", size = 1895920, upload-time = "2026-03-12T03:40:13.921Z" },
{ url = "https://files.pythonhosted.org/packages/2c/9f/04e6f26534da2e1629b2b48255c264cabf5eedc5141d04516d9d68a24111/black-26.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41cd2012d35b47d589cb8a16faf8a32ef7a336f56356babd9fcf70939ad1897f", size = 1718499, upload-time = "2026-03-12T03:40:15.239Z" },
{ url = "https://files.pythonhosted.org/packages/04/91/a5935b2a63e31b331060c4a9fdb5a6c725840858c599032a6f3aac94055f/black-26.3.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f76ff19ec5297dd8e66eb64deda23631e642c9393ab592826fd4bdc97a4bce7", size = 1794994, upload-time = "2026-03-12T03:40:17.124Z" },
{ url = "https://files.pythonhosted.org/packages/e7/0a/86e462cdd311a3c2a8ece708d22aba17d0b2a0d5348ca34b40cdcbea512e/black-26.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:ddb113db38838eb9f043623ba274cfaf7d51d5b0c22ecb30afe58b1bb8322983", size = 1420867, upload-time = "2026-03-12T03:40:18.83Z" },
{ url = "https://files.pythonhosted.org/packages/5b/e5/22515a19cb7eaee3440325a6b0d95d2c0e88dd180cb011b12ae488e031d1/black-26.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:dfdd51fc3e64ea4f35873d1b3fb25326773d55d2329ff8449139ebaad7357efb", size = 1230124, upload-time = "2026-03-12T03:40:20.425Z" },
{ url = "https://files.pythonhosted.org/packages/f5/77/5728052a3c0450c53d9bb3945c4c46b91baa62b2cafab6801411b6271e45/black-26.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:855822d90f884905362f602880ed8b5df1b7e3ee7d0db2502d4388a954cc8c54", size = 1895034, upload-time = "2026-03-12T03:40:21.813Z" },
{ url = "https://files.pythonhosted.org/packages/52/73/7cae55fdfdfbe9d19e9a8d25d145018965fe2079fa908101c3733b0c55a0/black-26.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8a33d657f3276328ce00e4d37fe70361e1ec7614da5d7b6e78de5426cb56332f", size = 1718503, upload-time = "2026-03-12T03:40:23.666Z" },
{ url = "https://files.pythonhosted.org/packages/e1/87/af89ad449e8254fdbc74654e6467e3c9381b61472cc532ee350d28cfdafb/black-26.3.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f1cd08e99d2f9317292a311dfe578fd2a24b15dbce97792f9c4d752275c1fa56", size = 1793557, upload-time = "2026-03-12T03:40:25.497Z" },
{ url = "https://files.pythonhosted.org/packages/43/10/d6c06a791d8124b843bf325ab4ac7d2f5b98731dff84d6064eafd687ded1/black-26.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:c7e72339f841b5a237ff14f7d3880ddd0fc7f98a1199e8c4327f9a4f478c1839", size = 1422766, upload-time = "2026-03-12T03:40:27.14Z" },
{ url = "https://files.pythonhosted.org/packages/59/4f/40a582c015f2d841ac24fed6390bd68f0fc896069ff3a886317959c9daf8/black-26.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:afc622538b430aa4c8c853f7f63bc582b3b8030fd8c80b70fb5fa5b834e575c2", size = 1232140, upload-time = "2026-03-12T03:40:28.882Z" },
{ url = "https://files.pythonhosted.org/packages/d5/da/e36e27c9cebc1311b7579210df6f1c86e50f2d7143ae4fcf8a5017dc8809/black-26.3.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2d6bfaf7fd0993b420bed691f20f9492d53ce9a2bcccea4b797d34e947318a78", size = 1889234, upload-time = "2026-03-12T03:40:30.964Z" },
{ url = "https://files.pythonhosted.org/packages/0e/7b/9871acf393f64a5fa33668c19350ca87177b181f44bb3d0c33b2d534f22c/black-26.3.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f89f2ab047c76a9c03f78d0d66ca519e389519902fa27e7a91117ef7611c0568", size = 1720522, upload-time = "2026-03-12T03:40:32.346Z" },
{ url = "https://files.pythonhosted.org/packages/03/87/e766c7f2e90c07fb7586cc787c9ae6462b1eedab390191f2b7fc7f6170a9/black-26.3.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b07fc0dab849d24a80a29cfab8d8a19187d1c4685d8a5e6385a5ce323c1f015f", size = 1787824, upload-time = "2026-03-12T03:40:33.636Z" },
{ url = "https://files.pythonhosted.org/packages/ac/94/2424338fb2d1875e9e83eed4c8e9c67f6905ec25afd826a911aea2b02535/black-26.3.1-cp314-cp314-win_amd64.whl", hash = "sha256:0126ae5b7c09957da2bdbd91a9ba1207453feada9e9fe51992848658c6c8e01c", size = 1445855, upload-time = "2026-03-12T03:40:35.442Z" },
{ url = "https://files.pythonhosted.org/packages/86/43/0c3338bd928afb8ee7471f1a4eec3bdbe2245ccb4a646092a222e8669840/black-26.3.1-cp314-cp314-win_arm64.whl", hash = "sha256:92c0ec1f2cc149551a2b7b47efc32c866406b6891b0ee4625e95967c8f4acfb1", size = 1258109, upload-time = "2026-03-12T03:40:36.832Z" },
{ url = "https://files.pythonhosted.org/packages/8e/0d/52d98722666d6fc6c3dd4c76df339501d6efd40e0ff95e6186a7b7f0befd/black-26.3.1-py3-none-any.whl", hash = "sha256:2bd5aa94fc267d38bb21a70d7410a89f1a1d318841855f698746f8e7f51acd1b", size = 207542, upload-time = "2026-03-12T03:36:01.668Z" },
]
[[package]]
name = "cachetools"
version = "7.0.5"
@ -851,6 +895,7 @@ dev = [
{ name = "pytest-asyncio" },
{ name = "pytest-cov" },
{ name = "pytest-env" },
{ name = "pytest-examples" },
{ name = "pytest-flakefinder" },
{ name = "pytest-httpx" },
{ name = "pytest-report" },
@ -914,6 +959,7 @@ dev = [
{ name = "pytest-asyncio", specifier = ">=1.2.0" },
{ name = "pytest-cov", specifier = ">=6.1.1" },
{ name = "pytest-env", specifier = ">=1.1.5" },
{ name = "pytest-examples", specifier = ">=0.0.18" },
{ name = "pytest-flakefinder", specifier = ">=1.1.0" },
{ name = "pytest-httpx", specifier = ">=0.35.0" },
{ name = "pytest-report", specifier = ">=0.2.1" },
@ -1626,6 +1672,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/5e/75/bd9b7bb966668920f06b200e84454c8f3566b102183bc55c5473d96cb2b9/msal_extensions-1.3.1-py3-none-any.whl", hash = "sha256:96d3de4d034504e969ac5e85bae8106c8373b5c6568e4c8fa7af2eca9dbe6bca", size = 20583, upload-time = "2025-03-14T23:51:03.016Z" },
]
[[package]]
name = "mypy-extensions"
version = "1.1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" },
]
[[package]]
name = "openai"
version = "2.30.0"
@ -1766,6 +1821,15 @@ 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" },
]
[[package]]
name = "pathspec"
version = "1.0.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" },
]
[[package]]
name = "pdbpp"
version = "0.12.1"
@ -2402,6 +2466,20 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/27/16/ad52f56b96d851a2bcfdc1e754c3531341885bd7177a128c13ff2ca72ab4/pytest_env-1.6.0-py3-none-any.whl", hash = "sha256:1e7f8a62215e5885835daaed694de8657c908505b964ec8097a7ce77b403d9a3", size = 10400, upload-time = "2026-03-12T22:39:41.887Z" },
]
[[package]]
name = "pytest-examples"
version = "0.0.18"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "black" },
{ name = "pytest" },
{ name = "ruff" },
]
sdist = { url = "https://files.pythonhosted.org/packages/af/71/4ae972fd95f474454aa450108ee1037830e7ba11840363e981b8d48fd16a/pytest_examples-0.0.18.tar.gz", hash = "sha256:9a464f007f805b113677a15e2f8942ebb92d7d3eb5312e9a405d018478ec9801", size = 21237, upload-time = "2025-05-06T07:46:10.705Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/09/52/7bbfb6e987d9a8a945f22941a8da63e3529465f1b106ef0e26f5df7c780d/pytest_examples-0.0.18-py3-none-any.whl", hash = "sha256:86c195b98c4e55049a0df3a0a990ca89123b7280473ab57608eecc6c47bcfe9c", size = 18169, upload-time = "2025-05-06T07:46:09.349Z" },
]
[[package]]
name = "pytest-flakefinder"
version = "1.1.0"
@ -2500,6 +2578,45 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/1b/d0/397f9626e711ff749a95d96b7af99b9c566a9bb5129b8e4c10fc4d100304/python_multipart-0.0.22-py3-none-any.whl", hash = "sha256:2b2cd894c83d21bf49d702499531c7bafd057d730c201782048f7945d82de155", size = 24579, upload-time = "2026-01-25T10:15:54.811Z" },
]
[[package]]
name = "pytokens"
version = "0.4.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b6/34/b4e015b99031667a7b960f888889c5bd34ef585c85e1cb56a594b92836ac/pytokens-0.4.1.tar.gz", hash = "sha256:292052fe80923aae2260c073f822ceba21f3872ced9a68bb7953b348e561179a", size = 23015, upload-time = "2026-01-30T01:03:45.924Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/42/24/f206113e05cb8ef51b3850e7ef88f20da6f4bf932190ceb48bd3da103e10/pytokens-0.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2a44ed93ea23415c54f3face3b65ef2b844d96aeb3455b8a69b3df6beab6acc5", size = 161522, upload-time = "2026-01-30T01:02:50.393Z" },
{ url = "https://files.pythonhosted.org/packages/d4/e9/06a6bf1b90c2ed81a9c7d2544232fe5d2891d1cd480e8a1809ca354a8eb2/pytokens-0.4.1-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:add8bf86b71a5d9fb5b89f023a80b791e04fba57960aa790cc6125f7f1d39dfe", size = 246945, upload-time = "2026-01-30T01:02:52.399Z" },
{ url = "https://files.pythonhosted.org/packages/69/66/f6fb1007a4c3d8b682d5d65b7c1fb33257587a5f782647091e3408abe0b8/pytokens-0.4.1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:670d286910b531c7b7e3c0b453fd8156f250adb140146d234a82219459b9640c", size = 259525, upload-time = "2026-01-30T01:02:53.737Z" },
{ url = "https://files.pythonhosted.org/packages/04/92/086f89b4d622a18418bac74ab5db7f68cf0c21cf7cc92de6c7b919d76c88/pytokens-0.4.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4e691d7f5186bd2842c14813f79f8884bb03f5995f0575272009982c5ac6c0f7", size = 262693, upload-time = "2026-01-30T01:02:54.871Z" },
{ url = "https://files.pythonhosted.org/packages/b4/7b/8b31c347cf94a3f900bdde750b2e9131575a61fdb620d3d3c75832262137/pytokens-0.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:27b83ad28825978742beef057bfe406ad6ed524b2d28c252c5de7b4a6dd48fa2", size = 103567, upload-time = "2026-01-30T01:02:56.414Z" },
{ url = "https://files.pythonhosted.org/packages/3d/92/790ebe03f07b57e53b10884c329b9a1a308648fc083a6d4a39a10a28c8fc/pytokens-0.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d70e77c55ae8380c91c0c18dea05951482e263982911fc7410b1ffd1dadd3440", size = 160864, upload-time = "2026-01-30T01:02:57.882Z" },
{ url = "https://files.pythonhosted.org/packages/13/25/a4f555281d975bfdd1eba731450e2fe3a95870274da73fb12c40aeae7625/pytokens-0.4.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a58d057208cb9075c144950d789511220b07636dd2e4708d5645d24de666bdc", size = 248565, upload-time = "2026-01-30T01:02:59.912Z" },
{ url = "https://files.pythonhosted.org/packages/17/50/bc0394b4ad5b1601be22fa43652173d47e4c9efbf0044c62e9a59b747c56/pytokens-0.4.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b49750419d300e2b5a3813cf229d4e5a4c728dae470bcc89867a9ad6f25a722d", size = 260824, upload-time = "2026-01-30T01:03:01.471Z" },
{ url = "https://files.pythonhosted.org/packages/4e/54/3e04f9d92a4be4fc6c80016bc396b923d2a6933ae94b5f557c939c460ee0/pytokens-0.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9907d61f15bf7261d7e775bd5d7ee4d2930e04424bab1972591918497623a16", size = 264075, upload-time = "2026-01-30T01:03:04.143Z" },
{ url = "https://files.pythonhosted.org/packages/d1/1b/44b0326cb5470a4375f37988aea5d61b5cc52407143303015ebee94abfd6/pytokens-0.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:ee44d0f85b803321710f9239f335aafe16553b39106384cef8e6de40cb4ef2f6", size = 103323, upload-time = "2026-01-30T01:03:05.412Z" },
{ url = "https://files.pythonhosted.org/packages/41/5d/e44573011401fb82e9d51e97f1290ceb377800fb4eed650b96f4753b499c/pytokens-0.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:140709331e846b728475786df8aeb27d24f48cbcf7bcd449f8de75cae7a45083", size = 160663, upload-time = "2026-01-30T01:03:06.473Z" },
{ url = "https://files.pythonhosted.org/packages/f0/e6/5bbc3019f8e6f21d09c41f8b8654536117e5e211a85d89212d59cbdab381/pytokens-0.4.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d6c4268598f762bc8e91f5dbf2ab2f61f7b95bdc07953b602db879b3c8c18e1", size = 255626, upload-time = "2026-01-30T01:03:08.177Z" },
{ url = "https://files.pythonhosted.org/packages/bf/3c/2d5297d82286f6f3d92770289fd439956b201c0a4fc7e72efb9b2293758e/pytokens-0.4.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24afde1f53d95348b5a0eb19488661147285ca4dd7ed752bbc3e1c6242a304d1", size = 269779, upload-time = "2026-01-30T01:03:09.756Z" },
{ url = "https://files.pythonhosted.org/packages/20/01/7436e9ad693cebda0551203e0bf28f7669976c60ad07d6402098208476de/pytokens-0.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5ad948d085ed6c16413eb5fec6b3e02fa00dc29a2534f088d3302c47eb59adf9", size = 268076, upload-time = "2026-01-30T01:03:10.957Z" },
{ url = "https://files.pythonhosted.org/packages/2e/df/533c82a3c752ba13ae7ef238b7f8cdd272cf1475f03c63ac6cf3fcfb00b6/pytokens-0.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:3f901fe783e06e48e8cbdc82d631fca8f118333798193e026a50ce1b3757ea68", size = 103552, upload-time = "2026-01-30T01:03:12.066Z" },
{ url = "https://files.pythonhosted.org/packages/cb/dc/08b1a080372afda3cceb4f3c0a7ba2bde9d6a5241f1edb02a22a019ee147/pytokens-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8bdb9d0ce90cbf99c525e75a2fa415144fd570a1ba987380190e8b786bc6ef9b", size = 160720, upload-time = "2026-01-30T01:03:13.843Z" },
{ url = "https://files.pythonhosted.org/packages/64/0c/41ea22205da480837a700e395507e6a24425151dfb7ead73343d6e2d7ffe/pytokens-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5502408cab1cb18e128570f8d598981c68a50d0cbd7c61312a90507cd3a1276f", size = 254204, upload-time = "2026-01-30T01:03:14.886Z" },
{ url = "https://files.pythonhosted.org/packages/e0/d2/afe5c7f8607018beb99971489dbb846508f1b8f351fcefc225fcf4b2adc0/pytokens-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29d1d8fb1030af4d231789959f21821ab6325e463f0503a61d204343c9b355d1", size = 268423, upload-time = "2026-01-30T01:03:15.936Z" },
{ url = "https://files.pythonhosted.org/packages/68/d4/00ffdbd370410c04e9591da9220a68dc1693ef7499173eb3e30d06e05ed1/pytokens-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b08dd6b86058b6dc07efe9e98414f5102974716232d10f32ff39701e841c4", size = 266859, upload-time = "2026-01-30T01:03:17.458Z" },
{ url = "https://files.pythonhosted.org/packages/a7/c9/c3161313b4ca0c601eeefabd3d3b576edaa9afdefd32da97210700e47652/pytokens-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:9bd7d7f544d362576be74f9d5901a22f317efc20046efe2034dced238cbbfe78", size = 103520, upload-time = "2026-01-30T01:03:18.652Z" },
{ url = "https://files.pythonhosted.org/packages/8f/a7/b470f672e6fc5fee0a01d9e75005a0e617e162381974213a945fcd274843/pytokens-0.4.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4a14d5f5fc78ce85e426aa159489e2d5961acf0e47575e08f35584009178e321", size = 160821, upload-time = "2026-01-30T01:03:19.684Z" },
{ url = "https://files.pythonhosted.org/packages/80/98/e83a36fe8d170c911f864bfded690d2542bfcfacb9c649d11a9e6eb9dc41/pytokens-0.4.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:97f50fd18543be72da51dd505e2ed20d2228c74e0464e4262e4899797803d7fa", size = 254263, upload-time = "2026-01-30T01:03:20.834Z" },
{ url = "https://files.pythonhosted.org/packages/0f/95/70d7041273890f9f97a24234c00b746e8da86df462620194cef1d411ddeb/pytokens-0.4.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc74c035f9bfca0255c1af77ddd2d6ae8419012805453e4b0e7513e17904545d", size = 268071, upload-time = "2026-01-30T01:03:21.888Z" },
{ url = "https://files.pythonhosted.org/packages/da/79/76e6d09ae19c99404656d7db9c35dfd20f2086f3eb6ecb496b5b31163bad/pytokens-0.4.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f66a6bbe741bd431f6d741e617e0f39ec7257ca1f89089593479347cc4d13324", size = 271716, upload-time = "2026-01-30T01:03:23.633Z" },
{ url = "https://files.pythonhosted.org/packages/79/37/482e55fa1602e0a7ff012661d8c946bafdc05e480ea5a32f4f7e336d4aa9/pytokens-0.4.1-cp314-cp314-win_amd64.whl", hash = "sha256:b35d7e5ad269804f6697727702da3c517bb8a5228afa450ab0fa787732055fc9", size = 104539, upload-time = "2026-01-30T01:03:24.788Z" },
{ url = "https://files.pythonhosted.org/packages/30/e8/20e7db907c23f3d63b0be3b8a4fd1927f6da2395f5bcc7f72242bb963dfe/pytokens-0.4.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8fcb9ba3709ff77e77f1c7022ff11d13553f3c30299a9fe246a166903e9091eb", size = 168474, upload-time = "2026-01-30T01:03:26.428Z" },
{ url = "https://files.pythonhosted.org/packages/d6/81/88a95ee9fafdd8f5f3452107748fd04c24930d500b9aba9738f3ade642cc/pytokens-0.4.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:79fc6b8699564e1f9b521582c35435f1bd32dd06822322ec44afdeba666d8cb3", size = 290473, upload-time = "2026-01-30T01:03:27.415Z" },
{ url = "https://files.pythonhosted.org/packages/cf/35/3aa899645e29b6375b4aed9f8d21df219e7c958c4c186b465e42ee0a06bf/pytokens-0.4.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d31b97b3de0f61571a124a00ffe9a81fb9939146c122c11060725bd5aea79975", size = 303485, upload-time = "2026-01-30T01:03:28.558Z" },
{ url = "https://files.pythonhosted.org/packages/52/a0/07907b6ff512674d9b201859f7d212298c44933633c946703a20c25e9d81/pytokens-0.4.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:967cf6e3fd4adf7de8fc73cd3043754ae79c36475c1c11d514fc72cf5490094a", size = 306698, upload-time = "2026-01-30T01:03:29.653Z" },
{ url = "https://files.pythonhosted.org/packages/39/2a/cbbf9250020a4a8dd53ba83a46c097b69e5eb49dd14e708f496f548c6612/pytokens-0.4.1-cp314-cp314t-win_amd64.whl", hash = "sha256:584c80c24b078eec1e227079d56dc22ff755e0ba8654d8383b2c549107528918", size = 116287, upload-time = "2026-01-30T01:03:30.912Z" },
{ url = "https://files.pythonhosted.org/packages/c6/78/397db326746f0a342855b81216ae1f0a32965deccfd7c830a2dbc66d2483/pytokens-0.4.1-py3-none-any.whl", hash = "sha256:26cef14744a8385f35d0e095dc8b3a7583f6c953c2e3d269c7f82484bf5ad2de", size = 13729, upload-time = "2026-01-30T01:03:45.029Z" },
]
[[package]]
name = "pywin32"
version = "311"