Compare commits

...

1 Commits

Author SHA1 Message Date
Sydney Runkle
856b8770e1 initial skills middleware 2025-10-16 23:44:30 -04:00
4 changed files with 1412 additions and 115 deletions

View File

@@ -13,12 +13,20 @@ from langchain_anthropic.middleware.file_search import (
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",

View File

@@ -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)

View File

@@ -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()

View File

@@ -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"
version = "1.17.3"
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" }
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/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" },
{ 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]]