From cadf6b47eaf7cedc44309f37876287d68173debc Mon Sep 17 00:00:00 2001 From: Samuel Colvin Date: Fri, 14 Mar 2025 11:04:55 +0000 Subject: [PATCH 1/2] lint docs examples --- README.md | 129 +++++++++++++++++++++++++++++------------ pyproject.toml | 1 + tests/test_examples.py | 14 +++++ uv.lock | 77 ++++++++++++++++++++++++ 4 files changed, 184 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 41d3cc622..c062f3a9b 100644 --- a/README.md +++ b/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()) ``` diff --git a/pyproject.toml b/pyproject.toml index 69db82c9c..046e90a48 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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] diff --git a/tests/test_examples.py b/tests/test_examples.py index b097fafbf..fb7b9c1ac 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -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,16 @@ 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] = ['D', 'Q001', '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) diff --git a/uv.lock b/uv.lock index 9188dd945..664256f67 100644 --- a/uv.lock +++ b/uv.lock @@ -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" From 7dfa509e3b8d6c52e598e7102cdb80d5d03847bf Mon Sep 17 00:00:00 2001 From: Samuel Colvin Date: Fri, 14 Mar 2025 11:22:14 +0000 Subject: [PATCH 2/2] format --- tests/test_examples.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/test_examples.py b/tests/test_examples.py index fb7b9c1ac..c5e8ec9d7 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -73,12 +73,13 @@ async def test_desktop(monkeypatch): assert "/fake/path/file2.txt" in content.text - -@pytest.mark.parametrize('example', find_examples('README.md'), ids=str) +@pytest.mark.parametrize("example", find_examples("README.md"), ids=str) def test_docs_examples(example: CodeExample, eval_example: EvalExample): - ruff_ignore: list[str] = ['D', 'Q001', 'F841', 'I001'] + ruff_ignore: list[str] = ["F841", "I001"] - eval_example.set_config(ruff_ignore=ruff_ignore, target_version='py310', line_length=88) + 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)