lint docs examples (#286)

* lint docs examples

* format
This commit is contained in:
Samuel Colvin
2025-03-14 11:30:57 +00:00
committed by GitHub
parent 97201cce59
commit 3775916c5c
4 changed files with 185 additions and 37 deletions

129
README.md
View File

@@ -99,12 +99,14 @@ from mcp.server.fastmcp import FastMCP
# Create an MCP server # Create an MCP server
mcp = FastMCP("Demo") mcp = FastMCP("Demo")
# Add an addition tool # Add an addition tool
@mcp.tool() @mcp.tool()
def add(a: int, b: int) -> int: def add(a: int, b: int) -> int:
"""Add two numbers""" """Add two numbers"""
return a + b return a + b
# Add a dynamic greeting resource # Add a dynamic greeting resource
@mcp.resource("greeting://{name}") @mcp.resource("greeting://{name}")
def get_greeting(name: str) -> str: def get_greeting(name: str) -> str:
@@ -139,9 +141,13 @@ The FastMCP server is your core interface to the MCP protocol. It handles connec
```python ```python
# Add lifespan support for startup/shutdown with strong typing # Add lifespan support for startup/shutdown with strong typing
from contextlib import asynccontextmanager
from dataclasses import dataclass from dataclasses import dataclass
from typing import AsyncIterator from typing import AsyncIterator
from mcp.server.fastmcp import FastMCP
from fake_database import Database # Replace with your actual DB type
from mcp.server.fastmcp import Context, FastMCP
# Create a named server # Create a named server
mcp = FastMCP("My App") mcp = FastMCP("My App")
@@ -149,24 +155,28 @@ mcp = FastMCP("My App")
# Specify dependencies for deployment and development # Specify dependencies for deployment and development
mcp = FastMCP("My App", dependencies=["pandas", "numpy"]) mcp = FastMCP("My App", dependencies=["pandas", "numpy"])
@dataclass @dataclass
class AppContext: class AppContext:
db: Database # Replace with your actual DB type db: Database
@asynccontextmanager @asynccontextmanager
async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]: async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]:
"""Manage application lifecycle with type-safe context""" """Manage application lifecycle with type-safe context"""
# Initialize on startup
db = await Database.connect()
try: try:
# Initialize on startup
await db.connect()
yield AppContext(db=db) yield AppContext(db=db)
finally: finally:
# Cleanup on shutdown # Cleanup on shutdown
await db.disconnect() await db.disconnect()
# Pass lifespan to server # Pass lifespan to server
mcp = FastMCP("My App", lifespan=app_lifespan) mcp = FastMCP("My App", lifespan=app_lifespan)
# Access type-safe lifespan context in tools # Access type-safe lifespan context in tools
@mcp.tool() @mcp.tool()
def query_db(ctx: Context) -> str: def query_db(ctx: Context) -> str:
@@ -180,11 +190,17 @@ def query_db(ctx: Context) -> str:
Resources are how you expose data to LLMs. They're similar to GET endpoints in a REST API - they provide data but shouldn't perform significant computation or have side effects: Resources are how you expose data to LLMs. They're similar to GET endpoints in a REST API - they provide data but shouldn't perform significant computation or have side effects:
```python ```python
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("My App")
@mcp.resource("config://app") @mcp.resource("config://app")
def get_config() -> str: def get_config() -> str:
"""Static configuration data""" """Static configuration data"""
return "App configuration here" return "App configuration here"
@mcp.resource("users://{user_id}/profile") @mcp.resource("users://{user_id}/profile")
def get_user_profile(user_id: str) -> str: def get_user_profile(user_id: str) -> str:
"""Dynamic user data""" """Dynamic user data"""
@@ -196,10 +212,17 @@ def get_user_profile(user_id: str) -> str:
Tools let LLMs take actions through your server. Unlike resources, tools are expected to perform computation and have side effects: Tools let LLMs take actions through your server. Unlike resources, tools are expected to perform computation and have side effects:
```python ```python
import httpx
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("My App")
@mcp.tool() @mcp.tool()
def calculate_bmi(weight_kg: float, height_m: float) -> float: def calculate_bmi(weight_kg: float, height_m: float) -> float:
"""Calculate BMI given weight in kg and height in meters""" """Calculate BMI given weight in kg and height in meters"""
return weight_kg / (height_m ** 2) return weight_kg / (height_m**2)
@mcp.tool() @mcp.tool()
async def fetch_weather(city: str) -> str: async def fetch_weather(city: str) -> str:
@@ -214,16 +237,22 @@ async def fetch_weather(city: str) -> str:
Prompts are reusable templates that help LLMs interact with your server effectively: Prompts are reusable templates that help LLMs interact with your server effectively:
```python ```python
from mcp.server.fastmcp import FastMCP, types
mcp = FastMCP("My App")
@mcp.prompt() @mcp.prompt()
def review_code(code: str) -> str: def review_code(code: str) -> str:
return f"Please review this code:\n\n{code}" return f"Please review this code:\n\n{code}"
@mcp.prompt() @mcp.prompt()
def debug_error(error: str) -> list[Message]: def debug_error(error: str) -> list[types.Message]:
return [ return [
UserMessage("I'm seeing this error:"), types.UserMessage("I'm seeing this error:"),
UserMessage(error), types.UserMessage(error),
AssistantMessage("I'll help debug that. What have you tried so far?") types.AssistantMessage("I'll help debug that. What have you tried so far?"),
] ]
``` ```
@@ -235,6 +264,9 @@ FastMCP provides an `Image` class that automatically handles image data:
from mcp.server.fastmcp import FastMCP, Image from mcp.server.fastmcp import FastMCP, Image
from PIL import Image as PILImage from PIL import Image as PILImage
mcp = FastMCP("My App")
@mcp.tool() @mcp.tool()
def create_thumbnail(image_path: str) -> Image: def create_thumbnail(image_path: str) -> Image:
"""Create a thumbnail from an image""" """Create a thumbnail from an image"""
@@ -250,6 +282,9 @@ The Context object gives your tools and resources access to MCP capabilities:
```python ```python
from mcp.server.fastmcp import FastMCP, Context from mcp.server.fastmcp import FastMCP, Context
mcp = FastMCP("My App")
@mcp.tool() @mcp.tool()
async def long_task(files: list[str], ctx: Context) -> str: async def long_task(files: list[str], ctx: Context) -> str:
"""Process multiple files with progress tracking""" """Process multiple files with progress tracking"""
@@ -322,16 +357,19 @@ from mcp.server.fastmcp import FastMCP
mcp = FastMCP("Echo") mcp = FastMCP("Echo")
@mcp.resource("echo://{message}") @mcp.resource("echo://{message}")
def echo_resource(message: str) -> str: def echo_resource(message: str) -> str:
"""Echo a message as a resource""" """Echo a message as a resource"""
return f"Resource echo: {message}" return f"Resource echo: {message}"
@mcp.tool() @mcp.tool()
def echo_tool(message: str) -> str: def echo_tool(message: str) -> str:
"""Echo a message as a tool""" """Echo a message as a tool"""
return f"Tool echo: {message}" return f"Tool echo: {message}"
@mcp.prompt() @mcp.prompt()
def echo_prompt(message: str) -> str: def echo_prompt(message: str) -> str:
"""Create an echo prompt""" """Create an echo prompt"""
@@ -343,20 +381,21 @@ def echo_prompt(message: str) -> str:
A more complex example showing database integration: A more complex example showing database integration:
```python ```python
from mcp.server.fastmcp import FastMCP
import sqlite3 import sqlite3
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("SQLite Explorer") mcp = FastMCP("SQLite Explorer")
@mcp.resource("schema://main") @mcp.resource("schema://main")
def get_schema() -> str: def get_schema() -> str:
"""Provide the database schema as a resource""" """Provide the database schema as a resource"""
conn = sqlite3.connect("database.db") conn = sqlite3.connect("database.db")
schema = conn.execute( schema = conn.execute("SELECT sql FROM sqlite_master WHERE type='table'").fetchall()
"SELECT sql FROM sqlite_master WHERE type='table'"
).fetchall()
return "\n".join(sql[0] for sql in schema if sql[0]) return "\n".join(sql[0] for sql in schema if sql[0])
@mcp.tool() @mcp.tool()
def query_data(sql: str) -> str: def query_data(sql: str) -> str:
"""Execute SQL queries safely""" """Execute SQL queries safely"""
@@ -378,20 +417,27 @@ For more control, you can use the low-level server implementation directly. This
from contextlib import asynccontextmanager from contextlib import asynccontextmanager
from typing import AsyncIterator from typing import AsyncIterator
from fake_database import Database # Replace with your actual DB type
from mcp.server import Server
@asynccontextmanager @asynccontextmanager
async def server_lifespan(server: Server) -> AsyncIterator[dict]: async def server_lifespan(server: Server) -> AsyncIterator[dict]:
"""Manage server startup and shutdown lifecycle.""" """Manage server startup and shutdown lifecycle."""
# Initialize resources on startup
db = await Database.connect()
try: try:
# Initialize resources on startup
await db.connect()
yield {"db": db} yield {"db": db}
finally: finally:
# Clean up on shutdown # Clean up on shutdown
await db.disconnect() await db.disconnect()
# Pass lifespan to server # Pass lifespan to server
server = Server("example-server", lifespan=server_lifespan) server = Server("example-server", lifespan=server_lifespan)
# Access lifespan context in handlers # Access lifespan context in handlers
@server.call_tool() @server.call_tool()
async def query_db(name: str, arguments: dict) -> list: async def query_db(name: str, arguments: dict) -> list:
@@ -406,14 +452,15 @@ The lifespan API provides:
- Type-safe context passing between lifespan and request handlers - Type-safe context passing between lifespan and request handlers
```python ```python
from mcp.server.lowlevel import Server, NotificationOptions
from mcp.server.models import InitializationOptions
import mcp.server.stdio import mcp.server.stdio
import mcp.types as types import mcp.types as types
from mcp.server.lowlevel import NotificationOptions, Server
from mcp.server.models import InitializationOptions
# Create a server instance # Create a server instance
server = Server("example-server") server = Server("example-server")
@server.list_prompts() @server.list_prompts()
async def handle_list_prompts() -> list[types.Prompt]: async def handle_list_prompts() -> list[types.Prompt]:
return [ return [
@@ -422,18 +469,16 @@ async def handle_list_prompts() -> list[types.Prompt]:
description="An example prompt template", description="An example prompt template",
arguments=[ arguments=[
types.PromptArgument( types.PromptArgument(
name="arg1", name="arg1", description="Example argument", required=True
description="Example argument",
required=True
) )
] ],
) )
] ]
@server.get_prompt() @server.get_prompt()
async def handle_get_prompt( async def handle_get_prompt(
name: str, name: str, arguments: dict[str, str] | None
arguments: dict[str, str] | None
) -> types.GetPromptResult: ) -> types.GetPromptResult:
if name != "example-prompt": if name != "example-prompt":
raise ValueError(f"Unknown prompt: {name}") raise ValueError(f"Unknown prompt: {name}")
@@ -443,14 +488,12 @@ async def handle_get_prompt(
messages=[ messages=[
types.PromptMessage( types.PromptMessage(
role="user", role="user",
content=types.TextContent( content=types.TextContent(type="text", text="Example prompt text"),
type="text",
text="Example prompt text"
)
) )
] ],
) )
async def run(): async def run():
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream): async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
await server.run( await server.run(
@@ -462,12 +505,14 @@ async def run():
capabilities=server.get_capabilities( capabilities=server.get_capabilities(
notification_options=NotificationOptions(), notification_options=NotificationOptions(),
experimental_capabilities={}, experimental_capabilities={},
) ),
) ),
) )
if __name__ == "__main__": if __name__ == "__main__":
import asyncio import asyncio
asyncio.run(run()) asyncio.run(run())
``` ```
@@ -476,18 +521,21 @@ if __name__ == "__main__":
The SDK provides a high-level client interface for connecting to MCP servers: The SDK provides a high-level client interface for connecting to MCP servers:
```python ```python
from mcp import ClientSession, StdioServerParameters from mcp import ClientSession, StdioServerParameters, types
from mcp.client.stdio import stdio_client from mcp.client.stdio import stdio_client
# Create server parameters for stdio connection # Create server parameters for stdio connection
server_params = StdioServerParameters( server_params = StdioServerParameters(
command="python", # Executable command="python", # Executable
args=["example_server.py"], # Optional command line arguments args=["example_server.py"], # Optional command line arguments
env=None # Optional environment variables env=None, # Optional environment variables
) )
# Optional: create a sampling callback # Optional: create a sampling callback
async def handle_sampling_message(message: types.CreateMessageRequestParams) -> types.CreateMessageResult: async def handle_sampling_message(
message: types.CreateMessageRequestParams,
) -> types.CreateMessageResult:
return types.CreateMessageResult( return types.CreateMessageResult(
role="assistant", role="assistant",
content=types.TextContent( content=types.TextContent(
@@ -498,9 +546,12 @@ async def handle_sampling_message(message: types.CreateMessageRequestParams) ->
stopReason="endTurn", stopReason="endTurn",
) )
async def run(): async def run():
async with stdio_client(server_params) as (read, write): async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write, sampling_callback=handle_sampling_message) as session: async with ClientSession(
read, write, sampling_callback=handle_sampling_message
) as session:
# Initialize the connection # Initialize the connection
await session.initialize() await session.initialize()
@@ -508,7 +559,9 @@ async def run():
prompts = await session.list_prompts() prompts = await session.list_prompts()
# Get a prompt # Get a prompt
prompt = await session.get_prompt("example-prompt", arguments={"arg1": "value"}) prompt = await session.get_prompt(
"example-prompt", arguments={"arg1": "value"}
)
# List available resources # List available resources
resources = await session.list_resources() resources = await session.list_resources()
@@ -522,8 +575,10 @@ async def run():
# Call a tool # Call a tool
result = await session.call_tool("tool-name", arguments={"arg1": "value"}) result = await session.call_tool("tool-name", arguments={"arg1": "value"})
if __name__ == "__main__": if __name__ == "__main__":
import asyncio import asyncio
asyncio.run(run()) asyncio.run(run())
``` ```

View File

@@ -49,6 +49,7 @@ dev-dependencies = [
"trio>=0.26.2", "trio>=0.26.2",
"pytest-flakefinder>=1.1.0", "pytest-flakefinder>=1.1.0",
"pytest-xdist>=3.6.1", "pytest-xdist>=3.6.1",
"pytest-examples>=0.0.14",
] ]
[build-system] [build-system]

