mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-22 13:34:17 +02:00
commit
6e71950104
15 changed files with 358 additions and 159 deletions
30
.github/workflows/lint.yml
vendored
Normal file
30
.github/workflows/lint.yml
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
name: Run Pre-commits
|
||||
|
||||
env:
|
||||
# enable colored output
|
||||
# https://github.com/pytest-dev/pytest/issues/7443
|
||||
PY_COLORS: 1
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
static_analysis:
|
||||
timeout-minutes: 1
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- name: Run pre-commit
|
||||
uses: pre-commit/action@v3.0.1
|
||||
30
.github/workflows/publish.yml
vendored
30
.github/workflows/publish.yml
vendored
|
|
@ -5,28 +5,22 @@ on:
|
|||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
publish-pypi-release:
|
||||
pypi-publish:
|
||||
name: Upload to PyPI
|
||||
runs-on: ubuntu-latest
|
||||
environment: release
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
id-token: write # For PyPI's trusted publishing
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.11"
|
||||
cache: pip
|
||||
cache-dependency-path: "**/pyproject.toml"
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
pip install setuptools wheel build
|
||||
fetch-depth: 0
|
||||
|
||||
- name: "Install uv"
|
||||
uses: astral-sh/setup-uv@v3
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
python -m build
|
||||
- name: Publish
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
verbose: true
|
||||
run: uv build
|
||||
|
||||
- name: Publish to PyPi
|
||||
run: uv publish -v dist/*
|
||||
|
|
|
|||
2
.github/workflows/run-tests.yml
vendored
2
.github/workflows/run-tests.yml
vendored
|
|
@ -28,7 +28,7 @@ jobs:
|
|||
run: uv python install 3.11
|
||||
|
||||
- name: Install FastMCP
|
||||
run: uv sync --all-extras --dev
|
||||
run: uv sync --extra dev
|
||||
|
||||
- name: Run tests
|
||||
run: uv run pytest -vv
|
||||
|
|
|
|||
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -11,4 +11,8 @@ wheels/
|
|||
.DS_Store
|
||||
|
||||
|
||||
src/fastmcp/_version.py
|
||||
src/fastmcp/_version.py
|
||||
|
||||
# editors
|
||||
.cursorrules
|
||||
.vscode/
|
||||
20
.pre-commit-config.yaml
Normal file
20
.pre-commit-config.yaml
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
fail_fast: true
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/abravalheri/validate-pyproject
|
||||
rev: v0.23
|
||||
hooks:
|
||||
- id: validate-pyproject
|
||||
|
||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||
rev: v3.1.0
|
||||
hooks:
|
||||
- id: prettier
|
||||
types_or: [yaml, json5]
|
||||
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.8.0
|
||||
hooks:
|
||||
- id: ruff-format
|
||||
- id: ruff
|
||||
args: [--fix, --exit-non-zero-on-fix]
|
||||
|
|
@ -4,7 +4,6 @@ FastMCP Echo Server
|
|||
|
||||
from fastmcp import FastMCP
|
||||
|
||||
|
||||
# Create server
|
||||
mcp = FastMCP("Echo Server")
|
||||
|
||||
|
|
|
|||
|
|
@ -24,15 +24,18 @@ build-backend = "setuptools.build_meta"
|
|||
[tool.setuptools_scm]
|
||||
write_to = "src/fastmcp/_version.py"
|
||||
|
||||
[dependency-groups]
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"copychat>=0.5.2",
|
||||
"ipython>=8.12.3",
|
||||
"pdbpp>=0.10.3",
|
||||
"pre-commit",
|
||||
"pytest-xdist>=3.6.1",
|
||||
"pytest>=8.3.3",
|
||||
"pytest-asyncio>=0.23.5",
|
||||
"ruff",
|
||||
]
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
asyncio_mode = "auto"
|
||||
asyncio_default_fixture_loop_scope = "session"
|
||||
|
|
|
|||
|
|
@ -1,17 +1,43 @@
|
|||
"""Base classes and interfaces for FastMCP resources."""
|
||||
|
||||
import abc
|
||||
from typing import Union
|
||||
from typing import Annotated, Union
|
||||
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
from pydantic.networks import _BaseUrl
|
||||
from pydantic import (
|
||||
AnyUrl,
|
||||
BaseModel,
|
||||
BeforeValidator,
|
||||
ConfigDict,
|
||||
Field,
|
||||
FileUrl,
|
||||
ValidationInfo,
|
||||
field_validator,
|
||||
)
|
||||
from pydantic.networks import _BaseUrl # TODO: remove this once pydantic is updated
|
||||
|
||||
|
||||
def maybe_cast_str_to_any_url(x) -> AnyUrl:
|
||||
if isinstance(x, FileUrl):
|
||||
return x
|
||||
elif isinstance(x, AnyUrl):
|
||||
return x
|
||||
elif isinstance(x, str):
|
||||
if x.startswith("file://"):
|
||||
return FileUrl(x)
|
||||
return AnyUrl(x)
|
||||
raise ValueError(f"Expected str or AnyUrl, got {type(x)}")
|
||||
|
||||
|
||||
LaxAnyUrl = Annotated[_BaseUrl | str, BeforeValidator(maybe_cast_str_to_any_url)]
|
||||
|
||||
|
||||
class Resource(BaseModel, abc.ABC):
|
||||
"""Base class for all resources."""
|
||||
|
||||
uri: _BaseUrl = Field(description="URI of the resource")
|
||||
name: str = Field(description="Name of the resource", default=None)
|
||||
model_config = ConfigDict(validate_default=True)
|
||||
|
||||
uri: LaxAnyUrl = Field(default=..., description="URI of the resource")
|
||||
name: str | None = Field(description="Name of the resource", default=None)
|
||||
description: str | None = Field(
|
||||
description="Description of the resource", default=None
|
||||
)
|
||||
|
|
@ -23,7 +49,7 @@ class Resource(BaseModel, abc.ABC):
|
|||
|
||||
@field_validator("name", mode="before")
|
||||
@classmethod
|
||||
def set_default_name(cls, name: str | None, info) -> str:
|
||||
def set_default_name(cls, name: str | None, info: ValidationInfo) -> str:
|
||||
"""Set default name from URI if not provided."""
|
||||
if name:
|
||||
return name
|
||||
|
|
@ -35,7 +61,3 @@ class Resource(BaseModel, abc.ABC):
|
|||
async def read(self) -> Union[str, bytes]:
|
||||
"""Read the resource content."""
|
||||
pass
|
||||
|
||||
model_config = {
|
||||
"validate_default": True,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
from typing import Callable, Dict, Optional, Union
|
||||
|
||||
from pydantic.networks import _BaseUrl
|
||||
from pydantic import AnyUrl
|
||||
|
||||
from fastmcp.resources.base import Resource
|
||||
from fastmcp.resources.templates import ResourceTemplate
|
||||
|
|
@ -64,7 +64,7 @@ class ResourceManager:
|
|||
self._templates[template.uri_template] = template
|
||||
return template
|
||||
|
||||
async def get_resource(self, uri: Union[_BaseUrl, str]) -> Optional[Resource]:
|
||||
async def get_resource(self, uri: Union[AnyUrl, str]) -> Optional[Resource]:
|
||||
"""Get resource by URI, checking concrete resources first, then templates."""
|
||||
uri_str = str(uri)
|
||||
logger.debug("Getting resource", extra={"uri": uri_str})
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
"""Concrete resource implementations."""
|
||||
|
||||
import pydantic_core
|
||||
import asyncio
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
|
@ -8,6 +7,7 @@ from typing import Any, Callable, Union
|
|||
|
||||
import httpx
|
||||
import pydantic.json
|
||||
import pydantic_core
|
||||
from pydantic import Field
|
||||
|
||||
from fastmcp.resources.base import Resource
|
||||
|
|
|
|||
|
|
@ -1,43 +1,47 @@
|
|||
"""FastMCP - A more ergonomic interface for MCP servers."""
|
||||
|
||||
import pydantic_core
|
||||
from typing import Any, Literal, Optional, Union, Dict
|
||||
|
||||
from mcp.server import RequestContext
|
||||
from pydantic import BaseModel
|
||||
from pydantic.networks import AnyUrl
|
||||
|
||||
from fastmcp.utilities.logging import get_logger
|
||||
import asyncio
|
||||
import functools
|
||||
import json
|
||||
from typing import Callable, Sequence
|
||||
import inspect
|
||||
import json
|
||||
import re
|
||||
from itertools import chain
|
||||
from typing import Any, Callable, Dict, Literal, Sequence
|
||||
|
||||
import pydantic_core
|
||||
import uvicorn
|
||||
from mcp.server import Server as MCPServer
|
||||
from mcp.server.stdio import stdio_server
|
||||
from mcp.server.sse import SseServerTransport
|
||||
from mcp.server.stdio import stdio_server
|
||||
from mcp.shared.context import RequestContext
|
||||
from mcp.types import (
|
||||
EmbeddedResource,
|
||||
GetPromptResult,
|
||||
ImageContent,
|
||||
TextContent,
|
||||
)
|
||||
from mcp.types import (
|
||||
Prompt as MCPPrompt,
|
||||
)
|
||||
from mcp.types import (
|
||||
Resource as MCPResource,
|
||||
Tool as MCPTool,
|
||||
ResourceTemplate as MCPResourceTemplate,
|
||||
TextContent,
|
||||
ImageContent,
|
||||
EmbeddedResource,
|
||||
Prompt as MCPPrompt,
|
||||
GetPromptResult,
|
||||
)
|
||||
from pydantic_settings import BaseSettings
|
||||
from pydantic.networks import _BaseUrl
|
||||
from mcp.types import (
|
||||
ResourceTemplate as MCPResourceTemplate,
|
||||
)
|
||||
from mcp.types import (
|
||||
Tool as MCPTool,
|
||||
)
|
||||
from pydantic import BaseModel
|
||||
from pydantic.networks import AnyUrl
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
from fastmcp.exceptions import ResourceError
|
||||
from fastmcp.resources import Resource, ResourceManager, FunctionResource
|
||||
from fastmcp.tools import ToolManager
|
||||
from fastmcp.utilities.logging import configure_logging
|
||||
from fastmcp.utilities.types import Image
|
||||
from fastmcp.prompts import Prompt, PromptManager
|
||||
from itertools import chain
|
||||
from fastmcp.resources import FunctionResource, Resource, ResourceManager
|
||||
from fastmcp.tools import ToolManager
|
||||
from fastmcp.utilities.logging import configure_logging, get_logger
|
||||
from fastmcp.utilities.types import Image
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
|
|
@ -49,7 +53,7 @@ class Settings(BaseSettings):
|
|||
For example, FASTMCP_DEBUG=true will set debug=True.
|
||||
"""
|
||||
|
||||
model_config: dict = dict(env_prefix="FASTMCP_")
|
||||
model_config: SettingsConfigDict = SettingsConfigDict(env_prefix="FASTMCP_")
|
||||
|
||||
# Server settings
|
||||
debug: bool = False
|
||||
|
|
@ -70,7 +74,7 @@ class Settings(BaseSettings):
|
|||
|
||||
|
||||
class FastMCP:
|
||||
def __init__(self, name=None, **settings: Optional[Settings]):
|
||||
def __init__(self, name: str | None = None, **settings: Any):
|
||||
self.settings = Settings(**settings)
|
||||
self._mcp_server = MCPServer(name=name or "FastMCP")
|
||||
self._tool_manager = ToolManager(
|
||||
|
|
@ -144,7 +148,7 @@ class FastMCP:
|
|||
|
||||
async def call_tool(
|
||||
self, name: str, arguments: dict
|
||||
) -> Sequence[Union[TextContent, ImageContent]]:
|
||||
) -> Sequence[TextContent | ImageContent]:
|
||||
"""Call a tool by name with arguments."""
|
||||
context = self.get_context()
|
||||
result = await self._tool_manager.call_tool(name, arguments, context=context)
|
||||
|
|
@ -158,7 +162,7 @@ class FastMCP:
|
|||
return [
|
||||
MCPResource(
|
||||
uri=resource.uri,
|
||||
name=resource.name,
|
||||
name=resource.name or "",
|
||||
description=resource.description,
|
||||
mimeType=resource.mime_type,
|
||||
)
|
||||
|
|
@ -176,7 +180,7 @@ class FastMCP:
|
|||
for template in templates
|
||||
]
|
||||
|
||||
async def read_resource(self, uri: _BaseUrl) -> Union[str, bytes]:
|
||||
async def read_resource(self, uri: AnyUrl | str) -> str | bytes:
|
||||
"""Read a resource by URI."""
|
||||
resource = await self._resource_manager.get_resource(uri)
|
||||
if not resource:
|
||||
|
|
@ -191,8 +195,8 @@ class FastMCP:
|
|||
def add_tool(
|
||||
self,
|
||||
fn: Callable,
|
||||
name: Optional[str] = None,
|
||||
description: Optional[str] = None,
|
||||
name: str | None = None,
|
||||
description: str | None = None,
|
||||
) -> None:
|
||||
"""Add a tool to the server.
|
||||
|
||||
|
|
@ -206,9 +210,7 @@ class FastMCP:
|
|||
"""
|
||||
self._tool_manager.add_tool(fn, name=name, description=description)
|
||||
|
||||
def tool(
|
||||
self, name: Optional[str] = None, description: Optional[str] = None
|
||||
) -> Callable:
|
||||
def tool(self, name: str | None = None, description: str | None = None) -> Callable:
|
||||
"""Decorator to register a tool.
|
||||
|
||||
Tools can optionally request a Context object by adding a parameter with the Context type annotation.
|
||||
|
|
@ -258,9 +260,9 @@ class FastMCP:
|
|||
self,
|
||||
uri: str,
|
||||
*,
|
||||
name: Optional[str] = None,
|
||||
description: Optional[str] = None,
|
||||
mime_type: Optional[str] = None,
|
||||
name: str | None = None,
|
||||
description: str | None = None,
|
||||
mime_type: str | None = None,
|
||||
) -> Callable:
|
||||
"""Decorator to register a function as a resource.
|
||||
|
||||
|
|
@ -326,7 +328,7 @@ class FastMCP:
|
|||
else:
|
||||
# Register as regular resource
|
||||
resource = FunctionResource(
|
||||
uri=uri,
|
||||
uri=AnyUrl(uri),
|
||||
name=name,
|
||||
description=description,
|
||||
mime_type=mime_type or "text/plain",
|
||||
|
|
@ -346,7 +348,7 @@ class FastMCP:
|
|||
self._prompt_manager.add_prompt(prompt)
|
||||
|
||||
def prompt(
|
||||
self, name: Optional[str] = None, description: Optional[str] = None
|
||||
self, name: str | None = None, description: str | None = None
|
||||
) -> Callable:
|
||||
"""Decorator to register a prompt.
|
||||
|
||||
|
|
@ -398,7 +400,7 @@ class FastMCP:
|
|||
async def run_stdio_async(self) -> None:
|
||||
"""Run the server using stdio transport."""
|
||||
async with stdio_server() as (read_stream, write_stream):
|
||||
print(f'Starting "{self.name}"...')
|
||||
logger.info(f'Starting "{self.name}"...')
|
||||
await self._mcp_server.run(
|
||||
read_stream,
|
||||
write_stream,
|
||||
|
|
@ -409,7 +411,6 @@ class FastMCP:
|
|||
"""Run the server using SSE transport."""
|
||||
from starlette.applications import Starlette
|
||||
from starlette.routing import Route
|
||||
import uvicorn
|
||||
|
||||
sse = SseServerTransport("/messages")
|
||||
|
||||
|
|
@ -434,12 +435,14 @@ class FastMCP:
|
|||
],
|
||||
)
|
||||
|
||||
uvicorn.run(
|
||||
config = uvicorn.Config(
|
||||
starlette_app,
|
||||
host=self.settings.host,
|
||||
port=self.settings.port,
|
||||
log_level=self.settings.log_level,
|
||||
log_level=self.settings.log_level.lower(),
|
||||
)
|
||||
server = uvicorn.Server(config)
|
||||
await server.serve()
|
||||
|
||||
async def list_prompts(self) -> list[MCPPrompt]:
|
||||
"""List all available prompts."""
|
||||
|
|
@ -461,7 +464,7 @@ class FastMCP:
|
|||
]
|
||||
|
||||
async def get_prompt(
|
||||
self, name: str, arguments: Optional[Dict[str, Any]] = None
|
||||
self, name: str, arguments: Dict[str, Any] | None = None
|
||||
) -> GetPromptResult:
|
||||
"""Get a prompt by name with arguments."""
|
||||
try:
|
||||
|
|
@ -475,7 +478,7 @@ class FastMCP:
|
|||
|
||||
def _convert_to_content(
|
||||
result: Any,
|
||||
) -> Sequence[Union[TextContent, ImageContent, EmbeddedResource]]:
|
||||
) -> Sequence[TextContent | ImageContent | EmbeddedResource]:
|
||||
"""Convert a result to a sequence of content objects."""
|
||||
if result is None:
|
||||
return []
|
||||
|
|
@ -532,14 +535,14 @@ class Context(BaseModel):
|
|||
The context is optional - tools that don't need it can omit the parameter.
|
||||
"""
|
||||
|
||||
_request_context: RequestContext
|
||||
_fastmcp: FastMCP
|
||||
_request_context: RequestContext | None
|
||||
_fastmcp: FastMCP | None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
request_context: RequestContext = None,
|
||||
fastmcp: FastMCP = None,
|
||||
request_context: RequestContext | None = None,
|
||||
fastmcp: FastMCP | None = None,
|
||||
**kwargs: Any,
|
||||
):
|
||||
super().__init__(**kwargs)
|
||||
|
|
@ -561,7 +564,7 @@ class Context(BaseModel):
|
|||
return self._request_context
|
||||
|
||||
async def report_progress(
|
||||
self, progress: float, total: Optional[float] = None
|
||||
self, progress: float, total: float | None = None
|
||||
) -> None:
|
||||
"""Report progress for the current operation.
|
||||
|
||||
|
|
@ -583,7 +586,7 @@ class Context(BaseModel):
|
|||
progress_token=progress_token, progress=progress, total=total
|
||||
)
|
||||
|
||||
async def read_resource(self, uri: Union[str, AnyUrl]) -> Union[str, bytes]:
|
||||
async def read_resource(self, uri: str | AnyUrl) -> str | bytes:
|
||||
"""Read a resource by URI.
|
||||
|
||||
Args:
|
||||
|
|
@ -592,6 +595,9 @@ class Context(BaseModel):
|
|||
Returns:
|
||||
The resource content as either text or bytes
|
||||
"""
|
||||
assert (
|
||||
self._fastmcp is not None
|
||||
), "Context is not available outside of a request"
|
||||
return await self._fastmcp.read_resource(uri)
|
||||
|
||||
def log(
|
||||
|
|
@ -599,7 +605,7 @@ class Context(BaseModel):
|
|||
level: Literal["debug", "info", "warning", "error"],
|
||||
message: str,
|
||||
*,
|
||||
logger_name: Optional[str] = None,
|
||||
logger_name: str | None = None,
|
||||
) -> None:
|
||||
"""Send a log message to the client.
|
||||
|
||||
|
|
@ -614,14 +620,18 @@ class Context(BaseModel):
|
|||
)
|
||||
|
||||
@property
|
||||
def client_id(self) -> Optional[str]:
|
||||
def client_id(self) -> str | None:
|
||||
"""Get the client ID if available."""
|
||||
return self.request_context.meta.clientId if self.request_context.meta else None
|
||||
return (
|
||||
getattr(self.request_context.meta, "client_id", None)
|
||||
if self.request_context.meta
|
||||
else None
|
||||
)
|
||||
|
||||
@property
|
||||
def request_id(self) -> str:
|
||||
"""Get the unique ID for this request."""
|
||||
return self.request_context.request_id
|
||||
return str(self.request_context.request_id)
|
||||
|
||||
@property
|
||||
def session(self):
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import pytest
|
||||
from fastmcp.resources import Resource, FunctionResource
|
||||
|
||||
from fastmcp.resources import FunctionResource, Resource
|
||||
|
||||
|
||||
class TestResourceValidation:
|
||||
|
|
@ -95,4 +96,4 @@ class TestResourceValidation:
|
|||
pass
|
||||
|
||||
with pytest.raises(TypeError, match="abstract method"):
|
||||
ConcreteResource(uri="test://test", name="test")
|
||||
ConcreteResource(uri="test://test", name="test") # type: ignore
|
||||
|
|
|
|||
|
|
@ -1,16 +1,18 @@
|
|||
import base64
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Union
|
||||
|
||||
import pytest
|
||||
from mcp.shared.exceptions import McpError
|
||||
from mcp.shared.memory import (
|
||||
create_connected_server_and_client_session as client_session,
|
||||
)
|
||||
from mcp.shared.exceptions import McpError
|
||||
from fastmcp import FastMCP, Context
|
||||
from mcp.types import ImageContent, TextContent
|
||||
|
||||
from fastmcp import Context, FastMCP
|
||||
from fastmcp.prompts.base import EmbeddedResource, Message, UserMessage
|
||||
from fastmcp.resources import FileResource, FunctionResource
|
||||
from fastmcp.utilities.types import Image
|
||||
from mcp.types import TextContent, ImageContent
|
||||
from fastmcp.prompts.base import Message, UserMessage, TextContent, EmbeddedResource
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
import base64
|
||||
from typing import Union, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from fastmcp import Context
|
||||
|
|
@ -35,7 +37,7 @@ class TestServer:
|
|||
|
||||
with pytest.raises(TypeError, match="The @tool decorator was used incorrectly"):
|
||||
|
||||
@mcp.tool # Missing parentheses
|
||||
@mcp.tool # Missing parentheses #type: ignore
|
||||
def add(x: int, y: int) -> int:
|
||||
return x + y
|
||||
|
||||
|
|
@ -55,7 +57,7 @@ class TestServer:
|
|||
TypeError, match="The @resource decorator was used incorrectly"
|
||||
):
|
||||
|
||||
@mcp.resource # Missing parentheses
|
||||
@mcp.resource # Missing parentheses #type: ignore
|
||||
def get_data(x: str) -> str:
|
||||
return f"Data: {x}"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import logging
|
||||
from typing import Optional
|
||||
|
||||
import pytest
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
|
||||
from fastmcp.exceptions import ToolError
|
||||
from fastmcp.tools import ToolManager
|
||||
|
|
@ -69,7 +70,7 @@ class TestAddTools:
|
|||
def test_add_invalid_tool(self):
|
||||
manager = ToolManager()
|
||||
with pytest.raises(AttributeError):
|
||||
manager.add_tool(1)
|
||||
manager.add_tool(1) # type: ignore
|
||||
|
||||
def test_add_lambda(self):
|
||||
manager = ToolManager()
|
||||
|
|
@ -185,7 +186,7 @@ class TestContextHandling:
|
|||
return str(x)
|
||||
|
||||
manager = ToolManager()
|
||||
tool = manager.add_tool(tool_with_context)
|
||||
manager.add_tool(tool_with_context)
|
||||
|
||||
mcp = FastMCP()
|
||||
ctx = mcp.get_context()
|
||||
|
|
@ -201,7 +202,7 @@ class TestContextHandling:
|
|||
return str(x)
|
||||
|
||||
manager = ToolManager()
|
||||
tool = manager.add_tool(async_tool)
|
||||
manager.add_tool(async_tool)
|
||||
|
||||
mcp = FastMCP()
|
||||
ctx = mcp.get_context()
|
||||
|
|
@ -216,7 +217,7 @@ class TestContextHandling:
|
|||
return str(x)
|
||||
|
||||
manager = ToolManager()
|
||||
tool = manager.add_tool(tool_with_context)
|
||||
manager.add_tool(tool_with_context)
|
||||
# Should not raise an error when context is not provided
|
||||
result = await manager.call_tool("tool_with_context", {"x": 42})
|
||||
assert result == "42"
|
||||
|
|
@ -229,7 +230,7 @@ class TestContextHandling:
|
|||
raise ValueError("Test error")
|
||||
|
||||
manager = ToolManager()
|
||||
tool = manager.add_tool(tool_with_context)
|
||||
manager.add_tool(tool_with_context)
|
||||
|
||||
mcp = FastMCP()
|
||||
ctx = mcp.get_context()
|
||||
|
|
|
|||
221
uv.lock
generated
221
uv.lock
generated
|
|
@ -25,25 +25,13 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/e4/f5/f2b75d2fc6f1a260f340f0e7c6a060f4dd2961cc16884ed851b0d18da06a/anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d", size = 90377 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "appnope"
|
||||
version = "0.1.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/35/5d/752690df9ef5b76e169e68d6a129fa6d08a7100ca7f754c89495db3c6019/appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee", size = 4170 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "asttokens"
|
||||
version = "2.4.1"
|
||||
version = "3.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "six" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/45/1d/f03bcb60c4a3212e15f99a56085d93093a497718adf828d050b9d675da81/asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0", size = 62284 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/4a/e7/82da0a03e7ba5141f05cce0d302e6eed121ae055e0456ca228bf693984bc/asttokens-3.0.0.tar.gz", hash = "sha256:0dcd8baa8d62b0c1d118b399b2ddba3c4aff271d0d7a9e0d4c1681c79035bbc7", size = 61978 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/45/86/4736ac618d82a20d87d2f92ae19441ebc7ac9e7a581d7e58bbe79233b24a/asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24", size = 27764 },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/8a/c46dcc25341b5bce5472c718902eb3d38600a903b14fa6aeecef3f21a46f/asttokens-3.0.0-py3-none-any.whl", hash = "sha256:e3078351a059199dd5138cb1c706e6430c05eff2ff136af5eb4790f9d28932e2", size = 26918 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -55,15 +43,6 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/6a/21/5b6702a7f963e95456c0de2d495f67bf5fd62840ac655dc451586d23d39a/attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2", size = 63001 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backcall"
|
||||
version = "0.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/a2/40/764a663805d84deee23043e1426a9175567db89c8b3287b5c2ad9f71aa93/backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e", size = 18041 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/4c/1c/ff6546b6c12603d8dd1070aa3c3d273ad4c07f5771689a7b69a550e8c951/backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255", size = 11157 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2024.8.30"
|
||||
|
|
@ -73,6 +52,15 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", size = 167321 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfgv"
|
||||
version = "3.4.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/11/74/539e56497d9bd1d484fd863dd69cbbfa653cd2aa27abfe35653494d85e94/cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560", size = 7114 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c5/55/51844dd50c4fc7a33b653bfaba4c2456f06955289ca770a5dbd5fd267374/cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9", size = 7249 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "charset-normalizer"
|
||||
version = "3.4.0"
|
||||
|
|
@ -189,6 +177,15 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/d5/50/83c593b07763e1161326b3b8c6686f0f4b0f24d5526546bee538c89837d6/decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186", size = 9073 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "distlib"
|
||||
version = "0.3.9"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/0d/dd/1bec4c5ddb504ca60fc29472f3d27e8d4da1257a854e1d96742f15c1d02d/distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403", size = 613923 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "exceptiongroup"
|
||||
version = "1.2.2"
|
||||
|
|
@ -231,7 +228,7 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "fastmcp"
|
||||
version = "0.2.1.dev44+gbfae276.d20241130"
|
||||
version = "0.2.1.dev56+g0fb37ec.d20241201"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "httpx" },
|
||||
|
|
@ -241,33 +238,42 @@ dependencies = [
|
|||
{ name = "typer" },
|
||||
]
|
||||
|
||||
[package.dev-dependencies]
|
||||
[package.optional-dependencies]
|
||||
dev = [
|
||||
{ name = "copychat" },
|
||||
{ name = "ipython" },
|
||||
{ name = "pdbpp" },
|
||||
{ name = "pre-commit" },
|
||||
{ name = "pytest" },
|
||||
{ name = "pytest-asyncio" },
|
||||
{ name = "pytest-xdist" },
|
||||
{ name = "ruff" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "copychat", marker = "extra == 'dev'", specifier = ">=0.5.2" },
|
||||
{ name = "httpx", specifier = ">=0.26.0" },
|
||||
{ name = "ipython", marker = "extra == 'dev'", specifier = ">=8.12.3" },
|
||||
{ name = "mcp", specifier = ">=1.0.0,<2.0.0" },
|
||||
{ name = "pdbpp", marker = "extra == 'dev'", specifier = ">=0.10.3" },
|
||||
{ name = "pre-commit", marker = "extra == 'dev'" },
|
||||
{ name = "pydantic", specifier = ">=2.5.3,<3.0.0" },
|
||||
{ name = "pydantic-settings", specifier = ">=2.6.1" },
|
||||
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=8.3.3" },
|
||||
{ name = "pytest-asyncio", marker = "extra == 'dev'", specifier = ">=0.23.5" },
|
||||
{ name = "pytest-xdist", marker = "extra == 'dev'", specifier = ">=3.6.1" },
|
||||
{ name = "ruff", marker = "extra == 'dev'" },
|
||||
{ name = "typer", specifier = ">=0.9.0" },
|
||||
]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [
|
||||
{ name = "copychat", specifier = ">=0.5.2" },
|
||||
{ name = "ipython", specifier = ">=8.12.3" },
|
||||
{ name = "pdbpp", specifier = ">=0.10.3" },
|
||||
{ name = "pytest", specifier = ">=8.3.3" },
|
||||
{ name = "pytest-asyncio", specifier = ">=0.23.5" },
|
||||
{ name = "pytest-xdist", specifier = ">=3.6.1" },
|
||||
[[package]]
|
||||
name = "filelock"
|
||||
version = "3.16.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9d/db/3ef5bb276dae18d6ec2124224403d1d67bccdbefc17af4cc8f553e341ab1/filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435", size = 18037 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/f8/feced7779d755758a52d1f6635d990b8d98dc0a29fa568bbe0625f18fdf3/filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0", size = 16163 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -340,6 +346,15 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "identify"
|
||||
version = "2.6.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/1a/5f/05f0d167be94585d502b4adf8c7af31f1dc0b1c7e14f9938a88fdbbcf4a7/identify-2.6.3.tar.gz", hash = "sha256:62f5dae9b5fef52c84cc188514e9ea4f3f636b1d8799ab5ebc475471f9e47a02", size = 99179 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/f5/09644a3ad803fae9eca8efa17e1f2aef380c7f0b02f7ec4e8d446e51d64a/identify-2.6.3-py2.py3-none-any.whl", hash = "sha256:9edba65473324c2ea9684b1f944fe3191db3345e50b6d04571d10ed164f8d7bd", size = 99049 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.10"
|
||||
|
|
@ -360,25 +375,24 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "ipython"
|
||||
version = "8.12.3"
|
||||
version = "8.30.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "appnope", marker = "sys_platform == 'darwin'" },
|
||||
{ name = "backcall" },
|
||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
||||
{ name = "decorator" },
|
||||
{ name = "exceptiongroup", marker = "python_full_version < '3.11'" },
|
||||
{ name = "jedi" },
|
||||
{ name = "matplotlib-inline" },
|
||||
{ name = "pexpect", marker = "sys_platform != 'win32'" },
|
||||
{ name = "pickleshare" },
|
||||
{ name = "pexpect", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" },
|
||||
{ name = "prompt-toolkit" },
|
||||
{ name = "pygments" },
|
||||
{ name = "stack-data" },
|
||||
{ name = "traitlets" },
|
||||
{ name = "typing-extensions", marker = "python_full_version < '3.12'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9e/6a/44ef299b1762f5a73841e87fae8a73a8cc8aee538d6dc8c77a5afe1fd2ce/ipython-8.12.3.tar.gz", hash = "sha256:3910c4b54543c2ad73d06579aa771041b7d5707b033bd488669b4cf544e3b363", size = 5470171 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d8/8b/710af065ab8ed05649afa5bd1e07401637c9ec9fb7cfda9eac7e91e9fbd4/ipython-8.30.0.tar.gz", hash = "sha256:cb0a405a306d2995a5cbb9901894d240784a9f341394c6ba3f4fe8c6eb89ff6e", size = 5592205 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8d/97/8fe103906cd81bc42d3b0175b5534a9f67dccae47d6451131cf8d0d70bb2/ipython-8.12.3-py3-none-any.whl", hash = "sha256:b0340d46a933d27c657b211a329d0be23793c36595acf9e6ef4164bc01a1804c", size = 798307 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/f3/1332ba2f682b07b304ad34cad2f003adcfeb349486103f4b632335074a7c/ipython-8.30.0-py3-none-any.whl", hash = "sha256:85ec56a7e20f6c38fce7727dcca699ae4ffc85985aa7b23635a8008f918ae321", size = 820765 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -443,6 +457,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 = "nodeenv"
|
||||
version = "1.9.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "24.2"
|
||||
|
|
@ -497,12 +520,12 @@ wheels = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "pickleshare"
|
||||
version = "0.7.5"
|
||||
name = "platformdirs"
|
||||
version = "4.3.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d8/b6/df3c1c9b616e9c0edbc4fbab6ddd09df9535849c64ba51fcb6531c32d4d8/pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca", size = 6161 }
|
||||
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/9a/41/220f49aaea88bc6fa6cba8d05ecf24676326156c23b991e80b3f2fc24c77/pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56", size = 6877 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3c/a6/bc1012356d8ece4d66dd75c4b9fc6c1f6650ddd5991e421177d9f8f671be/platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb", size = 18439 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -514,6 +537,22 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pre-commit"
|
||||
version = "4.0.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "cfgv" },
|
||||
{ name = "identify" },
|
||||
{ name = "nodeenv" },
|
||||
{ name = "pyyaml" },
|
||||
{ name = "virtualenv" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/2e/c8/e22c292035f1bac8b9f5237a2622305bc0304e776080b246f3df57c4ff9f/pre_commit-4.0.1.tar.gz", hash = "sha256:80905ac375958c0444c65e9cebebd948b3cdb518f335a091a670a89d652139d2", size = 191678 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/16/8f/496e10d51edd6671ebe0432e33ff800aa86775d2d147ce7d43389324a525/pre_commit-4.0.1-py2.py3-none-any.whl", hash = "sha256:efde913840816312445dc98787724647c65473daefe420785f885e8ed9a06878", size = 218713 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prompt-toolkit"
|
||||
version = "3.0.48"
|
||||
|
|
@ -724,6 +763,50 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pyyaml"
|
||||
version = "6.0.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/95/a3fac87cb7158e231b5a6012e438c647e1a87f09f8e0d123acec8ab8bf71/PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086", size = 184199 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c7/7a/68bd47624dab8fd4afbfd3c48e3b79efe09098ae941de5b58abcbadff5cb/PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf", size = 171758 },
|
||||
{ url = "https://files.pythonhosted.org/packages/49/ee/14c54df452143b9ee9f0f29074d7ca5516a36edb0b4cc40c3f280131656f/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237", size = 718463 },
|
||||
{ url = "https://files.pythonhosted.org/packages/4d/61/de363a97476e766574650d742205be468921a7b532aa2499fcd886b62530/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b", size = 719280 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/4e/1523cb902fd98355e2e9ea5e5eb237cbc5f3ad5f3075fa65087aa0ecb669/PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed", size = 751239 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/33/5504b3a9a4464893c32f118a9cc045190a91637b119a9c881da1cf6b7a72/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180", size = 695802 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5c/20/8347dcabd41ef3a3cdc4f7b7a2aff3d06598c8779faa189cdbf878b626a4/PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68", size = 720527 },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/aa/5afe99233fb360d0ff37377145a949ae258aaab831bde4792b32650a4378/PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99", size = 144052 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/84/0fa4b06f6d6c958d207620fc60005e241ecedceee58931bb20138e1e5776/PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e", size = 161774 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829 },
|
||||
{ url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167 },
|
||||
{ url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301 },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638 },
|
||||
{ url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980 },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 },
|
||||
{ url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 },
|
||||
{ url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 },
|
||||
{ url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 },
|
||||
{ url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "2024.11.6"
|
||||
|
|
@ -822,6 +905,31 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ruff"
|
||||
version = "0.8.1"
|
||||
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 }
|
||||
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 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shellingham"
|
||||
version = "1.5.4"
|
||||
|
|
@ -831,15 +939,6 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "six"
|
||||
version = "1.16.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/71/39/171f1c67cd00715f190ba0b100d606d440a28c93c7714febeca8b79af85e/six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", size = 34041 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254", size = 11053 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smmap"
|
||||
version = "5.0.1"
|
||||
|
|
@ -1029,6 +1128,20 @@ wheels = [
|
|||
{ url = "https://files.pythonhosted.org/packages/50/c1/2d27b0a15826c2b71dcf6e2f5402181ef85acf439617bb2f1453125ce1f3/uvicorn-0.32.1-py3-none-any.whl", hash = "sha256:82ad92fd58da0d12af7482ecdb5f2470a04c9c9a53ced65b9bbb4a205377602e", size = 63828 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "virtualenv"
|
||||
version = "20.28.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "distlib" },
|
||||
{ name = "filelock" },
|
||||
{ name = "platformdirs" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/bf/75/53316a5a8050069228a2f6d11f32046cfa94fbb6cc3f08703f59b873de2e/virtualenv-20.28.0.tar.gz", hash = "sha256:2c9c3262bb8e7b87ea801d715fae4495e6032450c71d2309be9550e7364049aa", size = 7650368 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/10/f9/0919cf6f1432a8c4baa62511f8f8da8225432d22e83e3476f5be1a1edc6e/virtualenv-20.28.0-py3-none-any.whl", hash = "sha256:23eae1b4516ecd610481eda647f3a7c09aea295055337331bb4e6892ecce47b0", size = 4276702 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wcwidth"
|
||||
version = "0.2.13"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue