mirror of
https://github.com/aljazceru/mcp-python-sdk.git
synced 2025-12-19 06:54:18 +01:00
129
README.md
129
README.md
@@ -99,12 +99,14 @@ from mcp.server.fastmcp import FastMCP
|
||||
# Create an MCP server
|
||||
mcp = FastMCP("Demo")
|
||||
|
||||
|
||||
# Add an addition tool
|
||||
@mcp.tool()
|
||||
def add(a: int, b: int) -> int:
|
||||
"""Add two numbers"""
|
||||
return a + b
|
||||
|
||||
|
||||
# Add a dynamic greeting resource
|
||||
@mcp.resource("greeting://{name}")
|
||||
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
|
||||
# Add lifespan support for startup/shutdown with strong typing
|
||||
from contextlib import asynccontextmanager
|
||||
from dataclasses import dataclass
|
||||
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
|
||||
mcp = FastMCP("My App")
|
||||
@@ -149,24 +155,28 @@ mcp = FastMCP("My App")
|
||||
# Specify dependencies for deployment and development
|
||||
mcp = FastMCP("My App", dependencies=["pandas", "numpy"])
|
||||
|
||||
|
||||
@dataclass
|
||||
class AppContext:
|
||||
db: Database # Replace with your actual DB type
|
||||
db: Database
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def app_lifespan(server: FastMCP) -> AsyncIterator[AppContext]:
|
||||
"""Manage application lifecycle with type-safe context"""
|
||||
# Initialize on startup
|
||||
db = await Database.connect()
|
||||
try:
|
||||
# Initialize on startup
|
||||
await db.connect()
|
||||
yield AppContext(db=db)
|
||||
finally:
|
||||
# Cleanup on shutdown
|
||||
await db.disconnect()
|
||||
|
||||
|
||||
# Pass lifespan to server
|
||||
mcp = FastMCP("My App", lifespan=app_lifespan)
|
||||
|
||||
|
||||
# Access type-safe lifespan context in tools
|
||||
@mcp.tool()
|
||||
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:
|
||||
|
||||
```python
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("My App")
|
||||
|
||||
|
||||
@mcp.resource("config://app")
|
||||
def get_config() -> str:
|
||||
"""Static configuration data"""
|
||||
return "App configuration here"
|
||||
|
||||
|
||||
@mcp.resource("users://{user_id}/profile")
|
||||
def get_user_profile(user_id: str) -> str:
|
||||
"""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:
|
||||
|
||||
```python
|
||||
import httpx
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("My App")
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def calculate_bmi(weight_kg: float, height_m: float) -> float:
|
||||
"""Calculate BMI given weight in kg and height in meters"""
|
||||
return weight_kg / (height_m ** 2)
|
||||
return weight_kg / (height_m**2)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
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:
|
||||
|
||||
```python
|
||||
from mcp.server.fastmcp import FastMCP, types
|
||||
|
||||
mcp = FastMCP("My App")
|
||||
|
||||
|
||||
@mcp.prompt()
|
||||
def review_code(code: str) -> str:
|
||||
return f"Please review this code:\n\n{code}"
|
||||
|
||||
|
||||
@mcp.prompt()
|
||||
def debug_error(error: str) -> list[Message]:
|
||||
def debug_error(error: str) -> list[types.Message]:
|
||||
return [
|
||||
UserMessage("I'm seeing this error:"),
|
||||
UserMessage(error),
|
||||
AssistantMessage("I'll help debug that. What have you tried so far?")
|
||||
types.UserMessage("I'm seeing this error:"),
|
||||
types.UserMessage(error),
|
||||
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 PIL import Image as PILImage
|
||||
|
||||
mcp = FastMCP("My App")
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def create_thumbnail(image_path: str) -> Image:
|
||||
"""Create a thumbnail from an image"""
|
||||
@@ -250,6 +282,9 @@ The Context object gives your tools and resources access to MCP capabilities:
|
||||
```python
|
||||
from mcp.server.fastmcp import FastMCP, Context
|
||||
|
||||
mcp = FastMCP("My App")
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
async def long_task(files: list[str], ctx: Context) -> str:
|
||||
"""Process multiple files with progress tracking"""
|
||||
@@ -322,16 +357,19 @@ from mcp.server.fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("Echo")
|
||||
|
||||
|
||||
@mcp.resource("echo://{message}")
|
||||
def echo_resource(message: str) -> str:
|
||||
"""Echo a message as a resource"""
|
||||
return f"Resource echo: {message}"
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def echo_tool(message: str) -> str:
|
||||
"""Echo a message as a tool"""
|
||||
return f"Tool echo: {message}"
|
||||
|
||||
|
||||
@mcp.prompt()
|
||||
def echo_prompt(message: str) -> str:
|
||||
"""Create an echo prompt"""
|
||||
@@ -343,20 +381,21 @@ def echo_prompt(message: str) -> str:
|
||||
A more complex example showing database integration:
|
||||
|
||||
```python
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
import sqlite3
|
||||
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP("SQLite Explorer")
|
||||
|
||||
|
||||
@mcp.resource("schema://main")
|
||||
def get_schema() -> str:
|
||||
"""Provide the database schema as a resource"""
|
||||
conn = sqlite3.connect("database.db")
|
||||
schema = conn.execute(
|
||||
"SELECT sql FROM sqlite_master WHERE type='table'"
|
||||
).fetchall()
|
||||
schema = conn.execute("SELECT sql FROM sqlite_master WHERE type='table'").fetchall()
|
||||
return "\n".join(sql[0] for sql in schema if sql[0])
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def query_data(sql: str) -> str:
|
||||
"""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 typing import AsyncIterator
|
||||
|
||||
from fake_database import Database # Replace with your actual DB type
|
||||
|
||||
from mcp.server import Server
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def server_lifespan(server: Server) -> AsyncIterator[dict]:
|
||||
"""Manage server startup and shutdown lifecycle."""
|
||||
# Initialize resources on startup
|
||||
db = await Database.connect()
|
||||
try:
|
||||
# Initialize resources on startup
|
||||
await db.connect()
|
||||
yield {"db": db}
|
||||
finally:
|
||||
# Clean up on shutdown
|
||||
await db.disconnect()
|
||||
|
||||
|
||||
# Pass lifespan to server
|
||||
server = Server("example-server", lifespan=server_lifespan)
|
||||
|
||||
|
||||
# Access lifespan context in handlers
|
||||
@server.call_tool()
|
||||
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
|
||||
|
||||
```python
|
||||
from mcp.server.lowlevel import Server, NotificationOptions
|
||||
from mcp.server.models import InitializationOptions
|
||||
import mcp.server.stdio
|
||||
import mcp.types as types
|
||||
from mcp.server.lowlevel import NotificationOptions, Server
|
||||
from mcp.server.models import InitializationOptions
|
||||
|
||||
# Create a server instance
|
||||
server = Server("example-server")
|
||||
|
||||
|
||||
@server.list_prompts()
|
||||
async def handle_list_prompts() -> list[types.Prompt]:
|
||||
return [
|
||||
@@ -422,18 +469,16 @@ async def handle_list_prompts() -> list[types.Prompt]:
|
||||
description="An example prompt template",
|
||||
arguments=[
|
||||
types.PromptArgument(
|
||||
name="arg1",
|
||||
description="Example argument",
|
||||
required=True
|
||||
name="arg1", description="Example argument", required=True
|
||||
)
|
||||
]
|
||||
],
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
@server.get_prompt()
|
||||
async def handle_get_prompt(
|
||||
name: str,
|
||||
arguments: dict[str, str] | None
|
||||
name: str, arguments: dict[str, str] | None
|
||||
) -> types.GetPromptResult:
|
||||
if name != "example-prompt":
|
||||
raise ValueError(f"Unknown prompt: {name}")
|
||||
@@ -443,14 +488,12 @@ async def handle_get_prompt(
|
||||
messages=[
|
||||
types.PromptMessage(
|
||||
role="user",
|
||||
content=types.TextContent(
|
||||
type="text",
|
||||
text="Example prompt text"
|
||||
)
|
||||
content=types.TextContent(type="text", text="Example prompt text"),
|
||||
)
|
||||
]
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
async def run():
|
||||
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
|
||||
await server.run(
|
||||
@@ -462,12 +505,14 @@ async def run():
|
||||
capabilities=server.get_capabilities(
|
||||
notification_options=NotificationOptions(),
|
||||
experimental_capabilities={},
|
||||
)
|
||||
)
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import asyncio
|
||||
|
||||
asyncio.run(run())
|
||||
```
|
||||
|
||||
@@ -476,18 +521,21 @@ if __name__ == "__main__":
|
||||
The SDK provides a high-level client interface for connecting to MCP servers:
|
||||
|
||||
```python
|
||||
from mcp import ClientSession, StdioServerParameters
|
||||
from mcp import ClientSession, StdioServerParameters, types
|
||||
from mcp.client.stdio import stdio_client
|
||||
|
||||
# Create server parameters for stdio connection
|
||||
server_params = StdioServerParameters(
|
||||
command="python", # Executable
|
||||
args=["example_server.py"], # Optional command line arguments
|
||||
env=None # Optional environment variables
|
||||
command="python", # Executable
|
||||
args=["example_server.py"], # Optional command line arguments
|
||||
env=None, # Optional environment variables
|
||||
)
|
||||
|
||||
|
||||
# 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(
|
||||
role="assistant",
|
||||
content=types.TextContent(
|
||||
@@ -498,9 +546,12 @@ async def handle_sampling_message(message: types.CreateMessageRequestParams) ->
|
||||
stopReason="endTurn",
|
||||
)
|
||||
|
||||
|
||||
async def run():
|
||||
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
|
||||
await session.initialize()
|
||||
|
||||
@@ -508,7 +559,9 @@ async def run():
|
||||
prompts = await session.list_prompts()
|
||||
|
||||
# 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
|
||||
resources = await session.list_resources()
|
||||
@@ -522,8 +575,10 @@ async def run():
|
||||
# Call a tool
|
||||
result = await session.call_tool("tool-name", arguments={"arg1": "value"})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import asyncio
|
||||
|
||||
asyncio.run(run())
|
||||
```
|
||||
|
||||
|
||||
@@ -49,6 +49,7 @@ dev-dependencies = [
|
||||
"trio>=0.26.2",
|
||||
"pytest-flakefinder>=1.1.0",
|
||||
"pytest-xdist>=3.6.1",
|
||||
"pytest-examples>=0.0.14",
|
||||
]
|
||||
|
||||
[build-system]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""Tests for example servers"""
|
||||
|
||||
import pytest
|
||||
from pytest_examples import CodeExample, EvalExample, find_examples
|
||||
|
||||
from mcp.shared.memory import (
|
||||
create_connected_server_and_client_session as client_session,
|
||||
@@ -70,3 +71,17 @@ async def test_desktop(monkeypatch):
|
||||
assert isinstance(content.text, str)
|
||||
assert "/fake/path/file1.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
77
uv.lock
generated
@@ -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 },
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "certifi"
|
||||
version = "2024.12.14"
|
||||
@@ -220,6 +254,7 @@ ws = [
|
||||
dev = [
|
||||
{ name = "pyright" },
|
||||
{ name = "pytest" },
|
||||
{ name = "pytest-examples" },
|
||||
{ name = "pytest-flakefinder" },
|
||||
{ name = "pytest-xdist" },
|
||||
{ name = "ruff" },
|
||||
@@ -247,6 +282,7 @@ provides-extras = ["cli", "rich", "ws"]
|
||||
dev = [
|
||||
{ name = "pyright", specifier = ">=1.1.391" },
|
||||
{ name = "pytest", specifier = ">=8.3.4" },
|
||||
{ name = "pytest-examples", specifier = ">=0.0.14" },
|
||||
{ name = "pytest-flakefinder", specifier = ">=1.1.0" },
|
||||
{ name = "pytest-xdist", specifier = ">=3.6.1" },
|
||||
{ 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 },
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "nodeenv"
|
||||
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 },
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "pluggy"
|
||||
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 },
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "pytest-flakefinder"
|
||||
version = "1.1.0"
|
||||
|
||||
Reference in New Issue
Block a user