View File

@@ -1,6 +1,7 @@
"""Tests for example servers""" """Tests for example servers"""
import pytest import pytest
from pytest_examples import CodeExample, EvalExample, find_examples
from mcp.shared.memory import ( from mcp.shared.memory import (
create_connected_server_and_client_session as client_session, create_connected_server_and_client_session as client_session,
@@ -70,3 +71,17 @@ async def test_desktop(monkeypatch):
assert isinstance(content.text, str) assert isinstance(content.text, str)
assert "/fake/path/file1.txt" in content.text assert "/fake/path/file1.txt" in content.text
assert "/fake/path/file2.txt" in content.text assert "/fake/path/file2.txt" in content.text
@pytest.mark.parametrize("example", find_examples("README.md"), ids=str)
def test_docs_examples(example: CodeExample, eval_example: EvalExample):
ruff_ignore: list[str] = ["F841", "I001"]
eval_example.set_config(
ruff_ignore=ruff_ignore, target_version="py310", line_length=88
)
if eval_example.update_examples: # pragma: no cover
eval_example.format(example)
else:
eval_example.lint(example)

77
uv.lock generated
View File

@@ -46,6 +46,40 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/89/aa/ab0f7891a01eeb2d2e338ae8fecbe57fcebea1a24dbb64d45801bfab481d/attrs-24.3.0-py3-none-any.whl", hash = "sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308", size = 63397 }, { url = "https://files.pythonhosted.org/packages/89/aa/ab0f7891a01eeb2d2e338ae8fecbe57fcebea1a24dbb64d45801bfab481d/attrs-24.3.0-py3-none-any.whl", hash = "sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308", size = 63397 },
] ]
[[package]]
name = "black"
version = "25.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "click" },
{ name = "mypy-extensions" },
{ name = "packaging" },
{ name = "pathspec" },
{ name = "platformdirs" },
{ name = "tomli", marker = "python_full_version < '3.11'" },
{ name = "typing-extensions", marker = "python_full_version < '3.11'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/94/49/26a7b0f3f35da4b5a65f081943b7bcd22d7002f5f0fb8098ec1ff21cb6ef/black-25.1.0.tar.gz", hash = "sha256:33496d5cd1222ad73391352b4ae8da15253c5de89b93a80b3e2c8d9a19ec2666", size = 649449 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/4d/3b/4ba3f93ac8d90410423fdd31d7541ada9bcee1df32fb90d26de41ed40e1d/black-25.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:759e7ec1e050a15f89b770cefbf91ebee8917aac5c20483bc2d80a6c3a04df32", size = 1629419 },
{ url = "https://files.pythonhosted.org/packages/b4/02/0bde0485146a8a5e694daed47561785e8b77a0466ccc1f3e485d5ef2925e/black-25.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e519ecf93120f34243e6b0054db49c00a35f84f195d5bce7e9f5cfc578fc2da", size = 1461080 },
{ url = "https://files.pythonhosted.org/packages/52/0e/abdf75183c830eaca7589144ff96d49bce73d7ec6ad12ef62185cc0f79a2/black-25.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:055e59b198df7ac0b7efca5ad7ff2516bca343276c466be72eb04a3bcc1f82d7", size = 1766886 },
{ url = "https://files.pythonhosted.org/packages/dc/a6/97d8bb65b1d8a41f8a6736222ba0a334db7b7b77b8023ab4568288f23973/black-25.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:db8ea9917d6f8fc62abd90d944920d95e73c83a5ee3383493e35d271aca872e9", size = 1419404 },
{ url = "https://files.pythonhosted.org/packages/7e/4f/87f596aca05c3ce5b94b8663dbfe242a12843caaa82dd3f85f1ffdc3f177/black-25.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a39337598244de4bae26475f77dda852ea00a93bd4c728e09eacd827ec929df0", size = 1614372 },
{ url = "https://files.pythonhosted.org/packages/e7/d0/2c34c36190b741c59c901e56ab7f6e54dad8df05a6272a9747ecef7c6036/black-25.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96c1c7cd856bba8e20094e36e0f948718dc688dba4a9d78c3adde52b9e6c2299", size = 1442865 },
{ url = "https://files.pythonhosted.org/packages/21/d4/7518c72262468430ead45cf22bd86c883a6448b9eb43672765d69a8f1248/black-25.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce2e264d59c91e52d8000d507eb20a9aca4a778731a08cfff7e5ac4a4bb7096", size = 1749699 },
{ url = "https://files.pythonhosted.org/packages/58/db/4f5beb989b547f79096e035c4981ceb36ac2b552d0ac5f2620e941501c99/black-25.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:172b1dbff09f86ce6f4eb8edf9dede08b1fce58ba194c87d7a4f1a5aa2f5b3c2", size = 1428028 },
{ url = "https://files.pythonhosted.org/packages/83/71/3fe4741df7adf015ad8dfa082dd36c94ca86bb21f25608eb247b4afb15b2/black-25.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4b60580e829091e6f9238c848ea6750efed72140b91b048770b64e74fe04908b", size = 1650988 },
{ url = "https://files.pythonhosted.org/packages/13/f3/89aac8a83d73937ccd39bbe8fc6ac8860c11cfa0af5b1c96d081facac844/black-25.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e2978f6df243b155ef5fa7e558a43037c3079093ed5d10fd84c43900f2d8ecc", size = 1453985 },
{ url = "https://files.pythonhosted.org/packages/6f/22/b99efca33f1f3a1d2552c714b1e1b5ae92efac6c43e790ad539a163d1754/black-25.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b48735872ec535027d979e8dcb20bf4f70b5ac75a8ea99f127c106a7d7aba9f", size = 1783816 },
{ url = "https://files.pythonhosted.org/packages/18/7e/a27c3ad3822b6f2e0e00d63d58ff6299a99a5b3aee69fa77cd4b0076b261/black-25.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:ea0213189960bda9cf99be5b8c8ce66bb054af5e9e861249cd23471bd7b0b3ba", size = 1440860 },
{ url = "https://files.pythonhosted.org/packages/98/87/0edf98916640efa5d0696e1abb0a8357b52e69e82322628f25bf14d263d1/black-25.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8f0b18a02996a836cc9c9c78e5babec10930862827b1b724ddfe98ccf2f2fe4f", size = 1650673 },
{ url = "https://files.pythonhosted.org/packages/52/e5/f7bf17207cf87fa6e9b676576749c6b6ed0d70f179a3d812c997870291c3/black-25.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:afebb7098bfbc70037a053b91ae8437c3857482d3a690fefc03e9ff7aa9a5fd3", size = 1453190 },
{ url = "https://files.pythonhosted.org/packages/e3/ee/adda3d46d4a9120772fae6de454c8495603c37c4c3b9c60f25b1ab6401fe/black-25.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:030b9759066a4ee5e5aca28c3c77f9c64789cdd4de8ac1df642c40b708be6171", size = 1782926 },
{ url = "https://files.pythonhosted.org/packages/cc/64/94eb5f45dcb997d2082f097a3944cfc7fe87e071907f677e80788a2d7b7a/black-25.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:a22f402b410566e2d1c950708c77ebf5ebd5d0d88a6a2e87c86d9fb48afa0d18", size = 1442613 },
{ url = "https://files.pythonhosted.org/packages/09/71/54e999902aed72baf26bca0d50781b01838251a462612966e9fc4891eadd/black-25.1.0-py3-none-any.whl", hash = "sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717", size = 207646 },
]
[[package]] [[package]]
name = "certifi" name = "certifi"
version = "2024.12.14" version = "2024.12.14"
@@ -220,6 +254,7 @@ ws = [
dev = [ dev = [
{ name = "pyright" }, { name = "pyright" },
{ name = "pytest" }, { name = "pytest" },
{ name = "pytest-examples" },
{ name = "pytest-flakefinder" }, { name = "pytest-flakefinder" },
{ name = "pytest-xdist" }, { name = "pytest-xdist" },
{ name = "ruff" }, { name = "ruff" },
@@ -247,6 +282,7 @@ provides-extras = ["cli", "rich", "ws"]
dev = [ dev = [
{ name = "pyright", specifier = ">=1.1.391" }, { name = "pyright", specifier = ">=1.1.391" },
{ name = "pytest", specifier = ">=8.3.4" }, { name = "pytest", specifier = ">=8.3.4" },
{ name = "pytest-examples", specifier = ">=0.0.14" },
{ name = "pytest-flakefinder", specifier = ">=1.1.0" }, { name = "pytest-flakefinder", specifier = ">=1.1.0" },
{ name = "pytest-xdist", specifier = ">=3.6.1" }, { name = "pytest-xdist", specifier = ">=3.6.1" },
{ name = "ruff", specifier = ">=0.8.5" }, { name = "ruff", specifier = ">=0.8.5" },
@@ -361,6 +397,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 },
] ]
[[package]]
name = "mypy-extensions"
version = "1.0.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/98/a4/1ab47638b92648243faf97a5aeb6ea83059cc3624972ab6b8d2316078d3f/mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782", size = 4433 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2a/e2/5d3f6ada4297caebe1a2add3b126fe800c96f56dbe5d1988a2cbe0b267aa/mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d", size = 4695 },
]
[[package]] [[package]]
name = "nodeenv" name = "nodeenv"
version = "1.9.1" version = "1.9.1"
@@ -391,6 +436,24 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 },
] ]
[[package]]
name = "pathspec"
version = "0.12.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 },
]
[[package]]
name = "platformdirs"
version = "4.3.6"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/13/fc/128cc9cb8f03208bdbf93d3aa862e16d376844a14f9a0ce5cf4507372de4/platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907", size = 21302 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 },
]
[[package]] [[package]]
name = "pluggy" name = "pluggy"
version = "1.5.0" version = "1.5.0"
@@ -550,6 +613,20 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/11/92/76a1c94d3afee238333bc0a42b82935dd8f9cf8ce9e336ff87ee14d9e1cf/pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", size = 343083 }, { url = "https://files.pythonhosted.org/packages/11/92/76a1c94d3afee238333bc0a42b82935dd8f9cf8ce9e336ff87ee14d9e1cf/pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", size = 343083 },
] ]
[[package]]
name = "pytest-examples"
version = "0.0.14"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "black" },
{ name = "pytest" },
{ name = "ruff" },
]
sdist = { url = "https://files.pythonhosted.org/packages/d2/a7/b81d5cf26e9713a2d4c8e6863ee009360c5c07a0cfb880456ec8b09adab7/pytest_examples-0.0.14.tar.gz", hash = "sha256:776d1910709c0c5ce01b29bfe3651c5312d5cfe5c063e23ca6f65aed9af23f09", size = 20767 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2b/99/f418071551ff2b5e8c06bd8b82b1f4fd472b5e4162f018773ba4ef52b6e8/pytest_examples-0.0.14-py3-none-any.whl", hash = "sha256:867a7ea105635d395df712a4b8d0df3bda4c3d78ae97a57b4f115721952b5e25", size = 17919 },
]
[[package]] [[package]]
name = "pytest-flakefinder" name = "pytest-flakefinder"
version = "1.1.0" version = "1.1.0"