Merge branch 'main' into v1.2.x
This commit is contained in:
commit
1e52765052
18 changed files with 345 additions and 76 deletions
73
CLAUDE.md
Normal file
73
CLAUDE.md
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
# Tool Usage Learnings
|
||||
|
||||
This file is intended to be used by an LLM such as Claude.
|
||||
|
||||
## UV Package Manager
|
||||
|
||||
- Use `uv run` to run Python tools without activating virtual environments
|
||||
- For formatting: `uv run ruff format .`
|
||||
- For type checking: `uv run pyright`
|
||||
- For upgrading packages:
|
||||
- `uv add --dev package --upgrade-package package` to upgrade a specific package
|
||||
- Don't use `@latest` syntax - it doesn't work
|
||||
- Be careful with `uv pip install` as it may downgrade packages
|
||||
|
||||
## Git and GitHub CLI
|
||||
|
||||
- When using gh CLI for PRs, always quote title and body:
|
||||
```bash
|
||||
gh pr create --title "\"my title\"" --body "\"my body\""
|
||||
```
|
||||
- For git commits, use double quotes and escape inner quotes:
|
||||
```bash
|
||||
git commit -am "\"fix: my commit message\""
|
||||
```
|
||||
|
||||
## Python Tools
|
||||
|
||||
### Ruff
|
||||
- Handles both formatting and linting
|
||||
- For formatting: `uv run ruff format .`
|
||||
- For checking: `uv run ruff check .`
|
||||
- For auto-fixing: `uv run ruff check . --fix`
|
||||
- Common issues:
|
||||
- Line length (default 88 chars)
|
||||
- Import sorting (I001 errors)
|
||||
- Unused imports
|
||||
- When line length errors occur:
|
||||
- For strings, use parentheses and line continuation
|
||||
- For function calls, use multiple lines with proper indentation
|
||||
- For imports, split into multiple lines
|
||||
|
||||
### Pyright
|
||||
- Type checker
|
||||
- Run with: `uv run pyright`
|
||||
- Version warnings can be ignored if type checking passes
|
||||
- Common issues:
|
||||
- Optional types need explicit None checks
|
||||
- String operations need type narrowing
|
||||
|
||||
## Pre-commit Hooks
|
||||
|
||||
- Configuration in `.pre-commit-config.yaml`
|
||||
- Runs automatically on git commit
|
||||
- Includes:
|
||||
- Prettier for YAML/JSON formatting
|
||||
- Ruff for Python formatting and linting
|
||||
- When updating ruff version:
|
||||
- Check available versions on PyPI
|
||||
- Update `rev` in config to match available version
|
||||
- Add and commit config changes before other changes
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. Always check git status and diff before committing
|
||||
2. Run formatters before type checkers
|
||||
3. When fixing CI:
|
||||
- Start with formatting issues
|
||||
- Then fix type errors
|
||||
- Then address any remaining linting issues
|
||||
4. For type errors:
|
||||
- Get full context around error lines
|
||||
- Consider optional types
|
||||
- Add type narrowing checks when needed
|
||||
64
examples/fastmcp/unicode_example.py
Normal file
64
examples/fastmcp/unicode_example.py
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
"""
|
||||
Example FastMCP server that uses Unicode characters in various places to help test
|
||||
Unicode handling in tools and inspectors.
|
||||
"""
|
||||
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
mcp = FastMCP()
|
||||
|
||||
|
||||
@mcp.tool(
|
||||
description="🌟 A tool that uses various Unicode characters in its description: "
|
||||
"á é í ó ú ñ 漢字 🎉"
|
||||
)
|
||||
def hello_unicode(name: str = "世界", greeting: str = "¡Hola") -> str:
|
||||
"""
|
||||
A simple tool that demonstrates Unicode handling in:
|
||||
- Tool description (emojis, accents, CJK characters)
|
||||
- Parameter defaults (CJK characters)
|
||||
- Return values (Spanish punctuation, emojis)
|
||||
"""
|
||||
return f"{greeting}, {name}! 👋"
|
||||
|
||||
|
||||
@mcp.tool(description="🎨 Tool that returns a list of emoji categories")
|
||||
def list_emoji_categories() -> list[str]:
|
||||
"""Returns a list of emoji categories with emoji examples."""
|
||||
return [
|
||||
"😀 Smileys & Emotion",
|
||||
"👋 People & Body",
|
||||
"🐶 Animals & Nature",
|
||||
"🍎 Food & Drink",
|
||||
"⚽ Activities",
|
||||
"🌍 Travel & Places",
|
||||
"💡 Objects",
|
||||
"❤️ Symbols",
|
||||
"🚩 Flags",
|
||||
]
|
||||
|
||||
|
||||
@mcp.tool(description="🔤 Tool that returns text in different scripts")
|
||||
def multilingual_hello() -> str:
|
||||
"""Returns hello in different scripts and writing systems."""
|
||||
return "\n".join(
|
||||
[
|
||||
"English: Hello!",
|
||||
"Spanish: ¡Hola!",
|
||||
"French: Bonjour!",
|
||||
"German: Grüß Gott!",
|
||||
"Russian: Привет!",
|
||||
"Greek: Γεια σας!",
|
||||
"Hebrew: !שָׁלוֹם",
|
||||
"Arabic: !مرحبا",
|
||||
"Hindi: नमस्ते!",
|
||||
"Chinese: 你好!",
|
||||
"Japanese: こんにちは!",
|
||||
"Korean: 안녕하세요!",
|
||||
"Thai: สวัสดี!",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run()
|
||||
|
|
@ -90,9 +90,9 @@ def main(port: int, transport: str) -> int:
|
|||
if transport == "sse":
|
||||
from mcp.server.sse import SseServerTransport
|
||||
from starlette.applications import Starlette
|
||||
from starlette.routing import Route
|
||||
from starlette.routing import Mount, Route
|
||||
|
||||
sse = SseServerTransport("/messages")
|
||||
sse = SseServerTransport("/messages/")
|
||||
|
||||
async def handle_sse(request):
|
||||
async with sse.connect_sse(
|
||||
|
|
@ -102,14 +102,11 @@ def main(port: int, transport: str) -> int:
|
|||
streams[0], streams[1], app.create_initialization_options()
|
||||
)
|
||||
|
||||
async def handle_messages(request):
|
||||
await sse.handle_post_message(request.scope, request.receive, request._send)
|
||||
|
||||
starlette_app = Starlette(
|
||||
debug=True,
|
||||
routes=[
|
||||
Route("/sse", endpoint=handle_sse),
|
||||
Route("/messages", endpoint=handle_messages, methods=["POST"]),
|
||||
Mount("/messages/", app=sse.handle_post_message),
|
||||
],
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -47,9 +47,9 @@ def main(port: int, transport: str) -> int:
|
|||
if transport == "sse":
|
||||
from mcp.server.sse import SseServerTransport
|
||||
from starlette.applications import Starlette
|
||||
from starlette.routing import Route
|
||||
from starlette.routing import Mount, Route
|
||||
|
||||
sse = SseServerTransport("/messages")
|
||||
sse = SseServerTransport("/messages/")
|
||||
|
||||
async def handle_sse(request):
|
||||
async with sse.connect_sse(
|
||||
|
|
@ -59,14 +59,11 @@ def main(port: int, transport: str) -> int:
|
|||
streams[0], streams[1], app.create_initialization_options()
|
||||
)
|
||||
|
||||
async def handle_messages(request):
|
||||
await sse.handle_post_message(request.scope, request.receive, request._send)
|
||||
|
||||
starlette_app = Starlette(
|
||||
debug=True,
|
||||
routes=[
|
||||
Route("/sse", endpoint=handle_sse),
|
||||
Route("/messages", endpoint=handle_messages, methods=["POST"]),
|
||||
Mount("/messages/", app=sse.handle_post_message),
|
||||
],
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -60,9 +60,9 @@ def main(port: int, transport: str) -> int:
|
|||
if transport == "sse":
|
||||
from mcp.server.sse import SseServerTransport
|
||||
from starlette.applications import Starlette
|
||||
from starlette.routing import Route
|
||||
from starlette.routing import Mount, Route
|
||||
|
||||
sse = SseServerTransport("/messages")
|
||||
sse = SseServerTransport("/messages/")
|
||||
|
||||
async def handle_sse(request):
|
||||
async with sse.connect_sse(
|
||||
|
|
@ -72,14 +72,11 @@ def main(port: int, transport: str) -> int:
|
|||
streams[0], streams[1], app.create_initialization_options()
|
||||
)
|
||||
|
||||
async def handle_messages(request):
|
||||
await sse.handle_post_message(request.scope, request.receive, request._send)
|
||||
|
||||
starlette_app = Starlette(
|
||||
debug=True,
|
||||
routes=[
|
||||
Route("/sse", endpoint=handle_sse),
|
||||
Route("/messages", endpoint=handle_messages, methods=["POST"]),
|
||||
Mount("/messages/", app=sse.handle_post_message),
|
||||
],
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -42,9 +42,9 @@ mcp = "mcp.cli:app [cli]"
|
|||
[tool.uv]
|
||||
resolution = "lowest-direct"
|
||||
dev-dependencies = [
|
||||
"pyright>=1.1.378",
|
||||
"pytest>=8.3.3",
|
||||
"ruff>=0.8.1",
|
||||
"pyright>=1.1.391",
|
||||
"pytest>=8.3.4",
|
||||
"ruff>=0.8.5",
|
||||
"trio>=0.26.2",
|
||||
"pytest-flakefinder>=1.1.0",
|
||||
"pytest-xdist>=3.6.1",
|
||||
|
|
@ -83,4 +83,4 @@ target-version = "py310"
|
|||
members = ["examples/servers/*"]
|
||||
|
||||
[tool.uv.sources]
|
||||
mcp = { workspace = true }
|
||||
mcp = { workspace = true }
|
||||
|
|
@ -87,7 +87,7 @@ def update_claude_config(
|
|||
args = ["run"]
|
||||
|
||||
# Collect all packages in a set to deduplicate
|
||||
packages = {"fastmcp"}
|
||||
packages = {"mcp"}
|
||||
if with_packages:
|
||||
packages.update(pkg for pkg in with_packages if pkg)
|
||||
|
||||
|
|
@ -107,7 +107,7 @@ def update_claude_config(
|
|||
file_spec = str(Path(file_spec).resolve())
|
||||
|
||||
# Add fastmcp run command
|
||||
args.extend(["fastmcp", "run", file_spec])
|
||||
args.extend(["mcp", "run", file_spec])
|
||||
|
||||
server_config = {
|
||||
"command": "uv",
|
||||
|
|
|
|||
|
|
@ -373,7 +373,7 @@ def install(
|
|||
list[str],
|
||||
typer.Option(
|
||||
"--env-var",
|
||||
"-e",
|
||||
"-v",
|
||||
help="Environment variables in KEY=VALUE format",
|
||||
),
|
||||
] = [],
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import os
|
||||
import sys
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import Literal
|
||||
|
||||
import anyio
|
||||
import anyio.lowlevel
|
||||
|
|
@ -65,6 +66,21 @@ class StdioServerParameters(BaseModel):
|
|||
If not specified, the result of get_default_environment() will be used.
|
||||
"""
|
||||
|
||||
encoding: str = "utf-8"
|
||||
"""
|
||||
The text encoding used when sending/receiving messages to the server
|
||||
|
||||
defaults to utf-8
|
||||
"""
|
||||
|
||||
encoding_error_handler: Literal["strict", "ignore", "replace"] = "strict"
|
||||
"""
|
||||
The text encoding error handler.
|
||||
|
||||
See https://docs.python.org/3/library/codecs.html#codec-base-classes for
|
||||
explanations of possible values
|
||||
"""
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def stdio_client(server: StdioServerParameters):
|
||||
|
|
@ -93,7 +109,11 @@ async def stdio_client(server: StdioServerParameters):
|
|||
try:
|
||||
async with read_stream_writer:
|
||||
buffer = ""
|
||||
async for chunk in TextReceiveStream(process.stdout):
|
||||
async for chunk in TextReceiveStream(
|
||||
process.stdout,
|
||||
encoding=server.encoding,
|
||||
errors=server.encoding_error_handler,
|
||||
):
|
||||
lines = (buffer + chunk).split("\n")
|
||||
buffer = lines.pop()
|
||||
|
||||
|
|
@ -115,7 +135,12 @@ async def stdio_client(server: StdioServerParameters):
|
|||
async with write_stream_reader:
|
||||
async for message in write_stream_reader:
|
||||
json = message.model_dump_json(by_alias=True, exclude_none=True)
|
||||
await process.stdin.send((json + "\n").encode())
|
||||
await process.stdin.send(
|
||||
(json + "\n").encode(
|
||||
encoding=server.encoding,
|
||||
errors=server.encoding_error_handler,
|
||||
)
|
||||
)
|
||||
except anyio.ClosedResourceError:
|
||||
await anyio.lowlevel.checkpoint()
|
||||
|
||||
|
|
|
|||
|
|
@ -423,9 +423,9 @@ class FastMCP:
|
|||
async def run_sse_async(self) -> None:
|
||||
"""Run the server using SSE transport."""
|
||||
from starlette.applications import Starlette
|
||||
from starlette.routing import Route
|
||||
from starlette.routing import Mount, Route
|
||||
|
||||
sse = SseServerTransport("/messages")
|
||||
sse = SseServerTransport("/messages/")
|
||||
|
||||
async def handle_sse(request):
|
||||
async with sse.connect_sse(
|
||||
|
|
@ -437,14 +437,11 @@ class FastMCP:
|
|||
self._mcp_server.create_initialization_options(),
|
||||
)
|
||||
|
||||
async def handle_messages(request):
|
||||
await sse.handle_post_message(request.scope, request.receive, request._send)
|
||||
|
||||
starlette_app = Starlette(
|
||||
debug=self.settings.debug,
|
||||
routes=[
|
||||
Route("/sse", endpoint=handle_sse),
|
||||
Route("/messages", endpoint=handle_messages, methods=["POST"]),
|
||||
Mount("/messages/", app=sse.handle_post_message),
|
||||
],
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -101,8 +101,9 @@ class NotificationOptions:
|
|||
|
||||
|
||||
class Server:
|
||||
def __init__(self, name: str):
|
||||
def __init__(self, name: str, version: str | None = None):
|
||||
self.name = name
|
||||
self.version = version
|
||||
self.request_handlers: dict[
|
||||
type, Callable[..., Awaitable[types.ServerResult]]
|
||||
] = {
|
||||
|
|
@ -133,7 +134,7 @@ class Server:
|
|||
|
||||
return InitializationOptions(
|
||||
server_name=self.name,
|
||||
server_version=pkg_version("mcp"),
|
||||
server_version=self.version if self.version else pkg_version("mcp"),
|
||||
capabilities=self.get_capabilities(
|
||||
notification_options or NotificationOptions(),
|
||||
experimental_capabilities or {},
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@ This module implements a Server-Sent Events (SSE) transport layer for MCP server
|
|||
Example usage:
|
||||
```
|
||||
# Create an SSE transport at an endpoint
|
||||
sse = SseServerTransport("/messages")
|
||||
sse = SseServerTransport("/messages/")
|
||||
|
||||
# Create Starlette routes for SSE and message handling
|
||||
routes = [
|
||||
Route("/sse", endpoint=handle_sse),
|
||||
Route("/messages", endpoint=handle_messages, methods=["POST"])
|
||||
Mount("/messages/", app=sse.handle_post_message),
|
||||
]
|
||||
|
||||
# Define handler functions
|
||||
|
|
@ -23,9 +23,6 @@ Example usage:
|
|||
streams[0], streams[1], app.create_initialization_options()
|
||||
)
|
||||
|
||||
async def handle_messages(request):
|
||||
await sse.handle_post_message(request.scope, request.receive, request._send)
|
||||
|
||||
# Create and run Starlette app
|
||||
starlette_app = Starlette(routes=routes)
|
||||
uvicorn.run(starlette_app, host="0.0.0.0", port=port)
|
||||
|
|
|
|||
|
|
@ -7,3 +7,8 @@ class McpError(Exception):
|
|||
"""
|
||||
|
||||
error: ErrorData
|
||||
|
||||
def __init__(self, error: ErrorData):
|
||||
"""Initialize McpError."""
|
||||
super().__init__(error.message)
|
||||
self.error = error
|
||||
|
|
|
|||
52
tests/client/test_config.py
Normal file
52
tests/client/test_config.py
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import json
|
||||
import subprocess
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from mcp.cli.claude import update_claude_config
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def temp_config_dir(tmp_path):
|
||||
"""Create a temporary Claude config directory."""
|
||||
config_dir = tmp_path / "Claude"
|
||||
config_dir.mkdir()
|
||||
return config_dir
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_config_path(temp_config_dir):
|
||||
"""Mock get_claude_config_path to return our temporary directory."""
|
||||
with patch("mcp.cli.claude.get_claude_config_path", return_value=temp_config_dir):
|
||||
yield temp_config_dir
|
||||
|
||||
|
||||
def test_command_execution(mock_config_path):
|
||||
"""Test that the generated command can actually be executed."""
|
||||
# Setup
|
||||
server_name = "test_server"
|
||||
file_spec = "test_server.py:app"
|
||||
|
||||
# Update config
|
||||
success = update_claude_config(
|
||||
file_spec=file_spec,
|
||||
server_name=server_name,
|
||||
)
|
||||
assert success
|
||||
|
||||
# Read the generated config
|
||||
config_file = mock_config_path / "claude_desktop_config.json"
|
||||
config = json.loads(config_file.read_text())
|
||||
|
||||
# Get the command and args
|
||||
server_config = config["mcpServers"][server_name]
|
||||
command = server_config["command"]
|
||||
args = server_config["args"]
|
||||
|
||||
test_args = [command] + args + ["--help"]
|
||||
|
||||
result = subprocess.run(test_args, capture_output=True, text=True, timeout=5)
|
||||
|
||||
assert result.returncode == 0
|
||||
assert "usage" in result.stdout.lower()
|
||||
35
tests/issues/test_100_tool_listing.py
Normal file
35
tests/issues/test_100_tool_listing.py
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import pytest
|
||||
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
pytestmark = pytest.mark.anyio
|
||||
|
||||
|
||||
async def test_list_tools_returns_all_tools():
|
||||
mcp = FastMCP("TestTools")
|
||||
|
||||
# Create 100 tools with unique names
|
||||
num_tools = 100
|
||||
for i in range(num_tools):
|
||||
|
||||
@mcp.tool(name=f"tool_{i}")
|
||||
def dummy_tool_func():
|
||||
f"""Tool number {i}"""
|
||||
return i
|
||||
|
||||
globals()[f"dummy_tool_{i}"] = (
|
||||
dummy_tool_func # Keep reference to avoid garbage collection
|
||||
)
|
||||
|
||||
# Get all tools
|
||||
tools = await mcp.list_tools()
|
||||
|
||||
# Verify we get all tools
|
||||
assert len(tools) == num_tools, f"Expected {num_tools} tools, but got {len(tools)}"
|
||||
|
||||
# Verify each tool is unique and has the correct name
|
||||
tool_names = [tool.name for tool in tools]
|
||||
expected_names = [f"tool_{i}" for i in range(num_tools)]
|
||||
assert sorted(tool_names) == sorted(
|
||||
expected_names
|
||||
), "Tool names don't match expected names"
|
||||
|
|
@ -30,6 +30,34 @@ class TestServer:
|
|||
mcp = FastMCP()
|
||||
assert mcp.name == "FastMCP"
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_non_ascii_description(self):
|
||||
"""Test that FastMCP handles non-ASCII characters in descriptions correctly"""
|
||||
mcp = FastMCP()
|
||||
|
||||
@mcp.tool(
|
||||
description=(
|
||||
"🌟 This tool uses emojis and UTF-8 characters: á é í ó ú ñ 漢字 🎉"
|
||||
)
|
||||
)
|
||||
def hello_world(name: str = "世界") -> str:
|
||||
return f"¡Hola, {name}! 👋"
|
||||
|
||||
async with client_session(mcp._mcp_server) as client:
|
||||
tools = await client.list_tools()
|
||||
assert len(tools.tools) == 1
|
||||
tool = tools.tools[0]
|
||||
assert tool.description is not None
|
||||
assert "🌟" in tool.description
|
||||
assert "漢字" in tool.description
|
||||
assert "🎉" in tool.description
|
||||
|
||||
result = await client.call_tool("hello_world", {})
|
||||
assert len(result.content) == 1
|
||||
content = result.content[0]
|
||||
assert isinstance(content, TextContent)
|
||||
assert "¡Hola, 世界! 👋" == content.text
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_add_tool_decorator(self):
|
||||
mcp = FastMCP()
|
||||
|
|
|
|||
|
|
@ -18,12 +18,12 @@ from mcp.types import (
|
|||
|
||||
@pytest.mark.anyio
|
||||
async def test_server_session_initialize():
|
||||
server_to_client_send, server_to_client_receive = anyio.create_memory_object_stream(
|
||||
1, JSONRPCMessage
|
||||
)
|
||||
client_to_server_send, client_to_server_receive = anyio.create_memory_object_stream(
|
||||
1, JSONRPCMessage
|
||||
)
|
||||
server_to_client_send, server_to_client_receive = anyio.create_memory_object_stream[
|
||||
JSONRPCMessage
|
||||
](1)
|
||||
client_to_server_send, client_to_server_receive = anyio.create_memory_object_stream[
|
||||
JSONRPCMessage
|
||||
](1)
|
||||
|
||||
async def run_client(client: ClientSession):
|
||||
async for message in client_session.incoming_messages:
|
||||
|
|
|
|||
57
uv.lock
generated
57
uv.lock
generated
|
|
@ -240,11 +240,11 @@ requires-dist = [
|
|||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [
|
||||
{ name = "pyright", specifier = ">=1.1.378" },
|
||||
{ name = "pytest", specifier = ">=8.3.3" },
|
||||
{ name = "pyright", specifier = ">=1.1.391" },
|
||||
{ name = "pytest", specifier = ">=8.3.4" },
|
||||
{ name = "pytest-flakefinder", specifier = ">=1.1.0" },
|
||||
{ name = "pytest-xdist", specifier = ">=3.6.1" },
|
||||
{ name = "ruff", specifier = ">=0.8.1" },
|
||||
{ name = "ruff", specifier = ">=0.8.5" },
|
||||
{ name = "trio", specifier = ">=0.26.2" },
|
||||
]
|
||||
|
||||
|
|
@ -517,19 +517,20 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "pyright"
|
||||
version = "1.1.378"
|
||||
version = "1.1.391"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "nodeenv" },
|
||||
{ name = "typing-extensions" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/3d/f0/e8aa5555410d88f898bef04da2102b0a9bf144658c98d34872e91621ced2/pyright-1.1.378.tar.gz", hash = "sha256:78a043be2876d12d0af101d667e92c7734f3ebb9db71dccc2c220e7e7eb89ca2", size = 17486 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/11/05/4ea52a8a45cc28897edb485b4102d37cbfd5fce8445d679cdeb62bfad221/pyright-1.1.391.tar.gz", hash = "sha256:66b2d42cdf5c3cbab05f2f4b76e8bec8aa78e679bfa0b6ad7b923d9e027cadb2", size = 21965 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/38/c6/f0d4bc20c13b20cecfbf13c699477c825e45767f1dc5068137323f86e495/pyright-1.1.378-py3-none-any.whl", hash = "sha256:8853776138b01bc284da07ac481235be7cc89d3176b073d2dba73636cb95be79", size = 18222 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/89/66f49552fbeb21944c8077d11834b2201514a56fd1b7747ffff9630f1bd9/pyright-1.1.391-py3-none-any.whl", hash = "sha256:54fa186f8b3e8a55a44ebfa842636635688670c6896dcf6cf4a7fc75062f4d15", size = 18579 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytest"
|
||||
version = "8.3.3"
|
||||
version = "8.3.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
|
|
@ -539,9 +540,9 @@ dependencies = [
|
|||
{ name = "pluggy" },
|
||||
{ name = "tomli", marker = "python_full_version < '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8b/6c/62bbd536103af674e227c41a8f3dcd022d591f6eed5facb5a0f31ee33bbc/pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181", size = 1442487 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/05/35/30e0d83068951d90a01852cb1cef56e5d8a09d20c7f511634cc2f7e0372a/pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761", size = 1445919 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/77/7440a06a8ead44c7757a64362dd22df5760f9b12dc5f11b6188cd2fc27a0/pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2", size = 342341 },
|
||||
{ url = "https://files.pythonhosted.org/packages/11/92/76a1c94d3afee238333bc0a42b82935dd8f9cf8ce9e336ff87ee14d9e1cf/pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", size = 343083 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -594,27 +595,27 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.8.1"
|
||||
version = "0.8.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/95/d0/8ff5b189d125f4260f2255d143bf2fa413b69c2610c405ace7a0a8ec81ec/ruff-0.8.1.tar.gz", hash = "sha256:3583db9a6450364ed5ca3f3b4225958b24f78178908d5c4bc0f46251ccca898f", size = 3313222 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/25/5d/4b5403f3e89837decfd54c51bea7f94b7d3fae77e08858603d0e04d7ad17/ruff-0.8.5.tar.gz", hash = "sha256:1098d36f69831f7ff2a1da3e6407d5fbd6dfa2559e4f74ff2d260c5588900317", size = 3454835 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a2/d6/1a6314e568db88acdbb5121ed53e2c52cebf3720d3437a76f82f923bf171/ruff-0.8.1-py3-none-linux_armv6l.whl", hash = "sha256:fae0805bd514066f20309f6742f6ee7904a773eb9e6c17c45d6b1600ca65c9b5", size = 10532605 },
|
||||
{ url = "https://files.pythonhosted.org/packages/89/a8/a957a8812e31facffb6a26a30be0b5b4af000a6e30c7d43a22a5232a3398/ruff-0.8.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b8a4f7385c2285c30f34b200ca5511fcc865f17578383db154e098150ce0a087", size = 10278243 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/23/9db40fa19c453fabf94f7a35c61c58f20e8200b4734a20839515a19da790/ruff-0.8.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:cd054486da0c53e41e0086e1730eb77d1f698154f910e0cd9e0d64274979a209", size = 9917739 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e2/a0/6ee2d949835d5701d832fc5acd05c0bfdad5e89cfdd074a171411f5ccad5/ruff-0.8.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2029b8c22da147c50ae577e621a5bfbc5d1fed75d86af53643d7a7aee1d23871", size = 10779153 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/25/9c11dca9404ef1eb24833f780146236131a3c7941de394bc356912ef1041/ruff-0.8.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2666520828dee7dfc7e47ee4ea0d928f40de72056d929a7c5292d95071d881d1", size = 10304387 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c8/b9/84c323780db1b06feae603a707d82dbbd85955c8c917738571c65d7d5aff/ruff-0.8.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:333c57013ef8c97a53892aa56042831c372e0bb1785ab7026187b7abd0135ad5", size = 11360351 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/e1/9d4bbb2ace7aad14ded20e4674a48cda5b902aed7a1b14e6b028067060c4/ruff-0.8.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:288326162804f34088ac007139488dcb43de590a5ccfec3166396530b58fb89d", size = 12022879 },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/28/752ff6120c0e7f9981bc4bc275d540c7f36db1379ba9db9142f69c88db21/ruff-0.8.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b12c39b9448632284561cbf4191aa1b005882acbc81900ffa9f9f471c8ff7e26", size = 11610354 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/8c/967b61c2cc8ebd1df877607fbe462bc1e1220b4a30ae3352648aec8c24bd/ruff-0.8.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:364e6674450cbac8e998f7b30639040c99d81dfb5bbc6dfad69bc7a8f916b3d1", size = 12813976 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/29/e059f945d6bd2d90213387b8c360187f2fefc989ddcee6bbf3c241329b92/ruff-0.8.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b22346f845fec132aa39cd29acb94451d030c10874408dbf776af3aaeb53284c", size = 11154564 },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/47/cbd05e5a62f3fb4c072bc65c1e8fd709924cad1c7ec60a1000d1e4ee8307/ruff-0.8.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b2f2f7a7e7648a2bfe6ead4e0a16745db956da0e3a231ad443d2a66a105c04fa", size = 10760604 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bb/ee/4c3981c47147c72647a198a94202633130cfda0fc95cd863a553b6f65c6a/ruff-0.8.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:adf314fc458374c25c5c4a4a9270c3e8a6a807b1bec018cfa2813d6546215540", size = 10391071 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/e6/083eb61300214590b188616a8ac6ae1ef5730a0974240fb4bec9c17de78b/ruff-0.8.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a885d68342a231b5ba4d30b8c6e1b1ee3a65cf37e3d29b3c74069cdf1ee1e3c9", size = 10896657 },
|
||||
{ url = "https://files.pythonhosted.org/packages/77/bd/aacdb8285d10f1b943dbeb818968efca35459afc29f66ae3bd4596fbf954/ruff-0.8.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d2c16e3508c8cc73e96aa5127d0df8913d2290098f776416a4b157657bee44c5", size = 11228362 },
|
||||
{ url = "https://files.pythonhosted.org/packages/39/72/fcb7ad41947f38b4eaa702aca0a361af0e9c2bf671d7fd964480670c297e/ruff-0.8.1-py3-none-win32.whl", hash = "sha256:93335cd7c0eaedb44882d75a7acb7df4b77cd7cd0d2255c93b28791716e81790", size = 8803476 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/ea/cae9aeb0f4822c44651c8407baacdb2e5b4dcd7b31a84e1c5df33aa2cc20/ruff-0.8.1-py3-none-win_amd64.whl", hash = "sha256:2954cdbe8dfd8ab359d4a30cd971b589d335a44d444b6ca2cb3d1da21b75e4b6", size = 9614463 },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/76/fbb4bd23dfb48fa7758d35b744413b650a9fd2ddd93bca77e30376864414/ruff-0.8.1-py3-none-win_arm64.whl", hash = "sha256:55873cc1a473e5ac129d15eccb3c008c096b94809d693fc7053f588b67822737", size = 8959621 },
|
||||
{ url = "https://files.pythonhosted.org/packages/73/f8/03391745a703ce11678eb37c48ae89ec60396ea821e9d0bcea7c8e88fd91/ruff-0.8.5-py3-none-linux_armv6l.whl", hash = "sha256:5ad11a5e3868a73ca1fa4727fe7e33735ea78b416313f4368c504dbeb69c0f88", size = 10626889 },
|
||||
{ url = "https://files.pythonhosted.org/packages/55/74/83bb74a44183b904216f3edfb9995b89830c83aaa6ce84627f74da0e0cf8/ruff-0.8.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:f69ab37771ea7e0715fead8624ec42996d101269a96e31f4d31be6fc33aa19b7", size = 10398233 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/7a/a162a4feb3ef85d594527165e366dde09d7a1e534186ff4ba5d127eda850/ruff-0.8.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b5462d7804558ccff9c08fe8cbf6c14b7efe67404316696a2dde48297b1925bb", size = 10001843 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e7/9f/5ee5dcd135411402e35b6ec6a8dfdadbd31c5cd1c36a624d356a38d76090/ruff-0.8.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d56de7220a35607f9fe59f8a6d018e14504f7b71d784d980835e20fc0611cd50", size = 10872507 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b6/67/db2df2dd4a34b602d7f6ebb1b3744c8157f0d3579973ffc58309c9c272e8/ruff-0.8.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9d99cf80b0429cbebf31cbbf6f24f05a29706f0437c40413d950e67e2d4faca4", size = 10377200 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/ff/fe3a6a73006bced73e60d171d154a82430f61d97e787f511a24bd6302611/ruff-0.8.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b75ac29715ac60d554a049dbb0ef3b55259076181c3369d79466cb130eb5afd", size = 11433155 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e3/95/c1d1a1fe36658c1f3e1b47e1cd5f688b72d5786695b9e621c2c38399a95e/ruff-0.8.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c9d526a62c9eda211b38463528768fd0ada25dad524cb33c0e99fcff1c67b5dc", size = 12139227 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1b/fe/644b70d473a27b5112ac7a3428edcc1ce0db775c301ff11aa146f71886e0/ruff-0.8.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:587c5e95007612c26509f30acc506c874dab4c4abbacd0357400bd1aa799931b", size = 11697941 },
|
||||
{ url = "https://files.pythonhosted.org/packages/00/39/4f83e517ec173e16a47c6d102cd22a1aaebe80e1208a1f2e83ab9a0e4134/ruff-0.8.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:622b82bf3429ff0e346835ec213aec0a04d9730480cbffbb6ad9372014e31bbd", size = 12967686 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/f6/52a2973ff108d74b5da706a573379eea160bece098f7cfa3f35dc4622710/ruff-0.8.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f99be814d77a5dac8a8957104bdd8c359e85c86b0ee0e38dca447cb1095f70fb", size = 11253788 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/1f/3b30f3c65b1303cb8e268ec3b046b77ab21ed8e26921cfc7e8232aa57f2c/ruff-0.8.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:c01c048f9c3385e0fd7822ad0fd519afb282af9cf1778f3580e540629df89725", size = 10860360 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a5/a8/2a3ea6bacead963f7aeeba0c61815d9b27b0d638e6a74984aa5cc5d27733/ruff-0.8.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:7512e8cb038db7f5db6aae0e24735ff9ea03bb0ed6ae2ce534e9baa23c1dc9ea", size = 10457922 },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/47/8f9514b670969aab57c5fc826fb500a16aee8feac1bcf8a91358f153a5ba/ruff-0.8.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:762f113232acd5b768d6b875d16aad6b00082add40ec91c927f0673a8ec4ede8", size = 10958347 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0d/d6/78a9af8209ad99541816d74f01ce678fc01ebb3f37dd7ab8966646dcd92b/ruff-0.8.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:03a90200c5dfff49e4c967b405f27fdfa81594cbb7c5ff5609e42d7fe9680da5", size = 11328882 },
|
||||
{ url = "https://files.pythonhosted.org/packages/54/77/5c8072ec7afdfdf42c7a4019044486a2b6c85ee73617f8875ec94b977fed/ruff-0.8.5-py3-none-win32.whl", hash = "sha256:8710ffd57bdaa6690cbf6ecff19884b8629ec2a2a2a2f783aa94b1cc795139ed", size = 8802515 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bc/b6/47d2b06784de8ae992c45cceb2a30f3f205b3236a629d7ca4c0c134839a2/ruff-0.8.5-py3-none-win_amd64.whl", hash = "sha256:4020d8bf8d3a32325c77af452a9976a9ad6455773bcb94991cf15bd66b347e47", size = 9684231 },
|
||||
{ url = "https://files.pythonhosted.org/packages/bf/5e/ffee22bf9f9e4b2669d1f0179ae8804584939fb6502b51f2401e26b1e028/ruff-0.8.5-py3-none-win_arm64.whl", hash = "sha256:134ae019ef13e1b060ab7136e7828a6d83ea727ba123381307eb37c6bd5e01cb", size = 9124741 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
Loading…
Reference in a new issue