From 856b8770e1a4b6db45235927a7940060d6feaf0a Mon Sep 17 00:00:00 2001 From: Sydney Runkle Date: Thu, 16 Oct 2025 23:44:30 -0400 Subject: [PATCH] initial skills middleware --- .../middleware/__init__.py | 8 + .../langchain_anthropic/middleware/skills.py | 572 ++++++++++++++ .../unit_tests/middleware/test_skills.py | 744 ++++++++++++++++++ libs/partners/anthropic/uv.lock | 207 +++-- 4 files changed, 1414 insertions(+), 117 deletions(-) create mode 100644 libs/partners/anthropic/langchain_anthropic/middleware/skills.py create mode 100644 libs/partners/anthropic/tests/unit_tests/middleware/test_skills.py diff --git a/libs/partners/anthropic/langchain_anthropic/middleware/__init__.py b/libs/partners/anthropic/langchain_anthropic/middleware/__init__.py index d1a34993c8de0..f5716c766e00e 100644 --- a/libs/partners/anthropic/langchain_anthropic/middleware/__init__.py +++ b/libs/partners/anthropic/langchain_anthropic/middleware/__init__.py @@ -13,12 +13,20 @@ from langchain_anthropic.middleware.prompt_caching import ( AnthropicPromptCachingMiddleware, ) +from langchain_anthropic.middleware.skills import ( + ClaudeSkillsMiddleware, + LocalSkillConfig, + SkillConfig, +) __all__ = [ "AnthropicPromptCachingMiddleware", "ClaudeBashToolMiddleware", + "ClaudeSkillsMiddleware", "FilesystemClaudeMemoryMiddleware", "FilesystemClaudeTextEditorMiddleware", + "LocalSkillConfig", + "SkillConfig", "StateClaudeMemoryMiddleware", "StateClaudeTextEditorMiddleware", "StateFileSearchMiddleware", diff --git a/libs/partners/anthropic/langchain_anthropic/middleware/skills.py b/libs/partners/anthropic/langchain_anthropic/middleware/skills.py new file mode 100644 index 0000000000000..5c6f8365a5830 --- /dev/null +++ b/libs/partners/anthropic/langchain_anthropic/middleware/skills.py @@ -0,0 +1,572 @@ +"""Anthropic Skills middleware. + +Requires: + - langchain: For agent middleware framework + - langchain-anthropic: For ChatAnthropic model (already a dependency) +""" + +import hashlib +import os +import zipfile +from collections.abc import Awaitable, Callable +from io import BytesIO +from pathlib import Path +from typing import Any, Literal +from warnings import warn + +from langchain_anthropic.chat_models import ChatAnthropic + +try: + from langchain.agents.middleware.types import ( + AgentMiddleware, + ModelCallResult, + ModelRequest, + ModelResponse, + ) +except ImportError as e: + msg = ( + "ClaudeSkillsMiddleware requires 'langchain' to be installed. " + "This middleware is designed for use with LangChain agents. " + "Install it with: pip install langchain" + ) + raise ImportError(msg) from e + + +# Pre-built Anthropic skill IDs +ANTHROPIC_SKILLS = {"pptx", "xlsx", "docx", "pdf"} + +# Required beta headers for Skills functionality +REQUIRED_BETAS = [ + "code-execution-2025-08-25", + "skills-2025-10-02", + "files-api-2025-04-14", +] + + +class SkillConfig: + """Configuration for a single skill. + + Args: + skill_id: The skill identifier. For Anthropic pre-built skills, use + `'pptx'`, `'xlsx'`, `'docx'`, or `'pdf'`. For custom skills, use + the ID from the `/v1/skills` endpoint. + type: The skill type. `'anthropic'` for pre-built skills or `'custom'` + for user-uploaded skills. + version: The skill version. Use `'latest'` for the most recent version, + or specify a date-based version for Anthropic skills (e.g., `'20251002'`) + or epoch timestamp for custom skills. + """ + + def __init__( + self, + skill_id: str, + type: Literal["anthropic", "custom"] = "anthropic", # noqa: A002 + version: str = "latest", + ) -> None: + """Initialize skill configuration.""" + self.skill_id = skill_id + self.type = type + self.version = version + + def to_dict(self) -> dict[str, str]: + """Convert skill configuration to API format. + + Returns: + Dictionary with skill configuration for API requests. + """ + return { + "type": self.type, + "skill_id": self.skill_id, + "version": self.version, + } + + +class LocalSkillConfig(SkillConfig): + """Configuration for a skill loaded from local files. + + This class handles loading skills from the local filesystem and uploading + them to the Anthropic API. Skills are expected to be in a directory containing + a `SKILL.md` file with YAML frontmatter. + + Args: + path: Path to the skill directory or zip file containing the skill. + The directory must contain a `SKILL.md` file with required frontmatter. + display_title: Optional display title for the skill when uploaded. + If not provided, the name from the SKILL.md frontmatter will be used. + auto_upload: If `True`, automatically upload the skill when the middleware + is initialized. If `False`, you must manually upload via the + `upload_skill` method. + + Example: + ```python + from langchain_anthropic.middleware import ( + ClaudeSkillsMiddleware, + LocalSkillConfig, + ) + + # Create middleware with local skill + middleware = ClaudeSkillsMiddleware( + skills=[ + "pptx", # Pre-built skill + LocalSkillConfig(path="./my-custom-skill"), # Local skill + ] + ) + ``` + """ + + def __init__( + self, + path: str | Path, + *, + display_title: str | None = None, + auto_upload: bool = True, + ) -> None: + """Initialize local skill configuration. + + Args: + path: Path to the skill directory or zip file. + display_title: Optional display title for the skill. + auto_upload: Whether to automatically upload the skill. + + Raises: + FileNotFoundError: If the path does not exist. + ValueError: If the path is not a valid skill directory or zip file. + """ + self.path = Path(path) + self.display_title = display_title + self.auto_upload = auto_upload + self._uploaded_skill_id: str | None = None + self._content_hash: str | None = None + + # Validate path exists + if not self.path.exists(): + msg = f"Skill path does not exist: {self.path}" + raise FileNotFoundError(msg) + + # Validate it's a directory or zip file + if not (self.path.is_dir() or self.path.suffix == ".zip"): + msg = f"Skill path must be a directory or .zip file, got: {self.path}" + raise ValueError(msg) + + # For directories, validate SKILL.md exists + if self.path.is_dir(): + skill_md = self.path / "SKILL.md" + if not skill_md.exists(): + msg = f"Skill directory must contain a SKILL.md file: {self.path}" + raise ValueError(msg) + + # Initialize parent class with placeholder values + # These will be set after upload + super().__init__( + skill_id="", # Will be set after upload + type="custom", + version="latest", + ) + + def _compute_content_hash(self) -> str: + """Compute a hash of the skill content for caching. + + Returns: + SHA256 hash of the skill content. + """ + hasher = hashlib.sha256() + + if self.path.is_dir(): + # Hash all files in the directory + for root, _dirs, files in os.walk(self.path): + for file in sorted(files): # Sort for consistent hashing + file_path = Path(root) / file + hasher.update(str(file_path.relative_to(self.path)).encode()) + hasher.update(file_path.read_bytes()) + else: + # Hash the zip file + hasher.update(self.path.read_bytes()) + + return hasher.hexdigest() + + def _create_zip_bytes(self) -> bytes: + """Create a zip file from the skill directory or read existing zip. + + The zip must contain a top-level folder with all files inside it, + as required by the Anthropic Skills API. + + Returns: + Bytes of the zip file. + """ + if self.path.suffix == ".zip": + return self.path.read_bytes() + + # Create zip file in memory with top-level folder + # The directory name becomes the top-level folder in the zip + zip_buffer = BytesIO() + + with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zip_file: + for root, _dirs, files in os.walk(self.path): + for file in files: + file_path = Path(root) / file + # Create path relative to parent, preserving directory name + rel_path = file_path.relative_to(self.path.parent) + zip_file.write(file_path, str(rel_path)) + + return zip_buffer.getvalue() + + def upload_skill(self, client: Any) -> str: + """Upload the skill to Anthropic API. + + Args: + client: Anthropic client instance. + + Returns: + The uploaded skill ID. + + Raises: + ValueError: If upload fails. + """ + # Check if already uploaded with same content + content_hash = self._compute_content_hash() + if self._uploaded_skill_id and self._content_hash == content_hash: + return self._uploaded_skill_id + + # Create zip file + zip_bytes = self._create_zip_bytes() + + # Upload to Anthropic + try: + response = client.beta.skills.create( + files=[("skill.zip", zip_bytes, "application/zip")], + display_title=self.display_title, + betas=REQUIRED_BETAS, + ) + + # Store the skill ID and hash + self._uploaded_skill_id = response.id + self._content_hash = content_hash + self.skill_id = response.id + + return response.id + + except Exception as e: + msg = f"Failed to upload skill from {self.path}: {e}" + raise ValueError(msg) from e + + @property + def is_uploaded(self) -> bool: + """Check if the skill has been uploaded. + + Returns: + `True` if the skill has been uploaded, `False` otherwise. + """ + return self._uploaded_skill_id is not None + + +class ClaudeSkillsMiddleware(AgentMiddleware): + """Skills Middleware for Claude. + + Enables Claude to use Skills - specialized capabilities for document processing, + data manipulation, and other domain-specific tasks. Skills are folders containing + instructions, scripts, and resources that Claude loads when relevant. + + This middleware: + + - Adds skills to model requests via the `container` parameter + - Injects required beta headers for Skills functionality + - Automatically adds code execution tool if not present (required for Skills) + - Supports both Anthropic pre-built skills and custom skills + - Handles uploading local skills to the Anthropic API + + Requires both 'langchain' and 'langchain-anthropic' packages to be installed. + + Learn more about Claude Skills + [here](https://docs.claude.com/en/docs/agents-and-tools/agent-skills/overview). + + Example: + Basic usage with pre-built skills: + + ```python + from langchain_anthropic.middleware import ClaudeSkillsMiddleware + + # Enable PowerPoint and Excel skills + skills_middleware = ClaudeSkillsMiddleware(skills=["pptx", "xlsx"]) + + # Use with an agent + agent = create_react_agent( + model=ChatAnthropic(model="claude-sonnet-4-5-20250929"), + tools=[...], + middleware=[skills_middleware], + ) + ``` + + Advanced usage with custom skills from API: + + ```python + from langchain_anthropic.middleware import ( + ClaudeSkillsMiddleware, + SkillConfig, + ) + + # Mix pre-built and custom skills + skills_middleware = ClaudeSkillsMiddleware( + skills=[ + "pptx", # Pre-built skill + SkillConfig( + skill_id="custom-skill-123", + type="custom", + version="latest", + ), + ] + ) + ``` + + Using local skill files: + + ```python + from pathlib import Path + from langchain_anthropic.middleware import ( + ClaudeSkillsMiddleware, + LocalSkillConfig, + ) + + # Load skill from local directory + skills_middleware = ClaudeSkillsMiddleware( + skills=[ + "xlsx", # Pre-built skill + LocalSkillConfig( + path="./my-custom-skill", # Directory with SKILL.md + display_title="My Custom Skill", + auto_upload=True, # Automatically upload when first used + ), + ] + ) + ``` + """ + + def __init__( + self, + skills: list[str | SkillConfig | LocalSkillConfig], + unsupported_model_behavior: Literal["ignore", "warn", "raise"] = "warn", + code_execution_tool_name: str = "code_execution", + ) -> None: + """Initialize the middleware with skills configuration. + + Args: + skills: List of skills to enable. Can be: + - Skill ID strings for Anthropic pre-built skills + (`'pptx'`, `'xlsx'`, `'docx'`, `'pdf'`) + - `SkillConfig` objects for custom skills or fine-grained control + - `LocalSkillConfig` objects for skills loaded from local files + unsupported_model_behavior: Behavior when a non-Anthropic model is used. + - `'ignore'`: Skip skills injection silently + - `'warn'`: Log a warning and skip skills injection + - `'raise'`: Raise an error + code_execution_tool_name: Name of the code execution tool. If not present + in the request, it will be automatically added. + + Raises: + ValueError: If more than 8 skills are provided (API limit) or if + invalid skill configuration is detected. + """ + if len(skills) > 8: + msg = ( + "Anthropic API supports a maximum of 8 skills per request, " + f"but {len(skills)} were provided." + ) + raise ValueError(msg) + + self.skills = self._normalize_skills(skills) + self.unsupported_model_behavior = unsupported_model_behavior + self.code_execution_tool_name = code_execution_tool_name + + def _normalize_skills( + self, skills: list[str | SkillConfig | LocalSkillConfig] + ) -> list[SkillConfig | LocalSkillConfig]: + """Normalize skill configurations. + + Args: + skills: List of skill IDs, SkillConfig objects, or LocalSkillConfig objects. + + Returns: + List of SkillConfig or LocalSkillConfig objects. + """ + normalized = [] + for skill in skills: + if isinstance(skill, str): + # Infer type based on skill_id + skill_type: Literal["anthropic", "custom"] = ( + "anthropic" if skill in ANTHROPIC_SKILLS else "custom" + ) + normalized.append( + SkillConfig(skill_id=skill, type=skill_type, version="latest") + ) + else: + normalized.append(skill) + return normalized + + def _should_apply_skills(self, request: ModelRequest) -> bool: + """Check if skills should be applied to the request. + + Args: + request: The model request to check. + + Returns: + `True` if skills should be applied, `False` otherwise. + + Raises: + ValueError: If model is unsupported and behavior is set to `'raise'`. + """ + if not isinstance(request.model, ChatAnthropic): + msg = ( + "ClaudeSkillsMiddleware only supports Anthropic models, " + f"not instances of {type(request.model)}" + ) + if self.unsupported_model_behavior == "raise": + raise ValueError(msg) + if self.unsupported_model_behavior == "warn": + warn(msg, stacklevel=3) + return False + + return True + + def _ensure_code_execution_tool(self, request: ModelRequest) -> None: + """Ensure code execution tool is present in the request. + + Skills require the code execution tool to be enabled. If not present, + this method will automatically add it. + + Args: + request: The model request to check and modify. + """ + tools = request.tools or [] + + # Check if code execution tool exists + has_code_execution = any( + ( + isinstance(tool, dict) + and tool.get("name") == self.code_execution_tool_name + ) + or (isinstance(tool, str) and tool == self.code_execution_tool_name) + or (hasattr(tool, "name") and tool.name == self.code_execution_tool_name) + for tool in tools + ) + + if not has_code_execution: + # Automatically add code execution tool + tools = list(tools) + tools.append( + { + "type": "code_execution_20250825", + "name": self.code_execution_tool_name, + } + ) + request.tools = tools + + def _upload_local_skills(self, request: ModelRequest) -> None: + """Upload any local skills that need uploading. + + Args: + request: The model request to get Anthropic client from. + + Raises: + ValueError: If upload fails or if model doesn't have a client. + """ + # Check if there are any local skills that need uploading + has_local_skills = any( + isinstance(skill, LocalSkillConfig) + and skill.auto_upload + and not skill.is_uploaded + for skill in self.skills + ) + + if not has_local_skills: + return + + # Get Anthropic client from the model + if not isinstance(request.model, ChatAnthropic): + return + + # Get the underlying client + if not hasattr(request.model, "_client"): + msg = "ChatAnthropic model does not have a _client attribute" + raise ValueError(msg) + + client = request.model._client # noqa: SLF001 + + # Upload any local skills that need it + for skill in self.skills: + if ( + isinstance(skill, LocalSkillConfig) + and skill.auto_upload + and not skill.is_uploaded + ): + skill.upload_skill(client) + + def _inject_skills_config(self, request: ModelRequest) -> None: + """Inject skills configuration into the request. + + Args: + request: The model request to modify. + """ + # Add required beta headers + model_settings = request.model_settings or {} + existing_betas = model_settings.get("betas", []) + + # Merge with required betas, avoiding duplicates + all_betas = list(set(existing_betas + REQUIRED_BETAS)) + model_settings["betas"] = all_betas + + # Create or update container configuration + container = model_settings.get("container", {}) + container["skills"] = [skill.to_dict() for skill in self.skills] + model_settings["container"] = container + + # Update request + request.model_settings = model_settings + + def wrap_model_call( + self, + request: ModelRequest, + handler: Callable[[ModelRequest], ModelResponse], + ) -> ModelCallResult: + """Modify the model request to add skills configuration. + + Args: + request: The model request to potentially modify. + handler: The handler to execute the model request. + + Returns: + The model response from the handler. + + Raises: + ValueError: If the model is unsupported and behavior is set to `'raise'`. + """ + if not self._should_apply_skills(request): + return handler(request) + + self._ensure_code_execution_tool(request) + self._upload_local_skills(request) + self._inject_skills_config(request) + return handler(request) + + async def awrap_model_call( + self, + request: ModelRequest, + handler: Callable[[ModelRequest], Awaitable[ModelResponse]], + ) -> ModelCallResult: + """Modify the model request to add skills configuration (async version). + + Args: + request: The model request to potentially modify. + handler: The async handler to execute the model request. + + Returns: + The model response from the handler. + + Raises: + ValueError: If the model is unsupported and behavior is set to `'raise'`. + """ + if not self._should_apply_skills(request): + return await handler(request) + + self._ensure_code_execution_tool(request) + self._upload_local_skills(request) + self._inject_skills_config(request) + return await handler(request) diff --git a/libs/partners/anthropic/tests/unit_tests/middleware/test_skills.py b/libs/partners/anthropic/tests/unit_tests/middleware/test_skills.py new file mode 100644 index 0000000000000..ec79482d629b0 --- /dev/null +++ b/libs/partners/anthropic/tests/unit_tests/middleware/test_skills.py @@ -0,0 +1,744 @@ +"""Tests for Anthropic Skills middleware.""" + +import tempfile +import warnings +from pathlib import Path +from typing import Any, cast +from unittest.mock import MagicMock + +import pytest +from langchain.agents.middleware.types import ModelRequest, ModelResponse +from langchain_core.callbacks import ( + AsyncCallbackManagerForLLMRun, + CallbackManagerForLLMRun, +) +from langchain_core.language_models import BaseChatModel +from langchain_core.messages import AIMessage, BaseMessage, HumanMessage +from langchain_core.outputs import ChatGeneration, ChatResult +from langgraph.runtime import Runtime + +from langchain_anthropic.chat_models import ChatAnthropic +from langchain_anthropic.middleware import ( + ClaudeSkillsMiddleware, + LocalSkillConfig, + SkillConfig, +) + + +class FakeToolCallingModel(BaseChatModel): + """Fake model for testing middleware.""" + + def _generate( + self, + messages: list[BaseMessage], + stop: list[str] | None = None, + run_manager: CallbackManagerForLLMRun | None = None, + **kwargs: Any, + ) -> ChatResult: + """Top Level call""" + messages_string = "-".join([str(m.content) for m in messages]) + message = AIMessage(content=messages_string, id="0") + return ChatResult(generations=[ChatGeneration(message=message)]) + + async def _agenerate( + self, + messages: list[BaseMessage], + stop: list[str] | None = None, + run_manager: AsyncCallbackManagerForLLMRun | None = None, + **kwargs: Any, + ) -> ChatResult: + """Async top level call""" + messages_string = "-".join([str(m.content) for m in messages]) + message = AIMessage(content=messages_string, id="0") + return ChatResult(generations=[ChatGeneration(message=message)]) + + @property + def _llm_type(self) -> str: + return "fake-tool-call-model" + + +def test_skill_config_initialization() -> None: + """Test SkillConfig initialization and to_dict method.""" + # Test with default values (Anthropic skill) + skill = SkillConfig(skill_id="pptx") + assert skill.skill_id == "pptx" + assert skill.type == "anthropic" + assert skill.version == "latest" + assert skill.to_dict() == { + "type": "anthropic", + "skill_id": "pptx", + "version": "latest", + } + + # Test with custom skill and specific version + skill = SkillConfig( + skill_id="custom-123", + type="custom", + version="1234567890", + ) + assert skill.skill_id == "custom-123" + assert skill.type == "custom" + assert skill.version == "1234567890" + assert skill.to_dict() == { + "type": "custom", + "skill_id": "custom-123", + "version": "1234567890", + } + + +def test_claude_skills_middleware_initialization() -> None: + """Test ClaudeSkillsMiddleware initialization.""" + # Test with string skill IDs (Anthropic pre-built skills) + middleware = ClaudeSkillsMiddleware(skills=["pptx", "xlsx"]) + assert len(middleware.skills) == 2 + assert middleware.skills[0].skill_id == "pptx" + assert middleware.skills[0].type == "anthropic" + assert middleware.skills[1].skill_id == "xlsx" + assert middleware.skills[1].type == "anthropic" + + # Test with SkillConfig objects + middleware = ClaudeSkillsMiddleware( + skills=[ + SkillConfig(skill_id="pptx", type="anthropic", version="20251002"), + SkillConfig(skill_id="custom-123", type="custom", version="latest"), + ] + ) + assert len(middleware.skills) == 2 + assert middleware.skills[0].version == "20251002" + assert middleware.skills[1].type == "custom" + + # Test with mixed skills + middleware = ClaudeSkillsMiddleware( + skills=[ + "pptx", + SkillConfig(skill_id="custom-456", type="custom"), + ] + ) + assert len(middleware.skills) == 2 + assert middleware.skills[0].skill_id == "pptx" + assert middleware.skills[1].skill_id == "custom-456" + + +def test_claude_skills_middleware_max_skills_validation() -> None: + """Test that ClaudeSkillsMiddleware validates maximum skill count.""" + # Should raise error when more than 8 skills are provided + with pytest.raises( + ValueError, + match="Anthropic API supports a maximum of 8 skills per request", + ): + ClaudeSkillsMiddleware(skills=["pptx"] * 9) + + +def test_claude_skills_middleware_code_execution_auto_added() -> None: + """Test that code execution tool is automatically added if missing.""" + middleware = ClaudeSkillsMiddleware(skills=["pptx"]) + + mock_chat_anthropic = MagicMock(spec=ChatAnthropic) + + # Request without code execution tool + fake_request = ModelRequest( + model=mock_chat_anthropic, + messages=[HumanMessage("Create a presentation")], + system_prompt=None, + tool_choice=None, + tools=[], # No code execution tool initially + response_format=None, + state={"messages": [HumanMessage("Create a presentation")]}, + runtime=cast(Runtime, object()), + model_settings={}, + ) + + def mock_handler(req: ModelRequest) -> ModelResponse: + return ModelResponse(result=[AIMessage(content="mock response")]) + + # Should automatically add code_execution tool + result = middleware.wrap_model_call(fake_request, mock_handler) + assert isinstance(result, ModelResponse) + + # Verify code_execution was added to tools + assert len(fake_request.tools) == 1 + tool = fake_request.tools[0] + assert isinstance(tool, dict) + assert tool["name"] == "code_execution" + assert tool["type"] == "code_execution_20250825" + + +def test_claude_skills_middleware_basic_functionality() -> None: + """Test ClaudeSkillsMiddleware basic functionality.""" + middleware = ClaudeSkillsMiddleware(skills=["pptx", "xlsx"]) + + mock_chat_anthropic = MagicMock(spec=ChatAnthropic) + + fake_request = ModelRequest( + model=mock_chat_anthropic, + messages=[HumanMessage("Create a presentation")], + system_prompt=None, + tool_choice=None, + tools=[{"type": "code_execution_20250825", "name": "code_execution"}], + response_format=None, + state={"messages": [HumanMessage("Create a presentation")]}, + runtime=cast(Runtime, object()), + model_settings={}, + ) + + def mock_handler(req: ModelRequest) -> ModelResponse: + return ModelResponse(result=[AIMessage(content="mock response")]) + + result = middleware.wrap_model_call(fake_request, mock_handler) + assert isinstance(result, ModelResponse) + + # Check that model_settings were properly configured + assert "betas" in fake_request.model_settings + assert "code-execution-2025-08-25" in fake_request.model_settings["betas"] + assert "skills-2025-10-02" in fake_request.model_settings["betas"] + assert "files-api-2025-04-14" in fake_request.model_settings["betas"] + + # Check that container with skills was added + assert "container" in fake_request.model_settings + assert "skills" in fake_request.model_settings["container"] + skills = fake_request.model_settings["container"]["skills"] + assert len(skills) == 2 + assert skills[0]["skill_id"] == "pptx" + assert skills[1]["skill_id"] == "xlsx" + + +def test_claude_skills_middleware_preserves_existing_betas() -> None: + """Test that middleware preserves existing beta headers.""" + middleware = ClaudeSkillsMiddleware(skills=["pptx"]) + + mock_chat_anthropic = MagicMock(spec=ChatAnthropic) + + fake_request = ModelRequest( + model=mock_chat_anthropic, + messages=[HumanMessage("Create a presentation")], + system_prompt=None, + tool_choice=None, + tools=[{"type": "code_execution_20250825", "name": "code_execution"}], + response_format=None, + state={"messages": [HumanMessage("Create a presentation")]}, + runtime=cast(Runtime, object()), + model_settings={"betas": ["existing-beta-header"]}, + ) + + def mock_handler(req: ModelRequest) -> ModelResponse: + return ModelResponse(result=[AIMessage(content="mock response")]) + + middleware.wrap_model_call(fake_request, mock_handler) + + # Check that existing betas are preserved + betas = fake_request.model_settings["betas"] + assert "existing-beta-header" in betas + assert "code-execution-2025-08-25" in betas + assert "skills-2025-10-02" in betas + assert "files-api-2025-04-14" in betas + + +def test_claude_skills_middleware_unsupported_model() -> None: + """Test ClaudeSkillsMiddleware with unsupported model.""" + fake_request = ModelRequest( + model=FakeToolCallingModel(), + messages=[HumanMessage("Hello")], + system_prompt=None, + tool_choice=None, + tools=[{"type": "code_execution_20250825", "name": "code_execution"}], + response_format=None, + state={"messages": [HumanMessage("Hello")]}, + runtime=cast(Runtime, object()), + model_settings={}, + ) + + middleware = ClaudeSkillsMiddleware( + skills=["pptx"], unsupported_model_behavior="raise" + ) + + def mock_handler(req: ModelRequest) -> ModelResponse: + return ModelResponse(result=[AIMessage(content="mock response")]) + + # Test raise behavior + with pytest.raises( + ValueError, + match="ClaudeSkillsMiddleware only supports Anthropic models", + ): + middleware.wrap_model_call(fake_request, mock_handler) + + # Test warn behavior + middleware = ClaudeSkillsMiddleware( + skills=["pptx"], unsupported_model_behavior="warn" + ) + + with warnings.catch_warnings(record=True) as w: + result = middleware.wrap_model_call(fake_request, mock_handler) + assert isinstance(result, ModelResponse) + assert len(w) == 1 + assert "ClaudeSkillsMiddleware only supports Anthropic models" in str( + w[-1].message + ) + + # Test ignore behavior + middleware = ClaudeSkillsMiddleware( + skills=["pptx"], unsupported_model_behavior="ignore" + ) + result = middleware.wrap_model_call(fake_request, mock_handler) + assert isinstance(result, ModelResponse) + + +def test_claude_skills_middleware_custom_tool_name() -> None: + """Test middleware with custom code execution tool name.""" + middleware = ClaudeSkillsMiddleware( + skills=["pptx"], code_execution_tool_name="custom_code_exec" + ) + + mock_chat_anthropic = MagicMock(spec=ChatAnthropic) + + # Request with custom tool name + fake_request = ModelRequest( + model=mock_chat_anthropic, + messages=[HumanMessage("Create a presentation")], + system_prompt=None, + tool_choice=None, + tools=[{"type": "custom_exec", "name": "custom_code_exec"}], + response_format=None, + state={"messages": [HumanMessage("Create a presentation")]}, + runtime=cast(Runtime, object()), + model_settings={}, + ) + + def mock_handler(req: ModelRequest) -> ModelResponse: + return ModelResponse(result=[AIMessage(content="mock response")]) + + # Should work with custom tool name + result = middleware.wrap_model_call(fake_request, mock_handler) + assert isinstance(result, ModelResponse) + + +async def test_claude_skills_middleware_async() -> None: + """Test ClaudeSkillsMiddleware async path.""" + middleware = ClaudeSkillsMiddleware(skills=["pptx", "docx"]) + + mock_chat_anthropic = MagicMock(spec=ChatAnthropic) + + fake_request = ModelRequest( + model=mock_chat_anthropic, + messages=[HumanMessage("Create a document")], + system_prompt=None, + tool_choice=None, + tools=[{"type": "code_execution_20250825", "name": "code_execution"}], + response_format=None, + state={"messages": [HumanMessage("Create a document")]}, + runtime=cast(Runtime, object()), + model_settings={}, + ) + + async def mock_handler(req: ModelRequest) -> ModelResponse: + return ModelResponse(result=[AIMessage(content="mock response")]) + + result = await middleware.awrap_model_call(fake_request, mock_handler) + assert isinstance(result, ModelResponse) + + # Verify skills were configured + assert "container" in fake_request.model_settings + skills = fake_request.model_settings["container"]["skills"] + assert len(skills) == 2 + assert skills[0]["skill_id"] == "pptx" + assert skills[1]["skill_id"] == "docx" + + +async def test_claude_skills_middleware_async_unsupported_model() -> None: + """Test ClaudeSkillsMiddleware async path with unsupported model.""" + fake_request = ModelRequest( + model=FakeToolCallingModel(), + messages=[HumanMessage("Hello")], + system_prompt=None, + tool_choice=None, + tools=[{"type": "code_execution_20250825", "name": "code_execution"}], + response_format=None, + state={"messages": [HumanMessage("Hello")]}, + runtime=cast(Runtime, object()), + model_settings={}, + ) + + middleware = ClaudeSkillsMiddleware( + skills=["pptx"], unsupported_model_behavior="raise" + ) + + async def mock_handler(req: ModelRequest) -> ModelResponse: + return ModelResponse(result=[AIMessage(content="mock response")]) + + # Test raise behavior + with pytest.raises( + ValueError, + match="ClaudeSkillsMiddleware only supports Anthropic models", + ): + await middleware.awrap_model_call(fake_request, mock_handler) + + # Test warn behavior + middleware = ClaudeSkillsMiddleware( + skills=["pptx"], unsupported_model_behavior="warn" + ) + + with warnings.catch_warnings(record=True) as w: + result = await middleware.awrap_model_call(fake_request, mock_handler) + assert isinstance(result, ModelResponse) + assert len(w) == 1 + assert "ClaudeSkillsMiddleware only supports Anthropic models" in str( + w[-1].message + ) + + # Test ignore behavior + middleware = ClaudeSkillsMiddleware( + skills=["pptx"], unsupported_model_behavior="ignore" + ) + result = await middleware.awrap_model_call(fake_request, mock_handler) + assert isinstance(result, ModelResponse) + + +async def test_claude_skills_middleware_async_code_execution_auto_added() -> None: + """Test that async path automatically adds code execution tool.""" + middleware = ClaudeSkillsMiddleware(skills=["xlsx"]) + + mock_chat_anthropic = MagicMock(spec=ChatAnthropic) + + # Request without code execution tool + fake_request = ModelRequest( + model=mock_chat_anthropic, + messages=[HumanMessage("Process spreadsheet")], + system_prompt=None, + tool_choice=None, + tools=[], # No code execution tool initially + response_format=None, + state={"messages": [HumanMessage("Process spreadsheet")]}, + runtime=cast(Runtime, object()), + model_settings={}, + ) + + async def mock_handler(req: ModelRequest) -> ModelResponse: + return ModelResponse(result=[AIMessage(content="mock response")]) + + # Should automatically add code_execution tool + result = await middleware.awrap_model_call(fake_request, mock_handler) + assert isinstance(result, ModelResponse) + + # Verify code_execution was added to tools + assert len(fake_request.tools) == 1 + tool = fake_request.tools[0] + assert isinstance(tool, dict) + assert tool["name"] == "code_execution" + assert tool["type"] == "code_execution_20250825" + + +def test_claude_skills_middleware_all_pre_built_skills() -> None: + """Test middleware with all pre-built Anthropic skills.""" + middleware = ClaudeSkillsMiddleware(skills=["pptx", "xlsx", "docx", "pdf"]) + + mock_chat_anthropic = MagicMock(spec=ChatAnthropic) + + fake_request = ModelRequest( + model=mock_chat_anthropic, + messages=[HumanMessage("Process documents")], + system_prompt=None, + tool_choice=None, + tools=[{"type": "code_execution_20250825", "name": "code_execution"}], + response_format=None, + state={"messages": [HumanMessage("Process documents")]}, + runtime=cast(Runtime, object()), + model_settings={}, + ) + + def mock_handler(req: ModelRequest) -> ModelResponse: + return ModelResponse(result=[AIMessage(content="mock response")]) + + result = middleware.wrap_model_call(fake_request, mock_handler) + assert isinstance(result, ModelResponse) + + # Verify all skills are configured + skills = fake_request.model_settings["container"]["skills"] + assert len(skills) == 4 + skill_ids = {skill["skill_id"] for skill in skills} + assert skill_ids == {"pptx", "xlsx", "docx", "pdf"} + # All should be anthropic type with latest version by default + for skill in skills: + assert skill["type"] == "anthropic" + assert skill["version"] == "latest" + + +def test_local_skill_config_initialization() -> None: + """Test LocalSkillConfig initialization and validation.""" + with tempfile.TemporaryDirectory() as temp_dir: + skill_dir = Path(temp_dir) / "my-skill" + skill_dir.mkdir() + + # Create a valid SKILL.md file + skill_md = skill_dir / "SKILL.md" + skill_md.write_text( + """--- +name: My Custom Skill +description: A test skill for unit testing +--- + +# My Custom Skill + +This is a test skill.""" + ) + + # Test successful initialization + skill = LocalSkillConfig(path=skill_dir, display_title="Test Skill") + assert skill.path == skill_dir + assert skill.display_title == "Test Skill" + assert skill.type == "custom" + assert skill.version == "latest" + assert skill.auto_upload is True + assert not skill.is_uploaded + + # Test with auto_upload=False + skill = LocalSkillConfig(path=skill_dir, auto_upload=False) + assert skill.auto_upload is False + + +def test_local_skill_config_missing_path() -> None: + """Test LocalSkillConfig with non-existent path.""" + with pytest.raises(FileNotFoundError, match="Skill path does not exist"): + LocalSkillConfig(path="/nonexistent/path") + + +def test_local_skill_config_invalid_path_type() -> None: + """Test LocalSkillConfig with invalid path type.""" + with tempfile.TemporaryDirectory() as temp_dir: + # Create a regular file instead of a directory + invalid_path = Path(temp_dir) / "invalid.txt" + invalid_path.write_text("not a skill") + + with pytest.raises( + ValueError, + match=r"Skill path must be a directory or \.zip file", + ): + LocalSkillConfig(path=invalid_path) + + +def test_local_skill_config_missing_skill_md() -> None: + """Test LocalSkillConfig with directory missing SKILL.md.""" + with tempfile.TemporaryDirectory() as temp_dir: + skill_dir = Path(temp_dir) / "incomplete-skill" + skill_dir.mkdir() + + with pytest.raises( + ValueError, + match=r"Skill directory must contain a SKILL\.md file", + ): + LocalSkillConfig(path=skill_dir) + + +def test_local_skill_config_zip_file() -> None: + """Test LocalSkillConfig with zip file.""" + with tempfile.TemporaryDirectory() as temp_dir: + # Create a zip file + import zipfile + + zip_path = Path(temp_dir) / "skill.zip" + with zipfile.ZipFile(zip_path, "w") as zf: + zf.writestr( + "SKILL.md", + """--- +name: Zipped Skill +description: A skill from a zip file +--- + +# Zipped Skill""", + ) + + # Should initialize successfully + skill = LocalSkillConfig(path=zip_path) + assert skill.path == zip_path + assert skill.type == "custom" + + +def test_local_skill_config_content_hash() -> None: + """Test that content hash is computed correctly.""" + with tempfile.TemporaryDirectory() as temp_dir: + skill_dir = Path(temp_dir) / "my-skill" + skill_dir.mkdir() + + skill_md = skill_dir / "SKILL.md" + skill_md.write_text( + """--- +name: Hash Test +description: Testing hash computation +--- + +# Hash Test""" + ) + + skill = LocalSkillConfig(path=skill_dir) + hash1 = skill._compute_content_hash() + + # Hash should be consistent + hash2 = skill._compute_content_hash() + assert hash1 == hash2 + + # Modify content + skill_md.write_text( + """--- +name: Hash Test Modified +description: Modified content +--- + +# Modified""" + ) + + # Hash should change + hash3 = skill._compute_content_hash() + assert hash1 != hash3 + + +def test_local_skill_config_create_zip() -> None: + """Test creating zip from skill directory.""" + with tempfile.TemporaryDirectory() as temp_dir: + skill_dir = Path(temp_dir) / "my-skill" + skill_dir.mkdir() + + # Create multiple files + (skill_dir / "SKILL.md").write_text("# Test Skill") + (skill_dir / "helper.py").write_text("def helper(): pass") + + # Create subdirectory with file + subdir = skill_dir / "scripts" + subdir.mkdir() + (subdir / "script.py").write_text("print('hello')") + + skill = LocalSkillConfig(path=skill_dir) + zip_bytes = skill._create_zip_bytes() + + # Verify it's valid zip + import zipfile + from io import BytesIO + + with zipfile.ZipFile(BytesIO(zip_bytes), "r") as zf: + names = zf.namelist() + # Zip should contain top-level folder with all files inside + assert "my-skill/SKILL.md" in names + assert "my-skill/helper.py" in names + assert "my-skill/scripts/script.py" in names + + +def test_local_skill_config_upload() -> None: + """Test uploading a local skill.""" + with tempfile.TemporaryDirectory() as temp_dir: + skill_dir = Path(temp_dir) / "my-skill" + skill_dir.mkdir() + (skill_dir / "SKILL.md").write_text("# Test") + + skill = LocalSkillConfig(path=skill_dir) + + # Mock the Anthropic client + mock_client = MagicMock() + mock_response = MagicMock() + mock_response.id = "skill-123" + mock_client.beta.skills.create.return_value = mock_response + + # Upload + skill_id = skill.upload_skill(mock_client) + + assert skill_id == "skill-123" + assert skill.skill_id == "skill-123" + assert skill.is_uploaded + + # Verify upload was called + mock_client.beta.skills.create.assert_called_once() + + # Upload again with same content - should not call API again + skill_id2 = skill.upload_skill(mock_client) + assert skill_id2 == "skill-123" + assert mock_client.beta.skills.create.call_count == 1 # Still just 1 call + + +def test_claude_skills_middleware_with_local_skill() -> None: + """Test ClaudeSkillsMiddleware with LocalSkillConfig.""" + with tempfile.TemporaryDirectory() as temp_dir: + skill_dir = Path(temp_dir) / "my-skill" + skill_dir.mkdir() + (skill_dir / "SKILL.md").write_text("# Test Skill") + + # Create middleware with local skill (auto_upload=False) + local_skill = LocalSkillConfig(path=skill_dir, auto_upload=False) + middleware = ClaudeSkillsMiddleware(skills=["pptx", local_skill]) + + # Mock chat model with _client + mock_chat_anthropic = MagicMock(spec=ChatAnthropic) + mock_client = MagicMock() + mock_chat_anthropic._client = mock_client + + # Manually upload the skill first + mock_response = MagicMock() + mock_response.id = "local-skill-456" + mock_client.beta.skills.create.return_value = mock_response + local_skill.upload_skill(mock_client) + + fake_request = ModelRequest( + model=mock_chat_anthropic, + messages=[HumanMessage("Use skills")], + system_prompt=None, + tool_choice=None, + tools=[{"type": "code_execution_20250825", "name": "code_execution"}], + response_format=None, + state={"messages": [HumanMessage("Use skills")]}, + runtime=cast(Runtime, object()), + model_settings={}, + ) + + def mock_handler(req: ModelRequest) -> ModelResponse: + return ModelResponse(result=[AIMessage(content="mock response")]) + + result = middleware.wrap_model_call(fake_request, mock_handler) + assert isinstance(result, ModelResponse) + + # Verify both skills are configured + skills = fake_request.model_settings["container"]["skills"] + assert len(skills) == 2 + skill_ids = {skill["skill_id"] for skill in skills} + assert "pptx" in skill_ids + assert "local-skill-456" in skill_ids + + +def test_claude_skills_middleware_auto_upload() -> None: + """Test that middleware auto-uploads local skills when auto_upload=True.""" + with tempfile.TemporaryDirectory() as temp_dir: + skill_dir = Path(temp_dir) / "auto-skill" + skill_dir.mkdir() + (skill_dir / "SKILL.md").write_text("# Auto Upload Test") + + # Create middleware with auto_upload=True (default) + local_skill = LocalSkillConfig(path=skill_dir) + middleware = ClaudeSkillsMiddleware(skills=[local_skill]) + + # Mock chat model with _client + mock_chat_anthropic = MagicMock(spec=ChatAnthropic) + mock_client = MagicMock() + mock_chat_anthropic._client = mock_client + + mock_response = MagicMock() + mock_response.id = "auto-uploaded-789" + mock_client.beta.skills.create.return_value = mock_response + + fake_request = ModelRequest( + model=mock_chat_anthropic, + messages=[HumanMessage("Test")], + system_prompt=None, + tool_choice=None, + tools=[{"type": "code_execution_20250825", "name": "code_execution"}], + response_format=None, + state={"messages": [HumanMessage("Test")]}, + runtime=cast(Runtime, object()), + model_settings={}, + ) + + def mock_handler(req: ModelRequest) -> ModelResponse: + return ModelResponse(result=[AIMessage(content="mock response")]) + + # Should auto-upload during wrap_model_call + assert not local_skill.is_uploaded + result = middleware.wrap_model_call(fake_request, mock_handler) + assert isinstance(result, ModelResponse) + assert local_skill.is_uploaded + assert local_skill.skill_id == "auto-uploaded-789" + + # Verify upload was called + mock_client.beta.skills.create.assert_called_once() diff --git a/libs/partners/anthropic/uv.lock b/libs/partners/anthropic/uv.lock index 08c5b6905cfec..da9530b451687 100644 --- a/libs/partners/anthropic/uv.lock +++ b/libs/partners/anthropic/uv.lock @@ -10,9 +10,6 @@ resolution-markers = [ "python_full_version < '3.11' and platform_python_implementation == 'PyPy'", ] -[options] -prerelease-mode = "allow" - [[package]] name = "annotated-types" version = "0.7.0" @@ -247,11 +244,11 @@ wheels = [ [[package]] name = "defusedxml" -version = "0.8.0rc2" +version = "0.7.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5e/3b/b8849dcc3f96913924137dc4ea041d74aa513a3c5dda83d8366491290c74/defusedxml-0.8.0rc2.tar.gz", hash = "sha256:138c7d540a78775182206c7c97fe65b246a2f40b29471e1a2f1b0da76e7a3942", size = 52575, upload-time = "2023-09-29T08:01:27.517Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69", size = 75520, upload-time = "2021-03-08T10:59:26.269Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/c7/6b4ad89ca6f7732ff97ce5e9caa6fe739600d26c5d53c20d0bf9abb79ec5/defusedxml-0.8.0rc2-py2.py3-none-any.whl", hash = "sha256:1c812964311154c3bf4aaf3bc1443b31ee13530b7f255eaaa062c0553c76103d", size = 25756, upload-time = "2023-09-29T08:01:25.515Z" }, + { url = "https://files.pythonhosted.org/packages/07/6c/aa3f2f849e01cb6a001cd8554a88d4c77c5c1a31c95bdf1cf9301e6d9ef4/defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61", size = 25604, upload-time = "2021-03-08T10:59:24.45Z" }, ] [[package]] @@ -456,7 +453,7 @@ wheels = [ [[package]] name = "langchain" -version = "1.0.0rc1" +version = "1.0.0rc2" source = { editable = "../../langchain_v1" } dependencies = [ { name = "langchain-core" }, @@ -600,7 +597,7 @@ typing = [ [[package]] name = "langchain-core" -version = "1.0.0rc2" +version = "1.0.0rc3" source = { editable = "../../core" } dependencies = [ { name = "jsonpatch" }, @@ -703,7 +700,7 @@ typing = [ [[package]] name = "langgraph" -version = "1.0.0a4" +version = "1.0.0rc1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "langchain-core" }, @@ -713,9 +710,9 @@ dependencies = [ { name = "pydantic" }, { name = "xxhash" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f8/9e/fcf7d2dd275654a077c224c38e33674b1e5753146b7c7a228f3074767c3c/langgraph-1.0.0a4.tar.gz", hash = "sha256:7263648049967d4e2c15c6c94532c4398a05ef785ad09bd50cb2821f137ad0b9", size = 465213, upload-time = "2025-09-29T12:13:43.729Z" } +sdist = { url = "https://files.pythonhosted.org/packages/06/6b/27863bc2197fe2cae5ae3241a79e1868b98f8dfa03991eea4f607dba177a/langgraph-1.0.0rc1.tar.gz", hash = "sha256:0acc0eddbed6b353334a93de6943bb49820054cf14e1ca7dab0a91ac7add1ce2", size = 466052, upload-time = "2025-10-17T00:56:12.222Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/69/f1/6d19322b63840290d95defaf9787014455e8aa8394dcb67bcdd57a6f72fa/langgraph-1.0.0a4-py3-none-any.whl", hash = "sha256:91d26fd0f8045c3008673858c79d8aae7821cdef269443946d192719416c0e49", size = 154928, upload-time = "2025-09-29T12:13:42.154Z" }, + { url = "https://files.pythonhosted.org/packages/ee/5b/7d4aaf30f8c08d14ec16a49e89530c05ab9ccd7c00a54da6bd8adabefd26/langgraph-1.0.0rc1-py3-none-any.whl", hash = "sha256:9d84da21ae8bcc5b05dfa2e63396eb642d39c54d670406b7319810ede0c5ab26", size = 155229, upload-time = "2025-10-17T00:56:10.956Z" }, ] [[package]] @@ -733,15 +730,15 @@ wheels = [ [[package]] name = "langgraph-prebuilt" -version = "0.7.0a2" +version = "0.7.0rc1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "langchain-core" }, { name = "langgraph-checkpoint" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ce/a2/8c82bad7400328a10953e52355933a9e79778fbb7bc3389be6240be541af/langgraph_prebuilt-0.7.0a2.tar.gz", hash = "sha256:ecf154a68be5eb3316544c2df47a19e4cc0e2ce1e2bbd971ba28533695fa9ddc", size = 113658, upload-time = "2025-09-02T17:07:02.547Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/d3/6474eecd1cc95cead25fe0f1717f0b76e1f6edbc8631fc773f6bf7ac03ad/langgraph_prebuilt-0.7.0rc1.tar.gz", hash = "sha256:23f2c1c0a3f0c643a45f90d99ac951a5e1d1be3e711ae10d91b03e34c05b306b", size = 114860, upload-time = "2025-10-17T00:51:56.719Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/b9/e59ecfa7cac69fdcfa1274a7a575de64ba0351da30cf35be9dcb7f3b33c7/langgraph_prebuilt-0.7.0a2-py3-none-any.whl", hash = "sha256:757b93a3e44802ba18623bdca46384fae109736758496a83b043ce4b5074bc47", size = 28398, upload-time = "2025-09-02T17:07:01.633Z" }, + { url = "https://files.pythonhosted.org/packages/e1/70/6a46ebd63304028617b1de1377a159dedd95f0ce2e68a6d8b50c3378448c/langgraph_prebuilt-0.7.0rc1-py3-none-any.whl", hash = "sha256:7a2032683f1cab23d19f11f89805bc84b668502c491b6f041afb6932c8870e70", size = 28387, upload-time = "2025-10-17T00:51:55.75Z" }, ] [[package]] @@ -1589,14 +1586,14 @@ wheels = [ [[package]] name = "pytest-asyncio" -version = "0.24.0a0" +version = "0.23.8" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0a/b7/cf54bb6825bb654ca6a07dc3efee715cc1fd0d3cbd00a31a095b09cc86d3/pytest_asyncio-0.24.0a0.tar.gz", hash = "sha256:4c599f8e3657cdd8b15ad050eba551fb740b7e99c1a7dcc4a23b2141b10aee41", size = 49299, upload-time = "2024-07-30T13:44:17.677Z" } +sdist = { url = "https://files.pythonhosted.org/packages/de/b4/0b378b7bf26a8ae161c3890c0b48a91a04106c5713ce81b4b080ea2f4f18/pytest_asyncio-0.23.8.tar.gz", hash = "sha256:759b10b33a6dc61cce40a8bd5205e302978bbbcc00e279a8b61d9a6a3c82e4d3", size = 46920, upload-time = "2024-07-17T17:39:34.617Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/5b/9310aecc65cb4eef33793ee36e24fb74f758c568a7cd726a28be7352913e/pytest_asyncio-0.24.0a0-py3-none-any.whl", hash = "sha256:f6e24c9779fab29235463b2437eb121dde748e39e2f194489517d8a780d26a02", size = 18294, upload-time = "2024-07-30T13:44:16.183Z" }, + { url = "https://files.pythonhosted.org/packages/ee/82/62e2d63639ecb0fbe8a7ee59ef0bc69a4669ec50f6d3459f74ad4e4189a2/pytest_asyncio-0.23.8-py3-none-any.whl", hash = "sha256:50265d892689a5faefb84df80819d1ecef566eb3549cf915dfb33569359d1ce2", size = 17663, upload-time = "2024-07-17T17:39:32.478Z" }, ] [[package]] @@ -1614,24 +1611,24 @@ wheels = [ [[package]] name = "pytest-codspeed" -version = "4.2.0b0" +version = "4.1.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi" }, { name = "pytest" }, { name = "rich" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/55/d3/6f64533d723ad43d7e398c847d6efa763607e4340418ca823537516c1dd5/pytest_codspeed-4.2.0b0.tar.gz", hash = "sha256:4c7839eba10d6cba6b0ba78c1b87c73032fbd7fb1aec1f5b586a752076d4e32f", size = 113177, upload-time = "2025-10-07T15:47:15.114Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/ae/5d89a787151868d3ebd813b24a13fe4ac7e60148cf1b3454d351c8b99f69/pytest_codspeed-4.1.1.tar.gz", hash = "sha256:9acc3394cc8aafd4543193254831d87de6be79accfdbd43475919fdaa2fc8d81", size = 113149, upload-time = "2025-10-07T16:30:02.709Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/80/37b05042e6fbbbc3149a394208947eff12fecc7400b391bad47beb03e89d/pytest_codspeed-4.2.0b0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:46aebf7edd4ae50acd8238735bb0eb1508fd73931141179dc324ca25461245e1", size = 261976, upload-time = "2025-10-07T15:47:00.258Z" }, - { url = "https://files.pythonhosted.org/packages/ca/13/739c65bcec74a849714cec404ba316a94677b784fa6a0d1bae38fe9d8241/pytest_codspeed-4.2.0b0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee3ba60a4cfb922cfd3eaf8087877ea8672859cbfbf42d48ecf0f6bea2f55f18", size = 249290, upload-time = "2025-10-07T15:47:02.108Z" }, - { url = "https://files.pythonhosted.org/packages/44/ad/23f4f0767983867fd1d870e4f1de7b7de998fa5970080a95222bc0183402/pytest_codspeed-4.2.0b0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d2c3a2fd0e48a92f78d162b11b6fb514b2479b687a5be2e77901a753657f682a", size = 262013, upload-time = "2025-10-07T15:47:03.52Z" }, - { url = "https://files.pythonhosted.org/packages/68/65/72c98d2d08534d0a1e153e6ef8d4a48842df0690ce614f849b1d4f2dd1a6/pytest_codspeed-4.2.0b0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4a9dacfb63b383add866ca15841e0e7f1502765fad0d24d4a0d5ff2be8b0943a", size = 249305, upload-time = "2025-10-07T15:47:05.037Z" }, - { url = "https://files.pythonhosted.org/packages/b6/2b/0c34918b045d4cec003ebdd88c77561dd3930eeac6365f785358bd4c1f4e/pytest_codspeed-4.2.0b0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b31891269353f2847a1c75f8ae656788b0535acf3a4b9479dc097fc99c122441", size = 262235, upload-time = "2025-10-07T15:47:06.187Z" }, - { url = "https://files.pythonhosted.org/packages/1d/9c/5c8f8c8b5e6c6b5b17f8c598f97e768e005dc6a553b89846e03838b5aec8/pytest_codspeed-4.2.0b0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5bb520b7c6808d13bc7d22ee3ab12c8a64bec8be7598efb7f0caa32d7b3569a0", size = 249587, upload-time = "2025-10-07T15:47:07.298Z" }, - { url = "https://files.pythonhosted.org/packages/cf/ab/8e61e3e8db98c327aa84841487c4904ef63b3ed51fcd02da6efa878631f4/pytest_codspeed-4.2.0b0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f799a242cf48f4728bf48d92f76a08d775160d77cfcbf0818f787bdbfeb22128", size = 262243, upload-time = "2025-10-07T15:47:08.799Z" }, - { url = "https://files.pythonhosted.org/packages/f5/29/15152232deb737aacd5edd835564d05134dca1b36966427bc0a617773b15/pytest_codspeed-4.2.0b0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:21897ab2f25c26b3c8ebd7b8e1a69c473e8ab4d4dabeb354b46a4e9e86c11b97", size = 249591, upload-time = "2025-10-07T15:47:10.334Z" }, - { url = "https://files.pythonhosted.org/packages/6c/d6/92d43712617761cba42393d0232ee9a59d2450f9f37ba0362f59cd902278/pytest_codspeed-4.2.0b0-py3-none-any.whl", hash = "sha256:3d298c135f4fa7ef61df1cc392a065acf09e7ff0d843705fe58f947a50a87c13", size = 113638, upload-time = "2025-10-07T15:47:13.997Z" }, + { url = "https://files.pythonhosted.org/packages/0c/b4/6e76539ef17e398c06464c6d92da1b8978b46180df5446cdb76e2293c254/pytest_codspeed-4.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa83a1a0aaeb6bdb9918a18294708eebe765a3b5a855adccf9213629d2a0d302", size = 261944, upload-time = "2025-10-07T16:29:50.024Z" }, + { url = "https://files.pythonhosted.org/packages/43/6a/ba8aca4a07bb8f19fdeba752c7276b35cb63ff743695b137c2f68a53cf21/pytest_codspeed-4.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e6fe213b2589ffe6f2189b3b21ca14717c9346b226e6028d2e2b4d4d7dac750f", size = 249258, upload-time = "2025-10-07T16:29:52.694Z" }, + { url = "https://files.pythonhosted.org/packages/5e/13/be5ab2167d7fb477ba86061dfb6dee3b6feb698ef7d35e8912ba0cce94d5/pytest_codspeed-4.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:94b3bd5a71bfab4478e9a9b5058237cf2b34938570b43495093c2ea213175bd5", size = 261978, upload-time = "2025-10-07T16:29:53.671Z" }, + { url = "https://files.pythonhosted.org/packages/43/7f/60445f5e9bdaff4b9da1d7e4964e34d87bc160867aec2d99c1119e38b3f8/pytest_codspeed-4.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cfc1efdbcc92fb4b4cbc8eaa8d7387664b063c17e025985ece4816100f1fff29", size = 249269, upload-time = "2025-10-07T16:29:54.907Z" }, + { url = "https://files.pythonhosted.org/packages/6e/bf/b1d78c982d575636b38688f284bfd6836f5f232d95cef661a6345a91cc7b/pytest_codspeed-4.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:506d446d2911e5188aca7be702c2850a9b8680a72ed241a633d7edaeef00ac13", size = 262202, upload-time = "2025-10-07T16:29:55.792Z" }, + { url = "https://files.pythonhosted.org/packages/45/23/9ace1fcd72a84d845f522e06badada7d85f6a5a4d4aed54509b51af87068/pytest_codspeed-4.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1773c74394c98317c6846e9eb60c352222c031bdf1ded109f5c35772a3ce6dc2", size = 249554, upload-time = "2025-10-07T16:29:56.681Z" }, + { url = "https://files.pythonhosted.org/packages/da/45/09fa57357d46e46de968554cd0a20e4a8b2a48971fec9564c215c67ebcde/pytest_codspeed-4.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f8af528f7f950cb745971fc1e9f59ebc52cc4c51a7eac7a931577fd55d21b94", size = 262209, upload-time = "2025-10-07T16:29:57.659Z" }, + { url = "https://files.pythonhosted.org/packages/65/e5/ac697d8e249be059d4bec9ebcb22a5626e0d18fd6944665b6d0edca3a508/pytest_codspeed-4.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:db8b2b71cabde1a7ae77a29a3ce67bcb852c28d5599b4eb7428fdb26cd067815", size = 249559, upload-time = "2025-10-07T16:29:58.58Z" }, + { url = "https://files.pythonhosted.org/packages/31/b0/6f0502ca6497e4c53f2f425b57854b759f29d186804a64d090d9ac2878ca/pytest_codspeed-4.1.1-py3-none-any.whl", hash = "sha256:a0a7aa318b09d87541f4f65db9cd473b53d4f1589598d883b238fe208ae2ac8b", size = 113601, upload-time = "2025-10-07T16:30:01.58Z" }, ] [[package]] @@ -2051,95 +2048,71 @@ wheels = [ [[package]] name = "wrapt" -version = "2.0.0rc5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/46/57/0f1c9dd2471e9d2950788273533ba98dbfdc5e64e65ebf55ea47992d3859/wrapt-2.0.0rc5.tar.gz", hash = "sha256:9622d2d12dacc3f1bd6f015599cc6d406d00cdb8076ca52f6ba3c84bd32a6240", size = 80232, upload-time = "2025-10-16T05:42:39.044Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/87/b6/9b8946816f393009463c5075f9d2f7dee1424ec93c56ea116e519fca9874/wrapt-2.0.0rc5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:202cdeca52c6432f6429c9a54fc030bfb1ac8df7a5271be2cb521e1ef0e4d80b", size = 77394, upload-time = "2025-10-16T05:40:17.283Z" }, - { url = "https://files.pythonhosted.org/packages/13/4b/dd9464f56eefd367490e741680ab77482adbbfd9caf6985d3bb741b64728/wrapt-2.0.0rc5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e7cfb8a36af0a649e3a76ee1a4203964ea078a0552cd84a43a555f98fb7a2976", size = 60661, upload-time = "2025-10-16T05:40:19.251Z" }, - { url = "https://files.pythonhosted.org/packages/ca/49/3170249cce2ae5019fdce0e6dbd5ca96a124875b6fc22ee78747812252e2/wrapt-2.0.0rc5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7a7f3b6416aebd3ea89f60fc274b6f6e6fa49d809d3a4c420aebb0ab139fe446", size = 61513, upload-time = "2025-10-16T05:40:20.569Z" }, - { url = "https://files.pythonhosted.org/packages/0f/b5/70742a2253a210767304f9af8d17b3e4274821406f8b6c8b9c1955afcdd0/wrapt-2.0.0rc5-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:a186a1529212ebc9c34ee6fa1227a424281f850645c661a3f20567fadbec0583", size = 113296, upload-time = "2025-10-16T05:40:24.472Z" }, - { url = "https://files.pythonhosted.org/packages/3c/f1/c2858d9441c127ecd1a1d96e703ec6ab5e0753c203cd262b514663971f02/wrapt-2.0.0rc5-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:512308945e9f25254a06c2ed9d2e1db64525c2723d00eaba20b3e0ef2b09760b", size = 115194, upload-time = "2025-10-16T05:40:26.573Z" }, - { url = "https://files.pythonhosted.org/packages/65/1d/33ecd8195e75e164e3ce428f6152b6e54e3078617b5647927c711ccf6771/wrapt-2.0.0rc5-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:27979ed1676fced05548fb798dfe1575d90402ec7c86d981fdd082a284022e23", size = 111738, upload-time = "2025-10-16T05:40:22.844Z" }, - { url = "https://files.pythonhosted.org/packages/d6/06/ddccbc8e059f3bafed125ac500a14434c7dbfe8a2bcddb316ce491154274/wrapt-2.0.0rc5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3e57c5c7b2dcabc87e51de23275a664d299b36155456c7c6d232c9f0dbae739e", size = 114360, upload-time = "2025-10-16T05:40:28.322Z" }, - { url = "https://files.pythonhosted.org/packages/11/92/9d61235b7fda5e133e87bde542d3fc325985badefac12c9217e8483433a9/wrapt-2.0.0rc5-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:0d30a77881f72bf2c0a68d7b53080090cf2d3e005924f37f1cb678c9a7cdeeda", size = 110969, upload-time = "2025-10-16T05:40:29.933Z" }, - { url = "https://files.pythonhosted.org/packages/e5/56/fc0b0ff00136dbfc56b3f0ef972074b059e7aa2cef716857754613a901d9/wrapt-2.0.0rc5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cef66cb8637531fb6e8d5a20d7c6139089581c0bb71cb7e2f1706332d952a81b", size = 112998, upload-time = "2025-10-16T05:40:32.037Z" }, - { url = "https://files.pythonhosted.org/packages/f8/ae/63195ed2b669c3b6066f80276159843dc29eba103f63913c64f671d8f307/wrapt-2.0.0rc5-cp310-cp310-win32.whl", hash = "sha256:56dc4c09f4eb96fd0421330590d930c0aa2453d3e571be0b0fcc2c11fb86c238", size = 57958, upload-time = "2025-10-16T05:40:35.341Z" }, - { url = "https://files.pythonhosted.org/packages/da/b4/4b55bbe3fcb04dad58191f9d3b3cc542af5c0b788623ea3189eacedd462f/wrapt-2.0.0rc5-cp310-cp310-win_amd64.whl", hash = "sha256:46584e91020119217fb31c77e48c4b5e742a55156d679bbbafaae9120e7d5bc1", size = 60276, upload-time = "2025-10-16T05:40:33.361Z" }, - { url = "https://files.pythonhosted.org/packages/b5/2d/299518dc59228c833a826765d0787f09efef40fb62cb5cfe97f0386047dd/wrapt-2.0.0rc5-cp310-cp310-win_arm64.whl", hash = "sha256:4136c8c3b692f424a6bafd30d88e8f8b9359c85adf8f7ee4ebdd85b4860a65f8", size = 58882, upload-time = "2025-10-16T05:40:34.346Z" }, - { url = "https://files.pythonhosted.org/packages/27/4d/4feccc63fb79804ae63de34284e6876c1998fda8e318eef98bf8c460dba1/wrapt-2.0.0rc5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:970718971cf968b146c01e6e6f9c41236d53675ab85592ec6647ba3882260b35", size = 77393, upload-time = "2025-10-16T05:40:36.644Z" }, - { url = "https://files.pythonhosted.org/packages/37/a1/9ff0517fe43255720da6d3b44e4c78752f17c7cacaf7ac0d95673bf93f00/wrapt-2.0.0rc5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6f0fa8c2a0a81c99a12f439e9deaa807e8bd7fe785385069a8f6fc09de73e333", size = 60663, upload-time = "2025-10-16T05:40:37.637Z" }, - { url = "https://files.pythonhosted.org/packages/a6/2d/f2d399b8146635e45a71f9c1dc5f40118fc2af036a35166b1b64551c7ff0/wrapt-2.0.0rc5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e31e8e9aadf9db9ea09c4c5a68e31a3bb2d3eeb6f6fec3d94114ea2c56815dc7", size = 61512, upload-time = "2025-10-16T05:40:39.032Z" }, - { url = "https://files.pythonhosted.org/packages/0e/af/2e33164b7f30bd2dfbfb865b225271aac61b087949c716ea079f6ad82cad/wrapt-2.0.0rc5-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:04d62146236f04db2cfb2849044ea7cb7de4a09c72957a021b8d86a38578834f", size = 113709, upload-time = "2025-10-16T05:40:41.056Z" }, - { url = "https://files.pythonhosted.org/packages/b9/35/3862beefc28581f57980615de6f294fcd0f7ba275cc132e5e55e7951a44a/wrapt-2.0.0rc5-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3d286db7933503eea60a40cc851a8be298f039713c3d4c3563451297aaea075d", size = 115630, upload-time = "2025-10-16T05:40:42.068Z" }, - { url = "https://files.pythonhosted.org/packages/00/1b/e566e8ccdeec74dde73d84afb7e9b3fe7f99ba4a98c36d299cd29ee831f6/wrapt-2.0.0rc5-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:95f02381a72a937f0c10750ded29461473fac5f0cf348494f60315c07510a934", size = 112177, upload-time = "2025-10-16T05:40:40.027Z" }, - { url = "https://files.pythonhosted.org/packages/b6/58/2d2c06fc73c30e9c87ac0c5d7e908449267336e2d20bf3e94f0dea5ac0db/wrapt-2.0.0rc5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:42c9eac812abb0f4674df39182ba11c410a1b90d52d08d9407d29eb394883d85", size = 114916, upload-time = "2025-10-16T05:40:44.175Z" }, - { url = "https://files.pythonhosted.org/packages/dc/f0/5e2abfe90c54f37284de6d82b6fd899a9de1dd08b7d67ba2b2576d8c3717/wrapt-2.0.0rc5-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:116bc9b2de8e2513631134e4b3d34de2adf110f0cac69d3827dc361adf251b1b", size = 111427, upload-time = "2025-10-16T05:40:46.774Z" }, - { url = "https://files.pythonhosted.org/packages/b1/7e/aba0d44ad0a9d4af9e9a2dbde7df4945ffda929bf065325f10d684dfee9a/wrapt-2.0.0rc5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c7640251fe9fb23cdc9833b3c2df5c9b6e6115a8102616c9e3fe8fa821b352a1", size = 113509, upload-time = "2025-10-16T05:40:47.787Z" }, - { url = "https://files.pythonhosted.org/packages/df/cc/ab66d10fb4035e3cfefa8c3770ceb8590e8d80d0eda49073a3fcf7206904/wrapt-2.0.0rc5-cp311-cp311-win32.whl", hash = "sha256:5ceab372f3bd870a754a86ee2442b8629d580f762337aab5fb033a5e6b10f84c", size = 57952, upload-time = "2025-10-16T05:40:51.107Z" }, - { url = "https://files.pythonhosted.org/packages/95/fa/e1f741c716cb1461a072e2221a456543a9c30ae56d05cbc87133f289f728/wrapt-2.0.0rc5-cp311-cp311-win_amd64.whl", hash = "sha256:bc9792570ec814aeece8e979ef5bcb941503dfdef038d035414ae9196576548f", size = 60275, upload-time = "2025-10-16T05:40:49.131Z" }, - { url = "https://files.pythonhosted.org/packages/91/5f/1544b2edd6576d4741fe79859513fc4c7f1555d50af9bf2efb5c85977a58/wrapt-2.0.0rc5-cp311-cp311-win_arm64.whl", hash = "sha256:1000196191cb81b7e1a01d90ec69905aa1f4755fb803ce1a4633259e1fd768ef", size = 58880, upload-time = "2025-10-16T05:40:50.144Z" }, - { url = "https://files.pythonhosted.org/packages/01/76/0b99e2d2a456b4dc1677dc0875582631601b75ac959abb283d484171ec7f/wrapt-2.0.0rc5-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4750b305c80712e1a94c6ddfcdcbde374555628a16f89f7e4e7a944f8cecd37e", size = 78093, upload-time = "2025-10-16T05:40:52.086Z" }, - { url = "https://files.pythonhosted.org/packages/6e/73/3ceb99b6e434ded4bb421419aee260f3ce9097bfc9e1f9b76f0db6ea44fc/wrapt-2.0.0rc5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e33249b14933a737589ccdc8b005f30b1c995e4c98cfaa323922585d13d13399", size = 61171, upload-time = "2025-10-16T05:40:53.074Z" }, - { url = "https://files.pythonhosted.org/packages/2f/4e/ea2077d976fcf708ed01eafe08270b343c684ab155f6a25b36eb071c8616/wrapt-2.0.0rc5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a9cbcd3d49e37edc15944be15c4c5e617acc46feb9b0fad8396a76888291c439", size = 61691, upload-time = "2025-10-16T05:40:54.088Z" }, - { url = "https://files.pythonhosted.org/packages/09/11/117e9811478420fa9b8fbb0d370ed6dd6f8e730f9929e0798e012d1dda87/wrapt-2.0.0rc5-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5bd3aba6c71bcb395b790e03ef22e9d440461e0446826f59e4d9f4926cbccf60", size = 121117, upload-time = "2025-10-16T05:40:57.045Z" }, - { url = "https://files.pythonhosted.org/packages/f5/4c/e3053962a47dd42c7302a978b197efd91ee8a4160fa98a46cef33c74a5ba/wrapt-2.0.0rc5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a9a3b6700b569e8b8477356ef45840dc02a25150b503159c0a8d31ffe5e2954", size = 122468, upload-time = "2025-10-16T05:40:58.321Z" }, - { url = "https://files.pythonhosted.org/packages/05/72/cf3f7fe0f4bcb4875024197f549362ad1e2a29d8e61e1c048c9727e95f29/wrapt-2.0.0rc5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ee79ae5fd4565ac8bcebbf5b0e328131806f2d6d58a97293bf818caf95d5ccb5", size = 116790, upload-time = "2025-10-16T05:40:55.062Z" }, - { url = "https://files.pythonhosted.org/packages/52/60/94c42ac93e6812080475edddd637cd6d043d0efda05de06cbf7af9445003/wrapt-2.0.0rc5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7d5d7888fa87eaf12f5b304d3d1bb3ef754139efd434173de283ea7372b193ff", size = 120881, upload-time = "2025-10-16T05:40:59.704Z" }, - { url = "https://files.pythonhosted.org/packages/1e/1d/7df35971283985d4c7e459ae3b60ea6772991724fa9da027e0ef85445ce0/wrapt-2.0.0rc5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:535830b64fc4be8c335cc55283c22bd82487b08713279dc628639c97ad3003c4", size = 115805, upload-time = "2025-10-16T05:41:00.747Z" }, - { url = "https://files.pythonhosted.org/packages/89/05/4f78dbc6beff02cf4f4df6a1bf43cc5e11cdc823c7b049792d2567296ef5/wrapt-2.0.0rc5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8b902ac39edbd7dac763ac7e8347a49176a216ba48821135d4dcdc393d26cdd7", size = 120221, upload-time = "2025-10-16T05:41:01.804Z" }, - { url = "https://files.pythonhosted.org/packages/b9/c1/3bdbd54489a19a1e138dfef819487e6063943946e906a75d25a316197c73/wrapt-2.0.0rc5-cp312-cp312-win32.whl", hash = "sha256:d5b203c694e06064ebcdac91548857e3416ceba27615c341ee7f98e7305a563c", size = 58190, upload-time = "2025-10-16T05:41:05.682Z" }, - { url = "https://files.pythonhosted.org/packages/61/ed/5c3156a2613b3f054ef40d0dfc335c7bf1f399bea78aced9253d3113dfcf/wrapt-2.0.0rc5-cp312-cp312-win_amd64.whl", hash = "sha256:31415c6779045134665e1f5ee5413793fcd780cff669ad21c24a46e17e575098", size = 60421, upload-time = "2025-10-16T05:41:03.183Z" }, - { url = "https://files.pythonhosted.org/packages/ba/29/e2e1b6da585e1225946e9c649859a04a672a5ef9541625ef0da84e199786/wrapt-2.0.0rc5-cp312-cp312-win_arm64.whl", hash = "sha256:8ee44dfae990e273a615f220760834ac95a1c6bb1c687b2d834cfc18f3753a67", size = 58942, upload-time = "2025-10-16T05:41:04.539Z" }, - { url = "https://files.pythonhosted.org/packages/06/54/24dbd27f95960048722e472c8adb1382d602bc2b3f3e62a66dab6417ecbe/wrapt-2.0.0rc5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2a3d4a0ea3e4deecc54e480759285047383243e06697b5b93059a786a1c478ed", size = 78099, upload-time = "2025-10-16T05:41:06.795Z" }, - { url = "https://files.pythonhosted.org/packages/c9/cd/ccd6e81754ccc132ce768ea9b8b27d7c12797399b3356a91035e23ae56f6/wrapt-2.0.0rc5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:743c9f74aad945e77bb987b79bb9563703fb934cb9a178ca2ca842b2a52e8cd7", size = 61173, upload-time = "2025-10-16T05:41:07.783Z" }, - { url = "https://files.pythonhosted.org/packages/60/bd/c738081209cca7e88a1a7edabccee7e80f6ea263ed06c08edb37d1fcfb19/wrapt-2.0.0rc5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:213cdf861283244f6d018a83e4a632842e1dac15f177574cf2bb5de0dfe42c79", size = 61690, upload-time = "2025-10-16T05:41:09.329Z" }, - { url = "https://files.pythonhosted.org/packages/06/02/11c51633c58723d43216c73ed764a77b40b3a699c3387305994f0ccbcd49/wrapt-2.0.0rc5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:9bf37aa84c34ac526acf92bda8f4597840f21166505a3fc6a24fb6bd352f9784", size = 121141, upload-time = "2025-10-16T05:41:11.916Z" }, - { url = "https://files.pythonhosted.org/packages/d3/24/f482f1da723ae5fcbf2344f01db3de600504007a4d942a39f3eeff94e44e/wrapt-2.0.0rc5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1476b93afdce3b8fa5899e432b34209b159df7ea13d86f4255e229a49da2bf2e", size = 122515, upload-time = "2025-10-16T05:41:12.976Z" }, - { url = "https://files.pythonhosted.org/packages/a2/c8/896c6c6f5e3de8e07dc65856482fd08eba95279b8d0b5bb96ba44af5de87/wrapt-2.0.0rc5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d611ff3648403dd4f89ba61492c3271bfaf990542e08976a7b289d7a9414aefc", size = 116834, upload-time = "2025-10-16T05:41:10.624Z" }, - { url = "https://files.pythonhosted.org/packages/a4/4c/41762f6eb45bad4ff21e3bd9dc90d8b58631176977b8369ec8f8e815a0e9/wrapt-2.0.0rc5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d8d46f194c3fbaab3501d462963c852fd726566a6a60b8d060ffe13db167054f", size = 120924, upload-time = "2025-10-16T05:41:14.024Z" }, - { url = "https://files.pythonhosted.org/packages/6a/ff/01f4d28c85b221d3e58224902dda9e993a6c6f3d766fb73c233d069cb085/wrapt-2.0.0rc5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:9b5c6d44e67e36a6c52568861228f6c1e87b30e350e403e8351fc19a13c9f8a1", size = 115842, upload-time = "2025-10-16T05:41:15.407Z" }, - { url = "https://files.pythonhosted.org/packages/10/8b/8dd9de3ca56e51be772895b477c431a31e549b2e959f54bddc6aba2952c4/wrapt-2.0.0rc5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:59cd63af9b936aa0a4470c54f5a6621cf5ad21668978a9fc125bac2c10cf2e3a", size = 120252, upload-time = "2025-10-16T05:41:17.188Z" }, - { url = "https://files.pythonhosted.org/packages/24/c1/606d4a0c335ca7182b8464b1e70eb338a20d085e68ed4fac8432b9e0daf5/wrapt-2.0.0rc5-cp313-cp313-win32.whl", hash = "sha256:7ee672ec5f0f5edb112b82fd8ec2ffc16c0391c24506c2b46018dd8ecf13d707", size = 58193, upload-time = "2025-10-16T05:41:21.553Z" }, - { url = "https://files.pythonhosted.org/packages/62/66/c21260d7f0229d1e7eb40089d462b4cf3af511896a79867a57fd276741e5/wrapt-2.0.0rc5-cp313-cp313-win_amd64.whl", hash = "sha256:c8446447678f95f4cf11967385f5f40df947d19dbab554165e56b4f3f752697e", size = 60423, upload-time = "2025-10-16T05:41:18.725Z" }, - { url = "https://files.pythonhosted.org/packages/80/ea/2c3c917d4972b737ac9c5ac738767351f8995d5b4a168e9114bb0c30e502/wrapt-2.0.0rc5-cp313-cp313-win_arm64.whl", hash = "sha256:5748ec10a65b1e607167dc3dcd4cd5fa985528b8e51e088580c4033eabdb02ec", size = 58947, upload-time = "2025-10-16T05:41:19.997Z" }, - { url = "https://files.pythonhosted.org/packages/4f/91/fe5f4c97d0b0d77ee910c1c4cd4a67038c3b9c73ebddcd256c84f6823f29/wrapt-2.0.0rc5-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:69702e7538fce422906c135554c828f0c50393149df624bde1ad52da6d5e2c70", size = 81935, upload-time = "2025-10-16T05:41:22.834Z" }, - { url = "https://files.pythonhosted.org/packages/21/a9/ac98fe189e18f1d8adae56bf34f3521336d99e02625f72211c4aed7d4d93/wrapt-2.0.0rc5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:852f374dc49d4f13d4494f26dc35eae4e8ad4e79b40cbdf23a579e192ae0bbb8", size = 62926, upload-time = "2025-10-16T05:41:24.173Z" }, - { url = "https://files.pythonhosted.org/packages/eb/a4/b4b429f5ca80a4d8c0ef73e77c3102a8055d5ee1a61cca35dc5e5ea1c377/wrapt-2.0.0rc5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9e19366bfe505342bf9a226dbf7ae5e7faef5b054ba78c5bcb6e3cb8b03c27cf", size = 63605, upload-time = "2025-10-16T05:41:25.166Z" }, - { url = "https://files.pythonhosted.org/packages/4c/30/a41d42a258b7b6abbe8e1cb05daccfa60ed8a20800c2da9ae30ec33c4797/wrapt-2.0.0rc5-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:51c95fc0cde2a48bbdc0ef0ce7980b43547641685aef9dae06e7e55a28ce9418", size = 151635, upload-time = "2025-10-16T05:41:28.324Z" }, - { url = "https://files.pythonhosted.org/packages/e0/41/b8b6b36a7fcb97daaa836c6fafde11b75dd89bea00c8e42e6e556a814d91/wrapt-2.0.0rc5-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b9a8c28d3b3a70d257dff74c8a94ab3eba6bdab6db3d4f149f6ef4adc2f9d0a2", size = 157529, upload-time = "2025-10-16T05:41:29.737Z" }, - { url = "https://files.pythonhosted.org/packages/b7/1e/cd31b69c5dde92586bcab050c5a61c8b54bc31d7be7c4dbd9f2d72fc2946/wrapt-2.0.0rc5-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:140e10b4321cca537d4c1804399cee0c1e9c509e0ce3d50739879a39925d2b19", size = 145036, upload-time = "2025-10-16T05:41:26.463Z" }, - { url = "https://files.pythonhosted.org/packages/22/b8/ea5b8cc725419d97f2e7541858222a592e69307842b816499a0160ce5804/wrapt-2.0.0rc5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b780a2f93f8e121e4d7e83d0c9a3315d0792e5231e8b6507dee4cc30fc306f7", size = 154548, upload-time = "2025-10-16T05:41:30.896Z" }, - { url = "https://files.pythonhosted.org/packages/a6/16/335516a54ac3c22315b0e3e5cf5cf1f610303356c60023143ecac29cc3c4/wrapt-2.0.0rc5-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:78af75c5f022b5417ac6e8e27aa1a4698f93ddfaf164a3039687873877ff6c22", size = 143456, upload-time = "2025-10-16T05:41:32.68Z" }, - { url = "https://files.pythonhosted.org/packages/7c/18/f20e96034eeb7b123f3c4782b33a2c372ef3187ec77e1591ef393c2dea42/wrapt-2.0.0rc5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e0ce6ef3919afc4546e3b83b5c5399624dfd1b1b0a78eeb05c79020b94c5a6a2", size = 149372, upload-time = "2025-10-16T05:41:34.557Z" }, - { url = "https://files.pythonhosted.org/packages/f2/b5/f52dea903ea2fd10984b5848998117888518796c1eda34b9b73950d3c3fc/wrapt-2.0.0rc5-cp313-cp313t-win32.whl", hash = "sha256:9f0ba54cf5922f7c106a07b1aede2a1810289bf1433c79a04cf68157c48c8892", size = 59854, upload-time = "2025-10-16T05:41:38.353Z" }, - { url = "https://files.pythonhosted.org/packages/54/83/56546f16fd5b4c24e4caf0259e6197610bef2a481415685f826c177db6b2/wrapt-2.0.0rc5-cp313-cp313t-win_amd64.whl", hash = "sha256:fed4a4294327bcc3111cb6ba9efb09adccd04288ecf7e23e7629496c4547e19a", size = 63118, upload-time = "2025-10-16T05:41:35.705Z" }, - { url = "https://files.pythonhosted.org/packages/5e/bb/d4a8ed4d6d4bc262f82f69c79508f9b03d7efd465278f28177e4c73edfe4/wrapt-2.0.0rc5-cp313-cp313t-win_arm64.whl", hash = "sha256:13f5fbc142f52082efb597d2e47536c3a52bfb91c1f9a414d86942c8bf0cb866", size = 60371, upload-time = "2025-10-16T05:41:36.824Z" }, - { url = "https://files.pythonhosted.org/packages/ab/e8/363993e86e5a2ce1fa2370d8ee48f8f9a7c1fc3088c875530b4aafb29a4a/wrapt-2.0.0rc5-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da413190cf2ce273eede818d842c120baebc9e8c957b40db74f5544dc0342a86", size = 78229, upload-time = "2025-10-16T05:41:39.71Z" }, - { url = "https://files.pythonhosted.org/packages/c5/93/ecb551e36be23624ace6238de64a3906c339e856b68b8e8d4100e04d47b4/wrapt-2.0.0rc5-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8a2ddb7a2eb6fcd5715b5f7dff78cc7b4319b6799ecf7820938b595fba24d8a2", size = 61234, upload-time = "2025-10-16T05:41:40.757Z" }, - { url = "https://files.pythonhosted.org/packages/05/d9/f13de10a2e834d9a7834f19b47ffe521bc7a1171dba7c265ac49f1d28d40/wrapt-2.0.0rc5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1c448153a7fc56e4e26668d26947b6e9b17a310e53b00a4197f59a6f1f3687be", size = 61760, upload-time = "2025-10-16T05:41:41.77Z" }, - { url = "https://files.pythonhosted.org/packages/ee/76/4ea95e003f7d408dea6ac0435ce4f7b3e67399587eca27881aec36157d98/wrapt-2.0.0rc5-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:ea15caf6c969525a9a13a072ed7f66b7e200bd226bafa06af9b1cbb5c5ebcba3", size = 119969, upload-time = "2025-10-16T05:41:44.783Z" }, - { url = "https://files.pythonhosted.org/packages/e3/c8/23fefaca5cdf2f3dca489ff88e392c915933a548d5652ea63e00c3b3d823/wrapt-2.0.0rc5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cd043239057835d4ad9ed8eb04e641bf00b39a23a86646b6dffc57559c0e76e9", size = 122266, upload-time = "2025-10-16T05:41:45.838Z" }, - { url = "https://files.pythonhosted.org/packages/f4/8b/83f6f7ee3adfb84506678ffc288cda846fd0ade7ee60f2b32f12b30bf0e1/wrapt-2.0.0rc5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:63465c656e4f3c2125ca72e0fcd02fd9410aa7c4696a236f880adff04c43d539", size = 116945, upload-time = "2025-10-16T05:41:43.483Z" }, - { url = "https://files.pythonhosted.org/packages/4e/f4/456731e349b5bc3acbe8ca700fec12cfc4bb0bb040f57d806ce21748d3c2/wrapt-2.0.0rc5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cb12e4f3b4ab65d5c9645a426978e1216cd7dd540f310b157423d26a412353d0", size = 120785, upload-time = "2025-10-16T05:41:47.1Z" }, - { url = "https://files.pythonhosted.org/packages/bf/a1/bffdd04e3e67682595af320f9ceb0082f974b16f2d22c6439b1ec4086ae7/wrapt-2.0.0rc5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:7c10865f52f17bb2764db9e537c8e264180ef7f67819ff50b8ab0ff3a0ba4a78", size = 115854, upload-time = "2025-10-16T05:41:48.601Z" }, - { url = "https://files.pythonhosted.org/packages/a0/bb/7eaf21e694c80c1fad6bdbef8610e8c2f6d89fafb4cdd3075fd3454bde5f/wrapt-2.0.0rc5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:823a8850352480ed99fda30464492451acf2feffc2eb41df12fe79bdcd9234d7", size = 119176, upload-time = "2025-10-16T05:41:49.706Z" }, - { url = "https://files.pythonhosted.org/packages/32/62/03170931bc28bd9d02acc6122fa34c528eb10adc4b2d239a578e403f82e8/wrapt-2.0.0rc5-cp314-cp314-win32.whl", hash = "sha256:1258c10a8a0eb2e6aff8f06ed5d32a1bfaad24b1dbbec2ef917c4bae387dc7a6", size = 58673, upload-time = "2025-10-16T05:41:53.216Z" }, - { url = "https://files.pythonhosted.org/packages/38/11/9659431c85af2f860d9c46ca4b220b52783ceddf0a1121b1e452f81635e3/wrapt-2.0.0rc5-cp314-cp314-win_amd64.whl", hash = "sha256:1df509f43a220cde4016d621e9ce9d776bd443f8e583ee72aad87156e0f1b808", size = 60945, upload-time = "2025-10-16T05:41:51.167Z" }, - { url = "https://files.pythonhosted.org/packages/84/fc/83d1d42c59b45b35920a09f49f9017e523a17232e2db1e82f0d23228a2a3/wrapt-2.0.0rc5-cp314-cp314-win_arm64.whl", hash = "sha256:480628da3585e0637da62acbdf87ba19a1a00508b058b22577f42c7d84ffb14d", size = 59343, upload-time = "2025-10-16T05:41:52.199Z" }, - { url = "https://files.pythonhosted.org/packages/5d/3e/4f41b295649868e2f2d00e0a1d3ce7226e4baa0c8ce6c821442292e6e3ca/wrapt-2.0.0rc5-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:0b513277cd94eba52395e277d50ccd5bf2ecdc40d116dca50baad4de2f47be46", size = 81942, upload-time = "2025-10-16T05:41:54.282Z" }, - { url = "https://files.pythonhosted.org/packages/2c/1d/952c9f176784d02989b0da0f3619665021c6dad98cfb461f27a85bdc1acb/wrapt-2.0.0rc5-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:90dda73836f8bde801fa53b59e16b7863f7897b19e3aa8bea938c4db33a2fc9b", size = 62926, upload-time = "2025-10-16T05:41:55.303Z" }, - { url = "https://files.pythonhosted.org/packages/26/5d/4f46b72d6719ba9049e0e093c260f700a2793a01c47b83bbae7ad3529982/wrapt-2.0.0rc5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:4e91eedff840e90de3abd694096aefe4916564e0391429b9af5851aecdfd961e", size = 63606, upload-time = "2025-10-16T05:41:56.887Z" }, - { url = "https://files.pythonhosted.org/packages/52/4f/b56c4d7516789fb246c48df8de0a000478c511583bafb3776dec16260145/wrapt-2.0.0rc5-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:3f9b6a6138dabe45948630836ed1bd090f594f900b70b8ecaedd65c2d0ed9465", size = 151635, upload-time = "2025-10-16T05:41:59.434Z" }, - { url = "https://files.pythonhosted.org/packages/29/da/85fd0733a2f8107a1fa96bc26c72b0874df3528ae621b8dbc3ac97a81c99/wrapt-2.0.0rc5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2130d94898b13fc6a1469daa3897c4fffa7b6c3abfa2469785e696555498f42f", size = 157530, upload-time = "2025-10-16T05:42:00.654Z" }, - { url = "https://files.pythonhosted.org/packages/72/65/39c4b726f3bc42d3918b75fab8d52c234d9a6113e458e832f52df22b51c7/wrapt-2.0.0rc5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4aae5942fe69d27409f2d700afaaaf76fbb7ca27436f16aad9baf0220d68c258", size = 145056, upload-time = "2025-10-16T05:41:58.263Z" }, - { url = "https://files.pythonhosted.org/packages/db/f3/47b32f02b9fe1aa87ed532e007c70f24a9fca6c12a4f2bfe6720ef37c13d/wrapt-2.0.0rc5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:9ed20a56e98c9c5474894c4bf1c88024ec4d76e519440f4508191b4d6292eab4", size = 154541, upload-time = "2025-10-16T05:42:01.765Z" }, - { url = "https://files.pythonhosted.org/packages/13/eb/0ce25991243d2ed79bc106175f2f1356654905ed4b5b590d08ca48bf7732/wrapt-2.0.0rc5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:524777f776b4f886aa35baff21a2c5f1a967d3d1b2d26d423094e25e8fe98cf1", size = 143448, upload-time = "2025-10-16T05:42:03.046Z" }, - { url = "https://files.pythonhosted.org/packages/c0/4a/627dc73f9b3b0441660a98d2fb0b77d27fc9a0a83a9789b58e1297565ee3/wrapt-2.0.0rc5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:4b3a02c3313cc82001d5dbb9b7b452377ca01e0ac34fb1a1aeee784a0fe80de5", size = 149391, upload-time = "2025-10-16T05:42:04.194Z" }, - { url = "https://files.pythonhosted.org/packages/4b/45/0688a00c33e3bc177ad262b6a7cffc69d547abb86b7c9901b0295029f889/wrapt-2.0.0rc5-cp314-cp314t-win32.whl", hash = "sha256:273c8244de7714fa79c7510c8b787a8bb6fab8bbbe299866d392b99e9c34f98f", size = 60579, upload-time = "2025-10-16T05:42:07.649Z" }, - { url = "https://files.pythonhosted.org/packages/ee/ed/0707864c474f931b791210b98cb2de9a9dd7ed6c9cadb4d1fd5520afd6e4/wrapt-2.0.0rc5-cp314-cp314t-win_amd64.whl", hash = "sha256:d8f89df76f0ff8b876065424c0699e93f0e82e4dd23e3454b8fcdf3322319788", size = 63894, upload-time = "2025-10-16T05:42:05.323Z" }, - { url = "https://files.pythonhosted.org/packages/d2/53/98586856330a8bca326b2cfec360ead24b8de03327401194ec6baba4a6c4/wrapt-2.0.0rc5-cp314-cp314t-win_arm64.whl", hash = "sha256:4d9be2e727a88a68acb1466f9ab9803940f858effbc1b972480b194db18d8a5b", size = 60624, upload-time = "2025-10-16T05:42:06.455Z" }, - { url = "https://files.pythonhosted.org/packages/4b/d5/0db5eac4d795bce584f20e11bd71c0690a38e2577128ce73ccbbcebc587a/wrapt-2.0.0rc5-py3-none-any.whl", hash = "sha256:f0c89fd91cda69a9e02cdaa19d2734df6c005cb3f36a3a7079f266b0a731d95b", size = 29389, upload-time = "2025-10-16T05:42:37.564Z" }, +version = "1.17.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/8f/aeb76c5b46e273670962298c23e7ddde79916cb74db802131d49a85e4b7d/wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0", size = 55547, upload-time = "2025-08-12T05:53:21.714Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/23/bb82321b86411eb51e5a5db3fb8f8032fd30bd7c2d74bfe936136b2fa1d6/wrapt-1.17.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:88bbae4d40d5a46142e70d58bf664a89b6b4befaea7b2ecc14e03cedb8e06c04", size = 53482, upload-time = "2025-08-12T05:51:44.467Z" }, + { url = "https://files.pythonhosted.org/packages/45/69/f3c47642b79485a30a59c63f6d739ed779fb4cc8323205d047d741d55220/wrapt-1.17.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b13af258d6a9ad602d57d889f83b9d5543acd471eee12eb51f5b01f8eb1bc2", size = 38676, upload-time = "2025-08-12T05:51:32.636Z" }, + { url = "https://files.pythonhosted.org/packages/d1/71/e7e7f5670c1eafd9e990438e69d8fb46fa91a50785332e06b560c869454f/wrapt-1.17.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd341868a4b6714a5962c1af0bd44f7c404ef78720c7de4892901e540417111c", size = 38957, upload-time = "2025-08-12T05:51:54.655Z" }, + { url = "https://files.pythonhosted.org/packages/de/17/9f8f86755c191d6779d7ddead1a53c7a8aa18bccb7cea8e7e72dfa6a8a09/wrapt-1.17.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f9b2601381be482f70e5d1051a5965c25fb3625455a2bf520b5a077b22afb775", size = 81975, upload-time = "2025-08-12T05:52:30.109Z" }, + { url = "https://files.pythonhosted.org/packages/f2/15/dd576273491f9f43dd09fce517f6c2ce6eb4fe21681726068db0d0467096/wrapt-1.17.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:343e44b2a8e60e06a7e0d29c1671a0d9951f59174f3709962b5143f60a2a98bd", size = 83149, upload-time = "2025-08-12T05:52:09.316Z" }, + { url = "https://files.pythonhosted.org/packages/0c/c4/5eb4ce0d4814521fee7aa806264bf7a114e748ad05110441cd5b8a5c744b/wrapt-1.17.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:33486899acd2d7d3066156b03465b949da3fd41a5da6e394ec49d271baefcf05", size = 82209, upload-time = "2025-08-12T05:52:10.331Z" }, + { url = "https://files.pythonhosted.org/packages/31/4b/819e9e0eb5c8dc86f60dfc42aa4e2c0d6c3db8732bce93cc752e604bb5f5/wrapt-1.17.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e6f40a8aa5a92f150bdb3e1c44b7e98fb7113955b2e5394122fa5532fec4b418", size = 81551, upload-time = "2025-08-12T05:52:31.137Z" }, + { url = "https://files.pythonhosted.org/packages/f8/83/ed6baf89ba3a56694700139698cf703aac9f0f9eb03dab92f57551bd5385/wrapt-1.17.3-cp310-cp310-win32.whl", hash = "sha256:a36692b8491d30a8c75f1dfee65bef119d6f39ea84ee04d9f9311f83c5ad9390", size = 36464, upload-time = "2025-08-12T05:53:01.204Z" }, + { url = "https://files.pythonhosted.org/packages/2f/90/ee61d36862340ad7e9d15a02529df6b948676b9a5829fd5e16640156627d/wrapt-1.17.3-cp310-cp310-win_amd64.whl", hash = "sha256:afd964fd43b10c12213574db492cb8f73b2f0826c8df07a68288f8f19af2ebe6", size = 38748, upload-time = "2025-08-12T05:53:00.209Z" }, + { url = "https://files.pythonhosted.org/packages/bd/c3/cefe0bd330d389c9983ced15d326f45373f4073c9f4a8c2f99b50bfea329/wrapt-1.17.3-cp310-cp310-win_arm64.whl", hash = "sha256:af338aa93554be859173c39c85243970dc6a289fa907402289eeae7543e1ae18", size = 36810, upload-time = "2025-08-12T05:52:51.906Z" }, + { url = "https://files.pythonhosted.org/packages/52/db/00e2a219213856074a213503fdac0511203dceefff26e1daa15250cc01a0/wrapt-1.17.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:273a736c4645e63ac582c60a56b0acb529ef07f78e08dc6bfadf6a46b19c0da7", size = 53482, upload-time = "2025-08-12T05:51:45.79Z" }, + { url = "https://files.pythonhosted.org/packages/5e/30/ca3c4a5eba478408572096fe9ce36e6e915994dd26a4e9e98b4f729c06d9/wrapt-1.17.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5531d911795e3f935a9c23eb1c8c03c211661a5060aab167065896bbf62a5f85", size = 38674, upload-time = "2025-08-12T05:51:34.629Z" }, + { url = "https://files.pythonhosted.org/packages/31/25/3e8cc2c46b5329c5957cec959cb76a10718e1a513309c31399a4dad07eb3/wrapt-1.17.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0610b46293c59a3adbae3dee552b648b984176f8562ee0dba099a56cfbe4df1f", size = 38959, upload-time = "2025-08-12T05:51:56.074Z" }, + { url = "https://files.pythonhosted.org/packages/5d/8f/a32a99fc03e4b37e31b57cb9cefc65050ea08147a8ce12f288616b05ef54/wrapt-1.17.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b32888aad8b6e68f83a8fdccbf3165f5469702a7544472bdf41f582970ed3311", size = 82376, upload-time = "2025-08-12T05:52:32.134Z" }, + { url = "https://files.pythonhosted.org/packages/31/57/4930cb8d9d70d59c27ee1332a318c20291749b4fba31f113c2f8ac49a72e/wrapt-1.17.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cccf4f81371f257440c88faed6b74f1053eef90807b77e31ca057b2db74edb1", size = 83604, upload-time = "2025-08-12T05:52:11.663Z" }, + { url = "https://files.pythonhosted.org/packages/a8/f3/1afd48de81d63dd66e01b263a6fbb86e1b5053b419b9b33d13e1f6d0f7d0/wrapt-1.17.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8a210b158a34164de8bb68b0e7780041a903d7b00c87e906fb69928bf7890d5", size = 82782, upload-time = "2025-08-12T05:52:12.626Z" }, + { url = "https://files.pythonhosted.org/packages/1e/d7/4ad5327612173b144998232f98a85bb24b60c352afb73bc48e3e0d2bdc4e/wrapt-1.17.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:79573c24a46ce11aab457b472efd8d125e5a51da2d1d24387666cd85f54c05b2", size = 82076, upload-time = "2025-08-12T05:52:33.168Z" }, + { url = "https://files.pythonhosted.org/packages/bb/59/e0adfc831674a65694f18ea6dc821f9fcb9ec82c2ce7e3d73a88ba2e8718/wrapt-1.17.3-cp311-cp311-win32.whl", hash = "sha256:c31eebe420a9a5d2887b13000b043ff6ca27c452a9a22fa71f35f118e8d4bf89", size = 36457, upload-time = "2025-08-12T05:53:03.936Z" }, + { url = "https://files.pythonhosted.org/packages/83/88/16b7231ba49861b6f75fc309b11012ede4d6b0a9c90969d9e0db8d991aeb/wrapt-1.17.3-cp311-cp311-win_amd64.whl", hash = "sha256:0b1831115c97f0663cb77aa27d381237e73ad4f721391a9bfb2fe8bc25fa6e77", size = 38745, upload-time = "2025-08-12T05:53:02.885Z" }, + { url = "https://files.pythonhosted.org/packages/9a/1e/c4d4f3398ec073012c51d1c8d87f715f56765444e1a4b11e5180577b7e6e/wrapt-1.17.3-cp311-cp311-win_arm64.whl", hash = "sha256:5a7b3c1ee8265eb4c8f1b7d29943f195c00673f5ab60c192eba2d4a7eae5f46a", size = 36806, upload-time = "2025-08-12T05:52:53.368Z" }, + { url = "https://files.pythonhosted.org/packages/9f/41/cad1aba93e752f1f9268c77270da3c469883d56e2798e7df6240dcb2287b/wrapt-1.17.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ab232e7fdb44cdfbf55fc3afa31bcdb0d8980b9b95c38b6405df2acb672af0e0", size = 53998, upload-time = "2025-08-12T05:51:47.138Z" }, + { url = "https://files.pythonhosted.org/packages/60/f8/096a7cc13097a1869fe44efe68dace40d2a16ecb853141394047f0780b96/wrapt-1.17.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9baa544e6acc91130e926e8c802a17f3b16fbea0fd441b5a60f5cf2cc5c3deba", size = 39020, upload-time = "2025-08-12T05:51:35.906Z" }, + { url = "https://files.pythonhosted.org/packages/33/df/bdf864b8997aab4febb96a9ae5c124f700a5abd9b5e13d2a3214ec4be705/wrapt-1.17.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6b538e31eca1a7ea4605e44f81a48aa24c4632a277431a6ed3f328835901f4fd", size = 39098, upload-time = "2025-08-12T05:51:57.474Z" }, + { url = "https://files.pythonhosted.org/packages/9f/81/5d931d78d0eb732b95dc3ddaeeb71c8bb572fb01356e9133916cd729ecdd/wrapt-1.17.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:042ec3bb8f319c147b1301f2393bc19dba6e176b7da446853406d041c36c7828", size = 88036, upload-time = "2025-08-12T05:52:34.784Z" }, + { url = "https://files.pythonhosted.org/packages/ca/38/2e1785df03b3d72d34fc6252d91d9d12dc27a5c89caef3335a1bbb8908ca/wrapt-1.17.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3af60380ba0b7b5aeb329bc4e402acd25bd877e98b3727b0135cb5c2efdaefe9", size = 88156, upload-time = "2025-08-12T05:52:13.599Z" }, + { url = "https://files.pythonhosted.org/packages/b3/8b/48cdb60fe0603e34e05cffda0b2a4adab81fd43718e11111a4b0100fd7c1/wrapt-1.17.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b02e424deef65c9f7326d8c19220a2c9040c51dc165cddb732f16198c168396", size = 87102, upload-time = "2025-08-12T05:52:14.56Z" }, + { url = "https://files.pythonhosted.org/packages/3c/51/d81abca783b58f40a154f1b2c56db1d2d9e0d04fa2d4224e357529f57a57/wrapt-1.17.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:74afa28374a3c3a11b3b5e5fca0ae03bef8450d6aa3ab3a1e2c30e3a75d023dc", size = 87732, upload-time = "2025-08-12T05:52:36.165Z" }, + { url = "https://files.pythonhosted.org/packages/9e/b1/43b286ca1392a006d5336412d41663eeef1ad57485f3e52c767376ba7e5a/wrapt-1.17.3-cp312-cp312-win32.whl", hash = "sha256:4da9f45279fff3543c371d5ababc57a0384f70be244de7759c85a7f989cb4ebe", size = 36705, upload-time = "2025-08-12T05:53:07.123Z" }, + { url = "https://files.pythonhosted.org/packages/28/de/49493f962bd3c586ab4b88066e967aa2e0703d6ef2c43aa28cb83bf7b507/wrapt-1.17.3-cp312-cp312-win_amd64.whl", hash = "sha256:e71d5c6ebac14875668a1e90baf2ea0ef5b7ac7918355850c0908ae82bcb297c", size = 38877, upload-time = "2025-08-12T05:53:05.436Z" }, + { url = "https://files.pythonhosted.org/packages/f1/48/0f7102fe9cb1e8a5a77f80d4f0956d62d97034bbe88d33e94699f99d181d/wrapt-1.17.3-cp312-cp312-win_arm64.whl", hash = "sha256:604d076c55e2fdd4c1c03d06dc1a31b95130010517b5019db15365ec4a405fc6", size = 36885, upload-time = "2025-08-12T05:52:54.367Z" }, + { url = "https://files.pythonhosted.org/packages/fc/f6/759ece88472157acb55fc195e5b116e06730f1b651b5b314c66291729193/wrapt-1.17.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a47681378a0439215912ef542c45a783484d4dd82bac412b71e59cf9c0e1cea0", size = 54003, upload-time = "2025-08-12T05:51:48.627Z" }, + { url = "https://files.pythonhosted.org/packages/4f/a9/49940b9dc6d47027dc850c116d79b4155f15c08547d04db0f07121499347/wrapt-1.17.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:54a30837587c6ee3cd1a4d1c2ec5d24e77984d44e2f34547e2323ddb4e22eb77", size = 39025, upload-time = "2025-08-12T05:51:37.156Z" }, + { url = "https://files.pythonhosted.org/packages/45/35/6a08de0f2c96dcdd7fe464d7420ddb9a7655a6561150e5fc4da9356aeaab/wrapt-1.17.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:16ecf15d6af39246fe33e507105d67e4b81d8f8d2c6598ff7e3ca1b8a37213f7", size = 39108, upload-time = "2025-08-12T05:51:58.425Z" }, + { url = "https://files.pythonhosted.org/packages/0c/37/6faf15cfa41bf1f3dba80cd3f5ccc6622dfccb660ab26ed79f0178c7497f/wrapt-1.17.3-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:6fd1ad24dc235e4ab88cda009e19bf347aabb975e44fd5c2fb22a3f6e4141277", size = 88072, upload-time = "2025-08-12T05:52:37.53Z" }, + { url = "https://files.pythonhosted.org/packages/78/f2/efe19ada4a38e4e15b6dff39c3e3f3f73f5decf901f66e6f72fe79623a06/wrapt-1.17.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0ed61b7c2d49cee3c027372df5809a59d60cf1b6c2f81ee980a091f3afed6a2d", size = 88214, upload-time = "2025-08-12T05:52:15.886Z" }, + { url = "https://files.pythonhosted.org/packages/40/90/ca86701e9de1622b16e09689fc24b76f69b06bb0150990f6f4e8b0eeb576/wrapt-1.17.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:423ed5420ad5f5529db9ce89eac09c8a2f97da18eb1c870237e84c5a5c2d60aa", size = 87105, upload-time = "2025-08-12T05:52:17.914Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e0/d10bd257c9a3e15cbf5523025252cc14d77468e8ed644aafb2d6f54cb95d/wrapt-1.17.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e01375f275f010fcbf7f643b4279896d04e571889b8a5b3f848423d91bf07050", size = 87766, upload-time = "2025-08-12T05:52:39.243Z" }, + { url = "https://files.pythonhosted.org/packages/e8/cf/7d848740203c7b4b27eb55dbfede11aca974a51c3d894f6cc4b865f42f58/wrapt-1.17.3-cp313-cp313-win32.whl", hash = "sha256:53e5e39ff71b3fc484df8a522c933ea2b7cdd0d5d15ae82e5b23fde87d44cbd8", size = 36711, upload-time = "2025-08-12T05:53:10.074Z" }, + { url = "https://files.pythonhosted.org/packages/57/54/35a84d0a4d23ea675994104e667ceff49227ce473ba6a59ba2c84f250b74/wrapt-1.17.3-cp313-cp313-win_amd64.whl", hash = "sha256:1f0b2f40cf341ee8cc1a97d51ff50dddb9fcc73241b9143ec74b30fc4f44f6cb", size = 38885, upload-time = "2025-08-12T05:53:08.695Z" }, + { url = "https://files.pythonhosted.org/packages/01/77/66e54407c59d7b02a3c4e0af3783168fff8e5d61def52cda8728439d86bc/wrapt-1.17.3-cp313-cp313-win_arm64.whl", hash = "sha256:7425ac3c54430f5fc5e7b6f41d41e704db073309acfc09305816bc6a0b26bb16", size = 36896, upload-time = "2025-08-12T05:52:55.34Z" }, + { url = "https://files.pythonhosted.org/packages/02/a2/cd864b2a14f20d14f4c496fab97802001560f9f41554eef6df201cd7f76c/wrapt-1.17.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:cf30f6e3c077c8e6a9a7809c94551203c8843e74ba0c960f4a98cd80d4665d39", size = 54132, upload-time = "2025-08-12T05:51:49.864Z" }, + { url = "https://files.pythonhosted.org/packages/d5/46/d011725b0c89e853dc44cceb738a307cde5d240d023d6d40a82d1b4e1182/wrapt-1.17.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e228514a06843cae89621384cfe3a80418f3c04aadf8a3b14e46a7be704e4235", size = 39091, upload-time = "2025-08-12T05:51:38.935Z" }, + { url = "https://files.pythonhosted.org/packages/2e/9e/3ad852d77c35aae7ddebdbc3b6d35ec8013af7d7dddad0ad911f3d891dae/wrapt-1.17.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:5ea5eb3c0c071862997d6f3e02af1d055f381b1d25b286b9d6644b79db77657c", size = 39172, upload-time = "2025-08-12T05:51:59.365Z" }, + { url = "https://files.pythonhosted.org/packages/c3/f7/c983d2762bcce2326c317c26a6a1e7016f7eb039c27cdf5c4e30f4160f31/wrapt-1.17.3-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:281262213373b6d5e4bb4353bc36d1ba4084e6d6b5d242863721ef2bf2c2930b", size = 87163, upload-time = "2025-08-12T05:52:40.965Z" }, + { url = "https://files.pythonhosted.org/packages/e4/0f/f673f75d489c7f22d17fe0193e84b41540d962f75fce579cf6873167c29b/wrapt-1.17.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dc4a8d2b25efb6681ecacad42fca8859f88092d8732b170de6a5dddd80a1c8fa", size = 87963, upload-time = "2025-08-12T05:52:20.326Z" }, + { url = "https://files.pythonhosted.org/packages/df/61/515ad6caca68995da2fac7a6af97faab8f78ebe3bf4f761e1b77efbc47b5/wrapt-1.17.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:373342dd05b1d07d752cecbec0c41817231f29f3a89aa8b8843f7b95992ed0c7", size = 86945, upload-time = "2025-08-12T05:52:21.581Z" }, + { url = "https://files.pythonhosted.org/packages/d3/bd/4e70162ce398462a467bc09e768bee112f1412e563620adc353de9055d33/wrapt-1.17.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d40770d7c0fd5cbed9d84b2c3f2e156431a12c9a37dc6284060fb4bec0b7ffd4", size = 86857, upload-time = "2025-08-12T05:52:43.043Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b8/da8560695e9284810b8d3df8a19396a6e40e7518059584a1a394a2b35e0a/wrapt-1.17.3-cp314-cp314-win32.whl", hash = "sha256:fbd3c8319de8e1dc79d346929cd71d523622da527cca14e0c1d257e31c2b8b10", size = 37178, upload-time = "2025-08-12T05:53:12.605Z" }, + { url = "https://files.pythonhosted.org/packages/db/c8/b71eeb192c440d67a5a0449aaee2310a1a1e8eca41676046f99ed2487e9f/wrapt-1.17.3-cp314-cp314-win_amd64.whl", hash = "sha256:e1a4120ae5705f673727d3253de3ed0e016f7cd78dc463db1b31e2463e1f3cf6", size = 39310, upload-time = "2025-08-12T05:53:11.106Z" }, + { url = "https://files.pythonhosted.org/packages/45/20/2cda20fd4865fa40f86f6c46ed37a2a8356a7a2fde0773269311f2af56c7/wrapt-1.17.3-cp314-cp314-win_arm64.whl", hash = "sha256:507553480670cab08a800b9463bdb881b2edeed77dc677b0a5915e6106e91a58", size = 37266, upload-time = "2025-08-12T05:52:56.531Z" }, + { url = "https://files.pythonhosted.org/packages/77/ed/dd5cf21aec36c80443c6f900449260b80e2a65cf963668eaef3b9accce36/wrapt-1.17.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ed7c635ae45cfbc1a7371f708727bf74690daedc49b4dba310590ca0bd28aa8a", size = 56544, upload-time = "2025-08-12T05:51:51.109Z" }, + { url = "https://files.pythonhosted.org/packages/8d/96/450c651cc753877ad100c7949ab4d2e2ecc4d97157e00fa8f45df682456a/wrapt-1.17.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:249f88ed15503f6492a71f01442abddd73856a0032ae860de6d75ca62eed8067", size = 40283, upload-time = "2025-08-12T05:51:39.912Z" }, + { url = "https://files.pythonhosted.org/packages/d1/86/2fcad95994d9b572db57632acb6f900695a648c3e063f2cd344b3f5c5a37/wrapt-1.17.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:5a03a38adec8066d5a37bea22f2ba6bbf39fcdefbe2d91419ab864c3fb515454", size = 40366, upload-time = "2025-08-12T05:52:00.693Z" }, + { url = "https://files.pythonhosted.org/packages/64/0e/f4472f2fdde2d4617975144311f8800ef73677a159be7fe61fa50997d6c0/wrapt-1.17.3-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:5d4478d72eb61c36e5b446e375bbc49ed002430d17cdec3cecb36993398e1a9e", size = 108571, upload-time = "2025-08-12T05:52:44.521Z" }, + { url = "https://files.pythonhosted.org/packages/cc/01/9b85a99996b0a97c8a17484684f206cbb6ba73c1ce6890ac668bcf3838fb/wrapt-1.17.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223db574bb38637e8230eb14b185565023ab624474df94d2af18f1cdb625216f", size = 113094, upload-time = "2025-08-12T05:52:22.618Z" }, + { url = "https://files.pythonhosted.org/packages/25/02/78926c1efddcc7b3aa0bc3d6b33a822f7d898059f7cd9ace8c8318e559ef/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e405adefb53a435f01efa7ccdec012c016b5a1d3f35459990afc39b6be4d5056", size = 110659, upload-time = "2025-08-12T05:52:24.057Z" }, + { url = "https://files.pythonhosted.org/packages/dc/ee/c414501ad518ac3e6fe184753632fe5e5ecacdcf0effc23f31c1e4f7bfcf/wrapt-1.17.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:88547535b787a6c9ce4086917b6e1d291aa8ed914fdd3a838b3539dc95c12804", size = 106946, upload-time = "2025-08-12T05:52:45.976Z" }, + { url = "https://files.pythonhosted.org/packages/be/44/a1bd64b723d13bb151d6cc91b986146a1952385e0392a78567e12149c7b4/wrapt-1.17.3-cp314-cp314t-win32.whl", hash = "sha256:41b1d2bc74c2cac6f9074df52b2efbef2b30bdfe5f40cb78f8ca22963bc62977", size = 38717, upload-time = "2025-08-12T05:53:15.214Z" }, + { url = "https://files.pythonhosted.org/packages/79/d9/7cfd5a312760ac4dd8bf0184a6ee9e43c33e47f3dadc303032ce012b8fa3/wrapt-1.17.3-cp314-cp314t-win_amd64.whl", hash = "sha256:73d496de46cd2cdbdbcce4ae4bcdb4afb6a11234a1df9c085249d55166b95116", size = 41334, upload-time = "2025-08-12T05:53:14.178Z" }, + { url = "https://files.pythonhosted.org/packages/46/78/10ad9781128ed2f99dbc474f43283b13fea8ba58723e98844367531c18e9/wrapt-1.17.3-cp314-cp314t-win_arm64.whl", hash = "sha256:f38e60678850c42461d4202739f9bf1e3a737c7ad283638251e79cc49effb6b6", size = 38471, upload-time = "2025-08-12T05:52:57.784Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f6/a933bd70f98e9cf3e08167fc5cd7aaaca49147e48411c0bd5ae701bb2194/wrapt-1.17.3-py3-none-any.whl", hash = "sha256:7171ae35d2c33d326ac19dd8facb1e82e5fd04ef8c6c0e394d7af55a55051c22", size = 23591, upload-time = "2025-08-12T05:53:20.674Z" }, ] [[package]]