├── .gitignore ├── LICENSE.md ├── README.md ├── assets └── output.gif ├── pyproject.toml ├── src └── mcp_llm_bridge │ ├── __init__.py │ ├── bridge.py │ ├── config.py │ ├── create_test_db.py │ ├── llm_client.py │ ├── main.py │ ├── mcp_client.py │ └── tools.py ├── tests └── test_bridge.py └── uv.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Python 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | *.so 6 | .Python 7 | build/ 8 | develop-eggs/ 9 | dist/ 10 | downloads/ 11 | eggs/ 12 | .eggs/ 13 | lib/ 14 | lib64/ 15 | parts/ 16 | sdist/ 17 | var/ 18 | wheels/ 19 | *.egg-info/ 20 | .installed.cfg 21 | *.egg 22 | 23 | # Virtual Environment 24 | .venv 25 | venv/ 26 | env/ 27 | ENV/ 28 | 29 | # IDE 30 | .idea/ 31 | .vscode/ 32 | *.swp 33 | *.swo 34 | 35 | # Database 36 | *.db 37 | *.sqlite3 38 | 39 | # Environment variables 40 | .env 41 | 42 | # Logs 43 | *.log 44 | 45 | # OS 46 | .DS_Store -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Angel Bartolli 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MCP LLM Bridge 2 | 3 | A bridge connecting Model Context Protocol (MCP) servers to OpenAI-compatible LLMs. Primary support for OpenAI API, with additional compatibility for local endpoints that implement the OpenAI API specification. 4 | 5 | The implementation provides a bidirectional protocol translation layer between MCP and OpenAI's function-calling interface. It converts MCP tool specifications into OpenAI function schemas and handles the mapping of function invocations back to MCP tool executions. This enables any OpenAI-compatible language model to leverage MCP-compliant tools through a standardized interface, whether using cloud-based models or local implementations like Ollama. 6 | 7 | Read more about MCP by Anthropic here: 8 | 9 | - [Resources](https://modelcontextprotocol.io/docs/concepts/resources) 10 | - [Prompts](https://modelcontextprotocol.io/docs/concepts/prompts) 11 | - [Tools](https://modelcontextprotocol.io/docs/concepts/tools) 12 | - [Sampling](https://modelcontextprotocol.io/docs/concepts/sampling) 13 | 14 | Demo: 15 | 16 | ![MCP LLM Bridge Demo](assets/output.gif) 17 | 18 | ## Quick Start 19 | 20 | ```bash 21 | # Install 22 | curl -LsSf https://astral.sh/uv/install.sh | sh 23 | git clone https://github.com/bartolli/mcp-llm-bridge.git 24 | cd mcp-llm-bridge 25 | uv venv 26 | source .venv/bin/activate 27 | uv pip install -e . 28 | 29 | # Create test database 30 | python -m mcp_llm_bridge.create_test_db 31 | ``` 32 | 33 | ## Configuration 34 | 35 | ### OpenAI (Primary) 36 | 37 | Create `.env`: 38 | 39 | ```bash 40 | OPENAI_API_KEY=your_key 41 | OPENAI_MODEL=gpt-4o # or any other OpenAI model that supports tools 42 | ``` 43 | 44 | Note: reactivate the environment if needed to use the keys in `.env`: `source .venv/bin/activate` 45 | 46 | Then configure the bridge in [src/mcp_llm_bridge/main.py](src/mcp_llm_bridge/main.py) 47 | 48 | ```python 49 | config = BridgeConfig( 50 | mcp_server_params=StdioServerParameters( 51 | command="uvx", 52 | args=["mcp-server-sqlite", "--db-path", "test.db"], 53 | env=None 54 | ), 55 | llm_config=LLMConfig( 56 | api_key=os.getenv("OPENAI_API_KEY"), 57 | model=os.getenv("OPENAI_MODEL", "gpt-4o"), 58 | base_url=None 59 | ) 60 | ) 61 | ``` 62 | 63 | ### Additional Endpoint Support 64 | 65 | The bridge also works with any endpoint implementing the OpenAI API specification: 66 | 67 | #### Ollama 68 | 69 | ```python 70 | llm_config=LLMConfig( 71 | api_key="not-needed", 72 | model="mistral-nemo:12b-instruct-2407-q8_0", 73 | base_url="http://localhost:11434/v1" 74 | ) 75 | ``` 76 | 77 | Note: After testing various models, including `llama3.2:3b-instruct-fp16`, I found that `mistral-nemo:12b-instruct-2407-q8_0` handles complex queries more effectively. 78 | 79 | #### LM Studio 80 | 81 | ```python 82 | llm_config=LLMConfig( 83 | api_key="not-needed", 84 | model="local-model", 85 | base_url="http://localhost:1234/v1" 86 | ) 87 | ``` 88 | 89 | I didn't test this, but it should work. 90 | 91 | ## Usage 92 | 93 | ```bash 94 | python -m mcp_llm_bridge.main 95 | 96 | # Try: "What are the most expensive products in the database?" 97 | # Exit with 'quit' or Ctrl+C 98 | ``` 99 | 100 | ## Running Tests 101 | 102 | Install the package with test dependencies: 103 | 104 | ```bash 105 | uv pip install -e ".[test]" 106 | ``` 107 | 108 | Then run the tests: 109 | 110 | ```bash 111 | python -m pytest -v tests/ 112 | ``` 113 | ## License 114 | 115 | [MIT](LICENSE.md) 116 | 117 | ## Contributing 118 | 119 | PRs welcome. 120 | -------------------------------------------------------------------------------- /assets/output.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartolli/mcp-llm-bridge/82bd2fa2ee5c8690e718edac544e223c962405db/assets/output.gif -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "mcp-llm-bridge" 3 | version = "0.1.0" 4 | description = "Bridge between MCP protocol and LLM clients" 5 | readme = "README.md" 6 | requires-python = ">=3.12" 7 | authors = [ 8 | {name = "Your Name", email = "your.email@example.com"}, 9 | ] 10 | dependencies = [ 11 | "mcp>=1.0.0", 12 | "openai>=1.0.0", 13 | "python-dotenv>=0.19.0", 14 | "pydantic>=2.0.0", 15 | "asyncio>=3.4.3", 16 | "aiohttp>=3.8.0", 17 | "typing-extensions>=4.0.0", 18 | "colorlog>=6.9.0", 19 | ] 20 | 21 | [tool.poetry] 22 | name = "mcp-llm-bridge" 23 | version = "0.1.0" 24 | description = "Bridge between MCP protocol and LLM clients" 25 | authors = ["Your Name "] 26 | readme = "README.md" 27 | packages = [{include = "mcp_llm_bridge", from = "src"}] 28 | 29 | [tool.poetry.dependencies] 30 | python = ">=3.12" 31 | mcp = ">=1.0.0" 32 | openai = ">=1.0.0" 33 | python-dotenv = ">=0.19.0" 34 | pydantic = ">=2.0.0" 35 | asyncio = ">=3.4.3" 36 | aiohttp = ">=3.8.0" 37 | typing-extensions = ">=4.0.0" 38 | colorlog = ">=6.9.0" 39 | 40 | [project.optional-dependencies] 41 | test = [ 42 | "pytest>=7.0.0", 43 | "pytest-asyncio>=0.21.0", 44 | "pytest-mock>=3.10.0", 45 | "pytest-aiohttp>=1.0.4", 46 | ] 47 | 48 | [tool.poetry.group.test.dependencies] 49 | pytest = ">=7.0.0" 50 | pytest-asyncio = ">=0.21.0" 51 | pytest-mock = ">=3.10.0" 52 | pytest-aiohttp = ">=1.0.4" 53 | 54 | [build-system] 55 | requires = ["setuptools>=45", "wheel"] 56 | build-backend = "setuptools.build_meta" 57 | 58 | [tool.setuptools] 59 | package-dir = {"" = "src"} 60 | 61 | [tool.setuptools.packages.find] 62 | where = ["src"] 63 | include = ["mcp_llm_bridge*"] 64 | 65 | [tool.pytest.ini_options] 66 | asyncio_mode = "auto" 67 | asyncio_default_fixture_loop_scope = "function" 68 | testpaths = ["tests"] 69 | 70 | [tool.ruff] 71 | line-length = 100 72 | target-version = "py313" 73 | 74 | [tool.ruff.lint] 75 | select = ["E", "F", "I"] 76 | -------------------------------------------------------------------------------- /src/mcp_llm_bridge/__init__.py: -------------------------------------------------------------------------------- 1 | # src/mcp_llm_bridge/__init__.py 2 | from .mcp_client import MCPClient 3 | from .bridge import MCPLLMBridge, BridgeManager 4 | from .config import BridgeConfig, LLMConfig 5 | from .llm_client import LLMClient 6 | 7 | __all__ = ['MCPClient', 'MCPLLMBridge', 'BridgeManager', 'BridgeConfig', 'LLMConfig', 'LLMClient'] -------------------------------------------------------------------------------- /src/mcp_llm_bridge/bridge.py: -------------------------------------------------------------------------------- 1 | # src/mcp_llm_bridge/bridge.py 2 | from typing import Dict, List, Any, Optional 3 | from dataclasses import dataclass 4 | from mcp import ClientSession, StdioServerParameters 5 | from mcp_llm_bridge.mcp_client import MCPClient 6 | from mcp_llm_bridge.llm_client import LLMClient 7 | import asyncio 8 | import json 9 | from mcp_llm_bridge.config import BridgeConfig 10 | import logging 11 | import colorlog 12 | from mcp_llm_bridge.tools import DatabaseQueryTool 13 | 14 | handler = colorlog.StreamHandler() 15 | handler.setFormatter(colorlog.ColoredFormatter( 16 | "%(log_color)s%(levelname)s%(reset)s: %(cyan)s%(name)s%(reset)s - %(message)s", 17 | datefmt=None, 18 | reset=True, 19 | log_colors={ 20 | 'DEBUG': 'cyan', 21 | 'INFO': 'green', 22 | 'WARNING': 'yellow', 23 | 'ERROR': 'red', 24 | 'CRITICAL': 'red,bg_white', 25 | }, 26 | secondary_log_colors={}, 27 | style='%' 28 | )) 29 | 30 | logger = colorlog.getLogger(__name__) 31 | logger.addHandler(handler) 32 | logger.setLevel(logging.INFO) 33 | 34 | class MCPLLMBridge: 35 | """Bridge between MCP protocol and LLM client""" 36 | 37 | def __init__(self, config: BridgeConfig): 38 | self.config = config 39 | self.mcp_client = MCPClient(config.mcp_server_params) 40 | self.llm_client = LLMClient(config.llm_config) 41 | self.query_tool = DatabaseQueryTool("test.db") # Initialize database query tool 42 | 43 | # Combine system prompt with schema information 44 | schema_prompt = f""" 45 | Available Database Schema: 46 | {self.query_tool.get_schema_description()} 47 | 48 | When querying the database: 49 | 1. Use the exact column names as specified in the schema 50 | 2. Make sure your queries are valid SQL 51 | 3. The database is SQLite, so use compatible syntax 52 | """ 53 | if config.system_prompt: 54 | self.llm_client.system_prompt = f"{config.system_prompt}\n\n{schema_prompt}" 55 | else: 56 | self.llm_client.system_prompt = schema_prompt 57 | 58 | self.available_tools: List[Any] = [] 59 | self.tool_name_mapping: Dict[str, str] = {} # Maps OpenAI tool names to MCP tool names 60 | 61 | async def initialize(self): 62 | """Initialize both clients and set up tools""" 63 | try: 64 | # Connect MCP client 65 | await self.mcp_client.connect() 66 | 67 | # Get available tools from MCP and add our database tool 68 | mcp_tools = await self.mcp_client.get_available_tools() 69 | if hasattr(mcp_tools, 'tools'): 70 | self.available_tools = [*mcp_tools.tools, self.query_tool.get_tool_spec()] 71 | else: 72 | self.available_tools = [*mcp_tools, self.query_tool.get_tool_spec()] 73 | 74 | logger.debug(f"MCP Tools received: {self.available_tools}") 75 | 76 | # Convert and register tools with LLM client 77 | converted_tools = self._convert_mcp_tools_to_openai_format(self.available_tools) 78 | logger.debug(f"Converted tools for OpenAI: {converted_tools}") 79 | self.llm_client.tools = converted_tools 80 | 81 | return True 82 | except Exception as e: 83 | logger.error(f"Bridge initialization failed: {str(e)}", exc_info=True) 84 | return False 85 | 86 | def _convert_mcp_tools_to_openai_format(self, mcp_tools: List[Any]) -> List[Dict[str, Any]]: 87 | """Convert MCP tool format to OpenAI tool format""" 88 | openai_tools = [] 89 | 90 | logger.debug(f"Input mcp_tools type: {type(mcp_tools)}") 91 | logger.debug(f"Input mcp_tools: {mcp_tools}") 92 | 93 | # Extract tools from the response 94 | if hasattr(mcp_tools, 'tools'): 95 | tools_list = mcp_tools.tools 96 | logger.debug("Found ListToolsResult, extracting tools attribute") 97 | elif isinstance(mcp_tools, dict): 98 | tools_list = mcp_tools.get('tools', []) 99 | logger.debug("Found dict, extracting 'tools' key") 100 | else: 101 | tools_list = mcp_tools 102 | logger.debug("Using mcp_tools directly as list") 103 | 104 | logger.debug(f"Tools list type: {type(tools_list)}") 105 | logger.debug(f"Tools list: {tools_list}") 106 | 107 | # Process each tool in the list 108 | if isinstance(tools_list, list): 109 | logger.debug(f"Processing {len(tools_list)} tools") 110 | for tool in tools_list: 111 | logger.debug(f"Processing tool: {tool}, type: {type(tool)}") 112 | if hasattr(tool, 'name') and hasattr(tool, 'description'): 113 | openai_name = self._sanitize_tool_name(tool.name) 114 | self.tool_name_mapping[openai_name] = tool.name 115 | logger.debug(f"Tool has required attributes. Name: {tool.name}") 116 | 117 | tool_schema = getattr(tool, 'inputSchema', { 118 | "type": "object", 119 | "properties": {}, 120 | "required": [] 121 | }) 122 | 123 | openai_tool = { 124 | "type": "function", 125 | "function": { 126 | "name": openai_name, 127 | "description": tool.description, 128 | "parameters": tool_schema 129 | } 130 | } 131 | openai_tools.append(openai_tool) 132 | logger.debug(f"Converted tool {tool.name} to OpenAI format") 133 | else: 134 | logger.debug(f"Tool missing required attributes: has name = {hasattr(tool, 'name')}, has description = {hasattr(tool, 'description')}") 135 | else: 136 | logger.debug(f"Tools list is not a list, it's a {type(tools_list)}") 137 | 138 | return openai_tools 139 | 140 | def _sanitize_tool_name(self, name: str) -> str: 141 | """Sanitize tool name for OpenAI compatibility""" 142 | # Replace any characters that might cause issues 143 | return name.replace("-", "_").replace(" ", "_").lower() 144 | 145 | async def process_message(self, message: str) -> str: 146 | """Process a user message through the bridge""" 147 | try: 148 | # Send message to LLM 149 | logger.debug(f"Sending message to LLM: {message}") 150 | response = await self.llm_client.invoke_with_prompt(message) 151 | logger.debug(f"LLM Response: {response}") 152 | 153 | # Keep processing tool calls until we get a final response 154 | while response.is_tool_call: 155 | if not response.tool_calls: 156 | break 157 | 158 | logger.debug(f"Tool calls detected: {response.tool_calls}") 159 | tool_responses = await self._handle_tool_calls(response.tool_calls) 160 | logger.debug(f"Tool responses: {tool_responses}") 161 | 162 | # Continue the conversation with tool results 163 | response = await self.llm_client.invoke(tool_responses) 164 | logger.debug(f"Next LLM response: {response}") 165 | 166 | return response.content 167 | except Exception as e: 168 | logger.error(f"Error processing message: {str(e)}", exc_info=True) 169 | return f"Error processing message: {str(e)}" 170 | 171 | async def _handle_tool_calls(self, tool_calls: List[Dict[str, Any]]) -> List[Dict[str, Any]]: 172 | """Handle tool calls through MCP""" 173 | tool_responses = [] 174 | 175 | for tool_call in tool_calls: 176 | try: 177 | logger.debug(f"Processing tool call: {tool_call}") 178 | # Get original MCP tool name 179 | openai_name = tool_call.function.name 180 | mcp_name = self.tool_name_mapping.get(openai_name) 181 | 182 | if not mcp_name: 183 | raise ValueError(f"Unknown tool: {openai_name}") 184 | 185 | # Parse arguments 186 | arguments = json.loads(tool_call.function.arguments) 187 | logger.debug(f"Tool arguments: {arguments}") 188 | 189 | # Execute through MCP 190 | result = await self.mcp_client.call_tool(mcp_name, arguments) 191 | logger.debug(f"Raw MCP result: {result}") 192 | 193 | # Format response - handle both string and structured results 194 | if isinstance(result, str): 195 | output = result 196 | elif hasattr(result, 'content') and isinstance(result.content, list): 197 | # Handle MCP CallToolResult format 198 | output = " ".join( 199 | content.text for content in result.content 200 | if hasattr(content, 'text') 201 | ) 202 | else: 203 | output = str(result) # Use str() instead of json.dumps() 204 | 205 | logger.debug(f"Formatted output: {output}") 206 | 207 | # Format response 208 | tool_responses.append({ 209 | "tool_call_id": tool_call.id, 210 | "output": output 211 | }) 212 | 213 | except Exception as e: 214 | logger.error(f"Tool execution failed: {str(e)}", exc_info=True) 215 | tool_responses.append({ 216 | "tool_call_id": tool_call.id, 217 | "output": f"Error: {str(e)}" 218 | }) 219 | 220 | return tool_responses 221 | 222 | async def close(self): 223 | """Clean up resources""" 224 | await self.mcp_client.__aexit__(None, None, None) 225 | 226 | class BridgeManager: 227 | """Manager class for handling the bridge lifecycle""" 228 | 229 | def __init__(self, config: BridgeConfig): 230 | self.config = config 231 | self.bridge: Optional[MCPLLMBridge] = None 232 | 233 | async def __aenter__(self) -> MCPLLMBridge: 234 | """Context manager entry""" 235 | self.bridge = MCPLLMBridge(self.config) 236 | await self.bridge.initialize() 237 | return self.bridge 238 | 239 | async def __aexit__(self, exc_type, exc_val, exc_tb): 240 | """Context manager exit""" 241 | if self.bridge: 242 | await self.bridge.close() -------------------------------------------------------------------------------- /src/mcp_llm_bridge/config.py: -------------------------------------------------------------------------------- 1 | # src/mcp_llm_bridge/config.py 2 | from dataclasses import dataclass 3 | from typing import Optional 4 | from mcp import StdioServerParameters 5 | 6 | @dataclass 7 | class LLMConfig: 8 | """Configuration for LLM client""" 9 | api_key: str 10 | model: str 11 | base_url: Optional[str] = None 12 | temperature: float = 0.7 13 | max_tokens: int = 2000 14 | 15 | @dataclass 16 | class BridgeConfig: 17 | """Configuration for the MCP-LLM Bridge""" 18 | mcp_server_params: StdioServerParameters 19 | llm_config: LLMConfig 20 | system_prompt: Optional[str] = None -------------------------------------------------------------------------------- /src/mcp_llm_bridge/create_test_db.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | import os 3 | from datetime import datetime 4 | 5 | def create_test_database(db_path: str = "test.db"): 6 | """Create a test database with sample products""" 7 | 8 | # If database exists, remove it to start fresh 9 | if os.path.exists(db_path): 10 | os.remove(db_path) 11 | 12 | # Connect to database (this will create it if it doesn't exist) 13 | conn = sqlite3.connect(db_path) 14 | cursor = conn.cursor() 15 | 16 | # Create products table 17 | cursor.execute(""" 18 | CREATE TABLE products ( 19 | id INTEGER PRIMARY KEY AUTOINCREMENT, 20 | title TEXT NOT NULL, 21 | description TEXT, 22 | price REAL NOT NULL, 23 | category TEXT, 24 | stock INTEGER DEFAULT 0, 25 | created_at DATETIME DEFAULT CURRENT_TIMESTAMP 26 | ) 27 | """) 28 | 29 | # Sample product data 30 | products = [ 31 | ("Laptop Pro X", "High-performance laptop with 16GB RAM", 1299.99, "Electronics", 50), 32 | ("Wireless Mouse", "Ergonomic wireless mouse", 29.99, "Electronics", 200), 33 | ("Coffee Maker", "12-cup programmable coffee maker", 79.99, "Appliances", 30), 34 | ("Running Shoes", "Lightweight running shoes", 89.99, "Sports", 100), 35 | ("Yoga Mat", "Non-slip exercise yoga mat", 24.99, "Sports", 150), 36 | ("Smart Watch", "Fitness tracking smart watch", 199.99, "Electronics", 75), 37 | ("Backpack", "Water-resistant hiking backpack", 49.99, "Outdoor", 120), 38 | ("Water Bottle", "Insulated stainless steel bottle", 19.99, "Outdoor", 200), 39 | ("Desk Lamp", "LED desk lamp with adjustable brightness", 39.99, "Home", 80), 40 | ("Bluetooth Speaker", "Portable wireless speaker", 69.99, "Electronics", 60), 41 | ("Plant Pot", "Ceramic indoor plant pot", 15.99, "Home", 100), 42 | ("Chair", "Ergonomic office chair", 199.99, "Furniture", 25), 43 | ("Notebook", "Hardcover ruled notebook", 9.99, "Stationery", 300), 44 | ("Paint Set", "Acrylic paint set with brushes", 34.99, "Art", 45), 45 | ("Headphones", "Noise-cancelling headphones", 159.99, "Electronics", 40) 46 | ] 47 | 48 | # Insert products 49 | cursor.executemany( 50 | "INSERT INTO products (title, description, price, category, stock) VALUES (?, ?, ?, ?, ?)", 51 | products 52 | ) 53 | 54 | # Create categories table 55 | cursor.execute(""" 56 | CREATE TABLE categories ( 57 | id INTEGER PRIMARY KEY AUTOINCREMENT, 58 | name TEXT UNIQUE NOT NULL, 59 | description TEXT 60 | ) 61 | """) 62 | 63 | # Sample category data 64 | categories = [ 65 | ("Electronics", "Electronic devices and accessories"), 66 | ("Appliances", "Home appliances"), 67 | ("Sports", "Sports and fitness equipment"), 68 | ("Outdoor", "Outdoor and hiking gear"), 69 | ("Home", "Home decor and accessories"), 70 | ("Furniture", "Home and office furniture"), 71 | ("Stationery", "Writing and office supplies"), 72 | ("Art", "Art supplies and materials") 73 | ] 74 | 75 | # Insert categories 76 | cursor.executemany( 77 | "INSERT INTO categories (name, description) VALUES (?, ?)", 78 | categories 79 | ) 80 | 81 | # Commit changes and close connection 82 | conn.commit() 83 | conn.close() 84 | 85 | print(f"Database created successfully at {db_path}") 86 | print("Created tables: products, categories") 87 | print(f"Inserted {len(products)} products and {len(categories)} categories") 88 | 89 | if __name__ == "__main__": 90 | create_test_database() -------------------------------------------------------------------------------- /src/mcp_llm_bridge/llm_client.py: -------------------------------------------------------------------------------- 1 | # src/mcp_llm_bridge/llm_client.py 2 | from typing import Dict, List, Any, Optional 3 | import openai 4 | from mcp_llm_bridge.config import LLMConfig 5 | import logging 6 | import colorlog 7 | 8 | handler = colorlog.StreamHandler() 9 | handler.setFormatter(colorlog.ColoredFormatter( 10 | "%(log_color)s%(levelname)s%(reset)s: %(cyan)s%(name)s%(reset)s - %(message)s", 11 | datefmt=None, 12 | reset=True, 13 | log_colors={ 14 | 'DEBUG': 'cyan', 15 | 'INFO': 'green', 16 | 'WARNING': 'yellow', 17 | 'ERROR': 'red', 18 | 'CRITICAL': 'red,bg_white', 19 | }, 20 | secondary_log_colors={}, 21 | style='%' 22 | )) 23 | 24 | logger = colorlog.getLogger(__name__) 25 | logger.addHandler(handler) 26 | logger.setLevel(logging.INFO) 27 | 28 | class LLMResponse: 29 | """Standardized response format focusing on tool handling""" 30 | def __init__(self, completion: Any): 31 | self.completion = completion 32 | self.choice = completion.choices[0] 33 | self.message = self.choice.message 34 | self.stop_reason = self.choice.finish_reason 35 | self.is_tool_call = self.stop_reason == "tool_calls" 36 | 37 | # Format content for bridge compatibility 38 | self.content = self.message.content if self.message.content is not None else "" 39 | self.tool_calls = self.message.tool_calls if hasattr(self.message, "tool_calls") else None 40 | 41 | # Debug logging 42 | logger.debug(f"Raw completion: {completion}") 43 | logger.debug(f"Message content: {self.content}") 44 | logger.debug(f"Tool calls: {self.tool_calls}") 45 | 46 | def get_message(self) -> Dict[str, Any]: 47 | """Get standardized message format""" 48 | return { 49 | "role": "assistant", 50 | "content": self.content, 51 | "tool_calls": self.tool_calls 52 | } 53 | 54 | class LLMClient: 55 | """Client for interacting with OpenAI-compatible LLMs""" 56 | 57 | def __init__(self, config: LLMConfig): 58 | self.config = config 59 | self.client = openai.OpenAI( 60 | api_key=config.api_key, 61 | base_url=config.base_url 62 | ) 63 | self.tools = [] 64 | self.messages = [] 65 | self.system_prompt = None 66 | 67 | def _prepare_messages(self) -> List[Dict[str, Any]]: 68 | """Prepare messages for API call""" 69 | formatted_messages = [] 70 | 71 | if self.system_prompt: 72 | formatted_messages.append({ 73 | "role": "system", 74 | "content": self.system_prompt 75 | }) 76 | 77 | formatted_messages.extend(self.messages) 78 | return formatted_messages 79 | 80 | async def invoke_with_prompt(self, prompt: str) -> LLMResponse: 81 | """Send a single prompt to the LLM""" 82 | self.messages.append({ 83 | "role": "user", 84 | "content": prompt 85 | }) 86 | 87 | return await self.invoke([]) 88 | 89 | async def invoke(self, tool_results: Optional[List[Dict[str, Any]]] = None) -> LLMResponse: 90 | """Invoke the LLM with optional tool results""" 91 | if tool_results: 92 | for result in tool_results: 93 | self.messages.append({ 94 | "role": "tool", 95 | "content": str(result.get("output", "")), # Convert to string and provide default 96 | "tool_call_id": result["tool_call_id"] 97 | }) 98 | 99 | completion = self.client.chat.completions.create( 100 | model=self.config.model, 101 | messages=self._prepare_messages(), 102 | tools=self.tools if self.tools else None, 103 | temperature=self.config.temperature, 104 | max_tokens=self.config.max_tokens 105 | ) 106 | 107 | response = LLMResponse(completion) 108 | self.messages.append(response.get_message()) 109 | 110 | return response -------------------------------------------------------------------------------- /src/mcp_llm_bridge/main.py: -------------------------------------------------------------------------------- 1 | # src/mcp_llm_bridge/main.py 2 | import os 3 | import asyncio 4 | from dotenv import load_dotenv 5 | from mcp import StdioServerParameters 6 | from mcp_llm_bridge.config import BridgeConfig, LLMConfig 7 | from mcp_llm_bridge.bridge import BridgeManager 8 | import colorlog 9 | import logging 10 | 11 | handler = colorlog.StreamHandler() 12 | handler.setFormatter(colorlog.ColoredFormatter( 13 | "%(log_color)s%(levelname)s%(reset)s: %(cyan)s%(name)s%(reset)s - %(message)s", 14 | datefmt=None, 15 | reset=True, 16 | log_colors={ 17 | 'DEBUG': 'cyan', 18 | 'INFO': 'green', 19 | 'WARNING': 'yellow', 20 | 'ERROR': 'red', 21 | 'CRITICAL': 'red,bg_white', 22 | }, 23 | secondary_log_colors={}, 24 | style='%' 25 | )) 26 | 27 | logger = colorlog.getLogger(__name__) 28 | logger.addHandler(handler) 29 | logger.setLevel(logging.INFO) 30 | 31 | async def main(): 32 | # Load environment variables 33 | load_dotenv() 34 | 35 | # Get the project root directory (where test.db is located) 36 | project_root = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) 37 | db_path = os.path.join(project_root, "test.db") 38 | 39 | # Configure bridge 40 | config = BridgeConfig( 41 | mcp_server_params=StdioServerParameters( 42 | command="uvx", 43 | args=["mcp-server-sqlite", "--db-path", db_path], 44 | env=None 45 | ), 46 | # llm_config=LLMConfig( 47 | # api_key=os.getenv("OPENAI_API_KEY"), 48 | # model=os.getenv("OPENAI_MODEL", "gpt-4o"), 49 | # base_url=None 50 | # ), 51 | llm_config=LLMConfig( 52 | api_key="ollama", # Can be any string for local testing 53 | model="mistral-nemo:12b-instruct-2407-q8_0", 54 | base_url="http://localhost:11434/v1" # Point to your local model's endpoint 55 | ), 56 | system_prompt="You are a helpful assistant that can use tools to help answer questions." 57 | ) 58 | 59 | logger.info(f"Starting bridge with model: {config.llm_config.model}") 60 | logger.info(f"Using database at: {db_path}") 61 | 62 | # Use bridge with context manager 63 | async with BridgeManager(config) as bridge: 64 | while True: 65 | try: 66 | user_input = input("\nEnter your prompt (or 'quit' to exit): ") 67 | if user_input.lower() in ['quit', 'exit', 'q']: 68 | break 69 | 70 | response = await bridge.process_message(user_input) 71 | print(f"\nResponse: {response}") 72 | 73 | except KeyboardInterrupt: 74 | logger.info("\nExiting...") 75 | break 76 | except Exception as e: 77 | logger.error(f"\nError occurred: {e}") 78 | 79 | if __name__ == "__main__": 80 | asyncio.run(main()) -------------------------------------------------------------------------------- /src/mcp_llm_bridge/mcp_client.py: -------------------------------------------------------------------------------- 1 | # src/mcp_llm_bridge/mcp_client.py 2 | import logging 3 | from typing import Any, List 4 | from mcp import ClientSession, StdioServerParameters 5 | from mcp.client.stdio import stdio_client 6 | import colorlog 7 | 8 | handler = colorlog.StreamHandler() 9 | handler.setFormatter(colorlog.ColoredFormatter( 10 | "%(log_color)s%(levelname)s%(reset)s: %(cyan)s%(name)s%(reset)s - %(message)s", 11 | datefmt=None, 12 | reset=True, 13 | log_colors={ 14 | 'DEBUG': 'cyan', 15 | 'INFO': 'green', 16 | 'WARNING': 'yellow', 17 | 'ERROR': 'red', 18 | 'CRITICAL': 'red,bg_white', 19 | }, 20 | secondary_log_colors={}, 21 | style='%' 22 | )) 23 | 24 | logger = colorlog.getLogger(__name__) 25 | logger.addHandler(handler) 26 | logger.setLevel(logging.INFO) 27 | 28 | class MCPClient: 29 | """Client for interacting with MCP servers""" 30 | 31 | def __init__(self, server_params: StdioServerParameters): 32 | self.server_params = server_params 33 | self.session = None 34 | self._client = None 35 | 36 | async def __aenter__(self): 37 | """Async context manager entry""" 38 | await self.connect() 39 | return self 40 | 41 | async def __aexit__(self, exc_type, exc_val, exc_tb): 42 | """Async context manager exit""" 43 | if self.session: 44 | await self.session.__aexit__(exc_type, exc_val, exc_tb) 45 | if self._client: 46 | await self._client.__aexit__(exc_type, exc_val, exc_tb) 47 | 48 | async def connect(self): 49 | """Establishes connection to MCP server""" 50 | logger.debug("Connecting to MCP server...") 51 | self._client = stdio_client(self.server_params) 52 | self.read, self.write = await self._client.__aenter__() 53 | session = ClientSession(self.read, self.write) 54 | self.session = await session.__aenter__() 55 | await self.session.initialize() 56 | logger.debug("Connected to MCP server successfully") 57 | 58 | async def get_available_tools(self) -> List[Any]: 59 | """List available tools""" 60 | if not self.session: 61 | raise RuntimeError("Not connected to MCP server") 62 | 63 | logger.debug("Requesting available tools from MCP server") 64 | tools = await self.session.list_tools() 65 | logger.debug(f"Received tools from MCP server: {tools}") 66 | return tools 67 | 68 | async def call_tool(self, tool_name: str, arguments: dict) -> Any: 69 | """Call a tool with given arguments""" 70 | if not self.session: 71 | raise RuntimeError("Not connected to MCP server") 72 | 73 | logger.debug(f"Calling MCP tool '{tool_name}' with arguments: {arguments}") 74 | result = await self.session.call_tool(tool_name, arguments=arguments) 75 | logger.debug(f"Tool result: {result}") 76 | return result -------------------------------------------------------------------------------- /src/mcp_llm_bridge/tools.py: -------------------------------------------------------------------------------- 1 | from typing import Dict, List, Any 2 | from dataclasses import dataclass 3 | import sqlite3 4 | import logging 5 | 6 | @dataclass 7 | class DatabaseSchema: 8 | """Represents the schema of a database table""" 9 | table_name: str 10 | columns: Dict[str, str] 11 | description: str 12 | 13 | class DatabaseQueryTool: 14 | """Tool for executing database queries with schema validation""" 15 | 16 | def __init__(self, db_path: str): 17 | self.db_path = db_path 18 | self.logger = logging.getLogger(__name__) 19 | self.schemas: Dict[str, DatabaseSchema] = {} 20 | 21 | # Register default product schema 22 | self.register_schema(DatabaseSchema( 23 | table_name="products", 24 | columns={ 25 | "id": "INTEGER", 26 | "title": "TEXT", 27 | "description": "TEXT", 28 | "price": "REAL", 29 | "category": "TEXT", 30 | "stock": "INTEGER", 31 | "created_at": "DATETIME" 32 | }, 33 | description="Product catalog with items for sale" 34 | )) 35 | 36 | def register_schema(self, schema: DatabaseSchema): 37 | """Register a database schema""" 38 | self.schemas[schema.table_name] = schema 39 | 40 | def get_tool_spec(self) -> Dict[str, Any]: 41 | """Get the tool specification in MCP format""" 42 | schema_desc = "\n".join([ 43 | f"Table {schema.table_name}: {schema.description}\n" 44 | f"Columns: {', '.join(f'{name} ({type_})' for name, type_ in schema.columns.items())}" 45 | for schema in self.schemas.values() 46 | ]) 47 | 48 | return { 49 | "name": "query_database", 50 | "description": f"Execute SQL queries against the database. Available schemas:\n{schema_desc}", 51 | "inputSchema": { 52 | "type": "object", 53 | "properties": { 54 | "query": { 55 | "type": "string", 56 | "description": "SQL query to execute" 57 | } 58 | }, 59 | "required": ["query"] 60 | } 61 | } 62 | 63 | def get_schema_description(self) -> str: 64 | """Get a formatted description of all registered schemas""" 65 | schema_parts = [] 66 | for schema in self.schemas.values(): 67 | column_info = [] 68 | for name, type_ in schema.columns.items(): 69 | column_info.append(f" - {name} ({type_})") 70 | schema_parts.append(f"Table {schema.table_name}: {schema.description}\n" + "\n".join(column_info)) 71 | 72 | return "\n\n".join(schema_parts) 73 | 74 | def validate_query(self, query: str) -> bool: 75 | """Validate a query against registered schemas""" 76 | query = query.lower() 77 | for schema in self.schemas.values(): 78 | if schema.table_name in query: 79 | # Check if query references any non-existent columns 80 | for word in query.split(): 81 | if '.' in word: 82 | table, column = word.split('.') 83 | if table == schema.table_name and column not in schema.columns: 84 | return False 85 | return True 86 | 87 | async def execute(self, params: Dict[str, Any]) -> List[Dict[str, Any]]: 88 | """Execute a SQL query and return results""" 89 | query = params.get("query") 90 | if not query: 91 | raise ValueError("Query parameter is required") 92 | 93 | if not self.validate_query(query): 94 | raise ValueError("Query references invalid columns") 95 | 96 | conn = sqlite3.connect(self.db_path) 97 | try: 98 | cursor = conn.cursor() 99 | cursor.execute(query) 100 | columns = [description[0] for description in cursor.description] 101 | results = [] 102 | for row in cursor.fetchall(): 103 | results.append(dict(zip(columns, row))) 104 | return results 105 | finally: 106 | conn.close() -------------------------------------------------------------------------------- /tests/test_bridge.py: -------------------------------------------------------------------------------- 1 | # tests/test_bridge.py 2 | import pytest 3 | import os 4 | from unittest.mock import AsyncMock, MagicMock, patch 5 | from mcp import StdioServerParameters 6 | from mcp_llm_bridge.config import BridgeConfig, LLMConfig 7 | from mcp_llm_bridge.bridge import MCPLLMBridge, BridgeManager 8 | 9 | @pytest.fixture 10 | def mock_mcp_tool(): 11 | mock = MagicMock() 12 | mock.name = "test_tool" 13 | mock.description = "A test tool" 14 | mock.inputSchema = { 15 | "type": "object", 16 | "properties": { 17 | "arg1": {"type": "string"} 18 | } 19 | } 20 | return mock 21 | 22 | @pytest.fixture 23 | def mock_config(): 24 | return BridgeConfig( 25 | mcp_server_params=StdioServerParameters( 26 | command="uvx", 27 | args=["mcp-server-sqlite", "--db-path", "test.db"], 28 | env=None 29 | ), 30 | llm_config=LLMConfig( 31 | api_key="test-key", 32 | model="gpt-4", 33 | base_url=None 34 | ), 35 | system_prompt="Test system prompt" 36 | ) 37 | 38 | @pytest.fixture 39 | def mock_llm_response(): 40 | tool_call = MagicMock() 41 | tool_call.id = "call_1" 42 | tool_call.function = MagicMock() 43 | tool_call.function.name = "test_tool" 44 | tool_call.function.arguments = '{"arg1": "test"}' 45 | 46 | response = MagicMock() 47 | response.tool_calls = [tool_call] 48 | return response 49 | 50 | @pytest.mark.asyncio 51 | async def test_bridge_initialization(mock_config, mock_mcp_tool): 52 | with patch('mcp_llm_bridge.bridge.MCPClient') as MockMCPClient: 53 | # Setup mocks 54 | mock_mcp_instance = AsyncMock() 55 | mock_mcp_instance.get_available_tools.return_value = [mock_mcp_tool] 56 | MockMCPClient.return_value = mock_mcp_instance 57 | 58 | # Create bridge 59 | bridge = MCPLLMBridge(mock_config) 60 | 61 | # Test initialization 62 | success = await bridge.initialize() 63 | assert success == True 64 | 65 | # Verify MCP client was initialized 66 | MockMCPClient.assert_called_once() 67 | mock_mcp_instance.connect.assert_called_once() 68 | mock_mcp_instance.get_available_tools.assert_called_once() 69 | 70 | @pytest.mark.asyncio 71 | async def test_tool_conversion(mock_config, mock_mcp_tool): 72 | bridge = MCPLLMBridge(mock_config) 73 | converted_tools = bridge._convert_mcp_tools_to_openai_format([mock_mcp_tool]) 74 | 75 | assert len(converted_tools) == 1 76 | assert converted_tools[0]["type"] == "function" 77 | assert converted_tools[0]["function"]["name"] == "test_tool" 78 | assert converted_tools[0]["function"]["description"] == "A test tool" 79 | assert "parameters" in converted_tools[0]["function"] 80 | 81 | @pytest.mark.asyncio 82 | async def test_message_processing(mock_config, mock_llm_response): 83 | with patch('mcp_llm_bridge.bridge.MCPClient') as MockMCPClient, \ 84 | patch('mcp_llm_bridge.bridge.LLMClient') as MockLLMClient: 85 | 86 | # Setup mocks 87 | mock_mcp_instance = AsyncMock() 88 | mock_llm_instance = AsyncMock() 89 | 90 | # Create a mock response without tool calls 91 | mock_response = MagicMock() 92 | mock_response.content = "Final response" 93 | mock_response.tool_calls = None 94 | 95 | mock_llm_instance.invoke_with_prompt.return_value = mock_response 96 | 97 | MockMCPClient.return_value = mock_mcp_instance 98 | MockLLMClient.return_value = mock_llm_instance 99 | 100 | # Create and initialize bridge 101 | bridge = MCPLLMBridge(mock_config) 102 | await bridge.initialize() 103 | 104 | # Test message processing 105 | response = await bridge.process_message("Test message") 106 | 107 | # Verify interactions 108 | mock_llm_instance.invoke_with_prompt.assert_called_once_with("Test message") 109 | assert response == "Final response" 110 | 111 | @pytest.mark.asyncio 112 | async def test_tool_call_handling(mock_config, mock_llm_response): 113 | with patch('mcp_llm_bridge.bridge.MCPClient') as MockMCPClient, \ 114 | patch('mcp_llm_bridge.bridge.LLMClient') as MockLLMClient: 115 | 116 | # Setup mocks 117 | mock_mcp_instance = AsyncMock() 118 | mock_mcp_instance.call_tool.return_value = {"result": "tool_result"} 119 | 120 | mock_llm_instance = AsyncMock() 121 | mock_llm_instance.invoke_with_prompt.return_value = mock_llm_response 122 | mock_llm_instance.invoke.return_value = MagicMock(content="Final response") 123 | 124 | MockMCPClient.return_value = mock_mcp_instance 125 | MockLLMClient.return_value = mock_llm_instance 126 | 127 | # Create and initialize bridge 128 | bridge = MCPLLMBridge(mock_config) 129 | bridge.tool_name_mapping = {"test_tool": "test_tool"} 130 | await bridge.initialize() 131 | 132 | # Test tool call handling 133 | tool_responses = await bridge._handle_tool_calls(mock_llm_response.tool_calls) 134 | 135 | # Verify tool execution 136 | assert len(tool_responses) == 1 137 | assert tool_responses[0]["tool_call_id"] == "call_1" 138 | mock_mcp_instance.call_tool.assert_called_once_with( 139 | "test_tool", 140 | {"arg1": "test"} 141 | ) 142 | 143 | @pytest.mark.asyncio 144 | async def test_bridge_manager(mock_config): 145 | with patch('mcp_llm_bridge.bridge.MCPLLMBridge') as MockBridge: 146 | # Setup mock 147 | mock_bridge_instance = AsyncMock() 148 | mock_bridge_instance.initialize.return_value = True 149 | MockBridge.return_value = mock_bridge_instance 150 | 151 | # Test context manager 152 | async with BridgeManager(mock_config) as bridge: 153 | assert bridge is mock_bridge_instance 154 | mock_bridge_instance.initialize.assert_called_once() 155 | 156 | @pytest.mark.asyncio 157 | async def test_error_handling(mock_config): 158 | with patch('mcp_llm_bridge.bridge.MCPClient') as MockMCPClient: 159 | # Setup mock to raise an error 160 | mock_mcp_instance = AsyncMock() 161 | mock_mcp_instance.connect.side_effect = Exception("Connection error") 162 | MockMCPClient.return_value = mock_mcp_instance 163 | 164 | # Create bridge 165 | bridge = MCPLLMBridge(mock_config) 166 | 167 | # Test initialization failure 168 | success = await bridge.initialize() 169 | assert success == False 170 | 171 | @pytest.mark.asyncio 172 | async def test_tool_name_sanitization(mock_config): 173 | bridge = MCPLLMBridge(mock_config) 174 | 175 | test_cases = [ 176 | ("test-tool", "test_tool"), 177 | ("test tool", "test_tool"), 178 | ("Test-Tool", "test_tool"), 179 | ("test_tool", "test_tool"), 180 | ("test-tool-123", "test_tool_123"), 181 | ] 182 | 183 | for input_name, expected_output in test_cases: 184 | assert bridge._sanitize_tool_name(input_name) == expected_output 185 | 186 | @pytest.mark.asyncio 187 | async def test_bridge_cleanup(mock_config): 188 | with patch('mcp_llm_bridge.bridge.MCPClient') as MockMCPClient: 189 | # Setup mock 190 | mock_mcp_instance = AsyncMock() 191 | MockMCPClient.return_value = mock_mcp_instance 192 | 193 | # Create bridge 194 | bridge = MCPLLMBridge(mock_config) 195 | 196 | # Test cleanup 197 | await bridge.close() 198 | mock_mcp_instance.__aexit__.assert_called_once() -------------------------------------------------------------------------------- /uv.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | requires-python = ">=3.12" 3 | 4 | [[package]] 5 | name = "aiohappyeyeballs" 6 | version = "2.4.4" 7 | source = { registry = "https://pypi.org/simple" } 8 | sdist = { url = "https://files.pythonhosted.org/packages/7f/55/e4373e888fdacb15563ef6fa9fa8c8252476ea071e96fb46defac9f18bf2/aiohappyeyeballs-2.4.4.tar.gz", hash = "sha256:5fdd7d87889c63183afc18ce9271f9b0a7d32c2303e394468dd45d514a757745", size = 21977 } 9 | wheels = [ 10 | { url = "https://files.pythonhosted.org/packages/b9/74/fbb6559de3607b3300b9be3cc64e97548d55678e44623db17820dbd20002/aiohappyeyeballs-2.4.4-py3-none-any.whl", hash = "sha256:a980909d50efcd44795c4afeca523296716d50cd756ddca6af8c65b996e27de8", size = 14756 }, 11 | ] 12 | 13 | [[package]] 14 | name = "aiohttp" 15 | version = "3.11.9" 16 | source = { registry = "https://pypi.org/simple" } 17 | dependencies = [ 18 | { name = "aiohappyeyeballs" }, 19 | { name = "aiosignal" }, 20 | { name = "attrs" }, 21 | { name = "frozenlist" }, 22 | { name = "multidict" }, 23 | { name = "propcache" }, 24 | { name = "yarl" }, 25 | ] 26 | sdist = { url = "https://files.pythonhosted.org/packages/3f/24/d5c0aed3ed90896f8505786e3a1e348fd9c61284ef21f54ee9cdf8b92e4f/aiohttp-3.11.9.tar.gz", hash = "sha256:a9266644064779840feec0e34f10a89b3ff1d2d6b751fe90017abcad1864fa7c", size = 7668012 } 27 | wheels = [ 28 | { url = "https://files.pythonhosted.org/packages/fa/43/b3c28a7e8f8b5e8ef0bea9fcabe8e99787c70fa526e5bc8185fd89f46434/aiohttp-3.11.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c1f2d7fd583fc79c240094b3e7237d88493814d4b300d013a42726c35a734bc9", size = 703661 }, 29 | { url = "https://files.pythonhosted.org/packages/f3/2c/be4624671e5ed344fca9196d0823eb6a17383cbe13d051d22d3a1f6ecbf7/aiohttp-3.11.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d4b8a1b6c7a68c73191f2ebd3bf66f7ce02f9c374e309bdb68ba886bbbf1b938", size = 463054 }, 30 | { url = "https://files.pythonhosted.org/packages/d6/21/8d14fa0bdae468ebe419df1764583ecc9e995a2ccd8a11ee8146a09fb5e5/aiohttp-3.11.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bd3f711f4c99da0091ced41dccdc1bcf8be0281dc314d6d9c6b6cf5df66f37a9", size = 455006 }, 31 | { url = "https://files.pythonhosted.org/packages/42/de/3fc5e94a24bf079709e9fed3572ebb5efb32f0995baf08a985ee9f517b0b/aiohttp-3.11.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44cb1a1326a0264480a789e6100dc3e07122eb8cd1ad6b784a3d47d13ed1d89c", size = 1681364 }, 32 | { url = "https://files.pythonhosted.org/packages/69/e0/bd9346efcdd3344284e4b4088bc2c720065176bd9180517bdc7097218903/aiohttp-3.11.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7a7ddf981a0b953ade1c2379052d47ccda2f58ab678fca0671c7c7ca2f67aac2", size = 1735986 }, 33 | { url = "https://files.pythonhosted.org/packages/9b/a5/549ce29e21ebf555dcf5c81e19e6eb30eb8de26f8da304f05a28d6d66d8c/aiohttp-3.11.9-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6ffa45cc55b18d4ac1396d1ddb029f139b1d3480f1594130e62bceadf2e1a838", size = 1792263 }, 34 | { url = "https://files.pythonhosted.org/packages/7a/2b/23124c04701e0d2e215be59bf445c33602b1ccc4d9acb7bccc2ec20c892d/aiohttp-3.11.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cca505829cdab58c2495ff418c96092d225a1bbd486f79017f6de915580d3c44", size = 1690838 }, 35 | { url = "https://files.pythonhosted.org/packages/af/a6/ebb8be53787c57dd7dd8b9617357af60d603ccd2fbf7a9e306f33178894b/aiohttp-3.11.9-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44d323aa80a867cb6db6bebb4bbec677c6478e38128847f2c6b0f70eae984d72", size = 1618311 }, 36 | { url = "https://files.pythonhosted.org/packages/9b/3c/cb8e5af30e33775539b4a6ea818eb16b0b01f68ce7a2fa77dff5df3dee80/aiohttp-3.11.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b2fab23003c4bb2249729a7290a76c1dda38c438300fdf97d4e42bf78b19c810", size = 1640417 }, 37 | { url = "https://files.pythonhosted.org/packages/16/2d/62593ce65e5811ea46e521644e03d0c47345bf9b6c2e6efcb759915d6aa3/aiohttp-3.11.9-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:be0c7c98e38a1e3ad7a6ff64af8b6d6db34bf5a41b1478e24c3c74d9e7f8ed42", size = 1645507 }, 38 | { url = "https://files.pythonhosted.org/packages/4f/6b/810981c99932665a225d7bdffacbda512dde6f11364ce11477662e457115/aiohttp-3.11.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5cc5e0d069c56645446c45a4b5010d4b33ac6c5ebfd369a791b5f097e46a3c08", size = 1701090 }, 39 | { url = "https://files.pythonhosted.org/packages/1c/01/79c8d156534c034207ccbb94a51f1ae4a625834a31e27670175f1e1e79b2/aiohttp-3.11.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9bcf97b971289be69638d8b1b616f7e557e1342debc7fc86cf89d3f08960e411", size = 1733598 }, 40 | { url = "https://files.pythonhosted.org/packages/c0/8f/873f0d3a47ec203ccd04dbd623f2428b6010ba6b11107aa9b44ad0ebfc86/aiohttp-3.11.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c7333e7239415076d1418dbfb7fa4df48f3a5b00f8fdf854fca549080455bc14", size = 1693573 }, 41 | { url = "https://files.pythonhosted.org/packages/2f/8c/a4964108383eb8f0e5a85ee0fdc00f9f0bdf28bb6a751be05a63c047ccbe/aiohttp-3.11.9-cp312-cp312-win32.whl", hash = "sha256:9384b07cfd3045b37b05ed002d1c255db02fb96506ad65f0f9b776b762a7572e", size = 410354 }, 42 | { url = "https://files.pythonhosted.org/packages/c8/9e/79aed1b3e110a02081ca47ba4a27d7e20040af241643a2e527c668634f22/aiohttp-3.11.9-cp312-cp312-win_amd64.whl", hash = "sha256:f5252ba8b43906f206048fa569debf2cd0da0316e8d5b4d25abe53307f573941", size = 436657 }, 43 | { url = "https://files.pythonhosted.org/packages/33/ec/217d8918032703639d64360e4534a33899cc1a5eda89268d4fa621e18b67/aiohttp-3.11.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:282e0a7ddd36ebc411f156aeaa0491e8fe7f030e2a95da532cf0c84b0b70bc66", size = 696994 }, 44 | { url = "https://files.pythonhosted.org/packages/48/e4/262211b96cba78614be9bae7086af0dba8e8050c43996f2912992173eb57/aiohttp-3.11.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ebd3e6b0c7d4954cca59d241970011f8d3327633d555051c430bd09ff49dc494", size = 459669 }, 45 | { url = "https://files.pythonhosted.org/packages/51/f5/ef76735af2d69671aa8cb185c07da84973a2ca74bb44af9fdb980207118f/aiohttp-3.11.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:30f9f89ae625d412043f12ca3771b2ccec227cc93b93bb1f994db6e1af40a7d3", size = 451949 }, 46 | { url = "https://files.pythonhosted.org/packages/ba/83/867487d4ca86327060b93f3eea70963996a7ebb0c16f61c214f801351d4a/aiohttp-3.11.9-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a3b5b2c012d70c63d9d13c57ed1603709a4d9d7d473e4a9dfece0e4ea3d5f51", size = 1664171 }, 47 | { url = "https://files.pythonhosted.org/packages/ca/7d/b185b4b6b01bf66bcaf1b23afff3073fc85d2f0765203269ee4976be2cf8/aiohttp-3.11.9-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6ef1550bb5f55f71b97a6a395286db07f7f2c01c8890e613556df9a51da91e8d", size = 1716933 }, 48 | { url = "https://files.pythonhosted.org/packages/a9/b3/70d7f26a874e96f932237e53017b048ecd754f06a29947bdf7ce39cade98/aiohttp-3.11.9-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:317251b9c9a2f1a9ff9cd093775b34c6861d1d7df9439ce3d32a88c275c995cd", size = 1774117 }, 49 | { url = "https://files.pythonhosted.org/packages/a5/6e/457acf09ac5bd6db5ae8b1fa68beb3000c989a2a20dc265a507123f7a689/aiohttp-3.11.9-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21cbe97839b009826a61b143d3ca4964c8590d7aed33d6118125e5b71691ca46", size = 1676168 }, 50 | { url = "https://files.pythonhosted.org/packages/e8/e8/2b4719633d0a8189dfce343af800d23163b8831cb5aa175d4c400b03895b/aiohttp-3.11.9-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:618b18c3a2360ac940a5503da14fa4f880c5b9bc315ec20a830357bcc62e6bae", size = 1602187 }, 51 | { url = "https://files.pythonhosted.org/packages/d8/0c/8938b85edaf0a8fee2ede7bbffd32e09b056475f7586b0852973749c5fff/aiohttp-3.11.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a0cf4d814689e58f57ecd5d8c523e6538417ca2e72ff52c007c64065cef50fb2", size = 1617286 }, 52 | { url = "https://files.pythonhosted.org/packages/1e/5c/825714aa554c4ef331a8c1a16b3183c5e4bf27c66073955d4f51344907dc/aiohttp-3.11.9-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:15c4e489942d987d5dac0ba39e5772dcbed4cc9ae3710d1025d5ba95e4a5349c", size = 1615518 }, 53 | { url = "https://files.pythonhosted.org/packages/c8/1c/6c821e7cf956e833a72a5284ff19484c7dedb749224e16fda297fa38bbc2/aiohttp-3.11.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:ec8df0ff5a911c6d21957a9182402aad7bf060eaeffd77c9ea1c16aecab5adbf", size = 1684466 }, 54 | { url = "https://files.pythonhosted.org/packages/6b/47/3e921cbf7d7c4edfe95ddb7e8315a8f3645d824863ef2c2eab5dfa0342bc/aiohttp-3.11.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ed95d66745f53e129e935ad726167d3a6cb18c5d33df3165974d54742c373868", size = 1714304 }, 55 | { url = "https://files.pythonhosted.org/packages/25/89/e68e3efd357f233265abcf22c48c4d1e81f992f264cd4dc69b96c5a13c47/aiohttp-3.11.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:647ec5bee7e4ec9f1034ab48173b5fa970d9a991e565549b965e93331f1328fe", size = 1671774 }, 56 | { url = "https://files.pythonhosted.org/packages/79/e1/4adaed8c8ef93c2ae54b001cd0e8dd6c84b40044038acb322b649150dc96/aiohttp-3.11.9-cp313-cp313-win32.whl", hash = "sha256:ef2c9499b7bd1e24e473dc1a85de55d72fd084eea3d8bdeec7ee0720decb54fa", size = 409216 }, 57 | { url = "https://files.pythonhosted.org/packages/00/9b/bf33704ac9b438d6dad417f86f1e9439e2538180189b0e347a95ff819011/aiohttp-3.11.9-cp313-cp313-win_amd64.whl", hash = "sha256:84de955314aa5e8d469b00b14d6d714b008087a0222b0f743e7ffac34ef56aff", size = 435069 }, 58 | ] 59 | 60 | [[package]] 61 | name = "aiosignal" 62 | version = "1.3.1" 63 | source = { registry = "https://pypi.org/simple" } 64 | dependencies = [ 65 | { name = "frozenlist" }, 66 | ] 67 | sdist = { url = "https://files.pythonhosted.org/packages/ae/67/0952ed97a9793b4958e5736f6d2b346b414a2cd63e82d05940032f45b32f/aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc", size = 19422 } 68 | wheels = [ 69 | { url = "https://files.pythonhosted.org/packages/76/ac/a7305707cb852b7e16ff80eaf5692309bde30e2b1100a1fcacdc8f731d97/aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17", size = 7617 }, 70 | ] 71 | 72 | [[package]] 73 | name = "annotated-types" 74 | version = "0.7.0" 75 | source = { registry = "https://pypi.org/simple" } 76 | sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } 77 | wheels = [ 78 | { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, 79 | ] 80 | 81 | [[package]] 82 | name = "anyio" 83 | version = "4.6.2.post1" 84 | source = { registry = "https://pypi.org/simple" } 85 | dependencies = [ 86 | { name = "idna" }, 87 | { name = "sniffio" }, 88 | ] 89 | sdist = { url = "https://files.pythonhosted.org/packages/9f/09/45b9b7a6d4e45c6bcb5bf61d19e3ab87df68e0601fa8c5293de3542546cc/anyio-4.6.2.post1.tar.gz", hash = "sha256:4c8bc31ccdb51c7f7bd251f51c609e038d63e34219b44aa86e47576389880b4c", size = 173422 } 90 | wheels = [ 91 | { url = "https://files.pythonhosted.org/packages/e4/f5/f2b75d2fc6f1a260f340f0e7c6a060f4dd2961cc16884ed851b0d18da06a/anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d", size = 90377 }, 92 | ] 93 | 94 | [[package]] 95 | name = "asyncio" 96 | version = "3.4.3" 97 | source = { registry = "https://pypi.org/simple" } 98 | sdist = { url = "https://files.pythonhosted.org/packages/da/54/054bafaf2c0fb8473d423743e191fcdf49b2c1fd5e9af3524efbe097bafd/asyncio-3.4.3.tar.gz", hash = "sha256:83360ff8bc97980e4ff25c964c7bd3923d333d177aa4f7fb736b019f26c7cb41", size = 204411 } 99 | wheels = [ 100 | { url = "https://files.pythonhosted.org/packages/22/74/07679c5b9f98a7cb0fc147b1ef1cc1853bc07a4eb9cb5731e24732c5f773/asyncio-3.4.3-py3-none-any.whl", hash = "sha256:c4d18b22701821de07bd6aea8b53d21449ec0ec5680645e5317062ea21817d2d", size = 101767 }, 101 | ] 102 | 103 | [[package]] 104 | name = "attrs" 105 | version = "24.2.0" 106 | source = { registry = "https://pypi.org/simple" } 107 | sdist = { url = "https://files.pythonhosted.org/packages/fc/0f/aafca9af9315aee06a89ffde799a10a582fe8de76c563ee80bbcdc08b3fb/attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346", size = 792678 } 108 | wheels = [ 109 | { url = "https://files.pythonhosted.org/packages/6a/21/5b6702a7f963e95456c0de2d495f67bf5fd62840ac655dc451586d23d39a/attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2", size = 63001 }, 110 | ] 111 | 112 | [[package]] 113 | name = "certifi" 114 | version = "2024.8.30" 115 | source = { registry = "https://pypi.org/simple" } 116 | sdist = { url = "https://files.pythonhosted.org/packages/b0/ee/9b19140fe824b367c04c5e1b369942dd754c4c5462d5674002f75c4dedc1/certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9", size = 168507 } 117 | wheels = [ 118 | { url = "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", size = 167321 }, 119 | ] 120 | 121 | [[package]] 122 | name = "click" 123 | version = "8.1.7" 124 | source = { registry = "https://pypi.org/simple" } 125 | dependencies = [ 126 | { name = "colorama", marker = "platform_system == 'Windows'" }, 127 | ] 128 | sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 } 129 | wheels = [ 130 | { url = "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", size = 97941 }, 131 | ] 132 | 133 | [[package]] 134 | name = "colorama" 135 | version = "0.4.6" 136 | source = { registry = "https://pypi.org/simple" } 137 | sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } 138 | wheels = [ 139 | { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, 140 | ] 141 | 142 | [[package]] 143 | name = "colorlog" 144 | version = "6.9.0" 145 | source = { registry = "https://pypi.org/simple" } 146 | dependencies = [ 147 | { name = "colorama", marker = "sys_platform == 'win32'" }, 148 | ] 149 | sdist = { url = "https://files.pythonhosted.org/packages/d3/7a/359f4d5df2353f26172b3cc39ea32daa39af8de522205f512f458923e677/colorlog-6.9.0.tar.gz", hash = "sha256:bfba54a1b93b94f54e1f4fe48395725a3d92fd2a4af702f6bd70946bdc0c6ac2", size = 16624 } 150 | wheels = [ 151 | { url = "https://files.pythonhosted.org/packages/e3/51/9b208e85196941db2f0654ad0357ca6388ab3ed67efdbfc799f35d1f83aa/colorlog-6.9.0-py3-none-any.whl", hash = "sha256:5906e71acd67cb07a71e779c47c4bcb45fb8c2993eebe9e5adcd6a6f1b283eff", size = 11424 }, 152 | ] 153 | 154 | [[package]] 155 | name = "distro" 156 | version = "1.9.0" 157 | source = { registry = "https://pypi.org/simple" } 158 | sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722 } 159 | wheels = [ 160 | { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277 }, 161 | ] 162 | 163 | [[package]] 164 | name = "frozenlist" 165 | version = "1.5.0" 166 | source = { registry = "https://pypi.org/simple" } 167 | sdist = { url = "https://files.pythonhosted.org/packages/8f/ed/0f4cec13a93c02c47ec32d81d11c0c1efbadf4a471e3f3ce7cad366cbbd3/frozenlist-1.5.0.tar.gz", hash = "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817", size = 39930 } 168 | wheels = [ 169 | { url = "https://files.pythonhosted.org/packages/79/73/fa6d1a96ab7fd6e6d1c3500700963eab46813847f01ef0ccbaa726181dd5/frozenlist-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:31115ba75889723431aa9a4e77d5f398f5cf976eea3bdf61749731f62d4a4a21", size = 94026 }, 170 | { url = "https://files.pythonhosted.org/packages/ab/04/ea8bf62c8868b8eada363f20ff1b647cf2e93377a7b284d36062d21d81d1/frozenlist-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7437601c4d89d070eac8323f121fcf25f88674627505334654fd027b091db09d", size = 54150 }, 171 | { url = "https://files.pythonhosted.org/packages/d0/9a/8e479b482a6f2070b26bda572c5e6889bb3ba48977e81beea35b5ae13ece/frozenlist-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7948140d9f8ece1745be806f2bfdf390127cf1a763b925c4a805c603df5e697e", size = 51927 }, 172 | { url = "https://files.pythonhosted.org/packages/e3/12/2aad87deb08a4e7ccfb33600871bbe8f0e08cb6d8224371387f3303654d7/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feeb64bc9bcc6b45c6311c9e9b99406660a9c05ca8a5b30d14a78555088b0b3a", size = 282647 }, 173 | { url = "https://files.pythonhosted.org/packages/77/f2/07f06b05d8a427ea0060a9cef6e63405ea9e0d761846b95ef3fb3be57111/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:683173d371daad49cffb8309779e886e59c2f369430ad28fe715f66d08d4ab1a", size = 289052 }, 174 | { url = "https://files.pythonhosted.org/packages/bd/9f/8bf45a2f1cd4aa401acd271b077989c9267ae8463e7c8b1eb0d3f561b65e/frozenlist-1.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d57d8f702221405a9d9b40f9da8ac2e4a1a8b5285aac6100f3393675f0a85ee", size = 291719 }, 175 | { url = "https://files.pythonhosted.org/packages/41/d1/1f20fd05a6c42d3868709b7604c9f15538a29e4f734c694c6bcfc3d3b935/frozenlist-1.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c72000fbcc35b129cb09956836c7d7abf78ab5416595e4857d1cae8d6251a6", size = 267433 }, 176 | { url = "https://files.pythonhosted.org/packages/af/f2/64b73a9bb86f5a89fb55450e97cd5c1f84a862d4ff90d9fd1a73ab0f64a5/frozenlist-1.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000a77d6034fbad9b6bb880f7ec073027908f1b40254b5d6f26210d2dab1240e", size = 283591 }, 177 | { url = "https://files.pythonhosted.org/packages/29/e2/ffbb1fae55a791fd6c2938dd9ea779509c977435ba3940b9f2e8dc9d5316/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d7f5a50342475962eb18b740f3beecc685a15b52c91f7d975257e13e029eca9", size = 273249 }, 178 | { url = "https://files.pythonhosted.org/packages/2e/6e/008136a30798bb63618a114b9321b5971172a5abddff44a100c7edc5ad4f/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:87f724d055eb4785d9be84e9ebf0f24e392ddfad00b3fe036e43f489fafc9039", size = 271075 }, 179 | { url = "https://files.pythonhosted.org/packages/ae/f0/4e71e54a026b06724cec9b6c54f0b13a4e9e298cc8db0f82ec70e151f5ce/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6e9080bb2fb195a046e5177f10d9d82b8a204c0736a97a153c2466127de87784", size = 285398 }, 180 | { url = "https://files.pythonhosted.org/packages/4d/36/70ec246851478b1c0b59f11ef8ade9c482ff447c1363c2bd5fad45098b12/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b93d7aaa36c966fa42efcaf716e6b3900438632a626fb09c049f6a2f09fc631", size = 294445 }, 181 | { url = "https://files.pythonhosted.org/packages/37/e0/47f87544055b3349b633a03c4d94b405956cf2437f4ab46d0928b74b7526/frozenlist-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:52ef692a4bc60a6dd57f507429636c2af8b6046db8b31b18dac02cbc8f507f7f", size = 280569 }, 182 | { url = "https://files.pythonhosted.org/packages/f9/7c/490133c160fb6b84ed374c266f42800e33b50c3bbab1652764e6e1fc498a/frozenlist-1.5.0-cp312-cp312-win32.whl", hash = "sha256:29d94c256679247b33a3dc96cce0f93cbc69c23bf75ff715919332fdbb6a32b8", size = 44721 }, 183 | { url = "https://files.pythonhosted.org/packages/b1/56/4e45136ffc6bdbfa68c29ca56ef53783ef4c2fd395f7cbf99a2624aa9aaa/frozenlist-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:8969190d709e7c48ea386db202d708eb94bdb29207a1f269bab1196ce0dcca1f", size = 51329 }, 184 | { url = "https://files.pythonhosted.org/packages/da/3b/915f0bca8a7ea04483622e84a9bd90033bab54bdf485479556c74fd5eaf5/frozenlist-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a1a048f9215c90973402e26c01d1cff8a209e1f1b53f72b95c13db61b00f953", size = 91538 }, 185 | { url = "https://files.pythonhosted.org/packages/c7/d1/a7c98aad7e44afe5306a2b068434a5830f1470675f0e715abb86eb15f15b/frozenlist-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dd47a5181ce5fcb463b5d9e17ecfdb02b678cca31280639255ce9d0e5aa67af0", size = 52849 }, 186 | { url = "https://files.pythonhosted.org/packages/3a/c8/76f23bf9ab15d5f760eb48701909645f686f9c64fbb8982674c241fbef14/frozenlist-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1431d60b36d15cda188ea222033eec8e0eab488f39a272461f2e6d9e1a8e63c2", size = 50583 }, 187 | { url = "https://files.pythonhosted.org/packages/1f/22/462a3dd093d11df623179d7754a3b3269de3b42de2808cddef50ee0f4f48/frozenlist-1.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6482a5851f5d72767fbd0e507e80737f9c8646ae7fd303def99bfe813f76cf7f", size = 265636 }, 188 | { url = "https://files.pythonhosted.org/packages/80/cf/e075e407fc2ae7328155a1cd7e22f932773c8073c1fc78016607d19cc3e5/frozenlist-1.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44c49271a937625619e862baacbd037a7ef86dd1ee215afc298a417ff3270608", size = 270214 }, 189 | { url = "https://files.pythonhosted.org/packages/a1/58/0642d061d5de779f39c50cbb00df49682832923f3d2ebfb0fedf02d05f7f/frozenlist-1.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12f78f98c2f1c2429d42e6a485f433722b0061d5c0b0139efa64f396efb5886b", size = 273905 }, 190 | { url = "https://files.pythonhosted.org/packages/ab/66/3fe0f5f8f2add5b4ab7aa4e199f767fd3b55da26e3ca4ce2cc36698e50c4/frozenlist-1.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce3aa154c452d2467487765e3adc730a8c153af77ad84096bc19ce19a2400840", size = 250542 }, 191 | { url = "https://files.pythonhosted.org/packages/f6/b8/260791bde9198c87a465224e0e2bb62c4e716f5d198fc3a1dacc4895dbd1/frozenlist-1.5.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b7dc0c4338e6b8b091e8faf0db3168a37101943e687f373dce00959583f7439", size = 267026 }, 192 | { url = "https://files.pythonhosted.org/packages/2e/a4/3d24f88c527f08f8d44ade24eaee83b2627793fa62fa07cbb7ff7a2f7d42/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45e0896250900b5aa25180f9aec243e84e92ac84bd4a74d9ad4138ef3f5c97de", size = 257690 }, 193 | { url = "https://files.pythonhosted.org/packages/de/9a/d311d660420b2beeff3459b6626f2ab4fb236d07afbdac034a4371fe696e/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:561eb1c9579d495fddb6da8959fd2a1fca2c6d060d4113f5844b433fc02f2641", size = 253893 }, 194 | { url = "https://files.pythonhosted.org/packages/c6/23/e491aadc25b56eabd0f18c53bb19f3cdc6de30b2129ee0bc39cd387cd560/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:df6e2f325bfee1f49f81aaac97d2aa757c7646534a06f8f577ce184afe2f0a9e", size = 267006 }, 195 | { url = "https://files.pythonhosted.org/packages/08/c4/ab918ce636a35fb974d13d666dcbe03969592aeca6c3ab3835acff01f79c/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:140228863501b44b809fb39ec56b5d4071f4d0aa6d216c19cbb08b8c5a7eadb9", size = 276157 }, 196 | { url = "https://files.pythonhosted.org/packages/c0/29/3b7a0bbbbe5a34833ba26f686aabfe982924adbdcafdc294a7a129c31688/frozenlist-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7707a25d6a77f5d27ea7dc7d1fc608aa0a478193823f88511ef5e6b8a48f9d03", size = 264642 }, 197 | { url = "https://files.pythonhosted.org/packages/ab/42/0595b3dbffc2e82d7fe658c12d5a5bafcd7516c6bf2d1d1feb5387caa9c1/frozenlist-1.5.0-cp313-cp313-win32.whl", hash = "sha256:31a9ac2b38ab9b5a8933b693db4939764ad3f299fcaa931a3e605bc3460e693c", size = 44914 }, 198 | { url = "https://files.pythonhosted.org/packages/17/c4/b7db1206a3fea44bf3b838ca61deb6f74424a8a5db1dd53ecb21da669be6/frozenlist-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:11aabdd62b8b9c4b84081a3c246506d1cddd2dd93ff0ad53ede5defec7886b28", size = 51167 }, 199 | { url = "https://files.pythonhosted.org/packages/c6/c8/a5be5b7550c10858fcf9b0ea054baccab474da77d37f1e828ce043a3a5d4/frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3", size = 11901 }, 200 | ] 201 | 202 | [[package]] 203 | name = "h11" 204 | version = "0.14.0" 205 | source = { registry = "https://pypi.org/simple" } 206 | sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 } 207 | wheels = [ 208 | { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 }, 209 | ] 210 | 211 | [[package]] 212 | name = "httpcore" 213 | version = "1.0.7" 214 | source = { registry = "https://pypi.org/simple" } 215 | dependencies = [ 216 | { name = "certifi" }, 217 | { name = "h11" }, 218 | ] 219 | sdist = { url = "https://files.pythonhosted.org/packages/6a/41/d7d0a89eb493922c37d343b607bc1b5da7f5be7e383740b4753ad8943e90/httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c", size = 85196 } 220 | wheels = [ 221 | { url = "https://files.pythonhosted.org/packages/87/f5/72347bc88306acb359581ac4d52f23c0ef445b57157adedb9aee0cd689d2/httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd", size = 78551 }, 222 | ] 223 | 224 | [[package]] 225 | name = "httpx" 226 | version = "0.28.0" 227 | source = { registry = "https://pypi.org/simple" } 228 | dependencies = [ 229 | { name = "anyio" }, 230 | { name = "certifi" }, 231 | { name = "httpcore" }, 232 | { name = "idna" }, 233 | ] 234 | sdist = { url = "https://files.pythonhosted.org/packages/10/df/676b7cf674dd1bdc71a64ad393c89879f75e4a0ab8395165b498262ae106/httpx-0.28.0.tar.gz", hash = "sha256:0858d3bab51ba7e386637f22a61d8ccddaeec5f3fe4209da3a6168dbb91573e0", size = 141307 } 235 | wheels = [ 236 | { url = "https://files.pythonhosted.org/packages/8f/fb/a19866137577ba60c6d8b69498dc36be479b13ba454f691348ddf428f185/httpx-0.28.0-py3-none-any.whl", hash = "sha256:dc0b419a0cfeb6e8b34e85167c0da2671206f5095f1baa9663d23bcfd6b535fc", size = 73551 }, 237 | ] 238 | 239 | [[package]] 240 | name = "httpx-sse" 241 | version = "0.4.0" 242 | source = { registry = "https://pypi.org/simple" } 243 | sdist = { url = "https://files.pythonhosted.org/packages/4c/60/8f4281fa9bbf3c8034fd54c0e7412e66edbab6bc74c4996bd616f8d0406e/httpx-sse-0.4.0.tar.gz", hash = "sha256:1e81a3a3070ce322add1d3529ed42eb5f70817f45ed6ec915ab753f961139721", size = 12624 } 244 | wheels = [ 245 | { url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819 }, 246 | ] 247 | 248 | [[package]] 249 | name = "idna" 250 | version = "3.10" 251 | source = { registry = "https://pypi.org/simple" } 252 | sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } 253 | wheels = [ 254 | { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, 255 | ] 256 | 257 | [[package]] 258 | name = "iniconfig" 259 | version = "2.0.0" 260 | source = { registry = "https://pypi.org/simple" } 261 | sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } 262 | wheels = [ 263 | { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, 264 | ] 265 | 266 | [[package]] 267 | name = "jiter" 268 | version = "0.8.0" 269 | source = { registry = "https://pypi.org/simple" } 270 | sdist = { url = "https://files.pythonhosted.org/packages/78/1e/3462be93c2443392a710ae1c2bba2239f44bbf0c826baea77da9f8311678/jiter-0.8.0.tar.gz", hash = "sha256:86fee98b569d4cc511ff2e3ec131354fafebd9348a487549c31ad371ae730310", size = 162953 } 271 | wheels = [ 272 | { url = "https://files.pythonhosted.org/packages/d1/63/93084c4079b30e7832e1fb907045f8eca146d5d9a67bc62d311332416ab8/jiter-0.8.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:d91a52d8f49ada2672a4b808a0c5c25d28f320a2c9ca690e30ebd561eb5a1002", size = 304424 }, 273 | { url = "https://files.pythonhosted.org/packages/d2/68/ae698958b4d7d27632056cbfeae70e9d7a89ca0954ac6d0ef486afe5d8da/jiter-0.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c38cf25cf7862f61410b7a49684d34eb3b5bcbd7ddaf4773eea40e0bd43de706", size = 309584 }, 274 | { url = "https://files.pythonhosted.org/packages/05/b3/d04a1398644c5848339c201e81d1c0d5125097bfd84fd92ebebfe724659c/jiter-0.8.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6189beb5c4b3117624be6b2e84545cff7611f5855d02de2d06ff68e316182be", size = 333677 }, 275 | { url = "https://files.pythonhosted.org/packages/41/cd/76869353a0f5a91cf544bef80a9529d090b7d4254835997507738220e133/jiter-0.8.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e13fa849c0e30643554add089983caa82f027d69fad8f50acadcb21c462244ab", size = 354157 }, 276 | { url = "https://files.pythonhosted.org/packages/34/9e/64adbc6d578a80debf7a1e81871257266e2149eede59300de7641dcd1a5e/jiter-0.8.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d7765ca159d0a58e8e0f8ca972cd6d26a33bc97b4480d0d2309856763807cd28", size = 380841 }, 277 | { url = "https://files.pythonhosted.org/packages/9d/ef/4ae8f15859d4dae10bef6d1d4a7258fc450b1f9db635becd19403d906ba4/jiter-0.8.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1b0befe7c6e9fc867d5bed21bab0131dfe27d1fa5cd52ba2bced67da33730b7d", size = 388714 }, 278 | { url = "https://files.pythonhosted.org/packages/3d/dd/3e7e3cdacda1990c1f09d9d2abdf2f37e80f8a9abd17804d61a74d8403fd/jiter-0.8.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7d6363d4c6f1052b1d8b494eb9a72667c3ef5f80ebacfe18712728e85327000", size = 341876 }, 279 | { url = "https://files.pythonhosted.org/packages/44/5b/c9533eb01eee153fd6f936e76a35583f8e244d7a5db9c2b64b4451167368/jiter-0.8.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a873e57009863eeac3e3969e4653f07031d6270d037d6224415074ac17e5505c", size = 374683 }, 280 | { url = "https://files.pythonhosted.org/packages/f8/2f/34696e31a79c1b0b30e430dfdcd7c6ee7b5fd0f5b0df4503c1b01ec9bcba/jiter-0.8.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:2582912473c0d9940791479fe1bf2976a34f212eb8e0a82ee9e645ac275c5d16", size = 512132 }, 281 | { url = "https://files.pythonhosted.org/packages/3b/b3/041d97047a30b529d5d99b3cc5d9d58fc71d9c73f106e827ba28a99058b9/jiter-0.8.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:646163201af42f55393ee6e8f6136b8df488253a6533f4230a64242ecbfe6048", size = 505039 }, 282 | { url = "https://files.pythonhosted.org/packages/59/5b/630995b058aa26e8ba9b15731b121cec9fc0e105d5ae93d2ed754a0e44f5/jiter-0.8.0-cp312-none-win32.whl", hash = "sha256:96e75c9abfbf7387cba89a324d2356d86d8897ac58c956017d062ad510832dae", size = 205267 }, 283 | { url = "https://files.pythonhosted.org/packages/1b/0e/1b79afa5616309d4e2e84980c62a3f73c4035e5b856ad7601aebbb5a7db0/jiter-0.8.0-cp312-none-win_amd64.whl", hash = "sha256:ed6074552b4a32e047b52dad5ab497223721efbd0e9efe68c67749f094a092f7", size = 206572 }, 284 | { url = "https://files.pythonhosted.org/packages/78/56/8f8ab198d9080c19f692649364d87c4a487cb8568b958aa5ce4a14379cbf/jiter-0.8.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:dd5e351cb9b3e676ec3360a85ea96def515ad2b83c8ae3a251ce84985a2c9a6f", size = 304426 }, 285 | { url = "https://files.pythonhosted.org/packages/21/bc/b4a61e32dc4702840ce5088149a91b2f9e10ad121e62ab09a49124f387c5/jiter-0.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ba9f12b0f801ecd5ed0cec29041dc425d1050922b434314c592fc30d51022467", size = 309656 }, 286 | { url = "https://files.pythonhosted.org/packages/3a/c7/e662c2ad78d3f0aa9eb91f69e004298421bb288f988baa95cab5468b3434/jiter-0.8.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7ba461c3681728d556392e8ae56fb44a550155a24905f01982317b367c21dd4", size = 333677 }, 287 | { url = "https://files.pythonhosted.org/packages/d1/c8/406bf24e38f55005daa7514d22c6c798911ba197642cac1711eb623706b6/jiter-0.8.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3a15ed47ab09576db560dbc5c2c5a64477535beb056cd7d997d5dd0f2798770e", size = 354159 }, 288 | { url = "https://files.pythonhosted.org/packages/90/33/c7813184b29ecd20f651f1e335e0814e02bc96e5cf5531ec52397362b9cd/jiter-0.8.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cef55042816d0737142b0ec056c0356a5f681fb8d6aa8499b158e87098f4c6f8", size = 380842 }, 289 | { url = "https://files.pythonhosted.org/packages/ab/db/8e0ce77a5581783710de8ce70893d3a7e3fd38c8daa506c7d2be24e95c96/jiter-0.8.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:549f170215adeb5e866f10617c3d019d8eb4e6d4e3c6b724b3b8c056514a3487", size = 388715 }, 290 | { url = "https://files.pythonhosted.org/packages/22/04/b78c51485637bc8c16594ed58300d4d60754392ee5939019d38a91426805/jiter-0.8.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f867edeb279d22020877640d2ea728de5817378c60a51be8af731a8a8f525306", size = 343333 }, 291 | { url = "https://files.pythonhosted.org/packages/49/a3/ada1efbe7dda5c911d39610a946b70b7a5d55ef5b6fe54da3d02ae95e453/jiter-0.8.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:aef8845f463093799db4464cee2aa59d61aa8edcb3762aaa4aacbec3f478c929", size = 374682 }, 292 | { url = "https://files.pythonhosted.org/packages/dc/b4/cf5bcbfeeca7af7236060cb63cf9804c386be51005f6dac0465a2269034e/jiter-0.8.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:d0d6e22e4062c3d3c1bf3594baa2f67fc9dcdda8275abad99e468e0c6540bc54", size = 512132 }, 293 | { url = "https://files.pythonhosted.org/packages/d6/9b/f759873e9b87176acd2c8301d28fbbfee7cf1b17b80e6c5c21872d7a5b4a/jiter-0.8.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:079e62e64696241ac3f408e337aaac09137ed760ccf2b72b1094b48745c13641", size = 505038 }, 294 | { url = "https://files.pythonhosted.org/packages/d1/d9/f888c4c1580516fa305b5199c136153416c51b010161f5086829df7ebbe6/jiter-0.8.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74d2b56ed3da5760544df53b5f5c39782e68efb64dc3aa0bba4cc08815e6fae8", size = 308637 }, 295 | { url = "https://files.pythonhosted.org/packages/ff/ce/09003b57df19d8645cfbd327eb0848e0c3228f2bbfc3102a79ae43287c37/jiter-0.8.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:798dafe108cba58a7bb0a50d4d5971f98bb7f3c974e1373e750de6eb21c1a329", size = 341071 }, 296 | { url = "https://files.pythonhosted.org/packages/1e/5d/fcb55694705c045aaae0b1640e3cfc3dbe20e7b2642dfb2efdcc6e32822d/jiter-0.8.0-cp313-none-win32.whl", hash = "sha256:ca6d3064dfc743eb0d3d7539d89d4ba886957c717567adc72744341c1e3573c9", size = 204830 }, 297 | { url = "https://files.pythonhosted.org/packages/08/25/60931e5b0d0ad1a17c471b9e1727421f2abe6fa7612c6716ffcacf6f70ab/jiter-0.8.0-cp313-none-win_amd64.whl", hash = "sha256:38caedda64fe1f04b06d7011fc15e86b3b837ed5088657bf778656551e3cd8f9", size = 202905 }, 298 | ] 299 | 300 | [[package]] 301 | name = "mcp" 302 | version = "1.0.0" 303 | source = { registry = "https://pypi.org/simple" } 304 | dependencies = [ 305 | { name = "anyio" }, 306 | { name = "httpx" }, 307 | { name = "httpx-sse" }, 308 | { name = "pydantic" }, 309 | { name = "sse-starlette" }, 310 | { name = "starlette" }, 311 | ] 312 | sdist = { url = "https://files.pythonhosted.org/packages/97/de/a9ec0a1b6439f90ea59f89004bb2e7ec6890dfaeef809751d9e6577dca7e/mcp-1.0.0.tar.gz", hash = "sha256:dba51ce0b5c6a80e25576f606760c49a91ee90210fed805b530ca165d3bbc9b7", size = 82891 } 313 | wheels = [ 314 | { url = "https://files.pythonhosted.org/packages/56/89/900c0c8445ec001d3725e475fc553b0feb2e8a51be018f3bb7de51e683db/mcp-1.0.0-py3-none-any.whl", hash = "sha256:bbe70ffa3341cd4da78b5eb504958355c68381fb29971471cea1e642a2af5b8a", size = 36361 }, 315 | ] 316 | 317 | [[package]] 318 | name = "mcp-llm-bridge" 319 | version = "0.1.0" 320 | source = { editable = "." } 321 | dependencies = [ 322 | { name = "aiohttp" }, 323 | { name = "asyncio" }, 324 | { name = "colorlog" }, 325 | { name = "mcp" }, 326 | { name = "openai" }, 327 | { name = "pydantic" }, 328 | { name = "python-dotenv" }, 329 | { name = "typing-extensions" }, 330 | ] 331 | 332 | [package.optional-dependencies] 333 | test = [ 334 | { name = "pytest" }, 335 | { name = "pytest-aiohttp" }, 336 | { name = "pytest-asyncio" }, 337 | { name = "pytest-mock" }, 338 | ] 339 | 340 | [package.metadata] 341 | requires-dist = [ 342 | { name = "aiohttp", specifier = ">=3.8.0" }, 343 | { name = "asyncio", specifier = ">=3.4.3" }, 344 | { name = "colorlog", specifier = ">=6.9.0" }, 345 | { name = "mcp", specifier = ">=1.0.0" }, 346 | { name = "openai", specifier = ">=1.0.0" }, 347 | { name = "pydantic", specifier = ">=2.0.0" }, 348 | { name = "pytest", marker = "extra == 'test'", specifier = ">=7.0.0" }, 349 | { name = "pytest-aiohttp", marker = "extra == 'test'", specifier = ">=1.0.4" }, 350 | { name = "pytest-asyncio", marker = "extra == 'test'", specifier = ">=0.21.0" }, 351 | { name = "pytest-mock", marker = "extra == 'test'", specifier = ">=3.10.0" }, 352 | { name = "python-dotenv", specifier = ">=0.19.0" }, 353 | { name = "typing-extensions", specifier = ">=4.0.0" }, 354 | ] 355 | 356 | [[package]] 357 | name = "multidict" 358 | version = "6.1.0" 359 | source = { registry = "https://pypi.org/simple" } 360 | sdist = { url = "https://files.pythonhosted.org/packages/d6/be/504b89a5e9ca731cd47487e91c469064f8ae5af93b7259758dcfc2b9c848/multidict-6.1.0.tar.gz", hash = "sha256:22ae2ebf9b0c69d206c003e2f6a914ea33f0a932d4aa16f236afc049d9958f4a", size = 64002 } 361 | wheels = [ 362 | { url = "https://files.pythonhosted.org/packages/fd/16/92057c74ba3b96d5e211b553895cd6dc7cc4d1e43d9ab8fafc727681ef71/multidict-6.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b04772ed465fa3cc947db808fa306d79b43e896beb677a56fb2347ca1a49c1fa", size = 48713 }, 363 | { url = "https://files.pythonhosted.org/packages/94/3d/37d1b8893ae79716179540b89fc6a0ee56b4a65fcc0d63535c6f5d96f217/multidict-6.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6180c0ae073bddeb5a97a38c03f30c233e0a4d39cd86166251617d1bbd0af436", size = 29516 }, 364 | { url = "https://files.pythonhosted.org/packages/a2/12/adb6b3200c363062f805275b4c1e656be2b3681aada66c80129932ff0bae/multidict-6.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:071120490b47aa997cca00666923a83f02c7fbb44f71cf7f136df753f7fa8761", size = 29557 }, 365 | { url = "https://files.pythonhosted.org/packages/47/e9/604bb05e6e5bce1e6a5cf80a474e0f072e80d8ac105f1b994a53e0b28c42/multidict-6.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50b3a2710631848991d0bf7de077502e8994c804bb805aeb2925a981de58ec2e", size = 130170 }, 366 | { url = "https://files.pythonhosted.org/packages/7e/13/9efa50801785eccbf7086b3c83b71a4fb501a4d43549c2f2f80b8787d69f/multidict-6.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b58c621844d55e71c1b7f7c498ce5aa6985d743a1a59034c57a905b3f153c1ef", size = 134836 }, 367 | { url = "https://files.pythonhosted.org/packages/bf/0f/93808b765192780d117814a6dfcc2e75de6dcc610009ad408b8814dca3ba/multidict-6.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55b6d90641869892caa9ca42ff913f7ff1c5ece06474fbd32fb2cf6834726c95", size = 133475 }, 368 | { url = "https://files.pythonhosted.org/packages/d3/c8/529101d7176fe7dfe1d99604e48d69c5dfdcadb4f06561f465c8ef12b4df/multidict-6.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b820514bfc0b98a30e3d85462084779900347e4d49267f747ff54060cc33925", size = 131049 }, 369 | { url = "https://files.pythonhosted.org/packages/ca/0c/fc85b439014d5a58063e19c3a158a889deec399d47b5269a0f3b6a2e28bc/multidict-6.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10a9b09aba0c5b48c53761b7c720aaaf7cf236d5fe394cd399c7ba662d5f9966", size = 120370 }, 370 | { url = "https://files.pythonhosted.org/packages/db/46/d4416eb20176492d2258fbd47b4abe729ff3b6e9c829ea4236f93c865089/multidict-6.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e16bf3e5fc9f44632affb159d30a437bfe286ce9e02754759be5536b169b305", size = 125178 }, 371 | { url = "https://files.pythonhosted.org/packages/5b/46/73697ad7ec521df7de5531a32780bbfd908ded0643cbe457f981a701457c/multidict-6.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76f364861c3bfc98cbbcbd402d83454ed9e01a5224bb3a28bf70002a230f73e2", size = 119567 }, 372 | { url = "https://files.pythonhosted.org/packages/cd/ed/51f060e2cb0e7635329fa6ff930aa5cffa17f4c7f5c6c3ddc3500708e2f2/multidict-6.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:820c661588bd01a0aa62a1283f20d2be4281b086f80dad9e955e690c75fb54a2", size = 129822 }, 373 | { url = "https://files.pythonhosted.org/packages/df/9e/ee7d1954b1331da3eddea0c4e08d9142da5f14b1321c7301f5014f49d492/multidict-6.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0e5f362e895bc5b9e67fe6e4ded2492d8124bdf817827f33c5b46c2fe3ffaca6", size = 128656 }, 374 | { url = "https://files.pythonhosted.org/packages/77/00/8538f11e3356b5d95fa4b024aa566cde7a38aa7a5f08f4912b32a037c5dc/multidict-6.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ec660d19bbc671e3a6443325f07263be452c453ac9e512f5eb935e7d4ac28b3", size = 125360 }, 375 | { url = "https://files.pythonhosted.org/packages/be/05/5d334c1f2462d43fec2363cd00b1c44c93a78c3925d952e9a71caf662e96/multidict-6.1.0-cp312-cp312-win32.whl", hash = "sha256:58130ecf8f7b8112cdb841486404f1282b9c86ccb30d3519faf301b2e5659133", size = 26382 }, 376 | { url = "https://files.pythonhosted.org/packages/a3/bf/f332a13486b1ed0496d624bcc7e8357bb8053823e8cd4b9a18edc1d97e73/multidict-6.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:188215fc0aafb8e03341995e7c4797860181562380f81ed0a87ff455b70bf1f1", size = 28529 }, 377 | { url = "https://files.pythonhosted.org/packages/22/67/1c7c0f39fe069aa4e5d794f323be24bf4d33d62d2a348acdb7991f8f30db/multidict-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d569388c381b24671589335a3be6e1d45546c2988c2ebe30fdcada8457a31008", size = 48771 }, 378 | { url = "https://files.pythonhosted.org/packages/3c/25/c186ee7b212bdf0df2519eacfb1981a017bda34392c67542c274651daf23/multidict-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:052e10d2d37810b99cc170b785945421141bf7bb7d2f8799d431e7db229c385f", size = 29533 }, 379 | { url = "https://files.pythonhosted.org/packages/67/5e/04575fd837e0958e324ca035b339cea174554f6f641d3fb2b4f2e7ff44a2/multidict-6.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f90c822a402cb865e396a504f9fc8173ef34212a342d92e362ca498cad308e28", size = 29595 }, 380 | { url = "https://files.pythonhosted.org/packages/d3/b2/e56388f86663810c07cfe4a3c3d87227f3811eeb2d08450b9e5d19d78876/multidict-6.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b225d95519a5bf73860323e633a664b0d85ad3d5bede6d30d95b35d4dfe8805b", size = 130094 }, 381 | { url = "https://files.pythonhosted.org/packages/6c/ee/30ae9b4186a644d284543d55d491fbd4239b015d36b23fea43b4c94f7052/multidict-6.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:23bfd518810af7de1116313ebd9092cb9aa629beb12f6ed631ad53356ed6b86c", size = 134876 }, 382 | { url = "https://files.pythonhosted.org/packages/84/c7/70461c13ba8ce3c779503c70ec9d0345ae84de04521c1f45a04d5f48943d/multidict-6.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c09fcfdccdd0b57867577b719c69e347a436b86cd83747f179dbf0cc0d4c1f3", size = 133500 }, 383 | { url = "https://files.pythonhosted.org/packages/4a/9f/002af221253f10f99959561123fae676148dd730e2daa2cd053846a58507/multidict-6.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf6bea52ec97e95560af5ae576bdac3aa3aae0b6758c6efa115236d9e07dae44", size = 131099 }, 384 | { url = "https://files.pythonhosted.org/packages/82/42/d1c7a7301d52af79d88548a97e297f9d99c961ad76bbe6f67442bb77f097/multidict-6.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57feec87371dbb3520da6192213c7d6fc892d5589a93db548331954de8248fd2", size = 120403 }, 385 | { url = "https://files.pythonhosted.org/packages/68/f3/471985c2c7ac707547553e8f37cff5158030d36bdec4414cb825fbaa5327/multidict-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0c3f390dc53279cbc8ba976e5f8035eab997829066756d811616b652b00a23a3", size = 125348 }, 386 | { url = "https://files.pythonhosted.org/packages/67/2c/e6df05c77e0e433c214ec1d21ddd203d9a4770a1f2866a8ca40a545869a0/multidict-6.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:59bfeae4b25ec05b34f1956eaa1cb38032282cd4dfabc5056d0a1ec4d696d3aa", size = 119673 }, 387 | { url = "https://files.pythonhosted.org/packages/c5/cd/bc8608fff06239c9fb333f9db7743a1b2eafe98c2666c9a196e867a3a0a4/multidict-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b2f59caeaf7632cc633b5cf6fc449372b83bbdf0da4ae04d5be36118e46cc0aa", size = 129927 }, 388 | { url = "https://files.pythonhosted.org/packages/44/8e/281b69b7bc84fc963a44dc6e0bbcc7150e517b91df368a27834299a526ac/multidict-6.1.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:37bb93b2178e02b7b618893990941900fd25b6b9ac0fa49931a40aecdf083fe4", size = 128711 }, 389 | { url = "https://files.pythonhosted.org/packages/12/a4/63e7cd38ed29dd9f1881d5119f272c898ca92536cdb53ffe0843197f6c85/multidict-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4e9f48f58c2c523d5a06faea47866cd35b32655c46b443f163d08c6d0ddb17d6", size = 125519 }, 390 | { url = "https://files.pythonhosted.org/packages/38/e0/4f5855037a72cd8a7a2f60a3952d9aa45feedb37ae7831642102604e8a37/multidict-6.1.0-cp313-cp313-win32.whl", hash = "sha256:3a37ffb35399029b45c6cc33640a92bef403c9fd388acce75cdc88f58bd19a81", size = 26426 }, 391 | { url = "https://files.pythonhosted.org/packages/7e/a5/17ee3a4db1e310b7405f5d25834460073a8ccd86198ce044dfaf69eac073/multidict-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:e9aa71e15d9d9beaad2c6b9319edcdc0a49a43ef5c0a4c8265ca9ee7d6c67774", size = 28531 }, 392 | { url = "https://files.pythonhosted.org/packages/99/b7/b9e70fde2c0f0c9af4cc5277782a89b66d35948ea3369ec9f598358c3ac5/multidict-6.1.0-py3-none-any.whl", hash = "sha256:48e171e52d1c4d33888e529b999e5900356b9ae588c2f09a52dcefb158b27506", size = 10051 }, 393 | ] 394 | 395 | [[package]] 396 | name = "openai" 397 | version = "1.55.3" 398 | source = { registry = "https://pypi.org/simple" } 399 | dependencies = [ 400 | { name = "anyio" }, 401 | { name = "distro" }, 402 | { name = "httpx" }, 403 | { name = "jiter" }, 404 | { name = "pydantic" }, 405 | { name = "sniffio" }, 406 | { name = "tqdm" }, 407 | { name = "typing-extensions" }, 408 | ] 409 | sdist = { url = "https://files.pythonhosted.org/packages/1e/39/d4859d897da053b61b84403f67dbef1abd075e441cb354892ff14f98e2c7/openai-1.55.3.tar.gz", hash = "sha256:547e85b94535469f137a779d8770c8c5adebd507c2cc6340ca401a7c4d5d16f0", size = 314571 } 410 | wheels = [ 411 | { url = "https://files.pythonhosted.org/packages/10/06/691ef3f0112ecf0d7420d0bf35b5d16cf81554141f4b4913a9831031013d/openai-1.55.3-py3-none-any.whl", hash = "sha256:2a235d0e1e312cd982f561b18c27692e253852f4e5fb6ccf08cb13540a9bdaa1", size = 389558 }, 412 | ] 413 | 414 | [[package]] 415 | name = "packaging" 416 | version = "24.2" 417 | source = { registry = "https://pypi.org/simple" } 418 | sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } 419 | wheels = [ 420 | { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, 421 | ] 422 | 423 | [[package]] 424 | name = "pluggy" 425 | version = "1.5.0" 426 | source = { registry = "https://pypi.org/simple" } 427 | sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } 428 | wheels = [ 429 | { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, 430 | ] 431 | 432 | [[package]] 433 | name = "propcache" 434 | version = "0.2.1" 435 | source = { registry = "https://pypi.org/simple" } 436 | sdist = { url = "https://files.pythonhosted.org/packages/20/c8/2a13f78d82211490855b2fb303b6721348d0787fdd9a12ac46d99d3acde1/propcache-0.2.1.tar.gz", hash = "sha256:3f77ce728b19cb537714499928fe800c3dda29e8d9428778fc7c186da4c09a64", size = 41735 } 437 | wheels = [ 438 | { url = "https://files.pythonhosted.org/packages/4c/28/1d205fe49be8b1b4df4c50024e62480a442b1a7b818e734308bb0d17e7fb/propcache-0.2.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:081a430aa8d5e8876c6909b67bd2d937bfd531b0382d3fdedb82612c618bc41a", size = 79588 }, 439 | { url = "https://files.pythonhosted.org/packages/21/ee/fc4d893f8d81cd4971affef2a6cb542b36617cd1d8ce56b406112cb80bf7/propcache-0.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2ccec9ac47cf4e04897619c0e0c1a48c54a71bdf045117d3a26f80d38ab1fb0", size = 45825 }, 440 | { url = "https://files.pythonhosted.org/packages/4a/de/bbe712f94d088da1d237c35d735f675e494a816fd6f54e9db2f61ef4d03f/propcache-0.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:14d86fe14b7e04fa306e0c43cdbeebe6b2c2156a0c9ce56b815faacc193e320d", size = 45357 }, 441 | { url = "https://files.pythonhosted.org/packages/7f/14/7ae06a6cf2a2f1cb382586d5a99efe66b0b3d0c6f9ac2f759e6f7af9d7cf/propcache-0.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:049324ee97bb67285b49632132db351b41e77833678432be52bdd0289c0e05e4", size = 241869 }, 442 | { url = "https://files.pythonhosted.org/packages/cc/59/227a78be960b54a41124e639e2c39e8807ac0c751c735a900e21315f8c2b/propcache-0.2.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1cd9a1d071158de1cc1c71a26014dcdfa7dd3d5f4f88c298c7f90ad6f27bb46d", size = 247884 }, 443 | { url = "https://files.pythonhosted.org/packages/84/58/f62b4ffaedf88dc1b17f04d57d8536601e4e030feb26617228ef930c3279/propcache-0.2.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98110aa363f1bb4c073e8dcfaefd3a5cea0f0834c2aab23dda657e4dab2f53b5", size = 248486 }, 444 | { url = "https://files.pythonhosted.org/packages/1c/07/ebe102777a830bca91bbb93e3479cd34c2ca5d0361b83be9dbd93104865e/propcache-0.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:647894f5ae99c4cf6bb82a1bb3a796f6e06af3caa3d32e26d2350d0e3e3faf24", size = 243649 }, 445 | { url = "https://files.pythonhosted.org/packages/ed/bc/4f7aba7f08f520376c4bb6a20b9a981a581b7f2e385fa0ec9f789bb2d362/propcache-0.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfd3223c15bebe26518d58ccf9a39b93948d3dcb3e57a20480dfdd315356baff", size = 229103 }, 446 | { url = "https://files.pythonhosted.org/packages/fe/d5/04ac9cd4e51a57a96f78795e03c5a0ddb8f23ec098b86f92de028d7f2a6b/propcache-0.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d71264a80f3fcf512eb4f18f59423fe82d6e346ee97b90625f283df56aee103f", size = 226607 }, 447 | { url = "https://files.pythonhosted.org/packages/e3/f0/24060d959ea41d7a7cc7fdbf68b31852331aabda914a0c63bdb0e22e96d6/propcache-0.2.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e73091191e4280403bde6c9a52a6999d69cdfde498f1fdf629105247599b57ec", size = 221153 }, 448 | { url = "https://files.pythonhosted.org/packages/77/a7/3ac76045a077b3e4de4859a0753010765e45749bdf53bd02bc4d372da1a0/propcache-0.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3935bfa5fede35fb202c4b569bb9c042f337ca4ff7bd540a0aa5e37131659348", size = 222151 }, 449 | { url = "https://files.pythonhosted.org/packages/e7/af/5e29da6f80cebab3f5a4dcd2a3240e7f56f2c4abf51cbfcc99be34e17f0b/propcache-0.2.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f508b0491767bb1f2b87fdfacaba5f7eddc2f867740ec69ece6d1946d29029a6", size = 233812 }, 450 | { url = "https://files.pythonhosted.org/packages/8c/89/ebe3ad52642cc5509eaa453e9f4b94b374d81bae3265c59d5c2d98efa1b4/propcache-0.2.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1672137af7c46662a1c2be1e8dc78cb6d224319aaa40271c9257d886be4363a6", size = 238829 }, 451 | { url = "https://files.pythonhosted.org/packages/e9/2f/6b32f273fa02e978b7577159eae7471b3cfb88b48563b1c2578b2d7ca0bb/propcache-0.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b74c261802d3d2b85c9df2dfb2fa81b6f90deeef63c2db9f0e029a3cac50b518", size = 230704 }, 452 | { url = "https://files.pythonhosted.org/packages/5c/2e/f40ae6ff5624a5f77edd7b8359b208b5455ea113f68309e2b00a2e1426b6/propcache-0.2.1-cp312-cp312-win32.whl", hash = "sha256:d09c333d36c1409d56a9d29b3a1b800a42c76a57a5a8907eacdbce3f18768246", size = 40050 }, 453 | { url = "https://files.pythonhosted.org/packages/3b/77/a92c3ef994e47180862b9d7d11e37624fb1c00a16d61faf55115d970628b/propcache-0.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:c214999039d4f2a5b2073ac506bba279945233da8c786e490d411dfc30f855c1", size = 44117 }, 454 | { url = "https://files.pythonhosted.org/packages/0f/2a/329e0547cf2def8857157f9477669043e75524cc3e6251cef332b3ff256f/propcache-0.2.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aca405706e0b0a44cc6bfd41fbe89919a6a56999157f6de7e182a990c36e37bc", size = 77002 }, 455 | { url = "https://files.pythonhosted.org/packages/12/2d/c4df5415e2382f840dc2ecbca0eeb2293024bc28e57a80392f2012b4708c/propcache-0.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:12d1083f001ace206fe34b6bdc2cb94be66d57a850866f0b908972f90996b3e9", size = 44639 }, 456 | { url = "https://files.pythonhosted.org/packages/d0/5a/21aaa4ea2f326edaa4e240959ac8b8386ea31dedfdaa636a3544d9e7a408/propcache-0.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d93f3307ad32a27bda2e88ec81134b823c240aa3abb55821a8da553eed8d9439", size = 44049 }, 457 | { url = "https://files.pythonhosted.org/packages/4e/3e/021b6cd86c0acc90d74784ccbb66808b0bd36067a1bf3e2deb0f3845f618/propcache-0.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba278acf14471d36316159c94a802933d10b6a1e117b8554fe0d0d9b75c9d536", size = 224819 }, 458 | { url = "https://files.pythonhosted.org/packages/3c/57/c2fdeed1b3b8918b1770a133ba5c43ad3d78e18285b0c06364861ef5cc38/propcache-0.2.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4e6281aedfca15301c41f74d7005e6e3f4ca143584ba696ac69df4f02f40d629", size = 229625 }, 459 | { url = "https://files.pythonhosted.org/packages/9d/81/70d4ff57bf2877b5780b466471bebf5892f851a7e2ca0ae7ffd728220281/propcache-0.2.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5b750a8e5a1262434fb1517ddf64b5de58327f1adc3524a5e44c2ca43305eb0b", size = 232934 }, 460 | { url = "https://files.pythonhosted.org/packages/3c/b9/bb51ea95d73b3fb4100cb95adbd4e1acaf2cbb1fd1083f5468eeb4a099a8/propcache-0.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf72af5e0fb40e9babf594308911436c8efde3cb5e75b6f206c34ad18be5c052", size = 227361 }, 461 | { url = "https://files.pythonhosted.org/packages/f1/20/3c6d696cd6fd70b29445960cc803b1851a1131e7a2e4ee261ee48e002bcd/propcache-0.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b2d0a12018b04f4cb820781ec0dffb5f7c7c1d2a5cd22bff7fb055a2cb19ebce", size = 213904 }, 462 | { url = "https://files.pythonhosted.org/packages/a1/cb/1593bfc5ac6d40c010fa823f128056d6bc25b667f5393781e37d62f12005/propcache-0.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e800776a79a5aabdb17dcc2346a7d66d0777e942e4cd251defeb084762ecd17d", size = 212632 }, 463 | { url = "https://files.pythonhosted.org/packages/6d/5c/e95617e222be14a34c709442a0ec179f3207f8a2b900273720501a70ec5e/propcache-0.2.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:4160d9283bd382fa6c0c2b5e017acc95bc183570cd70968b9202ad6d8fc48dce", size = 207897 }, 464 | { url = "https://files.pythonhosted.org/packages/8e/3b/56c5ab3dc00f6375fbcdeefdede5adf9bee94f1fab04adc8db118f0f9e25/propcache-0.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:30b43e74f1359353341a7adb783c8f1b1c676367b011709f466f42fda2045e95", size = 208118 }, 465 | { url = "https://files.pythonhosted.org/packages/86/25/d7ef738323fbc6ebcbce33eb2a19c5e07a89a3df2fded206065bd5e868a9/propcache-0.2.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:58791550b27d5488b1bb52bc96328456095d96206a250d28d874fafe11b3dfaf", size = 217851 }, 466 | { url = "https://files.pythonhosted.org/packages/b3/77/763e6cef1852cf1ba740590364ec50309b89d1c818e3256d3929eb92fabf/propcache-0.2.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:0f022d381747f0dfe27e99d928e31bc51a18b65bb9e481ae0af1380a6725dd1f", size = 222630 }, 467 | { url = "https://files.pythonhosted.org/packages/4f/e9/0f86be33602089c701696fbed8d8c4c07b6ee9605c5b7536fd27ed540c5b/propcache-0.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:297878dc9d0a334358f9b608b56d02e72899f3b8499fc6044133f0d319e2ec30", size = 216269 }, 468 | { url = "https://files.pythonhosted.org/packages/cc/02/5ac83217d522394b6a2e81a2e888167e7ca629ef6569a3f09852d6dcb01a/propcache-0.2.1-cp313-cp313-win32.whl", hash = "sha256:ddfab44e4489bd79bda09d84c430677fc7f0a4939a73d2bba3073036f487a0a6", size = 39472 }, 469 | { url = "https://files.pythonhosted.org/packages/f4/33/d6f5420252a36034bc8a3a01171bc55b4bff5df50d1c63d9caa50693662f/propcache-0.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:556fc6c10989f19a179e4321e5d678db8eb2924131e64652a51fe83e4c3db0e1", size = 43363 }, 470 | { url = "https://files.pythonhosted.org/packages/41/b6/c5319caea262f4821995dca2107483b94a3345d4607ad797c76cb9c36bcc/propcache-0.2.1-py3-none-any.whl", hash = "sha256:52277518d6aae65536e9cea52d4e7fd2f7a66f4aa2d30ed3f2fcea620ace3c54", size = 11818 }, 471 | ] 472 | 473 | [[package]] 474 | name = "pydantic" 475 | version = "2.10.2" 476 | source = { registry = "https://pypi.org/simple" } 477 | dependencies = [ 478 | { name = "annotated-types" }, 479 | { name = "pydantic-core" }, 480 | { name = "typing-extensions" }, 481 | ] 482 | sdist = { url = "https://files.pythonhosted.org/packages/41/86/a03390cb12cf64e2a8df07c267f3eb8d5035e0f9a04bb20fb79403d2a00e/pydantic-2.10.2.tar.gz", hash = "sha256:2bc2d7f17232e0841cbba4641e65ba1eb6fafb3a08de3a091ff3ce14a197c4fa", size = 785401 } 483 | wheels = [ 484 | { url = "https://files.pythonhosted.org/packages/d5/74/da832196702d0c56eb86b75bfa346db9238617e29b0b7ee3b8b4eccfe654/pydantic-2.10.2-py3-none-any.whl", hash = "sha256:cfb96e45951117c3024e6b67b25cdc33a3cb7b2fa62e239f7af1378358a1d99e", size = 456364 }, 485 | ] 486 | 487 | [[package]] 488 | name = "pydantic-core" 489 | version = "2.27.1" 490 | source = { registry = "https://pypi.org/simple" } 491 | dependencies = [ 492 | { name = "typing-extensions" }, 493 | ] 494 | sdist = { url = "https://files.pythonhosted.org/packages/a6/9f/7de1f19b6aea45aeb441838782d68352e71bfa98ee6fa048d5041991b33e/pydantic_core-2.27.1.tar.gz", hash = "sha256:62a763352879b84aa31058fc931884055fd75089cccbd9d58bb6afd01141b235", size = 412785 } 495 | wheels = [ 496 | { url = "https://files.pythonhosted.org/packages/be/51/2e9b3788feb2aebff2aa9dfbf060ec739b38c05c46847601134cc1fed2ea/pydantic_core-2.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9cbd94fc661d2bab2bc702cddd2d3370bbdcc4cd0f8f57488a81bcce90c7a54f", size = 1895239 }, 497 | { url = "https://files.pythonhosted.org/packages/7b/9e/f8063952e4a7d0127f5d1181addef9377505dcce3be224263b25c4f0bfd9/pydantic_core-2.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f8c4718cd44ec1580e180cb739713ecda2bdee1341084c1467802a417fe0f02", size = 1805070 }, 498 | { url = "https://files.pythonhosted.org/packages/2c/9d/e1d6c4561d262b52e41b17a7ef8301e2ba80b61e32e94520271029feb5d8/pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15aae984e46de8d376df515f00450d1522077254ef6b7ce189b38ecee7c9677c", size = 1828096 }, 499 | { url = "https://files.pythonhosted.org/packages/be/65/80ff46de4266560baa4332ae3181fffc4488ea7d37282da1a62d10ab89a4/pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ba5e3963344ff25fc8c40da90f44b0afca8cfd89d12964feb79ac1411a260ac", size = 1857708 }, 500 | { url = "https://files.pythonhosted.org/packages/d5/ca/3370074ad758b04d9562b12ecdb088597f4d9d13893a48a583fb47682cdf/pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:992cea5f4f3b29d6b4f7f1726ed8ee46c8331c6b4eed6db5b40134c6fe1768bb", size = 2037751 }, 501 | { url = "https://files.pythonhosted.org/packages/b1/e2/4ab72d93367194317b99d051947c071aef6e3eb95f7553eaa4208ecf9ba4/pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0325336f348dbee6550d129b1627cb8f5351a9dc91aad141ffb96d4937bd9529", size = 2733863 }, 502 | { url = "https://files.pythonhosted.org/packages/8a/c6/8ae0831bf77f356bb73127ce5a95fe115b10f820ea480abbd72d3cc7ccf3/pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7597c07fbd11515f654d6ece3d0e4e5093edc30a436c63142d9a4b8e22f19c35", size = 2161161 }, 503 | { url = "https://files.pythonhosted.org/packages/f1/f4/b2fe73241da2429400fc27ddeaa43e35562f96cf5b67499b2de52b528cad/pydantic_core-2.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3bbd5d8cc692616d5ef6fbbbd50dbec142c7e6ad9beb66b78a96e9c16729b089", size = 1993294 }, 504 | { url = "https://files.pythonhosted.org/packages/77/29/4bb008823a7f4cc05828198153f9753b3bd4c104d93b8e0b1bfe4e187540/pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:dc61505e73298a84a2f317255fcc72b710b72980f3a1f670447a21efc88f8381", size = 2001468 }, 505 | { url = "https://files.pythonhosted.org/packages/f2/a9/0eaceeba41b9fad851a4107e0cf999a34ae8f0d0d1f829e2574f3d8897b0/pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:e1f735dc43da318cad19b4173dd1ffce1d84aafd6c9b782b3abc04a0d5a6f5bb", size = 2091413 }, 506 | { url = "https://files.pythonhosted.org/packages/d8/36/eb8697729725bc610fd73940f0d860d791dc2ad557faaefcbb3edbd2b349/pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f4e5658dbffe8843a0f12366a4c2d1c316dbe09bb4dfbdc9d2d9cd6031de8aae", size = 2154735 }, 507 | { url = "https://files.pythonhosted.org/packages/52/e5/4f0fbd5c5995cc70d3afed1b5c754055bb67908f55b5cb8000f7112749bf/pydantic_core-2.27.1-cp312-none-win32.whl", hash = "sha256:672ebbe820bb37988c4d136eca2652ee114992d5d41c7e4858cdd90ea94ffe5c", size = 1833633 }, 508 | { url = "https://files.pythonhosted.org/packages/ee/f2/c61486eee27cae5ac781305658779b4a6b45f9cc9d02c90cb21b940e82cc/pydantic_core-2.27.1-cp312-none-win_amd64.whl", hash = "sha256:66ff044fd0bb1768688aecbe28b6190f6e799349221fb0de0e6f4048eca14c16", size = 1986973 }, 509 | { url = "https://files.pythonhosted.org/packages/df/a6/e3f12ff25f250b02f7c51be89a294689d175ac76e1096c32bf278f29ca1e/pydantic_core-2.27.1-cp312-none-win_arm64.whl", hash = "sha256:9a3b0793b1bbfd4146304e23d90045f2a9b5fd5823aa682665fbdaf2a6c28f3e", size = 1883215 }, 510 | { url = "https://files.pythonhosted.org/packages/0f/d6/91cb99a3c59d7b072bded9959fbeab0a9613d5a4935773c0801f1764c156/pydantic_core-2.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f216dbce0e60e4d03e0c4353c7023b202d95cbaeff12e5fd2e82ea0a66905073", size = 1895033 }, 511 | { url = "https://files.pythonhosted.org/packages/07/42/d35033f81a28b27dedcade9e967e8a40981a765795c9ebae2045bcef05d3/pydantic_core-2.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a2e02889071850bbfd36b56fd6bc98945e23670773bc7a76657e90e6b6603c08", size = 1807542 }, 512 | { url = "https://files.pythonhosted.org/packages/41/c2/491b59e222ec7e72236e512108ecad532c7f4391a14e971c963f624f7569/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42b0e23f119b2b456d07ca91b307ae167cc3f6c846a7b169fca5326e32fdc6cf", size = 1827854 }, 513 | { url = "https://files.pythonhosted.org/packages/e3/f3/363652651779113189cefdbbb619b7b07b7a67ebb6840325117cc8cc3460/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:764be71193f87d460a03f1f7385a82e226639732214b402f9aa61f0d025f0737", size = 1857389 }, 514 | { url = "https://files.pythonhosted.org/packages/5f/97/be804aed6b479af5a945daec7538d8bf358d668bdadde4c7888a2506bdfb/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c00666a3bd2f84920a4e94434f5974d7bbc57e461318d6bb34ce9cdbbc1f6b2", size = 2037934 }, 515 | { url = "https://files.pythonhosted.org/packages/42/01/295f0bd4abf58902917e342ddfe5f76cf66ffabfc57c2e23c7681a1a1197/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ccaa88b24eebc0f849ce0a4d09e8a408ec5a94afff395eb69baf868f5183107", size = 2735176 }, 516 | { url = "https://files.pythonhosted.org/packages/9d/a0/cd8e9c940ead89cc37812a1a9f310fef59ba2f0b22b4e417d84ab09fa970/pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c65af9088ac534313e1963443d0ec360bb2b9cba6c2909478d22c2e363d98a51", size = 2160720 }, 517 | { url = "https://files.pythonhosted.org/packages/73/ae/9d0980e286627e0aeca4c352a60bd760331622c12d576e5ea4441ac7e15e/pydantic_core-2.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:206b5cf6f0c513baffaeae7bd817717140770c74528f3e4c3e1cec7871ddd61a", size = 1992972 }, 518 | { url = "https://files.pythonhosted.org/packages/bf/ba/ae4480bc0292d54b85cfb954e9d6bd226982949f8316338677d56541b85f/pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:062f60e512fc7fff8b8a9d680ff0ddaaef0193dba9fa83e679c0c5f5fbd018bc", size = 2001477 }, 519 | { url = "https://files.pythonhosted.org/packages/55/b7/e26adf48c2f943092ce54ae14c3c08d0d221ad34ce80b18a50de8ed2cba8/pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:a0697803ed7d4af5e4c1adf1670af078f8fcab7a86350e969f454daf598c4960", size = 2091186 }, 520 | { url = "https://files.pythonhosted.org/packages/ba/cc/8491fff5b608b3862eb36e7d29d36a1af1c945463ca4c5040bf46cc73f40/pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:58ca98a950171f3151c603aeea9303ef6c235f692fe555e883591103da709b23", size = 2154429 }, 521 | { url = "https://files.pythonhosted.org/packages/78/d8/c080592d80edd3441ab7f88f865f51dae94a157fc64283c680e9f32cf6da/pydantic_core-2.27.1-cp313-none-win32.whl", hash = "sha256:8065914ff79f7eab1599bd80406681f0ad08f8e47c880f17b416c9f8f7a26d05", size = 1833713 }, 522 | { url = "https://files.pythonhosted.org/packages/83/84/5ab82a9ee2538ac95a66e51f6838d6aba6e0a03a42aa185ad2fe404a4e8f/pydantic_core-2.27.1-cp313-none-win_amd64.whl", hash = "sha256:ba630d5e3db74c79300d9a5bdaaf6200172b107f263c98a0539eeecb857b2337", size = 1987897 }, 523 | { url = "https://files.pythonhosted.org/packages/df/c3/b15fb833926d91d982fde29c0624c9f225da743c7af801dace0d4e187e71/pydantic_core-2.27.1-cp313-none-win_arm64.whl", hash = "sha256:45cf8588c066860b623cd11c4ba687f8d7175d5f7ef65f7129df8a394c502de5", size = 1882983 }, 524 | ] 525 | 526 | [[package]] 527 | name = "pytest" 528 | version = "8.3.4" 529 | source = { registry = "https://pypi.org/simple" } 530 | dependencies = [ 531 | { name = "colorama", marker = "sys_platform == 'win32'" }, 532 | { name = "iniconfig" }, 533 | { name = "packaging" }, 534 | { name = "pluggy" }, 535 | ] 536 | sdist = { url = "https://files.pythonhosted.org/packages/05/35/30e0d83068951d90a01852cb1cef56e5d8a09d20c7f511634cc2f7e0372a/pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761", size = 1445919 } 537 | wheels = [ 538 | { url = "https://files.pythonhosted.org/packages/11/92/76a1c94d3afee238333bc0a42b82935dd8f9cf8ce9e336ff87ee14d9e1cf/pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6", size = 343083 }, 539 | ] 540 | 541 | [[package]] 542 | name = "pytest-aiohttp" 543 | version = "1.0.5" 544 | source = { registry = "https://pypi.org/simple" } 545 | dependencies = [ 546 | { name = "aiohttp" }, 547 | { name = "pytest" }, 548 | { name = "pytest-asyncio" }, 549 | ] 550 | sdist = { url = "https://files.pythonhosted.org/packages/28/ad/7915ae42ca364a66708755517c5d669a7a4921d70d1070d3b660ea716a3e/pytest-aiohttp-1.0.5.tar.gz", hash = "sha256:880262bc5951e934463b15e3af8bb298f11f7d4d3ebac970aab425aff10a780a", size = 12209 } 551 | wheels = [ 552 | { url = "https://files.pythonhosted.org/packages/9a/a7/6e50ba2c0a27a34859a952162e63362a13142ce3c646e925b76de440e102/pytest_aiohttp-1.0.5-py3-none-any.whl", hash = "sha256:63a5360fd2f34dda4ab8e6baee4c5f5be4cd186a403cabd498fced82ac9c561e", size = 8547 }, 553 | ] 554 | 555 | [[package]] 556 | name = "pytest-asyncio" 557 | version = "0.24.0" 558 | source = { registry = "https://pypi.org/simple" } 559 | dependencies = [ 560 | { name = "pytest" }, 561 | ] 562 | sdist = { url = "https://files.pythonhosted.org/packages/52/6d/c6cf50ce320cf8611df7a1254d86233b3df7cc07f9b5f5cbcb82e08aa534/pytest_asyncio-0.24.0.tar.gz", hash = "sha256:d081d828e576d85f875399194281e92bf8a68d60d72d1a2faf2feddb6c46b276", size = 49855 } 563 | wheels = [ 564 | { url = "https://files.pythonhosted.org/packages/96/31/6607dab48616902f76885dfcf62c08d929796fc3b2d2318faf9fd54dbed9/pytest_asyncio-0.24.0-py3-none-any.whl", hash = "sha256:a811296ed596b69bf0b6f3dc40f83bcaf341b155a269052d82efa2b25ac7037b", size = 18024 }, 565 | ] 566 | 567 | [[package]] 568 | name = "pytest-mock" 569 | version = "3.14.0" 570 | source = { registry = "https://pypi.org/simple" } 571 | dependencies = [ 572 | { name = "pytest" }, 573 | ] 574 | sdist = { url = "https://files.pythonhosted.org/packages/c6/90/a955c3ab35ccd41ad4de556596fa86685bf4fc5ffcc62d22d856cfd4e29a/pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0", size = 32814 } 575 | wheels = [ 576 | { url = "https://files.pythonhosted.org/packages/f2/3b/b26f90f74e2986a82df6e7ac7e319b8ea7ccece1caec9f8ab6104dc70603/pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f", size = 9863 }, 577 | ] 578 | 579 | [[package]] 580 | name = "python-dotenv" 581 | version = "1.0.1" 582 | source = { registry = "https://pypi.org/simple" } 583 | sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115 } 584 | wheels = [ 585 | { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 }, 586 | ] 587 | 588 | [[package]] 589 | name = "sniffio" 590 | version = "1.3.1" 591 | source = { registry = "https://pypi.org/simple" } 592 | sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } 593 | wheels = [ 594 | { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, 595 | ] 596 | 597 | [[package]] 598 | name = "sse-starlette" 599 | version = "2.1.3" 600 | source = { registry = "https://pypi.org/simple" } 601 | dependencies = [ 602 | { name = "anyio" }, 603 | { name = "starlette" }, 604 | { name = "uvicorn" }, 605 | ] 606 | sdist = { url = "https://files.pythonhosted.org/packages/72/fc/56ab9f116b2133521f532fce8d03194cf04dcac25f583cf3d839be4c0496/sse_starlette-2.1.3.tar.gz", hash = "sha256:9cd27eb35319e1414e3d2558ee7414487f9529ce3b3cf9b21434fd110e017169", size = 19678 } 607 | wheels = [ 608 | { url = "https://files.pythonhosted.org/packages/52/aa/36b271bc4fa1d2796311ee7c7283a3a1c348bad426d37293609ca4300eef/sse_starlette-2.1.3-py3-none-any.whl", hash = "sha256:8ec846438b4665b9e8c560fcdea6bc8081a3abf7942faa95e5a744999d219772", size = 9383 }, 609 | ] 610 | 611 | [[package]] 612 | name = "starlette" 613 | version = "0.41.3" 614 | source = { registry = "https://pypi.org/simple" } 615 | dependencies = [ 616 | { name = "anyio" }, 617 | ] 618 | sdist = { url = "https://files.pythonhosted.org/packages/1a/4c/9b5764bd22eec91c4039ef4c55334e9187085da2d8a2df7bd570869aae18/starlette-0.41.3.tar.gz", hash = "sha256:0e4ab3d16522a255be6b28260b938eae2482f98ce5cc934cb08dce8dc3ba5835", size = 2574159 } 619 | wheels = [ 620 | { url = "https://files.pythonhosted.org/packages/96/00/2b325970b3060c7cecebab6d295afe763365822b1306a12eeab198f74323/starlette-0.41.3-py3-none-any.whl", hash = "sha256:44cedb2b7c77a9de33a8b74b2b90e9f50d11fcf25d8270ea525ad71a25374ff7", size = 73225 }, 621 | ] 622 | 623 | [[package]] 624 | name = "tqdm" 625 | version = "4.67.1" 626 | source = { registry = "https://pypi.org/simple" } 627 | dependencies = [ 628 | { name = "colorama", marker = "platform_system == 'Windows'" }, 629 | ] 630 | sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737 } 631 | wheels = [ 632 | { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540 }, 633 | ] 634 | 635 | [[package]] 636 | name = "typing-extensions" 637 | version = "4.12.2" 638 | source = { registry = "https://pypi.org/simple" } 639 | sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } 640 | wheels = [ 641 | { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, 642 | ] 643 | 644 | [[package]] 645 | name = "uvicorn" 646 | version = "0.32.1" 647 | source = { registry = "https://pypi.org/simple" } 648 | dependencies = [ 649 | { name = "click" }, 650 | { name = "h11" }, 651 | ] 652 | sdist = { url = "https://files.pythonhosted.org/packages/6a/3c/21dba3e7d76138725ef307e3d7ddd29b763119b3aa459d02cc05fefcff75/uvicorn-0.32.1.tar.gz", hash = "sha256:ee9519c246a72b1c084cea8d3b44ed6026e78a4a309cbedae9c37e4cb9fbb175", size = 77630 } 653 | wheels = [ 654 | { url = "https://files.pythonhosted.org/packages/50/c1/2d27b0a15826c2b71dcf6e2f5402181ef85acf439617bb2f1453125ce1f3/uvicorn-0.32.1-py3-none-any.whl", hash = "sha256:82ad92fd58da0d12af7482ecdb5f2470a04c9c9a53ced65b9bbb4a205377602e", size = 63828 }, 655 | ] 656 | 657 | [[package]] 658 | name = "yarl" 659 | version = "1.18.3" 660 | source = { registry = "https://pypi.org/simple" } 661 | dependencies = [ 662 | { name = "idna" }, 663 | { name = "multidict" }, 664 | { name = "propcache" }, 665 | ] 666 | sdist = { url = "https://files.pythonhosted.org/packages/b7/9d/4b94a8e6d2b51b599516a5cb88e5bc99b4d8d4583e468057eaa29d5f0918/yarl-1.18.3.tar.gz", hash = "sha256:ac1801c45cbf77b6c99242eeff4fffb5e4e73a800b5c4ad4fc0be5def634d2e1", size = 181062 } 667 | wheels = [ 668 | { url = "https://files.pythonhosted.org/packages/33/85/bd2e2729752ff4c77338e0102914897512e92496375e079ce0150a6dc306/yarl-1.18.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1dd4bdd05407ced96fed3d7f25dbbf88d2ffb045a0db60dbc247f5b3c5c25d50", size = 142644 }, 669 | { url = "https://files.pythonhosted.org/packages/ff/74/1178322cc0f10288d7eefa6e4a85d8d2e28187ccab13d5b844e8b5d7c88d/yarl-1.18.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7c33dd1931a95e5d9a772d0ac5e44cac8957eaf58e3c8da8c1414de7dd27c576", size = 94962 }, 670 | { url = "https://files.pythonhosted.org/packages/be/75/79c6acc0261e2c2ae8a1c41cf12265e91628c8c58ae91f5ff59e29c0787f/yarl-1.18.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25b411eddcfd56a2f0cd6a384e9f4f7aa3efee14b188de13048c25b5e91f1640", size = 92795 }, 671 | { url = "https://files.pythonhosted.org/packages/6b/32/927b2d67a412c31199e83fefdce6e645247b4fb164aa1ecb35a0f9eb2058/yarl-1.18.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:436c4fc0a4d66b2badc6c5fc5ef4e47bb10e4fd9bf0c79524ac719a01f3607c2", size = 332368 }, 672 | { url = "https://files.pythonhosted.org/packages/19/e5/859fca07169d6eceeaa4fde1997c91d8abde4e9a7c018e371640c2da2b71/yarl-1.18.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e35ef8683211db69ffe129a25d5634319a677570ab6b2eba4afa860f54eeaf75", size = 342314 }, 673 | { url = "https://files.pythonhosted.org/packages/08/75/76b63ccd91c9e03ab213ef27ae6add2e3400e77e5cdddf8ed2dbc36e3f21/yarl-1.18.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84b2deecba4a3f1a398df819151eb72d29bfeb3b69abb145a00ddc8d30094512", size = 341987 }, 674 | { url = "https://files.pythonhosted.org/packages/1a/e1/a097d5755d3ea8479a42856f51d97eeff7a3a7160593332d98f2709b3580/yarl-1.18.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e5a1fea0fd4f5bfa7440a47eff01d9822a65b4488f7cff83155a0f31a2ecba", size = 336914 }, 675 | { url = "https://files.pythonhosted.org/packages/0b/42/e1b4d0e396b7987feceebe565286c27bc085bf07d61a59508cdaf2d45e63/yarl-1.18.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0e883008013c0e4aef84dcfe2a0b172c4d23c2669412cf5b3371003941f72bb", size = 325765 }, 676 | { url = "https://files.pythonhosted.org/packages/7e/18/03a5834ccc9177f97ca1bbb245b93c13e58e8225276f01eedc4cc98ab820/yarl-1.18.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5a3f356548e34a70b0172d8890006c37be92995f62d95a07b4a42e90fba54272", size = 344444 }, 677 | { url = "https://files.pythonhosted.org/packages/c8/03/a713633bdde0640b0472aa197b5b86e90fbc4c5bc05b727b714cd8a40e6d/yarl-1.18.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ccd17349166b1bee6e529b4add61727d3f55edb7babbe4069b5764c9587a8cc6", size = 340760 }, 678 | { url = "https://files.pythonhosted.org/packages/eb/99/f6567e3f3bbad8fd101886ea0276c68ecb86a2b58be0f64077396cd4b95e/yarl-1.18.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b958ddd075ddba5b09bb0be8a6d9906d2ce933aee81100db289badbeb966f54e", size = 346484 }, 679 | { url = "https://files.pythonhosted.org/packages/8e/a9/84717c896b2fc6cb15bd4eecd64e34a2f0a9fd6669e69170c73a8b46795a/yarl-1.18.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c7d79f7d9aabd6011004e33b22bc13056a3e3fb54794d138af57f5ee9d9032cb", size = 359864 }, 680 | { url = "https://files.pythonhosted.org/packages/1e/2e/d0f5f1bef7ee93ed17e739ec8dbcb47794af891f7d165fa6014517b48169/yarl-1.18.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4891ed92157e5430874dad17b15eb1fda57627710756c27422200c52d8a4e393", size = 364537 }, 681 | { url = "https://files.pythonhosted.org/packages/97/8a/568d07c5d4964da5b02621a517532adb8ec5ba181ad1687191fffeda0ab6/yarl-1.18.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ce1af883b94304f493698b00d0f006d56aea98aeb49d75ec7d98cd4a777e9285", size = 357861 }, 682 | { url = "https://files.pythonhosted.org/packages/7d/e3/924c3f64b6b3077889df9a1ece1ed8947e7b61b0a933f2ec93041990a677/yarl-1.18.3-cp312-cp312-win32.whl", hash = "sha256:f91c4803173928a25e1a55b943c81f55b8872f0018be83e3ad4938adffb77dd2", size = 84097 }, 683 | { url = "https://files.pythonhosted.org/packages/34/45/0e055320daaabfc169b21ff6174567b2c910c45617b0d79c68d7ab349b02/yarl-1.18.3-cp312-cp312-win_amd64.whl", hash = "sha256:7e2ee16578af3b52ac2f334c3b1f92262f47e02cc6193c598502bd46f5cd1477", size = 90399 }, 684 | { url = "https://files.pythonhosted.org/packages/30/c7/c790513d5328a8390be8f47be5d52e141f78b66c6c48f48d241ca6bd5265/yarl-1.18.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:90adb47ad432332d4f0bc28f83a5963f426ce9a1a8809f5e584e704b82685dcb", size = 140789 }, 685 | { url = "https://files.pythonhosted.org/packages/30/aa/a2f84e93554a578463e2edaaf2300faa61c8701f0898725842c704ba5444/yarl-1.18.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:913829534200eb0f789d45349e55203a091f45c37a2674678744ae52fae23efa", size = 94144 }, 686 | { url = "https://files.pythonhosted.org/packages/c6/fc/d68d8f83714b221a85ce7866832cba36d7c04a68fa6a960b908c2c84f325/yarl-1.18.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ef9f7768395923c3039055c14334ba4d926f3baf7b776c923c93d80195624782", size = 91974 }, 687 | { url = "https://files.pythonhosted.org/packages/56/4e/d2563d8323a7e9a414b5b25341b3942af5902a2263d36d20fb17c40411e2/yarl-1.18.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a19f62ff30117e706ebc9090b8ecc79aeb77d0b1f5ec10d2d27a12bc9f66d0", size = 333587 }, 688 | { url = "https://files.pythonhosted.org/packages/25/c9/cfec0bc0cac8d054be223e9f2c7909d3e8442a856af9dbce7e3442a8ec8d/yarl-1.18.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e17c9361d46a4d5addf777c6dd5eab0715a7684c2f11b88c67ac37edfba6c482", size = 344386 }, 689 | { url = "https://files.pythonhosted.org/packages/ab/5d/4c532190113b25f1364d25f4c319322e86232d69175b91f27e3ebc2caf9a/yarl-1.18.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a74a13a4c857a84a845505fd2d68e54826a2cd01935a96efb1e9d86c728e186", size = 345421 }, 690 | { url = "https://files.pythonhosted.org/packages/23/d1/6cdd1632da013aa6ba18cee4d750d953104a5e7aac44e249d9410a972bf5/yarl-1.18.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41f7ce59d6ee7741af71d82020346af364949314ed3d87553763a2df1829cc58", size = 339384 }, 691 | { url = "https://files.pythonhosted.org/packages/9a/c4/6b3c39bec352e441bd30f432cda6ba51681ab19bb8abe023f0d19777aad1/yarl-1.18.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f52a265001d830bc425f82ca9eabda94a64a4d753b07d623a9f2863fde532b53", size = 326689 }, 692 | { url = "https://files.pythonhosted.org/packages/23/30/07fb088f2eefdc0aa4fc1af4e3ca4eb1a3aadd1ce7d866d74c0f124e6a85/yarl-1.18.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:82123d0c954dc58db301f5021a01854a85bf1f3bb7d12ae0c01afc414a882ca2", size = 345453 }, 693 | { url = "https://files.pythonhosted.org/packages/63/09/d54befb48f9cd8eec43797f624ec37783a0266855f4930a91e3d5c7717f8/yarl-1.18.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2ec9bbba33b2d00999af4631a3397d1fd78290c48e2a3e52d8dd72db3a067ac8", size = 341872 }, 694 | { url = "https://files.pythonhosted.org/packages/91/26/fd0ef9bf29dd906a84b59f0cd1281e65b0c3e08c6aa94b57f7d11f593518/yarl-1.18.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fbd6748e8ab9b41171bb95c6142faf068f5ef1511935a0aa07025438dd9a9bc1", size = 347497 }, 695 | { url = "https://files.pythonhosted.org/packages/d9/b5/14ac7a256d0511b2ac168d50d4b7d744aea1c1aa20c79f620d1059aab8b2/yarl-1.18.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:877d209b6aebeb5b16c42cbb377f5f94d9e556626b1bfff66d7b0d115be88d0a", size = 359981 }, 696 | { url = "https://files.pythonhosted.org/packages/ca/b3/d493221ad5cbd18bc07e642894030437e405e1413c4236dd5db6e46bcec9/yarl-1.18.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b464c4ab4bfcb41e3bfd3f1c26600d038376c2de3297760dfe064d2cb7ea8e10", size = 366229 }, 697 | { url = "https://files.pythonhosted.org/packages/04/56/6a3e2a5d9152c56c346df9b8fb8edd2c8888b1e03f96324d457e5cf06d34/yarl-1.18.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8d39d351e7faf01483cc7ff7c0213c412e38e5a340238826be7e0e4da450fdc8", size = 360383 }, 698 | { url = "https://files.pythonhosted.org/packages/fd/b7/4b3c7c7913a278d445cc6284e59b2e62fa25e72758f888b7a7a39eb8423f/yarl-1.18.3-cp313-cp313-win32.whl", hash = "sha256:61ee62ead9b68b9123ec24bc866cbef297dd266175d53296e2db5e7f797f902d", size = 310152 }, 699 | { url = "https://files.pythonhosted.org/packages/f5/d5/688db678e987c3e0fb17867970700b92603cadf36c56e5fb08f23e822a0c/yarl-1.18.3-cp313-cp313-win_amd64.whl", hash = "sha256:578e281c393af575879990861823ef19d66e2b1d0098414855dd367e234f5b3c", size = 315723 }, 700 | { url = "https://files.pythonhosted.org/packages/f5/4b/a06e0ec3d155924f77835ed2d167ebd3b211a7b0853da1cf8d8414d784ef/yarl-1.18.3-py3-none-any.whl", hash = "sha256:b57f4f58099328dfb26c6a771d09fb20dbbae81d20cfb66141251ea063bd101b", size = 45109 }, 701 | ] 702 | --------------------------------------------------------------------------------