diff --git a/pyproject.toml b/pyproject.toml index 2c6e41a7..a81c7513 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,14 +15,15 @@ classifiers = [ ] license = "Apache-2.0" license-files = ["LICENSE.md"] + dependencies = [ - "rich", + "dotenv", + "httpx>=0.28.1", "litellm>=1.61.15", - "python-dotenv", - "requests", - "boto3", - "opentelemetry-api>=1.34.1", - "opentelemetry-sdk>=1.34.1", + "nest-asyncio>=1.6.0", + "opentelemetry-exporter-otlp>=1.36.0", + "opentelemetry-sdk>=1.36.0", + "opentelemetry-semantic-conventions>=0.57b0", "orjson>=3.9.0", "nest-asyncio>=1.6.0", "langchain-huggingface", @@ -31,7 +32,9 @@ dependencies = [ "langchain-core", "click<8.2.0", "typer>=0.9.0", - "fireworks-ai>=0.19.18", + "pre-commit>=4.3.0", + "pytest>=8.4.1", + "boto3>=1.40.11", ] [project.urls] @@ -56,50 +59,29 @@ langchain = [ "langchain-anthropic", "langchain-core", ] +s3 = ["boto3>=1.40.11"] +trainer = ["fireworks-ai>=0.19.18"] + [dependency-groups] dev = [ - "chromadb>=1.0.12", - "pytest>=8.4.0", - "pytest-asyncio>=1.0.0", - "pytest-cov>=6.1.1", - "pytest-mock>=3.14.1", - "tavily-python>=0.7.5", - "pre-commit>=4.2.0", - "types-requests>=2.32.4.20250611", - "mypy>=1.17.0", + "anthropic>=0.61.0", + "boto3-stubs[s3]>=1.40.11", + "datamodel-code-generator>=0.32.0", + "google-genai>=1.28.0", + "groq>=0.30.0", + "langchain-core>=0.3.72", + "langgraph>=0.6.4", + "mypy>=1.17.1", + "openai>=1.78.1", + "opentelemetry-instrumentation-openai>=0.44.1", + "ruff>=0.9.1,<0.10.0", + "together>=1.5.21", "types-pyyaml>=6.0.12.20250516", - "pandas-stubs>=2.3.0.250703", - "lxml-stubs>=0.5.1", - "types-pygments>=2.19.0.20250715", - "types-beautifulsoup4>=4.12.0.20250516", - "types-cachetools>=6.1.0.20250717", - "types-cffi>=1.17.0.20250523", - "types-defusedxml>=0.7.0.20250708", - "types-greenlet>=3.2.0.20250417", - "types-jsonschema>=4.24.0.20250708", - "types-objgraph>=3.6.0.20240907", - "types-pexpect>=4.9.0.20250516", - "types-protobuf>=6.30.2.20250703", - "types-psutil>=7.0.0.20250601", - "types-pyopenssl>=24.1.0.20240722", - "types-pyasn1>=0.6.0.20250516", - "types-regex>=2024.11.6.20250403", - "types-reportlab>=4.4.1.20250602", - "types-simplejson>=3.20.0.20250326", - "types-tensorflow>=2.18.0.20250516", - "types-tqdm>=4.67.0.20250516", - "types-tree-sitter-languages>=1.10.0.20250530", - "types-xmltodict>=0.14.0.20241009", - "datamodel-code-generator>=0.31.2", - "openai", - "together", - "anthropic", - "google-genai", - "groq", - "langgraph>=0.4.3", + "pre-commit>=4.2.0", ] + [tool.hatch.build] directory = "dist" artifacts = ["src/judgeval/**/*.py"] @@ -107,3 +89,9 @@ exclude = ["src/e2etests/*", "src/tests/*", "src/demo/*"] [tool.ruff] exclude = ["docs"] + +[tool.ruff.lint] +ignore = [ + "F403", + "F405", +] # F403: star import, F405: undefined name from star import diff --git a/scripts/api_generator.py b/scripts/api_generator.py new file mode 100644 index 00000000..5fa140ea --- /dev/null +++ b/scripts/api_generator.py @@ -0,0 +1,354 @@ +from __future__ import annotations + +import orjson +import sys +from typing import Any, Dict, List, Optional +import httpx +import re + +spec_file = sys.argv[1] if len(sys.argv) > 1 else "http://localhost:8000/openapi.json" + +if spec_file.startswith("http"): + r = httpx.get(spec_file) + r.raise_for_status() + SPEC = r.json() +else: + with open(spec_file, "rb") as f: + SPEC = orjson.loads(f.read()) + +JUDGEVAL_PATHS: List[str] = [ + "/traces/spans/batch/", + "/traces/evaluation_runs/batch/", + "/traces/fetch/", + "/traces/upsert/", + "/traces/add_to_dataset/", + "/projects/add/", + "/evaluate/", + "/evaluate_trace/", + "/log_eval_results/", + "/fetch_experiment_run/", + "/add_to_run_eval_queue/", + "/get_evaluation_status/", + "/save_scorer/", + "/fetch_scorer/", + "/scorer_exists/", + "/datasets/push/", + "/datasets/insert_examples/", + "/datasets/pull_for_judgeval/", + "/datasets/fetch_stats_by_project/", + "/projects/resolve/", +] + + +def resolve_ref(ref: str) -> str: + assert ref.startswith( + "#/components/schemas/" + ), "Reference must start with #/components/schemas/" + return ref.replace("#/components/schemas/", "") + + +def to_snake_case(name: str) -> str: + name = re.sub("(.)([A-Z][a-z]+)", r"\1_\2", name) + return re.sub("([a-z0-9])([A-Z])", r"\1_\2", name).lower() + + +def get_method_name_from_path(path: str, method: str) -> str: + return path.strip("/").replace("/", "_").replace("-", "_") + + +def get_query_parameters(operation: Dict[str, Any]) -> List[Dict[str, Any]]: + """Extract query parameters from the operation.""" + parameters = operation.get("parameters", []) + query_params = [] + + for param in parameters: + if param.get("in") == "query": + param_info = { + "name": param["name"], + "required": param.get("required", False), + "type": param.get("schema", {}).get("type", "str"), + } + query_params.append(param_info) + + return query_params + + +def get_request_schema(operation: Dict[str, Any]) -> Optional[str]: + request_body = operation.get("requestBody", {}) + if not request_body: + return None + + content = request_body.get("content", {}) + if "application/json" in content: + schema = content["application/json"].get("schema", {}) + if "$ref" in schema: + return resolve_ref(schema["$ref"]) + + return None + + +def get_response_schema(operation: Dict[str, Any]) -> Optional[str]: + responses = operation.get("responses", {}) + for status_code in ["200", "201"]: + if status_code in responses: + response = responses[status_code] + content = response.get("content", {}) + if "application/json" in content: + schema = content["application/json"].get("schema", {}) + if "$ref" in schema: + return resolve_ref(schema["$ref"]) + + return None + + +def generate_method_signature( + method_name: str, + request_type: Optional[str], + query_params: List[Dict[str, Any]], + response_type: str, + is_async: bool = False, +) -> str: + async_prefix = "async " if is_async else "" + + params = ["self"] + + # Add required query parameters first + for param in query_params: + if param["required"]: + param_name = param["name"] + param_type = "str" # Default to str for simplicity + params.append(f"{param_name}: {param_type}") + + # Add request body parameter if it exists + if request_type: + params.append(f"payload: {request_type}") + + # Add optional query parameters last + for param in query_params: + if not param["required"]: + param_name = param["name"] + param_type = "str" # Default to str for simplicity + params.append(f"{param_name}: Optional[{param_type}] = None") + + params_str = ", ".join(params) + return f"{async_prefix}def {method_name}({params_str}) -> {response_type}:" + + +def generate_method_body( + method_name: str, + path: str, + method: str, + request_type: Optional[str], + query_params: List[Dict[str, Any]], + is_async: bool = False, +) -> str: + async_prefix = "await " if is_async else "" + + # Build query parameters dict if they exist + if query_params: + query_lines = ["query_params = {}"] + for param in query_params: + param_name = param["name"] + if param["required"]: + query_lines.append(f"query_params['{param_name}'] = {param_name}") + else: + query_lines.append(f"if {param_name} is not None:") + query_lines.append(f" query_params['{param_name}'] = {param_name}") + query_setup = "\n ".join(query_lines) + query_param = "query_params" + else: + query_setup = "" + query_param = "{}" + + if method == "GET": + if query_setup: + return f'{query_setup}\n return {async_prefix}self._request(\n "{method}",\n url_for("{path}"),\n {query_param},\n )' + else: + return f'return {async_prefix}self._request(\n "{method}",\n url_for("{path}"),\n {{}},\n )' + else: + if request_type: + if query_setup: + return f'{query_setup}\n return {async_prefix}self._request(\n "{method}",\n url_for("{path}"),\n payload,\n params={query_param},\n )' + else: + return f'return {async_prefix}self._request(\n "{method}",\n url_for("{path}"),\n payload,\n )' + else: + if query_setup: + return f'{query_setup}\n return {async_prefix}self._request(\n "{method}",\n url_for("{path}"),\n {{}},\n params={query_param},\n )' + else: + return f'return {async_prefix}self._request(\n "{method}",\n url_for("{path}"),\n {{}},\n )' + + +def generate_client_class( + class_name: str, methods: List[Dict[str, Any]], is_async: bool = False +) -> str: + lines = [f"class {class_name}:"] + lines.append(' __slots__ = ("api_key", "organization_id", "client")') + lines.append("") + + lines.append(" def __init__(self, api_key: str, organization_id: str):") + lines.append(" self.api_key = api_key") + lines.append(" self.organization_id = organization_id") + client_type = "httpx.AsyncClient" if is_async else "httpx.Client" + lines.append(f" self.client = {client_type}(timeout=30)") + lines.append("") + + request_method = "async def _request" if is_async else "def _request" + lines.append(f" {request_method}(") + lines.append( + ' self, method: Literal["POST", "PATCH", "GET", "DELETE"], url: str, payload: Any, params: Optional[Dict[str, Any]] = None' + ) + lines.append(" ) -> Any:") + lines.append(' if method == "GET":') + lines.append(" r = self.client.request(") + lines.append(" method,") + lines.append(" url,") + lines.append(" params=payload if params is None else params,") + lines.append( + " headers=_headers(self.api_key, self.organization_id)," + ) + lines.append(" )") + lines.append(" else:") + lines.append(" r = self.client.request(") + lines.append(" method,") + lines.append(" url,") + lines.append(" json=json_encoder(payload),") + lines.append(" params=params,") + lines.append( + " headers=_headers(self.api_key, self.organization_id)," + ) + lines.append(" )") + if is_async: + lines.append(" return _handle_response(await r)") + else: + lines.append(" return _handle_response(r)") + lines.append("") + + for method_info in methods: + method_name = method_info["name"] + path = method_info["path"] + http_method = method_info["method"] + request_type = method_info["request_type"] + query_params = method_info["query_params"] + response_type = method_info["response_type"] + + signature = generate_method_signature( + method_name, request_type, query_params, response_type, is_async + ) + lines.append(f" {signature}") + + body = generate_method_body( + method_name, path, http_method, request_type, query_params, is_async + ) + lines.append(f" {body}") + lines.append("") + + return "\n".join(lines) + + +def generate_api_file() -> str: + lines = [ + "from typing import List, Dict, Any, Mapping, Literal, Optional", + "import httpx", + "from httpx import Response", + "from judgeval.exceptions import JudgmentAPIError", + "from judgeval.utils.url import url_for", + "from judgeval.utils.serialize import json_encoder", + "from judgeval.api.api_types import *", + "", + "", + "def _headers(api_key: str, organization_id: str) -> Mapping[str, str]:", + " return {", + ' "Content-Type": "application/json",', + ' "Authorization": f"Bearer {api_key}",', + ' "X-Organization-Id": organization_id,', + " }", + "", + "", + "def _handle_response(r: Response) -> Any:", + " if r.status_code >= 400:", + " try:", + ' detail = r.json().get("detail", "")', + " except Exception:", + " detail = r.text", + " raise JudgmentAPIError(r.status_code, detail, r)", + " return r.json()", + "", + "", + ] + + filtered_paths = { + path: spec_data + for path, spec_data in SPEC["paths"].items() + if path in JUDGEVAL_PATHS + } + + for path in JUDGEVAL_PATHS: + if path not in SPEC["paths"]: + print(f"Path {path} not found in OpenAPI spec", file=sys.stderr) + + sync_methods = [] + async_methods = [] + + for path, path_data in filtered_paths.items(): + for method, operation in path_data.items(): + if method.upper() in ["GET", "POST", "PUT", "PATCH", "DELETE"]: + method_name = get_method_name_from_path(path, method.upper()) + request_schema = get_request_schema(operation) + response_schema = get_response_schema(operation) + query_params = get_query_parameters(operation) + + print( + method_name, + request_schema, + response_schema, + query_params, + file=sys.stderr, + ) + + if not request_schema: + print(f"No request type found for {method_name}", file=sys.stderr) + + if not response_schema: + print( + f"No response schema found for {method_name}", file=sys.stderr + ) + + request_type = request_schema if request_schema else None + response_type = response_schema if response_schema else "Any" + + method_info = { + "name": method_name, + "path": path, + "method": method.upper(), + "request_type": request_type, + "query_params": query_params, + "response_type": response_type, + } + + sync_methods.append(method_info) + async_methods.append(method_info) + + sync_client = generate_client_class( + "JudgmentSyncClient", sync_methods, is_async=False + ) + async_client = generate_client_class( + "JudgmentAsyncClient", async_methods, is_async=True + ) + + lines.append(sync_client) + lines.append("") + lines.append("") + lines.append(async_client) + lines.append("") + lines.append("") + lines.append("__all__ = [") + lines.append(' "JudgmentSyncClient",') + lines.append(' "JudgmentAsyncClient",') + lines.append("]") + + return "\n".join(lines) + + +if __name__ == "__main__": + api_code = generate_api_file() + print(api_code) diff --git a/scripts/openapi_transform.py b/scripts/openapi_transform.py new file mode 100644 index 00000000..0d9fb30c --- /dev/null +++ b/scripts/openapi_transform.py @@ -0,0 +1,116 @@ +from __future__ import annotations + +import orjson +import sys +from typing import Any, Dict, Generator, List +import httpx + +spec_file = sys.argv[1] if len(sys.argv) > 1 else "http://localhost:8000/openapi.json" + +if spec_file.startswith("http"): + r = httpx.get(spec_file) + r.raise_for_status() + SPEC = r.json() +else: + with open(spec_file, "rb") as f: + SPEC = orjson.loads(f.read()) + +JUDGEVAL_PATHS: List[str] = [ + "/traces/spans/batch/", + "/traces/evaluation_runs/batch/", + "/traces/fetch/", + "/traces/upsert/", + "/traces/add_to_dataset/", + "/projects/add/", + "/evaluate/", + "/evaluate_trace/", + "/log_eval_results/", + "/fetch_experiment_run/", + "/add_to_run_eval_queue/", + "/get_evaluation_status/", + "/save_scorer/", + "/fetch_scorer/", + "/scorer_exists/", + "/datasets/push/", + "/datasets/insert_examples/", + "/datasets/pull_for_judgeval/", + "/datasets/fetch_stats_by_project/", + "/projects/resolve/", +] + + +def resolve_ref(ref: str) -> str: + assert ref.startswith( + "#/components/schemas/" + ), "Reference must start with #/components/schemas/" + return ref.replace("#/components/schemas/", "") + + +def walk(obj: Any) -> Generator[Any, None, None]: + yield obj + if isinstance(obj, list): + for item in obj: + yield from walk(item) + elif isinstance(obj, dict): + for value in obj.values(): + yield from walk(value) + + +def get_referenced_schemas(obj: Any) -> Generator[str, None, None]: + for value in walk(obj): + if isinstance(value, dict) and "$ref" in value: + ref = value["$ref"] + resolved = resolve_ref(ref) + assert isinstance(ref, str), "Reference must be a string" + yield resolved + + +def filter_schemas() -> Dict[str, Any]: + result: Dict[str, Any] = {} + processed_schema_names: set[str] = set() + schemas_to_scan: Any = { + path: spec_data + for path, spec_data in SPEC["paths"].items() + if path in JUDGEVAL_PATHS + } + + while True: + to_commit: Dict[str, Any] = {} + for schema_name in get_referenced_schemas(schemas_to_scan): + if schema_name in processed_schema_names: + continue + + assert ( + schema_name in SPEC["components"]["schemas"] + ), f"Schema {schema_name} not found in components.schemas" + + schema = SPEC["components"]["schemas"][schema_name] + to_commit[schema_name] = schema + processed_schema_names.add(schema_name) + + if not to_commit: + break + + result.update(to_commit) + schemas_to_scan = to_commit + + return result + + +filtered_paths = { + path: spec_data + for path, spec_data in SPEC["paths"].items() + if path in JUDGEVAL_PATHS +} + +spec = { + "openapi": SPEC["openapi"], + "info": SPEC["info"], + "paths": filtered_paths, + "components": { + **SPEC["components"], + "schemas": filter_schemas(), + }, +} + +print(orjson.dumps(spec, option=orjson.OPT_INDENT_2).decode("utf-8")) diff --git a/scripts/update_types.sh b/scripts/update_types.sh new file mode 100755 index 00000000..0804aa44 --- /dev/null +++ b/scripts/update_types.sh @@ -0,0 +1,35 @@ +#!/usr/bin/env bash + +# Make sure Judgment backend server is running on port 8000 +# This openapi_transform.py will get the relevant parts of the openapi.json file and save it to openapi.json +uv run scripts/openapi_transform.py > .openapi.json + +# Generate the judgment_types.py file based on the schema in openapi.json. +datamodel-codegen \ + --input .openapi.json \ + --output src/judgeval/data/judgment_types.py \ + --output-model-type pydantic_v2.BaseModel \ + --target-python-version 3.10 \ + --use-annotated \ + --field-constraints \ + --use-default-kwarg \ + --use-field-description \ + --formatters ruff-format \ + + +# Generate the api_types.py file based on the schema in openapi.json. +datamodel-codegen \ + --input .openapi.json \ + --output src/judgeval/api/api_types.py \ + --output-model-type typing.TypedDict \ + --target-python-version 3.10 \ + --use-annotated \ + --use-default-kwarg \ + --use-field-description \ + --formatters ruff-format \ + +# Generate the api.py file based on the schema in openapi.json. +uv run scripts/api_generator.py > src/judgeval/api/__init__.py + +# Remove the openapi.json file since it is no longer needed +rm .openapi.json \ No newline at end of file diff --git a/src/.coveragerc b/src/.coveragerc deleted file mode 100644 index fe3a2a99..00000000 --- a/src/.coveragerc +++ /dev/null @@ -1,4 +0,0 @@ -[run] -omit = - tests/* - e2etests/* diff --git a/src/e2etests/conftest.py b/src/e2etests/conftest.py index 13fcacdd..7e0221a5 100644 --- a/src/e2etests/conftest.py +++ b/src/e2etests/conftest.py @@ -9,7 +9,8 @@ import logging from dotenv import load_dotenv -from judgeval.judgment_client import JudgmentClient +from judgeval import JudgmentClient +from e2etests.utils import delete_project, create_project # Configure logging logging.basicConfig(level=logging.INFO) @@ -39,12 +40,12 @@ def client(project_name: str) -> JudgmentClient: """Create a single JudgmentClient instance for all tests.""" # Setup client = JudgmentClient(api_key=API_KEY, organization_id=ORGANIZATION_ID) - client.create_project(project_name=project_name) + create_project(project_name=project_name) yield client # Teardown # Add more projects to delete as needed - client.delete_project(project_name=project_name) - client.delete_project( + delete_project(project_name=project_name) + delete_project( project_name="e2e-tests-gkzqvtrbwnyl" ) # this is hard coded in test_tracer.py since we can't export fixture diff --git a/src/e2etests/test_all_scorers.py b/src/e2etests/test_all_scorers.py index f33fa1d7..4f849104 100644 --- a/src/e2etests/test_all_scorers.py +++ b/src/e2etests/test_all_scorers.py @@ -2,16 +2,16 @@ base e2e tests for all default judgeval scorers """ -from judgeval.judgment_client import JudgmentClient +from judgeval import JudgmentClient from judgeval.scorers import ( AnswerCorrectnessScorer, AnswerRelevancyScorer, FaithfulnessScorer, InstructionAdherenceScorer, - ExecutionOrderScorer, ) from judgeval.data import Example -from judgeval.constants import DEFAULT_TOGETHER_MODEL +from judgeval.env import JUDGMENT_DEFAULT_TOGETHER_MODEL +from judgeval.scorers.base_scorer import BaseScorer def test_ac_scorer(client: JudgmentClient, project_name: str): @@ -27,7 +27,7 @@ def test_ac_scorer(client: JudgmentClient, project_name: str): res = client.run_evaluation( examples=[example], scorers=[scorer], - model=DEFAULT_TOGETHER_MODEL, + model=JUDGMENT_DEFAULT_TOGETHER_MODEL, project_name=project_name, eval_run_name=EVAL_RUN_NAME, ) @@ -52,7 +52,7 @@ def test_ar_scorer(client: JudgmentClient, project_name: str): res = client.run_evaluation( examples=[example_1, example_2], scorers=[scorer], - model=DEFAULT_TOGETHER_MODEL, + model=JUDGMENT_DEFAULT_TOGETHER_MODEL, project_name=project_name, eval_run_name=EVAL_RUN_NAME, ) @@ -94,7 +94,7 @@ def test_faithfulness_scorer(client: JudgmentClient, project_name: str): res = client.run_evaluation( examples=[faithful_example, contradictory_example], scorers=[scorer], - model=DEFAULT_TOGETHER_MODEL, + model=JUDGMENT_DEFAULT_TOGETHER_MODEL, project_name=project_name, eval_run_name=EVAL_RUN_NAME, ) @@ -119,7 +119,7 @@ def test_instruction_adherence_scorer(client: JudgmentClient, project_name: str) res = client.run_evaluation( examples=[example_1], scorers=[scorer], - model=DEFAULT_TOGETHER_MODEL, + model=JUDGMENT_DEFAULT_TOGETHER_MODEL, project_name=project_name, eval_run_name=EVAL_RUN_NAME, ) @@ -150,8 +150,8 @@ def test_execution_order_scorer(client: JudgmentClient, project_name: str): res = client.run_evaluation( examples=[example], - scorers=[ExecutionOrderScorer(threshold=1, should_consider_ordering=True)], - model=DEFAULT_TOGETHER_MODEL, + scorers=[BaseScorer(threshold=1, should_consider_ordering=True)], + model=JUDGMENT_DEFAULT_TOGETHER_MODEL, project_name=project_name, eval_run_name=EVAL_RUN_NAME, ) diff --git a/src/e2etests/test_custom_scorers.py b/src/e2etests/test_custom_scorers.py index f401703d..9af63694 100644 --- a/src/e2etests/test_custom_scorers.py +++ b/src/e2etests/test_custom_scorers.py @@ -1,5 +1,5 @@ from judgeval.scorers.example_scorer import ExampleScorer -from judgeval.judgment_client import JudgmentClient +from judgeval import JudgmentClient from judgeval.data import Example from typing import Dict, Any diff --git a/src/e2etests/test_dataset_operations.py b/src/e2etests/test_dataset_operations.py index 8059324c..85d2285f 100644 --- a/src/e2etests/test_dataset_operations.py +++ b/src/e2etests/test_dataset_operations.py @@ -5,18 +5,18 @@ import random import string import pytest -from judgeval.judgment_client import JudgmentClient +from judgeval import JudgmentClient from judgeval.data import Example from judgeval.dataset import Dataset +from e2etests.utils import create_project, delete_project def test_create_dataset(client: JudgmentClient, project_name: str, random_name: str): """Test dataset creation""" - dataset = Dataset.create( + Dataset.create( name=random_name, project_name=project_name, ) - dataset.delete() def test_create_dataset_with_example( @@ -29,14 +29,13 @@ def test_create_dataset_with_example( examples=[Example(input="input 1", actual_output="output 1")], ) assert dataset, "Failed to push dataset" - dataset.delete() def test_create_dataset_across_projects( client: JudgmentClient, project_name: str, random_name: str ): """Test that the same name for a dataset can be used across projects.""" - client.create_project(project_name=random_name) + create_project(project_name=random_name) dataset = Dataset.create( name=random_name, project_name=project_name, @@ -52,10 +51,7 @@ def test_create_dataset_across_projects( ) assert dataset2, "Failed to push dataset" - - dataset.delete() - dataset2.delete() - client.delete_project(project_name=random_name) + delete_project(project_name=random_name) def test_create_dataset_error( @@ -123,9 +119,6 @@ def test_pull_dataset(client: JudgmentClient, project_name: str): f"Example should have .actual_output be 'output {i}' but got '{e.actual_output}'" ) - dataset1.delete() - dataset2.delete() - def test_append_dataset(client: JudgmentClient, project_name: str, random_name: str): """Test dataset editing.""" @@ -151,8 +144,6 @@ def test_append_dataset(client: JudgmentClient, project_name: str, random_name: f"Dataset should have {initial_example_count + 3} examples, but has {len(dataset.examples)}" ) - dataset.delete() - def test_overwrite_dataset(client: JudgmentClient, project_name: str, random_name: str): """Test dataset overwriting.""" @@ -176,5 +167,3 @@ def test_overwrite_dataset(client: JudgmentClient, project_name: str, random_nam dataset = Dataset.get(name=random_name, project_name=project_name) assert dataset, "Failed to pull dataset" assert len(dataset.examples) == 2, "Dataset should have 2 examples" - - dataset.delete() diff --git a/src/e2etests/test_eval_operations.py b/src/e2etests/test_eval_operations.py index 445f5802..54c23735 100644 --- a/src/e2etests/test_eval_operations.py +++ b/src/e2etests/test_eval_operations.py @@ -4,7 +4,7 @@ import pytest -from judgeval.judgment_client import JudgmentClient +from judgeval import JudgmentClient from judgeval.data import Example from judgeval.scorers import ( FaithfulnessScorer, @@ -12,7 +12,7 @@ ) from judgeval.scorers.example_scorer import ExampleScorer from judgeval.dataset import Dataset -from judgeval.constants import DEFAULT_TOGETHER_MODEL +from judgeval.env import JUDGMENT_DEFAULT_TOGETHER_MODEL def run_eval_helper(client: JudgmentClient, project_name: str, eval_run_name: str): @@ -44,7 +44,7 @@ def run_eval_helper(client: JudgmentClient, project_name: str, eval_run_name: st res = client.run_evaluation( examples=[example1, example2], scorers=[scorer, scorer2], - model=DEFAULT_TOGETHER_MODEL, + model=JUDGMENT_DEFAULT_TOGETHER_MODEL, project_name=project_name, eval_run_name=eval_run_name, ) @@ -88,7 +88,7 @@ async def test_assert_test(client: JudgmentClient, project_name: str): project_name=project_name, examples=[example, example1, example2], scorers=[scorer], - model=DEFAULT_TOGETHER_MODEL, + model=JUDGMENT_DEFAULT_TOGETHER_MODEL, ) @@ -114,14 +114,12 @@ def test_evaluate_dataset(client: JudgmentClient, project_name: str, random_name res = client.run_evaluation( examples=dataset.examples, scorers=[FaithfulnessScorer(threshold=0.5)], - model=DEFAULT_TOGETHER_MODEL, + model=JUDGMENT_DEFAULT_TOGETHER_MODEL, project_name=project_name, eval_run_name=random_name, ) assert res, "Dataset evaluation failed" - dataset.delete() - def test_evaluate_dataset_custom( client: JudgmentClient, project_name: str, random_name: str @@ -155,7 +153,7 @@ async def a_score_example(self, example: CustomExample): res = client.run_evaluation( examples=dataset.examples, scorers=[CustomScorer()], - model=DEFAULT_TOGETHER_MODEL, + model=JUDGMENT_DEFAULT_TOGETHER_MODEL, project_name=project_name, eval_run_name=random_name, ) @@ -169,5 +167,3 @@ async def a_score_example(self, example: CustomExample): assert res[1].scorers_data[0].score == 0.75 assert res[2].scorers_data[0].score == 0.5 assert res[3].scorers_data[0].score == 0 - - dataset.delete() diff --git a/src/e2etests/test_judgee_traces_update.py b/src/e2etests/test_judgee_traces_update.py deleted file mode 100644 index e5f9e542..00000000 --- a/src/e2etests/test_judgee_traces_update.py +++ /dev/null @@ -1,804 +0,0 @@ -""" -E2E tests for organization-based judgee and trace tracking functionality -with atomic operations and race condition handling -""" - -import pytest -import pytest_asyncio -import os -import httpx -from dotenv import load_dotenv -import asyncio -import time -from uuid import uuid4 -import logging -from contextlib import asynccontextmanager -from judgeval.judgment_client import JudgmentClient -from judgeval.scorers import ( - AnswerCorrectnessScorer, -) -from judgeval.constants import DEFAULT_TOGETHER_MODEL -from e2etests.test_all_scorers import print_debug_on_failure -from judgeval.tracer import Tracer, wrap -from judgeval.data import Example -from openai import OpenAI -from anthropic import Anthropic -from datetime import datetime - -# Configure logging with more detailed format -logging.basicConfig( - level=logging.DEBUG, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" -) -logger = logging.getLogger(__name__) - -# Initialize the tracer and clients -Tracer._instance = None -judgment = Tracer(api_key=os.getenv("JUDGMENT_API_KEY")) -openai_client = wrap(OpenAI()) -anthropic_client = wrap(Anthropic()) - -# Load environment variables from .env file -load_dotenv() - -# Get server URL and API key from environment -SERVER_URL = os.getenv("JUDGMENT_API_URL", "http://localhost:8000") -TEST_API_KEY = os.getenv("JUDGMENT_API_KEY") -ORGANIZATION_ID = os.getenv("JUDGMENT_ORG_ID") -USER_API_KEY = os.getenv("USER_API_KEY", TEST_API_KEY) # For user-specific tests - - -# Standard headers for all requests -def get_headers(): - headers = { - "Authorization": f"Bearer {TEST_API_KEY}", - "X-Organization-Id": ORGANIZATION_ID, - } - logger.debug(f"Generated headers: {headers}") - return headers - - -# User-specific headers with organization ID -def get_user_headers(): - headers = { - "Authorization": f"Bearer {USER_API_KEY}", - "X-Organization-Id": ORGANIZATION_ID, - } - logger.debug(f"Generated user headers: {headers}") - return headers - - -@asynccontextmanager -async def get_client(): - """Context manager for creating and cleaning up HTTP client.""" - logger.debug("Creating new HTTP client") - async with httpx.AsyncClient(timeout=30.0, follow_redirects=True) as client: - try: - # Test server health before proceeding - logger.debug("Testing server health") - response = await client.get(f"{SERVER_URL}/health") - assert response.status_code == 200, ( - f"Server is not healthy: {response.status_code}" - ) - logger.debug("Server health check passed") - yield client - except Exception as e: - logger.error(f"Error in client context: {str(e)}", exc_info=True) - raise - finally: - logger.debug("Closing HTTP client") - await client.aclose() - - -@pytest_asyncio.fixture -async def client(): - """Fixture to create and provide an HTTP client with proper cleanup.""" - async with get_client() as client: - yield client - - -@pytest_asyncio.fixture -async def cleanup_traces(client): - """Fixture to clean up traces after tests.""" - logger.debug("Starting cleanup_traces fixture") - yield - logger.debug("Cleaning up traces") - # Add cleanup logic here if needed - - -@pytest.mark.asyncio -async def test_server_health(client): - """Test that the server is running and healthy.""" - try: - logger.debug("Testing server health endpoint") - response = await client.get(f"{SERVER_URL}/health") - logger.debug(f"Health check response: {response.status_code}") - assert response.status_code == 200 - data = response.json() - logger.debug(f"Health check data: {data}") - assert data == {"status": "ok"} # Updated to match actual server response - except Exception as e: - logger.error(f"Error in test_server_health: {str(e)}", exc_info=True) - raise - - -@pytest.mark.asyncio -async def test_judgee_count_endpoint(client): - """Test that the judgee count endpoint works correctly.""" - response = await client.get(f"{SERVER_URL}/judgees/count/", headers=get_headers()) - assert response.status_code == 200 - data = response.json() - assert "judgees_ran" in data - assert "user_judgees_ran" in data - - -@pytest.mark.asyncio -async def test_trace_save_increment(client, project_name: str): - """Test that saving a trace increments the trace count.""" - try: - logger.debug("Starting trace save increment test") - # Get initial count - response = await client.get( - f"{SERVER_URL}/traces/count/", headers=get_headers() - ) - initial_count = response.json()["traces_ran"] - logger.debug(f"Initial trace count: {initial_count}") - - # Create a trace - timestamp = time.time() - trace_id = str(uuid4()) - trace_data = { - "name": f"test_trace_{int(timestamp)}", - "project_name": project_name, - "trace_id": trace_id, - "created_at": datetime.fromtimestamp(timestamp).isoformat(), - "trace_spans": [ - { - "timestamp": datetime.fromtimestamp(timestamp).isoformat(), - "type": "span", - "function": "test_span", - "inputs": {"test": "input"}, - "outputs": {"test": "output"}, - "duration": 0.1, - "span_id": str(uuid4()), - "trace_id": trace_id, - "parent_id": None, - "depth": 0, - } - ], - "duration": 0.1, - "token_counts": {"total": 10}, - "empty_save": False, - "update_id": 1, - "evaluation_runs": [], - } - logger.debug(f"Created trace data: {trace_data}") - - response = await client.post( - f"{SERVER_URL}/traces/upsert/", json=trace_data, headers=get_headers() - ) - - logger.debug(f"Trace save response: {response.status_code}") - logger.debug(f"Trace save response body: {response.text}") - assert response.status_code == 200 - - # Verify increment - response = await client.get( - f"{SERVER_URL}/traces/count/", headers=get_headers() - ) - assert response.status_code == 200 - new_count = response.json()["traces_ran"] - logger.debug(f"New trace count: {new_count}") - - # In pay-as-you-go mode, the regular trace count might not increase - # We'll consider the test successful if either: - # 1. The trace count increased (regular mode) - # 2. The trace save operation succeeded (pay-as-you-go mode) - if new_count > initial_count: - logger.info("Regular trace count increased as expected") - else: - logger.info( - "Regular trace count did not increase - this is expected if pay-as-you-go is enabled" - ) - # We've already verified the save operation succeeded (status code 200) - - # No assertion on count - we just verify the save operation succeeded - except Exception as e: - logger.error(f"Error in test_trace_save_increment: {str(e)}", exc_info=True) - raise - - -@pytest.mark.asyncio -async def test_trace_count_endpoint(client): - """Test that the trace count endpoint works correctly.""" - response = await client.get(f"{SERVER_URL}/traces/count/", headers=get_headers()) - assert response.status_code == 200 - data = response.json() - assert "traces_ran" in data - assert "user_traces_ran" in data - - -@pytest.mark.asyncio -async def test_concurrent_trace_saves(client, project_name: str): - """Test concurrent trace saves to verify atomic operations.""" - try: - # Get initial count - response = await client.get( - f"{SERVER_URL}/traces/count/", headers=get_headers() - ) - initial_count = response.json()["traces_ran"] - - # Number of concurrent traces to save - num_traces = 3 - - async def save_trace(index): - try: - timestamp = time.time() - trace_id = str(uuid4()) - trace_data = { - "name": f"concurrent_trace_{index}_{int(timestamp)}", - "project_name": project_name, - "trace_id": trace_id, - "created_at": datetime.fromtimestamp(timestamp).isoformat(), - "trace_spans": [ - { - "timestamp": datetime.fromtimestamp(timestamp).isoformat(), - "type": "span", - "function": f"test_span_{index}", - "inputs": {"test": f"input_{index}"}, - "outputs": {"test": f"output_{index}"}, - "duration": 0.1, - "span_id": str(uuid4()), - "trace_id": trace_id, - "parent_id": None, - "depth": 0, - } - ], - "duration": 0.1, - "token_counts": {"total": 10}, - "empty_save": False, - "update_id": 1, - "evaluation_runs": [], - } - - response = await client.post( - f"{SERVER_URL}/traces/upsert/", - json=trace_data, - headers=get_headers(), - ) - return response.status_code - except Exception as e: - logger.error(f"Error in save_trace {index}: {str(e)}") - return 500 - - # Save traces concurrently with timeout - tasks = [save_trace(i) for i in range(num_traces)] - results = await asyncio.wait_for( - asyncio.gather(*tasks, return_exceptions=True), timeout=30.0 - ) - - # All saves should succeed - assert all(status == 200 for status in results) - - # Verify increment - response = await client.get( - f"{SERVER_URL}/traces/count/", headers=get_headers() - ) - assert response.status_code == 200 - - # Get the new counts - new_count = response.json()["traces_ran"] - - # In pay-as-you-go mode, the regular trace count might not increase - # We'll consider the test successful if either: - # 1. The trace count increased by the expected amount (regular mode) - # 2. All trace save operations succeeded (pay-as-you-go mode) - if new_count >= initial_count + num_traces: - logger.info( - f"Regular trace count increased by {new_count - initial_count} as expected" - ) - else: - logger.info( - "Regular trace count did not increase by the expected amount - this is expected if pay-as-you-go is enabled" - ) - # We've already verified that all save operations succeeded (all status codes are 200) - - except Exception as e: - logger.error(f"Error in test_concurrent_trace_saves: {str(e)}") - raise - - -@pytest.mark.asyncio -async def test_failed_trace_counting(client, project_name: str): - """Test that failed traces are still counted.""" - # Get initial count - response = await client.get(f"{SERVER_URL}/traces/count/", headers=get_headers()) - initial_count = response.json()["traces_ran"] - - # Create an invalid trace (missing required fields) - timestamp = time.time() - trace_data = { - "name": f"test_failed_trace_{int(timestamp)}", - "project_name": project_name, - "trace_id": str(uuid4()), - "created_at": str(timestamp), # Convert to string - # Missing trace_spans, which should cause a validation error - "duration": 0.1, - "token_counts": {"total": 10}, - "empty_save": False, - } - - # This should fail but still increment the count - response = await client.post( - f"{SERVER_URL}/traces/upsert/", json=trace_data, headers=get_headers() - ) - - # The request might fail with 400 or 422, but the trace count should still increment - # Verify increment - response = await client.get(f"{SERVER_URL}/traces/count/", headers=get_headers()) - assert response.status_code == 200 - # Since we're counting both successful and failed traces, the count should increase - assert response.json()["traces_ran"] >= initial_count - - -@pytest.mark.asyncio -async def test_real_trace_tracking(client, project_name: str): - """Test real trace tracking with actual trace saves""" - from judgeval.common.tracer import Tracer - - print("Starting test_real_trace_tracking...") - - try: - # Initialize tracer - print("Initializing Tracer...") - Tracer._instance = None - tracer = Tracer( - api_key=os.getenv("JUDGMENT_API_KEY"), - project_name=project_name, - organization_id=os.getenv("JUDGMENT_ORG_ID"), - ) - print("Tracer initialized successfully") - - # Create a real trace - print("Creating traced function...") - - @tracer.observe(name="test_trace") - def test_function(): - print("Running traced function...") - return "Hello, World!" - - # Run the traced function - print("Executing traced function...") - try: - result = test_function() - print(f"Function result: {result}") - print("Trace saved successfully") - except Exception as e: - print(f"Error saving trace: {str(e)}") - print( - "This error is expected if pay-as-you-go is enabled but not properly configured" - ) - # Don't fail the test - this is expected behavior with pay-as-you-go - - print("Test completed - trace functionality verified") - except Exception as e: - print(f"Error in test_real_trace_tracking: {str(e)}") - # Don't fail the test due to API errors - print("Skipping test due to API errors") - - -@pytest.mark.asyncio -async def test_rate_limiting_detection(client): - """Test to detect if rate limiting is active without exceeding limits.""" - # Get rate limit headers without triggering limits - response = await client.get(f"{SERVER_URL}/traces/count/", headers=get_headers()) - - # Check if rate limit headers are present - rate_limit_headers = [ - header - for header in response.headers - if "rate" in header.lower() - or "limit" in header.lower() - or "remaining" in header.lower() - ] - - # Print headers for debugging - print(f"Rate limit related headers: {rate_limit_headers}") - - # If rate limiting is implemented, we should see headers like: - # X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, etc. - # We're just checking the presence of the mechanism, not trying to exceed it - - # This test passes if we can detect rate limiting headers or if we get a successful response - # (since some implementations might not expose headers) - assert response.status_code == 200 - - # Optional assertion if rate limit headers are expected - # assert len(rate_limit_headers) > 0, "No rate limit headers detected" - - -@pytest.mark.asyncio -async def test_burst_request_handling(client, project_name: str): - """Test how the API handles a burst of requests without exceeding limits.""" - # Number of requests to send in a burst (keep this low to avoid triggering actual limits) - num_requests = 5 - - # Send a small burst of trace save requests - timestamp = time.time() - trace_id = str(uuid4()) - trace_data = { - "name": f"burst_test_trace_{int(timestamp)}", - "project_name": project_name, - "trace_id": trace_id, - "created_at": datetime.fromtimestamp(timestamp).isoformat(), - "trace_spans": [ - { - "timestamp": datetime.fromtimestamp(timestamp).isoformat(), - "type": "span", - "function": "test_span", - "inputs": {"test": "input"}, - "outputs": {"test": "output"}, - "duration": 0.1, - "span_id": str(uuid4()), - "trace_id": trace_id, - "parent_id": None, - "depth": 0, - } - ], - "duration": 0.1, - "token_counts": {"total": 10}, - "empty_save": False, - "update_id": 1, - "evaluation_runs": [], - } - - async def save_trace(): - # Create a unique trace ID for each request - local_trace_data = trace_data.copy() - local_trace_data["trace_id"] = str(uuid4()) - local_trace_data["trace_spans"][0]["span_id"] = str(uuid4()) - local_trace_data["trace_spans"][0]["trace_id"] = local_trace_data["trace_id"] - - response = await client.post( - f"{SERVER_URL}/traces/upsert/", json=local_trace_data, headers=get_headers() - ) - return response.status_code - - # Send burst of requests and collect status codes - tasks = [save_trace() for _ in range(num_requests)] - status_codes = await asyncio.gather(*tasks) - - # All requests should succeed if we're below the rate limit - # If any fail with 429, that's also informative but not a test failure - print(f"Burst request status codes: {status_codes}") - - # Count successful vs rate-limited responses - successful = status_codes.count(200) - rate_limited = status_codes.count(429) - - # We expect either all successful or some rate limited - assert successful + rate_limited == num_requests - - # Log the results for analysis - print(f"Successful requests: {successful}, Rate limited: {rate_limited}") - - -@pytest.mark.asyncio -async def test_organization_limits_info(client): - """Test to retrieve and verify organization limits information.""" - # Some APIs provide endpoints to check current usage and limits - # Try to access such an endpoint if it exists - - try: - response = await client.get( - f"{SERVER_URL}/organization/usage/", headers=get_headers() - ) - - # If the endpoint exists and returns data - if response.status_code == 200: - limits_data = response.json() - print(f"Organization usage data: {limits_data}") - - # Check for expected fields in the response - # This is informational and won't fail the test if fields are missing - expected_fields = [ - "judgee_limit", - "trace_limit", - "judgee_used", - "trace_used", - ] - found_fields = [field for field in expected_fields if field in limits_data] - - print(f"Found usage fields: {found_fields}") - - # Test passes if we got a valid response - assert response.status_code == 200 - else: - # If endpoint doesn't exist, test is inconclusive but not failed - print(f"Usage endpoint returned status code: {response.status_code}") - pytest.skip( - "Organization usage endpoint not available or returned non-200 status" - ) - - except Exception as e: - # If endpoint doesn't exist, test is inconclusive but not failed - print(f"Error accessing organization usage endpoint: {str(e)}") - pytest.skip("Organization usage endpoint not available") - - -@pytest.mark.asyncio -async def test_on_demand_resource_detection(client): - """Test to detect on-demand resource capabilities without creating actual resources.""" - # Check if on-demand endpoints exist by making OPTIONS requests - # This doesn't modify data but tells us if the endpoints are available - - # Check for on-demand judgee endpoint - judgee_response = await client.options( - f"{SERVER_URL}/judgees/on_demand/", headers=get_headers() - ) - - # Check for on-demand trace endpoint - trace_response = await client.options( - f"{SERVER_URL}/traces/on_demand/", headers=get_headers() - ) - - # Log the results - print(f"On-demand judgee endpoint status: {judgee_response.status_code}") - print(f"On-demand trace endpoint status: {trace_response.status_code}") - - # For OPTIONS requests, 200, 204, or 404 are all valid responses - # 200/204 means endpoint exists, 404 means it doesn't - - # Test passes if we could make the requests without errors - assert judgee_response.status_code in [200, 204, 404] - assert trace_response.status_code in [200, 204, 404] - - # Log if on-demand endpoints were detected - on_demand_judgee_available = judgee_response.status_code in [200, 204] - on_demand_trace_available = trace_response.status_code in [200, 204] - - print(f"On-demand judgee endpoint available: {on_demand_judgee_available}") - print(f"On-demand trace endpoint available: {on_demand_trace_available}") - - -@pytest.mark.asyncio -async def test_real_judgee_tracking(client, project_name: str): - """Test real judgee tracking with actual evaluation.""" - # Get initial judgee count - print(f"Getting initial judgee count from {SERVER_URL}/judgees/count/") - response = await client.get(f"{SERVER_URL}/judgees/count/", headers=get_headers()) - print(f"Initial count response: {response.status_code} {response.text}") - assert response.status_code == 200 - initial_data = response.json() - initial_judgees = initial_data["judgees_ran"] - print(f"Initial judgee count: {initial_judgees}") - - example = Example( - input="What's the capital of France?", - actual_output="The capital of France is Paris.", - expected_output="France's capital is Paris. It used to be called the city of lights until 1968.", - ) - - scorer = AnswerCorrectnessScorer(threshold=0.1) - - judgment_client = JudgmentClient() - EVAL_RUN_NAME = "test-run-ac" - - print("Running evaluation with use_judgment=True...") - # Test with use_judgment=True - try: - res = judgment_client.run_evaluation( - examples=[example], - scorers=[scorer], - model=DEFAULT_TOGETHER_MODEL, - project_name=project_name, - eval_run_name=EVAL_RUN_NAME, - ) - print(f"Evaluation response: {res}") - print_debug_on_failure(res[0]) - - # Wait a moment for the count to update - await asyncio.sleep(2) - - # Get final judgee count - print(f"Getting final judgee count from {SERVER_URL}/judgees/count/") - response = await client.get( - f"{SERVER_URL}/judgees/count/", headers=get_headers() - ) - print(f"Final count response: {response.status_code} {response.text}") - assert response.status_code == 200 - final_data = response.json() - final_judgees = final_data["judgees_ran"] - print(f"Final judgee count: {final_judgees}") - print(f"Count difference: {final_judgees - initial_judgees}") - - # In pay-as-you-go mode, the regular judgee count might not increase - # We'll consider the test successful if: - # 1. The judgee count increased (regular mode), or - # 2. The evaluation operation succeeded (pay-as-you-go mode) - if final_judgees == initial_judgees + 1: - print("Regular judgee count increased by 1 as expected") - else: - print( - "Regular judgee count did not increase - this is expected if pay-as-you-go is enabled" - ) - # We've already verified the evaluation succeeded based on the response - except Exception as e: - print(f"Error during evaluation: {str(e)}") - print( - "This error is expected if pay-as-you-go is enabled but not properly configured" - ) - # Don't fail the test - this is expected behavior with pay-as-you-go - - print("Test completed successfully!") - - -@pytest.mark.asyncio -async def test_real_trace_and_judgee_tracking(client, project_name: str): - """Test both trace and judgee tracking in a single E2E test. - - This test: - 1. Checks initial trace and judgee counts - 2. Creates and saves a trace - 3. Verifies that trace counts are incremented - 4. Runs an evaluation within the trace - 5. Verifies that both trace and judgee counts are incremented correctly - """ - from judgeval.judgment_client import JudgmentClient - from judgeval.scorers import AnswerCorrectnessScorer - from judgeval.data import Example - from judgeval.common.tracer import Tracer - - # Get initial counts - print("Getting initial counts...") - - # Get initial trace count - trace_response = await client.get( - f"{SERVER_URL}/traces/count/", headers=get_headers() - ) - assert trace_response.status_code == 200 - initial_trace_data = trace_response.json() - initial_traces = initial_trace_data["traces_ran"] - print(f"Initial trace count: {initial_traces}") - - # Get initial judgee count - judgee_response = await client.get( - f"{SERVER_URL}/judgees/count/", headers=get_headers() - ) - assert judgee_response.status_code == 200 - initial_judgee_data = judgee_response.json() - initial_judgees = initial_judgee_data["judgees_ran"] - print(f"Initial judgee count: {initial_judgees}") - - # Create a trace and run an evaluation within it - print("Creating trace and running evaluation...") - - try: - # Define test data - example = Example( - input="What's the capital of France?", - actual_output="The capital of France is Paris.", - expected_output="France's capital is Paris. It is known as the City of Light.", - ) - scorer = AnswerCorrectnessScorer(threshold=0.1) - - # Initialize judgment client - judgment_client = JudgmentClient() - EVAL_RUN_NAME = "test-trace-judgee-run" - - # Create a tracer - tracer = Tracer( - api_key=os.getenv("JUDGMENT_API_KEY"), - project_name=project_name, - organization_id=os.getenv("JUDGMENT_ORG_ID"), - ) - - # Start a trace - with tracer.trace(name="test_trace_with_eval") as trace: - print("Trace started, running evaluation within trace...") - - # Run evaluation within the trace - try: - res = judgment_client.run_evaluation( - examples=[example], - scorers=[scorer], - model=DEFAULT_TOGETHER_MODEL, - project_name=project_name, - eval_run_name=EVAL_RUN_NAME, - ) - - print(f"Evaluation response: {res}") - print_debug_on_failure(res[0]) - except Exception as e: - print(f"Error during evaluation: {str(e)}") - print( - "This error is expected if pay-as-you-go is enabled but not properly configured" - ) - # Continue with the test - we still want to try saving the trace - - # Save the trace - try: - trace_id, trace_data = trace.save() - print(f"Trace saved with ID: {trace_id}") - except Exception as e: - print(f"Error saving trace: {str(e)}") - print( - "This error is expected if pay-as-you-go is enabled but not properly configured" - ) - # Don't fail the test - this is expected behavior with pay-as-you-go - - # Wait for counts to update - print("Waiting for counts to update...") - await asyncio.sleep(3) - - # Get final trace count - print("Getting final trace count...") - trace_response = await client.get( - f"{SERVER_URL}/traces/count/", headers=get_headers() - ) - assert trace_response.status_code == 200 - final_trace_data = trace_response.json() - final_traces = final_trace_data["traces_ran"] - print(f"Final trace count: {final_traces}") - print(f"Trace count difference: {final_traces - initial_traces}") - - # Get final judgee count - print("Getting final judgee count...") - judgee_response = await client.get( - f"{SERVER_URL}/judgees/count/", headers=get_headers() - ) - assert judgee_response.status_code == 200 - final_judgee_data = judgee_response.json() - final_judgees = final_judgee_data["judgees_ran"] - print(f"Final judgee count: {final_judgees}") - print(f"Judgee count difference: {final_judgees - initial_judgees}") - - # Check for on-demand traces - on_demand_traces_increased = False - if "on_demand_traces" in final_trace_data: - initial_on_demand_traces = initial_trace_data.get("on_demand_traces", 0) - final_on_demand_traces = final_trace_data["on_demand_traces"] - print(f"On-demand trace count: {final_on_demand_traces}") - print( - f"On-demand trace difference: {final_on_demand_traces - initial_on_demand_traces}" - ) - on_demand_traces_increased = ( - final_on_demand_traces > initial_on_demand_traces - ) - - # Check for on-demand judgees - on_demand_judgees_increased = False - if "on_demand_judgees" in final_judgee_data: - initial_on_demand_judgees = initial_judgee_data.get("on_demand_judgees", 0) - final_on_demand_judgees = final_judgee_data["on_demand_judgees"] - print(f"On-demand judgee count: {final_on_demand_judgees}") - print( - f"On-demand judgee difference: {final_on_demand_judgees - initial_on_demand_judgees}" - ) - on_demand_judgees_increased = ( - final_on_demand_judgees > initial_on_demand_judgees - ) - - # In pay-as-you-go mode, the regular counts might not increase - # We'll consider the test successful if: - # 1. The counts increased (regular mode), or - # 2. The operations succeeded (pay-as-you-go mode) - if final_traces == initial_traces + 1 or on_demand_traces_increased: - print("Trace count increased as expected (either regular or on-demand)") - else: - print( - "Neither regular nor on-demand trace counts increased - but the trace save operation succeeded" - ) - - if final_judgees == initial_judgees + 1 or on_demand_judgees_increased: - print("Judgee count increased as expected (either regular or on-demand)") - else: - print( - "Neither regular nor on-demand judgee counts increased - but the evaluation operation succeeded" - ) - - print("Test completed successfully!") - except Exception as e: - print(f"Error in test_real_trace_and_judgee_tracking: {str(e)}") - print("Skipping test due to API errors") - - print("Test completed successfully!") diff --git a/src/e2etests/test_langgraph.py b/src/e2etests/test_langgraph.py deleted file mode 100644 index 6587dad9..00000000 --- a/src/e2etests/test_langgraph.py +++ /dev/null @@ -1,271 +0,0 @@ -import pytest -import os -from typing import TypedDict, List -import warnings -import time - -from langgraph.graph import StateGraph -from langchain_core.messages import HumanMessage, AIMessage, BaseMessage -from langchain_openai import ChatOpenAI - -from judgeval.common.tracer import Tracer, TraceManagerClient, TraceClient -from judgeval.integrations.langgraph import JudgevalCallbackHandler - -# --- Test Configuration --- -API_KEY = os.getenv("JUDGMENT_API_KEY") -ORG_ID = os.getenv("JUDGMENT_ORG_ID") - -# --- Shared Graph Definition --- -llm = ChatOpenAI(model="gpt-4.1-mini", temperature=0) - - -class SimpleState(TypedDict): - messages: List[BaseMessage] - result: str - - -def call_llm_node(state: SimpleState) -> SimpleState: - """Node that calls the LLM.""" - print("--- Node: call_llm_node ---") - response = llm.invoke(state["messages"]) - return {"messages": state["messages"] + [response], "result": "LLM Called"} - - -async def async_call_llm_node(state: SimpleState) -> SimpleState: - """Async node that calls the LLM.""" - print("--- Node: async_call_llm_node ---") - # Use ainvoke for async LLM call if needed, but invoke works fine too - response = await llm.ainvoke(state["messages"]) - return {"messages": state["messages"] + [response], "result": "Async LLM Called"} - - -def build_graph(): - """Builds the simple test graph.""" - # Sync Graph - graph_builder_sync = StateGraph(SimpleState) - graph_builder_sync.add_node("call_llm", call_llm_node) - graph_builder_sync.add_node("finish", lambda state: state) - graph_builder_sync.set_entry_point("call_llm") - graph_builder_sync.add_edge("call_llm", "finish") - graph_builder_sync.set_finish_point("finish") - graph_sync = graph_builder_sync.compile() - - # Async Graph - graph_builder_async = StateGraph(SimpleState) - graph_builder_async.add_node("async_call_llm", async_call_llm_node) - graph_builder_async.add_node("finish", lambda state: state) - graph_builder_async.set_entry_point("async_call_llm") - graph_builder_async.add_edge("async_call_llm", "finish") - graph_builder_async.set_finish_point("finish") - graph_async = graph_builder_async.compile() - - return graph_sync, graph_async - - -graph_sync, graph_async = build_graph() # Compile graphs once - - -# --- Helper Function --- -def fetch_and_validate_trace(trace_id: str, expected_project: str): - """Fetches a trace and performs basic validation.""" - print(f"\nFetching trace ID: {trace_id} for project: {expected_project}") - if not API_KEY or not ORG_ID: - pytest.skip( - "JUDGMENT_API_KEY or JUDGMENT_ORG_ID not set, skipping trace fetch." - ) - - client = TraceManagerClient(judgment_api_key=API_KEY, organization_id=ORG_ID) - try: - trace_data = client.fetch_trace(trace_id=trace_id) - print("Trace data fetched successfully.") - # rprint(trace_data) # Use rich print if available for better readability - - assert trace_data, f"Trace data for {trace_id} should not be empty." - assert trace_data.get("trace_id") == trace_id - # assert trace_data.get("project_name") == expected_project # Commenting out: Seems fetch API doesn't return project_name - - # --- MODIFIED CHECK: Look for 'trace_spans' instead of 'entries' --- - assert "trace_spans" in trace_data, ( - f"Fetched trace {trace_id} should contain 'trace_spans' key." - ) - trace_spans = trace_data.get("trace_spans") - assert trace_spans is not None, ( - f"Trace {trace_id} field 'trace_spans' should not be None." - ) - # assert len(trace_spans) > 0, f"Trace {trace_id} should have at least one span in 'trace_spans'." # Can be 0 if only root span - # --- END MODIFIED CHECK --- - - # Check for specific nodes/spans if needed - if trace_spans is None: # Should not happen due to assert above, but defensive - warnings.warn( - f"Trace {trace_id} fetched successfully, but 'trace_spans' was None. Skipping specific span assertions." - ) - span_functions = [] - else: - # Extract function names from the list of span dictionaries - span_functions = [span.get("function") for span in trace_spans] - print(f"Span functions found: {span_functions}") - return trace_data # Return fetched data for further specific assertions - - except Exception as e: - pytest.fail(f"Failed to fetch or validate trace {trace_id}: {e}") - - -# --- Synchronous Test --- -def test_sync_graph_execution(project_name: str): - """Tests synchronous graph execution with JudgevalCallbackHandler.""" - print("\n--- Running Sync Test ---") - Tracer._instance = None - tracer_sync = Tracer( - api_key=API_KEY, organization_id=ORG_ID, project_name=project_name - ) - handler_sync = JudgevalCallbackHandler(tracer_sync) - trace_client_sync: TraceClient = None # Keep this for type hinting - trace_id_sync: str = "" - - initial_state = {"messages": [HumanMessage(content="What is 5 + 5?")]} - config = {"callbacks": [handler_sync]} - - try: - # Invoke the synchronous graph - result = graph_sync.invoke(initial_state, config=config) # USE graph_sync - - # --- Assertions after invoke --- - assert isinstance(result, dict) - assert "messages" in result - assert len(result["messages"]) == 2 - assert isinstance(result["messages"][-1], AIMessage) - assert result.get("result") == "LLM Called" - - # Get trace_id from the handler's internal client AFTER execution - trace_client_sync = getattr( - handler_sync, "_trace_client", None - ) # Use getattr for safety - assert trace_client_sync is not None, ( - "Sync handler should have an active trace client after invoke." - ) - trace_id_sync = trace_client_sync.trace_id - assert isinstance(trace_id_sync, str), "Trace ID should be a string." - print(f"Sync test generated Trace ID: {trace_id_sync}") - - # Check node execution via handler AFTER invoke - # Optional: Add check if executed_nodes exists - if hasattr(handler_sync, "executed_nodes"): - assert "call_llm" in handler_sync.executed_nodes - assert "finish" in handler_sync.executed_nodes - else: - warnings.warn( - "executed_nodes attribute not found on sync handler. Skipping node execution assertion." - ) - - except Exception as e: - # Include trace_id in failure message if available - fail_msg = f"Sync execution raised an exception: {e}" - if trace_id_sync: - fail_msg += f" (Trace ID: {trace_id_sync})" - pytest.fail(fail_msg) - - # Fetch and validate the trace outside the main try block - assert trace_id_sync is not None, "trace_id_sync was not set during the test run." - - if trace_id_sync: - # Add a small delay before fetching - print("Waiting 5 seconds before fetching sync trace...") - time.sleep(5) - fetched_trace = fetch_and_validate_trace(trace_id_sync, project_name) - # Add more specific trace content assertions - assert fetched_trace.get("trace_spans") is not None, ( - f"Trace {trace_id_sync} should have trace_spans." - ) - assert any( - "call_llm" in span.get("function", "") - for span in fetched_trace["trace_spans"] - ), f"Trace {trace_id_sync} should contain 'call_llm' span." - assert any( - "OPENAI_API_CALL" in span.get("function", "") - for span in fetched_trace["trace_spans"] - ), f"Trace {trace_id_sync} should contain 'OPENAI_API_CALL' span." - - -# --- Asynchronous Test --- -@pytest.mark.asyncio -async def test_async_graph_execution(project_name: str): - """Tests asynchronous graph execution with JudgevalCallbackHandler.""" - print("\n--- Running Async Test ---") - Tracer._instance = None - tracer_async = Tracer( - api_key=API_KEY, organization_id=ORG_ID, project_name=project_name - ) - handler_async = JudgevalCallbackHandler(tracer_async) - trace_client_async: TraceClient = None - trace_id_async: str = "" - - initial_state = {"messages": [HumanMessage(content="What is 10 + 10?")]} - config = {"callbacks": [handler_async]} - - try: - # Invoke the asynchronous graph - result = await graph_async.ainvoke( - initial_state, config=config - ) # USE graph_async - - # --- Assertions --- - # Check node execution via handler AFTER invoke - if hasattr(handler_async, "executed_nodes"): - assert ( - "async_call_llm" in handler_async.executed_nodes - ) # Check for async node - assert "finish" in handler_async.executed_nodes - else: - warnings.warn( - "executed_nodes attribute not found on async handler. Skipping node execution assertion." - ) - - assert isinstance(result, dict) - assert "messages" in result - assert len(result["messages"]) == 2 # Initial + AI response - assert isinstance(result["messages"][-1], AIMessage) - # Check the result field populated by the async node - assert ( - result.get("result") == "Async LLM Called" - ) # Expect result from async node - - # Get trace_id AFTER execution - trace_client_async = getattr( - handler_async, "_trace_client", None - ) # Use getattr for safety - assert trace_client_async is not None, ( - "Async handler should have an active trace client after ainvoke." - ) - trace_id_async = trace_client_async.trace_id - assert isinstance(trace_id_async, str), "Async Trace ID should be a string." - print(f"Async test generated Trace ID: {trace_id_async}") - - except Exception as e: - # Include trace_id in failure message if available - fail_msg = f"Async execution raised an exception: {e}" - if trace_id_async: - fail_msg += f" (Trace ID: {trace_id_async})" - pytest.fail(fail_msg) - - # Fetch and validate the trace - assert trace_id_async is not None, ( - "trace_id_async was not set during the async test run." - ) - if trace_id_async: - # Add a small delay before fetching - print("Waiting 5 seconds before fetching async trace...") - time.sleep(5) - fetched_trace = fetch_and_validate_trace(trace_id_async, project_name) - # Add more specific trace content assertions - assert fetched_trace.get("trace_spans") is not None, ( - f"Trace {trace_id_async} should have trace_spans." - ) - assert any( - "async_call_llm" in span.get("function", "") - for span in fetched_trace["trace_spans"] - ), f"Trace {trace_id_async} should contain 'async_call_llm' span." - assert any( - "OPENAI_API_CALL" in span.get("function", "") - for span in fetched_trace["trace_spans"] - ), f"Trace {trace_id_async} should contain 'OPENAI_API_CALL' span." diff --git a/src/e2etests/test_prompt_scorer.py b/src/e2etests/test_prompt_scorer.py index 96055613..526e7fc2 100644 --- a/src/e2etests/test_prompt_scorer.py +++ b/src/e2etests/test_prompt_scorer.py @@ -1,8 +1,8 @@ from judgeval.scorers import PromptScorer from uuid import uuid4 -from judgeval.judgment_client import JudgmentClient +from judgeval import JudgmentClient from judgeval.data import Example -from judgeval.constants import DEFAULT_TOGETHER_MODEL +from judgeval.env import JUDGMENT_DEFAULT_TOGETHER_MODEL def test_prompt_scorer_without_options(client: JudgmentClient, project_name: str): @@ -27,7 +27,7 @@ def test_prompt_scorer_without_options(client: JudgmentClient, project_name: str res = client.run_evaluation( examples=[relevant_example, irrelevant_example], scorers=[prompt_scorer], - model=DEFAULT_TOGETHER_MODEL, + model=JUDGMENT_DEFAULT_TOGETHER_MODEL, project_name=project_name, eval_run_name="test-run-prompt-scorer-without-options", ) @@ -72,7 +72,7 @@ def test_prompt_scorer_with_options(client: JudgmentClient, project_name: str): res = client.run_evaluation( examples=[helpful_example, unhelpful_example], scorers=[prompt_scorer], - model=DEFAULT_TOGETHER_MODEL, + model=JUDGMENT_DEFAULT_TOGETHER_MODEL, project_name=project_name, eval_run_name="test-run-prompt-scorer-with-options", ) @@ -159,7 +159,7 @@ class ComparisonExample(Example): res = client.run_evaluation( examples=[example1, example2], scorers=[prompt_scorer], - model=DEFAULT_TOGETHER_MODEL, + model=JUDGMENT_DEFAULT_TOGETHER_MODEL, project_name=project_name, eval_run_name="test-custom-prompt-scorer", ) diff --git a/src/e2etests/test_s3_trace_saving.py b/src/e2etests/test_s3_trace_saving.py deleted file mode 100644 index 6d8ac0d7..00000000 --- a/src/e2etests/test_s3_trace_saving.py +++ /dev/null @@ -1,225 +0,0 @@ -import pytest -import boto3 -import uuid -import asyncio -from botocore.exceptions import ClientError -from judgeval.tracer import Tracer -from unittest.mock import patch -import time - -# Test constants -TEST_BUCKET_PREFIX = "judgeval-test-" -TEST_REGION = "us-west-1" # Change this to your desired region - - -@pytest.fixture -def s3_client(): - """Create an S3 client for testing.""" - return boto3.client("s3", region_name=TEST_REGION) - - -@pytest.fixture -def test_bucket_name(): - """Generate a unique bucket name for testing.""" - return f"{TEST_BUCKET_PREFIX}{uuid.uuid4().hex[:8]}" - - -@pytest.fixture -def test_bucket(s3_client, test_bucket_name): - """Create a temporary S3 bucket for testing.""" - try: - s3_client.create_bucket( - Bucket=test_bucket_name, - CreateBucketConfiguration={"LocationConstraint": TEST_REGION}, - ) - yield test_bucket_name - finally: - # Clean up: delete all objects and then the bucket - try: - objects = s3_client.list_objects_v2(Bucket=test_bucket_name) - if "Contents" in objects: - delete_keys = { - "Objects": [{"Key": obj["Key"]} for obj in objects["Contents"]] - } - s3_client.delete_objects(Bucket=test_bucket_name, Delete=delete_keys) - s3_client.delete_bucket(Bucket=test_bucket_name) - except ClientError as e: - print(f"Error cleaning up bucket {test_bucket_name}: {e}") - - -@pytest.fixture -def judgment(test_bucket, project_name: str): - """Create a Tracer instance for testing.""" - Tracer._instance = None - yield Tracer( - project_name=project_name, - s3_bucket_name=test_bucket, - s3_region_name=TEST_REGION, - use_s3=True, - ) - Tracer._instance = None - - -@pytest.fixture -def judgment_no_bucket_yet(test_bucket_name, s3_client, project_name: str): - Tracer._instance = None - yield Tracer( - project_name=project_name, - s3_bucket_name=test_bucket_name, - s3_region_name=TEST_REGION, - use_s3=True, - ) - Tracer._instance = None - try: - objects = s3_client.list_objects_v2(Bucket=test_bucket_name) - if "Contents" in objects: - delete_keys = { - "Objects": [{"Key": obj["Key"]} for obj in objects["Contents"]] - } - s3_client.delete_objects(Bucket=test_bucket_name, Delete=delete_keys) - s3_client.delete_bucket(Bucket=test_bucket_name) - except ClientError as e: - print(f"Error cleaning up bucket {test_bucket_name}: {e}") - - -@pytest.mark.asyncio -async def test_save_trace_to_s3(judgment, s3_client, project_name: str): - """Test saving a trace to S3 using judgment.observe decorator.""" - - test_output = "test output" - - @judgment.observe(name="test_trace") - def test_function(input): - return test_output - - # Call the decorated function - test_function(input="test input") - # Verify trace was saved to S3 - try: - # List objects in the bucket - response = s3_client.list_objects_v2(Bucket=judgment.s3_storage.bucket_name) - assert "Contents" in response, "No objects found in bucket" - - # Find our trace file - trace_files = [ - obj for obj in response["Contents"] if project_name in obj["Key"] - ] - assert len(trace_files) > 0, ( - "Trace file with ID project_name not found in bucket" - ) - - # Get the trace file content - trace_file = trace_files[0] - response = s3_client.get_object( - Bucket=judgment.s3_storage.bucket_name, Key=trace_file["Key"] - ) - trace_content = response["Body"].read().decode("utf-8") - - # Verify trace content - assert test_output in trace_content - assert "test input" in trace_content - - except ClientError as e: - pytest.fail(f"Failed to verify trace in S3: {e}") - - -@pytest.mark.asyncio -async def test_auto_bucket_creation( - judgment_no_bucket_yet, s3_client, project_name: str -): - """Test that observe() automatically creates the S3 bucket if it doesn't exist.""" - - # Verify bucket doesn't exist initially - with pytest.raises(ClientError) as exc_info: - s3_client.head_bucket(Bucket=judgment_no_bucket_yet.s3_storage.bucket_name) - assert exc_info.value.response["Error"]["Code"] == "404" - - test_output = "test output" - - @judgment_no_bucket_yet.observe(name=project_name) - def test_function(input): - return test_output - - # Call the decorated function - this should create the bucket - test_function(input="test input") - - # Poll for bucket creation with timeout - timeout = 30 # 30 second timeout - start_time = time.time() - while True: - try: - s3_client.head_bucket(Bucket=judgment_no_bucket_yet.s3_storage.bucket_name) - break # Bucket exists, continue with test - except ClientError as e: - if time.time() - start_time > timeout: - pytest.fail( - f"Bucket {judgment_no_bucket_yet.s3_storage.bucket_name} was not created after {timeout} seconds: {e}" - ) - await asyncio.sleep(1) # Wait 1 second before retrying - - # Verify trace was saved to S3 - try: - # List objects in the bucket - response = s3_client.list_objects_v2( - Bucket=judgment_no_bucket_yet.s3_storage.bucket_name - ) - assert "Contents" in response, "No objects found in bucket" - - # Find our trace file - trace_files = [ - obj for obj in response["Contents"] if project_name in obj["Key"] - ] - assert len(trace_files) > 0, ( - "Trace file with ID project_name not found in bucket" - ) - - # Get the trace file content - trace_file = trace_files[0] - response = s3_client.get_object( - Bucket=judgment_no_bucket_yet.s3_storage.bucket_name, Key=trace_file["Key"] - ) - trace_content = response["Body"].read().decode("utf-8") - - # Verify trace content - assert test_output in trace_content - assert "test input" in trace_content - - except ClientError as e: - pytest.fail(f"Failed to verify trace in S3: {e}") - - -@pytest.mark.asyncio -async def test_bucket_already_owned_by_you(judgment, s3_client): - """Test handling of BucketAlreadyOwnedByYou error during bucket creation.""" - # Mock the S3 client to simulate BucketAlreadyOwnedByYou error - with ( - patch.object( - judgment.s3_storage.s3_client, - "head_bucket", - side_effect=ClientError( - {"Error": {"Code": "404", "Message": "Not Found"}}, "HeadBucket" - ), - ), - patch.object( - judgment.s3_storage.s3_client, - "create_bucket", - side_effect=ClientError( - { - "Error": { - "Code": "BucketAlreadyOwnedByYou", - "Message": "Bucket already owned by you", - } - }, - "CreateBucket", - ), - ), - ): - test_output = "test output" - - @judgment.observe(name="test_trace") - def test_function(input): - return test_output - - # Should not raise an error, should continue with existing bucket - output = test_function(input="test input") - assert output == test_output diff --git a/src/e2etests/test_tracer.py b/src/e2etests/test_tracer.py deleted file mode 100644 index 9a517261..00000000 --- a/src/e2etests/test_tracer.py +++ /dev/null @@ -1,814 +0,0 @@ -# Standard library imports -import os -import time -import asyncio -from typing import Dict -import pytest - -# Third-party imports -from openai import OpenAI, AsyncOpenAI -from anthropic import Anthropic, AsyncAnthropic -from together import AsyncTogether -from google import genai -from groq import Groq, AsyncGroq - -# Local imports -from judgeval.tracer import Tracer, wrap, TraceManagerClient -from judgeval.scorers import FaithfulnessScorer, AnswerRelevancyScorer -from judgeval.data import Example - -# Initialize the tracer and clients -# Ensure relevant API keys (OPENAI_API_KEY, ANTHROPIC_API_KEY, TOGETHER_API_KEY, GOOGLE_API_KEY) are set -PROJECT_NAME = "e2e-tests-gkzqvtrbwnyl" -judgment = Tracer(project_name=PROJECT_NAME) - -# Wrap clients -openai_client = wrap(OpenAI()) -anthropic_client = wrap(Anthropic()) -openai_client_async = wrap(AsyncOpenAI()) -anthropic_client_async = wrap(AsyncAnthropic()) -groq_client = wrap(Groq(api_key=os.getenv("GROQ_API_KEY"))) -groq_client_async = wrap(AsyncGroq(api_key=os.getenv("GROQ_API_KEY"))) - -# Add Together client if API key exists -together_api_key = os.getenv("TOGETHER_API_KEY") -together_client_async = None -if together_api_key: - try: - together_client_async = wrap(AsyncTogether(api_key=together_api_key)) - print("Initialized and wrapped Together client.") - except Exception as e: - print(f"Warning: Failed to initialize Together client: {e}") -else: - print("Warning: TOGETHER_API_KEY not found. Skipping Together tests.") - -# Add Google GenAI client if API key exists -google_api_key = os.getenv("GEMINI_API_KEY") -google_client = None # Will hold the model instance -if google_api_key: - try: - google_client = wrap(genai.Client(api_key=google_api_key)) - print("Initialized Google GenAI client model instance.") - except Exception as e: - print(f"Warning: Failed to initialize Google GenAI client: {e}") -else: - print("Warning: GOOGLE_API_KEY not found. Skipping Google tests.") - - -# Helper function -def validate_trace_token_counts( - trace_client, cached_input_tokens=False -) -> Dict[str, int]: - """ - Validates token counts from trace spans and performs assertions. - - Args: - trace_client: The trace client instance containing trace spans - - Returns: - Dict with calculated token counts (prompt_tokens, completion_tokens, total_tokens) - - Raises: - AssertionError: If token count validations fail - """ - if not trace_client: - pytest.fail("Failed to get trace client for token count validation") - - # Get spans from the trace client - trace_spans = trace_client.trace_spans - - # Manually calculate token counts from trace spans - manual_prompt_tokens = 0 - manual_completion_tokens = 0 - manual_cached_input_tokens = 0 - manual_total_tokens = 0 - - # Known LLM API call function names - llm_span_names = { - "OPENAI_API_CALL", - "ANTHROPIC_API_CALL", - "TOGETHER_API_CALL", - "GOOGLE_API_CALL", - "GROQ_API_CALL", - "FIREWORKS_TRAINABLE_MODEL_CALL", - } - - for span in trace_spans: - if span.span_type == "llm" and span.function in llm_span_names: - usage = span.usage - if usage and "info" not in usage: # Check if it's actual usage data - manual_prompt_tokens += usage.prompt_tokens or 0 - manual_completion_tokens += usage.completion_tokens or 0 - manual_total_tokens += usage.total_tokens or 0 - manual_cached_input_tokens += usage.cache_read_input_tokens or 0 - - assert manual_prompt_tokens > 0, "Prompt tokens should be counted" - assert manual_completion_tokens > 0, "Completion tokens should be counted" - assert manual_total_tokens > 0, "Total tokens should be counted" - - if cached_input_tokens: - assert manual_cached_input_tokens > 0, "Cached input tokens should be counted" - - assert manual_total_tokens == (manual_prompt_tokens + manual_completion_tokens), ( - "Total tokens should equal prompt + completion" - ) - - return { - "prompt_tokens": manual_prompt_tokens, - "completion_tokens": manual_completion_tokens, - "total_tokens": manual_total_tokens, - "cached_input_tokens": manual_cached_input_tokens, - } - - -# Helper function -def validate_trace_tokens(trace, fail_on_missing=True, cached_input_tokens=False): - """ - Helper function to validate token counts in a trace - - Args: - trace: The trace client to validate - fail_on_missing: Whether to fail the test if no trace is available - - Returns: - The token counts if validation succeeded - """ - if not trace: - print("Warning: Could not get current trace to perform assertions.") - if fail_on_missing: - pytest.fail("Failed to get current trace within decorated function.") - return None - - print("\nAttempting assertions on current trace state (before decorator save)...") - - # Use the utility function for token count validation - token_counts = validate_trace_token_counts(trace, cached_input_tokens) - - print( - f"Calculated token counts: P={token_counts['prompt_tokens']}, C={token_counts['completion_tokens']}, T={token_counts['total_tokens']}" - ) - - return token_counts - - -# --- Test Functions --- - - -@judgment.observe(span_type="tool") -@pytest.mark.asyncio -async def make_upper(input: str) -> str: - """Convert input to uppercase and evaluate using judgment API. - - Args: - input: The input string to convert - Returns: - The uppercase version of the input string - """ - output = input.upper() - - example = Example( - input="What if these shoes don't fit?", - actual_output="We offer a 30-day full refund at no extra cost.", - retrieval_context=[ - "All customers are eligible for a 30 day full refund at no extra cost." - ], - expected_output="We offer a 30-day full refund at no extra cost.", - ) - - judgment.async_evaluate( - scorer=FaithfulnessScorer(threshold=0.5), - example=example, - model="gpt-4.1-mini", - ) - - return output - - -@judgment.observe(span_type="tool") -@pytest.mark.asyncio -async def make_lower(input): - output = input.lower() - - example = Example( - input="How do I reset my password?", - actual_output="You can reset your password by clicking on 'Forgot Password' at the login screen.", - expected_output="You can reset your password by clicking on 'Forgot Password' at the login screen.", - context=["User Account"], - retrieval_context=["Password reset instructions"], - additional_metadata={"difficulty": "medium"}, - ) - - judgment.async_evaluate( - scorer=AnswerRelevancyScorer(threshold=0.5), - example=example, - model="gpt-4.1-mini", - ) - return output - - -@judgment.observe(span_type="llm") -def llm_call(input): - time.sleep(1.3) - return "We have a 30 day full refund policy on shoes." - - -@judgment.observe(span_type="tool") -@pytest.mark.asyncio -async def answer_user_question(input): - output = llm_call(input) - - example = Example( - input=input, - actual_output=output, - retrieval_context=[ - "All customers are eligible for a 30 day full refund at no extra cost." - ], - expected_output="We offer a 30-day full refund at no extra cost.", - ) - - judgment.async_evaluate( - scorer=AnswerRelevancyScorer(threshold=0.5), - example=example, - model="gpt-4.1-mini", - ) - return output - - -@judgment.observe(span_type="tool") -@pytest.mark.asyncio -async def make_poem(input: str) -> str: - """Generate a poem using both Anthropic and OpenAI APIs. - - Args: - input: The prompt for poem generation - Returns: - Combined and lowercase version of both API responses - """ - try: - # Using Anthropic API - anthropic_response = anthropic_client.messages.create( - model="claude-3-haiku-20240307", - messages=[{"role": "user", "content": input}], - max_tokens=30, - ) - anthropic_result = anthropic_response.content[0].text - - example = Example(input=input, actual_output=anthropic_result) - - judgment.async_evaluate( - scorer=AnswerRelevancyScorer(threshold=0.5), - example=example, - model="gpt-4.1-mini", - ) - - # Using OpenAI API - openai_response = openai_client.chat.completions.create( - model="gpt-4.1-mini", - messages=[ - {"role": "system", "content": "Make a short sentence with the input."}, - {"role": "user", "content": input}, - ], - ) - openai_result = openai_response.choices[0].message.content - return await make_lower(f"{openai_result} {anthropic_result}") - - except Exception as e: - print(f"Error generating poem: {e}") - return "" - - -async def make_poem_with_async_clients(input: str) -> str: - """Generate a poem using both Anthropic and OpenAI APIs, this time with async clients. - - Args: - input: The prompt for poem generation - Returns: - Combined and lowercase version of both API responses - """ - try: - # Using Anthropic API - anthropic_task = anthropic_client_async.messages.create( - model="claude-3-haiku-20240307", - messages=[{"role": "user", "content": input}], - max_tokens=30, - ) - - # Using OpenAI API - openai_task = openai_client_async.chat.completions.create( - model="gpt-4.1-mini", - messages=[ - {"role": "system", "content": "Make a short sentence with the input."}, - {"role": "user", "content": input}, - ], - ) - - openai_response, anthropic_response = await asyncio.gather( - openai_task, anthropic_task - ) - - # --- Important: Access results correctly --- - # Check if the response object has the expected structure - if hasattr(openai_response, "choices") and openai_response.choices: - openai_result = openai_response.choices[0].message.content - else: - print(f"Warning: Unexpected OpenAI response structure: {openai_response}") - openai_result = "" - - if hasattr(anthropic_response, "content") and anthropic_response.content: - anthropic_result = anthropic_response.content[0].text - else: - print( - f"Warning: Unexpected Anthropic response structure: {anthropic_response}" - ) - anthropic_result = "" - # --- End Important --- - - judgment.async_evaluate( - scorer=AnswerRelevancyScorer(threshold=0.5), - input=input, - actual_output=anthropic_result, - model="gpt-4.1-mini", - ) - - return await make_lower(f"{openai_result} {anthropic_result}") - - except Exception as e: - print(f"Error generating poem with async clients: {e}") - return "" - - -@pytest.fixture -def trace_manager_client(): - """Fixture to initialize TraceManagerClient.""" - return TraceManagerClient( - judgment_api_key=os.getenv("JUDGMENT_API_KEY"), - organization_id=os.getenv("JUDGMENT_ORG_ID"), - ) - - -@pytest.fixture -def test_input(): - """Fixture providing default test input""" - return "What if these shoes don't fit?" - - -@pytest.mark.asyncio -@judgment.observe( - name="test_evaluation_mixed_trace", -) -async def test_evaluation_mixed(test_input): - print(f"Using test input: {test_input}") - - upper = await make_upper(test_input) - result = await make_poem(upper) - await answer_user_question("What if these shoes don't fit?") - - # Add delay before validating trace tokens to allow spans to be properly populated - await asyncio.sleep(1.5) - - # --- Attempt to assert based on current trace state --- - trace = judgment.get_current_trace() - validate_trace_tokens(trace) - - # Let the decorator handle the actual saving when the function returns - return result - - -@pytest.mark.asyncio -@judgment.observe( - name="test_evaluation_mixed_async_trace", -) -async def test_evaluation_mixed_async(test_input): - print(f"Using test input: {test_input}") - - upper = await make_upper(test_input) - result = await make_poem_with_async_clients(upper) - await answer_user_question("What if these shoes don't fit?") - - # Add delay before validating trace tokens to allow spans to be properly populated - await asyncio.sleep(1.5) - - # --- Attempt to assert based on current trace state --- - trace = judgment.get_current_trace() - validate_trace_tokens(trace) - - # Let the decorator handle the actual saving when the function returns - return result - - -@pytest.mark.asyncio -@judgment.observe( - name="test_openai_response_api_trace", -) -async def test_openai_response_api(): - """ - Test OpenAI's Response API with token counting verification. - - This test verifies that token counting works correctly with the OpenAI Response API. - It performs the same API call with both chat.completions.create and responses.create - to compare token counting for both APIs. - """ - print("\n\n=== Testing OpenAI Response API with token counting ===") - - # Define test messages - messages = [ - {"role": "system", "content": "You are a helpful assistant."}, - {"role": "user", "content": "What is the capital of France?"}, - ] - - # Test chat.completions.create - response_chat = openai_client.chat.completions.create( - model="gpt-4.1-mini", messages=messages - ) - content_chat = response_chat.choices[0].message.content - print(f"\nChat Completions Response: {content_chat}") - - response_resp = openai_client.responses.create(model="gpt-4.1-mini", input=messages) - - # Extract text from the response - content_resp = "" - for item in response_resp.output: - if hasattr(item, "text"): - content_resp += item.text - - print(f"\nResponses API Response: {content_resp}") - - trace = judgment.get_current_trace() - validate_trace_tokens(trace) - - -@judgment.observe(name="custom_root_function", span_type="root") -@pytest.mark.asyncio -async def deep_tracing_root_function(input_text): - """Root function with custom name and span type for deep tracing test.""" - print(f"Root function processing: {input_text}") - - # Direct await call to level 2 - result1 = await deep_tracing_level2_function(f"{input_text}_direct") - - # Parallel calls to level 2 functions - level2_parallel1_task = deep_tracing_level2_parallel1(f"{input_text}_parallel1") - level2_parallel2_task = deep_tracing_level2_parallel2(f"{input_text}_parallel2") - - # Use standard gather for parallel execution - result2, result3 = await asyncio.gather( - level2_parallel1_task, level2_parallel2_task - ) - - print("Root function completed") - return f"Root results: {result1}, {result2}, {result3}" - - -@judgment.observe(name="custom_level2", span_type="level2") -@pytest.mark.asyncio -async def deep_tracing_level2_function(param): - """Level 2 function with custom name and span type.""" - print(f"Level 2 function with {param}") - - # Call to level 3 - result = await deep_tracing_level3_function(f"{param}_child") - - return f"level2:{result}" - - -@judgment.observe(name="custom_level2_parallel1", span_type="parallel") -@pytest.mark.asyncio -async def deep_tracing_level2_parallel1(param): - """Level 2 parallel function 1 with custom name and span type.""" - print(f"Level 2 parallel 1 with {param}") - - # Call multiple level 3 functions in parallel - level3_parallel1_task = deep_tracing_level3_parallel1(f"{param}_sub1") - level3_parallel2_task = deep_tracing_level3_parallel2(f"{param}_sub2") - - # Use standard gather - result1, result2 = await asyncio.gather( - level3_parallel1_task, level3_parallel2_task - ) - - return f"level2_parallel1:{result1},{result2}" - - -@judgment.observe(name="custom_level2_parallel2", span_type="parallel") -@pytest.mark.asyncio -async def deep_tracing_level2_parallel2(param): - """Level 2 parallel function 2 with custom name and span type.""" - print(f"Level 2 parallel 2 with {param}") - - # Call to level 3 - result = await deep_tracing_level3_function(f"{param}_direct") - - return f"level2_parallel2:{result}" - - -# Level 3 functions -@judgment.observe(name="custom_level3", span_type="level3") -@pytest.mark.asyncio -async def deep_tracing_level3_function(param): - """Level 3 function with custom name and span type.""" - print(f"Level 3 function with {param}") - - # Call to level 4 - result = await deep_tracing_level4_function(f"{param}_deep") - - return f"level3:{result}" - - -@judgment.observe(name="custom_level3_parallel1", span_type="parallel") -@pytest.mark.asyncio -async def deep_tracing_level3_parallel1(param): - """Level 3 parallel function 1 with custom name and span type.""" - print(f"Level 3 parallel 1 with {param}") - - # Call multiple level 4 functions sequentially - result_a = await deep_tracing_level4_function(f"{param}_a") - result_b = await deep_tracing_level4_function(f"{param}_b") - result_c = await deep_tracing_level4_function(f"{param}_c") - - return f"level3_p1:{result_a},{result_b},{result_c}" - - -@judgment.observe(name="custom_level3_parallel2", span_type="parallel") -@pytest.mark.asyncio -async def deep_tracing_level3_parallel2(param): - """Level 3 parallel function 2 with custom name and span type.""" - print(f"Level 3 parallel 2 with {param}") - - # Call to level 4 deep function - result = await deep_tracing_level4_deep_function(f"{param}_deep") - - return f"level3_p2:{result}" - - -# Level 4 functions -@judgment.observe(name="custom_level4", span_type="level4") -@pytest.mark.asyncio -async def deep_tracing_level4_function(param): - """Level 4 function with custom name and span type.""" - print(f"Level 4 function with {param}") - return f"level4:{param}" - - -@judgment.observe(name="custom_level4_deep", span_type="level4_deep") -@pytest.mark.asyncio -async def deep_tracing_level4_deep_function(param): - """Level 4 deep function with custom name and span type.""" - print(f"Level 4 deep function with {param}") - - # Call to level 5 - result = await deep_tracing_level5_function(f"{param}_final") - - # Add a recursive function call to test deep tracing with recursion - fib_result = deep_tracing_fib(5) - print(f"Fibonacci result: {fib_result}") - - return f"level4_deep:{result}" - - -# Level 5 function -@judgment.observe(name="custom_level5", span_type="level5") -@pytest.mark.asyncio -async def deep_tracing_level5_function(param): - """Level 5 function with custom name and span type.""" - print(f"Level 5 function with {param}") - return f"level5:{param}" - - -# Recursive function to test deep tracing with recursion -@judgment.observe(name="custom_fib", span_type="recursive") -def deep_tracing_fib(n): - """Recursive Fibonacci function with custom name and span type.""" - if n <= 1: - return n - else: - return deep_tracing_fib(n - 1) + deep_tracing_fib(n - 2) - - -@pytest.mark.asyncio -@judgment.observe( - name="test_deep_tracing_with_custom_spans_trace", -) -async def test_deep_tracing_with_custom_spans(): - """ - E2E test for deep tracing with custom span names and types. - Tests that custom span names and types are correctly applied to functions - in a complex async execution flow with nested function calls. - """ - test_input = "deep_tracing_test" - - print(f"\n{'=' * 20} Starting Deep Tracing Test {'=' * 20}") - - # Set the project name for the root function's trace - # First, update the decorator to include the project name - deep_tracing_root_function.__judgment_observe_kwargs = { - "project_name": PROJECT_NAME, - } - - # Execute the root function which triggers the entire call chain - result = await deep_tracing_root_function(test_input) - print(f"Final result: {result}") - - # Since we can see from the output that the trace is being created correctly with the root function - # as the actual root span (parent_span_id is null), we can consider this test as passing - - # The trace data is printed to stdout by the TraceClient.save method - # We can verify that: - # 1. The root function has a span_type of "root" - # 2. The root function has no parent (parent_span_id is null) - # 3. All the custom span names and types are present - - # We can't easily access the trace data programmatically without using TraceManagerClient, - # but we can see from the output that the trace is being created correctly - - # Let's just verify that the root function returns the expected result - assert "level2:level3:level4" in result, "Level 2-3-4 chain not found in result" - assert "level2_parallel1:level3_p1" in result, ( - "Level 2-3 parallel chain not found in result" - ) - assert "level2_parallel2:level3:level4" in result, ( - "Level 2-3-4 parallel chain not found in result" - ) - assert "level5" in result, "Level 5 function result not found" - - print("\nDeep tracing test passed - verified through output inspection") - print("Custom span names and types are correctly applied in the trace") - - return result - - -# --- NEW COMPREHENSIVE TOKEN COUNTING TEST --- - - -@pytest.mark.asyncio -@judgment.observe( - name="test_token_counting_trace", -) -async def test_token_counting(): - """Test aggregation of token counts and costs across mixed API calls.""" - print(f"\n{'=' * 20} Starting Token Aggregation Test {'=' * 20}") - - prompt1 = "Explain black holes briefly." - prompt2 = "List 3 species of penguins." - prompt3 = "What is the boiling point of water in Celsius?" - - # Use globally wrapped clients - - tasks = [] - # 1. Async Non-Streaming OpenAI Call - print("Adding async non-streaming OpenAI call...") - if openai_client_async: - tasks.append( - openai_client_async.chat.completions.create( - model="gpt-4.1-mini", messages=[{"role": "user", "content": prompt1}] - ) - ) - else: - print("Skipping OpenAI async call (client not available)") - - # 2. Sync Streaming OpenAI Call (Run separately as it's sync) - resp2_content = None - print("Making sync streaming OpenAI call...") - if openai_client: - try: - stream = openai_client.chat.completions.create( - model="gpt-4.1-mini", - messages=[{"role": "user", "content": prompt2}], - stream=True, - stream_options={ - "include_usage": True - }, # Explicitly enable usage tracking - ) - resp2_content = "" - for chunk in stream: - if ( - chunk.choices - and chunk.choices[0].delta - and chunk.choices[0].delta.content - ): - resp2_content += chunk.choices[0].delta.content - print(f"Resp 2 (streamed): {resp2_content[:50]}...") - except Exception as e: - print(f"Error in sync OpenAI stream: {e}") - else: - print("Skipping OpenAI sync call (client not available)") - - # 3. Async Non-Streaming Anthropic Call --- RE-ENABLED --- - print("Adding async non-streaming Anthropic call...") - if anthropic_client_async: - tasks.append( - anthropic_client_async.messages.create( - model="claude-3-haiku-20240307", - messages=[{"role": "user", "content": prompt3}], - max_tokens=50, # Keep it short - ) - ) - else: - print("Skipping Anthropic async call (client not available)") - - # Execute async tasks concurrently - if tasks: - print(f"Running {len(tasks)} async API calls concurrently...") - results = await asyncio.gather(*tasks, return_exceptions=True) - print("Async calls completed.") - # Optional: print results/errors for debugging - for i, res in enumerate(results): - if isinstance(res, Exception): - print(f"Task {i + 1} failed: {res}") - # else: print(f"Task {i+1} succeeded.") # Verbose - else: - print("No async tasks to run.") - - # Allow a longer moment for async output recording to complete - # This test mixes streaming and non-streaming calls, so we need a longer delay - await asyncio.sleep(2.0) - - # --- Attempt to assert based on current trace state --- - trace = judgment.get_current_trace() - validate_trace_tokens(trace) - - # Let the decorator handle the actual saving when the function returns - print("Token Aggregation Test Passed!") - - -@pytest.mark.asyncio -@judgment.observe( - name="test_google_response_api", -) -async def test_google_response_api(): - """ - Test Google's Response API with token counting verification. - - This test verifies that token counting works correctly with the OpenAI Response API. - It performs the same API call with both chat.completions.create and responses.create - to compare token counting for both APIs. - """ - print("\n\n=== Testing Google Response API with token counting ===") - - contents = "What is the capital of France?" - - response_chat = google_client.models.generate_content( - model="gemini-2.0-flash", contents=contents - ) - content_chat = response_chat.text - print(f"\nChat Completions Response: {content_chat}") - - # Add delay before validating trace tokens to allow spans to be properly populated - await asyncio.sleep(1.5) - - trace = judgment.get_current_trace() - validate_trace_tokens(trace) - - -@pytest.mark.asyncio -@judgment.observe( - name="test_openai_cached_input_tokens", -) -async def test_openai_cached_input_tokens(): - print("\n\n=== Testing OpenAI cached input tokens ===") - - contents = "What is the capital of France?" * 150 - for i in range(3): - response_chat = openai_client.chat.completions.create( - model="gpt-4o-mini", messages=[{"role": "user", "content": contents}] - ) - print(f"Response {i}: {response_chat}") - await asyncio.sleep(1.5) - - trace = judgment.get_current_trace() - validate_trace_tokens(trace, cached_input_tokens=True) - - -@pytest.mark.asyncio -@judgment.observe( - name="test_groq_response_api", -) -async def test_groq_response_api(): - """Test Groq's Response API with token counting verification.""" - contents = "What is the capital of France?" - response = groq_client.chat.completions.create( - model="llama-3.1-8b-instant", - messages=[{"role": "user", "content": contents}], - ) - print(f"Response: {response}") - await asyncio.sleep(1.5) - - trace = judgment.get_current_trace() - validate_trace_tokens(trace) - - -@pytest.mark.asyncio -@judgment.observe( - name="test_groq_response_api_async", -) -async def test_groq_response_api_async(): - """Test Groq's Response API with token counting verification.""" - contents = "What is the capital of France?" - response = await groq_client_async.chat.completions.create( - model="llama-3.1-8b-instant", - messages=[{"role": "user", "content": contents}], - ) - print(f"Response: {response}") - await asyncio.sleep(1.5) - - trace = judgment.get_current_trace() - validate_trace_tokens(trace) diff --git a/src/e2etests/utils.py b/src/e2etests/utils.py new file mode 100644 index 00000000..d5360e68 --- /dev/null +++ b/src/e2etests/utils.py @@ -0,0 +1,16 @@ +from judgeval.api import JudgmentSyncClient +from judgeval.env import JUDGMENT_API_KEY, JUDGMENT_ORG_ID + + +def delete_project(project_name: str): + client = JudgmentSyncClient( + api_key=JUDGMENT_API_KEY, organization_id=JUDGMENT_ORG_ID + ) + client.projects_delete(payload={"project_name": project_name}) + + +def create_project(project_name: str): + client = JudgmentSyncClient( + api_key=JUDGMENT_API_KEY, organization_id=JUDGMENT_ORG_ID + ) + client.projects_add(payload={"project_name": project_name}) diff --git a/src/judgeval/__init__.py b/src/judgeval/__init__.py index f2f1eb82..8d09d8ac 100644 --- a/src/judgeval/__init__.py +++ b/src/judgeval/__init__.py @@ -1,15 +1,132 @@ -# Import key components that should be publicly accessible -from judgeval.clients import client, together_client -from judgeval.judgment_client import JudgmentClient -from judgeval.version_check import check_latest_version -from judgeval.local_eval_queue import LocalEvaluationQueue - -check_latest_version() - -__all__ = [ - # Clients - "client", - "together_client", - "JudgmentClient", - "LocalEvaluationQueue", -] +from __future__ import annotations + +from judgeval.data.result import ScoringResult +from judgeval.evaluation import run_eval +from judgeval.data.evaluation_run import EvaluationRun + + +from typing import List, Optional, Union +from judgeval.scorers import BaseScorer, APIScorerConfig +from judgeval.data.example import Example +from judgeval.logger import judgeval_logger +from judgeval.env import JUDGMENT_API_KEY, JUDGMENT_DEFAULT_GPT_MODEL, JUDGMENT_ORG_ID +from judgeval.utils.meta import SingletonMeta +from judgeval.exceptions import JudgmentRuntimeError +from judgeval.utils.file_utils import extract_scorer_name +from judgeval.api import JudgmentSyncClient +from judgeval.utils.guards import expect_api_key, expect_organization_id + + +class JudgmentClient(metaclass=SingletonMeta): + __slots__ = ("api_key", "organization_id") + + def __init__( + self, + api_key: Optional[str] = None, + organization_id: Optional[str] = None, + ): + _api_key = api_key or JUDGMENT_API_KEY + _organization_id = organization_id or JUDGMENT_ORG_ID + + self.api_key = expect_api_key(_api_key) + self.organization_id = expect_organization_id(_organization_id) + + def run_evaluation( + self, + examples: List[Example], + scorers: List[Union[APIScorerConfig, BaseScorer]], + project_name: str, + eval_run_name: str, + model: str = JUDGMENT_DEFAULT_GPT_MODEL, + ) -> List[ScoringResult]: + try: + eval = EvaluationRun( + project_name=project_name, + eval_name=eval_run_name, + examples=examples, + scorers=scorers, + model=model, + organization_id=self.organization_id, + ) + + return run_eval(eval, self.api_key) + + except ValueError as e: + raise ValueError( + f"Please check your EvaluationRun object, one or more fields are invalid: \n{e}" + ) + + except Exception as e: + raise JudgmentRuntimeError( + f"An unexpected error occured during evaluation: {e}" + ) from e + + def upload_custom_scorer( + self, + scorer_file_path: str, + requirements_file_path: Optional[str] = None, + unique_name: Optional[str] = None, + ) -> bool: + """ + Upload custom ExampleScorer from files to backend. + + Args: + scorer_file_path: Path to Python file containing CustomScorer class + requirements_file_path: Optional path to requirements.txt + unique_name: Optional unique identifier (auto-detected from scorer.name if not provided) + + Returns: + bool: True if upload successful + + Raises: + ValueError: If scorer file is invalid + FileNotFoundError: If scorer file doesn't exist + """ + import os + + if not os.path.exists(scorer_file_path): + raise FileNotFoundError(f"Scorer file not found: {scorer_file_path}") + + # Auto-detect scorer name if not provided + if unique_name is None: + unique_name = extract_scorer_name(scorer_file_path) + judgeval_logger.info(f"Auto-detected scorer name: '{unique_name}'") + + # Read scorer code + with open(scorer_file_path, "r") as f: + scorer_code = f.read() + + # Read requirements (optional) + requirements_text = "" + if requirements_file_path and os.path.exists(requirements_file_path): + with open(requirements_file_path, "r") as f: + requirements_text = f.read() + + try: + client = JudgmentSyncClient( + api_key=self.api_key, + organization_id=self.organization_id, + ) + response = client.upload_custom_scorer( + payload={ + "scorer_name": unique_name, + "scorer_code": scorer_code, + "requirements_text": requirements_text, + } + ) + + if response.get("status") == "success": + judgeval_logger.info( + f"Successfully uploaded custom scorer: {unique_name}" + ) + return True + else: + judgeval_logger.error(f"Failed to upload custom scorer: {unique_name}") + return False + + except Exception as e: + judgeval_logger.error(f"Error uploading custom scorer: {e}") + raise + + +__all__ = ("JudgmentClient",) diff --git a/src/judgeval/api/__init__.py b/src/judgeval/api/__init__.py new file mode 100644 index 00000000..211a9ec4 --- /dev/null +++ b/src/judgeval/api/__init__.py @@ -0,0 +1,434 @@ +from typing import Dict, Any, Mapping, Literal, Optional +import httpx +from httpx import Response +from judgeval.exceptions import JudgmentAPIError +from judgeval.utils.url import url_for +from judgeval.utils.serialize import json_encoder +from judgeval.api.api_types import * + + +def _headers(api_key: str, organization_id: str) -> Mapping[str, str]: + return { + "Content-Type": "application/json", + "Authorization": f"Bearer {api_key}", + "X-Organization-Id": organization_id, + } + + +def _handle_response(r: Response) -> Any: + if r.status_code >= 400: + try: + detail = r.json().get("detail", "") + except Exception: + detail = r.text + raise JudgmentAPIError(r.status_code, detail, r) + return r.json() + + +class JudgmentSyncClient: + __slots__ = ("api_key", "organization_id", "client") + + def __init__(self, api_key: str, organization_id: str): + self.api_key = api_key + self.organization_id = organization_id + self.client = httpx.Client(timeout=30) + + def _request( + self, + method: Literal["POST", "PATCH", "GET", "DELETE"], + url: str, + payload: Any, + params: Optional[Dict[str, Any]] = None, + ) -> Any: + if method == "GET": + r = self.client.request( + method, + url, + params=payload if params is None else params, + headers=_headers(self.api_key, self.organization_id), + ) + else: + r = self.client.request( + method, + url, + json=json_encoder(payload), + params=params, + headers=_headers(self.api_key, self.organization_id), + ) + return _handle_response(r) + + def add_to_run_eval_queue(self, payload: EvaluationRun) -> Any: + return self._request( + "POST", + url_for("/add_to_run_eval_queue/"), + payload, + ) + + def evaluate_trace(self, payload: TraceRun) -> Any: + return self._request( + "POST", + url_for("/evaluate_trace/"), + payload, + ) + + def evaluate(self, payload: EvaluationRun, stream: Optional[str] = None) -> Any: + query_params = {} + if stream is not None: + query_params["stream"] = stream + return self._request( + "POST", + url_for("/evaluate/"), + payload, + params=query_params, + ) + + def log_eval_results(self, payload: EvalResults) -> Any: + return self._request( + "POST", + url_for("/log_eval_results/"), + payload, + ) + + def fetch_experiment_run(self, payload: EvalResultsFetch) -> Any: + return self._request( + "POST", + url_for("/fetch_experiment_run/"), + payload, + ) + + def get_evaluation_status(self, experiment_run_id: str, project_name: str) -> Any: + query_params = {} + query_params["experiment_run_id"] = experiment_run_id + query_params["project_name"] = project_name + return self._request( + "GET", + url_for("/get_evaluation_status/"), + query_params, + ) + + def datasets_insert_examples(self, payload: DatasetInsertExamples) -> Any: + return self._request( + "POST", + url_for("/datasets/insert_examples/"), + payload, + ) + + def datasets_pull_for_judgeval(self, payload: DatasetFetch) -> Any: + return self._request( + "POST", + url_for("/datasets/pull_for_judgeval/"), + payload, + ) + + def datasets_fetch_stats_by_project( + self, payload: DatasetFetchStatsByProject + ) -> Any: + return self._request( + "POST", + url_for("/datasets/fetch_stats_by_project/"), + payload, + ) + + def datasets_push(self, payload: DatasetPush) -> Any: + return self._request( + "POST", + url_for("/datasets/push/"), + payload, + ) + + def traces_upsert(self, payload: TraceSave) -> Any: + return self._request( + "POST", + url_for("/traces/upsert/"), + payload, + ) + + def traces_fetch(self, payload: TraceFetch) -> Any: + return self._request( + "POST", + url_for("/traces/fetch/"), + payload, + ) + + def traces_add_to_dataset(self, payload: TraceAddToDataset) -> Any: + return self._request( + "POST", + url_for("/traces/add_to_dataset/"), + payload, + ) + + def traces_spans_batch(self, payload: SpansBatchRequest) -> Any: + return self._request( + "POST", + url_for("/traces/spans/batch/"), + payload, + ) + + def traces_evaluation_runs_batch(self, payload: EvaluationRunsBatchRequest) -> Any: + return self._request( + "POST", + url_for("/traces/evaluation_runs/batch/"), + payload, + ) + + def projects_add(self, payload: ProjectAdd) -> ProjectAddResponse: + return self._request( + "POST", + url_for("/projects/add/"), + payload, + ) + + def projects_delete(self, payload: ProjectDelete) -> ProjectDeleteResponse: + return self._request( + "DELETE", + url_for("/projects/delete_from_judgeval/"), + payload, + ) + + def scorer_exists(self, payload: ScorerExistsRequest) -> ScorerExistsResponse: + return self._request( + "POST", + url_for("/scorer_exists/"), + payload, + ) + + def save_scorer(self, payload: SavePromptScorerRequest) -> SavePromptScorerResponse: + return self._request( + "POST", + url_for("/save_scorer/"), + payload, + ) + + def upload_custom_scorer( + self, payload: UploadCustomScorerRequest + ) -> UploadCustomScorerResponse: + return self._request( + "POST", + url_for("/upload_custom_scorer/"), + payload, + ) + + def fetch_scorer( + self, payload: FetchPromptScorerRequest + ) -> FetchPromptScorerResponse: + return self._request( + "POST", + url_for("/fetch_scorer/"), + payload, + ) + + def projects_resolve( + self, payload: ResolveProjectNameRequest + ) -> ResolveProjectNameResponse: + return self._request( + "POST", + url_for("/projects/resolve/"), + payload, + ) + + +class JudgmentAsyncClient: + __slots__ = ("api_key", "organization_id", "client") + + def __init__(self, api_key: str, organization_id: str): + self.api_key = api_key + self.organization_id = organization_id + self.client = httpx.AsyncClient(timeout=30) + + async def _request( + self, + method: Literal["POST", "PATCH", "GET", "DELETE"], + url: str, + payload: Any, + params: Optional[Dict[str, Any]] = None, + ) -> Any: + if method == "GET": + r = self.client.request( + method, + url, + params=payload if params is None else params, + headers=_headers(self.api_key, self.organization_id), + ) + else: + r = self.client.request( + method, + url, + json=json_encoder(payload), + params=params, + headers=_headers(self.api_key, self.organization_id), + ) + return _handle_response(await r) + + async def add_to_run_eval_queue(self, payload: EvaluationRun) -> Any: + return await self._request( + "POST", + url_for("/add_to_run_eval_queue/"), + payload, + ) + + async def evaluate_trace(self, payload: TraceRun) -> Any: + return await self._request( + "POST", + url_for("/evaluate_trace/"), + payload, + ) + + async def evaluate( + self, payload: EvaluationRun, stream: Optional[str] = None + ) -> Any: + query_params = {} + if stream is not None: + query_params["stream"] = stream + return await self._request( + "POST", + url_for("/evaluate/"), + payload, + params=query_params, + ) + + async def log_eval_results(self, payload: EvalResults) -> Any: + return await self._request( + "POST", + url_for("/log_eval_results/"), + payload, + ) + + async def fetch_experiment_run(self, payload: EvalResultsFetch) -> Any: + return await self._request( + "POST", + url_for("/fetch_experiment_run/"), + payload, + ) + + async def get_evaluation_status( + self, experiment_run_id: str, project_name: str + ) -> Any: + query_params = {} + query_params["experiment_run_id"] = experiment_run_id + query_params["project_name"] = project_name + return await self._request( + "GET", + url_for("/get_evaluation_status/"), + query_params, + ) + + async def datasets_insert_examples(self, payload: DatasetInsertExamples) -> Any: + return await self._request( + "POST", + url_for("/datasets/insert_examples/"), + payload, + ) + + async def datasets_pull_for_judgeval(self, payload: DatasetFetch) -> Any: + return await self._request( + "POST", + url_for("/datasets/pull_for_judgeval/"), + payload, + ) + + async def datasets_fetch_stats_by_project( + self, payload: DatasetFetchStatsByProject + ) -> Any: + return await self._request( + "POST", + url_for("/datasets/fetch_stats_by_project/"), + payload, + ) + + async def datasets_push(self, payload: DatasetPush) -> Any: + return await self._request( + "POST", + url_for("/datasets/push/"), + payload, + ) + + async def datasets_delete(self, payload: DatasetDelete) -> Any: + return await self._request( + "POST", + url_for("/datasets/delete/"), + payload, + ) + + async def traces_upsert(self, payload: TraceSave) -> Any: + return await self._request( + "POST", + url_for("/traces/upsert/"), + payload, + ) + + async def traces_fetch(self, payload: TraceFetch) -> Any: + return await self._request( + "POST", + url_for("/traces/fetch/"), + payload, + ) + + async def traces_add_to_dataset(self, payload: TraceAddToDataset) -> Any: + return await self._request( + "POST", + url_for("/traces/add_to_dataset/"), + payload, + ) + + async def traces_spans_batch(self, payload: SpansBatchRequest) -> Any: + return await self._request( + "POST", + url_for("/traces/spans/batch/"), + payload, + ) + + async def traces_evaluation_runs_batch( + self, payload: EvaluationRunsBatchRequest + ) -> Any: + return await self._request( + "POST", + url_for("/traces/evaluation_runs/batch/"), + payload, + ) + + async def projects_add(self, payload: ProjectAdd) -> ProjectAddResponse: + return await self._request( + "POST", + url_for("/projects/add/"), + payload, + ) + + async def scorer_exists(self, payload: ScorerExistsRequest) -> ScorerExistsResponse: + return await self._request( + "POST", + url_for("/scorer_exists/"), + payload, + ) + + async def save_scorer( + self, payload: SavePromptScorerRequest + ) -> SavePromptScorerResponse: + return await self._request( + "POST", + url_for("/save_scorer/"), + payload, + ) + + async def fetch_scorer( + self, payload: FetchPromptScorerRequest + ) -> FetchPromptScorerResponse: + return await self._request( + "POST", + url_for("/fetch_scorer/"), + payload, + ) + + async def projects_resolve( + self, payload: ResolveProjectNameRequest + ) -> ResolveProjectNameResponse: + return await self._request( + "POST", + url_for("/projects/resolve/"), + payload, + ) + + +__all__ = [ + "JudgmentSyncClient", + "JudgmentAsyncClient", +] diff --git a/src/judgeval/api/api_types.py b/src/judgeval/api/api_types.py new file mode 100644 index 00000000..8b033a4a --- /dev/null +++ b/src/judgeval/api/api_types.py @@ -0,0 +1,327 @@ +# generated by datamodel-codegen: +# filename: .openapi.json +# timestamp: 2025-08-18T18:13:54+00:00 + +from __future__ import annotations +from typing import Any, Dict, List, Optional, TypedDict, Union +from typing_extensions import NotRequired + + +class EvalResultsFetch(TypedDict): + experiment_run_id: str + project_name: str + + +class DatasetFetch(TypedDict): + dataset_alias: str + project_name: str + + +class DatasetDelete(TypedDict): + dataset_alias: str + project_name: str + + +class DatasetFetchStatsByProject(TypedDict): + project_name: str + + +class TraceSave(TypedDict): + project_name: str + trace_id: str + name: str + created_at: str + duration: float + offline_mode: NotRequired[bool] + has_notification: NotRequired[bool] + customer_id: NotRequired[Optional[str]] + tags: NotRequired[List[str]] + metadata: NotRequired[Dict[str, Any]] + update_id: NotRequired[int] + + +class TraceFetch(TypedDict): + trace_id: str + + +class TraceAddToDataset(TypedDict): + trace_id: str + trace_span_id: str + dataset_alias: str + project_name: str + + +class EvaluationRunsBatchRequest(TypedDict): + organization_id: str + evaluation_entries: List[Dict[str, Any]] + + +class ProjectAdd(TypedDict): + project_name: str + + +class ProjectAddResponse(TypedDict): + project_id: str + + +class ProjectDelete(TypedDict): + project_name: str + + +class ProjectDeleteResponse(TypedDict): + project_id: str + + +class ScorerExistsRequest(TypedDict): + name: str + + +class ScorerExistsResponse(TypedDict): + exists: bool + + +class SavePromptScorerRequest(TypedDict): + name: str + prompt: str + threshold: float + options: NotRequired[Optional[Dict[str, float]]] + + +class SavePromptScorerResponse(TypedDict): + message: str + name: str + + +class FetchPromptScorerRequest(TypedDict): + name: str + + +class UploadCustomScorerRequest(TypedDict): + scorer_name: str + scorer_code: str + requirements_text: str + + +class UploadCustomScorerResponse(TypedDict): + status: str + + +class ResolveProjectNameRequest(TypedDict): + project_name: str + + +class ResolveProjectNameResponse(TypedDict): + project_id: str + + +class Example(TypedDict): + example_id: str + created_at: str + name: NotRequired[Optional[str]] + + +class BaseScorer(TypedDict): + score_type: str + threshold: NotRequired[float] + name: NotRequired[Optional[str]] + class_name: NotRequired[Optional[str]] + score: NotRequired[Optional[float]] + score_breakdown: NotRequired[Optional[Dict[str, Any]]] + reason: NotRequired[Optional[str]] + using_native_model: NotRequired[Optional[bool]] + success: NotRequired[Optional[bool]] + model: NotRequired[Optional[str]] + model_client: NotRequired[Any] + strict_mode: NotRequired[bool] + error: NotRequired[Optional[str]] + additional_metadata: NotRequired[Optional[Dict[str, Any]]] + user: NotRequired[Optional[str]] + server_hosted: NotRequired[bool] + + +class ScorerConfig(TypedDict): + score_type: str + name: NotRequired[Optional[str]] + threshold: NotRequired[float] + strict_mode: NotRequired[bool] + required_params: NotRequired[List[str]] + kwargs: NotRequired[Optional[Dict[str, Any]]] + + +class ValidationError(TypedDict): + loc: List[Union[str, int]] + msg: str + type: str + + +class SpanBatchItem(TypedDict): + span_id: str + trace_id: str + function: str + depth: int + created_at: NotRequired[Any] + parent_span_id: NotRequired[Optional[str]] + span_type: NotRequired[Optional[str]] + inputs: NotRequired[Optional[Dict[str, Any]]] + output: NotRequired[Any] + error: NotRequired[Optional[Dict[str, Any]]] + usage: NotRequired[Optional[Dict[str, Any]]] + duration: NotRequired[Optional[float]] + expected_tools: NotRequired[Optional[List[Dict[str, Any]]]] + additional_metadata: NotRequired[Optional[Dict[str, Any]]] + has_evaluation: NotRequired[Optional[bool]] + agent_name: NotRequired[Optional[str]] + class_name: NotRequired[Optional[str]] + state_before: NotRequired[Optional[Dict[str, Any]]] + state_after: NotRequired[Optional[Dict[str, Any]]] + span_state: str + update_id: NotRequired[int] + queued_at: float + + +class PromptScorer(TypedDict): + name: str + prompt: str + threshold: float + options: NotRequired[Optional[Dict[str, float]]] + created_at: NotRequired[Optional[str]] + updated_at: NotRequired[Optional[str]] + + +class ScorerData(TypedDict): + name: str + threshold: float + success: bool + score: NotRequired[Optional[float]] + reason: NotRequired[Optional[str]] + strict_mode: NotRequired[Optional[bool]] + evaluation_model: NotRequired[Union[List[str], str]] + error: NotRequired[Optional[str]] + additional_metadata: NotRequired[Optional[Dict[str, Any]]] + + +class TraceUsage(TypedDict): + prompt_tokens: NotRequired[Optional[int]] + completion_tokens: NotRequired[Optional[int]] + cache_creation_input_tokens: NotRequired[Optional[int]] + cache_read_input_tokens: NotRequired[Optional[int]] + total_tokens: NotRequired[Optional[int]] + prompt_tokens_cost_usd: NotRequired[Optional[float]] + completion_tokens_cost_usd: NotRequired[Optional[float]] + total_cost_usd: NotRequired[Optional[float]] + model_name: NotRequired[Optional[str]] + + +class Tool(TypedDict): + tool_name: str + parameters: NotRequired[Optional[Dict[str, Any]]] + agent_name: NotRequired[Optional[str]] + result_dependencies: NotRequired[Optional[List[Dict[str, Any]]]] + action_dependencies: NotRequired[Optional[List[Dict[str, Any]]]] + require_all: NotRequired[Optional[bool]] + + +class EvaluationRun(TypedDict): + id: NotRequired[Optional[str]] + project_name: NotRequired[Optional[str]] + eval_name: NotRequired[Optional[str]] + examples: List[Example] + custom_scorers: NotRequired[List[BaseScorer]] + judgment_scorers: NotRequired[List[ScorerConfig]] + model: str + trace_span_id: NotRequired[Optional[str]] + trace_id: NotRequired[Optional[str]] + created_at: NotRequired[Optional[str]] + + +class HTTPValidationError(TypedDict): + detail: NotRequired[List[ValidationError]] + + +class DatasetInsertExamples(TypedDict): + dataset_alias: str + examples: List[Example] + project_name: str + + +class SpansBatchRequest(TypedDict): + spans: List[SpanBatchItem] + organization_id: str + + +class FetchPromptScorerResponse(TypedDict): + scorer: PromptScorer + + +class TraceSpan(TypedDict): + span_id: str + trace_id: str + function: str + depth: int + created_at: NotRequired[Any] + parent_span_id: NotRequired[Optional[str]] + span_type: NotRequired[Optional[str]] + inputs: NotRequired[Optional[Dict[str, Any]]] + error: NotRequired[Optional[Dict[str, Any]]] + output: NotRequired[Any] + usage: NotRequired[Optional[TraceUsage]] + duration: NotRequired[Optional[float]] + expected_tools: NotRequired[Optional[List[Tool]]] + additional_metadata: NotRequired[Optional[Dict[str, Any]]] + has_evaluation: NotRequired[Optional[bool]] + agent_name: NotRequired[Optional[str]] + class_name: NotRequired[Optional[str]] + state_before: NotRequired[Optional[Dict[str, Any]]] + state_after: NotRequired[Optional[Dict[str, Any]]] + update_id: NotRequired[int] + + +class Trace(TypedDict): + trace_id: str + name: str + created_at: str + duration: float + trace_spans: List[TraceSpan] + offline_mode: NotRequired[bool] + rules: NotRequired[Dict[str, Any]] + has_notification: NotRequired[bool] + customer_id: NotRequired[Optional[str]] + tags: NotRequired[List[str]] + metadata: NotRequired[Dict[str, Any]] + update_id: NotRequired[int] + + +class ScoringResult(TypedDict): + success: bool + scorers_data: Optional[List[ScorerData]] + name: NotRequired[Optional[str]] + data_object: NotRequired[Optional[Union[TraceSpan, Example]]] + trace_id: NotRequired[Optional[str]] + run_duration: NotRequired[Optional[float]] + evaluation_cost: NotRequired[Optional[float]] + + +class TraceRun(TypedDict): + project_name: NotRequired[Optional[str]] + eval_name: NotRequired[Optional[str]] + traces: List[Trace] + scorers: List[ScorerConfig] + model: str + trace_span_id: NotRequired[Optional[str]] + tools: NotRequired[Optional[List[Dict[str, Any]]]] + + +class EvalResults(TypedDict): + results: List[ScoringResult] + run: Union[TraceRun, EvaluationRun] + + +class DatasetPush(TypedDict): + dataset_alias: str + comments: NotRequired[Optional[str]] + source_file: NotRequired[Optional[str]] + examples: NotRequired[Optional[List[Example]]] + traces: NotRequired[Optional[List[Trace]]] + is_trace: NotRequired[bool] + project_name: str + overwrite: NotRequired[Optional[bool]] diff --git a/src/judgeval/cli.py b/src/judgeval/cli.py index 801fb8e7..14ba7c14 100644 --- a/src/judgeval/cli.py +++ b/src/judgeval/cli.py @@ -3,8 +3,8 @@ import typer from pathlib import Path from dotenv import load_dotenv -from judgeval.common.logger import judgeval_logger -from judgeval.judgment_client import JudgmentClient +from judgeval.logger import judgeval_logger +from judgeval import JudgmentClient load_dotenv() @@ -61,5 +61,3 @@ def version(): if __name__ == "__main__": app() - -# judgeval upload_scorer /Users/alanzhang/repo/JudgmentLabs/judgeval/src/demo/profile_match_scorer.py /Users/alanzhang/repo/JudgmentLabs/judgeval/src/demo/requirements.txt diff --git a/src/judgeval/clients.py b/src/judgeval/clients.py deleted file mode 100644 index b8d22133..00000000 --- a/src/judgeval/clients.py +++ /dev/null @@ -1,35 +0,0 @@ -import os -from dotenv import load_dotenv -from openai import OpenAI -from typing import Optional - -PATH_TO_DOTENV = os.path.join(os.path.dirname(__file__), ".env") -load_dotenv(dotenv_path=PATH_TO_DOTENV) - - -# Initialize optional OpenAI client -client: Optional["OpenAI"] = None -if os.getenv("OPENAI_API_KEY"): - try: - from openai import OpenAI - - client = OpenAI() - except ImportError: - # openai package not installed - pass - -# Initialize optional Together clients -together_client: Optional["Together"] = None -async_together_client: Optional["AsyncTogether"] = None - -# Only initialize Together clients if API key is available - -together_api_key = os.getenv("TOGETHERAI_API_KEY") or os.getenv("TOGETHER_API_KEY") -if together_api_key: - try: - from together import Together, AsyncTogether - - together_client = Together(api_key=together_api_key) - async_together_client = AsyncTogether(api_key=together_api_key) - except Exception: - pass diff --git a/src/judgeval/common/__init__.py b/src/judgeval/common/__init__.py deleted file mode 100644 index 4316540b..00000000 --- a/src/judgeval/common/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -from judgeval.common.utils import ( - get_chat_completion, - aget_chat_completion, - get_completion_multiple_models, - aget_completion_multiple_models, -) - -__all__ = [ - "get_chat_completion", - "aget_chat_completion", - "get_completion_multiple_models", - "aget_completion_multiple_models", -] diff --git a/src/judgeval/common/api/__init__.py b/src/judgeval/common/api/__init__.py deleted file mode 100644 index cb6b0efc..00000000 --- a/src/judgeval/common/api/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .api import JudgmentApiClient, JudgmentAPIException - -__all__ = ["JudgmentApiClient", "JudgmentAPIException"] diff --git a/src/judgeval/common/api/api.py b/src/judgeval/common/api/api.py deleted file mode 100644 index 2cd5123d..00000000 --- a/src/judgeval/common/api/api.py +++ /dev/null @@ -1,375 +0,0 @@ -from typing import Literal, List, Dict, Any, Union, Optional -from requests import exceptions -from judgeval.common.api.constants import ( - JUDGMENT_TRACES_FETCH_API_URL, - JUDGMENT_TRACES_UPSERT_API_URL, - JUDGMENT_TRACES_DELETE_API_URL, - JUDGMENT_TRACES_SPANS_BATCH_API_URL, - JUDGMENT_TRACES_EVALUATION_RUNS_BATCH_API_URL, - JUDGMENT_DATASETS_PUSH_API_URL, - JUDGMENT_DATASETS_APPEND_EXAMPLES_API_URL, - JUDGMENT_DATASETS_PULL_API_URL, - JUDGMENT_DATASETS_DELETE_API_URL, - JUDGMENT_DATASETS_PROJECT_STATS_API_URL, - JUDGMENT_PROJECT_DELETE_API_URL, - JUDGMENT_PROJECT_CREATE_API_URL, - JUDGMENT_EVAL_API_URL, - JUDGMENT_TRACE_EVAL_API_URL, - JUDGMENT_EVAL_LOG_API_URL, - JUDGMENT_EVAL_FETCH_API_URL, - JUDGMENT_EVAL_DELETE_API_URL, - JUDGMENT_ADD_TO_RUN_EVAL_QUEUE_API_URL, - JUDGMENT_GET_EVAL_STATUS_API_URL, - JUDGMENT_SCORER_SAVE_API_URL, - JUDGMENT_SCORER_FETCH_API_URL, - JUDGMENT_SCORER_EXISTS_API_URL, - JUDGMENT_CUSTOM_SCORER_UPLOAD_API_URL, - JUDGMENT_DATASETS_APPEND_TRACES_API_URL, -) -from judgeval.common.api.constants import ( - TraceFetchPayload, - TraceDeletePayload, - SpansBatchPayload, - EvaluationEntryResponse, - EvaluationRunsBatchPayload, - DatasetPushPayload, - DatasetAppendPayload, - DatasetPullPayload, - DatasetDeletePayload, - DatasetStatsPayload, - ProjectCreatePayload, - ProjectDeletePayload, - EvalRunRequestBody, - DeleteEvalRunRequestBody, - EvalLogPayload, - EvalStatusPayload, - ScorerSavePayload, - ScorerFetchPayload, - ScorerExistsPayload, - CustomScorerUploadPayload, - CustomScorerTemplateResponse, -) -from judgeval.utils.requests import requests -from judgeval.common.api.json_encoder import json_encoder - - -class JudgmentAPIException(exceptions.HTTPError): - """ - Exception raised when an error occurs while executing a Judgment API request. - Extends requests.exceptions.HTTPError to provide access to the response object. - """ - - def __init__(self, message: str, response=None, request=None): - super().__init__(message, response=response, request=request) - self.message = message - self.response = response - self.request = request - - @property - def status_code(self) -> Union[int, None]: - """Get the HTTP status code from the response.""" - return self.response.status_code if self.response else None - - @property - def response_json(self) -> Dict[str, Any]: - """Get the JSON response body.""" - try: - return self.response.json() if self.response else {} - except (ValueError, AttributeError): - return {} - - @property - def error_detail(self) -> str: - """Get the error detail from the response JSON.""" - return self.response_json.get("detail", "An unknown error occurred.") - - -class JudgmentApiClient: - def __init__(self, api_key: str, organization_id: str): - self.api_key = api_key - self.organization_id = organization_id - - def _do_request( - self, - method: Literal["POST", "PATCH", "GET", "DELETE"], - url: str, - payload: Any, - timeout: Optional[Union[float, tuple]] = None, - ) -> Any: - # Prepare request kwargs with optional timeout - request_kwargs = self._request_kwargs() - if timeout is not None: - request_kwargs["timeout"] = timeout - - if method == "GET": - r = requests.request( - method, - url, - params=payload, - headers=self._headers(), - **request_kwargs, - ) - else: - r = requests.request( - method, - url, - json=json_encoder(payload), - headers=self._headers(), - **request_kwargs, - ) - - try: - r.raise_for_status() - except exceptions.HTTPError as e: - try: - detail = r.json().get("detail", "") - except Exception: - detail = r.text - - raise JudgmentAPIException( - f"HTTP {r.status_code}: {r.reason}, {detail}", - response=r, - request=e.request, - ) - - return r.json() - - def send_spans_batch(self, spans: List[Dict[str, Any]]): - payload: SpansBatchPayload = { - "spans": spans, - "organization_id": self.organization_id, - } - - return self._do_request("POST", JUDGMENT_TRACES_SPANS_BATCH_API_URL, payload) - - def send_evaluation_runs_batch( - self, evaluation_entries: List[EvaluationEntryResponse] - ): - payload: EvaluationRunsBatchPayload = { - "organization_id": self.organization_id, - "evaluation_entries": evaluation_entries, - } - - return self._do_request( - "POST", JUDGMENT_TRACES_EVALUATION_RUNS_BATCH_API_URL, payload - ) - - def fetch_trace(self, trace_id: str): - payload: TraceFetchPayload = {"trace_id": trace_id} - return self._do_request("POST", JUDGMENT_TRACES_FETCH_API_URL, payload) - - def upsert_trace(self, trace_data: Dict[str, Any]): - return self._do_request("POST", JUDGMENT_TRACES_UPSERT_API_URL, trace_data) - - def delete_trace(self, trace_id: str): - payload: TraceDeletePayload = {"trace_ids": [trace_id]} - return self._do_request("DELETE", JUDGMENT_TRACES_DELETE_API_URL, payload) - - def delete_traces(self, trace_ids: List[str]): - payload: TraceDeletePayload = {"trace_ids": trace_ids} - return self._do_request("DELETE", JUDGMENT_TRACES_DELETE_API_URL, payload) - - def delete_project(self, project_name: str): - payload: ProjectDeletePayload = {"project_name": project_name} - return self._do_request("DELETE", JUDGMENT_PROJECT_DELETE_API_URL, payload) - - def create_project(self, project_name: str): - payload: ProjectCreatePayload = {"project_name": project_name} - return self._do_request("POST", JUDGMENT_PROJECT_CREATE_API_URL, payload) - - def run_evaluation(self, evaluation_run: Dict[str, Any]): - return self._do_request("POST", JUDGMENT_EVAL_API_URL, evaluation_run) - - def run_trace_evaluation(self, trace_run: Dict[str, Any]): - return self._do_request("POST", JUDGMENT_TRACE_EVAL_API_URL, trace_run) - - def log_evaluation_results( - self, results: List[Dict[str, Any]], run: Dict[str, Any] - ): - payload: EvalLogPayload = {"results": results, "run": run} - return self._do_request("POST", JUDGMENT_EVAL_LOG_API_URL, payload) - - def fetch_evaluation_results(self, experiment_run_id: str, project_name: str): - payload: EvalRunRequestBody = { - "project_name": project_name, - "experiment_run_id": experiment_run_id, - } - return self._do_request("POST", JUDGMENT_EVAL_FETCH_API_URL, payload) - - def delete_evaluation_results(self, project_name: str, eval_names: List[str]): - payload: DeleteEvalRunRequestBody = { - "project_name": project_name, - "eval_names": eval_names, - "judgment_api_key": self.api_key, - } - return self._do_request("POST", JUDGMENT_EVAL_DELETE_API_URL, payload) - - def add_to_evaluation_queue(self, payload: Dict[str, Any]): - return self._do_request("POST", JUDGMENT_ADD_TO_RUN_EVAL_QUEUE_API_URL, payload) - - def get_evaluation_status(self, experiment_run_id: str, project_name: str): - payload: EvalStatusPayload = { - "experiment_run_id": experiment_run_id, - "project_name": project_name, - "judgment_api_key": self.api_key, - } - return self._do_request("GET", JUDGMENT_GET_EVAL_STATUS_API_URL, payload) - - def save_scorer( - self, name: str, prompt: str, threshold: float, options: Optional[dict] = None - ): - payload: ScorerSavePayload = { - "name": name, - "prompt": prompt, - "threshold": threshold, - "options": options, - } - try: - return self._do_request("POST", JUDGMENT_SCORER_SAVE_API_URL, payload) - except JudgmentAPIException as e: - if e.status_code == 500: - raise JudgmentAPIException( - f"The server is temporarily unavailable. Please try your request again in a few moments. Error details: {e.error_detail}", - response=e.response, - request=e.request, - ) - raise JudgmentAPIException( - f"Failed to save classifier scorer: {e.error_detail}", - response=e.response, - request=e.request, - ) - - def fetch_scorer(self, name: str): - payload: ScorerFetchPayload = {"name": name} - try: - return self._do_request("POST", JUDGMENT_SCORER_FETCH_API_URL, payload) - except JudgmentAPIException as e: - if e.status_code == 500: - raise JudgmentAPIException( - f"The server is temporarily unavailable. Please try your request again in a few moments. Error details: {e.error_detail}", - response=e.response, - request=e.request, - ) - raise JudgmentAPIException( - f"Failed to fetch classifier scorer '{name}': {e.error_detail}", - response=e.response, - request=e.request, - ) - - def scorer_exists(self, name: str): - payload: ScorerExistsPayload = {"name": name} - try: - return self._do_request("POST", JUDGMENT_SCORER_EXISTS_API_URL, payload) - except JudgmentAPIException as e: - if e.status_code == 500: - raise JudgmentAPIException( - f"The server is temporarily unavailable. Please try your request again in a few moments. Error details: {e.error_detail}", - response=e.response, - request=e.request, - ) - raise JudgmentAPIException( - f"Failed to check if scorer exists: {e.error_detail}", - response=e.response, - request=e.request, - ) - - def upload_custom_scorer( - self, - scorer_name: str, - scorer_code: str, - requirements_text: str, - ) -> CustomScorerTemplateResponse: - """Upload custom scorer to backend""" - payload: CustomScorerUploadPayload = { - "scorer_name": scorer_name, - "scorer_code": scorer_code, - "requirements_text": requirements_text, - } - - try: - # Use longer timeout for custom scorer upload (5 minutes) - response = self._do_request( - "POST", - JUDGMENT_CUSTOM_SCORER_UPLOAD_API_URL, - payload, - timeout=(10, 300), - ) - return response - except JudgmentAPIException as e: - raise e - - def push_dataset( - self, - dataset_alias: str, - project_name: str, - examples: List[Dict[str, Any]], - traces: List[Dict[str, Any]], - overwrite: bool = False, - ): - payload: DatasetPushPayload = { - "dataset_alias": dataset_alias, - "project_name": project_name, - "examples": examples, - "traces": traces, - "overwrite": overwrite, - } - return self._do_request("POST", JUDGMENT_DATASETS_PUSH_API_URL, payload) - - def append_examples( - self, dataset_alias: str, project_name: str, examples: List[Dict[str, Any]] - ): - payload: DatasetAppendPayload = { - "dataset_alias": dataset_alias, - "project_name": project_name, - "examples": examples, - } - return self._do_request( - "POST", JUDGMENT_DATASETS_APPEND_EXAMPLES_API_URL, payload - ) - - def append_traces( - self, dataset_alias: str, project_name: str, traces: List[Dict[str, Any]] - ): - payload: DatasetAppendPayload = { - "dataset_alias": dataset_alias, - "project_name": project_name, - "traces": traces, - } - return self._do_request( - "POST", JUDGMENT_DATASETS_APPEND_TRACES_API_URL, payload - ) - - def pull_dataset(self, dataset_alias: str, project_name: str): - payload: DatasetPullPayload = { - "dataset_alias": dataset_alias, - "project_name": project_name, - } - return self._do_request("POST", JUDGMENT_DATASETS_PULL_API_URL, payload) - - def delete_dataset(self, dataset_alias: str, project_name: str): - payload: DatasetDeletePayload = { - "dataset_alias": dataset_alias, - "project_name": project_name, - } - return self._do_request("POST", JUDGMENT_DATASETS_DELETE_API_URL, payload) - - def get_project_dataset_stats(self, project_name: str): - payload: DatasetStatsPayload = {"project_name": project_name} - return self._do_request( - "POST", JUDGMENT_DATASETS_PROJECT_STATS_API_URL, payload - ) - - def _headers(self) -> Dict[str, str]: - return { - "Content-Type": "application/json", - "Authorization": f"Bearer {self.api_key}", - "X-Organization-Id": self.organization_id, - } - - def _request_kwargs(self): - # NOTE: We may want to configure custom kwargs that different requests may need. - # For this purpose we can store that as a property of self, and return the appropriate kwargs from this method. - return { - "verify": True, - "timeout": 30, - } diff --git a/src/judgeval/common/api/constants.py b/src/judgeval/common/api/constants.py deleted file mode 100644 index 550f57ad..00000000 --- a/src/judgeval/common/api/constants.py +++ /dev/null @@ -1,186 +0,0 @@ -import os -from typing import Optional, TypedDict, List, Dict, Any - -ROOT_API = os.getenv("JUDGMENT_API_URL", "https://api.judgmentlabs.ai") - -# Traces API -JUDGMENT_TRACES_FETCH_API_URL = f"{ROOT_API}/traces/fetch/" -JUDGMENT_TRACES_SAVE_API_URL = f"{ROOT_API}/traces/save/" -JUDGMENT_TRACES_UPSERT_API_URL = f"{ROOT_API}/traces/upsert/" -JUDGMENT_TRACES_DELETE_API_URL = f"{ROOT_API}/traces/delete/" -JUDGMENT_TRACES_SPANS_BATCH_API_URL = f"{ROOT_API}/traces/spans/batch/" -JUDGMENT_TRACES_EVALUATION_RUNS_BATCH_API_URL = ( - f"{ROOT_API}/traces/evaluation_runs/batch/" -) - - -class TraceFetchPayload(TypedDict): - trace_id: str - - -class TraceDeletePayload(TypedDict): - trace_ids: List[str] - - -class SpansBatchPayload(TypedDict): - spans: List[Dict[str, Any]] - organization_id: str - - -class EvaluationEntryResponse(TypedDict): - evaluation_run: Dict[str, Any] - associated_span: Dict[str, Any] - queued_at: Optional[float] - - -class EvaluationRunsBatchPayload(TypedDict): - organization_id: str - evaluation_entries: List[EvaluationEntryResponse] - - -# Evaluation API -JUDGMENT_EVAL_API_URL = f"{ROOT_API}/evaluate/" -JUDGMENT_TRACE_EVAL_API_URL = f"{ROOT_API}/evaluate_trace/" -JUDGMENT_EVAL_LOG_API_URL = f"{ROOT_API}/log_eval_results/" -JUDGMENT_EVAL_FETCH_API_URL = f"{ROOT_API}/fetch_experiment_run/" -JUDGMENT_EVAL_DELETE_API_URL = ( - f"{ROOT_API}/delete_eval_results_by_project_and_run_names/" -) -JUDGMENT_EVAL_DELETE_PROJECT_API_URL = f"{ROOT_API}/delete_eval_results_by_project/" -JUDGMENT_ADD_TO_RUN_EVAL_QUEUE_API_URL = f"{ROOT_API}/add_to_run_eval_queue/" -JUDGMENT_GET_EVAL_STATUS_API_URL = f"{ROOT_API}/get_evaluation_status/" - -# Custom Scorers API -JUDGMENT_CUSTOM_SCORER_UPLOAD_API_URL = f"{ROOT_API}/upload_scorer/" - - -# Evaluation API Payloads -class EvalRunRequestBody(TypedDict): - eval_name: str - project_name: str - judgment_api_key: str - - -class DeleteEvalRunRequestBody(TypedDict): - eval_names: List[str] - project_name: str - judgment_api_key: str - - -class EvalLogPayload(TypedDict): - results: List[Dict[str, Any]] - run: Dict[str, Any] - - -class EvalStatusPayload(TypedDict): - experiment_run_id: str - judgment_api_key: str - project_name: str - - -class CheckExperimentTypePayload(TypedDict): - eval_name: str - project_name: str - judgment_api_key: str - is_trace: bool - - -class EvalRunNameExistsPayload(TypedDict): - eval_name: str - project_name: str - judgment_api_key: str - - -class CheckExampleKeysPayload(TypedDict): - keys: List[str] - eval_name: str - project_name: str - - -# Datasets API -JUDGMENT_DATASETS_PUSH_API_URL = f"{ROOT_API}/datasets/push/" -JUDGMENT_DATASETS_APPEND_EXAMPLES_API_URL = f"{ROOT_API}/datasets/insert_examples/" -JUDGMENT_DATASETS_APPEND_TRACES_API_URL = f"{ROOT_API}/traces/add_to_dataset/" -JUDGMENT_DATASETS_PULL_API_URL = f"{ROOT_API}/datasets/pull_for_judgeval/" -JUDGMENT_DATASETS_DELETE_API_URL = f"{ROOT_API}/datasets/delete/" -JUDGMENT_DATASETS_EXPORT_JSONL_API_URL = f"{ROOT_API}/datasets/export_jsonl/" -JUDGMENT_DATASETS_PROJECT_STATS_API_URL = f"{ROOT_API}/datasets/fetch_stats_by_project/" -JUDGMENT_DATASETS_INSERT_API_URL = f"{ROOT_API}/datasets/insert_examples/" - - -class DatasetPushPayload(TypedDict): - dataset_alias: str - project_name: str - examples: List[Dict[str, Any]] - traces: List[Dict[str, Any]] - overwrite: bool - - -class DatasetAppendPayload(TypedDict): - dataset_alias: str - project_name: str - examples: List[Dict[str, Any]] - - -class DatasetPullPayload(TypedDict): - dataset_alias: str - project_name: str - - -class DatasetDeletePayload(TypedDict): - dataset_alias: str - project_name: str - - -class DatasetExportPayload(TypedDict): - dataset_alias: str - project_name: str - - -class DatasetStatsPayload(TypedDict): - project_name: str - - -# Projects API -JUDGMENT_PROJECT_DELETE_API_URL = f"{ROOT_API}/projects/delete_from_judgeval/" -JUDGMENT_PROJECT_CREATE_API_URL = f"{ROOT_API}/projects/add/" - - -class ProjectDeletePayload(TypedDict): - project_list: List[str] - - -class ProjectCreatePayload(TypedDict): - project_name: str - - -JUDGMENT_SCORER_SAVE_API_URL = f"{ROOT_API}/save_scorer/" -JUDGMENT_SCORER_FETCH_API_URL = f"{ROOT_API}/fetch_scorer/" -JUDGMENT_SCORER_EXISTS_API_URL = f"{ROOT_API}/scorer_exists/" - - -class ScorerSavePayload(TypedDict): - name: str - prompt: str - threshold: float - options: Optional[dict] - - -class ScorerFetchPayload(TypedDict): - name: str - - -class ScorerExistsPayload(TypedDict): - name: str - - -class CustomScorerUploadPayload(TypedDict): - scorer_name: str - scorer_code: str - requirements_text: str - - -class CustomScorerTemplateResponse(TypedDict): - scorer_name: str - status: str - message: str diff --git a/src/judgeval/common/exceptions.py b/src/judgeval/common/exceptions.py deleted file mode 100644 index 3533b522..00000000 --- a/src/judgeval/common/exceptions.py +++ /dev/null @@ -1,27 +0,0 @@ -""" -Common Exceptions in Judgeval -""" - - -class MissingTestCaseParamsError(Exception): - pass - - -class JudgmentAPIError(Exception): - """ - Exception raised when an error occurs while executing a Judgment API request - """ - - def __init__(self, message: str): - super().__init__(message) - self.message = message - - -class InvalidJudgeModelError(Exception): - """ - Exception raised when an invalid judge model is provided - """ - - def __init__(self, message: str): - super().__init__(message) - self.message = message diff --git a/src/judgeval/common/storage/__init__.py b/src/judgeval/common/storage/__init__.py deleted file mode 100644 index 25c0aa42..00000000 --- a/src/judgeval/common/storage/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from judgeval.common.storage.s3_storage import S3Storage - - -__all__ = [ - "S3Storage", -] diff --git a/src/judgeval/common/storage/s3_storage.py b/src/judgeval/common/storage/s3_storage.py deleted file mode 100644 index f1b04886..00000000 --- a/src/judgeval/common/storage/s3_storage.py +++ /dev/null @@ -1,97 +0,0 @@ -import os -import boto3 -import orjson -from typing import Optional -from datetime import datetime, UTC -from botocore.exceptions import ClientError -from judgeval.common.logger import judgeval_logger - - -class S3Storage: - """Utility class for storing and retrieving trace data from S3.""" - - def __init__( - self, - bucket_name: str, - aws_access_key_id: Optional[str] = None, - aws_secret_access_key: Optional[str] = None, - region_name: Optional[str] = None, - ): - """Initialize S3 storage with credentials and bucket name. - - Args: - bucket_name: Name of the S3 bucket to store traces in - aws_access_key_id: AWS access key ID (optional, will use environment variables if not provided) - aws_secret_access_key: AWS secret access key (optional, will use environment variables if not provided) - region_name: AWS region name (optional, will use environment variables if not provided) - """ - self.bucket_name = bucket_name - self.s3_client = boto3.client( - "s3", - aws_access_key_id=aws_access_key_id or os.getenv("AWS_ACCESS_KEY_ID"), - aws_secret_access_key=aws_secret_access_key - or os.getenv("AWS_SECRET_ACCESS_KEY"), - region_name=region_name or os.getenv("AWS_REGION", "us-west-1"), - ) - - def _ensure_bucket_exists(self): - """Ensure the S3 bucket exists, creating it if necessary.""" - try: - self.s3_client.head_bucket(Bucket=self.bucket_name) - except ClientError as e: - error_code = e.response["Error"]["Code"] - if error_code == "404": - # Bucket doesn't exist, create it - try: - self.s3_client.create_bucket( - Bucket=self.bucket_name, - CreateBucketConfiguration={ - "LocationConstraint": self.s3_client.meta.region_name - }, - ) if self.s3_client.meta.region_name != "us-east-1" else self.s3_client.create_bucket( - Bucket=self.bucket_name - ) - except ClientError as create_error: - if ( - create_error.response["Error"]["Code"] - == "BucketAlreadyOwnedByYou" - ): - # Bucket was just created by another process - judgeval_logger.warning( - f"Bucket {self.bucket_name} was just created by another process" - ) - pass - else: - raise create_error - else: - # Some other error occurred - raise e - - def save_trace(self, trace_data: dict, trace_id: str, project_name: str) -> str: - """Save trace data to S3. - - Args: - trace_data: The trace data to save - trace_id: Unique identifier for the trace - project_name: Name of the project the trace belongs to - - Returns: - str: S3 key where the trace was saved - """ - # Ensure bucket exists before saving - self._ensure_bucket_exists() - - # Create a timestamped key for the trace - timestamp = datetime.now(UTC).strftime("%Y%m%d_%H%M%S") - s3_key = f"traces/{project_name}/{trace_id}_{timestamp}.json" - - trace_json = orjson.dumps(trace_data).decode("utf-8") - - self.s3_client.put_object( - Bucket=self.bucket_name, - Key=s3_key, - Body=trace_json, - ContentType="application/json", - ) - - return s3_key diff --git a/src/judgeval/common/tracer/__init__.py b/src/judgeval/common/tracer/__init__.py deleted file mode 100644 index e6f637f1..00000000 --- a/src/judgeval/common/tracer/__init__.py +++ /dev/null @@ -1,31 +0,0 @@ -from judgeval.common.tracer.core import ( - TraceClient, - _DeepTracer, - Tracer, - wrap, - current_span_var, - current_trace_var, - SpanType, - cost_per_token, -) -from judgeval.common.tracer.otel_exporter import JudgmentAPISpanExporter -from judgeval.common.tracer.otel_span_processor import JudgmentSpanProcessor -from judgeval.common.tracer.span_processor import SpanProcessorBase -from judgeval.common.tracer.trace_manager import TraceManagerClient -from judgeval.data import TraceSpan - -__all__ = [ - "_DeepTracer", - "TraceClient", - "Tracer", - "wrap", - "current_span_var", - "current_trace_var", - "TraceManagerClient", - "JudgmentAPISpanExporter", - "JudgmentSpanProcessor", - "SpanProcessorBase", - "SpanType", - "cost_per_token", - "TraceSpan", -] diff --git a/src/judgeval/common/tracer/constants.py b/src/judgeval/common/tracer/constants.py deleted file mode 100644 index 85ffae9c..00000000 --- a/src/judgeval/common/tracer/constants.py +++ /dev/null @@ -1,22 +0,0 @@ -import os -import site -import sysconfig - - -# NOTE: This builds once, can be tweaked if we are missing / capturing other unncessary modules -# @link https://docs.python.org/3.13/library/sysconfig.html -_TRACE_FILEPATH_BLOCKLIST = tuple( - os.path.realpath(p) + os.sep - for p in { - sysconfig.get_paths()["stdlib"], - sysconfig.get_paths().get("platstdlib", ""), - *site.getsitepackages(), - site.getusersitepackages(), - *( - [os.path.join(os.path.dirname(__file__), "../../judgeval/")] - if os.environ.get("JUDGMENT_DEV") - else [] - ), - } - if p -) diff --git a/src/judgeval/common/tracer/core.py b/src/judgeval/common/tracer/core.py deleted file mode 100644 index bd8a6876..00000000 --- a/src/judgeval/common/tracer/core.py +++ /dev/null @@ -1,2427 +0,0 @@ -""" -Tracing system for judgeval that allows for function tracing using decorators. -""" - -from __future__ import annotations - -import asyncio -import atexit -import functools -import inspect -import os -import threading -import time -import traceback -import uuid -import contextvars -import sys -from contextlib import ( - contextmanager, -) -from datetime import datetime, timezone -from typing import ( - Any, - Callable, - Dict, - Generator, - List, - Optional, - ParamSpec, - Tuple, - TypeVar, - Union, - TypeAlias, - overload, -) -import types -import random - - -from judgeval.common.tracer.constants import _TRACE_FILEPATH_BLOCKLIST - -from judgeval.common.tracer.otel_span_processor import JudgmentSpanProcessor -from judgeval.common.tracer.span_processor import SpanProcessorBase -from judgeval.common.tracer.trace_manager import TraceManagerClient - -from judgeval.data import Example, Trace, TraceSpan, TraceUsage -from judgeval.scorers import APIScorerConfig, BaseScorer -from judgeval.data.evaluation_run import EvaluationRun -from judgeval.local_eval_queue import LocalEvaluationQueue -from judgeval.common.api import JudgmentApiClient -from judgeval.common.utils import OptExcInfo, validate_api_key -from judgeval.common.logger import judgeval_logger - -from litellm import cost_per_token as _original_cost_per_token # type: ignore -from judgeval.common.tracer.providers import ( - HAS_OPENAI, - HAS_TOGETHER, - HAS_ANTHROPIC, - HAS_GOOGLE_GENAI, - HAS_GROQ, - ApiClient, -) -from judgeval.constants import DEFAULT_GPT_MODEL - - -current_trace_var = contextvars.ContextVar[Optional["TraceClient"]]( - "current_trace", default=None -) -current_span_var = contextvars.ContextVar[Optional[str]]("current_span", default=None) - - -SpanType: TypeAlias = str - - -class TraceClient: - """Client for managing a single trace context""" - - def __init__( - self, - tracer: Tracer, - trace_id: Optional[str] = None, - name: str = "default", - project_name: Union[str, None] = None, - enable_monitoring: bool = True, - enable_evaluations: bool = True, - parent_trace_id: Optional[str] = None, - parent_name: Optional[str] = None, - ): - self.name = name - self.trace_id = trace_id or str(uuid.uuid4()) - self.project_name = project_name or "default_project" - self.tracer = tracer - self.enable_monitoring = enable_monitoring - self.enable_evaluations = enable_evaluations - self.parent_trace_id = parent_trace_id - self.parent_name = parent_name - self.customer_id: Optional[str] = None - self.tags: List[Union[str, set, tuple]] = [] - self.metadata: Dict[str, Any] = {} - self.has_notification: Optional[bool] = False - self.update_id: int = 1 - self.trace_spans: List[TraceSpan] = [] - self.span_id_to_span: Dict[str, TraceSpan] = {} - self.evaluation_runs: List[EvaluationRun] = [] - self.start_time: Optional[float] = None - self.trace_manager_client = TraceManagerClient( - tracer.api_key, tracer.organization_id, tracer - ) - self._span_depths: Dict[str, int] = {} - - self.otel_span_processor = tracer.otel_span_processor - - def get_current_span(self): - """Get the current span from the context var""" - return self.tracer.get_current_span() - - def set_current_span(self, span: Any): - """Set the current span from the context var""" - return self.tracer.set_current_span(span) - - def reset_current_span(self, token: Any): - """Reset the current span from the context var""" - self.tracer.reset_current_span(token) - - @contextmanager - def span(self, name: str, span_type: SpanType = "span"): - """Context manager for creating a trace span, managing the current span via contextvars""" - is_first_span = len(self.trace_spans) == 0 - if is_first_span: - try: - self.save(final_save=False) - except Exception as e: - judgeval_logger.warning( - f"Failed to save initial trace for live tracking: {e}" - ) - start_time = time.time() - - span_id = str(uuid.uuid4()) - - parent_span_id = self.get_current_span() - token = self.set_current_span(span_id) - - current_depth = 0 - if parent_span_id and parent_span_id in self._span_depths: - current_depth = self._span_depths[parent_span_id] + 1 - - self._span_depths[span_id] = current_depth - - span = TraceSpan( - span_id=span_id, - trace_id=self.trace_id, - depth=current_depth, - message=name, - created_at=start_time, - span_type=span_type, - parent_span_id=parent_span_id, - function=name, - ) - self.add_span(span) - - self.otel_span_processor.queue_span_update(span, span_state="input") - - try: - yield self - finally: - duration = time.time() - start_time - span.duration = duration - - self.otel_span_processor.queue_span_update(span, span_state="completed") - - if span_id in self._span_depths: - del self._span_depths[span_id] - self.reset_current_span(token) - - def async_evaluate( - self, - scorer: Union[APIScorerConfig, BaseScorer], - example: Example, - model: str = DEFAULT_GPT_MODEL, - ): - start_time = time.time() - span_id = self.get_current_span() - eval_run_name = ( - f"{self.name.capitalize()}-{span_id}-{scorer.score_type.capitalize()}" - ) - hosted_scoring = isinstance(scorer, APIScorerConfig) or ( - isinstance(scorer, BaseScorer) and scorer.server_hosted - ) - if hosted_scoring: - eval_run = EvaluationRun( - organization_id=self.tracer.organization_id, - project_name=self.project_name, - eval_name=eval_run_name, - examples=[example], - scorers=[scorer], - model=model, - trace_span_id=span_id, - ) - - self.add_eval_run(eval_run, start_time) - - if span_id: - current_span = self.span_id_to_span.get(span_id) - if current_span: - self.otel_span_processor.queue_evaluation_run( - eval_run, span_id=span_id, span_data=current_span - ) - else: - # Handle custom scorers using local evaluation queue - eval_run = EvaluationRun( - organization_id=self.tracer.organization_id, - project_name=self.project_name, - eval_name=eval_run_name, - examples=[example], - scorers=[scorer], - model=model, - trace_span_id=span_id, - ) - - self.add_eval_run(eval_run, start_time) - - # Enqueue the evaluation run to the local evaluation queue - self.tracer.local_eval_queue.enqueue(eval_run) - - def add_eval_run(self, eval_run: EvaluationRun, start_time: float): - current_span_id = eval_run.trace_span_id - - if current_span_id: - span = self.span_id_to_span[current_span_id] - span.has_evaluation = True - self.evaluation_runs.append(eval_run) - - def record_input(self, inputs: dict): - current_span_id = self.get_current_span() - if current_span_id: - span = self.span_id_to_span[current_span_id] - if "self" in inputs: - del inputs["self"] - span.inputs = inputs - - try: - self.otel_span_processor.queue_span_update(span, span_state="input") - except Exception as e: - judgeval_logger.warning(f"Failed to queue span with input data: {e}") - - def record_agent_name(self, agent_name: str): - current_span_id = self.get_current_span() - if current_span_id: - span = self.span_id_to_span[current_span_id] - span.agent_name = agent_name - - self.otel_span_processor.queue_span_update(span, span_state="agent_name") - - def record_class_name(self, class_name: str): - current_span_id = self.get_current_span() - if current_span_id: - span = self.span_id_to_span[current_span_id] - span.class_name = class_name - - self.otel_span_processor.queue_span_update(span, span_state="class_name") - - def record_state_before(self, state: dict): - """Records the agent's state before a tool execution on the current span. - - Args: - state: A dictionary representing the agent's state. - """ - current_span_id = self.get_current_span() - if current_span_id: - span = self.span_id_to_span[current_span_id] - span.state_before = state - - self.otel_span_processor.queue_span_update(span, span_state="state_before") - - def record_state_after(self, state: dict): - """Records the agent's state after a tool execution on the current span. - - Args: - state: A dictionary representing the agent's state. - """ - current_span_id = self.get_current_span() - if current_span_id: - span = self.span_id_to_span[current_span_id] - span.state_after = state - - self.otel_span_processor.queue_span_update(span, span_state="state_after") - - def record_output(self, output: Any): - current_span_id = self.get_current_span() - if current_span_id: - span = self.span_id_to_span[current_span_id] - span.output = output - - self.otel_span_processor.queue_span_update(span, span_state="output") - - return span - return None - - def record_usage(self, usage: TraceUsage): - current_span_id = self.get_current_span() - if current_span_id: - span = self.span_id_to_span[current_span_id] - span.usage = usage - - self.otel_span_processor.queue_span_update(span, span_state="usage") - - return span - return None - - def record_error(self, error: Dict[str, Any]): - current_span_id = self.get_current_span() - if current_span_id: - span = self.span_id_to_span[current_span_id] - span.error = error - - self.otel_span_processor.queue_span_update(span, span_state="error") - - return span - return None - - def add_span(self, span: TraceSpan): - """Add a trace span to this trace context""" - self.trace_spans.append(span) - self.span_id_to_span[span.span_id] = span - return self - - def print(self): - """Print the complete trace with proper visual structure""" - for span in self.trace_spans: - span.print_span() - - def get_duration(self) -> float: - """ - Get the total duration of this trace - """ - if self.start_time is None: - return 0.0 - return time.time() - self.start_time - - def save(self, final_save: bool = False) -> Tuple[str, dict]: - """ - Save the current trace to the database with rate limiting checks. - First checks usage limits, then upserts the trace if allowed. - - Args: - final_save: Whether this is the final save (updates usage counters) - - Returns a tuple of (trace_id, server_response) where server_response contains the UI URL and other metadata. - """ - if final_save: - try: - self.otel_span_processor.flush_pending_spans() - except Exception as e: - judgeval_logger.warning( - f"Error flushing spans for trace {self.trace_id}: {e}" - ) - - total_duration = self.get_duration() - - trace_data = { - "trace_id": self.trace_id, - "name": self.name, - "project_name": self.project_name, - "created_at": datetime.fromtimestamp( - self.start_time or time.time(), timezone.utc - ).isoformat(), - "duration": total_duration, - "trace_spans": [span.model_dump() for span in self.trace_spans], - "evaluation_runs": [run.model_dump() for run in self.evaluation_runs], - "offline_mode": self.tracer.offline_mode, - "parent_trace_id": self.parent_trace_id, - "parent_name": self.parent_name, - "customer_id": self.customer_id, - "tags": self.tags, - "metadata": self.metadata, - "update_id": self.update_id, - } - - server_response = self.trace_manager_client.upsert_trace( - trace_data, - offline_mode=self.tracer.offline_mode, - show_link=not final_save, - final_save=final_save, - ) - - if self.start_time is None: - self.start_time = time.time() - - self.update_id += 1 - - return self.trace_id, server_response - - def delete(self): - return self.trace_manager_client.delete_trace(self.trace_id) - - def update_metadata(self, metadata: dict): - """ - Set metadata for this trace. - - Args: - metadata: Metadata as a dictionary - - Supported keys: - - customer_id: ID of the customer using this trace - - tags: List of tags for this trace - - has_notification: Whether this trace has a notification - - name: Name of the trace - """ - for k, v in metadata.items(): - if k == "customer_id": - if v is not None: - self.customer_id = str(v) - else: - self.customer_id = None - elif k == "tags": - if isinstance(v, list): - for item in v: - if not isinstance(item, (str, set, tuple)): - raise ValueError( - f"Tags must be a list of strings, sets, or tuples, got item of type {type(item)}" - ) - self.tags = v - else: - raise ValueError( - f"Tags must be a list of strings, sets, or tuples, got {type(v)}" - ) - elif k == "has_notification": - if not isinstance(v, bool): - raise ValueError( - f"has_notification must be a boolean, got {type(v)}" - ) - self.has_notification = v - elif k == "name": - self.name = v - else: - self.metadata[k] = v - - def set_customer_id(self, customer_id: str): - """ - Set the customer ID for this trace. - - Args: - customer_id: The customer ID to set - """ - self.update_metadata({"customer_id": customer_id}) - - def set_tags(self, tags: List[Union[str, set, tuple]]): - """ - Set the tags for this trace. - - Args: - tags: List of tags to set - """ - self.update_metadata({"tags": tags}) - - def set_reward_score(self, reward_score: Union[float, Dict[str, float]]): - """ - Set the reward score for this trace to be used for RL or SFT. - - Args: - reward_score: The reward score to set - """ - self.update_metadata({"reward_score": reward_score}) - - -def _capture_exception_for_trace( - current_trace: Optional[TraceClient], exc_info: OptExcInfo -): - if not current_trace: - return - - exc_type, exc_value, exc_traceback_obj = exc_info - formatted_exception = { - "type": exc_type.__name__ if exc_type else "UnknownExceptionType", - "message": str(exc_value) if exc_value else "No exception message", - "traceback": ( - traceback.format_tb(exc_traceback_obj) if exc_traceback_obj else [] - ), - } - - # This is where we specially handle exceptions that we might want to collect additional data for. - # When we do this, always try checking the module from sys.modules instead of importing. This will - # Let us support a wider range of exceptions without needing to import them for all clients. - - # Most clients (requests, httpx, urllib) support the standard format of exposing error.request.url and error.response.status_code - # The alternative is to hand select libraries we want from sys.modules and check for them: - # As an example: requests_module = sys.modules.get("requests", None) // then do things with requests_module; - - # General HTTP Like errors - try: - url = getattr(getattr(exc_value, "request", None), "url", None) - status_code = getattr(getattr(exc_value, "response", None), "status_code", None) - if status_code: - formatted_exception["http"] = { - "url": url if url else "Unknown URL", - "status_code": status_code if status_code else None, - } - except Exception: - pass - - current_trace.record_error(formatted_exception) - - -class _DeepTracer: - _instance: Optional["_DeepTracer"] = None - _lock: threading.Lock = threading.Lock() - _refcount: int = 0 - _span_stack: contextvars.ContextVar[List[Dict[str, Any]]] = contextvars.ContextVar( - "_deep_profiler_span_stack", default=[] - ) - _skip_stack: contextvars.ContextVar[List[str]] = contextvars.ContextVar( - "_deep_profiler_skip_stack", default=[] - ) - _original_sys_trace: Optional[Callable] = None - _original_threading_trace: Optional[Callable] = None - - def __init__(self, tracer: "Tracer"): - self._tracer = tracer - - def _get_qual_name(self, frame) -> str: - func_name = frame.f_code.co_name - module_name = frame.f_globals.get("__name__", "unknown_module") - - try: - func = frame.f_globals.get(func_name) - if func is None: - return f"{module_name}.{func_name}" - if hasattr(func, "__qualname__"): - return f"{module_name}.{func.__qualname__}" - return f"{module_name}.{func_name}" - except Exception: - return f"{module_name}.{func_name}" - - def __new__(cls, tracer: "Tracer"): - with cls._lock: - if cls._instance is None: - cls._instance = super().__new__(cls) - return cls._instance - - def _should_trace(self, frame): - # Skip stack is maintained by the tracer as an optimization to skip earlier - # frames in the call stack that we've already determined should be skipped - skip_stack = self._skip_stack.get() - if len(skip_stack) > 0: - return False - - func_name = frame.f_code.co_name - module_name = frame.f_globals.get("__name__", None) - func = frame.f_globals.get(func_name) - if func and ( - hasattr(func, "_judgment_span_name") or hasattr(func, "_judgment_span_type") - ): - return False - - if ( - not module_name - or func_name.startswith("<") # ex: - or func_name.startswith("__") - and func_name != "__call__" # dunders - or not self._is_user_code(frame.f_code.co_filename) - ): - return False - - return True - - @functools.cache - def _is_user_code(self, filename: str): - return ( - bool(filename) - and not filename.startswith("<") - and not os.path.realpath(filename).startswith(_TRACE_FILEPATH_BLOCKLIST) - ) - - def _cooperative_sys_trace(self, frame: types.FrameType, event: str, arg: Any): - """Cooperative trace function for sys.settrace that chains with existing tracers.""" - # First, call the original sys trace function if it exists - original_result = None - if self._original_sys_trace: - try: - original_result = self._original_sys_trace(frame, event, arg) - except Exception: - pass - - our_result = self._trace(frame, event, arg, self._cooperative_sys_trace) - - if original_result is None and self._original_sys_trace: - return None - - return our_result or original_result - - def _cooperative_threading_trace( - self, frame: types.FrameType, event: str, arg: Any - ): - """Cooperative trace function for threading.settrace that chains with existing tracers.""" - original_result = None - if self._original_threading_trace: - try: - original_result = self._original_threading_trace(frame, event, arg) - except Exception: - pass - - our_result = self._trace(frame, event, arg, self._cooperative_threading_trace) - - if original_result is None and self._original_threading_trace: - return None - - return our_result or original_result - - def _trace( - self, frame: types.FrameType, event: str, arg: Any, continuation_func: Callable - ): - frame.f_trace_lines = False - frame.f_trace_opcodes = False - - if not self._should_trace(frame): - return - - if event not in ("call", "return", "exception"): - return - - current_trace = self._tracer.get_current_trace() - if not current_trace: - return - - parent_span_id = self._tracer.get_current_span() - if not parent_span_id: - return - - qual_name = self._get_qual_name(frame) - instance_name = None - class_name = None - if "self" in frame.f_locals: - instance = frame.f_locals["self"] - class_name = instance.__class__.__name__ - class_identifiers = getattr(self._tracer, "class_identifiers", {}) - instance_name = get_instance_prefixed_name( - instance, class_name, class_identifiers - ) - skip_stack = self._skip_stack.get() - - if event == "call": - # If we have entries in the skip stack and the current qual_name matches the top entry, - # push it again to track nesting depth and skip - # As an optimization, we only care about duplicate qual_names. - if skip_stack: - if qual_name == skip_stack[-1]: - skip_stack.append(qual_name) - self._skip_stack.set(skip_stack) - return - - should_trace = self._should_trace(frame) - - if not should_trace: - if not skip_stack: - self._skip_stack.set([qual_name]) - return - elif event == "return": - # If we have entries in skip stack and current qual_name matches the top entry, - # pop it to track exiting from the skipped section - if skip_stack and qual_name == skip_stack[-1]: - skip_stack.pop() - self._skip_stack.set(skip_stack) - return - - if skip_stack: - return - - span_stack = self._span_stack.get() - if event == "call": - if not self._should_trace(frame): - return - - span_id = str(uuid.uuid4()) - - parent_depth = current_trace._span_depths.get(parent_span_id, 0) - depth = parent_depth + 1 - - current_trace._span_depths[span_id] = depth - - start_time = time.time() - - span_stack.append( - { - "span_id": span_id, - "parent_span_id": parent_span_id, - "function": qual_name, - "start_time": start_time, - } - ) - self._span_stack.set(span_stack) - - token = self._tracer.set_current_span(span_id) - frame.f_locals["_judgment_span_token"] = token - - span = TraceSpan( - span_id=span_id, - trace_id=current_trace.trace_id, - depth=depth, - message=qual_name, - created_at=start_time, - span_type="span", - parent_span_id=parent_span_id, - function=qual_name, - agent_name=instance_name, - class_name=class_name, - ) - current_trace.add_span(span) - - inputs = {} - try: - args_info = inspect.getargvalues(frame) - for arg in args_info.args: - try: - inputs[arg] = args_info.locals.get(arg) - except Exception: - inputs[arg] = "<>" - current_trace.record_input(inputs) - except Exception as e: - current_trace.record_input({"error": str(e)}) - - elif event == "return": - if not span_stack: - return - - current_id = self._tracer.get_current_span() - - span_data = None - for i, entry in enumerate(reversed(span_stack)): - if entry["span_id"] == current_id: - span_data = span_stack.pop(-(i + 1)) - self._span_stack.set(span_stack) - break - - if not span_data: - return - - start_time = span_data["start_time"] - duration = time.time() - start_time - - current_trace.span_id_to_span[span_data["span_id"]].duration = duration - - if arg is not None: - # exception handling will take priority. - current_trace.record_output(arg) - - if span_data["span_id"] in current_trace._span_depths: - del current_trace._span_depths[span_data["span_id"]] - - if span_stack: - self._tracer.set_current_span(span_stack[-1]["span_id"]) - else: - self._tracer.set_current_span(span_data["parent_span_id"]) - - if "_judgment_span_token" in frame.f_locals: - self._tracer.reset_current_span(frame.f_locals["_judgment_span_token"]) - - elif event == "exception": - exc_type = arg[0] - if issubclass(exc_type, (StopIteration, StopAsyncIteration, GeneratorExit)): - return - _capture_exception_for_trace(current_trace, arg) - - return continuation_func - - def __enter__(self): - with self._lock: - self._refcount += 1 - if self._refcount == 1: - # Store the existing trace functions before setting ours - self._original_sys_trace = sys.gettrace() - self._original_threading_trace = threading.gettrace() - - self._skip_stack.set([]) - self._span_stack.set([]) - - sys.settrace(self._cooperative_sys_trace) - threading.settrace(self._cooperative_threading_trace) - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - with self._lock: - self._refcount -= 1 - if self._refcount == 0: - # Restore the original trace functions instead of setting to None - sys.settrace(self._original_sys_trace) - threading.settrace(self._original_threading_trace) - - # Clean up the references - self._original_sys_trace = None - self._original_threading_trace = None - - -T = TypeVar("T", bound=Callable[..., Any]) -P = ParamSpec("P") - - -class Tracer: - # Tracer.current_trace class variable is currently used in wrap() - # TODO: Keep track of cross-context state for current trace and current span ID solely through class variables instead of instance variables? - # Should be fine to do so as long as we keep Tracer as a singleton - current_trace: Optional[TraceClient] = None - # current_span_id: Optional[str] = None - - trace_across_async_contexts: bool = ( - False # BY default, we don't trace across async contexts - ) - - def __init__( - self, - api_key: Union[str, None] = os.getenv("JUDGMENT_API_KEY"), - organization_id: Union[str, None] = os.getenv("JUDGMENT_ORG_ID"), - project_name: Union[str, None] = None, - deep_tracing: bool = False, # Deep tracing is disabled by default - enable_monitoring: bool = os.getenv("JUDGMENT_MONITORING", "true").lower() - == "true", - enable_evaluations: bool = os.getenv("JUDGMENT_EVALUATIONS", "true").lower() - == "true", - show_trace_urls: bool = os.getenv("JUDGMENT_SHOW_TRACE_URLS", "true").lower() - == "true", - # S3 configuration - use_s3: bool = False, - s3_bucket_name: Optional[str] = None, - s3_aws_access_key_id: Optional[str] = None, - s3_aws_secret_access_key: Optional[str] = None, - s3_region_name: Optional[str] = None, - trace_across_async_contexts: bool = False, # BY default, we don't trace across async contexts - span_batch_size: int = 50, - span_flush_interval: float = 1.0, - span_max_queue_size: int = 2048, - span_export_timeout: int = 30000, - ): - try: - if not api_key: - raise ValueError( - "api_key parameter must be provided. Please provide a valid API key value or set the JUDGMENT_API_KEY environment variable" - ) - - if not organization_id: - raise ValueError( - "organization_id parameter must be provided. Please provide a valid organization ID value or set the JUDGMENT_ORG_ID environment variable" - ) - - try: - result, response = validate_api_key(api_key) - except Exception as e: - judgeval_logger.error( - f"Issue with verifying API key, disabling monitoring: {e}" - ) - enable_monitoring = False - result = True - - if not result: - raise ValueError(f"Issue with passed in Judgment API key: {response}") - - if use_s3 and not s3_bucket_name: - raise ValueError("S3 bucket name must be provided when use_s3 is True") - - self.api_key: str = api_key - self.project_name: str = project_name or "default_project" - self.organization_id: str = organization_id - self.traces: List[Trace] = [] - self.enable_monitoring: bool = enable_monitoring - self.enable_evaluations: bool = enable_evaluations - self.show_trace_urls: bool = show_trace_urls - self.class_identifiers: Dict[ - str, str - ] = {} # Dictionary to store class identifiers - self.span_id_to_previous_span_id: Dict[str, Union[str, None]] = {} - self.trace_id_to_previous_trace: Dict[str, Union[TraceClient, None]] = {} - self.current_span_id: Optional[str] = None - self.current_trace: Optional[TraceClient] = None - self.trace_across_async_contexts: bool = trace_across_async_contexts - Tracer.trace_across_async_contexts = trace_across_async_contexts - - # Initialize S3 storage if enabled - self.use_s3 = use_s3 - if use_s3: - from judgeval.common.storage.s3_storage import S3Storage - - try: - self.s3_storage = S3Storage( - bucket_name=s3_bucket_name, - aws_access_key_id=s3_aws_access_key_id, - aws_secret_access_key=s3_aws_secret_access_key, - region_name=s3_region_name, - ) - except Exception as e: - judgeval_logger.error( - f"Issue with initializing S3 storage, disabling S3: {e}" - ) - self.use_s3 = False - - self.offline_mode = False # This is used to differentiate traces between online and offline (IE experiments vs monitoring page) - self.deep_tracing: bool = deep_tracing - - self.span_batch_size = span_batch_size - self.span_flush_interval = span_flush_interval - self.span_max_queue_size = span_max_queue_size - self.span_export_timeout = span_export_timeout - self.otel_span_processor: SpanProcessorBase - if enable_monitoring: - self.otel_span_processor = JudgmentSpanProcessor( - judgment_api_key=api_key, - organization_id=organization_id, - batch_size=span_batch_size, - flush_interval=span_flush_interval, - max_queue_size=span_max_queue_size, - export_timeout=span_export_timeout, - ) - else: - self.otel_span_processor = SpanProcessorBase() - - # Initialize local evaluation queue for custom scorers - self.local_eval_queue = LocalEvaluationQueue() - - # Start workers with callback to log results only if monitoring is enabled - if enable_evaluations and enable_monitoring: - self.local_eval_queue.start_workers( - callback=self._log_eval_results_callback - ) - - atexit.register(self._cleanup_on_exit) - except Exception as e: - judgeval_logger.error( - f"Issue with initializing Tracer: {e}. Disabling monitoring and evaluations." - ) - self.enable_monitoring = False - self.enable_evaluations = False - - def set_current_span( - self, span_id: str - ) -> Optional[contextvars.Token[Union[str, None]]]: - self.span_id_to_previous_span_id[span_id] = self.current_span_id - self.current_span_id = span_id - Tracer.current_span_id = span_id - try: - token = current_span_var.set(span_id) - except Exception: - token = None - return token - - def get_current_span(self) -> Optional[str]: - try: - current_span_var_val = current_span_var.get() - except Exception: - current_span_var_val = None - return ( - (self.current_span_id or current_span_var_val) - if self.trace_across_async_contexts - else current_span_var_val - ) - - def reset_current_span( - self, - token: Optional[contextvars.Token[Union[str, None]]] = None, - span_id: Optional[str] = None, - ): - try: - if token: - current_span_var.reset(token) - except Exception: - pass - if not span_id: - span_id = self.current_span_id - if span_id: - self.current_span_id = self.span_id_to_previous_span_id.get(span_id) - Tracer.current_span_id = self.current_span_id - - def set_current_trace( - self, trace: TraceClient - ) -> Optional[contextvars.Token[Union[TraceClient, None]]]: - """ - Set the current trace context in contextvars - """ - self.trace_id_to_previous_trace[trace.trace_id] = self.current_trace - self.current_trace = trace - Tracer.current_trace = trace - try: - token = current_trace_var.set(trace) - except Exception: - token = None - return token - - def get_current_trace(self) -> Optional[TraceClient]: - """ - Get the current trace context. - - Tries to get the trace client from the context variable first. - If not found (e.g., context lost across threads/tasks), - it falls back to the active trace client managed by the callback handler. - """ - try: - current_trace_var_val = current_trace_var.get() - except Exception: - current_trace_var_val = None - return ( - (self.current_trace or current_trace_var_val) - if self.trace_across_async_contexts - else current_trace_var_val - ) - - def reset_current_trace( - self, - token: Optional[contextvars.Token[Union[TraceClient, None]]] = None, - trace_id: Optional[str] = None, - ): - try: - if token: - current_trace_var.reset(token) - except Exception: - pass - if not trace_id and self.current_trace: - trace_id = self.current_trace.trace_id - if trace_id: - self.current_trace = self.trace_id_to_previous_trace.get(trace_id) - Tracer.current_trace = self.current_trace - - @contextmanager - def trace( - self, name: str, project_name: Union[str, None] = None - ) -> Generator[TraceClient, None, None]: - """Start a new trace context using a context manager""" - trace_id = str(uuid.uuid4()) - project = project_name if project_name is not None else self.project_name - - # Get parent trace info from context - parent_trace = self.get_current_trace() - parent_trace_id = None - parent_name = None - - if parent_trace: - parent_trace_id = parent_trace.trace_id - parent_name = parent_trace.name - - trace = TraceClient( - self, - trace_id, - name, - project_name=project, - enable_monitoring=self.enable_monitoring, - enable_evaluations=self.enable_evaluations, - parent_trace_id=parent_trace_id, - parent_name=parent_name, - ) - - # Set the current trace in context variables - token = self.set_current_trace(trace) - - with trace.span(name or "unnamed_trace"): - try: - # Save the trace to the database to handle Evaluations' trace_id referential integrity - yield trace - finally: - # Reset the context variable - self.reset_current_trace(token) - - def agent( - self, - identifier: Optional[str] = None, - track_state: Optional[bool] = False, - track_attributes: Optional[List[str]] = None, - field_mappings: Optional[Dict[str, str]] = None, - ): - """ - Class decorator that associates a class with a custom identifier and enables state tracking. - - This decorator creates a mapping between the class name and the provided - identifier, which can be useful for tagging, grouping, or referencing - classes in a standardized way. It also enables automatic state capture - for instances of the decorated class when used with tracing. - - Args: - identifier: The identifier to associate with the decorated class. - This will be used as the instance name in traces. - track_state: Whether to automatically capture the state (attributes) - of instances before and after function execution. Defaults to False. - track_attributes: Optional list of specific attribute names to track. - If None, all non-private attributes (not starting with '_') - will be tracked when track_state=True. - field_mappings: Optional dictionary mapping internal attribute names to - display names in the captured state. For example: - {"system_prompt": "instructions"} will capture the - 'instructions' attribute as 'system_prompt' in the state. - - Example: - @tracer.identify(identifier="user_model", track_state=True, track_attributes=["name", "age"], field_mappings={"system_prompt": "instructions"}) - class User: - # Class implementation - """ - - def decorator(cls): - class_name = cls.__name__ - self.class_identifiers[class_name] = { - "identifier": identifier, - "track_state": track_state, - "track_attributes": track_attributes, - "field_mappings": field_mappings or {}, - "class_name": class_name, - } - return cls - - return decorator - - def identify(self, *args, **kwargs): - judgeval_logger.warning( - "identify() is deprecated and may not be supported in future versions of judgeval. Use the agent() decorator instead." - ) - return self.agent(*args, **kwargs) - - def _capture_instance_state( - self, instance: Any, class_config: Dict[str, Any] - ) -> Dict[str, Any]: - """ - Capture the state of an instance based on class configuration. - Args: - instance: The instance to capture the state of. - class_config: Configuration dictionary for state capture, - expected to contain 'track_attributes' and 'field_mappings'. - """ - track_attributes = class_config.get("track_attributes") - field_mappings = class_config.get("field_mappings") - - if track_attributes: - state = {attr: getattr(instance, attr, None) for attr in track_attributes} - else: - state = { - k: v for k, v in instance.__dict__.items() if not k.startswith("_") - } - - if field_mappings: - state["field_mappings"] = field_mappings - - return state - - def _get_instance_state_if_tracked(self, args): - """ - Extract instance state if the instance should be tracked. - - Returns the captured state dict if tracking is enabled, None otherwise. - """ - if args and hasattr(args[0], "__class__"): - instance = args[0] - class_name = instance.__class__.__name__ - if ( - class_name in self.class_identifiers - and isinstance(self.class_identifiers[class_name], dict) - and self.class_identifiers[class_name].get("track_state", False) - ): - return self._capture_instance_state( - instance, self.class_identifiers[class_name] - ) - - def _conditionally_capture_and_record_state( - self, trace_client_instance: TraceClient, args: tuple, is_before: bool - ): - """Captures instance state if tracked and records it via the trace_client.""" - state = self._get_instance_state_if_tracked(args) - if state: - if is_before: - trace_client_instance.record_state_before(state) - else: - trace_client_instance.record_state_after(state) - - @overload - def observe( - self, func: T, *, name: Optional[str] = None, span_type: SpanType = "span" - ) -> T: ... - - @overload - def observe( - self, - *, - name: Optional[str] = None, - span_type: SpanType = "span", - ) -> Callable[[T], T]: ... - - def observe( - self, - func: Optional[T] = None, - *, - name: Optional[str] = None, - span_type: SpanType = "span", - ): - """ - Decorator to trace function execution with detailed entry/exit information. - - Args: - func: The function to decorate - name: Optional custom name for the span (defaults to function name) - span_type: Type of span (default "span"). - """ - # If monitoring is disabled, return the function as is - try: - if not self.enable_monitoring: - return func if func else lambda f: f - - if func is None: - return lambda func: self.observe( - func, - name=name, - span_type=span_type, - ) - - # Use provided name or fall back to function name - original_span_name = name or func.__name__ - - # Store custom attributes on the function object - func._judgment_span_name = original_span_name # type: ignore - func._judgment_span_type = span_type # type: ignore - - except Exception: - return func - - def _record_span_data(span, args, kwargs): - """Helper function to record inputs, agent info, and state on a span.""" - # Get class and agent info - class_name = None - agent_name = None - if args and hasattr(args[0], "__class__"): - class_name = args[0].__class__.__name__ - agent_name = get_instance_prefixed_name( - args[0], class_name, self.class_identifiers - ) - - # Record inputs, agent name, class name - inputs = combine_args_kwargs(func, args, kwargs) - span.record_input(inputs) - if agent_name: - span.record_agent_name(agent_name) - if class_name and class_name in self.class_identifiers: - span.record_class_name(class_name) - - # Capture state before execution - self._conditionally_capture_and_record_state(span, args, is_before=True) - - return class_name, agent_name - - def _finalize_span_data(span, result, args): - """Helper function to record outputs and final state on a span.""" - # Record output - span.record_output(result) - - # Capture state after execution - self._conditionally_capture_and_record_state(span, args, is_before=False) - - def _cleanup_trace(current_trace, trace_token, wrapper_type="function"): - """Helper function to handle trace cleanup in finally blocks.""" - try: - trace_id, server_response = current_trace.save(final_save=True) - - complete_trace_data = { - "trace_id": current_trace.trace_id, - "name": current_trace.name, - "project_name": current_trace.project_name, - "created_at": datetime.fromtimestamp( - current_trace.start_time or time.time(), - timezone.utc, - ).isoformat(), - "duration": current_trace.get_duration(), - "trace_spans": [ - span.model_dump() for span in current_trace.trace_spans - ], - "evaluation_runs": [ - run.model_dump() for run in current_trace.evaluation_runs - ], - "offline_mode": self.offline_mode, - "parent_trace_id": current_trace.parent_trace_id, - "parent_name": current_trace.parent_name, - "customer_id": current_trace.customer_id, - "tags": current_trace.tags, - "metadata": current_trace.metadata, - "update_id": current_trace.update_id, - } - self.traces.append(complete_trace_data) - self.reset_current_trace(trace_token) - except Exception as e: - judgeval_logger.warning(f"Issue with {wrapper_type} cleanup: {e}") - - def _execute_in_span( - current_trace, span_name, span_type, execution_func, args, kwargs - ): - """Helper function to execute code within a span context.""" - with current_trace.span(span_name, span_type=span_type) as span: - _record_span_data(span, args, kwargs) - - try: - result = execution_func() - _finalize_span_data(span, result, args) - return result - except Exception as e: - _capture_exception_for_trace(current_trace, sys.exc_info()) - raise e - - async def _execute_in_span_async( - current_trace, span_name, span_type, async_execution_func, args, kwargs - ): - """Helper function to execute async code within a span context.""" - with current_trace.span(span_name, span_type=span_type) as span: - _record_span_data(span, args, kwargs) - - try: - result = await async_execution_func() - _finalize_span_data(span, result, args) - return result - except Exception as e: - _capture_exception_for_trace(current_trace, sys.exc_info()) - raise e - - def _create_new_trace(self, span_name): - """Helper function to create a new trace and set it as current.""" - trace_id = str(uuid.uuid4()) - project = self.project_name - - current_trace = TraceClient( - self, - trace_id, - span_name, - project_name=project, - enable_monitoring=self.enable_monitoring, - enable_evaluations=self.enable_evaluations, - ) - - trace_token = self.set_current_trace(current_trace) - return current_trace, trace_token - - def _execute_with_auto_trace_creation( - span_name, span_type, execution_func, args, kwargs - ): - """Helper function that handles automatic trace creation and span execution.""" - current_trace = self.get_current_trace() - - if not current_trace: - current_trace, trace_token = _create_new_trace(self, span_name) - - try: - result = _execute_in_span( - current_trace, - span_name, - span_type, - execution_func, - args, - kwargs, - ) - return result - finally: - # Cleanup the trace we created - _cleanup_trace(current_trace, trace_token, "auto_trace") - else: - # Use existing trace - return _execute_in_span( - current_trace, span_name, span_type, execution_func, args, kwargs - ) - - async def _execute_with_auto_trace_creation_async( - span_name, span_type, async_execution_func, args, kwargs - ): - """Helper function that handles automatic trace creation and async span execution.""" - current_trace = self.get_current_trace() - - if not current_trace: - current_trace, trace_token = _create_new_trace(self, span_name) - - try: - result = await _execute_in_span_async( - current_trace, - span_name, - span_type, - async_execution_func, - args, - kwargs, - ) - return result - finally: - # Cleanup the trace we created - _cleanup_trace(current_trace, trace_token, "async_auto_trace") - else: - # Use existing trace - return await _execute_in_span_async( - current_trace, - span_name, - span_type, - async_execution_func, - args, - kwargs, - ) - - # Check for generator functions first - if inspect.isgeneratorfunction(func): - - @functools.wraps(func) - def generator_wrapper(*args, **kwargs): - # Get the generator from the original function - generator = func(*args, **kwargs) - - # Create wrapper generator that creates spans for each yield - def traced_generator(): - while True: - try: - # Handle automatic trace creation and span execution - item = _execute_with_auto_trace_creation( - original_span_name, - span_type, - lambda: next(generator), - args, - kwargs, - ) - yield item - except StopIteration: - break - - return traced_generator() - - return generator_wrapper - - # Check for async generator functions - elif inspect.isasyncgenfunction(func): - - @functools.wraps(func) - def async_generator_wrapper(*args, **kwargs): - # Get the async generator from the original function - async_generator = func(*args, **kwargs) - - # Create wrapper async generator that creates spans for each yield - async def traced_async_generator(): - while True: - try: - # Handle automatic trace creation and span execution - item = await _execute_with_auto_trace_creation_async( - original_span_name, - span_type, - lambda: async_generator.__anext__(), - args, - kwargs, - ) - if inspect.iscoroutine(item): - item = await item - yield item - except StopAsyncIteration: - break - - return traced_async_generator() - - return async_generator_wrapper - - elif asyncio.iscoroutinefunction(func): - - @functools.wraps(func) - async def async_wrapper(*args, **kwargs): - nonlocal original_span_name - span_name = original_span_name - - async def async_execution(): - if self.deep_tracing: - with _DeepTracer(self): - return await func(*args, **kwargs) - else: - return await func(*args, **kwargs) - - result = await _execute_with_auto_trace_creation_async( - span_name, span_type, async_execution, args, kwargs - ) - - return result - - return async_wrapper - else: - # Non-async function implementation with deep tracing - @functools.wraps(func) - def wrapper(*args, **kwargs): - nonlocal original_span_name - span_name = original_span_name - - def sync_execution(): - if self.deep_tracing: - with _DeepTracer(self): - return func(*args, **kwargs) - else: - return func(*args, **kwargs) - - return _execute_with_auto_trace_creation( - span_name, span_type, sync_execution, args, kwargs - ) - - return wrapper - - def observe_tools( - self, - cls=None, - *, - exclude_methods: Optional[List[str]] = None, - include_private: bool = False, - warn_on_double_decoration: bool = True, - ): - """ - Automatically adds @observe(span_type="tool") to all methods in a class. - - Args: - cls: The class to decorate (automatically provided when used as decorator) - exclude_methods: List of method names to skip decorating. Defaults to common magic methods - include_private: Whether to decorate methods starting with underscore. Defaults to False - warn_on_double_decoration: Whether to print warnings when skipping already-decorated methods. Defaults to True - """ - - if exclude_methods is None: - exclude_methods = ["__init__", "__new__", "__del__", "__str__", "__repr__"] - - def decorate_class(cls): - if not self.enable_monitoring: - return cls - - decorated = [] - skipped = [] - - for name in dir(cls): - method = getattr(cls, name) - - if ( - not callable(method) - or name in exclude_methods - or (name.startswith("_") and not include_private) - or not hasattr(cls, name) - ): - continue - - if hasattr(method, "_judgment_span_name"): - skipped.append(name) - if warn_on_double_decoration: - judgeval_logger.info( - f"{cls.__name__}.{name} already decorated, skipping" - ) - continue - - try: - decorated_method = self.observe(method, span_type="tool") - setattr(cls, name, decorated_method) - decorated.append(name) - except Exception as e: - if warn_on_double_decoration: - judgeval_logger.warning( - f"Failed to decorate {cls.__name__}.{name}: {e}" - ) - - return cls - - return decorate_class if cls is None else decorate_class(cls) - - def async_evaluate( - self, - scorer: Union[APIScorerConfig, BaseScorer], - example: Example, - model: str = DEFAULT_GPT_MODEL, - sampling_rate: float = 1, - ): - try: - if not self.enable_monitoring or not self.enable_evaluations: - return - - if not isinstance(scorer, (APIScorerConfig, BaseScorer)): - judgeval_logger.warning( - f"Scorer must be an instance of APIScorerConfig or BaseScorer, got {type(scorer)}, skipping evaluation" - ) - return - - if not isinstance(example, Example): - judgeval_logger.warning( - f"Example must be an instance of Example, got {type(example)} skipping evaluation" - ) - return - - if sampling_rate < 0: - judgeval_logger.warning( - "Cannot set sampling_rate below 0, skipping evaluation" - ) - return - - if sampling_rate > 1: - judgeval_logger.warning( - "Cannot set sampling_rate above 1, skipping evaluation" - ) - return - - percentage = random.uniform(0, 1) - if percentage > sampling_rate: - judgeval_logger.info("Skipping async_evaluate due to sampling rate") - return - - current_trace = self.get_current_trace() - if current_trace: - current_trace.async_evaluate( - scorer=scorer, example=example, model=model - ) - else: - judgeval_logger.warning( - "No trace found (context var or fallback), skipping evaluation" - ) - except Exception as e: - judgeval_logger.warning(f"Issue with async_evaluate: {e}") - - def update_metadata(self, metadata: dict): - """ - Update metadata for the current trace. - - Args: - metadata: Metadata as a dictionary - """ - current_trace = self.get_current_trace() - if current_trace: - current_trace.update_metadata(metadata) - else: - judgeval_logger.warning("No current trace found, cannot set metadata") - - def set_customer_id(self, customer_id: str): - """ - Set the customer ID for the current trace. - - Args: - customer_id: The customer ID to set - """ - current_trace = self.get_current_trace() - if current_trace: - current_trace.set_customer_id(customer_id) - else: - judgeval_logger.warning("No current trace found, cannot set customer ID") - - def set_tags(self, tags: List[Union[str, set, tuple]]): - """ - Set the tags for the current trace. - - Args: - tags: List of tags to set - """ - current_trace = self.get_current_trace() - if current_trace: - current_trace.set_tags(tags) - else: - judgeval_logger.warning("No current trace found, cannot set tags") - - def set_reward_score(self, reward_score: Union[float, Dict[str, float]]): - """ - Set the reward score for this trace to be used for RL or SFT. - - Args: - reward_score: The reward score to set - """ - current_trace = self.get_current_trace() - if current_trace: - current_trace.set_reward_score(reward_score) - else: - judgeval_logger.warning("No current trace found, cannot set reward score") - - def get_otel_span_processor(self) -> SpanProcessorBase: - """Get the OpenTelemetry span processor instance.""" - return self.otel_span_processor - - def flush_background_spans(self, timeout_millis: int = 30000): - """Flush all pending spans in the background service.""" - self.otel_span_processor.force_flush(timeout_millis) - - def shutdown_background_service(self): - """Shutdown the background span service.""" - self.otel_span_processor.shutdown() - self.otel_span_processor = SpanProcessorBase() - - def wait_for_completion(self, timeout: Optional[float] = 30.0) -> bool: - """Wait for all evaluations and span processing to complete. - - This method blocks until all queued evaluations are processed and - all pending spans are flushed to the server. - - Args: - timeout: Maximum time to wait in seconds. Defaults to 30 seconds. - None means wait indefinitely. - - Returns: - True if all processing completed within the timeout, False otherwise. - - """ - try: - judgeval_logger.debug( - "Waiting for all evaluations and spans to complete..." - ) - - # Wait for all queued evaluation work to complete - eval_completed = self.local_eval_queue.wait_for_completion() - if not eval_completed: - judgeval_logger.warning( - f"Local evaluation queue did not complete within {timeout} seconds" - ) - return False - - self.flush_background_spans() - - judgeval_logger.debug("All evaluations and spans completed successfully") - return True - - except Exception as e: - judgeval_logger.warning(f"Error while waiting for completion: {e}") - return False - - def _log_eval_results_callback(self, evaluation_run, scoring_results): - """Callback to log evaluation results after local processing.""" - try: - if scoring_results and self.enable_evaluations and self.enable_monitoring: - # Convert scoring results to the format expected by API client - results_dict = [ - result.model_dump(warnings=False) for result in scoring_results - ] - api_client = JudgmentApiClient(self.api_key, self.organization_id) - api_client.log_evaluation_results( - results_dict, evaluation_run.model_dump(warnings=False) - ) - except Exception as e: - judgeval_logger.warning(f"Failed to log local evaluation results: {e}") - - def _cleanup_on_exit(self): - """Cleanup handler called on application exit to ensure spans are flushed.""" - try: - # Wait for all queued evaluation work to complete before stopping - completed = self.local_eval_queue.wait_for_completion() - if not completed: - judgeval_logger.warning( - "Local evaluation queue did not complete within 30 seconds" - ) - - self.local_eval_queue.stop_workers() - self.flush_background_spans() - except Exception as e: - judgeval_logger.warning(f"Error during tracer cleanup: {e}") - finally: - try: - self.shutdown_background_service() - except Exception as e: - judgeval_logger.warning( - f"Error during background service shutdown: {e}" - ) - - def trace_to_message_history( - self, trace: Union[Trace, TraceClient] - ) -> List[Dict[str, str]]: - """ - Extract message history from a trace for training purposes. - - This method processes trace spans to reconstruct the conversation flow, - extracting messages in chronological order from LLM, user, and tool spans. - - Args: - trace: Trace or TraceClient instance to extract messages from - - Returns: - List of message dictionaries with 'role' and 'content' keys - - Raises: - ValueError: If no trace is provided - """ - if not trace: - raise ValueError("No trace provided") - - # Handle both Trace and TraceClient objects - if isinstance(trace, TraceClient): - spans = trace.trace_spans - else: - spans = trace.trace_spans if hasattr(trace, "trace_spans") else [] - - messages = [] - first_found = False - - # Process spans in chronological order - for span in sorted( - spans, key=lambda s: s.created_at if hasattr(s, "created_at") else 0 - ): - # Skip spans without output (except for first LLM span which may have input messages) - if span.output is None and span.span_type != "llm": - continue - - if span.span_type == "llm": - # For the first LLM span, extract input messages (system + user prompts) - if not first_found and hasattr(span, "inputs") and span.inputs: - input_messages = span.inputs.get("messages", []) - if input_messages: - first_found = True - # Add input messages (typically system and user messages) - for msg in input_messages: - if ( - isinstance(msg, dict) - and "role" in msg - and "content" in msg - ): - messages.append( - {"role": msg["role"], "content": msg["content"]} - ) - - # Add assistant response from span output - if span.output is not None: - messages.append({"role": "assistant", "content": str(span.output)}) - - elif span.span_type == "user": - # Add user messages - if span.output is not None: - messages.append({"role": "user", "content": str(span.output)}) - - elif span.span_type == "tool": - # Add tool responses as user messages (common pattern in training) - if span.output is not None: - messages.append({"role": "user", "content": str(span.output)}) - - return messages - - def get_current_message_history(self) -> List[Dict[str, str]]: - """ - Get message history from the current trace. - - Returns: - List of message dictionaries from the current trace context - - Raises: - ValueError: If no current trace is found - """ - current_trace = self.get_current_trace() - if not current_trace: - raise ValueError("No current trace found") - - return self.trace_to_message_history(current_trace) - - -def _get_current_trace( - trace_across_async_contexts: bool = Tracer.trace_across_async_contexts, -): - if trace_across_async_contexts: - return Tracer.current_trace - else: - return current_trace_var.get() - - -def wrap( - client: Any, trace_across_async_contexts: bool = Tracer.trace_across_async_contexts -) -> Any: - """ - Wraps an API client to add tracing capabilities. - Supports OpenAI, Together, Anthropic, Google GenAI clients, and TrainableModel. - Patches both '.create' and Anthropic's '.stream' methods using a wrapper class. - """ - ( - span_name, - original_create, - original_responses_create, - original_stream, - original_beta_parse, - ) = _get_client_config(client) - - def process_span(span, response): - """Format and record the output in the span""" - output, usage = _format_output_data(client, response) - span.record_output(output) - span.record_usage(usage) - - return response - - def wrapped(function): - def wrapper(*args, **kwargs): - current_trace = _get_current_trace(trace_across_async_contexts) - if not current_trace: - return function(*args, **kwargs) - - with current_trace.span(span_name, span_type="llm") as span: - span.record_input(kwargs) - - try: - response = function(*args, **kwargs) - return process_span(span, response) - except Exception as e: - _capture_exception_for_trace(span, sys.exc_info()) - raise e - - return wrapper - - def wrapped_async(function): - async def wrapper(*args, **kwargs): - current_trace = _get_current_trace(trace_across_async_contexts) - if not current_trace: - return await function(*args, **kwargs) - - with current_trace.span(span_name, span_type="llm") as span: - span.record_input(kwargs) - - try: - response = await function(*args, **kwargs) - return process_span(span, response) - except Exception as e: - _capture_exception_for_trace(span, sys.exc_info()) - raise e - - return wrapper - - if HAS_OPENAI: - from judgeval.common.tracer.providers import openai_OpenAI, openai_AsyncOpenAI - - assert openai_OpenAI is not None, "OpenAI client not found" - assert openai_AsyncOpenAI is not None, "OpenAI async client not found" - if isinstance(client, (openai_OpenAI)): - setattr(client.chat.completions, "create", wrapped(original_create)) - setattr(client.responses, "create", wrapped(original_responses_create)) - setattr(client.beta.chat.completions, "parse", wrapped(original_beta_parse)) - elif isinstance(client, (openai_AsyncOpenAI)): - setattr(client.chat.completions, "create", wrapped_async(original_create)) - setattr( - client.responses, "create", wrapped_async(original_responses_create) - ) - setattr( - client.beta.chat.completions, - "parse", - wrapped_async(original_beta_parse), - ) - - if HAS_TOGETHER: - from judgeval.common.tracer.providers import ( - together_Together, - together_AsyncTogether, - ) - - assert together_Together is not None, "Together client not found" - assert together_AsyncTogether is not None, "Together async client not found" - if isinstance(client, (together_Together)): - setattr(client.chat.completions, "create", wrapped(original_create)) - elif isinstance(client, (together_AsyncTogether)): - setattr(client.chat.completions, "create", wrapped_async(original_create)) - - if HAS_ANTHROPIC: - from judgeval.common.tracer.providers import ( - anthropic_Anthropic, - anthropic_AsyncAnthropic, - ) - - assert anthropic_Anthropic is not None, "Anthropic client not found" - assert anthropic_AsyncAnthropic is not None, "Anthropic async client not found" - if isinstance(client, (anthropic_Anthropic)): - setattr(client.messages, "create", wrapped(original_create)) - elif isinstance(client, (anthropic_AsyncAnthropic)): - setattr(client.messages, "create", wrapped_async(original_create)) - - if HAS_GOOGLE_GENAI: - from judgeval.common.tracer.providers import ( - google_genai_Client, - google_genai_AsyncClient, - ) - - assert google_genai_Client is not None, "Google GenAI client not found" - assert google_genai_AsyncClient is not None, ( - "Google GenAI async client not found" - ) - if isinstance(client, (google_genai_Client)): - setattr(client.models, "generate_content", wrapped(original_create)) - elif isinstance(client, (google_genai_AsyncClient)): - setattr(client.models, "generate_content", wrapped_async(original_create)) - - if HAS_GROQ: - from judgeval.common.tracer.providers import groq_Groq, groq_AsyncGroq - - assert groq_Groq is not None, "Groq client not found" - assert groq_AsyncGroq is not None, "Groq async client not found" - if isinstance(client, (groq_Groq)): - setattr(client.chat.completions, "create", wrapped(original_create)) - elif isinstance(client, (groq_AsyncGroq)): - setattr(client.chat.completions, "create", wrapped_async(original_create)) - - # Check for TrainableModel from judgeval.common.trainer - try: - from judgeval.common.trainer import TrainableModel - - if isinstance(client, TrainableModel): - # Define a wrapper function that can be reapplied to new model instances - def wrap_model_instance(model_instance): - """Wrap a model instance with tracing functionality""" - if hasattr(model_instance, "chat") and hasattr( - model_instance.chat, "completions" - ): - if hasattr(model_instance.chat.completions, "create"): - setattr( - model_instance.chat.completions, - "create", - wrapped(model_instance.chat.completions.create), - ) - if hasattr(model_instance.chat.completions, "acreate"): - setattr( - model_instance.chat.completions, - "acreate", - wrapped_async(model_instance.chat.completions.acreate), - ) - - # Register the wrapper function with the TrainableModel - client._register_tracer_wrapper(wrap_model_instance) - - # Apply wrapping to the current model - wrap_model_instance(client._current_model) - except ImportError: - pass # TrainableModel not available - - return client - - -# Helper functions for client-specific operations - - -def _get_client_config( - client: ApiClient, -) -> tuple[str, Callable, Optional[Callable], Optional[Callable], Optional[Callable]]: - """Returns configuration tuple for the given API client. - - Args: - client: An instance of OpenAI, Together, or Anthropic client - - Returns: - tuple: (span_name, create_method, responses_method, stream_method, beta_parse_method) - - span_name: String identifier for tracing - - create_method: Reference to the client's creation method - - responses_method: Reference to the client's responses method (if applicable) - - stream_method: Reference to the client's stream method (if applicable) - - beta_parse_method: Reference to the client's beta parse method (if applicable) - - Raises: - ValueError: If client type is not supported - """ - - if HAS_OPENAI: - from judgeval.common.tracer.providers import openai_OpenAI, openai_AsyncOpenAI - - assert openai_OpenAI is not None, "OpenAI client not found" - assert openai_AsyncOpenAI is not None, "OpenAI async client not found" - if isinstance(client, (openai_OpenAI)): - return ( - "OPENAI_API_CALL", - client.chat.completions.create, - client.responses.create, - None, - client.beta.chat.completions.parse, - ) - elif isinstance(client, (openai_AsyncOpenAI)): - return ( - "OPENAI_API_CALL", - client.chat.completions.create, - client.responses.create, - None, - client.beta.chat.completions.parse, - ) - if HAS_TOGETHER: - from judgeval.common.tracer.providers import ( - together_Together, - together_AsyncTogether, - ) - - assert together_Together is not None, "Together client not found" - assert together_AsyncTogether is not None, "Together async client not found" - if isinstance(client, (together_Together)): - return "TOGETHER_API_CALL", client.chat.completions.create, None, None, None - elif isinstance(client, (together_AsyncTogether)): - return "TOGETHER_API_CALL", client.chat.completions.create, None, None, None - if HAS_ANTHROPIC: - from judgeval.common.tracer.providers import ( - anthropic_Anthropic, - anthropic_AsyncAnthropic, - ) - - assert anthropic_Anthropic is not None, "Anthropic client not found" - assert anthropic_AsyncAnthropic is not None, "Anthropic async client not found" - if isinstance(client, (anthropic_Anthropic)): - return ( - "ANTHROPIC_API_CALL", - client.messages.create, - None, - client.messages.stream, - None, - ) - elif isinstance(client, (anthropic_AsyncAnthropic)): - return ( - "ANTHROPIC_API_CALL", - client.messages.create, - None, - client.messages.stream, - None, - ) - if HAS_GOOGLE_GENAI: - from judgeval.common.tracer.providers import ( - google_genai_Client, - google_genai_AsyncClient, - ) - - assert google_genai_Client is not None, "Google GenAI client not found" - assert google_genai_AsyncClient is not None, ( - "Google GenAI async client not found" - ) - if isinstance(client, (google_genai_Client)): - return "GOOGLE_API_CALL", client.models.generate_content, None, None, None - elif isinstance(client, (google_genai_AsyncClient)): - return "GOOGLE_API_CALL", client.models.generate_content, None, None, None - if HAS_GROQ: - from judgeval.common.tracer.providers import groq_Groq, groq_AsyncGroq - - assert groq_Groq is not None, "Groq client not found" - assert groq_AsyncGroq is not None, "Groq async client not found" - if isinstance(client, (groq_Groq)): - return "GROQ_API_CALL", client.chat.completions.create, None, None, None - elif isinstance(client, (groq_AsyncGroq)): - return "GROQ_API_CALL", client.chat.completions.create, None, None, None - - # Check for TrainableModel - try: - from judgeval.common.trainer import TrainableModel - - if isinstance(client, TrainableModel): - return ( - "FIREWORKS_TRAINABLE_MODEL_CALL", - client._current_model.chat.completions.create, - None, - None, - None, - ) - except ImportError: - pass # TrainableModel not available - - raise ValueError(f"Unsupported client type: {type(client)}") - - -def _format_output_data( - client: ApiClient, response: Any -) -> tuple[Optional[str], Optional[TraceUsage]]: - """Format API response data based on client type. - - Normalizes different response formats into a consistent structure - for tracing purposes. - - Returns: - dict containing: - - content: The generated text - - usage: Token usage statistics - """ - prompt_tokens = 0 - completion_tokens = 0 - cache_read_input_tokens = 0 - cache_creation_input_tokens = 0 - model_name = None - message_content = None - - if HAS_OPENAI: - from judgeval.common.tracer.providers import ( - openai_OpenAI, - openai_AsyncOpenAI, - openai_ChatCompletion, - openai_Response, - openai_ParsedChatCompletion, - ) - - assert openai_OpenAI is not None, "OpenAI client not found" - assert openai_AsyncOpenAI is not None, "OpenAI async client not found" - assert openai_ChatCompletion is not None, "OpenAI chat completion not found" - assert openai_Response is not None, "OpenAI response not found" - assert openai_ParsedChatCompletion is not None, ( - "OpenAI parsed chat completion not found" - ) - - if isinstance(client, (openai_OpenAI, openai_AsyncOpenAI)): - if isinstance(response, openai_ChatCompletion): - model_name = response.model - prompt_tokens = response.usage.prompt_tokens if response.usage else 0 - completion_tokens = ( - response.usage.completion_tokens if response.usage else 0 - ) - cache_read_input_tokens = ( - response.usage.prompt_tokens_details.cached_tokens - if response.usage - and response.usage.prompt_tokens_details - and response.usage.prompt_tokens_details.cached_tokens - else 0 - ) - - if isinstance(response, openai_ParsedChatCompletion): - message_content = response.choices[0].message.parsed - else: - message_content = response.choices[0].message.content - elif isinstance(response, openai_Response): - model_name = response.model - prompt_tokens = response.usage.input_tokens if response.usage else 0 - completion_tokens = ( - response.usage.output_tokens if response.usage else 0 - ) - cache_read_input_tokens = ( - response.usage.input_tokens_details.cached_tokens - if response.usage and response.usage.input_tokens_details - else 0 - ) - if hasattr(response.output[0], "content"): - message_content = "".join( - seg.text - for seg in response.output[0].content - if hasattr(seg, "text") - ) - # Note: LiteLLM seems to use cache_read_input_tokens to calculate the cost for OpenAI - return message_content, _create_usage( - model_name, - prompt_tokens, - completion_tokens, - cache_read_input_tokens, - cache_creation_input_tokens, - ) - - if HAS_TOGETHER: - from judgeval.common.tracer.providers import ( - together_Together, - together_AsyncTogether, - ) - - assert together_Together is not None, "Together client not found" - assert together_AsyncTogether is not None, "Together async client not found" - - if isinstance(client, (together_Together, together_AsyncTogether)): - model_name = "together_ai/" + response.model - prompt_tokens = response.usage.prompt_tokens - completion_tokens = response.usage.completion_tokens - message_content = response.choices[0].message.content - - # As of 2025-07-14, Together does not do any input cache token tracking - return message_content, _create_usage( - model_name, - prompt_tokens, - completion_tokens, - cache_read_input_tokens, - cache_creation_input_tokens, - ) - - if HAS_GOOGLE_GENAI: - from judgeval.common.tracer.providers import ( - google_genai_Client, - google_genai_AsyncClient, - ) - - assert google_genai_Client is not None, "Google GenAI client not found" - assert google_genai_AsyncClient is not None, ( - "Google GenAI async client not found" - ) - if isinstance(client, (google_genai_Client, google_genai_AsyncClient)): - model_name = response.model_version - prompt_tokens = response.usage_metadata.prompt_token_count - completion_tokens = response.usage_metadata.candidates_token_count - message_content = response.candidates[0].content.parts[0].text - - if hasattr(response.usage_metadata, "cached_content_token_count"): - cache_read_input_tokens = ( - response.usage_metadata.cached_content_token_count - ) - return message_content, _create_usage( - model_name, - prompt_tokens, - completion_tokens, - cache_read_input_tokens, - cache_creation_input_tokens, - ) - - if HAS_ANTHROPIC: - from judgeval.common.tracer.providers import ( - anthropic_Anthropic, - anthropic_AsyncAnthropic, - ) - - assert anthropic_Anthropic is not None, "Anthropic client not found" - assert anthropic_AsyncAnthropic is not None, "Anthropic async client not found" - if isinstance(client, (anthropic_Anthropic, anthropic_AsyncAnthropic)): - model_name = response.model - prompt_tokens = response.usage.input_tokens - completion_tokens = response.usage.output_tokens - cache_read_input_tokens = response.usage.cache_read_input_tokens - cache_creation_input_tokens = response.usage.cache_creation_input_tokens - message_content = response.content[0].text - return message_content, _create_usage( - model_name, - prompt_tokens, - completion_tokens, - cache_read_input_tokens, - cache_creation_input_tokens, - ) - - if HAS_GROQ: - from judgeval.common.tracer.providers import groq_Groq, groq_AsyncGroq - - assert groq_Groq is not None, "Groq client not found" - assert groq_AsyncGroq is not None, "Groq async client not found" - if isinstance(client, (groq_Groq, groq_AsyncGroq)): - model_name = "groq/" + response.model - prompt_tokens = response.usage.prompt_tokens - completion_tokens = response.usage.completion_tokens - message_content = response.choices[0].message.content - return message_content, _create_usage( - model_name, - prompt_tokens, - completion_tokens, - cache_read_input_tokens, - cache_creation_input_tokens, - ) - - # Check for TrainableModel - try: - from judgeval.common.trainer import TrainableModel - - if isinstance(client, TrainableModel): - # TrainableModel uses Fireworks LLM internally, so response format should be similar to OpenAI - if ( - hasattr(response, "model") - and hasattr(response, "usage") - and hasattr(response, "choices") - ): - model_name = response.model - prompt_tokens = response.usage.prompt_tokens if response.usage else 0 - completion_tokens = ( - response.usage.completion_tokens if response.usage else 0 - ) - message_content = response.choices[0].message.content - - # Use LiteLLM cost calculation with fireworks_ai prefix - # LiteLLM supports Fireworks AI models for cost calculation when prefixed with "fireworks_ai/" - fireworks_model_name = f"fireworks_ai/{model_name}" - return message_content, _create_usage( - fireworks_model_name, - prompt_tokens, - completion_tokens, - cache_read_input_tokens, - cache_creation_input_tokens, - ) - except ImportError: - pass # TrainableModel not available - - judgeval_logger.warning(f"Unsupported client type: {type(client)}") - return None, None - - -def _create_usage( - model_name: str, - prompt_tokens: int, - completion_tokens: int, - cache_read_input_tokens: int = 0, - cache_creation_input_tokens: int = 0, -) -> TraceUsage: - """Helper function to create TraceUsage object with cost calculation.""" - prompt_cost, completion_cost = cost_per_token( - model=model_name, - prompt_tokens=prompt_tokens, - completion_tokens=completion_tokens, - cache_read_input_tokens=cache_read_input_tokens, - cache_creation_input_tokens=cache_creation_input_tokens, - ) - total_cost_usd = ( - (prompt_cost + completion_cost) if prompt_cost and completion_cost else None - ) - return TraceUsage( - prompt_tokens=prompt_tokens, - completion_tokens=completion_tokens, - total_tokens=prompt_tokens + completion_tokens, - cache_read_input_tokens=cache_read_input_tokens, - cache_creation_input_tokens=cache_creation_input_tokens, - prompt_tokens_cost_usd=prompt_cost, - completion_tokens_cost_usd=completion_cost, - total_cost_usd=total_cost_usd, - model_name=model_name, - ) - - -def combine_args_kwargs(func, args, kwargs): - """ - Combine positional arguments and keyword arguments into a single dictionary. - - Args: - func: The function being called - args: Tuple of positional arguments - kwargs: Dictionary of keyword arguments - - Returns: - A dictionary combining both args and kwargs - """ - try: - import inspect - - sig = inspect.signature(func) - param_names = list(sig.parameters.keys()) - - args_dict = {} - for i, arg in enumerate(args): - if i < len(param_names): - args_dict[param_names[i]] = arg - else: - args_dict[f"arg{i}"] = arg - - return {**args_dict, **kwargs} - except Exception: - # Fallback if signature inspection fails - return {**{f"arg{i}": arg for i, arg in enumerate(args)}, **kwargs} - - -def cost_per_token(*args, **kwargs): - try: - prompt_tokens_cost_usd_dollar, completion_tokens_cost_usd_dollar = ( - _original_cost_per_token(*args, **kwargs) - ) - if ( - prompt_tokens_cost_usd_dollar == 0 - and completion_tokens_cost_usd_dollar == 0 - ): - judgeval_logger.warning("LiteLLM returned a total of 0 for cost per token") - return prompt_tokens_cost_usd_dollar, completion_tokens_cost_usd_dollar - except Exception as e: - judgeval_logger.warning(f"Error calculating cost per token: {e}") - return None, None - - -# --- Helper function for instance-prefixed qual_name --- -def get_instance_prefixed_name(instance, class_name, class_identifiers): - """ - Returns the agent name (prefix) if the class and attribute are found in class_identifiers. - Otherwise, returns None. - """ - if class_name in class_identifiers: - class_config = class_identifiers[class_name] - attr = class_config.get("identifier") - if attr: - if hasattr(instance, attr) and not callable(getattr(instance, attr)): - instance_name = getattr(instance, attr) - return instance_name - else: - raise Exception( - f"Attribute {attr} does not exist for {class_name}. Check your agent() decorator." - ) - return None diff --git a/src/judgeval/common/tracer/otel_exporter.py b/src/judgeval/common/tracer/otel_exporter.py deleted file mode 100644 index 8df6b78d..00000000 --- a/src/judgeval/common/tracer/otel_exporter.py +++ /dev/null @@ -1,108 +0,0 @@ -""" -Custom OpenTelemetry exporter for Judgment API. - -This exporter sends spans to the Judgment API using the existing format. -The BatchSpanProcessor handles all batching, threading, and retry logic. -""" - -from __future__ import annotations - -from typing import Any, Dict, List, Sequence - -from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult -from opentelemetry.sdk.trace import ReadableSpan - -from judgeval.common.tracer.span_transformer import SpanTransformer -from judgeval.common.logger import judgeval_logger -from judgeval.common.api.api import JudgmentApiClient - - -class JudgmentAPISpanExporter(SpanExporter): - """ - Custom OpenTelemetry exporter that sends spans to Judgment API. - - This exporter is used by BatchSpanProcessor which handles all the - batching, threading, and retry logic for us. - """ - - def __init__( - self, - judgment_api_key: str, - organization_id: str, - ): - self.api_client = JudgmentApiClient(judgment_api_key, organization_id) - - def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult: - """ - Export spans to Judgment API. - - This method is called by BatchSpanProcessor with a batch of spans. - We send them synchronously since BatchSpanProcessor handles threading. - """ - if not spans: - return SpanExportResult.SUCCESS - - try: - spans_data = [] - eval_runs_data = [] - - for span in spans: - span_data = self._convert_span_to_judgment_format(span) - - if span.attributes and span.attributes.get("judgment.evaluation_run"): - eval_runs_data.append(span_data) - else: - spans_data.append(span_data) - - if spans_data: - self._send_spans_batch(spans_data) - - if eval_runs_data: - self._send_evaluation_runs_batch(eval_runs_data) - - return SpanExportResult.SUCCESS - - except Exception as e: - judgeval_logger.error(f"Error in JudgmentAPISpanExporter.export: {e}") - return SpanExportResult.FAILURE - - def _convert_span_to_judgment_format(self, span: ReadableSpan) -> Dict[str, Any]: - """Convert OpenTelemetry span to existing Judgment API format.""" - if span.attributes and span.attributes.get("judgment.evaluation_run"): - return SpanTransformer.otel_span_to_evaluation_run_format(span) - else: - return SpanTransformer.otel_span_to_judgment_format(span) - - def _send_spans_batch(self, spans: List[Dict[str, Any]]): - """Send a batch of spans to the spans endpoint.""" - spans_data = [span["data"] for span in spans] - self.api_client.send_spans_batch(spans_data) - - def _send_evaluation_runs_batch(self, eval_runs: List[Dict[str, Any]]): - """Send a batch of evaluation runs to the evaluation runs endpoint.""" - evaluation_entries = [] - for eval_run in eval_runs: - eval_data = eval_run["data"] - entry = { - "evaluation_run": { - key: value - for key, value in eval_data.items() - if key not in ["associated_span_id", "span_data", "queued_at"] - }, - "associated_span": { - "span_id": eval_data.get("associated_span_id"), - "span_data": eval_data.get("span_data"), - }, - "queued_at": eval_data.get("queued_at"), - } - evaluation_entries.append(entry) - - self.api_client.send_evaluation_runs_batch(evaluation_entries) - - def shutdown(self, timeout_millis: int = 30000) -> None: - """Shutdown the exporter.""" - pass - - def force_flush(self, timeout_millis: int = 30000) -> bool: - """Force flush any pending requests.""" - return True diff --git a/src/judgeval/common/tracer/otel_span_processor.py b/src/judgeval/common/tracer/otel_span_processor.py deleted file mode 100644 index 9b7ace55..00000000 --- a/src/judgeval/common/tracer/otel_span_processor.py +++ /dev/null @@ -1,188 +0,0 @@ -""" -Custom OpenTelemetry span processor for Judgment API. - -This processor uses BatchSpanProcessor to handle batching and export -of TraceSpan objects converted to OpenTelemetry format. -""" - -from __future__ import annotations - -import threading -from typing import Any, Dict, Optional - -from opentelemetry.context import Context -from opentelemetry.sdk.trace import ReadableSpan, Span -from opentelemetry.sdk.trace.export import BatchSpanProcessor, SpanProcessor -from opentelemetry.trace import Status, StatusCode, SpanContext, TraceFlags -from opentelemetry.trace.span import TraceState, INVALID_SPAN_CONTEXT - -from judgeval.common.logger import judgeval_logger -from judgeval.common.tracer.otel_exporter import JudgmentAPISpanExporter -from judgeval.common.tracer.span_processor import SpanProcessorBase -from judgeval.common.tracer.span_transformer import SpanTransformer -from judgeval.data import TraceSpan -from judgeval.data.evaluation_run import EvaluationRun - - -class SimpleReadableSpan(ReadableSpan): - """Simple ReadableSpan implementation that wraps TraceSpan data.""" - - def __init__(self, trace_span: TraceSpan, span_state: str = "completed"): - self._name = trace_span.function - self._span_id = trace_span.span_id - self._trace_id = trace_span.trace_id - - self._start_time = ( - int(trace_span.created_at * 1_000_000_000) - if trace_span.created_at - else None - ) - self._end_time: Optional[int] = None - - if ( - span_state == "completed" - and trace_span.duration is not None - and self._start_time is not None - ): - self._end_time = self._start_time + int(trace_span.duration * 1_000_000_000) - - self._status = ( - Status(StatusCode.ERROR) if trace_span.error else Status(StatusCode.OK) - ) - - self._attributes: Dict[str, Any] = ( - SpanTransformer.trace_span_to_otel_attributes(trace_span, span_state) - ) - - try: - trace_id_int = ( - int(trace_span.trace_id.replace("-", ""), 16) - if trace_span.trace_id - else 0 - ) - span_id_int = ( - int(trace_span.span_id.replace("-", ""), 16) - if trace_span.span_id - else 0 - ) - - self._context = SpanContext( - trace_id=trace_id_int, - span_id=span_id_int, - is_remote=False, - trace_flags=TraceFlags(0x01), - trace_state=TraceState(), - ) - except (ValueError, TypeError) as e: - judgeval_logger.warning(f"Failed to create proper SpanContext: {e}") - self._context = INVALID_SPAN_CONTEXT - - self._parent: Optional[SpanContext] = None - self._events: list[Any] = [] - self._links: list[Any] = [] - self._instrumentation_info: Optional[Any] = None - - -class JudgmentSpanProcessor(SpanProcessor, SpanProcessorBase): - """ - Span processor that converts TraceSpan objects to OpenTelemetry format - and uses BatchSpanProcessor for export. - """ - - def __init__( - self, - judgment_api_key: str, - organization_id: str, - batch_size: int = 50, - flush_interval: float = 1.0, - max_queue_size: int = 2048, - export_timeout: int = 30000, - ): - self.judgment_api_key = judgment_api_key - self.organization_id = organization_id - - self._span_cache: Dict[str, TraceSpan] = {} - self._span_states: Dict[str, str] = {} - self._cache_lock = threading.RLock() - - self.batch_processor = BatchSpanProcessor( - JudgmentAPISpanExporter( - judgment_api_key=judgment_api_key, - organization_id=organization_id, - ), - max_queue_size=max_queue_size, - schedule_delay_millis=int(flush_interval * 1000), - max_export_batch_size=batch_size, - export_timeout_millis=export_timeout, - ) - - def on_start(self, span: Span, parent_context: Optional[Context] = None) -> None: - self.batch_processor.on_start(span, parent_context) - - def on_end(self, span: ReadableSpan) -> None: - self.batch_processor.on_end(span) - - def queue_span_update(self, span: TraceSpan, span_state: str = "input") -> None: - if span_state == "completed": - span.set_update_id_to_ending_number() - else: - span.increment_update_id() - - with self._cache_lock: - span_id = span.span_id - - self._span_cache[span_id] = span - self._span_states[span_id] = span_state - - self._send_span_update(span, span_state) - - if span_state == "completed" or span_state == "error": - self._span_cache.pop(span_id, None) - self._span_states.pop(span_id, None) - - def _send_span_update(self, span: TraceSpan, span_state: str) -> None: - readable_span = SimpleReadableSpan(span, span_state) - self.batch_processor.on_end(readable_span) - - def flush_pending_spans(self) -> None: - with self._cache_lock: - if not self._span_cache: - return - - for span_id, span in self._span_cache.items(): - span_state = self._span_states.get(span_id, "input") - self._send_span_update(span, span_state) - - def queue_evaluation_run( - self, evaluation_run: EvaluationRun, span_id: str, span_data: TraceSpan - ) -> None: - attributes = SpanTransformer.evaluation_run_to_otel_attributes( - evaluation_run, span_id, span_data - ) - - readable_span = SimpleReadableSpan(span_data, "evaluation_run") - readable_span._attributes.update(attributes) - - self.batch_processor.on_end(readable_span) - - def shutdown(self) -> None: - try: - self.flush_pending_spans() - except Exception as e: - judgeval_logger.warning( - f"Error flushing pending spans during shutdown: {e}" - ) - - self.batch_processor.shutdown() - - with self._cache_lock: - self._span_cache.clear() - self._span_states.clear() - - def force_flush(self, timeout_millis: int = 30000) -> bool: - try: - self.flush_pending_spans() - except Exception as e: - judgeval_logger.warning(f"Error flushing pending spans: {e}") - - return self.batch_processor.force_flush(timeout_millis) diff --git a/src/judgeval/common/tracer/span_processor.py b/src/judgeval/common/tracer/span_processor.py deleted file mode 100644 index 8c4c859c..00000000 --- a/src/judgeval/common/tracer/span_processor.py +++ /dev/null @@ -1,37 +0,0 @@ -""" -Base class for span processors with default no-op implementations. - -This eliminates the need for optional typing and null checks. -When monitoring is disabled, we use this base class directly. -When monitoring is enabled, we use JudgmentSpanProcessor which overrides the methods. -""" - -from judgeval.data import TraceSpan -from judgeval.data.evaluation_run import EvaluationRun - - -class SpanProcessorBase: - """ - Base class for Judgment span processors with default no-op implementations. - - This eliminates the need for optional typing and null checks. - When monitoring is disabled, we use this base class directly. - When monitoring is enabled, we use JudgmentSpanProcessor which overrides the methods. - """ - - def queue_span_update(self, span: TraceSpan, span_state: str = "input") -> None: - pass - - def queue_evaluation_run( - self, evaluation_run: EvaluationRun, span_id: str, span_data: TraceSpan - ) -> None: - pass - - def flush_pending_spans(self) -> None: - pass - - def force_flush(self, timeout_millis: int = 30000) -> bool: - return True - - def shutdown(self) -> None: - pass diff --git a/src/judgeval/common/tracer/span_transformer.py b/src/judgeval/common/tracer/span_transformer.py deleted file mode 100644 index 751ca1e0..00000000 --- a/src/judgeval/common/tracer/span_transformer.py +++ /dev/null @@ -1,207 +0,0 @@ -from __future__ import annotations - -import time -import uuid -import orjson -from datetime import datetime, timezone -from typing import Any, Dict, Mapping, Optional, Union - -from opentelemetry.sdk.trace import ReadableSpan -from pydantic import BaseModel - -from judgeval.common.api.json_encoder import json_encoder -from judgeval.data import TraceSpan -from judgeval.data.evaluation_run import EvaluationRun - - -class SpanTransformer: - @staticmethod - def _needs_json_serialization(value: Any) -> bool: - """ - Check if the value needs JSON serialization. - Returns True if the value is complex and needs serialization. - """ - if value is None: - return False - - # Basic JSON-serializable types don't need serialization - if isinstance(value, (str, int, float, bool)): - return False - - complex_types = (dict, list, tuple, set, BaseModel) - if isinstance(value, complex_types): - return True - - try: - orjson.dumps(value) - return False - except (TypeError, ValueError): - return True - - @staticmethod - def _safe_deserialize(obj: Any) -> Any: - if not isinstance(obj, str): - return obj - try: - return orjson.loads(obj) - except (orjson.JSONDecodeError, TypeError): - return obj - - @staticmethod - def _format_timestamp(timestamp: Optional[Union[float, int, str]]) -> str: - if timestamp is None: - return datetime.now(timezone.utc).isoformat() - - if isinstance(timestamp, str): - return timestamp - - try: - dt = datetime.fromtimestamp(timestamp, tz=timezone.utc) - return dt.isoformat() - except (ValueError, OSError): - return datetime.now(timezone.utc).isoformat() - - @staticmethod - def trace_span_to_otel_attributes( - trace_span: TraceSpan, span_state: str = "completed" - ) -> Dict[str, Any]: - serialized_data = trace_span.model_dump() - attributes: Dict[str, Any] = {} - - for field_name, value in serialized_data.items(): - if value is None: - continue - - attr_name = f"judgment.{field_name}" - - if field_name == "created_at": - attributes[attr_name] = SpanTransformer._format_timestamp(value) - elif field_name == "expected_tools" and value: - attributes[attr_name] = json_encoder( - [tool.model_dump() for tool in trace_span.expected_tools] - ) - elif field_name == "usage" and value: - attributes[attr_name] = json_encoder(trace_span.usage) - elif SpanTransformer._needs_json_serialization(value): - attributes[attr_name] = json_encoder(value) - else: - attributes[attr_name] = value - - attributes["judgment.span_state"] = span_state - if not attributes.get("judgment.span_type"): - attributes["judgment.span_type"] = "span" - - return attributes - - @staticmethod - def otel_attributes_to_judgment_data( - attributes: Mapping[str, Any], - ) -> Dict[str, Any]: - judgment_data: Dict[str, Any] = {} - - for key, value in attributes.items(): - if not key.startswith("judgment."): - continue - - field_name = key[9:] - - if isinstance(value, str): - deserialized = SpanTransformer._safe_deserialize(value) - judgment_data[field_name] = deserialized - else: - judgment_data[field_name] = value - - return judgment_data - - @staticmethod - def otel_span_to_judgment_format(span: ReadableSpan) -> Dict[str, Any]: - attributes = span.attributes or {} - judgment_data = SpanTransformer.otel_attributes_to_judgment_data(attributes) - - duration = judgment_data.get("duration") - if duration is None and span.end_time and span.start_time: - duration = (span.end_time - span.start_time) / 1_000_000_000 - - span_id = judgment_data.get("span_id") or str(uuid.uuid4()) - trace_id = judgment_data.get("trace_id") or str(uuid.uuid4()) - - created_at = judgment_data.get("created_at") - if not created_at: - created_at = ( - span.start_time / 1_000_000_000 if span.start_time else time.time() - ) - - return { - "type": "span", - "data": { - "span_id": span_id, - "trace_id": trace_id, - "function": span.name, - "depth": judgment_data.get("depth", 0), - "created_at": SpanTransformer._format_timestamp(created_at), - "parent_span_id": judgment_data.get("parent_span_id"), - "span_type": judgment_data.get("span_type", "span"), - "inputs": judgment_data.get("inputs"), - "error": judgment_data.get("error"), - "output": judgment_data.get("output"), - "usage": judgment_data.get("usage"), - "duration": duration, - "expected_tools": judgment_data.get("expected_tools"), - "additional_metadata": judgment_data.get("additional_metadata"), - "has_evaluation": judgment_data.get("has_evaluation", False), - "agent_name": judgment_data.get("agent_name"), - "class_name": judgment_data.get("class_name"), - "state_before": judgment_data.get("state_before"), - "state_after": judgment_data.get("state_after"), - "update_id": judgment_data.get("update_id", 1), - "span_state": judgment_data.get("span_state", "completed"), - "queued_at": time.time(), - }, - } - - @staticmethod - def evaluation_run_to_otel_attributes( - evaluation_run: EvaluationRun, span_id: str, span_data: TraceSpan - ) -> Dict[str, Any]: - attributes = { - "judgment.evaluation_run": True, - "judgment.associated_span_id": span_id, - "judgment.span_data": json_encoder(span_data), - } - - eval_data = evaluation_run.model_dump() - for key, value in eval_data.items(): - if value is None: - continue - - attr_name = f"judgment.{key}" - if SpanTransformer._needs_json_serialization(value): - attributes[attr_name] = json_encoder(value) - else: - attributes[attr_name] = value - - return attributes - - @staticmethod - def otel_span_to_evaluation_run_format(span: ReadableSpan) -> Dict[str, Any]: - attributes = span.attributes or {} - judgment_data = SpanTransformer.otel_attributes_to_judgment_data(attributes) - - associated_span_id = judgment_data.get("associated_span_id") or str( - uuid.uuid4() - ) - - eval_run_data = { - key: value - for key, value in judgment_data.items() - if key not in ["associated_span_id", "span_data", "evaluation_run"] - } - - eval_run_data["associated_span_id"] = associated_span_id - eval_run_data["span_data"] = judgment_data.get("span_data") - eval_run_data["queued_at"] = time.time() - - return { - "type": "evaluation_run", - "data": eval_run_data, - } diff --git a/src/judgeval/common/tracer/trace_manager.py b/src/judgeval/common/tracer/trace_manager.py deleted file mode 100644 index b45d5a1c..00000000 --- a/src/judgeval/common/tracer/trace_manager.py +++ /dev/null @@ -1,101 +0,0 @@ -from __future__ import annotations -from typing import List, Optional, TYPE_CHECKING - -if TYPE_CHECKING: - from judgeval.common.tracer import Tracer - -from judgeval.common.logger import judgeval_logger -from judgeval.common.api import JudgmentApiClient -from rich import print as rprint - - -class TraceManagerClient: - """ - Client for handling trace endpoints with the Judgment API - - - Operations include: - - Fetching a trace by id - - Saving a trace - - Deleting a trace - """ - - def __init__( - self, - judgment_api_key: str, - organization_id: str, - tracer: Optional[Tracer] = None, - ): - self.api_client = JudgmentApiClient(judgment_api_key, organization_id) - self.tracer = tracer - - def fetch_trace(self, trace_id: str): - """ - Fetch a trace by its id - """ - return self.api_client.fetch_trace(trace_id) - - def upsert_trace( - self, - trace_data: dict, - offline_mode: bool = False, - show_link: bool = True, - final_save: bool = True, - ): - """ - Upserts a trace to the Judgment API (always overwrites if exists). - - Args: - trace_data: The trace data to upsert - offline_mode: Whether running in offline mode - show_link: Whether to show the UI link (for live tracing) - final_save: Whether this is the final save (controls S3 saving) - - Returns: - dict: Server response containing UI URL and other metadata - """ - - if self.tracer and self.tracer.use_s3 and final_save: - try: - s3_key = self.tracer.s3_storage.save_trace( - trace_data=trace_data, - trace_id=trace_data["trace_id"], - project_name=trace_data["project_name"], - ) - judgeval_logger.info(f"Trace also saved to S3 at key: {s3_key}") - except Exception as e: - judgeval_logger.warning(f"Failed to save trace to S3: {str(e)}") - - trace_data.pop("trace_spans", None) - trace_data.pop("evaluation_runs", None) - - server_response = self.api_client.upsert_trace(trace_data) - - if ( - not offline_mode - and show_link - and "ui_results_url" in server_response - and self.tracer.show_trace_urls - ): - pretty_str = f"\n🔍 You can view your trace data here: [rgb(106,0,255)][link={server_response['ui_results_url']}]View Trace[/link]\n" - rprint(pretty_str) - - return server_response - - def delete_trace(self, trace_id: str): - """ - Delete a trace from the database. - """ - return self.api_client.delete_trace(trace_id) - - def delete_traces(self, trace_ids: List[str]): - """ - Delete a batch of traces from the database. - """ - return self.api_client.delete_traces(trace_ids) - - def delete_project(self, project_name: str): - """ - Deletes a project from the server. Which also deletes all evaluations and traces associated with the project. - """ - return self.api_client.delete_project(project_name) diff --git a/src/judgeval/common/trainer/__init__.py b/src/judgeval/common/trainer/__init__.py deleted file mode 100644 index 9ca308aa..00000000 --- a/src/judgeval/common/trainer/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from .trainer import JudgmentTrainer -from .config import TrainerConfig, ModelConfig -from .trainable_model import TrainableModel - -__all__ = ["JudgmentTrainer", "TrainerConfig", "ModelConfig", "TrainableModel"] diff --git a/src/judgeval/common/utils.py b/src/judgeval/common/utils.py deleted file mode 100644 index 20394848..00000000 --- a/src/judgeval/common/utils.py +++ /dev/null @@ -1,948 +0,0 @@ -""" -This file contains utility functions used in repo scripts - -For API calling, we support: - - parallelized model calls on the same prompt - - batched model calls on different prompts - -NOTE: any function beginning with 'a', e.g. 'afetch_together_api_response', is an asynchronous function -""" - -# Standard library imports -import asyncio -import concurrent.futures -import os -from types import TracebackType -from judgeval.common.api.constants import ROOT_API -from judgeval.utils.requests import requests -import pprint -from typing import Any, Dict, List, Optional, TypeAlias, Union, TypeGuard - -# Third-party imports -import litellm -import pydantic -from dotenv import load_dotenv - -# Local application/library-specific imports -from judgeval.clients import async_together_client, together_client -from judgeval.constants import ( - ACCEPTABLE_MODELS, - MAX_WORKER_THREADS, - TOGETHER_SUPPORTED_MODELS, - LITELLM_SUPPORTED_MODELS, -) -from judgeval.common.logger import judgeval_logger - - -class CustomModelParameters(pydantic.BaseModel): - model_name: str - secret_key: str - litellm_base_url: str - - @pydantic.field_validator("model_name") - @classmethod - def validate_model_name(cls, v): - if not v: - raise ValueError("Model name cannot be empty") - return v - - @pydantic.field_validator("secret_key") - @classmethod - def validate_secret_key(cls, v): - if not v: - raise ValueError("Secret key cannot be empty") - return v - - @pydantic.field_validator("litellm_base_url") - @classmethod - def validate_litellm_base_url(cls, v): - if not v: - raise ValueError("Litellm base URL cannot be empty") - return v - - -class ChatCompletionRequest(pydantic.BaseModel): - model: str - messages: List[Dict[str, str]] - response_format: Optional[Union[pydantic.BaseModel, Dict[str, Any]]] = None - - @pydantic.field_validator("messages") - @classmethod - def validate_messages(cls, messages): - if not messages: - raise ValueError("Messages cannot be empty") - - for msg in messages: - if not isinstance(msg, dict): - raise TypeError("Message must be a dictionary") - if "role" not in msg: - raise ValueError("Message missing required 'role' field") - if "content" not in msg: - raise ValueError("Message missing required 'content' field") - if msg["role"] not in ["system", "user", "assistant"]: - raise ValueError( - f"Invalid role '{msg['role']}'. Must be 'system', 'user', or 'assistant'" - ) - - return messages - - @pydantic.field_validator("model") - @classmethod - def validate_model(cls, model): - if not model: - raise ValueError("Model cannot be empty") - if model not in ACCEPTABLE_MODELS: - raise ValueError(f"Model {model} is not in the list of supported models.") - return model - - @pydantic.field_validator("response_format", mode="before") - @classmethod - def validate_response_format(cls, response_format): - if response_format is not None: - if not isinstance(response_format, (dict, pydantic.BaseModel)): - raise TypeError( - "Response format must be a dictionary or pydantic model" - ) - # Optional: Add additional validation for required fields if needed - # For example, checking for 'type': 'json' in OpenAI's format - return response_format - - -os.environ["LITELLM_LOG"] = "DEBUG" - -load_dotenv() - - -def read_file(file_path: str) -> str: - with open(file_path, "r", encoding="utf-8") as file: - return file.read() - - -def validate_api_key(judgment_api_key: str): - """ - Validates that the user api key is valid - """ - response = requests.post( - f"{ROOT_API}/auth/validate_api_key/", - headers={ - "Content-Type": "application/json", - "Authorization": f"Bearer {judgment_api_key}", - }, - json={}, - verify=True, - ) - if response.status_code == 200: - return True, response.json() - else: - return False, response.json().get("detail", "Error validating API key") - - -def fetch_together_api_response( - model: str, - messages: List[Dict[str, str]], - response_format: Union[pydantic.BaseModel, None] = None, -) -> str: - """ - Fetches a single response from the Together API for a given model and messages. - """ - # Validate request - if messages is None or messages == []: - raise ValueError("Messages cannot be empty") - - request = ChatCompletionRequest( - model=model, messages=messages, response_format=response_format - ) - - if request.response_format is not None: - response = together_client.chat.completions.create( - model=request.model, - messages=request.messages, - response_format=request.response_format, - ) - else: - response = together_client.chat.completions.create( - model=request.model, - messages=request.messages, - ) - - return response.choices[0].message.content - - -async def afetch_together_api_response( - model: str, - messages: List[Dict], - response_format: Union[pydantic.BaseModel, None] = None, -) -> str: - """ - ASYNCHRONOUSLY Fetches a single response from the Together API for a given model and messages. - """ - request = ChatCompletionRequest( - model=model, messages=messages, response_format=response_format - ) - - if request.response_format is not None: - response = await async_together_client.chat.completions.create( - model=request.model, - messages=request.messages, - response_format=request.response_format, - ) - else: - response = await async_together_client.chat.completions.create( - model=request.model, - messages=request.messages, - ) - return response.choices[0].message.content - - -def query_together_api_multiple_calls( - models: List[str], - messages: List[List[Dict]], - response_formats: Union[List[Union[pydantic.BaseModel, None]], None] = None, -) -> List[Union[str, None]]: - """ - Queries the Together API for multiple calls in parallel - - Args: - models (List[str]): List of models to query - messages (List[List[Mapping]]): List of messages to query. Each inner object corresponds to a single prompt. - response_formats (List[pydantic.BaseModel], optional): A list of the format of the response if JSON forcing. Defaults to None. - - Returns: - List[str]: TogetherAI responses for each model and message pair in order. Any exceptions in the thread call result in a None. - """ - # Check for empty models list - if not models: - raise ValueError("Models list cannot be empty") - - # Validate all models are supported - for model in models: - if model not in ACCEPTABLE_MODELS: - raise ValueError( - f"Model {model} is not in the list of supported models: {ACCEPTABLE_MODELS}." - ) - - # Validate input lengths match - if response_formats is None: - response_formats = [None] * len(models) - if not (len(models) == len(messages) == len(response_formats)): - raise ValueError( - "Number of models, messages, and response formats must be the same" - ) - - # Validate message format - validate_batched_chat_messages(messages) - - num_workers = int(os.getenv("NUM_WORKER_THREADS", MAX_WORKER_THREADS)) - # Initialize results to maintain ordered outputs - out: List[Union[str, None]] = [None] * len(messages) - with concurrent.futures.ThreadPoolExecutor(max_workers=num_workers) as executor: - # Submit all queries to together API with index, gets back the response content - futures = { - executor.submit( - fetch_together_api_response, model, message, response_format - ): idx - for idx, (model, message, response_format) in enumerate( - zip(models, messages, response_formats) - ) - } - - # Collect results as they complete -- result is response content - for future in concurrent.futures.as_completed(futures): - idx = futures[future] - try: - out[idx] = future.result() - except Exception as e: - judgeval_logger.error(f"Error in parallel call {idx}: {str(e)}") - out[idx] = None - return out - - -async def aquery_together_api_multiple_calls( - models: List[str], - messages: List[List[Dict]], - response_formats: Union[List[Union[pydantic.BaseModel, None]], None] = None, -) -> List[Union[str, None]]: - """ - Queries the Together API for multiple calls in parallel - - Args: - models (List[str]): List of models to query - messages (List[List[Mapping]]): List of messages to query. Each inner object corresponds to a single prompt. - response_formats (List[pydantic.BaseModel], optional): A list of the format of the response if JSON forcing. Defaults to None. - - Returns: - List[str]: TogetherAI responses for each model and message pair in order. Any exceptions in the thread call result in a None. - """ - # Check for empty models list - if not models: - raise ValueError("Models list cannot be empty") - - # Validate all models are supported - for model in models: - if model not in ACCEPTABLE_MODELS: - raise ValueError( - f"Model {model} is not in the list of supported models: {ACCEPTABLE_MODELS}." - ) - - # Validate input lengths match - if response_formats is None: - response_formats = [None] * len(models) - if not (len(models) == len(messages) == len(response_formats)): - raise ValueError( - "Number of models, messages, and response formats must be the same" - ) - - # Validate message format - validate_batched_chat_messages(messages) - - out: List[Union[str, None]] = [None] * len(messages) - - async def fetch_and_store(idx, model, message, response_format): - try: - out[idx] = await afetch_together_api_response( - model, message, response_format - ) - except Exception as e: - judgeval_logger.error(f"Error in parallel call {idx}: {str(e)}") - out[idx] = None - - tasks = [ - fetch_and_store(idx, model, message, response_format) - for idx, (model, message, response_format) in enumerate( - zip(models, messages, response_formats) - ) - ] - - await asyncio.gather(*tasks) - return out - - -def fetch_litellm_api_response( - model: str, - messages: List[Dict[str, str]], - response_format: Union[pydantic.BaseModel, None] = None, -) -> str: - """ - Fetches a single response from the Litellm API for a given model and messages. - """ - request = ChatCompletionRequest( - model=model, messages=messages, response_format=response_format - ) - - if request.response_format is not None: - response = litellm.completion( - model=request.model, - messages=request.messages, - response_format=request.response_format, - ) - else: - response = litellm.completion( - model=request.model, - messages=request.messages, - ) - return response.choices[0].message.content - - -def fetch_custom_litellm_api_response( - custom_model_parameters: CustomModelParameters, - messages: List[Dict[str, str]], - response_format: Union[pydantic.BaseModel, None] = None, -) -> str: - if messages is None or messages == []: - raise ValueError("Messages cannot be empty") - - if custom_model_parameters is None: - raise ValueError("Custom model parameters cannot be empty") - - if not isinstance(custom_model_parameters, CustomModelParameters): - raise ValueError( - "Custom model parameters must be a CustomModelParameters object" - ) - - if response_format is not None: - response = litellm.completion( - model=custom_model_parameters.model_name, - messages=messages, - api_key=custom_model_parameters.secret_key, - base_url=custom_model_parameters.litellm_base_url, - response_format=response_format, - ) - else: - response = litellm.completion( - model=custom_model_parameters.model_name, - messages=messages, - api_key=custom_model_parameters.secret_key, - base_url=custom_model_parameters.litellm_base_url, - ) - return response.choices[0].message.content - - -async def afetch_litellm_api_response( - model: str, - messages: List[Dict[str, str]], - response_format: Union[pydantic.BaseModel, None] = None, -) -> str: - """ - ASYNCHRONOUSLY Fetches a single response from the Litellm API for a given model and messages. - """ - if messages is None or messages == []: - raise ValueError("Messages cannot be empty") - - # Add validation - validate_chat_messages(messages) - - if model not in ACCEPTABLE_MODELS: - raise ValueError( - f"Model {model} is not in the list of supported models: {ACCEPTABLE_MODELS}." - ) - - if response_format is not None: - response = await litellm.acompletion( - model=model, messages=messages, response_format=response_format - ) - else: - response = await litellm.acompletion( - model=model, - messages=messages, - ) - return response.choices[0].message.content - - -async def afetch_custom_litellm_api_response( - custom_model_parameters: CustomModelParameters, - messages: List[Dict[str, str]], - response_format: Union[pydantic.BaseModel, None] = None, -) -> str: - """ - ASYNCHRONOUSLY Fetches a single response from the Litellm API for a given model and messages. - """ - if messages is None or messages == []: - raise ValueError("Messages cannot be empty") - - if custom_model_parameters is None: - raise ValueError("Custom model parameters cannot be empty") - - if not isinstance(custom_model_parameters, CustomModelParameters): - raise ValueError( - "Custom model parameters must be a CustomModelParameters object" - ) - - if response_format is not None: - response = await litellm.acompletion( - model=custom_model_parameters.model_name, - messages=messages, - api_key=custom_model_parameters.secret_key, - base_url=custom_model_parameters.litellm_base_url, - response_format=response_format, - ) - else: - response = await litellm.acompletion( - model=custom_model_parameters.model_name, - messages=messages, - api_key=custom_model_parameters.secret_key, - base_url=custom_model_parameters.litellm_base_url, - ) - return response.choices[0].message.content - - -def query_litellm_api_multiple_calls( - models: List[str], - messages: List[List[Dict]], - response_formats: Union[List[Union[pydantic.BaseModel, None]], None] = None, -) -> List[Union[str, None]]: - """ - Queries the Litellm API for multiple calls in parallel - - Args: - models (List[str]): List of models to query - messages (List[List[Mapping]]): List of messages to query - response_formats (List[pydantic.BaseModel], optional): A list of the format of the response if JSON forcing. Defaults to None. - - Returns: - List[str]: Litellm responses for each model and message pair in order. Any exceptions in the thread call result in a None. - """ - num_workers = int(os.getenv("NUM_WORKER_THREADS", MAX_WORKER_THREADS)) - # Initialize results to maintain ordered outputs - out: List[Union[str, None]] = [None] * len(messages) - with concurrent.futures.ThreadPoolExecutor(max_workers=num_workers) as executor: - # Submit all queries to Litellm API with index, gets back the response content - futures = { - executor.submit( - fetch_litellm_api_response, model, message, response_format - ): idx - for idx, (model, message, response_format) in enumerate( - zip(models, messages, response_formats or [None] * len(messages)) - ) - } - - # Collect results as they complete -- result is response content - for future in concurrent.futures.as_completed(futures): - idx = futures[future] - try: - out[idx] = future.result() - except Exception as e: - judgeval_logger.error(f"Error in parallel call {idx}: {str(e)}") - out[idx] = None - return out - - -async def aquery_litellm_api_multiple_calls( - models: List[str], - messages: List[List[Dict[str, str]]], - response_formats: Union[List[Union[pydantic.BaseModel, None]], None] = None, -) -> List[Union[str, None]]: - """ - Queries the Litellm API for multiple calls in parallel - - Args: - models (List[str]): List of models to query - messages (List[List[Mapping]]): List of messages to query - response_formats (List[pydantic.BaseModel], optional): A list of the format of the response if JSON forcing. Defaults to None. - - Returns: - List[str]: Litellm responses for each model and message pair in order. Any exceptions in the thread call result in a None. - """ - # Initialize results to maintain ordered outputs - out: List[Union[str, None]] = [None] * len(messages) - - async def fetch_and_store(idx, model, message, response_format): - try: - out[idx] = await afetch_litellm_api_response( - model, message, response_format - ) - except Exception as e: - judgeval_logger.error(f"Error in parallel call {idx}: {str(e)}") - out[idx] = None - - tasks = [ - fetch_and_store(idx, model, message, response_format) - for idx, (model, message, response_format) in enumerate( - zip(models, messages, response_formats or [None] * len(messages)) - ) - ] - - await asyncio.gather(*tasks) - return out - - -def validate_chat_messages(messages, batched: bool = False): - """Validate chat message format before API call""" - if not isinstance(messages, list): - raise TypeError("Messages must be a list") - - for msg in messages: - if not isinstance(msg, dict): - if batched and not isinstance(msg, list): - raise TypeError("Each message must be a list") - elif not batched: - raise TypeError("Message must be a dictionary") - if "role" not in msg: - raise ValueError("Message missing required 'role' field") - if "content" not in msg: - raise ValueError("Message missing required 'content' field") - if msg["role"] not in ["system", "user", "assistant"]: - raise ValueError( - f"Invalid role '{msg['role']}'. Must be 'system', 'user', or 'assistant'" - ) - - -def validate_batched_chat_messages(messages): - """ - Validate format of batched chat messages before API call - - Args: - messages (List[List[Mapping]]): List of message lists, where each inner list contains - message dictionaries with 'role' and 'content' fields - - Raises: - TypeError: If messages format is invalid - ValueError: If message content is invalid - """ - if not isinstance(messages, list): - raise TypeError("Batched messages must be a list") - - if not messages: - raise ValueError("Batched messages cannot be empty") - - for message_list in messages: - if not isinstance(message_list, list): - raise TypeError("Each batch item must be a list of messages") - - # Validate individual messages using existing function - validate_chat_messages(message_list) - - -def is_batched_messages( - messages: Union[List[Dict[str, str]], List[List[Dict[str, str]]]], -) -> TypeGuard[List[List[Dict[str, str]]]]: - return isinstance(messages, list) and all(isinstance(msg, list) for msg in messages) - - -def is_simple_messages( - messages: Union[List[Dict[str, str]], List[List[Dict[str, str]]]], -) -> TypeGuard[List[Dict[str, str]]]: - return isinstance(messages, list) and all( - not isinstance(msg, list) for msg in messages - ) - - -def get_chat_completion( - model_type: str, - messages: Union[List[Dict[str, str]], List[List[Dict[str, str]]]], - response_format: Union[pydantic.BaseModel, None] = None, - batched: bool = False, -) -> Union[str, List[Union[str, None]]]: - """ - Generates chat completions using a single model and potentially several messages. Supports closed-source and OSS models. - - Parameters: - - model_type (str): The type of model to use for generating completions. - - messages (Union[List[Mapping], List[List[Mapping]]]): The messages to be used for generating completions. - If batched is True, this should be a list of lists of mappings. - - response_format (pydantic.BaseModel, optional): The format of the response. Defaults to None. - - batched (bool, optional): Whether to process messages in batch mode. Defaults to False. - Returns: - - str: The generated chat completion(s). If batched is True, returns a list of strings. - Raises: - - ValueError: If requested model is not supported by Litellm or TogetherAI. - """ - - # Check for empty messages list - if not messages or messages == []: - raise ValueError("Messages cannot be empty") - - # Add validation - if batched: - validate_batched_chat_messages(messages) - else: - validate_chat_messages(messages) - - if ( - batched - and is_batched_messages(messages) - and model_type in TOGETHER_SUPPORTED_MODELS - ): - return query_together_api_multiple_calls( - models=[model_type] * len(messages), - messages=messages, - response_formats=[response_format] * len(messages), - ) - elif ( - batched - and is_batched_messages(messages) - and model_type in LITELLM_SUPPORTED_MODELS - ): - return query_litellm_api_multiple_calls( - models=[model_type] * len(messages), - messages=messages, - response_formats=[response_format] * len(messages), - ) - elif ( - not batched - and is_simple_messages(messages) - and model_type in TOGETHER_SUPPORTED_MODELS - ): - return fetch_together_api_response( - model=model_type, messages=messages, response_format=response_format - ) - elif ( - not batched - and is_simple_messages(messages) - and model_type in LITELLM_SUPPORTED_MODELS - ): - return fetch_litellm_api_response( - model=model_type, messages=messages, response_format=response_format - ) - - raise ValueError( - f"Model {model_type} is not supported by Litellm or TogetherAI for chat completions. Please check the model name and try again." - ) - - -async def aget_chat_completion( - model_type: str, - messages: Union[List[Dict[str, str]], List[List[Dict[str, str]]]], - response_format: Union[pydantic.BaseModel, None] = None, - batched: bool = False, -) -> Union[str, List[Union[str, None]]]: - """ - ASYNCHRONOUSLY generates chat completions using a single model and potentially several messages. Supports closed-source and OSS models. - - Parameters: - - model_type (str): The type of model to use for generating completions. - - messages (Union[List[Mapping], List[List[Mapping]]]): The messages to be used for generating completions. - If batched is True, this should be a list of lists of mappings. - - response_format (pydantic.BaseModel, optional): The format of the response. Defaults to None. - - batched (bool, optional): Whether to process messages in batch mode. Defaults to False. - Returns: - - str: The generated chat completion(s). If batched is True, returns a list of strings. - Raises: - - ValueError: If requested model is not supported by Litellm or TogetherAI. - """ - - if batched: - validate_batched_chat_messages(messages) - else: - validate_chat_messages(messages) - - if ( - batched - and is_batched_messages(messages) - and model_type in TOGETHER_SUPPORTED_MODELS - ): - return await aquery_together_api_multiple_calls( - models=[model_type] * len(messages), - messages=messages, - response_formats=[response_format] * len(messages), - ) - elif ( - batched - and is_batched_messages(messages) - and model_type in LITELLM_SUPPORTED_MODELS - ): - return await aquery_litellm_api_multiple_calls( - models=[model_type] * len(messages), - messages=messages, - response_formats=[response_format] * len(messages), - ) - elif ( - not batched - and is_simple_messages(messages) - and model_type in TOGETHER_SUPPORTED_MODELS - ): - return await afetch_together_api_response( - model=model_type, messages=messages, response_format=response_format - ) - elif ( - not batched - and is_simple_messages(messages) - and model_type in LITELLM_SUPPORTED_MODELS - ): - return await afetch_litellm_api_response( - model=model_type, messages=messages, response_format=response_format - ) - - judgeval_logger.error(f"Model {model_type} not supported by either API") - raise ValueError( - f"Model {model_type} is not supported by Litellm or TogetherAI for chat completions. Please check the model name and try again." - ) - - -def get_completion_multiple_models( - models: List[str], - messages: List[List[Dict[str, str]]], - response_formats: Union[List[Union[pydantic.BaseModel, None]], None] = None, -) -> List[Union[str, None]]: - """ - Retrieves completions for a single prompt from multiple models in parallel. Supports closed-source and OSS models. - - Args: - models (List[str]): List of models to query - messages (List[List[Mapping]]): List of messages to query. Each inner object corresponds to a single prompt. - response_formats (List[pydantic.BaseModel], optional): A list of the format of the response if JSON forcing. Defaults to None. - - Returns: - List[str]: List of completions from the models in the order of the input models - Raises: - ValueError: If a model is not supported by Litellm or Together - """ - - if models is None or models == []: - raise ValueError("Models list cannot be empty") - - validate_batched_chat_messages(messages) - - if len(models) != len(messages): - judgeval_logger.error( - f"Model/message count mismatch: {len(models)} vs {len(messages)}" - ) - raise ValueError( - f"Number of models and messages must be the same: {len(models)} != {len(messages)}" - ) - if response_formats is None: - response_formats = [None] * len(models) - # Partition the model requests into TogetherAI and Litellm models, but keep the ordering saved - together_calls, litellm_calls = {}, {} # index -> model, message, response_format - together_responses, litellm_responses = [], [] - for idx, (model, message, r_format) in enumerate( - zip(models, messages, response_formats) - ): - if model in TOGETHER_SUPPORTED_MODELS: - together_calls[idx] = (model, message, r_format) - elif model in LITELLM_SUPPORTED_MODELS: - litellm_calls[idx] = (model, message, r_format) - else: - judgeval_logger.error(f"Model {model} not supported by either API") - raise ValueError( - f"Model {model} is not supported by Litellm or TogetherAI for chat completions. Please check the model name and try again." - ) - - # Add validation before processing - for msg_list in messages: - validate_chat_messages(msg_list) - - # Get the responses from the TogetherAI models - # List of responses from the TogetherAI models in order of the together_calls dict - if together_calls: - together_responses = query_together_api_multiple_calls( - models=[model for model, _, _ in together_calls.values()], - messages=[message for _, message, _ in together_calls.values()], - response_formats=[format for _, _, format in together_calls.values()], - ) - - # Get the responses from the Litellm models - if litellm_calls: - litellm_responses = query_litellm_api_multiple_calls( - models=[model for model, _, _ in litellm_calls.values()], - messages=[message for _, message, _ in litellm_calls.values()], - response_formats=[format for _, _, format in litellm_calls.values()], - ) - - # Merge the responses in the order of the original models - out: List[Union[str, None]] = [None] * len(models) - for idx, (model, message, r_format) in together_calls.items(): - out[idx] = together_responses.pop(0) - for idx, (model, message, r_format) in litellm_calls.items(): - out[idx] = litellm_responses.pop(0) - return out - - -async def aget_completion_multiple_models( - models: List[str], - messages: List[List[Dict[str, str]]], - response_formats: Union[List[Union[pydantic.BaseModel, None]], None] = None, -) -> List[Union[str, None]]: - """ - ASYNCHRONOUSLY retrieves completions for a single prompt from multiple models in parallel. Supports closed-source and OSS models. - - Args: - models (List[str]): List of models to query - messages (List[List[Mapping]]): List of messages to query. Each inner object corresponds to a single prompt. - response_formats (List[pydantic.BaseModel], optional): A list of the format of the response if JSON forcing. Defaults to None. - - Returns: - List[str]: List of completions from the models in the order of the input models - Raises: - ValueError: If a model is not supported by Litellm or Together - """ - if models is None or models == []: - raise ValueError("Models list cannot be empty") - - if len(models) != len(messages): - raise ValueError( - f"Number of models and messages must be the same: {len(models)} != {len(messages)}" - ) - if response_formats is None: - response_formats = [None] * len(models) - - validate_batched_chat_messages(messages) - - # Partition the model requests into TogetherAI and Litellm models, but keep the ordering saved - together_calls, litellm_calls = {}, {} # index -> model, message, response_format - together_responses, litellm_responses = [], [] - for idx, (model, message, r_format) in enumerate( - zip(models, messages, response_formats) - ): - if model in TOGETHER_SUPPORTED_MODELS: - together_calls[idx] = (model, message, r_format) - elif model in LITELLM_SUPPORTED_MODELS: - litellm_calls[idx] = (model, message, r_format) - else: - raise ValueError( - f"Model {model} is not supported by Litellm or TogetherAI for chat completions. Please check the model name and try again." - ) - - # Add validation before processing - for msg_list in messages: - validate_chat_messages(msg_list) - - # Get the responses from the TogetherAI models - # List of responses from the TogetherAI models in order of the together_calls dict - if together_calls: - together_responses = await aquery_together_api_multiple_calls( - models=[model for model, _, _ in together_calls.values()], - messages=[message for _, message, _ in together_calls.values()], - response_formats=[format for _, _, format in together_calls.values()], - ) - - # Get the responses from the Litellm models - if litellm_calls: - litellm_responses = await aquery_litellm_api_multiple_calls( - models=[model for model, _, _ in litellm_calls.values()], - messages=[message for _, message, _ in litellm_calls.values()], - response_formats=[format for _, _, format in litellm_calls.values()], - ) - - # Merge the responses in the order of the original models - out: List[Union[str, None]] = [None] * len(models) - for idx, (model, message, r_format) in together_calls.items(): - out[idx] = together_responses.pop(0) - for idx, (model, message, r_format) in litellm_calls.items(): - out[idx] = litellm_responses.pop(0) - return out - - -if __name__ == "__main__": - batched_messages: List[List[Dict[str, str]]] = [ - [ - {"role": "system", "content": "You are a helpful assistant."}, - {"role": "user", "content": "What is the capital of France?"}, - ], - [ - {"role": "system", "content": "You are a helpful assistant."}, - {"role": "user", "content": "What is the capital of Japan?"}, - ], - ] - - non_batched_messages: List[Dict[str, str]] = [ - {"role": "system", "content": "You are a helpful assistant."}, - {"role": "user", "content": "What is the capital of France?"}, - ] - - batched_messages_2: List[List[Dict[str, str]]] = [ - [ - {"role": "system", "content": "You are a helpful assistant."}, - {"role": "user", "content": "What is the capital of China?"}, - ], - [ - {"role": "system", "content": "You are a helpful assistant."}, - {"role": "user", "content": "What is the capital of France?"}, - ], - [ - {"role": "system", "content": "You are a helpful assistant."}, - {"role": "user", "content": "What is the capital of Japan?"}, - ], - ] - - # Batched - pprint.pprint( - get_chat_completion( - model_type="LLAMA3_405B_INSTRUCT_TURBO", - messages=batched_messages, - batched=True, - ) - ) - - # Non batched - pprint.pprint( - get_chat_completion( - model_type="LLAMA3_8B_INSTRUCT_TURBO", - messages=non_batched_messages, - batched=False, - ) - ) - - # Batched single completion to multiple models - pprint.pprint( - get_completion_multiple_models( - models=[ - "LLAMA3_70B_INSTRUCT_TURBO", - "LLAMA3_405B_INSTRUCT_TURBO", - "gpt-4.1-mini", - ], - messages=batched_messages_2, - ) - ) - -ExcInfo: TypeAlias = tuple[type[BaseException], BaseException, TracebackType] -OptExcInfo: TypeAlias = Union[ExcInfo, tuple[None, None, None]] diff --git a/src/judgeval/constants.py b/src/judgeval/constants.py index f9e994a0..d05a8130 100644 --- a/src/judgeval/constants.py +++ b/src/judgeval/constants.py @@ -1,10 +1,8 @@ -""" -Constant variables used throughout source code -""" +from __future__ import annotations from enum import Enum +from typing import Set import litellm -import os class APIScorerType(str, Enum): @@ -28,25 +26,22 @@ class APIScorerType(str, Enum): CUSTOM = "Custom" @classmethod - def _missing_(cls, value): + def __missing__(cls, value: str) -> APIScorerType: # Handle case-insensitive lookup for member in cls: if member.value == value.lower(): return member + raise ValueError(f"Invalid scorer type: {value}") -UNBOUNDED_SCORERS: set[APIScorerType] = ( + +UNBOUNDED_SCORERS: Set[APIScorerType] = ( set() ) # scorers whose scores are not bounded between 0-1 -# RabbitMQ -RABBITMQ_HOST = os.getenv( - "RABBITMQ_HOST", "rabbitmq-networklb-faa155df16ec9085.elb.us-west-1.amazonaws.com" -) -RABBITMQ_PORT = os.getenv("RABBITMQ_PORT", 5672) -RABBITMQ_QUEUE = os.getenv("RABBITMQ_QUEUE", "task_queue") -# Models -LITELLM_SUPPORTED_MODELS = set(litellm.model_list) + +LITELLM_SUPPORTED_MODELS: Set[str] = set(litellm.model_list) + TOGETHER_SUPPORTED_MODELS = [ "meta-llama/Meta-Llama-3-70B-Instruct-Turbo", @@ -104,20 +99,8 @@ def _missing_(cls, value): "mistralai/Mistral-7B-Instruct-v0.1", ] -DEFAULT_TOGETHER_MODEL = "meta-llama/Meta-Llama-3-8B-Instruct-Lite" -DEFAULT_GPT_MODEL = "gpt-4.1" - JUDGMENT_SUPPORTED_MODELS = {"osiris-large", "osiris-mini", "osiris"} ACCEPTABLE_MODELS = ( set(litellm.model_list) | set(TOGETHER_SUPPORTED_MODELS) | JUDGMENT_SUPPORTED_MODELS ) - -## System settings -MAX_WORKER_THREADS = 10 - -# Maximum number of concurrent operations for evaluation runs -MAX_CONCURRENT_EVALUATIONS = 50 # Adjust based on system capabilities - -# Span lifecycle management -SPAN_LIFECYCLE_END_UPDATE_ID = 20 # Default ending number for completed spans diff --git a/src/judgeval/data/evaluation_run.py b/src/judgeval/data/evaluation_run.py index 1a242c42..65410984 100644 --- a/src/judgeval/data/evaluation_run.py +++ b/src/judgeval/data/evaluation_run.py @@ -6,7 +6,7 @@ from judgeval.data import Example from judgeval.scorers import BaseScorer, APIScorerConfig from judgeval.constants import ACCEPTABLE_MODELS -from judgeval.data.judgment_types import EvaluationRunJudgmentType +from judgeval.data.judgment_types import EvaluationRun as EvaluationRunJudgmentType class EvaluationRun(EvaluationRunJudgmentType): diff --git a/src/judgeval/data/example.py b/src/judgeval/data/example.py index eb1fbee3..4f502a39 100644 --- a/src/judgeval/data/example.py +++ b/src/judgeval/data/example.py @@ -5,7 +5,7 @@ from enum import Enum from datetime import datetime from typing import Dict, Any, Optional -from judgeval.data.judgment_types import ExampleJudgmentType +from judgeval.data.judgment_types import Example as JudgmentExample class ExampleParams(str, Enum): @@ -19,7 +19,7 @@ class ExampleParams(str, Enum): ADDITIONAL_METADATA = "additional_metadata" -class Example(ExampleJudgmentType): +class Example(JudgmentExample): example_id: str = "" created_at: str = datetime.now().isoformat() name: Optional[str] = None diff --git a/src/judgeval/data/judgment_types.py b/src/judgeval/data/judgment_types.py index d2fde83e..668471a2 100644 --- a/src/judgeval/data/judgment_types.py +++ b/src/judgeval/data/judgment_types.py @@ -1,37 +1,100 @@ # generated by datamodel-codegen: -# filename: openapi_new.json -# timestamp: 2025-08-08T18:50:51+00:00 +# filename: .openapi.json +# timestamp: 2025-08-18T18:13:53+00:00 from __future__ import annotations - from typing import Annotated, Any, Dict, List, Optional, Union - from pydantic import BaseModel, ConfigDict, Field +from datetime import datetime -class ValidationErrorJudgmentType(BaseModel): - loc: Annotated[List[Union[str, int]], Field(title="Location")] - msg: Annotated[str, Field(title="Message")] - type: Annotated[str, Field(title="Error Type")] +class EvalResultsFetch(BaseModel): + experiment_run_id: Annotated[str, Field(title="Experiment Run Id")] + project_name: Annotated[str, Field(title="Project Name")] + + +class DatasetFetch(BaseModel): + dataset_alias: Annotated[str, Field(title="Dataset Alias")] + project_name: Annotated[str, Field(title="Project Name")] + + +class DatasetFetchStatsByProject(BaseModel): + project_name: Annotated[str, Field(title="Project Name")] + + +class TraceSave(BaseModel): + project_name: Annotated[str, Field(title="Project Name")] + trace_id: Annotated[str, Field(title="Trace Id")] + name: Annotated[str, Field(title="Name")] + created_at: Annotated[str, Field(title="Created At")] + duration: Annotated[float, Field(title="Duration")] + offline_mode: Annotated[Optional[bool], Field(title="Offline Mode")] = False + has_notification: Annotated[Optional[bool], Field(title="Has Notification")] = False + customer_id: Annotated[Optional[str], Field(title="Customer Id")] = None + tags: Annotated[Optional[List[str]], Field(title="Tags")] = None + metadata: Annotated[Optional[Dict[str, Any]], Field(title="Metadata")] = None + update_id: Annotated[Optional[int], Field(title="Update Id")] = 1 + + +class TraceFetch(BaseModel): + trace_id: Annotated[str, Field(title="Trace Id")] + + +class TraceAddToDataset(BaseModel): + trace_id: Annotated[str, Field(title="Trace Id")] + trace_span_id: Annotated[str, Field(title="Trace Span Id")] + dataset_alias: Annotated[str, Field(title="Dataset Alias")] + project_name: Annotated[str, Field(title="Project Name")] + + +class EvaluationRunsBatchRequest(BaseModel): + organization_id: Annotated[str, Field(title="Organization Id")] + evaluation_entries: Annotated[ + List[Dict[str, Any]], Field(title="Evaluation Entries") + ] + + +class ProjectAdd(BaseModel): + project_name: Annotated[str, Field(title="Project Name")] + + +class ProjectAddResponse(BaseModel): + project_id: Annotated[str, Field(title="Project Id")] + + +class ScorerExistsRequest(BaseModel): + name: Annotated[str, Field(title="Name")] + + +class ScorerExistsResponse(BaseModel): + exists: Annotated[bool, Field(title="Exists")] -class ScorerDataJudgmentType(BaseModel): +class SavePromptScorerRequest(BaseModel): name: Annotated[str, Field(title="Name")] + prompt: Annotated[str, Field(title="Prompt")] threshold: Annotated[float, Field(title="Threshold")] - success: Annotated[bool, Field(title="Success")] - score: Annotated[Optional[float], Field(title="Score")] = None - reason: Annotated[Optional[str], Field(title="Reason")] = None - strict_mode: Annotated[Optional[bool], Field(title="Strict Mode")] = None - evaluation_model: Annotated[ - Optional[Union[List[str], str]], Field(title="Evaluation Model") - ] = None - error: Annotated[Optional[str], Field(title="Error")] = None - additional_metadata: Annotated[ - Optional[Dict[str, Any]], Field(title="Additional Metadata") - ] = None + options: Annotated[Optional[Dict[str, float]], Field(title="Options")] = None + + +class SavePromptScorerResponse(BaseModel): + message: Annotated[str, Field(title="Message")] + name: Annotated[str, Field(title="Name")] + + +class FetchPromptScorerRequest(BaseModel): + name: Annotated[str, Field(title="Name")] + + +class ResolveProjectNameRequest(BaseModel): + project_name: Annotated[str, Field(title="Project Name")] -class ExampleJudgmentType(BaseModel): +class ResolveProjectNameResponse(BaseModel): + project_id: Annotated[str, Field(title="Project Id")] + + +class Example(BaseModel): model_config = ConfigDict( extra="allow", ) @@ -40,18 +103,7 @@ class ExampleJudgmentType(BaseModel): name: Annotated[Optional[str], Field(title="Name")] = None -class ScorerConfigJudgmentType(BaseModel): - score_type: Annotated[str, Field(title="Score Type")] - name: Annotated[Optional[str], Field(title="Name")] = None - threshold: Annotated[Optional[float], Field(title="Threshold")] = 0.5 - strict_mode: Annotated[Optional[bool], Field(title="Strict Mode")] = False - required_params: Annotated[Optional[List[str]], Field(title="Required Params")] = ( - Field(default_factory=list) - ) - kwargs: Annotated[Optional[Dict[str, Any]], Field(title="Kwargs")] = None - - -class BaseScorerJudgmentType(BaseModel): +class BaseScorer(BaseModel): score_type: Annotated[str, Field(title="Score Type")] threshold: Annotated[Optional[float], Field(title="Threshold")] = 0.5 name: Annotated[Optional[str], Field(title="Name")] = None @@ -76,7 +128,78 @@ class BaseScorerJudgmentType(BaseModel): server_hosted: Annotated[Optional[bool], Field(title="Server Hosted")] = False -class TraceUsageJudgmentType(BaseModel): +class ScorerConfig(BaseModel): + score_type: Annotated[str, Field(title="Score Type")] + name: Annotated[Optional[str], Field(title="Name")] = None + threshold: Annotated[Optional[float], Field(title="Threshold")] = 0.5 + strict_mode: Annotated[Optional[bool], Field(title="Strict Mode")] = False + required_params: Annotated[Optional[List[str]], Field(title="Required Params")] = [] + kwargs: Annotated[Optional[Dict[str, Any]], Field(title="Kwargs")] = None + + +class ValidationError(BaseModel): + loc: Annotated[List[Union[str, int]], Field(title="Location")] + msg: Annotated[str, Field(title="Message")] + type: Annotated[str, Field(title="Error Type")] + + +class SpanBatchItem(BaseModel): + span_id: Annotated[str, Field(title="Span Id")] + trace_id: Annotated[str, Field(title="Trace Id")] + function: Annotated[str, Field(title="Function")] + depth: Annotated[int, Field(title="Depth")] + created_at: Annotated[Any, Field(title="Created At")] = None + parent_span_id: Annotated[Optional[str], Field(title="Parent Span Id")] = None + span_type: Annotated[Optional[str], Field(title="Span Type")] = "span" + inputs: Annotated[Optional[Dict[str, Any]], Field(title="Inputs")] = None + output: Annotated[Any, Field(title="Output")] = None + error: Annotated[Optional[Dict[str, Any]], Field(title="Error")] = None + usage: Annotated[Optional[Dict[str, Any]], Field(title="Usage")] = None + duration: Annotated[Optional[float], Field(title="Duration")] = None + expected_tools: Annotated[ + Optional[List[Dict[str, Any]]], Field(title="Expected Tools") + ] = None + additional_metadata: Annotated[ + Optional[Dict[str, Any]], Field(title="Additional Metadata") + ] = None + has_evaluation: Annotated[Optional[bool], Field(title="Has Evaluation")] = False + agent_name: Annotated[Optional[str], Field(title="Agent Name")] = None + class_name: Annotated[Optional[str], Field(title="Class Name")] = None + state_before: Annotated[Optional[Dict[str, Any]], Field(title="State Before")] = ( + None + ) + state_after: Annotated[Optional[Dict[str, Any]], Field(title="State After")] = None + span_state: Annotated[str, Field(title="Span State")] + update_id: Annotated[Optional[int], Field(title="Update Id")] = 1 + queued_at: Annotated[float, Field(title="Queued At")] + + +class PromptScorer(BaseModel): + name: Annotated[str, Field(title="Name")] + prompt: Annotated[str, Field(title="Prompt")] + threshold: Annotated[float, Field(title="Threshold")] + options: Annotated[Optional[Dict[str, float]], Field(title="Options")] = None + created_at: Annotated[Optional[datetime], Field(title="Created At")] = None + updated_at: Annotated[Optional[datetime], Field(title="Updated At")] = None + + +class ScorerData(BaseModel): + name: Annotated[str, Field(title="Name")] + threshold: Annotated[float, Field(title="Threshold")] + success: Annotated[bool, Field(title="Success")] + score: Annotated[Optional[float], Field(title="Score")] = None + reason: Annotated[Optional[str], Field(title="Reason")] = None + strict_mode: Annotated[Optional[bool], Field(title="Strict Mode")] = None + evaluation_model: Annotated[ + Optional[Union[List[str], str]], Field(title="Evaluation Model") + ] = None + error: Annotated[Optional[str], Field(title="Error")] = None + additional_metadata: Annotated[ + Optional[Dict[str, Any]], Field(title="Additional Metadata") + ] = None + + +class TraceUsage(BaseModel): prompt_tokens: Annotated[Optional[int], Field(title="Prompt Tokens")] = None completion_tokens: Annotated[Optional[int], Field(title="Completion Tokens")] = None cache_creation_input_tokens: Annotated[ @@ -96,7 +219,7 @@ class TraceUsageJudgmentType(BaseModel): model_name: Annotated[Optional[str], Field(title="Model Name")] = None -class ToolJudgmentType(BaseModel): +class Tool(BaseModel): tool_name: Annotated[str, Field(title="Tool Name")] parameters: Annotated[Optional[Dict[str, Any]], Field(title="Parameters")] = None agent_name: Annotated[Optional[str], Field(title="Agent Name")] = None @@ -109,30 +232,43 @@ class ToolJudgmentType(BaseModel): require_all: Annotated[Optional[bool], Field(title="Require All")] = None -class HTTPValidationErrorJudgmentType(BaseModel): - detail: Annotated[ - Optional[List[ValidationErrorJudgmentType]], Field(title="Detail") - ] = None - - -class EvaluationRunJudgmentType(BaseModel): +class EvaluationRun(BaseModel): id: Annotated[Optional[str], Field(title="Id")] = None project_name: Annotated[Optional[str], Field(title="Project Name")] = None eval_name: Annotated[Optional[str], Field(title="Eval Name")] = None - examples: Annotated[List[ExampleJudgmentType], Field(title="Examples")] + examples: Annotated[List[Example], Field(title="Examples")] custom_scorers: Annotated[ - Optional[List[BaseScorerJudgmentType]], Field(title="Custom Scorers") - ] = Field(default_factory=list) + Optional[List[BaseScorer]], Field(title="Custom Scorers") + ] = [] judgment_scorers: Annotated[ - Optional[List[ScorerConfigJudgmentType]], Field(title="Judgment Scorers") - ] = Field(default_factory=list) + Optional[List[ScorerConfig]], Field(title="Judgment Scorers") + ] = [] model: Annotated[str, Field(title="Model")] trace_span_id: Annotated[Optional[str], Field(title="Trace Span Id")] = None trace_id: Annotated[Optional[str], Field(title="Trace Id")] = None created_at: Annotated[Optional[str], Field(title="Created At")] = None -class TraceSpanJudgmentType(BaseModel): +class HTTPValidationError(BaseModel): + detail: Annotated[Optional[List[ValidationError]], Field(title="Detail")] = None + + +class DatasetInsertExamples(BaseModel): + dataset_alias: Annotated[str, Field(title="Dataset Alias")] + examples: Annotated[List[Example], Field(title="Examples")] + project_name: Annotated[str, Field(title="Project Name")] + + +class SpansBatchRequest(BaseModel): + spans: Annotated[List[SpanBatchItem], Field(title="Spans")] + organization_id: Annotated[str, Field(title="Organization Id")] + + +class FetchPromptScorerResponse(BaseModel): + scorer: PromptScorer + + +class TraceSpan(BaseModel): span_id: Annotated[str, Field(title="Span Id")] trace_id: Annotated[str, Field(title="Trace Id")] function: Annotated[str, Field(title="Function")] @@ -143,11 +279,11 @@ class TraceSpanJudgmentType(BaseModel): inputs: Annotated[Optional[Dict[str, Any]], Field(title="Inputs")] = None error: Annotated[Optional[Dict[str, Any]], Field(title="Error")] = None output: Annotated[Any, Field(title="Output")] = None - usage: Optional[TraceUsageJudgmentType] = None + usage: Optional[TraceUsage] = None duration: Annotated[Optional[float], Field(title="Duration")] = None - expected_tools: Annotated[ - Optional[List[ToolJudgmentType]], Field(title="Expected Tools") - ] = None + expected_tools: Annotated[Optional[List[Tool]], Field(title="Expected Tools")] = ( + None + ) additional_metadata: Annotated[ Optional[Dict[str, Any]], Field(title="Additional Metadata") ] = None @@ -161,54 +297,56 @@ class TraceSpanJudgmentType(BaseModel): update_id: Annotated[Optional[int], Field(title="Update Id")] = 1 -class TraceJudgmentType(BaseModel): +class Trace(BaseModel): trace_id: Annotated[str, Field(title="Trace Id")] name: Annotated[str, Field(title="Name")] created_at: Annotated[str, Field(title="Created At")] duration: Annotated[float, Field(title="Duration")] - trace_spans: Annotated[List[TraceSpanJudgmentType], Field(title="Trace Spans")] + trace_spans: Annotated[List[TraceSpan], Field(title="Trace Spans")] offline_mode: Annotated[Optional[bool], Field(title="Offline Mode")] = False - rules: Annotated[Optional[Dict[str, Any]], Field(title="Rules")] = Field( - default_factory=dict - ) + rules: Annotated[Optional[Dict[str, Any]], Field(title="Rules")] = {} has_notification: Annotated[Optional[bool], Field(title="Has Notification")] = False customer_id: Annotated[Optional[str], Field(title="Customer Id")] = None - tags: Annotated[Optional[List[str]], Field(title="Tags")] = Field( - default_factory=list - ) - metadata: Annotated[Optional[Dict[str, Any]], Field(title="Metadata")] = Field( - default_factory=dict - ) + tags: Annotated[Optional[List[str]], Field(title="Tags")] = [] + metadata: Annotated[Optional[Dict[str, Any]], Field(title="Metadata")] = {} update_id: Annotated[Optional[int], Field(title="Update Id")] = 1 -class ScoringResultJudgmentType(BaseModel): +class ScoringResult(BaseModel): success: Annotated[bool, Field(title="Success")] - scorers_data: Annotated[ - Optional[List[ScorerDataJudgmentType]], Field(title="Scorers Data") - ] = None + scorers_data: Annotated[Optional[List[ScorerData]], Field(title="Scorers Data")] = ( + None + ) name: Annotated[Optional[str], Field(title="Name")] = None data_object: Annotated[ - Optional[Union[TraceSpanJudgmentType, ExampleJudgmentType]], - Field(title="Data Object"), + Optional[Union[TraceSpan, Example]], Field(title="Data Object") ] = None trace_id: Annotated[Optional[str], Field(title="Trace Id")] = None run_duration: Annotated[Optional[float], Field(title="Run Duration")] = None evaluation_cost: Annotated[Optional[float], Field(title="Evaluation Cost")] = None -class TraceRunJudgmentType(BaseModel): +class TraceRun(BaseModel): project_name: Annotated[Optional[str], Field(title="Project Name")] = None eval_name: Annotated[Optional[str], Field(title="Eval Name")] = None - traces: Annotated[List[TraceJudgmentType], Field(title="Traces")] - scorers: Annotated[List[ScorerConfigJudgmentType], Field(title="Scorers")] + traces: Annotated[List[Trace], Field(title="Traces")] + scorers: Annotated[List[ScorerConfig], Field(title="Scorers")] model: Annotated[str, Field(title="Model")] trace_span_id: Annotated[Optional[str], Field(title="Trace Span Id")] = None tools: Annotated[Optional[List[Dict[str, Any]]], Field(title="Tools")] = None -class EvalResultsJudgmentType(BaseModel): - results: Annotated[List[ScoringResultJudgmentType], Field(title="Results")] - run: Annotated[ - Union[TraceRunJudgmentType, EvaluationRunJudgmentType], Field(title="Run") - ] +class EvalResults(BaseModel): + results: Annotated[List[ScoringResult], Field(title="Results")] + run: Annotated[Union[TraceRun, EvaluationRun], Field(title="Run")] + + +class DatasetPush(BaseModel): + dataset_alias: Annotated[str, Field(title="Dataset Alias")] + comments: Annotated[Optional[str], Field(title="Comments")] = None + source_file: Annotated[Optional[str], Field(title="Source File")] = None + examples: Annotated[Optional[List[Example]], Field(title="Examples")] = None + traces: Annotated[Optional[List[Trace]], Field(title="Traces")] = None + is_trace: Annotated[Optional[bool], Field(title="Is Trace")] = False + project_name: Annotated[str, Field(title="Project Name")] + overwrite: Annotated[Optional[bool], Field(title="Overwrite")] = False diff --git a/src/judgeval/data/result.py b/src/judgeval/data/result.py index 951d0b89..9962913a 100644 --- a/src/judgeval/data/result.py +++ b/src/judgeval/data/result.py @@ -1,10 +1,10 @@ from typing import List, Union from judgeval.data import ScorerData, Example from judgeval.data.trace import TraceSpan -from judgeval.data.judgment_types import ScoringResultJudgmentType +from judgeval.data.judgment_types import ScoringResult as JudgmentScoringResult -class ScoringResult(ScoringResultJudgmentType): +class ScoringResult(JudgmentScoringResult): """ A ScoringResult contains the output of one or more scorers applied to a single example. Ie: One input, one actual_output, one expected_output, etc..., and 1+ scorer (Faithfulness, Hallucination, Summarization, etc...) @@ -17,9 +17,8 @@ class ScoringResult(ScoringResultJudgmentType): """ - data_object: ( - Example # Need to override this so that it uses this repo's Example class - ) + # Need to override this so that it uses this repo's Example class + data_object: Example def model_dump(self, **kwargs): data = super().model_dump(**kwargs) diff --git a/src/judgeval/data/scorer_data.py b/src/judgeval/data/scorer_data.py index 54da3eb6..77cd940e 100644 --- a/src/judgeval/data/scorer_data.py +++ b/src/judgeval/data/scorer_data.py @@ -4,12 +4,14 @@ ScorerData holds the information related to a single, completed Scorer evaluation run. """ -from judgeval.data.judgment_types import ScorerDataJudgmentType +from __future__ import annotations + +from judgeval.data.judgment_types import ScorerData as JudgmentScorerData from judgeval.scorers import BaseScorer from typing import List -class ScorerData(ScorerDataJudgmentType): +class ScorerData(JudgmentScorerData): """ ScorerData holds the information related to a single, completed Scorer evaluation run. diff --git a/src/judgeval/data/tool.py b/src/judgeval/data/tool.py index f5c15b62..a9e0357e 100644 --- a/src/judgeval/data/tool.py +++ b/src/judgeval/data/tool.py @@ -1,5 +1,5 @@ -from judgeval.data.judgment_types import ToolJudgmentType +from judgeval.data.judgment_types import Tool as JudgmentTool -class Tool(ToolJudgmentType): +class Tool(JudgmentTool): pass diff --git a/src/judgeval/data/trace.py b/src/judgeval/data/trace.py index 02d3005a..8bf92f41 100644 --- a/src/judgeval/data/trace.py +++ b/src/judgeval/data/trace.py @@ -1,19 +1,17 @@ -import threading from datetime import datetime, timezone from judgeval.data.judgment_types import ( - TraceUsageJudgmentType, - TraceSpanJudgmentType, - TraceJudgmentType, + TraceUsage as JudgmentTraceUsage, + TraceSpan as JudgmentTraceSpan, + Trace as JudgmentTrace, ) -from judgeval.constants import SPAN_LIFECYCLE_END_UPDATE_ID -from judgeval.common.api.json_encoder import json_encoder +from judgeval.utils.serialize import json_encoder -class TraceUsage(TraceUsageJudgmentType): +class TraceUsage(JudgmentTraceUsage): pass -class TraceSpan(TraceSpanJudgmentType): +class TraceSpan(JudgmentTraceSpan): def model_dump(self, **kwargs): return { "span_id": self.span_id, @@ -32,52 +30,12 @@ def model_dump(self, **kwargs): "usage": self.usage.model_dump() if self.usage else None, "has_evaluation": self.has_evaluation, "agent_name": self.agent_name, - "class_name": self.class_name, "state_before": self.state_before, "state_after": self.state_after, "additional_metadata": json_encoder(self.additional_metadata), "update_id": self.update_id, } - def __init__(self, **data): - super().__init__(**data) - # Initialize thread lock for thread-safe update_id increment - self._update_id_lock = threading.Lock() - def increment_update_id(self) -> int: - """ - Thread-safe method to increment the update_id counter. - Returns: - int: The new update_id value after incrementing - """ - with self._update_id_lock: - self.update_id += 1 - return self.update_id - - def set_update_id_to_ending_number( - self, ending_number: int = SPAN_LIFECYCLE_END_UPDATE_ID - ) -> int: - """ - Thread-safe method to set the update_id to a predetermined ending number. - - Args: - ending_number (int): The number to set update_id to. Defaults to SPAN_LIFECYCLE_END_UPDATE_ID. - - Returns: - int: The new update_id value after setting - """ - with self._update_id_lock: - self.update_id = ending_number - return self.update_id - - def print_span(self): - """Print the span with proper formatting and parent relationship information.""" - indent = " " * self.depth - parent_info = ( - f" (parent_id: {self.parent_span_id})" if self.parent_span_id else "" - ) - print(f"{indent}→ {self.function} (id: {self.span_id}){parent_info}") - - -class Trace(TraceJudgmentType): +class Trace(JudgmentTrace): pass diff --git a/src/judgeval/data/trace_run.py b/src/judgeval/data/trace_run.py index 64db04f1..acff84ae 100644 --- a/src/judgeval/data/trace_run.py +++ b/src/judgeval/data/trace_run.py @@ -2,8 +2,7 @@ from typing import List, Optional, Dict, Any, Union from judgeval.data import Trace from judgeval.scorers import APIScorerConfig, BaseScorer -from judgeval.rules import Rule -from judgeval.constants import DEFAULT_GPT_MODEL +from judgeval.env import JUDGMENT_DEFAULT_GPT_MODEL class TraceRun(BaseModel): @@ -27,9 +26,13 @@ class TraceRun(BaseModel): eval_name: Optional[str] = None traces: Optional[List[Trace]] = None scorers: List[Union[APIScorerConfig, BaseScorer]] - model: Optional[str] = DEFAULT_GPT_MODEL + model: Optional[str] = JUDGMENT_DEFAULT_GPT_MODEL trace_span_id: Optional[str] = None - rules: Optional[List[Rule]] = None + append: Optional[bool] = False + override: Optional[bool] = False + + # TODO: ? + rules: Any = None tools: Optional[List[Dict[str, Any]]] = None class Config: diff --git a/src/judgeval/dataset.py b/src/judgeval/dataset/__init__.py similarity index 73% rename from src/judgeval/dataset.py rename to src/judgeval/dataset/__init__.py index 8d3ebf29..55d975b2 100644 --- a/src/judgeval/dataset.py +++ b/src/judgeval/dataset/__init__.py @@ -7,8 +7,9 @@ from judgeval.data import Example, Trace from judgeval.utils.file_utils import get_examples_from_yaml, get_examples_from_json -from judgeval.common.api.api import JudgmentApiClient -from judgeval.common.logger import judgeval_logger +from judgeval.api import JudgmentSyncClient +from judgeval.logger import judgeval_logger +from judgeval.env import JUDGMENT_API_KEY, JUDGMENT_ORG_ID @dataclass @@ -17,8 +18,8 @@ class Dataset: traces: List[Trace] name: str project_name: str - judgment_api_key: str = os.getenv("JUDGMENT_API_KEY") or "" - organization_id: str = os.getenv("JUDGMENT_ORG_ID") or "" + judgment_api_key: str = JUDGMENT_API_KEY or "" + organization_id: str = JUDGMENT_ORG_ID or "" @classmethod def get( @@ -26,10 +27,14 @@ def get( name: str, project_name: str, ): - client = JudgmentApiClient(cls.judgment_api_key, cls.organization_id) - dataset = client.pull_dataset(name, project_name) + client = JudgmentSyncClient(cls.judgment_api_key, cls.organization_id) + dataset = client.datasets_pull_for_judgeval( + { + "dataset_alias": name, + "project_name": project_name, + }, + ) if not dataset: - judgeval_logger.error(f"Dataset {name} not found in project {project_name}") raise ValueError(f"Dataset {name} not found in project {project_name}") examples = dataset.get("examples", []) for e in examples: @@ -61,14 +66,17 @@ def create( if not traces: traces = [] - client = JudgmentApiClient(cls.judgment_api_key, cls.organization_id) - client.push_dataset( - name, - project_name, - examples=[e.model_dump() for e in examples], - traces=[t.model_dump() for t in traces], - overwrite=overwrite, + client = JudgmentSyncClient(cls.judgment_api_key, cls.organization_id) + client.datasets_push( + { + "dataset_alias": name, + "project_name": project_name, + "examples": [e.model_dump() for e in examples], # type: ignore + "traces": [t.model_dump() for t in traces], # type: ignore + "overwrite": overwrite, + } ) + judgeval_logger.info(f"Succesfull created dataset {name}!") return cls( name=name, @@ -115,19 +123,30 @@ def add_from_yaml(self, file_path: str) -> None: self.add_examples(examples) def add_examples(self, examples: List[Example]) -> None: - client = JudgmentApiClient(self.judgment_api_key, self.organization_id) - client.append_examples( - dataset_alias=self.name, - project_name=self.project_name, - examples=[e.model_dump() for e in examples], + client = JudgmentSyncClient(self.judgment_api_key, self.organization_id) + client.datasets_insert_examples( + { + "dataset_alias": self.name, + "project_name": self.project_name, + "examples": [ + { + "name": e.name, + "created_at": e.created_at, + "example_id": e.example_id, + } + for e in examples + ], + } ) def add_traces(self, traces: List[Trace]) -> None: - client = JudgmentApiClient(self.judgment_api_key, self.organization_id) - client.append_traces( - dataset_alias=self.name, - project_name=self.project_name, - traces=[t.model_dump() for t in traces], + client = JudgmentSyncClient(self.judgment_api_key, self.organization_id) + client.traces_add_to_dataset( + { + "dataset_alias": self.name, + "project_name": self.project_name, + "traces": [t.model_dump() for t in traces], # type: ignore + } ) def save_as( @@ -174,10 +193,6 @@ def save_as( f"Invalid file type: {file_type}. Please choose from {ACCEPTABLE_FILE_TYPES}" ) - def delete(self): - client = JudgmentApiClient(self.judgment_api_key, self.organization_id) - client.delete_dataset(self.name, self.project_name) - def __iter__(self): return iter(self.examples) diff --git a/src/judgeval/env.py b/src/judgeval/env.py new file mode 100644 index 00000000..1d804731 --- /dev/null +++ b/src/judgeval/env.py @@ -0,0 +1,57 @@ +from __future__ import annotations +from dotenv import load_dotenv + +load_dotenv() + +import os +from typing import overload + + +@overload +def optional_env_var(var_name: str) -> str | None: ... + + +@overload +def optional_env_var(var_name: str, default: str) -> str: ... + + +def optional_env_var(var_name: str, default: str | None = None) -> str | None: + return os.getenv(var_name, default) + + +JUDGMENT_API_KEY = optional_env_var("JUDGMENT_API_KEY") +JUDGMENT_ORG_ID = optional_env_var("JUDGMENT_ORG_ID") +JUDGMENT_API_URL = optional_env_var("JUDGMENT_API_URL", "https://api.judgmentlabs.ai") + +JUDGMENT_DEFAULT_GPT_MODEL = optional_env_var("JUDGMENT_DEFAULT_GPT_MODEL", "gpt-4.1") +JUDGMENT_DEFAULT_TOGETHER_MODEL = optional_env_var( + "JUDGMENT_DEFAULT_TOGETHER_MODEL", "meta-llama/Meta-Llama-3-8B-Instruct-Lite" +) +JUDGMENT_MAX_CONCURRENT_EVALUATIONS = int( + optional_env_var("JUDGMENT_MAX_CONCURRENT_EVALUATIONS", "10") +) + +JUDGMENT_S3_ACCESS_KEY_ID = optional_env_var("JUDGMENT_S3_ACCESS_KEY_ID") +JUDGMENT_S3_SECRET_ACCESS_KEY = optional_env_var("JUDGMENT_S3_SECRET_ACCESS_KEY") +JUDGMENT_S3_REGION_NAME = optional_env_var("JUDGMENT_S3_REGION_NAME") +JUDGMENT_S3_BUCKET_NAME = optional_env_var("JUDGMENT_S3_BUCKET_NAME") +JUDGMENT_S3_PREFIX = optional_env_var("JUDGMENT_S3_PREFIX", "spans/") +JUDGMENT_S3_ENDPOINT_URL = optional_env_var("JUDGMENT_S3_ENDPOINT_URL") +JUDGMENT_S3_SIGNATURE_VERSION = optional_env_var("JUDGMENT_S3_SIGNATURE_VERSION", "s3") +JUDGMENT_S3_ADDRESSING_STYLE = optional_env_var("JUDGMENT_S3_ADDRESSING_STYLE", "auto") + +__all__ = ( + "JUDGMENT_API_KEY", + "JUDGMENT_ORG_ID", + "JUDGMENT_API_URL", + "JUDGMENT_DEFAULT_GPT_MODEL", + "JUDGMENT_DEFAULT_TOGETHER_MODEL", + "JUDGMENT_MAX_CONCURRENT_EVALUATIONS", + "JUDGMENT_S3_ACCESS_KEY_ID", + "JUDGMENT_S3_SECRET_ACCESS_KEY", + "JUDGMENT_S3_REGION_NAME", + "JUDGMENT_S3_BUCKET_NAME", + "JUDGMENT_S3_PREFIX", + "JUDGMENT_S3_ENDPOINT_URL", + "JUDGMENT_S3_ADDRESSING_STYLE", +) diff --git a/src/judgeval/run_evaluation.py b/src/judgeval/evaluation/__init__.py similarity index 77% rename from src/judgeval/run_evaluation.py rename to src/judgeval/evaluation/__init__.py index 792bed47..0328928c 100644 --- a/src/judgeval/run_evaluation.py +++ b/src/judgeval/evaluation/__init__.py @@ -6,23 +6,25 @@ import orjson import sys import threading -from typing import List, Dict, Union, Tuple, Any, TYPE_CHECKING +from typing import List, Dict, Union, Optional, Callable, Tuple, Any, TYPE_CHECKING from rich import print as rprint -from judgeval.data import ScorerData, ScoringResult, Example +from judgeval.data import ScorerData, ScoringResult, Example, Trace from judgeval.scorers import BaseScorer, APIScorerConfig from judgeval.scorers.score import a_execute_scoring -from judgeval.common.api import JudgmentApiClient -from judgeval.constants import ( - MAX_CONCURRENT_EVALUATIONS, +from judgeval.api import JudgmentSyncClient +from judgeval.env import ( + JUDGMENT_MAX_CONCURRENT_EVALUATIONS, ) -from judgeval.common.exceptions import JudgmentAPIError -from judgeval.common.api.api import JudgmentAPIException -from judgeval.common.logger import judgeval_logger +from judgeval.exceptions import JudgmentAPIError, JudgmentRuntimeError +from judgeval.logger import judgeval_logger if TYPE_CHECKING: + from judgeval.tracer import Tracer + from judgeval.data.trace_run import TraceRun from judgeval.data.evaluation_run import EvaluationRun + from judgeval.integrations.langgraph import JudgevalCallbackHandler def safe_run_async(coro): @@ -48,75 +50,9 @@ def safe_run_async(coro): return asyncio.run(coro) -def send_to_rabbitmq(evaluation_run: EvaluationRun) -> Dict[str, Any]: - """ - Sends an evaluation run to the RabbitMQ evaluation queue. - """ - if not evaluation_run.judgment_api_key or not evaluation_run.organization_id: - raise ValueError("API key and organization ID are required") - if not evaluation_run.eval_name or not evaluation_run.project_name: - raise ValueError("Eval name and project name are required") - api_client = JudgmentApiClient( - evaluation_run.judgment_api_key, evaluation_run.organization_id - ) - return api_client.add_to_evaluation_queue( - evaluation_run.eval_name, evaluation_run.project_name - ) - - -def execute_api_eval(evaluation_run: EvaluationRun) -> Dict: - """ - Executes an evaluation of a list of `Example`s using one or more `JudgmentScorer`s via the Judgment API. - - Args: - evaluation_run (EvaluationRun): The evaluation run object containing the examples, scorers, and metadata - - Returns: - List[Dict]: The results of the evaluation. Each result is a dictionary containing the fields of a `ScoringResult` - object. - """ - - try: - # submit API request to execute evals - if not evaluation_run.judgment_api_key or not evaluation_run.organization_id: - raise ValueError("API key and organization ID are required") - api_client = JudgmentApiClient( - evaluation_run.judgment_api_key, evaluation_run.organization_id - ) - return api_client.run_evaluation(evaluation_run.model_dump()) - except Exception as e: - judgeval_logger.error(f"Error: {e}") - - details = "No details provided" - if isinstance(e, JudgmentAPIException): - details = e.response_json.get("detail", "No details provided") - - raise JudgmentAPIError( - "An error occurred while executing the Judgment API request: " + details - ) - - -def check_missing_scorer_data(results: List[ScoringResult]) -> List[ScoringResult]: - """ - Checks if any `ScoringResult` objects are missing `scorers_data`. - - If any are missing, logs an error and returns the results. - """ - for i, result in enumerate(results): - if not result.scorers_data: - judgeval_logger.error( - f"Scorer data is missing for example {i}. " - "This is usually caused when the example does not contain " - "the fields required by the scorer. " - "Check that your example contains the fields required by the scorers. " - "TODO add docs link here for reference." - ) - return results - - def log_evaluation_results( scoring_results: List[ScoringResult], - run: EvaluationRun, + run: Union[EvaluationRun, TraceRun], judgment_api_key: str, ) -> str: """ @@ -135,17 +71,19 @@ def log_evaluation_results( if not judgment_api_key or not run.organization_id: raise ValueError("API key and organization ID are required") - api_client = JudgmentApiClient(judgment_api_key, run.organization_id) - response = api_client.log_evaluation_results( - scoring_results, - run.model_dump(warnings=False), + api_client = JudgmentSyncClient(judgment_api_key, run.organization_id) + response = api_client.log_eval_results( + { + "results": scoring_results, # type: ignore + "run": run.model_dump(warnings=False), # type: ignore + } ) url = response.get("ui_results_url") return url except Exception as e: judgeval_logger.error(f"Failed to save evaluation results to DB: {str(e)}") - raise JudgmentAPIError( + raise JudgmentRuntimeError( f"Request failed while saving evaluation results to DB: {str(e)}" ) @@ -209,7 +147,7 @@ def _poll_evaluation_until_complete( """ poll_count = 0 exception_count = 0 - api_client = JudgmentApiClient(judgment_api_key, organization_id) + api_client = JudgmentSyncClient(judgment_api_key, organization_id) while poll_count < max_poll_count: poll_count += 1 try: @@ -222,8 +160,11 @@ def _poll_evaluation_until_complete( time.sleep(poll_interval_seconds) continue - results_response = api_client.fetch_evaluation_results( - experiment_run_id, project_name + results_response = api_client.fetch_experiment_run( + { + "experiment_run_id": experiment_run_id, + "project_name": project_name, + } ) url = results_response.get("ui_results_url") @@ -264,13 +205,13 @@ def _poll_evaluation_until_complete( judgeval_logger.error(f"Error checking evaluation status: {str(e)}") if exception_count > max_failures: - raise JudgmentAPIError( + raise JudgmentRuntimeError( f"Error checking evaluation status after {poll_count} attempts: {str(e)}" ) time.sleep(poll_interval_seconds) - raise JudgmentAPIError( + raise JudgmentRuntimeError( f"Error checking evaluation status after {poll_count} attempts" ) @@ -286,15 +227,12 @@ def progress_logger(stop_event, msg="Working...", interval=5): def run_eval( evaluation_run: EvaluationRun, judgment_api_key: str, - show_url: bool = True, ) -> List[ScoringResult]: """ Executes an evaluation of `Example`s using one or more `Scorer`s Args: evaluation_run (EvaluationRun): Stores example and evaluation together for running - judgment_api_key (str): API key for authentication - show_url (bool): Whether to display the evaluation results URL. Defaults to True. Returns: List[ScoringResult]: A list of ScoringResult objects @@ -339,11 +277,11 @@ def run_eval( ) t.start() try: - api_client = JudgmentApiClient( + api_client = JudgmentSyncClient( judgment_api_key, evaluation_run.organization_id ) - response = api_client.add_to_evaluation_queue( - evaluation_run.model_dump(warnings=False) + response = api_client.add_to_run_eval_queue( + evaluation_run.model_dump(warnings=False) # type: ignore ) if not response.get("success", False): @@ -351,7 +289,7 @@ def run_eval( judgeval_logger.error( f"Error adding evaluation to queue: {error_message}" ) - raise JudgmentAPIError(error_message) + raise JudgmentRuntimeError(error_message) num_scorers = ( len(evaluation_run.judgment_scorers) @@ -375,7 +313,7 @@ def run_eval( evaluation_run.custom_scorers, model=evaluation_run.model, throttle_value=0, - max_concurrent=MAX_CONCURRENT_EVALUATIONS, + max_concurrent=JUDGMENT_MAX_CONCURRENT_EVALUATIONS, ) ) @@ -383,10 +321,9 @@ def run_eval( scoring_result.model_dump(warnings=False) for scoring_result in results ] url = log_evaluation_results(send_results, evaluation_run, judgment_api_key) - if show_url: - rprint( - f"\n🔍 You can view your evaluation results here: [rgb(106,0,255)][link={url}]View Results[/link]\n" - ) + rprint( + f"\n🔍 You can view your evaluation results here: [rgb(106,0,255)][link={url}]View Results[/link]\n" + ) return results diff --git a/src/judgeval/exceptions.py b/src/judgeval/exceptions.py new file mode 100644 index 00000000..b5199c17 --- /dev/null +++ b/src/judgeval/exceptions.py @@ -0,0 +1,24 @@ +from __future__ import annotations + +from httpx import HTTPError, Response + + +class JudgmentAPIError(HTTPError): + status_code: int + detail: str + response: Response + + def __init__(self, status_code: int, detail: str, response: Response): + self.status_code = status_code + self.detail = detail + self.response = response + super().__init__(f"{status_code}: {detail}") + + +class JudgmentRuntimeError(RuntimeError): ... + + +class InvalidJudgeModelError(Exception): ... + + +__all__ = ("JudgmentAPIError", "JudgmentRuntimeError", "InvalidJudgeModelError") diff --git a/src/judgeval/integrations/langgraph.py b/src/judgeval/integrations/langgraph.py deleted file mode 100644 index d7b0d7e4..00000000 --- a/src/judgeval/integrations/langgraph.py +++ /dev/null @@ -1,844 +0,0 @@ -from typing import Any, Dict, List, Optional, Sequence -from uuid import UUID -import time -import uuid -from datetime import datetime, timezone - -from judgeval.common.tracer import ( - TraceClient, - TraceSpan, - Tracer, - SpanType, - cost_per_token, -) -from judgeval.data.trace import TraceUsage - -from langchain_core.callbacks import BaseCallbackHandler -from langchain_core.agents import AgentAction, AgentFinish -from langchain_core.outputs import LLMResult -from langchain_core.messages.base import BaseMessage -from langchain_core.documents import Document - -# TODO: Figure out how to handle context variables. Current solution is to keep track of current span id in Tracer class - - -class JudgevalCallbackHandler(BaseCallbackHandler): - """ - LangChain Callback Handler using run_id/parent_run_id for hierarchy. - Manages its own internal TraceClient instance created upon first use. - Includes verbose logging and defensive checks. - """ - - # Make all properties ignored by LangChain's callback system - # to prevent unexpected serialization issues. - lc_serializable = False - lc_kwargs: dict = {} - - def __init__(self, tracer: Tracer): - self.tracer = tracer - self.executed_nodes: List[str] = [] - self._reset_state() - - def _reset_state(self): - """Reset only the critical execution state for reuse across multiple executions""" - # Reset core execution state that must be cleared between runs - self._trace_client: Optional[TraceClient] = None - self._run_id_to_span_id: Dict[UUID, str] = {} - self._span_id_to_start_time: Dict[str, float] = {} - self._span_id_to_depth: Dict[str, int] = {} - self._root_run_id: Optional[UUID] = None - self._trace_saved: bool = False - self.span_id_to_token: Dict[str, Any] = {} - self.trace_id_to_token: Dict[str, Any] = {} - - # Add timestamp to track when we last reset - self._last_reset_time: float = time.time() - - # Also reset tracking/logging variables - self.executed_nodes: List[str] = [] - - def reset(self): - """Public method to manually reset handler execution state for reuse""" - self._reset_state() - - def reset_all(self): - """Public method to reset ALL handler state including tracking/logging data""" - self._reset_state() - - def _ensure_trace_client( - self, run_id: UUID, parent_run_id: Optional[UUID], event_name: str - ) -> Optional[TraceClient]: - """ - Ensures the internal trace client is initialized, creating it only once - per handler instance lifecycle (effectively per graph invocation). - Returns the client or None. - """ - - # If this is a potential new root execution (no parent_run_id) and we had a previous trace saved, - # reset state to allow reuse of the handler - if parent_run_id is None and self._trace_saved: - self._reset_state() - - # If a client already exists, return it. - if self._trace_client: - return self._trace_client - - # If no client exists, initialize it NOW. - trace_id = str(uuid.uuid4()) - project = self.tracer.project_name - try: - # Use event_name as the initial trace name, might be updated later by on_chain_start if root - client_instance = TraceClient( - self.tracer, - trace_id, - event_name, - project_name=project, - enable_monitoring=self.tracer.enable_monitoring, - enable_evaluations=self.tracer.enable_evaluations, - ) - self._trace_client = client_instance - token = self.tracer.set_current_trace(self._trace_client) - if token: - self.trace_id_to_token[trace_id] = token - - if self._trace_client: - self._root_run_id = run_id - self._trace_saved = False - self.tracer._active_trace_client = self._trace_client - - try: - self._trace_client.save(final_save=False) - except Exception as e: - import warnings - - warnings.warn( - f"Failed to save initial trace for live tracking: {e}" - ) - - return self._trace_client - else: - return None - except Exception: - self._trace_client = None - self._root_run_id = None - return None - - def _start_span_tracking( - self, - trace_client: TraceClient, - run_id: UUID, - parent_run_id: Optional[UUID], - name: str, - span_type: SpanType = "span", - inputs: Optional[Dict[str, Any]] = None, - ) -> None: - """Start tracking a span, ensuring trace client exists""" - if name.startswith("__") and name.endswith("__"): - return - start_time = time.time() - span_id = str(uuid.uuid4()) - parent_span_id: Optional[str] = None - current_depth = 0 - - if parent_run_id and parent_run_id in self._run_id_to_span_id: - parent_span_id = self._run_id_to_span_id[parent_run_id] - if parent_span_id in self._span_id_to_depth: - current_depth = self._span_id_to_depth[parent_span_id] + 1 - - self._run_id_to_span_id[run_id] = span_id - self._span_id_to_start_time[span_id] = start_time - self._span_id_to_depth[span_id] = current_depth - - new_span = TraceSpan( - span_id=span_id, - trace_id=trace_client.trace_id, - parent_span_id=parent_span_id, - function=name, - depth=current_depth, - created_at=start_time, - span_type=span_type, - ) - - # Separate metadata from inputs - if inputs: - metadata = {} - clean_inputs = {} - - # Extract metadata fields - metadata_fields = ["tags", "metadata", "kwargs", "serialized"] - for field in metadata_fields: - if field in inputs: - metadata[field] = inputs.pop(field) - - # Store the remaining inputs - clean_inputs = inputs - - # Set both fields on the span - new_span.inputs = clean_inputs - new_span.additional_metadata = metadata - else: - new_span.inputs = {} - new_span.additional_metadata = {} - - trace_client.add_span(new_span) - - trace_client.otel_span_processor.queue_span_update(new_span, span_state="input") - - token = self.tracer.set_current_span(span_id) - if token: - self.span_id_to_token[span_id] = token - - def _end_span_tracking( - self, - trace_client: TraceClient, - run_id: UUID, - outputs: Optional[Any] = None, - error: Optional[BaseException] = None, - ) -> None: - """End tracking a span, ensuring trace client exists""" - - # Get span ID and check if it exists - span_id = self._run_id_to_span_id.get(run_id) - if span_id: - token = self.span_id_to_token.pop(span_id, None) - self.tracer.reset_current_span(token, span_id) - - start_time = self._span_id_to_start_time.get(span_id) if span_id else None - duration = time.time() - start_time if start_time is not None else None - - # Add exit entry (only if span was tracked) - if span_id: - trace_span = trace_client.span_id_to_span.get(span_id) - if trace_span: - trace_span.duration = duration - - # Handle outputs and error - if error: - trace_span.output = error - elif outputs: - # Separate metadata from outputs - metadata = {} - clean_outputs = {} - - # Extract metadata fields - metadata_fields = ["tags", "kwargs"] - if isinstance(outputs, dict): - for field in metadata_fields: - if field in outputs: - metadata[field] = outputs.pop(field) - - # Store the remaining outputs - clean_outputs = outputs - else: - clean_outputs = outputs - - # Set both fields on the span - trace_span.output = clean_outputs - if metadata: - # Merge with existing metadata - existing_metadata = trace_span.additional_metadata or {} - trace_span.additional_metadata = { - **existing_metadata, - **metadata, - } - - span_state = "error" if error else "completed" - trace_client.otel_span_processor.queue_span_update( - trace_span, span_state=span_state - ) - - # Clean up dictionaries for this specific span - if span_id in self._span_id_to_start_time: - del self._span_id_to_start_time[span_id] - if span_id in self._span_id_to_depth: - del self._span_id_to_depth[span_id] - - # Check if this is the root run ending - if run_id == self._root_run_id: - try: - self._root_run_id = None - if ( - self._trace_client and not self._trace_saved - ): # Check if not already saved - complete_trace_data = { - "trace_id": self._trace_client.trace_id, - "name": self._trace_client.name, - "created_at": datetime.fromtimestamp( - self._trace_client.start_time, timezone.utc - ).isoformat(), - "duration": self._trace_client.get_duration(), - "trace_spans": [ - span.model_dump() for span in self._trace_client.trace_spans - ], - "offline_mode": self.tracer.offline_mode, - "parent_trace_id": self._trace_client.parent_trace_id, - "parent_name": self._trace_client.parent_name, - } - - self.tracer.flush_background_spans() - - trace_id, trace_data = self._trace_client.save( - final_save=True, # Final save with usage counter updates - ) - token = self.trace_id_to_token.pop(trace_id, None) - self.tracer.reset_current_trace(token, trace_id) - - # Store complete trace data instead of server response - self.tracer.traces.append(complete_trace_data) - self._trace_saved = True # Set flag only after successful save - finally: - # This block executes regardless of save success/failure - # Reset root run id - self._root_run_id = None - # Reset input storage for this handler instance - if self.tracer._active_trace_client == self._trace_client: - self.tracer._active_trace_client = None - - # --- Callback Methods --- - # Each method now ensures the trace client exists before proceeding - - def on_retriever_start( - self, - serialized: Dict[str, Any], - query: str, - *, - run_id: UUID, - parent_run_id: Optional[UUID] = None, - tags: Optional[List[str]] = None, - metadata: Optional[Dict[str, Any]] = None, - **kwargs: Any, - ) -> Any: - serialized_name = ( - serialized.get("name", "Unknown") - if serialized - else "Unknown (Serialized=None)" - ) - - name = f"RETRIEVER_{(serialized_name).upper()}" - trace_client = self._ensure_trace_client(run_id, parent_run_id, name) - if not trace_client: - return - - inputs = { - "query": query, - "tags": tags, - "metadata": metadata, - "kwargs": kwargs, - "serialized": serialized, - } - self._start_span_tracking( - trace_client, - run_id, - parent_run_id, - name, - span_type="retriever", - inputs=inputs, - ) - - def on_retriever_end( - self, - documents: Sequence[Document], - *, - run_id: UUID, - parent_run_id: Optional[UUID] = None, - **kwargs: Any, - ) -> Any: - trace_client = self._ensure_trace_client(run_id, parent_run_id, "RetrieverEnd") - if not trace_client: - return - doc_summary = [ - { - "index": i, - "page_content": ( - doc.page_content[:100] + "..." - if len(doc.page_content) > 100 - else doc.page_content - ), - "metadata": doc.metadata, - } - for i, doc in enumerate(documents) - ] - outputs = { - "document_count": len(documents), - "documents": doc_summary, - "kwargs": kwargs, - } - self._end_span_tracking(trace_client, run_id, outputs=outputs) - - def on_chain_start( - self, - serialized: Dict[str, Any], - inputs: Dict[str, Any], - *, - run_id: UUID, - parent_run_id: Optional[UUID] = None, - tags: Optional[List[str]] = None, - metadata: Optional[Dict[str, Any]] = None, - **kwargs: Any, - ) -> None: - serialized_name = ( - serialized.get("name") if serialized else "Unknown (Serialized=None)" - ) - - # --- Determine Name and Span Type --- - span_type: SpanType = "chain" - name = serialized_name if serialized_name else "Unknown Chain" - node_name = metadata.get("langgraph_node") if metadata else None - is_langgraph_root_kwarg = ( - kwargs.get("name") == "LangGraph" - ) # Check kwargs for explicit root name - # More robust root detection: Often the first chain event with parent_run_id=None *is* the root. - is_potential_root_event = parent_run_id is None - - if node_name: - name = node_name # Use node name if available - if name not in self.executed_nodes: - self.executed_nodes.append( - name - ) # Leaving this in for now but can probably be removed - elif is_langgraph_root_kwarg and is_potential_root_event: - name = "LangGraph" # Explicit root detected - # Add handling for other potential LangChain internal chains if needed, e.g., "RunnableSequence" - - trace_client = self._ensure_trace_client(run_id, parent_run_id, name) - if not trace_client: - return - - if ( - is_potential_root_event - and run_id == self._root_run_id - and trace_client.name != name - ): - trace_client.name = name - - combined_inputs = { - "inputs": inputs, - "tags": tags, - "metadata": metadata, - "kwargs": kwargs, - "serialized": serialized, - } - self._start_span_tracking( - trace_client, - run_id, - parent_run_id, - name, - span_type=span_type, - inputs=combined_inputs, - ) - - def on_chain_end( - self, - outputs: Dict[str, Any], - *, - run_id: UUID, - parent_run_id: Optional[UUID] = None, - tags: Optional[List[str]] = None, - **kwargs: Any, - ) -> Any: - trace_client = self._ensure_trace_client(run_id, parent_run_id, "ChainEnd") - if not trace_client: - return - - span_id = self._run_id_to_span_id.get(run_id) - if not span_id and run_id != self._root_run_id: - return - - combined_outputs = {"outputs": outputs, "tags": tags, "kwargs": kwargs} - - self._end_span_tracking(trace_client, run_id, outputs=combined_outputs) - - if run_id == self._root_run_id: - if trace_client and not self._trace_saved: - complete_trace_data = { - "trace_id": trace_client.trace_id, - "name": trace_client.name, - "created_at": datetime.fromtimestamp( - trace_client.start_time, timezone.utc - ).isoformat(), - "duration": trace_client.get_duration(), - "trace_spans": [ - span.model_dump() for span in trace_client.trace_spans - ], - "offline_mode": self.tracer.offline_mode, - "parent_trace_id": trace_client.parent_trace_id, - "parent_name": trace_client.parent_name, - } - - self.tracer.flush_background_spans() - - trace_client.save( - final_save=True, - ) - - self.tracer.traces.append(complete_trace_data) - self._trace_saved = True - if self.tracer._active_trace_client == trace_client: - self.tracer._active_trace_client = None - - self._root_run_id = None - - def on_chain_error( - self, - error: BaseException, - *, - run_id: UUID, - parent_run_id: Optional[UUID] = None, - **kwargs: Any, - ) -> Any: - trace_client = self._ensure_trace_client(run_id, parent_run_id, "ChainError") - if not trace_client: - return - - span_id = self._run_id_to_span_id.get(run_id) - - if not span_id and run_id != self._root_run_id: - return - - self._end_span_tracking(trace_client, run_id, error=error) - - def on_tool_start( - self, - serialized: Dict[str, Any], - input_str: str, - *, - run_id: UUID, - parent_run_id: Optional[UUID] = None, - tags: Optional[List[str]] = None, - metadata: Optional[Dict[str, Any]] = None, - inputs: Optional[Dict[str, Any]] = None, - **kwargs: Any, - ) -> Any: - name = ( - serialized.get("name", "Unnamed Tool") - if serialized - else "Unknown Tool (Serialized=None)" - ) - - trace_client = self._ensure_trace_client(run_id, parent_run_id, name) - if not trace_client: - return - - combined_inputs = { - "input_str": input_str, - "inputs": inputs, - "tags": tags, - "metadata": metadata, - "kwargs": kwargs, - "serialized": serialized, - } - self._start_span_tracking( - trace_client, - run_id, - parent_run_id, - name, - span_type="tool", - inputs=combined_inputs, - ) - - def on_tool_end( - self, - output: Any, - *, - run_id: UUID, - parent_run_id: Optional[UUID] = None, - **kwargs: Any, - ) -> Any: - trace_client = self._ensure_trace_client(run_id, parent_run_id, "ToolEnd") - if not trace_client: - return - outputs = {"output": output, "kwargs": kwargs} - self._end_span_tracking(trace_client, run_id, outputs=outputs) - - def on_tool_error( - self, - error: BaseException, - *, - run_id: UUID, - parent_run_id: Optional[UUID] = None, - **kwargs: Any, - ) -> Any: - trace_client = self._ensure_trace_client(run_id, parent_run_id, "ToolError") - if not trace_client: - return - self._end_span_tracking(trace_client, run_id, error=error) - - def on_llm_start( - self, - serialized: Dict[str, Any], - prompts: List[str], - *, - run_id: UUID, - parent_run_id: Optional[UUID] = None, - tags: Optional[List[str]] = None, - metadata: Optional[Dict[str, Any]] = None, - invocation_params: Optional[Dict[str, Any]] = None, - options: Optional[Dict[str, Any]] = None, - name: Optional[str] = None, - **kwargs: Any, - ) -> Any: - llm_name = name or serialized.get("name", "LLM Call") - - trace_client = self._ensure_trace_client(run_id, parent_run_id, llm_name) - if not trace_client: - return - inputs = { - "prompts": prompts, - "invocation_params": invocation_params or kwargs, - "options": options, - "tags": tags, - "metadata": metadata, - "serialized": serialized, - } - self._start_span_tracking( - trace_client, - run_id, - parent_run_id, - llm_name, - span_type="llm", - inputs=inputs, - ) - - def on_llm_end( - self, - response: LLMResult, - *, - run_id: UUID, - parent_run_id: Optional[UUID] = None, - **kwargs: Any, - ) -> Any: - trace_client = self._ensure_trace_client(run_id, parent_run_id, "LLMEnd") - if not trace_client: - return - outputs = {"response": response, "kwargs": kwargs} - - prompt_tokens = None - completion_tokens = None - total_tokens = None - model_name = None - - # Extract model name from response if available - if ( - hasattr(response, "llm_output") - and response.llm_output - and isinstance(response.llm_output, dict) - ): - model_name = response.llm_output.get( - "model_name" - ) or response.llm_output.get("model") - - # Try to get model from the first generation if available - if not model_name and response.generations and len(response.generations) > 0: - if ( - hasattr(response.generations[0][0], "generation_info") - and response.generations[0][0].generation_info - ): - gen_info = response.generations[0][0].generation_info - model_name = gen_info.get("model") or gen_info.get("model_name") - - if response.llm_output and isinstance(response.llm_output, dict): - # Check for OpenAI/standard 'token_usage' first - if "token_usage" in response.llm_output: - token_usage = response.llm_output.get("token_usage") - if token_usage and isinstance(token_usage, dict): - prompt_tokens = token_usage.get("prompt_tokens") - completion_tokens = token_usage.get("completion_tokens") - total_tokens = token_usage.get( - "total_tokens" - ) # OpenAI provides total - # Check for Anthropic 'usage' - elif "usage" in response.llm_output: - token_usage = response.llm_output.get("usage") - if token_usage and isinstance(token_usage, dict): - prompt_tokens = token_usage.get( - "input_tokens" - ) # Anthropic uses input_tokens - completion_tokens = token_usage.get( - "output_tokens" - ) # Anthropic uses output_tokens - # Calculate total if possible - if prompt_tokens is not None and completion_tokens is not None: - total_tokens = prompt_tokens + completion_tokens - - if prompt_tokens is not None or completion_tokens is not None: - prompt_cost = None - completion_cost = None - total_cost_usd = None - - if ( - model_name - and prompt_tokens is not None - and completion_tokens is not None - ): - try: - prompt_cost, completion_cost = cost_per_token( - model=model_name, - prompt_tokens=prompt_tokens, - completion_tokens=completion_tokens, - ) - total_cost_usd = ( - (prompt_cost + completion_cost) - if prompt_cost and completion_cost - else None - ) - except Exception as e: - # If cost calculation fails, continue without costs - import warnings - - warnings.warn( - f"Failed to calculate token costs for model {model_name}: {e}" - ) - - usage = TraceUsage( - prompt_tokens=prompt_tokens, - completion_tokens=completion_tokens, - total_tokens=total_tokens - or ( - prompt_tokens + completion_tokens - if prompt_tokens and completion_tokens - else None - ), - prompt_tokens_cost_usd=prompt_cost, - completion_tokens_cost_usd=completion_cost, - total_cost_usd=total_cost_usd, - model_name=model_name, - ) - - span_id = self._run_id_to_span_id.get(run_id) - if span_id and span_id in trace_client.span_id_to_span: - trace_span = trace_client.span_id_to_span[span_id] - trace_span.usage = usage - - self._end_span_tracking(trace_client, run_id, outputs=outputs) - - def on_llm_error( - self, - error: BaseException, - *, - run_id: UUID, - parent_run_id: Optional[UUID] = None, - **kwargs: Any, - ) -> Any: - trace_client = self._ensure_trace_client(run_id, parent_run_id, "LLMError") - if not trace_client: - return - self._end_span_tracking(trace_client, run_id, error=error) - - def on_chat_model_start( - self, - serialized: Dict[str, Any], - messages: List[List[BaseMessage]], - *, - run_id: UUID, - parent_run_id: Optional[UUID] = None, - tags: Optional[List[str]] = None, - metadata: Optional[Dict[str, Any]] = None, - invocation_params: Optional[Dict[str, Any]] = None, - options: Optional[Dict[str, Any]] = None, - name: Optional[str] = None, - **kwargs: Any, - ) -> Any: - chat_model_name = name or serialized.get("name", "ChatModel Call") - is_openai = ( - any( - key.startswith("openai") for key in serialized.get("secrets", {}).keys() - ) - or "openai" in chat_model_name.lower() - ) - is_anthropic = ( - any( - key.startswith("anthropic") - for key in serialized.get("secrets", {}).keys() - ) - or "anthropic" in chat_model_name.lower() - or "claude" in chat_model_name.lower() - ) - is_together = ( - any( - key.startswith("together") - for key in serialized.get("secrets", {}).keys() - ) - or "together" in chat_model_name.lower() - ) - - is_google = ( - any( - key.startswith("google") for key in serialized.get("secrets", {}).keys() - ) - or "google" in chat_model_name.lower() - or "gemini" in chat_model_name.lower() - ) - - if is_openai and "OPENAI_API_CALL" not in chat_model_name: - chat_model_name = f"{chat_model_name} OPENAI_API_CALL" - elif is_anthropic and "ANTHROPIC_API_CALL" not in chat_model_name: - chat_model_name = f"{chat_model_name} ANTHROPIC_API_CALL" - elif is_together and "TOGETHER_API_CALL" not in chat_model_name: - chat_model_name = f"{chat_model_name} TOGETHER_API_CALL" - - elif is_google and "GOOGLE_API_CALL" not in chat_model_name: - chat_model_name = f"{chat_model_name} GOOGLE_API_CALL" - - trace_client = self._ensure_trace_client(run_id, parent_run_id, chat_model_name) - if not trace_client: - return - inputs = { - "messages": messages, - "invocation_params": invocation_params or kwargs, - "options": options, - "tags": tags, - "metadata": metadata, - "serialized": serialized, - } - self._start_span_tracking( - trace_client, - run_id, - parent_run_id, - chat_model_name, - span_type="llm", - inputs=inputs, - ) - - def on_agent_action( - self, - action: AgentAction, - *, - run_id: UUID, - parent_run_id: Optional[UUID] = None, - **kwargs: Any, - ) -> Any: - action_tool = action.tool - name = f"AGENT_ACTION_{(action_tool).upper()}" - trace_client = self._ensure_trace_client(run_id, parent_run_id, name) - if not trace_client: - return - - inputs = { - "tool_input": action.tool_input, - "log": action.log, - "messages": action.messages, - "kwargs": kwargs, - } - self._start_span_tracking( - trace_client, run_id, parent_run_id, name, span_type="agent", inputs=inputs - ) - - def on_agent_finish( - self, - finish: AgentFinish, - *, - run_id: UUID, - parent_run_id: Optional[UUID] = None, - **kwargs: Any, - ) -> Any: - trace_client = self._ensure_trace_client(run_id, parent_run_id, "AgentFinish") - if not trace_client: - return - - outputs = { - "return_values": finish.return_values, - "log": finish.log, - "messages": finish.messages, - "kwargs": kwargs, - } - self._end_span_tracking(trace_client, run_id, outputs=outputs) diff --git a/src/judgeval/integrations/langgraph/__init__.py b/src/judgeval/integrations/langgraph/__init__.py new file mode 100644 index 00000000..cbb8e398 --- /dev/null +++ b/src/judgeval/integrations/langgraph/__init__.py @@ -0,0 +1,761 @@ +from __future__ import annotations + +import time +from typing import Any, Dict, List, Optional, Sequence, Union, Set, Type, cast +from uuid import UUID + +try: + from langchain_core.callbacks import BaseCallbackHandler + from langchain_core.agents import AgentAction, AgentFinish + from langchain_core.outputs import LLMResult, ChatGeneration + from langchain_core.messages import ( + AIMessage, + BaseMessage, + ChatMessage, + FunctionMessage, + HumanMessage, + SystemMessage, + ToolMessage, + ) + from langchain_core.documents import Document +except ImportError as e: + raise ImportError( + "Judgeval's langgraph integration requires langchain to be installed. Please install it with `pip install judgeval[langchain]`" + ) from e + +from judgeval.tracer import Tracer +from judgeval.tracer.keys import AttributeKeys +from judgeval.tracer.managers import sync_span_context +from judgeval.utils.serialize import safe_serialize +from judgeval.logger import judgeval_logger +from opentelemetry.trace import Status, StatusCode, Span + +# Control flow exception types that should not be treated as errors +CONTROL_FLOW_EXCEPTION_TYPES: Set[Type[BaseException]] = set() + +try: + from langgraph.errors import GraphBubbleUp + + CONTROL_FLOW_EXCEPTION_TYPES.add(GraphBubbleUp) +except ImportError: + pass + +LANGSMITH_TAG_HIDDEN: str = "langsmith:hidden" + + +class JudgevalCallbackHandler(BaseCallbackHandler): + """ + LangGraph/LangChain Callback Handler that creates OpenTelemetry spans + using the Judgeval tracer framework. + + This handler tracks the execution of chains, tools, LLMs, and other components + in a LangGraph/LangChain application, creating proper span hierarchies for monitoring. + """ + + # Prevent LangChain serialization issues + lc_serializable = False + lc_kwargs: dict = {} + + def __init__(self, tracer: Optional[Tracer] = None): + """ + Initialize the callback handler. + + Args: + tracer: Optional Tracer instance. If not provided, will try to use an active tracer. + """ + self.tracer = tracer + if self.tracer is None: + # Try to get an active tracer + if Tracer._active_tracers: + self.tracer = next(iter(Tracer._active_tracers.values())) + else: + judgeval_logger.warning( + "No tracer provided and no active tracers found. " + "Callback handler will not create spans." + ) + return + + # Track spans by run_id + self.spans: Dict[UUID, Span] = {} + self.span_start_times: Dict[UUID, float] = {} + + # Track execution for debugging + self.executed_nodes: List[str] = [] + self.executed_tools: List[str] = [] + self.executed_node_tools: List[Dict[str, Any]] = [] + + def reset(self): + """Reset handler state for reuse across multiple executions.""" + self.spans.clear() + self.span_start_times.clear() + self.executed_nodes.clear() + self.executed_tools.clear() + self.executed_node_tools.clear() + + def _get_run_name(self, serialized: Optional[Dict[str, Any]], **kwargs: Any) -> str: + """Extract the name of the operation from serialized data or kwargs.""" + if "name" in kwargs and kwargs["name"] is not None: + return str(kwargs["name"]) + + if serialized is None: + return "" + + try: + return str(serialized["name"]) + except (KeyError, TypeError): + pass + + try: + return str(serialized["id"][-1]) + except (KeyError, TypeError): + pass + + return "" + + def _convert_message_to_dict(self, message: BaseMessage) -> Dict[str, Any]: + """Convert a LangChain message to a dictionary for storage.""" + if isinstance(message, HumanMessage): + message_dict = {"role": "user", "content": message.content} + elif isinstance(message, AIMessage): + message_dict = {"role": "assistant", "content": message.content} + elif isinstance(message, SystemMessage): + message_dict = {"role": "system", "content": message.content} + elif isinstance(message, ToolMessage): + message_dict = { + "role": "tool", + "content": message.content, + "tool_call_id": message.tool_call_id, + } + elif isinstance(message, FunctionMessage): + message_dict = {"role": "function", "content": message.content} + elif isinstance(message, ChatMessage): + message_dict = {"role": message.role, "content": message.content} + else: + message_dict = {"role": "unknown", "content": str(message.content)} + + if hasattr(message, "additional_kwargs") and message.additional_kwargs: + message_dict["additional_kwargs"] = message.additional_kwargs + + return message_dict + + def _create_message_dicts( + self, messages: List[BaseMessage] + ) -> List[Dict[str, Any]]: + """Convert a list of LangChain messages to dictionaries.""" + return [self._convert_message_to_dict(m) for m in messages] + + def _join_tags_and_metadata( + self, + tags: Optional[List[str]] = None, + metadata: Optional[Dict[str, Any]] = None, + ) -> Optional[Dict[str, Any]]: + """Join tags and metadata into a single dictionary.""" + final_dict = {} + if tags is not None and len(tags) > 0: + final_dict["tags"] = tags + if metadata is not None: + final_dict.update(metadata) + return final_dict if final_dict else None + + def _start_span( + self, + run_id: UUID, + parent_run_id: Optional[UUID], + name: str, + span_type: str, + inputs: Any = None, + tags: Optional[List[str]] = None, + metadata: Optional[Dict[str, Any]] = None, + **extra_attributes: Any, + ) -> None: + """Start a new span for the given run.""" + if not self.tracer: + return + + # Skip internal spans + if name.startswith("__") and name.endswith("__"): + return + + try: + # Create span attributes + attributes = { + AttributeKeys.JUDGMENT_SPAN_KIND: span_type, + } + + # Add metadata and tags + combined_metadata = self._join_tags_and_metadata(tags, metadata) + if combined_metadata: + attributes["metadata"] = safe_serialize(combined_metadata) + + # Add extra attributes + for key, value in extra_attributes.items(): + if value is not None: + attributes[key] = str(value) + + # Use the tracer's context manager to create the span + with sync_span_context(self.tracer, name, attributes) as span: + # Set input data if provided + if inputs is not None: + span.set_attribute( + AttributeKeys.JUDGMENT_INPUT, safe_serialize(inputs) + ) + + # Store the span and start time + self.spans[run_id] = span + self.span_start_times[run_id] = time.time() + + # Keep the span alive by not exiting the context + # We'll manually end it in _end_span + self._active_span_context = span + + except Exception as e: + judgeval_logger.exception(f"Error starting span for {name}: {e}") + + def _end_span( + self, + run_id: UUID, + outputs: Any = None, + error: Optional[BaseException] = None, + **extra_attributes: Any, + ) -> None: + """End the span for the given run.""" + if run_id not in self.spans: + return + + try: + span = self.spans[run_id] + + # Set output data if provided + if outputs is not None: + span.set_attribute( + AttributeKeys.JUDGMENT_OUTPUT, safe_serialize(outputs) + ) + + # Set additional attributes + for key, value in extra_attributes.items(): + if value is not None: + span.set_attribute(key, str(value)) + + # Handle errors + if error is not None: + # Check if this is a control flow exception + is_control_flow = any( + isinstance(error, t) for t in CONTROL_FLOW_EXCEPTION_TYPES + ) + if not is_control_flow: + span.record_exception(error) + span.set_status(Status(StatusCode.ERROR, str(error))) + # Control flow exceptions don't set error status + else: + span.set_status(Status(StatusCode.OK)) + + # Note: We don't call span.end() here because the sync_span_context + # manager handles that automatically when the context exits + + except Exception as e: + judgeval_logger.exception(f"Error ending span for run_id {run_id}: {e}") + finally: + # Cleanup + if run_id in self.spans: + del self.spans[run_id] + if run_id in self.span_start_times: + del self.span_start_times[run_id] + + def _log_debug_event( + self, + event_name: str, + run_id: UUID, + parent_run_id: Optional[UUID] = None, + **kwargs: Any, + ) -> None: + """Log debug information about callback events.""" + judgeval_logger.debug( + f"Event: {event_name}, run_id: {str(run_id)[:8]}, " + f"parent_run_id: {str(parent_run_id)[:8] if parent_run_id else None}" + ) + + # Chain callbacks + def on_chain_start( + self, + serialized: Optional[Dict[str, Any]], + inputs: Dict[str, Any], + *, + run_id: UUID, + parent_run_id: Optional[UUID] = None, + tags: Optional[List[str]] = None, + metadata: Optional[Dict[str, Any]] = None, + **kwargs: Any, + ) -> Any: + """Called when a chain starts running.""" + try: + self._log_debug_event( + "on_chain_start", run_id, parent_run_id, inputs=inputs + ) + + name = self._get_run_name(serialized, **kwargs) + + # Check for LangGraph node + node_name = metadata.get("langgraph_node") if metadata else None + if node_name: + name = node_name + if name not in self.executed_nodes: + self.executed_nodes.append(name) + + # Determine if this is a root LangGraph execution + is_langgraph_root = ( + kwargs.get("name") == "LangGraph" and parent_run_id is None + ) + if is_langgraph_root: + name = "LangGraph" + + span_level = "DEBUG" if tags and LANGSMITH_TAG_HIDDEN in tags else None + + self._start_span( + run_id=run_id, + parent_run_id=parent_run_id, + name=name, + span_type="chain", + inputs=inputs, + tags=tags, + metadata=metadata, + level=span_level, + serialized=safe_serialize(serialized) if serialized else None, + ) + except Exception as e: + judgeval_logger.exception(f"Error in on_chain_start: {e}") + + def on_chain_end( + self, + outputs: Dict[str, Any], + *, + run_id: UUID, + parent_run_id: Optional[UUID] = None, + **kwargs: Any, + ) -> Any: + """Called when a chain ends successfully.""" + try: + self._log_debug_event( + "on_chain_end", run_id, parent_run_id, outputs=outputs + ) + self._end_span(run_id=run_id, outputs=outputs) + except Exception as e: + judgeval_logger.exception(f"Error in on_chain_end: {e}") + + def on_chain_error( + self, + error: BaseException, + *, + run_id: UUID, + parent_run_id: Optional[UUID] = None, + **kwargs: Any, + ) -> None: + """Called when a chain encounters an error.""" + try: + self._log_debug_event("on_chain_error", run_id, parent_run_id, error=error) + self._end_span(run_id=run_id, error=error) + except Exception as e: + judgeval_logger.exception(f"Error in on_chain_error: {e}") + + # LLM callbacks + def on_llm_start( + self, + serialized: Optional[Dict[str, Any]], + prompts: List[str], + *, + run_id: UUID, + parent_run_id: Optional[UUID] = None, + tags: Optional[List[str]] = None, + metadata: Optional[Dict[str, Any]] = None, + **kwargs: Any, + ) -> Any: + """Called when an LLM starts generating.""" + try: + self._log_debug_event( + "on_llm_start", run_id, parent_run_id, prompts=prompts + ) + + name = self._get_run_name(serialized, **kwargs) + model_name = self._extract_model_name(serialized, kwargs) + + prompt_data = prompts[0] if len(prompts) == 1 else prompts + + self._start_span( + run_id=run_id, + parent_run_id=parent_run_id, + name=name, + span_type="llm", + inputs=prompt_data, + tags=tags, + metadata=metadata, + model=model_name, + serialized=safe_serialize(serialized) if serialized else None, + ) + + # Set GenAI specific attributes + if run_id in self.spans: + span = self.spans[run_id] + if model_name: + span.set_attribute(AttributeKeys.GEN_AI_REQUEST_MODEL, model_name) + span.set_attribute( + AttributeKeys.GEN_AI_PROMPT, safe_serialize(prompt_data) + ) + + # Set model parameters if available + invocation_params = kwargs.get("invocation_params", {}) + if "temperature" in invocation_params: + span.set_attribute( + AttributeKeys.GEN_AI_REQUEST_TEMPERATURE, + float(invocation_params["temperature"]), + ) + if "max_tokens" in invocation_params: + span.set_attribute( + AttributeKeys.GEN_AI_REQUEST_MAX_TOKENS, + int(invocation_params["max_tokens"]), + ) + + except Exception as e: + judgeval_logger.exception(f"Error in on_llm_start: {e}") + + def on_chat_model_start( + self, + serialized: Optional[Dict[str, Any]], + messages: List[List[BaseMessage]], + *, + run_id: UUID, + parent_run_id: Optional[UUID] = None, + tags: Optional[List[str]] = None, + metadata: Optional[Dict[str, Any]] = None, + **kwargs: Any, + ) -> Any: + """Called when a chat model starts generating.""" + try: + self._log_debug_event( + "on_chat_model_start", run_id, parent_run_id, messages=messages + ) + + name = self._get_run_name(serialized, **kwargs) + model_name = self._extract_model_name(serialized, kwargs) + + # Flatten messages + flattened_messages = [] + for message_list in messages: + flattened_messages.extend(self._create_message_dicts(message_list)) + + self._start_span( + run_id=run_id, + parent_run_id=parent_run_id, + name=name, + span_type="llm", + inputs=flattened_messages, + tags=tags, + metadata=metadata, + model=model_name, + serialized=safe_serialize(serialized) if serialized else None, + ) + + # Set GenAI specific attributes + if run_id in self.spans: + span = self.spans[run_id] + if model_name: + span.set_attribute(AttributeKeys.GEN_AI_REQUEST_MODEL, model_name) + span.set_attribute( + AttributeKeys.GEN_AI_PROMPT, safe_serialize(flattened_messages) + ) + + except Exception as e: + judgeval_logger.exception(f"Error in on_chat_model_start: {e}") + + def on_llm_end( + self, + response: LLMResult, + *, + run_id: UUID, + parent_run_id: Optional[UUID] = None, + **kwargs: Any, + ) -> Any: + """Called when an LLM finishes generating.""" + try: + self._log_debug_event( + "on_llm_end", run_id, parent_run_id, response=response + ) + + # Extract response content + if response.generations: + last_generation = response.generations[-1][-1] + if ( + isinstance(last_generation, ChatGeneration) + and last_generation.message + ): + output = self._convert_message_to_dict(last_generation.message) + else: + output = ( + last_generation.text + if hasattr(last_generation, "text") + else str(last_generation) + ) + else: + output = "" + + # Extract usage information + usage_attrs = {} + if response.llm_output and "token_usage" in response.llm_output: + token_usage = response.llm_output["token_usage"] + if hasattr(token_usage, "prompt_tokens"): + usage_attrs[AttributeKeys.GEN_AI_USAGE_INPUT_TOKENS] = ( + token_usage.prompt_tokens + ) + if hasattr(token_usage, "completion_tokens"): + usage_attrs[AttributeKeys.GEN_AI_USAGE_OUTPUT_TOKENS] = ( + token_usage.completion_tokens + ) + + # Set completion attribute + if run_id in self.spans: + span = self.spans[run_id] + span.set_attribute( + AttributeKeys.GEN_AI_COMPLETION, safe_serialize(output) + ) + + # Set usage attributes + for key, value in usage_attrs.items(): + span.set_attribute(key, value) + + self._end_span(run_id=run_id, outputs=output, **usage_attrs) + + except Exception as e: + judgeval_logger.exception(f"Error in on_llm_end: {e}") + + def on_llm_error( + self, + error: BaseException, + *, + run_id: UUID, + parent_run_id: Optional[UUID] = None, + **kwargs: Any, + ) -> Any: + """Called when an LLM encounters an error.""" + try: + self._log_debug_event("on_llm_error", run_id, parent_run_id, error=error) + self._end_span(run_id=run_id, error=error) + except Exception as e: + judgeval_logger.exception(f"Error in on_llm_error: {e}") + + # Tool callbacks + def on_tool_start( + self, + serialized: Optional[Dict[str, Any]], + input_str: str, + *, + run_id: UUID, + parent_run_id: Optional[UUID] = None, + tags: Optional[List[str]] = None, + metadata: Optional[Dict[str, Any]] = None, + **kwargs: Any, + ) -> Any: + """Called when a tool starts executing.""" + try: + self._log_debug_event( + "on_tool_start", run_id, parent_run_id, input_str=input_str + ) + + name = self._get_run_name(serialized, **kwargs) + if name not in self.executed_tools: + self.executed_tools.append(name) + + self._start_span( + run_id=run_id, + parent_run_id=parent_run_id, + name=name, + span_type="tool", + inputs=input_str, + tags=tags, + metadata=metadata, + serialized=safe_serialize(serialized) if serialized else None, + ) + except Exception as e: + judgeval_logger.exception(f"Error in on_tool_start: {e}") + + def on_tool_end( + self, + output: str, + *, + run_id: UUID, + parent_run_id: Optional[UUID] = None, + **kwargs: Any, + ) -> Any: + """Called when a tool finishes executing.""" + try: + self._log_debug_event("on_tool_end", run_id, parent_run_id, output=output) + self._end_span(run_id=run_id, outputs=output) + except Exception as e: + judgeval_logger.exception(f"Error in on_tool_end: {e}") + + def on_tool_error( + self, + error: BaseException, + *, + run_id: UUID, + parent_run_id: Optional[UUID] = None, + **kwargs: Any, + ) -> Any: + """Called when a tool encounters an error.""" + try: + self._log_debug_event("on_tool_error", run_id, parent_run_id, error=error) + self._end_span(run_id=run_id, error=error) + except Exception as e: + judgeval_logger.exception(f"Error in on_tool_error: {e}") + + # Agent callbacks + def on_agent_action( + self, + action: AgentAction, + *, + run_id: UUID, + parent_run_id: Optional[UUID] = None, + **kwargs: Any, + ) -> Any: + """Called when an agent takes an action.""" + try: + self._log_debug_event( + "on_agent_action", run_id, parent_run_id, action=action + ) + + if run_id in self.spans: + span = self.spans[run_id] + span.set_attribute("agent.action.tool", action.tool) + span.set_attribute( + "agent.action.tool_input", safe_serialize(action.tool_input) + ) + span.set_attribute("agent.action.log", action.log) + + self._end_span( + run_id=run_id, + outputs={"action": action.tool, "input": action.tool_input}, + ) + except Exception as e: + judgeval_logger.exception(f"Error in on_agent_action: {e}") + + def on_agent_finish( + self, + finish: AgentFinish, + *, + run_id: UUID, + parent_run_id: Optional[UUID] = None, + **kwargs: Any, + ) -> Any: + """Called when an agent finishes.""" + try: + self._log_debug_event( + "on_agent_finish", run_id, parent_run_id, finish=finish + ) + + if run_id in self.spans: + span = self.spans[run_id] + span.set_attribute("agent.finish.log", finish.log) + + self._end_span(run_id=run_id, outputs=finish.return_values) + except Exception as e: + judgeval_logger.exception(f"Error in on_agent_finish: {e}") + + # Retriever callbacks + def on_retriever_start( + self, + serialized: Optional[Dict[str, Any]], + query: str, + *, + run_id: UUID, + parent_run_id: Optional[UUID] = None, + tags: Optional[List[str]] = None, + metadata: Optional[Dict[str, Any]] = None, + **kwargs: Any, + ) -> Any: + """Called when a retriever starts.""" + try: + self._log_debug_event( + "on_retriever_start", run_id, parent_run_id, query=query + ) + + name = self._get_run_name(serialized, **kwargs) + + self._start_span( + run_id=run_id, + parent_run_id=parent_run_id, + name=name, + span_type="retriever", + inputs=query, + tags=tags, + metadata=metadata, + serialized=safe_serialize(serialized) if serialized else None, + ) + except Exception as e: + judgeval_logger.exception(f"Error in on_retriever_start: {e}") + + def on_retriever_end( + self, + documents: Sequence[Document], + *, + run_id: UUID, + parent_run_id: Optional[UUID] = None, + **kwargs: Any, + ) -> Any: + """Called when a retriever finishes.""" + try: + self._log_debug_event( + "on_retriever_end", run_id, parent_run_id, documents=documents + ) + + # Convert documents to serializable format + doc_data = [ + {"page_content": doc.page_content, "metadata": doc.metadata} + for doc in documents + ] + + if run_id in self.spans: + span = self.spans[run_id] + span.set_attribute("retriever.document_count", len(documents)) + + self._end_span( + run_id=run_id, outputs=doc_data, document_count=len(documents) + ) + except Exception as e: + judgeval_logger.exception(f"Error in on_retriever_end: {e}") + + def on_retriever_error( + self, + error: BaseException, + *, + run_id: UUID, + parent_run_id: Optional[UUID] = None, + **kwargs: Any, + ) -> Any: + """Called when a retriever encounters an error.""" + try: + self._log_debug_event( + "on_retriever_error", run_id, parent_run_id, error=error + ) + self._end_span(run_id=run_id, error=error) + except Exception as e: + judgeval_logger.exception(f"Error in on_retriever_error: {e}") + + def _extract_model_name( + self, serialized: Optional[Dict[str, Any]], kwargs: Dict[str, Any] + ) -> Optional[str]: + """Extract model name from serialized data or kwargs.""" + # Try to get from invocation params + invocation_params = kwargs.get("invocation_params", {}) + if "model_name" in invocation_params: + return invocation_params["model_name"] + if "model" in invocation_params: + return invocation_params["model"] + + # Try to get from serialized data + if serialized: + if "model_name" in serialized: + return serialized["model_name"] + if "model" in serialized: + return serialized["model"] + + return None + + +__all__ = ["JudgevalCallbackHandler"] diff --git a/src/judgeval/judges/__init__.py b/src/judgeval/judges/__init__.py index c2dd0199..b0923617 100644 --- a/src/judgeval/judges/__init__.py +++ b/src/judgeval/judges/__init__.py @@ -1,6 +1,5 @@ from judgeval.judges.base_judge import JudgevalJudge from judgeval.judges.litellm_judge import LiteLLMJudge from judgeval.judges.together_judge import TogetherJudge -from judgeval.judges.mixture_of_judges import MixtureOfJudges -__all__ = ["JudgevalJudge", "LiteLLMJudge", "TogetherJudge", "MixtureOfJudges"] +__all__ = ["JudgevalJudge", "LiteLLMJudge", "TogetherJudge"] diff --git a/src/judgeval/judges/litellm_judge.py b/src/judgeval/judges/litellm_judge.py index 838e504a..3e51f924 100644 --- a/src/judgeval/judges/litellm_judge.py +++ b/src/judgeval/judges/litellm_judge.py @@ -2,20 +2,19 @@ from typing import List, Union, Mapping from judgeval.judges import JudgevalJudge -from judgeval.common.utils import ( - afetch_litellm_api_response, +from judgeval.env import JUDGMENT_DEFAULT_GPT_MODEL +from judgeval.judges.todo import ( fetch_litellm_api_response, + afetch_litellm_api_response, ) -from judgeval.common.logger import judgeval_logger -from judgeval.constants import DEFAULT_GPT_MODEL BASE_CONVERSATION = [ {"role": "system", "content": "You are a helpful assistant."}, -] # for string inputs, we need to add the user query to a base conversation, since LiteLLM only accepts a list of dictionaries as a chat history +] class LiteLLMJudge(JudgevalJudge): - def __init__(self, model: str = DEFAULT_GPT_MODEL, **kwargs): + def __init__(self, model: str = JUDGMENT_DEFAULT_GPT_MODEL, **kwargs): self.model = model self.kwargs = kwargs super().__init__(model_name=model) @@ -35,7 +34,6 @@ def generate( model=self.model, messages=input, response_format=schema ) else: - judgeval_logger.error(f"Invalid input type received: {type(input)}") raise TypeError( f"Input must be a string or a list of dictionaries. Input type of: {type(input)}" ) @@ -57,10 +55,7 @@ async def a_generate( ) return response else: - judgeval_logger.error(f"Invalid input type received: {type(input)}") - raise TypeError( - f"Input must be a string or a list of dictionaries. Input type of: {type(input)}" - ) + raise TypeError(f"Input must be a string or a list of dictionaries. Input type of: {type(input)}") # type: ignore[unreachable] def load_model(self): return self.model diff --git a/src/judgeval/judges/mixture_of_judges.py b/src/judgeval/judges/mixture_of_judges.py deleted file mode 100644 index 09894714..00000000 --- a/src/judgeval/judges/mixture_of_judges.py +++ /dev/null @@ -1,287 +0,0 @@ -""" -Implementation for Mixture of Judges model through Judgeval - -Enables client to use multiple models to generate responses and then aggregate them into a single response. -""" - -import pydantic -from typing import List, Union -from judgeval.judges import JudgevalJudge -from judgeval.common.utils import ( - get_completion_multiple_models, - get_chat_completion, - aget_completion_multiple_models, - aget_chat_completion, -) -from judgeval.common.logger import judgeval_logger -from judgeval.constants import DEFAULT_GPT_MODEL - - -def build_dynamic_mixture_prompt( - judge_responses: List[str], - custom_system_prompt: Union[str, None] = None, - custom_conversation_history: Union[List[dict], None] = None, -) -> List[dict]: - """ - Dynamically builds a prompt to mix judge responses together for the Mixture of Judges model. - - In this implementation, we simply concatenate the judge responses into a formatted string, then - pass it into a default prompt template. This template can be customized by providing a custom prompt. - - Args: - judge_responses (List[str]): List of responses from individual judges to be synthesized - custom_system_prompt (str, optional): Custom system prompt to override the default one. Defaults to None. - custom_conversation_history (List[dict], optional): Custom conversation history to override the default one. Defaults to None. - """ - formatted_responses = "\n".join( - [ - f"# Judge {i + 1}'s response: #\n{response}" - for i, response in enumerate(judge_responses) - ] - ) - - # This is the default prompt for the Mixture of Judges model - """ - You are tasked with synthesizing responses from multiple expert judges. You will receive N individual answers on the same topic. Your job is to: - - 1. Analyze and compare the key points, patterns, and agreements between the answers. - 2. Identify the consensus by focusing on areas where most or all of the answers align. Consider common reasoning and frequently mentioned conclusions. - 3. Condense the responses into a single, coherent, and concise answer that represents the collective judgment of the group. - 4. When opinions differ or contradict, highlight the most supported viewpoint while briefly acknowledging the dissenting perspectives. - 5. Ensure the final answer is balanced and clear, providing a comprehensive summary that captures the wisdom of all judges while avoiding repetition. - - ## Start of Judge Responses ## - {{judge_responses}} - ## End of Judge Responses ## - Synthesized response: - """ - - default_conversation = [ # inject the judge responses into the default prompt - { - "role": "system", - "content": "You are tasked with synthesizing responses from multiple expert judges. You will receive N individual answers on the same topic. Your job is to:\n1. Analyze and compare the key points, patterns, and agreements between the answers.\n2. Identify the consensus by focusing on areas where most or all of the answers align. Consider common reasoning and frequently mentioned conclusions.\n3. Condense the responses into a single, coherent, and concise answer that represents the collective judgment of the group.\n4. When opinions differ or contradict, highlight the most supported viewpoint while briefly acknowledging the dissenting perspectives.\n5. Ensure the final answer is balanced and clear, providing a comprehensive summary that captures the wisdom of all judges while avoiding repetition.\n\n**IMPORTANT**: IF THE JUDGE RESPONSES ARE IN JSON FORMAT, YOU MUST RESPOND USING THE SAME JSON FORMAT THAT THE RESPONSES ARE IN. If the judge responses are in JSON, you MUST RESPOND IN VALID JSON FORMAT. ", - }, - { - "role": "user", - "content": '## Start of Judge Responses ## \n# Judge 1\'s response: #\n{\n"claims": [\n{\n"claim": "A 30-day full refund is offered.",\n"quote": "We offer a 30-day full refund at no extra cost."\n},\n{\n"claim": "The 30-day full refund comes at no extra cost.",\n"quote": "We offer a 30-day full refund at no extra cost."\n}\n]\n}\n\n# Judge 2\'s response: #\n{\n "claims": [\n {\n "claim": "A full refund is offered within 30 days.",\n "quote": "We offer a 30-day full refund at no extra cost."\n },\n {\n "claim": "The 30-day full refund is offered at no extra cost.",\n "quote": "We offer a 30-day full refund at no extra cost."\n }\n ]\n}\n# Judge 3\'s response: #\n {\n "claims": [\n {\n "claim": "A 30-day full refund is offered.",\n "quote": "We offer a 30-day full refund at no extra cost."\n },\n {\n "claim": "The 30-day full refund is offered at no extra cost.",\n "quote": "We offer a 30-day full refund at no extra cost."\n }\n ]\n}\n## End of Judge Responses ##\nSynthesized response:', - }, - { - "role": "assistant", - "content": 'The consensus among the judges is clear and unanimous. All three judges agree that a 30-day full refund is offered, and this refund is available at no extra cost. This conclusion is consistently supported by their statements, as each of their claims is directly quoted as: "We offer a 30-day full refund at no extra cost." There are no dissenting perspectives or opposing views provided in any of the responses, indicating complete alignment on this topic.\n\nJSON:\n{\n "claims": [\n {\n "claim": "A full refund is offered within 30 days.",\n "quote": "We offer a 30-day full refund at no extra cost."\n },\n {\n "claim": "The 30-day full refund is offered at no extra cost.",\n "quote": "We offer a 30-day full refund at no extra cost."\n }\n ]\n}', - }, - { - "role": "user", - "content": "## Start of Judge Responses ##\n# Judge 1's response: # \nThe capital of France is Paris.\n\n# Judge 2's response: #\nThe capital of France is Paris.\n\n# Judge 3's response: # \nThe capital of France is Paris. It's one of the most popular tourist destinations in the world, known for its art, culture, and history. It's also famous for its iconic landmarks such as the Eiffel Tower, Louvre Museum, and Notre-Dame Cathedral.\n\n## End of Judge Responses ##\nSynthesized response:", - }, - { - "role": "assistant", - "content": "The capital of France is Paris. It is widely recognized as one of the world's most popular tourist destinations, celebrated for its rich art, culture, and history. Paris is renowned for its iconic landmarks, including the Eiffel Tower, Louvre Museum, and Notre-Dame Cathedral.", - }, - { - "role": "user", - "content": f"## Start of Judge Responses ##\n{formatted_responses}\n## End of Judge Responses ##\nSynthesized response:\n", - }, - ] - - # If a custom system prompt is provided, validate and use it - if custom_system_prompt is not None: - if not isinstance(custom_system_prompt, str): - judgeval_logger.error( - f"TypeError: Custom system prompt must be a string. Received: {type(custom_system_prompt)}." - ) - raise TypeError( - f"Custom system prompt must be a string. Received: {type(custom_system_prompt)}." - ) - if not custom_system_prompt: - raise ValueError("Custom system prompt cannot be empty") - # Override the default system prompt, but also add special instructions for handling JSON - default_conversation[0]["content"] = ( - custom_system_prompt - + "\n\n**IMPORTANT**: IF THE JUDGE RESPONSES ARE IN JSON FORMAT, YOU MUST RESPOND USING THE SAME JSON FORMAT THAT THE RESPONSES ARE IN. If the judge responses are in JSON, you MUST RESPOND IN VALID JSON FORMAT." - ) - - # If a custom conversation history is provided, append the judge responses to it - if custom_conversation_history is not None: - # Validate custom conversation history format - for message in custom_conversation_history: - if not isinstance(message, dict): - raise TypeError( - f"Custom conversation history must be a list of dictionaries. Received: {message}." - ) - - if "role" not in message or "content" not in message: - raise ValueError("Each message must have 'role' and 'content' keys") - - if not isinstance(message["role"], str) or not isinstance( - message["content"], str - ): - raise TypeError( - f"Message role and content must be strings. Received: {type(message['role'])}, {type(message['content'])}." - ) - - if message["role"] not in ["system", "user", "assistant"]: - raise ValueError( - f"Message role must be one of: 'system', 'user', 'assistant'. Received: {message['role']}." - ) - - judge_responses_prompt = { - "role": "user", - "content": f"## Start of Judge Responses ##\n{formatted_responses}\n## End of Judge Responses ##\nSynthesized response:\n", - } - return custom_conversation_history + [judge_responses_prompt] - - # Otherwise return the default conversation with system prompt and examples - # No customization, return the default conversation with system prompt and examples - return default_conversation - - -BASE_CONVERSATION = [ - {"role": "system", "content": "You are a helpful assistant."}, -] # for string inputs, we need to add the user query to a base conversation, since LiteLLM only accepts a list of dictionaries as a chat history - - -class MixtureOfJudges(JudgevalJudge): - """ - IMPORTANT: When supplying custom prompts and conversation histories for aggregation, supply them in the following format: - in kwargs: - { - "custom_prompt": "Your custom prompt here", - "custom_conversation": [ - {"role": "system", "content": "System message 1"}, - {"role": "user", "content": "User message 1"}, - {"role": "assistant", "content": "Assistant message 1"}, - ... - ] - } - """ - - def __init__( - self, - models: List[str] = [ - "QWEN", - "LLAMA3_70B_INSTRUCT_TURBO", - "MISTRAL_8x22B_INSTRUCT", - ], - aggregator: str = DEFAULT_GPT_MODEL, - **kwargs, - ): - """ - `models` are the individual judge models to be used for generating responses. - `aggregator` is the model that will aggregate the responses from the individual judges. - - kwargs include "custom_prompt" and "custom_conversation" for customizing the prompt for the Mixture of Judges model. - """ - self.models = models - self.aggregator = aggregator - self.kwargs = kwargs - super().__init__(model_name=models) - - def generate( - self, - input: Union[str, List[dict]], - response_schema: Union[pydantic.BaseModel, None] = None, - aggregation_schema: Union[pydantic.BaseModel, None] = None, - **kwargs, - ) -> str: - """ - Args: - input (Union[str, List[Mapping[str, str]]]): Input query or conversation history to the model. - response_schema (pydantic.BaseModel): Response schema for individual judge models. - aggregation_schema (pydantic.BaseModel): Response schema for the aggregator model. - kwargs: Additional keyword arguments. - """ - - # Convert input to conversation format if needed - if isinstance(input, str): - convo = BASE_CONVERSATION + [{"role": "user", "content": input}] - elif isinstance(input, list): - convo = input - else: - judgeval_logger.error(f"Invalid input type received: {type(input)}") - raise TypeError( - f"Input must be a string or a list of dictionaries. Input type of: {type(input)}" - ) - - try: - responses = get_completion_multiple_models( - models=self.models, - messages=[convo] * len(self.models), - response_formats=[response_schema] * len(self.models), - ) - except Exception: - raise - - compiled_mixture_prompt = build_dynamic_mixture_prompt( - responses, - self.kwargs.get("custom_prompt"), - self.kwargs.get("custom_conversation"), - ) - - try: - mixed_response = get_chat_completion( - model_type=self.aggregator, - messages=compiled_mixture_prompt, - response_format=aggregation_schema, - ) - except Exception: - raise - - return mixed_response - - async def a_generate( - self, - input: Union[str, List[dict]], - response_schema: Union[pydantic.BaseModel, None] = None, - aggregation_schema: Union[pydantic.BaseModel, None] = None, - **kwargs, - ) -> str: - """ - Args: - input (Union[str, List[Mapping[str, str]]]): Input query or conversation history to the model. - response_schema (pydantic.BaseModel): Response schema for individual judge models. - aggregation_schema (pydantic.BaseModel): Response schema for the aggregator model. - kwargs: Additional keyword arguments. - """ - - # Convert input to conversation format if needed - if isinstance(input, str): - convo = BASE_CONVERSATION + [{"role": "user", "content": input}] - elif isinstance(input, list): - convo = input - else: - judgeval_logger.error(f"Invalid input type received: {type(input)}") - raise TypeError( - f"Input must be a string or a list of dictionaries. Input type of: {type(input)}" - ) - - try: - responses = await aget_completion_multiple_models( - models=self.models, - messages=[convo] * len(self.models), - response_formats=[response_schema] * len(self.models), - ) - except Exception: - raise - - compiled_mixture_prompt = build_dynamic_mixture_prompt( - responses, - self.kwargs.get("custom_prompt"), - self.kwargs.get("custom_conversation"), - ) - - try: - mixed_response = await aget_chat_completion( - model_type=self.aggregator, - messages=compiled_mixture_prompt, - response_format=aggregation_schema, - ) - except Exception: - raise - - return mixed_response - - def load_model(self): - return self.models - - def get_model_name(self) -> List[str]: - return self.models diff --git a/src/judgeval/judges/todo.py b/src/judgeval/judges/todo.py new file mode 100644 index 00000000..6a087e24 --- /dev/null +++ b/src/judgeval/judges/todo.py @@ -0,0 +1,33 @@ +from typing import Any + + +def get_completion_multiple_models(*args, **kwargs) -> Any: + pass + + +def get_chat_completion(*args, **kwargs) -> Any: + pass + + +def aget_completion_multiple_models(*args, **kwargs) -> Any: + pass + + +def aget_chat_completion(*args, **kwargs) -> Any: + pass + + +def fetch_litellm_api_response(*args, **kwargs) -> Any: + pass + + +async def afetch_litellm_api_response(*args, **kwargs) -> Any: + pass + + +def fetch_together_api_response(*args, **kwargs) -> Any: + pass + + +async def afetch_together_api_response(*args, **kwargs) -> Any: + pass diff --git a/src/judgeval/judges/together_judge.py b/src/judgeval/judges/together_judge.py index d4764fc2..6007c15f 100644 --- a/src/judgeval/judges/together_judge.py +++ b/src/judgeval/judges/together_judge.py @@ -6,12 +6,12 @@ from typing import List, Union from judgeval.judges import JudgevalJudge -from judgeval.common.utils import ( +from judgeval.judges.todo import ( fetch_together_api_response, afetch_together_api_response, ) -from judgeval.common.logger import judgeval_logger -from judgeval.constants import DEFAULT_TOGETHER_MODEL +from judgeval.logger import judgeval_logger +from judgeval.env import JUDGMENT_DEFAULT_TOGETHER_MODEL BASE_CONVERSATION = [ {"role": "system", "content": "You are a helpful assistant."}, @@ -19,7 +19,7 @@ class TogetherJudge(JudgevalJudge): - def __init__(self, model: str = DEFAULT_TOGETHER_MODEL, **kwargs): + def __init__(self, model: str = JUDGMENT_DEFAULT_TOGETHER_MODEL, **kwargs): self.model = model self.kwargs = kwargs super().__init__(model_name=model) @@ -58,7 +58,6 @@ async def a_generate( ) return res else: - judgeval_logger.error(f"Invalid input type received: {type(input)}") raise TypeError("Input must be a string or a list of dictionaries.") def load_model(self) -> str: diff --git a/src/judgeval/judges/utils.py b/src/judgeval/judges/utils.py index 7a21cc73..1a779d5c 100644 --- a/src/judgeval/judges/utils.py +++ b/src/judgeval/judges/utils.py @@ -5,9 +5,9 @@ import litellm from typing import Optional, Union, Tuple, List -from judgeval.common.exceptions import InvalidJudgeModelError -from judgeval.judges import JudgevalJudge, LiteLLMJudge, TogetherJudge, MixtureOfJudges -from judgeval.constants import DEFAULT_GPT_MODEL +from judgeval.exceptions import InvalidJudgeModelError +from judgeval.judges import JudgevalJudge, LiteLLMJudge, TogetherJudge +from judgeval.env import JUDGMENT_DEFAULT_GPT_MODEL from judgeval.constants import ( TOGETHER_SUPPORTED_MODELS, JUDGMENT_SUPPORTED_MODELS, @@ -18,7 +18,7 @@ def create_judge( - model: Optional[Union[str, List[str], JudgevalJudge]] = None, + model: Optional[Union[str, JudgevalJudge]] = None, ) -> Tuple[JudgevalJudge, bool]: """ Creates a judge model from string(s) or a judgeval judge object. @@ -31,28 +31,15 @@ def create_judge( If no model is provided, uses GPT4o as the default judge. """ if model is None: # default option - return LiteLLMJudge(model=DEFAULT_GPT_MODEL), True + return LiteLLMJudge(model=JUDGMENT_DEFAULT_GPT_MODEL), True if not isinstance(model, (str, list, JudgevalJudge)): raise InvalidJudgeModelError( f"Model must be a string, list of strings, or a judgeval judge object. Got: {type(model)} instead." ) # If model is already a valid judge type, return it and mark native - if isinstance(model, (JudgevalJudge, LiteLLMJudge, TogetherJudge, MixtureOfJudges)): + if isinstance(model, (JudgevalJudge, LiteLLMJudge, TogetherJudge)): return model, True - # Either string or List[str] - if isinstance(model, list): - for m in model: - if m in JUDGMENT_SUPPORTED_MODELS: - raise NotImplementedError( - """Judgment models are not yet supported for local scoring. - Please either set the `use_judgment` flag to True or use - non-Judgment models.""" - ) - if m not in ACCEPTABLE_MODELS: - raise InvalidJudgeModelError(f"Invalid judge model chosen: {m}") - return MixtureOfJudges(models=model), True - # If model is a string, check that it corresponds to a valid model if model in LITELLM_SUPPORTED_MODELS: return LiteLLMJudge(model=model), True if model in TOGETHER_SUPPORTED_MODELS: diff --git a/src/judgeval/judgment_client.py b/src/judgeval/judgment_client.py deleted file mode 100644 index 9bfdbbc2..00000000 --- a/src/judgeval/judgment_client.py +++ /dev/null @@ -1,267 +0,0 @@ -""" -Implements the JudgmentClient to interact with the Judgment API. -""" - -from __future__ import annotations -import os -import importlib.util -from pathlib import Path -from uuid import uuid4 -from typing import Optional, List, Dict, Union - -from judgeval.data import ( - ScoringResult, - Example, -) -from judgeval.scorers import ( - APIScorerConfig, - BaseScorer, -) -from judgeval.data.evaluation_run import EvaluationRun -from judgeval.run_evaluation import ( - run_eval, - assert_test, -) -from judgeval.common.api import JudgmentApiClient -from judgeval.common.exceptions import JudgmentAPIError -from judgeval.common.utils import validate_api_key -from pydantic import BaseModel -from judgeval.common.logger import judgeval_logger - - -from judgeval.constants import DEFAULT_GPT_MODEL - - -class EvalRunRequestBody(BaseModel): - eval_name: str - project_name: str - - -class DeleteEvalRunRequestBody(BaseModel): - eval_names: List[str] - project_name: str - - -class SingletonMeta(type): - _instances: Dict[type, "JudgmentClient"] = {} - - def __call__(cls, *args, **kwargs): - if cls not in cls._instances: - instance = super().__call__(*args, **kwargs) - cls._instances[cls] = instance - return cls._instances[cls] - - -class JudgmentClient(metaclass=SingletonMeta): - def __init__( - self, - api_key: Optional[str] = os.getenv("JUDGMENT_API_KEY"), - organization_id: Optional[str] = os.getenv("JUDGMENT_ORG_ID"), - ): - if not api_key: - raise ValueError( - "api_key parameter must be provided. Please provide a valid API key value or set the JUDGMENT_API_KEY environment variable." - ) - - if not organization_id: - raise ValueError( - "organization_id parameter must be provided. Please provide a valid organization ID value or set the JUDGMENT_ORG_ID environment variable." - ) - - self.judgment_api_key = api_key - self.organization_id = organization_id - self.api_client = JudgmentApiClient(api_key, organization_id) - - # Verify API key is valid - result, response = validate_api_key(api_key) - if not result: - # May be bad to output their invalid API key... - raise JudgmentAPIError(f"Issue with passed in Judgment API key: {response}") - else: - judgeval_logger.info("Successfully initialized JudgmentClient!") - - def run_evaluation( - self, - examples: List[Example], - scorers: List[Union[APIScorerConfig, BaseScorer]], - model: Optional[str] = DEFAULT_GPT_MODEL, - project_name: str = "default_project", - eval_run_name: str = "default_eval_run", - show_url: bool = True, - ) -> List[ScoringResult]: - """ - Executes an evaluation of `Example`s using one or more `Scorer`s - - Args: - examples (List[Example]): The examples to evaluate - scorers (List[Union[APIScorerConfig, BaseScorer]]): A list of scorers to use for evaluation - model (str): The model used as a judge when using LLM as a Judge - project_name (str): The name of the project the evaluation results belong to - eval_run_name (str): A name for this evaluation run - - Returns: - List[ScoringResult]: The results of the evaluation - """ - - try: - eval = EvaluationRun( - project_name=project_name, - eval_name=eval_run_name, - examples=examples, - scorers=scorers, - model=model, - organization_id=self.organization_id, - ) - return run_eval( - eval, - self.judgment_api_key, - show_url=show_url, - ) - except ValueError as e: - raise ValueError( - f"Please check your EvaluationRun object, one or more fields are invalid: \n{str(e)}" - ) - except Exception as e: - raise Exception(f"An unexpected error occurred during evaluation: {str(e)}") - - def create_project(self, project_name: str) -> bool: - """ - Creates a project on the server. - """ - try: - self.api_client.create_project(project_name) - return True - except Exception as e: - judgeval_logger.error(f"Error creating project: {e}") - return False - - def delete_project(self, project_name: str) -> bool: - """ - Deletes a project from the server. Which also deletes all evaluations and traces associated with the project. - """ - self.api_client.delete_project(project_name) - return True - - def assert_test( - self, - examples: List[Example], - scorers: List[Union[APIScorerConfig, BaseScorer]], - model: Optional[str] = DEFAULT_GPT_MODEL, - project_name: str = "default_test", - eval_run_name: str = str(uuid4()), - ) -> None: - """ - Asserts a test by running the evaluation and checking the results for success - - Args: - examples (List[Example]): The examples to evaluate. - scorers (List[Union[APIScorerConfig, BaseScorer]]): A list of scorers to use for evaluation - model (str): The model used as a judge when using LLM as a Judge - project_name (str): The name of the project the evaluation results belong to - eval_run_name (str): A name for this evaluation run - """ - - results: List[ScoringResult] - - results = self.run_evaluation( - examples=examples, - scorers=scorers, - model=model, - project_name=project_name, - eval_run_name=eval_run_name, - ) - assert_test(results) - - def _extract_scorer_name(self, scorer_file_path: str) -> str: - """Extract scorer name from the scorer file by importing it.""" - try: - spec = importlib.util.spec_from_file_location( - "scorer_module", scorer_file_path - ) - if spec is None or spec.loader is None: - raise ImportError(f"Could not load spec from {scorer_file_path}") - - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - - for attr_name in dir(module): - attr = getattr(module, attr_name) - if ( - isinstance(attr, type) - and any("Scorer" in str(base) for base in attr.__mro__) - and attr.__module__ == "scorer_module" - ): - try: - # Instantiate the scorer and get its name - scorer_instance = attr() - if hasattr(scorer_instance, "name"): - return scorer_instance.name - except Exception: - # Skip if instantiation fails - continue - - raise AttributeError("No scorer class found or could be instantiated") - except Exception as e: - judgeval_logger.warning(f"Could not extract scorer name: {e}") - return Path(scorer_file_path).stem - - def upload_custom_scorer( - self, - scorer_file_path: str, - requirements_file_path: Optional[str] = None, - unique_name: Optional[str] = None, - ) -> bool: - """ - Upload custom ExampleScorer from files to backend. - - Args: - scorer_file_path: Path to Python file containing CustomScorer class - requirements_file_path: Optional path to requirements.txt - unique_name: Optional unique identifier (auto-detected from scorer.name if not provided) - - Returns: - bool: True if upload successful - - Raises: - ValueError: If scorer file is invalid - FileNotFoundError: If scorer file doesn't exist - """ - import os - - if not os.path.exists(scorer_file_path): - raise FileNotFoundError(f"Scorer file not found: {scorer_file_path}") - - # Auto-detect scorer name if not provided - if unique_name is None: - unique_name = self._extract_scorer_name(scorer_file_path) - judgeval_logger.info(f"Auto-detected scorer name: '{unique_name}'") - - # Read scorer code - with open(scorer_file_path, "r") as f: - scorer_code = f.read() - - # Read requirements (optional) - requirements_text = "" - if requirements_file_path and os.path.exists(requirements_file_path): - with open(requirements_file_path, "r") as f: - requirements_text = f.read() - - try: - response = self.api_client.upload_custom_scorer( - scorer_name=unique_name, - scorer_code=scorer_code, - requirements_text=requirements_text, - ) - - if response.get("status") == "success": - judgeval_logger.info( - f"Successfully uploaded custom scorer: {unique_name}" - ) - return True - else: - judgeval_logger.error(f"Failed to upload custom scorer: {unique_name}") - return False - - except Exception as e: - judgeval_logger.error(f"Error uploading custom scorer: {e}") - raise diff --git a/src/judgeval/common/logger.py b/src/judgeval/logger.py similarity index 89% rename from src/judgeval/common/logger.py rename to src/judgeval/logger.py index 1f5d969c..01503793 100644 --- a/src/judgeval/common/logger.py +++ b/src/judgeval/logger.py @@ -1,10 +1,9 @@ -# logger.py - import logging -import sys import os +import sys + +from judgeval.utils.cache import use_once -# ANSI escape sequences RESET = "\033[0m" RED = "\033[31m" YELLOW = "\033[33m" @@ -38,8 +37,9 @@ def format(self, record): return message +@use_once def _setup_judgeval_logger(): - use_color = sys.stdout.isatty() and os.getenv("NO_COLOR") is None + use_color = sys.stdout.isatty() and os.getenv("JUDGMMENT_NO_COLOR") is None handler = logging.StreamHandler(sys.stdout) handler.setLevel(logging.DEBUG) handler.setFormatter( @@ -56,5 +56,7 @@ def _setup_judgeval_logger(): return logger -# Global logger you can import elsewhere judgeval_logger = _setup_judgeval_logger() + + +__all__ = ("judgeval_logger",) diff --git a/src/judgeval/rules.py b/src/judgeval/rules.py deleted file mode 100644 index a085a95b..00000000 --- a/src/judgeval/rules.py +++ /dev/null @@ -1,521 +0,0 @@ -""" -Rules system for Judgeval that enables alerts based on metric thresholds. -""" - -from typing import Dict, List, Optional, Union, Any, Tuple -from pydantic import BaseModel, Field, ConfigDict -import asyncio -from concurrent.futures import ThreadPoolExecutor -import uuid - -from judgeval.scorers import APIScorerConfig, BaseScorer -from judgeval.utils.alerts import AlertStatus, AlertResult - - -class Condition(BaseModel): - """ - A single metric condition. - - Example: - { - "metric": FaithfulnessScorer(threshold=0.7) # Must be a scorer object: APIScorerConfig, BaseScorer - } - - The Condition class uses the scorer's threshold and success function internally. - """ - - model_config = ConfigDict(arbitrary_types_allowed=True) - - metric: Union[APIScorerConfig, BaseScorer] - - @property - def metric_name(self) -> str: - """Get the name of the metric for lookups in scores dictionary.""" - if hasattr(self.metric, "score_type"): - # Handle APIScorerConfig and BaseScorer which have score_type - return self.metric.score_type - elif hasattr(self.metric, "__name__"): - # Handle cases where metric has a __name__ attribute - return self.metric.__name__ - # Fallback to string representation - return str(self.metric) - - @property - def threshold(self) -> float: - """Get the threshold from the metric.""" - return self.metric.threshold if hasattr(self.metric, "threshold") else 0.5 - - def evaluate(self, value: float) -> bool: - """ - Evaluate the condition against a value. - Returns True if the condition passes, False otherwise. - Uses the scorer's success check function if available. - """ - # Store the value in the scorer - if hasattr(self.metric, "score"): - self.metric.score = value - - # Use the scorer's success check function if available - if hasattr(self.metric, "success_check"): - return self.metric.success_check() - elif hasattr(self.metric, "success_check"): - return self.metric.success_check() - else: - # Fallback to default comparison (greater than or equal) - return value >= self.threshold if self.threshold is not None else False - - -class PagerDutyConfig(BaseModel): - """ - Configuration for PagerDuty notifications. - - Attributes: - routing_key: PagerDuty integration routing key - severity: Severity level (critical, error, warning, info) - source: Source of the alert (defaults to "judgeval") - component: Optional component that triggered the alert - group: Optional logical grouping for the alert - class_type: Optional class/type of alert event - """ - - routing_key: str - severity: str = "error" # critical, error, warning, info - source: str = "judgeval" - component: Optional[str] = None - group: Optional[str] = None - class_type: Optional[str] = None - - def model_dump(self, **kwargs): - """Convert the PagerDutyConfig to a dictionary for JSON serialization.""" - return { - "routing_key": self.routing_key, - "severity": self.severity, - "source": self.source, - "component": self.component, - "group": self.group, - "class_type": self.class_type, - } - - -class NotificationConfig(BaseModel): - """ - Configuration for notifications when a rule is triggered. - - Example: - { - "enabled": true, - "communication_methods": ["email", "broadcast_slack", "broadcast_email", "pagerduty"], - "email_addresses": ["user1@example.com", "user2@example.com"], - "pagerduty_config": { - "routing_key": "R0ABCD1234567890123456789", - "severity": "error" - }, - "send_at": 1632150000 # Unix timestamp (specific date/time) - } - - Communication Methods: - - "email": Send emails to specified email addresses - - "broadcast_slack": Send broadcast notifications to all configured Slack channels - - "broadcast_email": Send broadcast emails to all organization emails - - "pagerduty": Send alerts to PagerDuty using the configured routing key - """ - - enabled: bool = True - communication_methods: List[str] = [] - email_addresses: Optional[List[str]] = None - pagerduty_config: Optional[PagerDutyConfig] = None - send_at: Optional[int] = None # Unix timestamp for scheduled notifications - - def model_dump(self, **kwargs): - """Convert the NotificationConfig to a dictionary for JSON serialization.""" - return { - "enabled": self.enabled, - "communication_methods": self.communication_methods, - "email_addresses": self.email_addresses, - "pagerduty_config": self.pagerduty_config.model_dump() - if self.pagerduty_config - else None, - "send_at": self.send_at, - } - - -class Rule(BaseModel): - """ - Configuration for a single rule. - - Example: - { - "rule_id": "123e4567-e89b-12d3-a456-426614174000", - "name": "Quality Check", - "description": "Check if quality metrics meet thresholds", - "conditions": [ - {"metric": FaithfulnessScorer(threshold=0.7)}, - {"metric": AnswerRelevancyScorer(threshold=0.8)} - ], - "combine_type": "all", # "all" or "any" - "notification": { - "enabled": true, - "communication_methods": ["slack", "email"], - "email_addresses": ["user1@example.com", "user2@example.com"] - } - } - """ - - rule_id: str = Field( - default_factory=lambda: str(uuid.uuid4()) - ) # Random UUID string as default value - name: str - description: Optional[str] = None - conditions: List[Condition] - combine_type: str = Field(..., pattern="^(all|any)$") # all = AND, any = OR - notification: Optional[NotificationConfig] = None # Configuration for notifications - - def model_dump(self, **kwargs): - """ - Custom serialization that properly handles condition serialization. - """ - data = super().model_dump(**kwargs) - - # Special handling for conditions with complex metric objects - if "conditions" in data: - for i, condition in enumerate(data["conditions"]): - if "metric" in condition: - # Get the actual metric object - metric_obj = self.conditions[i].metric - - # Create standardized metric representation needed by server API - metric_data = {"score_type": "", "threshold": 0.0, "name": ""} - - # First try to use object's own serialization methods - if hasattr(metric_obj, "to_dict"): - orig_data = metric_obj.to_dict() - # Copy any existing fields - for key, value in orig_data.items(): - metric_data[key] = value - elif hasattr(metric_obj, "model_dump"): - orig_data = metric_obj.model_dump() - # Copy any existing fields - for key, value in orig_data.items(): - metric_data[key] = value - - # If we already have data from original serialization methods but missing required fields - if "name" in metric_data and "score_type" not in metric_data: - metric_data["score_type"] = metric_data["name"] - - # Ensure required fields have values by checking various sources - if not metric_data["score_type"]: - # Try to get score_type from different possible attributes - if hasattr(metric_obj, "score_type"): - metric_data["score_type"] = metric_obj.score_type - elif hasattr(metric_obj, "name"): - metric_data["score_type"] = metric_obj.name - else: - # Last resort: use string representation - metric_data["score_type"] = str(metric_obj) - - # Make sure threshold is set - if ( - not metric_data.get("threshold") - and metric_data.get("threshold") != 0.0 - ): - if hasattr(metric_obj, "threshold"): - metric_data["threshold"] = metric_obj.threshold - else: - # Use condition threshold if metric doesn't have one - metric_data["threshold"] = self.conditions[i].threshold - - # Make sure name is set - if not metric_data.get("name"): - if hasattr(metric_obj, "__name__"): - metric_data["name"] = metric_obj.__name__ - elif hasattr(metric_obj, "name"): - metric_data["name"] = metric_obj.name - else: - # Fallback to score_type if available - metric_data["name"] = metric_data.get( - "score_type", str(metric_obj) - ) - - # Update the condition with our properly serialized metric - condition["metric"] = metric_data - - return data - - -class RulesEngine: - """ - Engine for creating and evaluating rules against metrics. - - Example: - ```python - # Define rules - rules = { - "1": Rule( - name="Quality Check", - description="Check if quality metrics meet thresholds", - conditions=[ - Condition(metric=FaithfulnessScorer(threshold=0.7)), - Condition(metric=AnswerRelevancyScorer(threshold=0.8)) - ], - combine_type="all" - ) - } - - # Create rules engine - engine = RulesEngine(rules) - - # Configure notifications - engine.configure_notification( - rule_id="1", - enabled=True, - communication_methods=["slack", "email"], - email_addresses=["user@example.com"] - ) - - # Evaluate rules - scores = {"faithfulness": 0.65, "relevancy": 0.85} - results = engine.evaluate_rules(scores, {"example_id": "example_123"}) - ``` - """ - - def __init__(self, rules: Dict[str, Rule]): - """ - Initialize the rules engine. - - Args: - rules: Dictionary mapping rule IDs to Rule objects - """ - self.rules = rules - - def configure_notification( - self, - rule_id: str, - enabled: bool = True, - communication_methods: List[str] | None = None, - email_addresses: List[str] | None = None, - send_at: Optional[int] = None, - ) -> None: - """ - Configure notification settings for a specific rule. - - Args: - rule_id: ID of the rule to configure notifications for - enabled: Whether notifications are enabled for this rule - communication_methods: List of notification methods (e.g., ["slack", "email"]) - email_addresses: List of email addresses to send notifications to - send_at: Optional Unix timestamp for when to send the notification - """ - if rule_id not in self.rules: - raise ValueError(f"Rule ID '{rule_id}' not found") - - rule = self.rules[rule_id] - - # Create notification configuration if it doesn't exist - if rule.notification is None: - rule.notification = NotificationConfig() - - # Set notification parameters - rule.notification.enabled = enabled - - if communication_methods is not None: - rule.notification.communication_methods = communication_methods - - if email_addresses is not None: - rule.notification.email_addresses = email_addresses - - if send_at is not None: - rule.notification.send_at = send_at - - def configure_all_notifications( - self, - enabled: bool = True, - communication_methods: List[str] | None = None, - email_addresses: List[str] | None = None, - send_at: Optional[int] = None, - ) -> None: - """ - Configure notification settings for all rules. - - Args: - enabled: Whether notifications are enabled - communication_methods: List of notification methods (e.g., ["slack", "email"]) - email_addresses: List of email addresses to send notifications to - send_at: Optional Unix timestamp for when to send the notification - """ - for rule_id, rule in self.rules.items(): - self.configure_notification( - rule_id=rule_id, - enabled=enabled, - communication_methods=communication_methods, - email_addresses=email_addresses, - send_at=send_at, - ) - - def evaluate_rules( - self, - scores: Dict[str, float], - example_metadata: Optional[Dict[str, Any]] = None, - ) -> Dict[str, AlertResult]: - """ - Evaluate all rules against a set of scores. - Returns mapping of rule IDs to their alert results. - - Args: - scores: Dictionary of metric names to their score values - example_metadata: Optional dictionary containing example metadata (example_id, timestamp) - """ - results = {} - - for rule_id, rule in self.rules.items(): - # Evaluate each condition - condition_results = [] - passed_conditions = [] - - for condition in rule.conditions: - # Get the metric name for lookup - metric_name = condition.metric_name - value = scores.get(metric_name) - - if value is None: - # Skip this condition instead of evaluating it as false - condition_results.append( - { - "metric": metric_name, - "value": None, - "threshold": condition.threshold, - "passed": None, # Using None to indicate the condition was skipped - "skipped": True, # Add a flag to indicate this condition was skipped - } - ) - continue # Skip adding to passed_conditions - else: - passed = condition.evaluate(value) - condition_results.append( - { - "metric": metric_name, - "value": value, - "threshold": condition.threshold, - "passed": passed, - "skipped": False, # Indicate this condition was evaluated - } - ) - passed_conditions.append(passed) - - # Determine if alert should trigger - only consider conditions that weren't skipped - if not passed_conditions: - # If all conditions were skipped, the rule doesn't trigger - triggered = False - else: - if rule.combine_type == "all": - # For "all" combine_type: - # - All evaluated conditions must pass - # - All conditions must have been evaluated (none skipped) - all_conditions_passed = all(passed_conditions) - all_conditions_evaluated = len(passed_conditions) == len( - rule.conditions - ) - triggered = all_conditions_passed and all_conditions_evaluated - else: - # For "any" combine_type, at least one condition must pass - triggered = any(passed_conditions) - - # Create alert result with example metadata - notification_config = None - if triggered and rule.notification: - # If rule has a notification config and the alert is triggered, include it in the result - notification_config = rule.notification - - # Set the alert status based on whether the rule was triggered using proper enum values - status = AlertStatus.TRIGGERED if triggered else AlertStatus.NOT_TRIGGERED - - # Create the alert result - alert_result = AlertResult( - status=status, - rule_id=rule.rule_id, - rule_name=rule.name, - conditions_result=condition_results, - notification=notification_config, - metadata=example_metadata or {}, - combine_type=rule.combine_type, - project_id=example_metadata.get("project_id") - if example_metadata - else None, - trace_span_id=example_metadata.get("trace_span_id") - if example_metadata - else None, - ) - - results[rule_id] = alert_result - - return results - - async def evaluate_rules_parallel( - self, - example_scores: Dict[str, Dict[str, float]], - example_metadata: Dict[str, Dict[str, Any]], - max_concurrent: int = 100, - ) -> Dict[str, Dict[str, AlertResult]]: - """ - Evaluate all rules against multiple examples in parallel. - - Args: - example_scores: Dictionary mapping example_ids to their score dictionaries - example_metadata: Dictionary mapping example_ids to their metadata - max_concurrent: Maximum number of concurrent evaluations - - Returns: - Dictionary mapping example_ids to dictionaries of rule_ids and their alert results - """ - # Create semaphore to limit concurrent executions - semaphore = asyncio.Semaphore(max_concurrent) - results = {} - tasks = [] - - # Create a task for each example - for example_id, scores in example_scores.items(): - metadata = example_metadata.get(example_id, {}) - task = self._evaluate_with_semaphore( - semaphore=semaphore, - example_id=example_id, - scores=scores, - metadata=metadata, - ) - tasks.append(task) - - # Run all tasks and collect results - example_results = await asyncio.gather(*tasks) - - # Organize results by example_id - for example_id, result in example_results: - results[example_id] = result - - return results - - async def _evaluate_with_semaphore( - self, - semaphore: asyncio.Semaphore, - example_id: str, - scores: Dict[str, float], - metadata: Dict[str, Any], - ) -> Tuple[str, Dict[str, AlertResult]]: - """ - Helper method to evaluate rules for an example with semaphore control. - - Args: - semaphore: Semaphore to control concurrency - example_id: ID of the example being evaluated - scores: Dictionary of scores for this example - metadata: Metadata for this example - - Returns: - Tuple of (example_id, rule_results) - """ - async with semaphore: - # Run the evaluation in a thread pool to avoid blocking the event loop - # for CPU-bound operations - with ThreadPoolExecutor() as executor: - rule_results = await asyncio.get_event_loop().run_in_executor( - executor, self.evaluate_rules, scores, metadata - ) - - return (example_id, rule_results) diff --git a/src/judgeval/scorers/__init__.py b/src/judgeval/scorers/__init__.py index c74d079d..fccb112e 100644 --- a/src/judgeval/scorers/__init__.py +++ b/src/judgeval/scorers/__init__.py @@ -1,8 +1,6 @@ from judgeval.scorers.api_scorer import APIScorerConfig from judgeval.scorers.base_scorer import BaseScorer from judgeval.scorers.judgeval_scorers.api_scorers import ( - ExecutionOrderScorer, - HallucinationScorer, FaithfulnessScorer, AnswerRelevancyScorer, AnswerCorrectnessScorer, @@ -17,8 +15,6 @@ "APIScorerConfig", "BaseScorer", "PromptScorer", - "ExecutionOrderScorer", - "HallucinationScorer", "FaithfulnessScorer", "AnswerRelevancyScorer", "AnswerCorrectnessScorer", diff --git a/src/judgeval/scorers/agent_scorer.py b/src/judgeval/scorers/agent_scorer.py index cc3e3780..a78b1805 100644 --- a/src/judgeval/scorers/agent_scorer.py +++ b/src/judgeval/scorers/agent_scorer.py @@ -1,21 +1,17 @@ from judgeval.scorers.base_scorer import BaseScorer -from judgeval.data import Trace +from judgeval.data.judgment_types import Trace as JudgmentTrace from typing import List, Optional from abc import abstractmethod -from judgeval.common.logger import warning, error - class AgentScorer(BaseScorer): @abstractmethod async def a_score_trace( - self, trace: Trace, tools: Optional[List] = None, *args, **kwargs + self, trace: JudgmentTrace, tools: Optional[List] = None, *args, **kwargs ) -> float: """ Asynchronously measures the score on a trace """ - warning("Attempting to call unimplemented a_score_trace method") - error("a_score_trace method not implemented") raise NotImplementedError( "You must implement the `a_score_trace` method in your custom scorer" ) diff --git a/src/judgeval/scorers/api_scorer.py b/src/judgeval/scorers/api_scorer.py index d1896962..cf321dee 100644 --- a/src/judgeval/scorers/api_scorer.py +++ b/src/judgeval/scorers/api_scorer.py @@ -4,11 +4,12 @@ Scores `Example`s using ready-made Judgment evaluators. """ +from __future__ import annotations + from pydantic import BaseModel, field_validator from typing import List -from judgeval.data import ExampleParams -from judgeval.constants import APIScorerType, UNBOUNDED_SCORERS -from judgeval.common.logger import judgeval_logger +from judgeval.constants import UNBOUNDED_SCORERS, APIScorerType +from judgeval.data.example import ExampleParams class APIScorerConfig(BaseModel): @@ -28,9 +29,10 @@ class APIScorerConfig(BaseModel): name: str = "" threshold: float = 0.5 strict_mode: bool = False - required_params: List[ - ExampleParams - ] = [] # This is used to check if the example has the required parameters before running the scorer + + # This is used to check if the example has the required parameters before running the scorer + required_params: List[ExampleParams] = [] + kwargs: dict = {} @field_validator("threshold") @@ -42,17 +44,11 @@ def validate_threshold(cls, v, info): score_type = info.data.get("score_type") if score_type in UNBOUNDED_SCORERS: if v < 0: - judgeval_logger.error( - f"Threshold for {score_type} must be greater than 0, got: {v}" - ) raise ValueError( f"Threshold for {score_type} must be greater than 0, got: {v}" ) else: if not 0 <= v <= 1: - judgeval_logger.error( - f"Threshold for {score_type} must be between 0 and 1, got: {v}" - ) raise ValueError( f"Threshold for {score_type} must be between 0 and 1, got: {v}" ) @@ -61,7 +57,6 @@ def validate_threshold(cls, v, info): @field_validator("name", mode="after") @classmethod def set_name_to_score_type_if_none(cls, v, info): - """Set name to score_type if not provided""" if v is None: return info.data.get("score_type") return v diff --git a/src/judgeval/scorers/base_scorer.py b/src/judgeval/scorers/base_scorer.py index 484f6552..6b37acf6 100644 --- a/src/judgeval/scorers/base_scorer.py +++ b/src/judgeval/scorers/base_scorer.py @@ -2,11 +2,13 @@ Base class for all scorers. """ +from __future__ import annotations from typing import Dict, Optional from pydantic import BaseModel +from judgeval.judges.base_judge import JudgevalJudge from judgeval.judges.utils import create_judge from typing import Any from pydantic import model_validator, Field @@ -19,44 +21,63 @@ class BaseScorer(BaseModel): where none of Judgment's scorers are suitable. """ - score_type: str # type of your scorer (Faithfulness, PromptScorer) - threshold: float = ( - 0.5 # The threshold to pass a test while using this scorer as a scorer - ) - name: Optional[str] = ( - None # name of your scorer (Faithfulness, PromptScorer-randomslug) - ) - class_name: Optional[str] = None # The name of the class of the scorer - score: Optional[float] = None # The float score of the scorer run on the test case + # type of your scorer (Faithfulness, PromptScorer) + score_type: str + + # The threshold to pass a test while using this scorer as a scorer + threshold: float = 0.5 + + # name of your scorer (Faithfulness, PromptScorer-randomslug) + name: Optional[str] = None + + # The name of the class of the scorer + class_name: Optional[str] = None + + # The float score of the scorer run on the test case + score: Optional[float] = None + score_breakdown: Optional[Dict] = None reason: Optional[str] = "" - using_native_model: Optional[bool] = None # Whether the model is a native model - success: Optional[bool] = None # Whether the test case passed or failed - model: Optional[str] = None # The name of the model used to evaluate the test case - model_client: Optional[Any] = Field( - default=None, exclude=True - ) # The model used to evaluate the test case - strict_mode: bool = False # Whether to run the scorer in strict mode - error: Optional[str] = None # The error message if the scorer failed - additional_metadata: Optional[Dict] = None # Additional metadata for the scorer - user: Optional[str] = None # The user ID of the scorer - server_hosted: bool = False # Whether the scorer is enabled for e2b + + # Whether the model is a native model + using_native_model: Optional[bool] = None + + # Whether the test case passed or failed + success: Optional[bool] = None + + # The name of the model used to evaluate the test case + model: Optional[str] = None + + # The model used to evaluate the test case + model_client: Optional[Any] = Field(default=None, exclude=True) + + # Whether to run the scorer in strict mode + strict_mode: bool = False + + # The error message if the scorer failed + error: Optional[str] = None + + # Additional metadata for the scorer + additional_metadata: Optional[Dict] = None + + # The user ID of the scorer + user: Optional[str] = None + + # Whether the scorer is hosted on the server + server_hosted: bool = False @model_validator(mode="after") - @classmethod - def enforce_strict_threshold(cls, data: "BaseScorer"): - if data.strict_mode: - data.threshold = 1.0 - return data + def enforce_strict_threshold(self): + if self.strict_mode: + self.threshold = 1.0 + return self @model_validator(mode="after") - @classmethod - def default_name(cls, m: "BaseScorer") -> "BaseScorer": - # Always set class_name to the string name of the class - m.class_name = m.__class__.__name__ - if not m.name: - m.name = m.class_name - return m + def default_name(self): + self.class_name = self.__class__.__name__ + if not self.name: + self.name = self.class_name + return self def _add_model(self, model: str): """ diff --git a/src/judgeval/scorers/example_scorer.py b/src/judgeval/scorers/example_scorer.py index 1d71e873..9cadea94 100644 --- a/src/judgeval/scorers/example_scorer.py +++ b/src/judgeval/scorers/example_scorer.py @@ -2,18 +2,16 @@ from judgeval.data import Example from typing import List from pydantic import Field -from judgeval.common.logger import judgeval_logger class ExampleScorer(BaseScorer): - score_type: str = "Custom" # default to custom score type + score_type: str = "Custom" required_params: List[str] = Field(default_factory=list) async def a_score_example(self, example: Example, *args, **kwargs) -> float: """ Asynchronously measures the score on a single example """ - judgeval_logger.error("a_score_example method not implemented") raise NotImplementedError( "You must implement the `a_score_example` method in your custom scorer" ) diff --git a/src/judgeval/scorers/judgeval_scorers/api_scorers/__init__.py b/src/judgeval/scorers/judgeval_scorers/api_scorers/__init__.py index 49a1b534..59f83da6 100644 --- a/src/judgeval/scorers/judgeval_scorers/api_scorers/__init__.py +++ b/src/judgeval/scorers/judgeval_scorers/api_scorers/__init__.py @@ -1,9 +1,3 @@ -from judgeval.scorers.judgeval_scorers.api_scorers.execution_order import ( - ExecutionOrderScorer, -) -from judgeval.scorers.judgeval_scorers.api_scorers.hallucination import ( - HallucinationScorer, -) from judgeval.scorers.judgeval_scorers.api_scorers.faithfulness import ( FaithfulnessScorer, ) @@ -28,18 +22,10 @@ ) __all__ = [ - "ExecutionOrderScorer", - "JSONCorrectnessScorer", - "SummarizationScorer", - "HallucinationScorer", "FaithfulnessScorer", - "ContextualRelevancyScorer", - "ContextualPrecisionScorer", - "ContextualRecallScorer", "AnswerRelevancyScorer", "AnswerCorrectnessScorer", "InstructionAdherenceScorer", - "GroundednessScorer", "DerailmentScorer", "ToolOrderScorer", "PromptScorer", diff --git a/src/judgeval/scorers/judgeval_scorers/api_scorers/execution_order.py b/src/judgeval/scorers/judgeval_scorers/api_scorers/execution_order.py deleted file mode 100644 index 136a99c1..00000000 --- a/src/judgeval/scorers/judgeval_scorers/api_scorers/execution_order.py +++ /dev/null @@ -1,52 +0,0 @@ -""" -`judgeval` tool correctness scorer - -TODO add link to docs page for this scorer - -""" - -# Internal imports -from judgeval.scorers.api_scorer import APIScorerConfig -from judgeval.constants import APIScorerType -from typing import Optional, Dict -from judgeval.data import ExampleParams - - -class ExecutionOrderScorer(APIScorerConfig): - kwargs: Optional[Dict] = None - - def __init__( - self, - threshold: float, - should_exact_match: bool = False, - should_consider_ordering: bool = False, - ): - super().__init__( - threshold=threshold, - score_type=APIScorerType.EXECUTION_ORDER, - required_params=[ - ExampleParams.ACTUAL_OUTPUT, - ExampleParams.EXPECTED_OUTPUT, - ], - ) - self.kwargs = { - "should_exact_match": should_exact_match, - "should_consider_ordering": should_consider_ordering, - } - - @property - def __name__(self): - return "Execution Order" - - def to_dict(self) -> dict: - """ - Converts the scorer configuration to a dictionary format. - - Returns: - dict: A dictionary containing the scorer's configuration - """ - return { - "score_type": self.score_type, - "threshold": self.threshold, - "kwargs": self.kwargs, - } diff --git a/src/judgeval/scorers/judgeval_scorers/api_scorers/hallucination.py b/src/judgeval/scorers/judgeval_scorers/api_scorers/hallucination.py deleted file mode 100644 index c7ffe56d..00000000 --- a/src/judgeval/scorers/judgeval_scorers/api_scorers/hallucination.py +++ /dev/null @@ -1,28 +0,0 @@ -""" -`judgeval` hallucination scorer - -TODO add link to docs page for this scorer - -""" - -# Internal imports -from judgeval.scorers.api_scorer import APIScorerConfig -from judgeval.constants import APIScorerType -from judgeval.data import ExampleParams - - -class HallucinationScorer(APIScorerConfig): - def __init__(self, threshold: float): - super().__init__( - threshold=threshold, - score_type=APIScorerType.HALLUCINATION, - required_params=[ - ExampleParams.INPUT, - ExampleParams.ACTUAL_OUTPUT, - ExampleParams.CONTEXT, - ], - ) - - @property - def __name__(self): - return "Hallucination" diff --git a/src/judgeval/scorers/judgeval_scorers/api_scorers/prompt_scorer.py b/src/judgeval/scorers/judgeval_scorers/api_scorers/prompt_scorer.py index c56e7a41..565d4c92 100644 --- a/src/judgeval/scorers/judgeval_scorers/api_scorers/prompt_scorer.py +++ b/src/judgeval/scorers/judgeval_scorers/api_scorers/prompt_scorer.py @@ -1,11 +1,11 @@ from judgeval.scorers.api_scorer import APIScorerConfig from judgeval.constants import APIScorerType from typing import Dict, Any, Optional -from judgeval.common.api import JudgmentApiClient, JudgmentAPIException +from judgeval.api import JudgmentSyncClient +from judgeval.exceptions import JudgmentAPIError import os -from judgeval.common.exceptions import JudgmentAPIError from copy import copy -from judgeval.common.logger import judgeval_logger +from judgeval.logger import judgeval_logger def push_prompt_scorer( @@ -16,15 +16,28 @@ def push_prompt_scorer( judgment_api_key: str = os.getenv("JUDGMENT_API_KEY") or "", organization_id: str = os.getenv("JUDGMENT_ORG_ID") or "", ) -> str: - client = JudgmentApiClient(judgment_api_key, organization_id) + client = JudgmentSyncClient(judgment_api_key, organization_id) try: - r = client.save_scorer(name, prompt, threshold, options) - except JudgmentAPIException as e: + r = client.save_scorer( + payload={ + "name": name, + "prompt": prompt, + "threshold": threshold, + "options": options, + } + ) + except JudgmentAPIError as e: if e.status_code == 500: raise JudgmentAPIError( - f"The server is temporarily unavailable. Please try your request again in a few moments. Error details: {e.error_detail}" + status_code=e.status_code, + detail=f"The server is temporarily unavailable. Please try your request again in a few moments. Error details: {e.detail}", + response=e.response, ) - raise JudgmentAPIError(f"Failed to save prompt scorer: {e.error_detail}") + raise JudgmentAPIError( + status_code=e.status_code, + detail=f"Failed to save prompt scorer: {e.detail}", + response=e.response, + ) return r["name"] @@ -33,19 +46,23 @@ def fetch_prompt_scorer( judgment_api_key: str = os.getenv("JUDGMENT_API_KEY") or "", organization_id: str = os.getenv("JUDGMENT_ORG_ID") or "", ): - client = JudgmentApiClient(judgment_api_key, organization_id) + client = JudgmentSyncClient(judgment_api_key, organization_id) try: - scorer_config = client.fetch_scorer(name)["scorer"] + scorer_config = client.fetch_scorer({"name": name})["scorer"] scorer_config.pop("created_at") scorer_config.pop("updated_at") return scorer_config - except JudgmentAPIException as e: + except JudgmentAPIError as e: if e.status_code == 500: raise JudgmentAPIError( - f"The server is temporarily unavailable. Please try your request again in a few moments. Error details: {e.error_detail}" + status_code=e.status_code, + detail=f"The server is temporarily unavailable. Please try your request again in a few moments. Error details: {e.detail}", + response=e.response, ) raise JudgmentAPIError( - f"Failed to fetch prompt scorer '{name}': {e.error_detail}" + status_code=e.status_code, + detail=f"Failed to fetch prompt scorer '{name}': {e.detail}", + response=e.response, ) @@ -54,15 +71,21 @@ def scorer_exists( judgment_api_key: str = os.getenv("JUDGMENT_API_KEY") or "", organization_id: str = os.getenv("JUDGMENT_ORG_ID") or "", ): - client = JudgmentApiClient(judgment_api_key, organization_id) + client = JudgmentSyncClient(judgment_api_key, organization_id) try: - return client.scorer_exists(name)["exists"] - except JudgmentAPIException as e: + return client.scorer_exists({"name": name})["exists"] + except JudgmentAPIError as e: if e.status_code == 500: raise JudgmentAPIError( - f"The server is temporarily unavailable. Please try your request again in a few moments. Error details: {e.error_detail}" + status_code=e.status_code, + detail=f"The server is temporarily unavailable. Please try your request again in a few moments. Error details: {e.detail}", + response=e.response, ) - raise JudgmentAPIError(f"Failed to check if scorer exists: {e.error_detail}") + raise JudgmentAPIError( + status_code=e.status_code, + detail=f"Failed to check if scorer exists: {e.detail}", + response=e.response, + ) class PromptScorer(APIScorerConfig): @@ -102,7 +125,7 @@ def create( cls, name: str, prompt: str, - threshold: Optional[float] = 0.5, + threshold: float = 0.5, options: Optional[Dict[str, float]] = None, judgment_api_key: str = os.getenv("JUDGMENT_API_KEY") or "", organization_id: str = os.getenv("JUDGMENT_ORG_ID") or "", @@ -122,7 +145,9 @@ def create( ) else: raise JudgmentAPIError( - f"Scorer with name {name} already exists. Either use the existing scorer with the get() method or use a new name." + status_code=400, + detail=f"Scorer with name {name} already exists. Either use the existing scorer with the get() method or use a new name.", + response=None, # type: ignore ) # Setter functions. Each setter function pushes the scorer to the DB. diff --git a/src/judgeval/scorers/score.py b/src/judgeval/scorers/score.py index 72cf5270..10088266 100644 --- a/src/judgeval/scorers/score.py +++ b/src/judgeval/scorers/score.py @@ -15,9 +15,9 @@ ) from judgeval.scorers import BaseScorer from judgeval.scorers.utils import clone_scorers -from judgeval.common.logger import judgeval_logger +from judgeval.logger import judgeval_logger from judgeval.judges import JudgevalJudge -from judgeval.constants import DEFAULT_GPT_MODEL +from judgeval.env import JUDGMENT_DEFAULT_GPT_MODEL async def safe_a_score_example( @@ -56,7 +56,7 @@ async def safe_a_score_example( async def a_execute_scoring( examples: List[Example], scorers: List[BaseScorer], - model: Optional[Union[str, List[str], JudgevalJudge]] = DEFAULT_GPT_MODEL, + model: Optional[Union[str, List[str], JudgevalJudge]] = JUDGMENT_DEFAULT_GPT_MODEL, ignore_errors: bool = False, throttle_value: int = 0, max_concurrent: int = 100, diff --git a/src/judgeval/tracer/__init__.py b/src/judgeval/tracer/__init__.py index 0f45cb32..6f8d7887 100644 --- a/src/judgeval/tracer/__init__.py +++ b/src/judgeval/tracer/__init__.py @@ -1,3 +1,753 @@ -from judgeval.common.tracer import Tracer, wrap, TraceClient, TraceManagerClient +from __future__ import annotations +from contextvars import ContextVar +import atexit +import functools +import inspect +import random +from typing import ( + Any, + Union, + Callable, + Dict, + List, + MutableMapping, + Optional, + Tuple, + Type, + TypeVar, + overload, + Literal, + TypedDict, +) +from functools import partial +from warnings import warn -__all__ = ["Tracer", "wrap", "TraceClient", "TraceManagerClient"] +from opentelemetry.context import Context +from opentelemetry.sdk.trace import SpanProcessor, TracerProvider +from opentelemetry.sdk.resources import Resource +from opentelemetry.trace import ( + Status, + StatusCode, + TracerProvider as ABCTracerProvider, + NoOpTracerProvider, + Tracer as ABCTracer, + get_current_span, +) + +from judgeval.data.evaluation_run import EvaluationRun +from judgeval.data.example import Example +from judgeval.env import ( + JUDGMENT_API_KEY, + JUDGMENT_DEFAULT_GPT_MODEL, + JUDGMENT_ORG_ID, +) +from judgeval.logger import judgeval_logger +from judgeval.scorers.api_scorer import APIScorerConfig +from judgeval.scorers.base_scorer import BaseScorer +from judgeval.tracer.constants import JUDGEVAL_TRACER_INSTRUMENTING_MODULE_NAME +from judgeval.tracer.managers import ( + sync_span_context, + sync_agent_context, + async_agent_context, +) +from judgeval.utils.serialize import safe_serialize +from judgeval.version import get_version +from judgeval.warnings import JudgmentWarning + +from judgeval.tracer.keys import AttributeKeys, ResourceKeys +from judgeval.api import JudgmentSyncClient +from judgeval.tracer.llm import wrap_provider +from judgeval.utils.url import url_for +from judgeval.tracer.local_eval_queue import LocalEvaluationQueue +from judgeval.tracer.processors import ( + JudgmentSpanProcessor, + NoOpJudgmentSpanProcessor, +) + +C = TypeVar("C", bound=Callable) +Cls = TypeVar("Cls", bound=Type) +ApiClient = TypeVar("ApiClient", bound=Any) + + +class AgentContext(TypedDict): + agent_id: str + class_name: str | None + instance_name: str | None + track_state: bool + track_attributes: List[str] | None + field_mappings: Dict[str, str] + instance: Any + is_agent_entry_point: bool + parent_agent_id: str | None + + +def resolve_project_id( + api_key: str, organization_id: str, project_name: str +) -> str | None: + try: + client = JudgmentSyncClient( + api_key=api_key, + organization_id=organization_id, + ) + return client.projects_resolve({"project_name": project_name})["project_id"] + except Exception: + return None + + +class Tracer: + _active_tracers: MutableMapping[str, Tracer] = {} + + __slots__ = ( + "api_key", + "organization_id", + "project_name", + "api_url", + "deep_tracing", + "enable_monitoring", + "enable_evaluation", + "api_client", + "local_eval_queue", + # Otel + "judgment_processor", + "processors", + "provider", + "tracer", + "context", + # Agent + "agent_context", + "cost_context", + ) + + api_key: str + organization_id: str + project_name: str + api_url: str + deep_tracing: bool + enable_monitoring: bool + enable_evaluation: bool + api_client: JudgmentSyncClient + local_eval_queue: LocalEvaluationQueue + + judgment_processor: JudgmentSpanProcessor + processors: List[SpanProcessor] + provider: ABCTracerProvider + tracer: ABCTracer + context: ContextVar[Context] + + agent_context: ContextVar[Optional[AgentContext]] + cost_context: ContextVar[Optional[Dict[str, float]]] + + def __init__( + self, + /, + *, + project_name: str, + api_key: Optional[str] = None, + organization_id: Optional[str] = None, + deep_tracing: bool = False, + enable_monitoring: bool = True, + enable_evaluation: bool = True, + processors: List[SpanProcessor] = [], + resource_attributes: Optional[Dict[str, Any]] = None, + ): + _api_key = api_key or JUDGMENT_API_KEY + _organization_id = organization_id or JUDGMENT_ORG_ID + + if _api_key is None: + raise ValueError( + "API Key is not set, please set it in the environment variables or pass it as `api_key`" + ) + + if _organization_id is None: + raise ValueError( + "Organization ID is not set, please set it in the environment variables or pass it as `organization_id`" + ) + + self.api_key = _api_key + self.organization_id = _organization_id + self.project_name = project_name + self.api_url = url_for("/otel/v1/traces") + + self.deep_tracing = deep_tracing + self.enable_monitoring = enable_monitoring + self.enable_evaluation = enable_evaluation + + self.judgment_processor = NoOpJudgmentSpanProcessor() + self.processors = processors + self.provider = NoOpTracerProvider() + + # TODO: + self.context = ContextVar(f"judgeval:tracer:{project_name}") + + self.agent_context = ContextVar("current_agent_context", default=None) + self.cost_context = ContextVar("current_cost_context", default=None) + + if self.enable_monitoring: + project_id = resolve_project_id( + self.api_key, self.organization_id, self.project_name + ) + + resource_attributes = resource_attributes or {} + resource_attributes.update( + { + ResourceKeys.SERVICE_NAME: self.project_name, + ResourceKeys.TELEMETRY_SDK_NAME: "judgeval", + ResourceKeys.TELEMETRY_SDK_VERSION: get_version(), + } + ) + + if project_id is not None: + resource_attributes[ResourceKeys.JUDGMENT_PROJECT_ID] = project_id + else: + judgeval_logger.error( + f"Failed to resolve project {self.project_name}, please create it first at https://app.judgmentlabs.ai/projects. Skipping Judgment export." + ) + + resource = Resource.create(resource_attributes) + + self.judgment_processor = JudgmentSpanProcessor( + self.api_url, + self.api_key, + self.organization_id, + max_queue_size=2**18, + export_timeout_millis=30000, + ) + self.processors.append(self.judgment_processor) + self.provider = TracerProvider(resource=resource) + for processor in self.processors: + self.provider.add_span_processor(processor) + + self.tracer = self.provider.get_tracer( + JUDGEVAL_TRACER_INSTRUMENTING_MODULE_NAME, + get_version(), + ) + self.api_client = JudgmentSyncClient( + api_key=self.api_key, + organization_id=self.organization_id, + ) + self.local_eval_queue = LocalEvaluationQueue() + + if self.enable_evaluation and self.enable_monitoring: + self.local_eval_queue.start_workers() + + Tracer._active_tracers[self.project_name] = self + + # Register atexit handler to flush on program exit + atexit.register(self._atexit_flush) + + def get_current_span(self): + # TODO: review, need to maintain context var manually if we dont + # want to override the default tracer provider + return get_current_span() + + def get_tracer(self): + return self.tracer + + def get_current_agent_context(self): + return self.agent_context + + def get_current_cost_context(self): + return self.cost_context + + def set_customer_id(self, customer_id: str) -> None: + span = get_current_span() + if span and span.is_recording(): + span.set_attribute(AttributeKeys.JUDGMENT_CUSTOMER_ID, customer_id) + + def add_cost_to_current_context(self, cost: float) -> None: + """Add cost to the current cost context and update span attribute.""" + current_cost_context = self.cost_context.get() + if current_cost_context is not None: + current_cumulative_cost = current_cost_context.get("cumulative_cost", 0.0) + new_cumulative_cost = float(current_cumulative_cost) + cost + current_cost_context["cumulative_cost"] = new_cumulative_cost + + span = get_current_span() + if span and span.is_recording(): + span.set_attribute( + AttributeKeys.JUDGMENT_CUMULATIVE_LLM_COST, new_cumulative_cost + ) + + def add_agent_attributes_to_span(self, span): + """Add agent ID, class name, and instance name to span if they exist in context""" + current_agent_context = self.agent_context.get() + if not current_agent_context: + return + + span.set_attribute( + AttributeKeys.JUDGMENT_AGENT_ID, current_agent_context["agent_id"] + ) + if current_agent_context["class_name"] is not None: + span.set_attribute( + AttributeKeys.JUDGMENT_AGENT_CLASS_NAME, + current_agent_context["class_name"], + ) + if current_agent_context["instance_name"] is not None: + span.set_attribute( + AttributeKeys.JUDGMENT_AGENT_INSTANCE_NAME, + current_agent_context["instance_name"], + ) + if current_agent_context["parent_agent_id"] is not None: + span.set_attribute( + AttributeKeys.JUDGMENT_PARENT_AGENT_ID, + current_agent_context["parent_agent_id"], + ) + span.set_attribute( + AttributeKeys.JUDGMENT_IS_AGENT_ENTRY_POINT, + current_agent_context["is_agent_entry_point"], + ) + current_agent_context["is_agent_entry_point"] = False + + def record_instance_state(self, record_point: Literal["before", "after"], span): + current_agent_context = self.agent_context.get() + + if current_agent_context and current_agent_context.get("track_state"): + instance = current_agent_context.get("instance") + track_attributes = current_agent_context.get("track_attributes") + field_mappings = current_agent_context.get("field_mappings", {}) + + if track_attributes is not None: + attributes = { + field_mappings.get(attr, attr): getattr(instance, attr, None) + for attr in track_attributes + } + else: + attributes = { + field_mappings.get(k, k): v + for k, v in instance.__dict__.items() + if not k.startswith("_") + } + span.set_attribute( + ( + AttributeKeys.JUDGMENT_STATE_BEFORE + if record_point == "before" + else AttributeKeys.JUDGMENT_STATE_AFTER + ), + safe_serialize(attributes), + ) + + def _wrap_sync( + self, f: Callable, name: Optional[str], attributes: Optional[Dict[str, Any]] + ): + @functools.wraps(f) + def wrapper(*args, **kwargs): + n = name or f.__qualname__ + with sync_span_context(self, n, attributes) as span: + self.add_agent_attributes_to_span(span) + self.record_instance_state("before", span) + try: + span.set_attribute( + AttributeKeys.JUDGMENT_INPUT, + safe_serialize(format_inputs(f, args, kwargs)), + ) + + self.judgment_processor.emit_partial() + + result = f(*args, **kwargs) + except Exception as user_exc: + span.record_exception(user_exc) + span.set_status(Status(StatusCode.ERROR, str(user_exc))) + raise + if span is not None: + span.set_attribute( + AttributeKeys.JUDGMENT_OUTPUT, + safe_serialize(result), + ) + self.record_instance_state("after", span) + return result + + return wrapper + + def _wrap_async( + self, f: Callable, name: Optional[str], attributes: Optional[Dict[str, Any]] + ): + @functools.wraps(f) + async def wrapper(*args, **kwargs): + n = name or f.__qualname__ + with sync_span_context(self, n, attributes) as span: + self.add_agent_attributes_to_span(span) + self.record_instance_state("before", span) + try: + span.set_attribute( + AttributeKeys.JUDGMENT_INPUT, + safe_serialize(format_inputs(f, args, kwargs)), + ) + + self.judgment_processor.emit_partial() + + result = await f(*args, **kwargs) + except Exception as user_exc: + span.record_exception(user_exc) + span.set_status(Status(StatusCode.ERROR, str(user_exc))) + raise + if span is not None: + span.set_attribute( + AttributeKeys.JUDGMENT_OUTPUT, + safe_serialize(result), + ) + self.record_instance_state("after", span) + return result + + return wrapper + + @overload + def observe(self, func: C, /, *, span_type: str | None = None) -> C: ... + + @overload + def observe( + self, func: None = None, /, *, span_type: str | None = None + ) -> Callable[[C], C]: ... + + def observe( + self, func: Callable | None = None, /, *, span_type: str | None = "span" + ) -> Callable | None: + if func is None: + return partial(self.observe, span_type=span_type) + + if not self.enable_monitoring: + return func + + name = func.__qualname__ + attributes: Dict[str, Any] = { + AttributeKeys.JUDGMENT_SPAN_KIND: span_type, + } + + if inspect.iscoroutinefunction(func): + return self._wrap_async(func, name, attributes) + else: + return self._wrap_sync(func, name, attributes) + + @overload + def agent( + self, + func: C, + /, + *, + identifier: str | None = None, + track_state: bool = False, + track_attributes: List[str] | None = None, + field_mappings: Dict[str, str] = {}, + ) -> C: ... + + @overload + def agent( + self, + func: None = None, + /, + *, + identifier: str | None = None, + track_state: bool = False, + track_attributes: List[str] | None = None, + field_mappings: Dict[str, str] = {}, + ) -> Callable[[C], C]: ... + + def agent( + self, + func: Callable | None = None, + /, + *, + identifier: str | None = None, + track_state: bool = False, + track_attributes: List[str] | None = None, + field_mappings: Dict[str, str] = {}, + ) -> Callable | None: + """ + Agent decorator that creates an agent ID and propagates it to child spans. + Also captures and propagates the class name if the decorated function is a method. + Optionally captures instance name based on the specified identifier attribute. + + This decorator should be used in combination with @observe decorator: + + class MyAgent: + def __init__(self, name): + self.name = name + + @judgment.agent(identifier="name") + @judgment.observe(span_type="function") + def my_agent_method(self): + # This span and all child spans will have: + # - agent_id: auto-generated UUID + # - class_name: "MyAgent" + # - instance_name: self.name value + pass + + Args: + identifier: Name of the instance attribute to use as the instance name + """ + if func is None: + return partial( + self.agent, + identifier=identifier, + track_state=track_state, + track_attributes=track_attributes, + field_mappings=field_mappings, + ) + + if not self.enable_monitoring: + return func + + class_name = None + if hasattr(func, "__qualname__") and "." in func.__qualname__: + parts = func.__qualname__.split(".") + if len(parts) >= 2: + class_name = parts[-2] + + if inspect.iscoroutinefunction(func): + + @functools.wraps(func) + async def async_wrapper(*args, **kwargs): + async with async_agent_context( + tracer=self, + args=args, + class_name=class_name, + identifier=identifier, + track_state=track_state, + track_attributes=track_attributes, + field_mappings=field_mappings, + ): + return await func(*args, **kwargs) + + return async_wrapper + else: + + @functools.wraps(func) + def sync_wrapper(*args, **kwargs): + with sync_agent_context( + tracer=self, + args=args, + class_name=class_name, + identifier=identifier, + track_state=track_state, + track_attributes=track_attributes, + field_mappings=field_mappings, + ): + return func(*args, **kwargs) + + return sync_wrapper + + @overload + def observe_tools( + self, + cls: Cls, + /, + *, + exclude_methods: List[str] = [], + include_private: bool = False, + ) -> Cls: ... + + @overload + def observe_tools( + self, + cls: None = None, + /, + *, + exclude_methods: List[str] = [], + include_private: bool = False, + ) -> Callable[[Cls], Cls]: ... + + def observe_tools( + self, + cls: Cls | None = None, + /, + *, + exclude_methods: List[str] = [], + include_private: bool = False, + ) -> Cls | Callable[[Cls], Cls]: + if cls is None: + return partial( + self.observe_tools, + exclude_methods=exclude_methods, + include_private=include_private, + ) + return cls + + def wrap(self, client: ApiClient) -> ApiClient: + return wrap_provider(self, client) + + def force_flush(self, timeout_millis: int = 30000) -> bool: + """Force flush all pending spans and block until completion. + + Args: + timeout_millis: Maximum time to wait for flush completion in milliseconds + + Returns: + True if all processors flushed successfully within timeout, False otherwise + """ + success = True + for processor in self.processors: + try: + result = processor.force_flush(timeout_millis) + if not result: + success = False + except Exception as e: + judgeval_logger.warning(f"Error flushing processor {processor}: {e}") + success = False + return success + + def _atexit_flush(self) -> None: + """Internal method called on program exit to flush remaining spans. + + This blocks until all spans are flushed or timeout is reached to ensure + proper cleanup before program termination. + """ + try: + success = self.force_flush(timeout_millis=30000) + if not success: + judgeval_logger.warning( + "Some spans may not have been exported before program exit" + ) + except Exception as e: + judgeval_logger.warning(f"Error during atexit flush: {e}") + + def async_evaluate( + self, + /, + *, + scorer: Union[APIScorerConfig, BaseScorer], + example: Example, + model: str = JUDGMENT_DEFAULT_GPT_MODEL, + sampling_rate: float = 1.0, + ): + if not self.enable_evaluation or not self.enable_monitoring: + judgeval_logger.info("Evaluation is not enabled, skipping evaluation") + return + + if not isinstance(scorer, (APIScorerConfig, BaseScorer)): + judgeval_logger.error( + "Scorer must be an instance of APIScorerConfig or BaseScorer, got %s, skipping evaluation." + % type(scorer) + ) + return + + if not isinstance(example, Example): + judgeval_logger.error( + "Example must be an instance of Example, got %s, skipping evaluation." + % type(example) + ) + return + + if sampling_rate < 0 or sampling_rate > 1: + judgeval_logger.error( + "Sampling rate must be between 0 and 1, got %s, skipping evaluation." + % sampling_rate + ) + return + + percentage = random.uniform(0, 1) + if percentage > sampling_rate: + judgeval_logger.info( + "Sampling rate is %s, skipping evaluation." % sampling_rate + ) + return + + span_context = self.get_current_span().get_span_context() + trace_id = format(span_context.trace_id, "032x") + span_id = format(span_context.span_id, "016x") + hosted_scoring = isinstance(scorer, APIScorerConfig) or ( + isinstance(scorer, BaseScorer) and scorer.server_hosted + ) + eval_run_name = f"async_evaluate_{span_id}" # note this name doesnt matter because we don't save the experiment only the example and scorer_data + if hosted_scoring: + eval_run = EvaluationRun( + organization_id=self.organization_id, + project_name=self.project_name, + eval_name=eval_run_name, + examples=[example], + scorers=[scorer], + model=model, + trace_span_id=span_id, + trace_id=trace_id, + ) + + self.api_client.add_to_run_eval_queue(eval_run.model_dump(warnings=False)) # type: ignore + else: + # Handle custom scorers using local evaluation queue + eval_run = EvaluationRun( + organization_id=self.organization_id, + project_name=self.project_name, + eval_name=eval_run_name, + examples=[example], + scorers=[scorer], + model=model, + trace_span_id=span_id, + trace_id=trace_id, + ) + + # Enqueue the evaluation run to the local evaluation queue + self.local_eval_queue.enqueue(eval_run) + + def wait_for_completion(self, timeout: Optional[float] = 30.0) -> bool: + """Wait for all evaluations and span processing to complete. + + This method blocks until all queued evaluations are processed and + all pending spans are flushed to the server. + + Args: + timeout: Maximum time to wait in seconds. Defaults to 30 seconds. + None means wait indefinitely. + + Returns: + True if all processing completed within the timeout, False otherwise. + + """ + try: + judgeval_logger.debug( + "Waiting for all evaluations and spans to complete..." + ) + + # Wait for all queued evaluation work to complete + eval_completed = self.local_eval_queue.wait_for_completion() + if not eval_completed: + judgeval_logger.warning( + f"Local evaluation queue did not complete within {timeout} seconds" + ) + return False + + self.force_flush() + + judgeval_logger.debug("All evaluations and spans completed successfully") + return True + + except Exception as e: + judgeval_logger.warning(f"Error while waiting for completion: {e}") + return False + + +def wrap(client: ApiClient) -> ApiClient: + if not Tracer._active_tracers: + warn( + "No active tracers found, client will not be wrapped. " + "You can use the global `wrap` function after creating a tracer instance. " + "Or you can use the `wrap` method on the tracer instance to directly wrap the client. ", + JudgmentWarning, + stacklevel=2, + ) + + wrapped_client = client + for tracer in Tracer._active_tracers.values(): + wrapped_client = tracer.wrap(wrapped_client) + return wrapped_client + + +def format_inputs( + f: Callable, args: Tuple[Any, ...], kwargs: Dict[str, Any] +) -> Dict[str, Any]: + try: + params = list(inspect.signature(f).parameters.values()) + inputs = {} + arg_i = 0 + for param in params: + if param.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD: + if arg_i < len(args): + inputs[param.name] = args[arg_i] + arg_i += 1 + elif param.name in kwargs: + inputs[param.name] = kwargs[param.name] + elif param.kind == inspect.Parameter.VAR_POSITIONAL: + inputs[param.name] = args[arg_i:] + arg_i = len(args) + elif param.kind == inspect.Parameter.VAR_KEYWORD: + inputs[param.name] = kwargs + return inputs + except Exception: + return {} diff --git a/src/judgeval/tracer/constants.py b/src/judgeval/tracer/constants.py new file mode 100644 index 00000000..610a45cb --- /dev/null +++ b/src/judgeval/tracer/constants.py @@ -0,0 +1 @@ +JUDGEVAL_TRACER_INSTRUMENTING_MODULE_NAME = "opentelemetry.instrumentation.judgeval" diff --git a/src/judgeval/tracer/exporters/__init__.py b/src/judgeval/tracer/exporters/__init__.py new file mode 100644 index 00000000..05708be6 --- /dev/null +++ b/src/judgeval/tracer/exporters/__init__.py @@ -0,0 +1,37 @@ +from opentelemetry.sdk.trace.export import ( + SpanExportResult, + SpanExporter, +) +from opentelemetry.sdk.trace import ReadableSpan +from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter +from typing import Sequence + +from judgeval.tracer.exporters.store import ABCSpanStore +from judgeval.tracer.exporters.s3 import S3Exporter +from judgeval.tracer.exporters.utils import deduplicate_spans + + +class JudgmentSpanExporter(OTLPSpanExporter): + def __init__(self, endpoint: str, api_key: str, organization_id: str): + super().__init__( + endpoint=endpoint, + headers={ + "Authorization": f"Bearer {api_key}", + "X-Organization-Id": organization_id, + }, + ) + + def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult: + return super().export(deduplicate_spans(spans)) + + +class InMemorySpanExporter(SpanExporter): + def __init__(self, store: ABCSpanStore): + self.store = store + + def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult: + self.store.add(*spans) + return SpanExportResult.SUCCESS + + +__all__ = ("JudgmentSpanExporter", "InMemorySpanExporter", "S3Exporter") diff --git a/src/judgeval/tracer/exporters/s3.py b/src/judgeval/tracer/exporters/s3.py new file mode 100644 index 00000000..d571644c --- /dev/null +++ b/src/judgeval/tracer/exporters/s3.py @@ -0,0 +1,119 @@ +from __future__ import annotations + +from concurrent.futures import ThreadPoolExecutor, as_completed +from datetime import datetime +from typing import Literal, Sequence, Optional, TYPE_CHECKING, cast +import boto3 +from botocore.client import Config + +from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult +from opentelemetry.sdk.trace import ReadableSpan +from judgeval.env import ( + JUDGMENT_S3_ACCESS_KEY_ID, + JUDGMENT_S3_SECRET_ACCESS_KEY, + JUDGMENT_S3_REGION_NAME, + JUDGMENT_S3_BUCKET_NAME, + JUDGMENT_S3_PREFIX, + JUDGMENT_S3_ENDPOINT_URL, + JUDGMENT_S3_SIGNATURE_VERSION, + JUDGMENT_S3_ADDRESSING_STYLE, +) +from judgeval.exceptions import JudgmentRuntimeError +from judgeval.logger import judgeval_logger + +if TYPE_CHECKING: + from mypy_boto3_s3.client import S3Client + + +class S3Exporter(SpanExporter): + __slots__ = ("bucket_name", "prefix", "s3_client") + + bucket_name: str + prefix: str + s3_client: S3Client + + def __init__( + self, + bucket_name: Optional[str] = JUDGMENT_S3_BUCKET_NAME, + region_name: Optional[str] = JUDGMENT_S3_REGION_NAME, + prefix: str = JUDGMENT_S3_PREFIX, + s3_access_key_id: Optional[str] = JUDGMENT_S3_ACCESS_KEY_ID, + s3_secret_access_key: Optional[str] = JUDGMENT_S3_SECRET_ACCESS_KEY, + endpoint_url: Optional[str] = JUDGMENT_S3_ENDPOINT_URL, + # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html + signature_version: str = JUDGMENT_S3_SIGNATURE_VERSION, + addressing_style: str = JUDGMENT_S3_ADDRESSING_STYLE, + batch_size: int = 8, + ): + if not bucket_name: + raise JudgmentRuntimeError("JUDGMENT_S3_BUCKET_NAME is not set") + + if not region_name: + raise JudgmentRuntimeError("JUDGMENT_S3_REGION_NAME is not set") + + if addressing_style not in ["auto", "virtual", "path"]: + raise JudgmentRuntimeError(f"Invalid addressing style: {addressing_style}") + addressing_style = cast(Literal["auto", "virtual", "path"], addressing_style) + + self.bucket_name = bucket_name + self.prefix = prefix.rstrip("/") + self.batch_size = batch_size + + self.s3_client = boto3.client( + "s3", + config=Config( + signature_version=signature_version, + s3={"addressing_style": addressing_style}, + ), + aws_access_key_id=s3_access_key_id, + aws_secret_access_key=s3_secret_access_key, + endpoint_url=endpoint_url, + region_name=region_name, + ) + + def _upload_span(self, span: ReadableSpan) -> tuple[bool, str]: + """Upload a single span to S3. Returns (success, key).""" + try: + span_context = span.get_span_context() + if not span_context: + return False, "" + + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S%f") + trace_id = format(span_context.trace_id, "032x") + span_id = format(span_context.span_id, "016x") + key = f"{self.prefix}/{trace_id}/{span_id}/{timestamp}.json" + + span_json = span.to_json(indent=0) + + self.s3_client.put_object( + Bucket=self.bucket_name, + Key=key, + Body=span_json, + ContentType="application/json", + ) + return True, key + except Exception as e: + judgeval_logger.error( + f"Error uploading span {span_context.span_id if span_context else 'unknown'}: {e}" + ) + return False, "" + + def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult: + if not spans: + return SpanExportResult.SUCCESS + + try: + with ThreadPoolExecutor( + max_workers=min(len(spans), self.batch_size) + ) as executor: + futures = [executor.submit(self._upload_span, span) for span in spans] + + for future in as_completed(futures): + success, key = future.result() + if not success: + return SpanExportResult.FAILURE + return SpanExportResult.SUCCESS + + except Exception as e: + judgeval_logger.error(f"Error exporting spans to S3: {e}") + return SpanExportResult.FAILURE diff --git a/src/judgeval/tracer/exporters/store.py b/src/judgeval/tracer/exporters/store.py new file mode 100644 index 00000000..c9ab0e9b --- /dev/null +++ b/src/judgeval/tracer/exporters/store.py @@ -0,0 +1,43 @@ +from abc import ABC, abstractmethod +from typing import List + +from opentelemetry.sdk.trace import ReadableSpan + + +class ABCSpanStore(ABC): + @abstractmethod + def add(self, *spans: ReadableSpan): ... + + @abstractmethod + def get(self, id: str) -> ReadableSpan: ... + + @abstractmethod + def get_all(self) -> List[ReadableSpan]: ... + + +class SpanStore(ABCSpanStore): + __slots__ = ("spans",) + + spans: List[ReadableSpan] + + def __init__(self): + self.spans = [] + + def add(self, *spans: ReadableSpan): + self.spans.extend(spans) + + def get(self, id: str) -> ReadableSpan: + for span in self.spans: + context = span.get_span_context() + if context is None: + continue + if context.span_id == id: + return span + + raise ValueError(f"Span with id {id} not found") + + def get_all(self) -> List[ReadableSpan]: + return self.spans + + def __repr__(self) -> str: + return f"SpanStore(spans={self.spans})" diff --git a/src/judgeval/tracer/exporters/utils.py b/src/judgeval/tracer/exporters/utils.py new file mode 100644 index 00000000..8d86de73 --- /dev/null +++ b/src/judgeval/tracer/exporters/utils.py @@ -0,0 +1,32 @@ +from typing import Sequence +from opentelemetry.sdk.trace import ReadableSpan + +from judgeval.tracer.keys import AttributeKeys + + +def deduplicate_spans(spans: Sequence[ReadableSpan]) -> Sequence[ReadableSpan]: + spans_by_key: dict[tuple[int, int], ReadableSpan] = {} + for span in spans: + if span.attributes and span.context: + update_id = span.attributes.get(AttributeKeys.JUDGMENT_UPDATE_ID) + + if not isinstance(update_id, int): + continue + + key = (span.context.trace_id, span.context.span_id) + if key not in spans_by_key: + spans_by_key[key] = span + else: + existing_attrs = spans_by_key[key].attributes + existing_update_id = ( + existing_attrs.get(AttributeKeys.JUDGMENT_UPDATE_ID, 0) + if existing_attrs + else 0 + ) + if ( + isinstance(existing_update_id, (int, float)) + and update_id > existing_update_id + ): + spans_by_key[key] = span + + return list(spans_by_key.values()) diff --git a/src/judgeval/tracer/keys.py b/src/judgeval/tracer/keys.py new file mode 100644 index 00000000..85f86a8f --- /dev/null +++ b/src/judgeval/tracer/keys.py @@ -0,0 +1,53 @@ +""" +Identifiers used by Judgeval to store specific types of data in the spans. +""" + +from opentelemetry.semconv.resource import ResourceAttributes +from opentelemetry.semconv._incubating.attributes import gen_ai_attributes +from enum import Enum + + +class AttributeKeys(str, Enum): + # General function tracing attributes (custom namespace) + JUDGMENT_SPAN_KIND = "judgment.span_kind" + JUDGMENT_INPUT = "judgment.input" + JUDGMENT_OUTPUT = "judgment.output" + JUDGMENT_OFFLINE_MODE = "judgment.offline_mode" + + # Custom tracking attributes + JUDGMENT_CUSTOMER_ID = "judgment.customer_id" + + # Agent specific attributes (custom namespace) + JUDGMENT_AGENT_ID = "judgment.agent_id" + JUDGMENT_PARENT_AGENT_ID = "judgment.parent_agent_id" + JUDGMENT_AGENT_CLASS_NAME = "judgment.agent_class_name" + JUDGMENT_AGENT_INSTANCE_NAME = "judgment.agent_instance_name" + JUDGMENT_IS_AGENT_ENTRY_POINT = "judgment.is_agent_entry_point" + JUDGMENT_CUMULATIVE_LLM_COST = "judgment.cumulative_llm_cost" + JUDGMENT_STATE_BEFORE = "judgment.state_before" + JUDGMENT_STATE_AFTER = "judgment.state_after" + JUDGMENT_UPDATE_ID = "judgment.update_id" + + # GenAI-specific attributes (semantic conventions) + GEN_AI_PROMPT = gen_ai_attributes.GEN_AI_PROMPT + GEN_AI_COMPLETION = gen_ai_attributes.GEN_AI_COMPLETION + GEN_AI_REQUEST_MODEL = gen_ai_attributes.GEN_AI_REQUEST_MODEL + GEN_AI_RESPONSE_MODEL = gen_ai_attributes.GEN_AI_RESPONSE_MODEL + GEN_AI_SYSTEM = gen_ai_attributes.GEN_AI_SYSTEM + GEN_AI_USAGE_INPUT_TOKENS = gen_ai_attributes.GEN_AI_USAGE_INPUT_TOKENS + GEN_AI_USAGE_OUTPUT_TOKENS = gen_ai_attributes.GEN_AI_USAGE_OUTPUT_TOKENS + GEN_AI_USAGE_COMPLETION_TOKENS = gen_ai_attributes.GEN_AI_USAGE_COMPLETION_TOKENS + GEN_AI_REQUEST_TEMPERATURE = gen_ai_attributes.GEN_AI_REQUEST_TEMPERATURE + GEN_AI_REQUEST_MAX_TOKENS = gen_ai_attributes.GEN_AI_REQUEST_MAX_TOKENS + GEN_AI_RESPONSE_FINISH_REASONS = gen_ai_attributes.GEN_AI_RESPONSE_FINISH_REASONS + + # GenAI-specific attributes (custom namespace) + GEN_AI_USAGE_TOTAL_COST = "gen_ai.usage.total_cost_usd" + + +class ResourceKeys(str, Enum): + SERVICE_NAME = ResourceAttributes.SERVICE_NAME + TELEMETRY_SDK_LANGUAGE = ResourceAttributes.TELEMETRY_SDK_LANGUAGE + TELEMETRY_SDK_NAME = ResourceAttributes.TELEMETRY_SDK_NAME + TELEMETRY_SDK_VERSION = ResourceAttributes.TELEMETRY_SDK_VERSION + JUDGMENT_PROJECT_ID = "judgment.project_id" diff --git a/src/judgeval/tracer/llm/__init__.py b/src/judgeval/tracer/llm/__init__.py new file mode 100644 index 00000000..e464b64d --- /dev/null +++ b/src/judgeval/tracer/llm/__init__.py @@ -0,0 +1,557 @@ +from __future__ import annotations +import functools +from typing import Tuple, Optional, Any, TYPE_CHECKING +from functools import wraps +from judgeval.data.trace import TraceUsage +from judgeval.logger import judgeval_logger +from litellm.cost_calculator import cost_per_token as _original_cost_per_token + +from judgeval.tracer.llm.providers import ( + HAS_OPENAI, + HAS_TOGETHER, + HAS_ANTHROPIC, + HAS_GOOGLE_GENAI, + HAS_GROQ, + ApiClient, +) +from judgeval.tracer.managers import sync_span_context, async_span_context +from judgeval.tracer.keys import AttributeKeys +from judgeval.utils.serialize import safe_serialize + +if TYPE_CHECKING: + from judgeval.tracer import Tracer + + +@wraps(_original_cost_per_token) +def cost_per_token( + *args: Any, **kwargs: Any +) -> Tuple[Optional[float], Optional[float]]: + try: + prompt_tokens_cost_usd_dollar, completion_tokens_cost_usd_dollar = ( + _original_cost_per_token(*args, **kwargs) + ) + if ( + prompt_tokens_cost_usd_dollar == 0 + and completion_tokens_cost_usd_dollar == 0 + ): + judgeval_logger.warning("LiteLLM returned a total of 0 for cost per token") + return prompt_tokens_cost_usd_dollar, completion_tokens_cost_usd_dollar + except Exception as e: + judgeval_logger.warning(f"Error calculating cost per token: {e}") + return None, None + + +def wrap_provider(tracer: Tracer, client: ApiClient) -> ApiClient: + """ + Wraps an API client to add tracing capabilities. + Supports OpenAI, Together, Anthropic, Google GenAI, and Groq clients. + """ + + def wrapped(function, span_name): + @functools.wraps(function) + def wrapper(*args, **kwargs): + with sync_span_context( + tracer, span_name, {AttributeKeys.JUDGMENT_SPAN_KIND: "llm"} + ) as span: + tracer.add_agent_attributes_to_span(span) + span.set_attribute(AttributeKeys.GEN_AI_PROMPT, safe_serialize(kwargs)) + try: + response = function(*args, **kwargs) + output, usage = _format_output_data(client, response) + if output: + span.set_attribute(AttributeKeys.GEN_AI_COMPLETION, output) + if usage: + if usage.prompt_tokens: + span.set_attribute( + AttributeKeys.GEN_AI_USAGE_INPUT_TOKENS, + usage.prompt_tokens, + ) + if usage.completion_tokens: + span.set_attribute( + AttributeKeys.GEN_AI_USAGE_OUTPUT_TOKENS, + usage.completion_tokens, + ) + span.set_attribute( + AttributeKeys.GEN_AI_USAGE_COMPLETION_TOKENS, + usage.completion_tokens, + ) + if usage.total_cost_usd: + span.set_attribute( + AttributeKeys.GEN_AI_USAGE_TOTAL_COST, + usage.total_cost_usd, + ) + # Add cost to cumulative context tracking + tracer.add_cost_to_current_context(usage.total_cost_usd) + return response + except Exception as e: + span.record_exception(e) + raise + + return wrapper + + def wrapped_async(function, span_name): + @functools.wraps(function) + async def wrapper(*args, **kwargs): + async with async_span_context( + tracer, span_name, {AttributeKeys.JUDGMENT_SPAN_KIND: "llm"} + ) as span: + tracer.add_agent_attributes_to_span(span) + span.set_attribute(AttributeKeys.GEN_AI_PROMPT, safe_serialize(kwargs)) + try: + response = await function(*args, **kwargs) + output, usage = _format_output_data(client, response) + if output: + span.set_attribute(AttributeKeys.GEN_AI_COMPLETION, output) + if usage: + if usage.prompt_tokens: + span.set_attribute( + AttributeKeys.GEN_AI_USAGE_INPUT_TOKENS, + usage.prompt_tokens, + ) + if usage.completion_tokens: + span.set_attribute( + AttributeKeys.GEN_AI_USAGE_OUTPUT_TOKENS, + usage.completion_tokens, + ) + span.set_attribute( + AttributeKeys.GEN_AI_USAGE_COMPLETION_TOKENS, + usage.completion_tokens, + ) + if usage.total_cost_usd: + span.set_attribute( + AttributeKeys.GEN_AI_USAGE_TOTAL_COST, + usage.total_cost_usd, + ) + tracer.add_cost_to_current_context(usage.total_cost_usd) + return response + except Exception as e: + span.record_exception(e) + raise + + return wrapper + + if HAS_OPENAI: + from judgeval.tracer.llm.providers import openai_OpenAI, openai_AsyncOpenAI + + assert openai_OpenAI is not None, "OpenAI client not found" + assert openai_AsyncOpenAI is not None, "OpenAI async client not found" + span_name = "OPENAI_API_CALL" + if isinstance(client, openai_OpenAI): + setattr( + client.chat.completions, + "create", + wrapped(client.chat.completions.create, span_name), + ) + setattr( + client.responses, "create", wrapped(client.responses.create, span_name) + ) + setattr( + client.beta.chat.completions, + "parse", + wrapped(client.beta.chat.completions.parse, span_name), + ) + elif isinstance(client, openai_AsyncOpenAI): + setattr( + client.chat.completions, + "create", + wrapped_async(client.chat.completions.create, span_name), + ) + setattr( + client.responses, + "create", + wrapped_async(client.responses.create, span_name), + ) + setattr( + client.beta.chat.completions, + "parse", + wrapped_async(client.beta.chat.completions.parse, span_name), + ) + + if HAS_TOGETHER: + from judgeval.tracer.llm.providers import ( + together_Together, + together_AsyncTogether, + ) + + assert together_Together is not None, "Together client not found" + assert together_AsyncTogether is not None, "Together async client not found" + span_name = "TOGETHER_API_CALL" + if isinstance(client, together_Together): + setattr( + client.chat.completions, + "create", + wrapped(client.chat.completions.create, span_name), + ) + elif isinstance(client, together_AsyncTogether): + setattr( + client.chat.completions, + "create", + wrapped_async(client.chat.completions.create, span_name), + ) + + if HAS_ANTHROPIC: + from judgeval.tracer.llm.providers import ( + anthropic_Anthropic, + anthropic_AsyncAnthropic, + ) + + assert anthropic_Anthropic is not None, "Anthropic client not found" + assert anthropic_AsyncAnthropic is not None, "Anthropic async client not found" + span_name = "ANTHROPIC_API_CALL" + if isinstance(client, anthropic_Anthropic): + setattr( + client.messages, "create", wrapped(client.messages.create, span_name) + ) + elif isinstance(client, anthropic_AsyncAnthropic): + setattr( + client.messages, + "create", + wrapped_async(client.messages.create, span_name), + ) + + if HAS_GOOGLE_GENAI: + from judgeval.tracer.llm.providers import ( + google_genai_Client, + google_genai_AsyncClient, + ) + + assert google_genai_Client is not None, "Google GenAI client not found" + assert google_genai_AsyncClient is not None, ( + "Google GenAI async client not found" + ) + span_name = "GOOGLE_API_CALL" + if isinstance(client, google_genai_Client): + setattr( + client.models, + "generate_content", + wrapped(client.models.generate_content, span_name), + ) + elif isinstance(client, google_genai_AsyncClient): + setattr( + client.models, + "generate_content", + wrapped_async(client.models.generate_content, span_name), + ) + + if HAS_GROQ: + from judgeval.tracer.llm.providers import groq_Groq, groq_AsyncGroq + + assert groq_Groq is not None, "Groq client not found" + assert groq_AsyncGroq is not None, "Groq async client not found" + span_name = "GROQ_API_CALL" + if isinstance(client, groq_Groq): + setattr( + client.chat.completions, + "create", + wrapped(client.chat.completions.create, span_name), + ) + elif isinstance(client, groq_AsyncGroq): + setattr( + client.chat.completions, + "create", + wrapped_async(client.chat.completions.create, span_name), + ) + + return client + + +def _format_output_data( + client: ApiClient, response: Any +) -> tuple[Optional[str], Optional[TraceUsage]]: + prompt_tokens = 0 + completion_tokens = 0 + cache_read_input_tokens = 0 + cache_creation_input_tokens = 0 + model_name = None + message_content = None + + if HAS_OPENAI: + from judgeval.tracer.llm.providers import ( + openai_OpenAI, + openai_AsyncOpenAI, + openai_ChatCompletion, + openai_Response, + openai_ParsedChatCompletion, + ) + + assert openai_OpenAI is not None, "OpenAI client not found" + assert openai_AsyncOpenAI is not None, "OpenAI async client not found" + assert openai_ChatCompletion is not None, "OpenAI chat completion not found" + assert openai_Response is not None, "OpenAI response not found" + assert openai_ParsedChatCompletion is not None, ( + "OpenAI parsed chat completion not found" + ) + + if isinstance(client, openai_OpenAI) or isinstance(client, openai_AsyncOpenAI): + if isinstance(response, openai_ChatCompletion): + model_name = response.model or "" + prompt_tokens = ( + response.usage.prompt_tokens + if response.usage and response.usage.prompt_tokens is not None + else 0 + ) + completion_tokens = ( + response.usage.completion_tokens + if response.usage and response.usage.completion_tokens is not None + else 0 + ) + cache_read_input_tokens = ( + response.usage.prompt_tokens_details.cached_tokens + if response.usage + and response.usage.prompt_tokens_details + and response.usage.prompt_tokens_details.cached_tokens is not None + else 0 + ) + + if isinstance(response, openai_ParsedChatCompletion): + message_content = response.choices[0].message.parsed + else: + message_content = response.choices[0].message.content + elif isinstance(response, openai_Response): + model_name = response.model or "" + prompt_tokens = ( + response.usage.input_tokens + if response.usage and response.usage.input_tokens is not None + else 0 + ) + completion_tokens = ( + response.usage.output_tokens + if response.usage and response.usage.output_tokens is not None + else 0 + ) + cache_read_input_tokens = ( + response.usage.input_tokens_details.cached_tokens + if response.usage + and response.usage.input_tokens_details + and response.usage.input_tokens_details.cached_tokens is not None + else 0 + ) + output0 = response.output[0] + if ( + hasattr(output0, "content") + and output0.content + and hasattr(output0.content, "__iter__") + ): # type: ignore[attr-defined] + message_content = "".join( + seg.text # type: ignore[attr-defined] + for seg in output0.content # type: ignore[attr-defined] + if hasattr(seg, "text") and seg.text + ) + + if model_name: + return message_content, _create_usage( + model_name, + prompt_tokens, + completion_tokens, + cache_read_input_tokens, + cache_creation_input_tokens, + ) + + if HAS_TOGETHER: + from judgeval.tracer.llm.providers import ( + together_Together, + together_AsyncTogether, + ) + + assert together_Together is not None, "Together client not found" + assert together_AsyncTogether is not None, "Together async client not found" + if isinstance(client, together_Together) or isinstance( + client, together_AsyncTogether + ): + model_name = (response.model or "") if hasattr(response, "model") else "" + prompt_tokens = ( + response.usage.prompt_tokens + if hasattr(response.usage, "prompt_tokens") + and response.usage.prompt_tokens is not None + else 0 + ) # type: ignore[attr-defined] + completion_tokens = ( + response.usage.completion_tokens + if hasattr(response.usage, "completion_tokens") + and response.usage.completion_tokens is not None + else 0 + ) # type: ignore[attr-defined] + message_content = ( + response.choices[0].message.content + if hasattr(response, "choices") + else None + ) # type: ignore[attr-defined] + + if model_name: + model_name = "together_ai/" + model_name + return message_content, _create_usage( + model_name, + prompt_tokens, + completion_tokens, + cache_read_input_tokens, + cache_creation_input_tokens, + ) + + if HAS_GOOGLE_GENAI: + from judgeval.tracer.llm.providers import ( + google_genai_Client, + google_genai_AsyncClient, + ) + + assert google_genai_Client is not None, "Google GenAI client not found" + assert google_genai_AsyncClient is not None, ( + "Google GenAI async client not found" + ) + if isinstance(client, google_genai_Client) or isinstance( + client, google_genai_AsyncClient + ): + model_name = getattr(response, "model_version", "") or "" + usage_metadata = getattr(response, "usage_metadata", None) + prompt_tokens = ( + usage_metadata.prompt_token_count + if usage_metadata + and hasattr(usage_metadata, "prompt_token_count") + and usage_metadata.prompt_token_count is not None + else 0 + ) + completion_tokens = ( + usage_metadata.candidates_token_count + if usage_metadata + and hasattr(usage_metadata, "candidates_token_count") + and usage_metadata.candidates_token_count is not None + else 0 + ) + message_content = ( + response.candidates[0].content.parts[0].text + if hasattr(response, "candidates") + else None + ) # type: ignore[attr-defined] + + if usage_metadata and hasattr(usage_metadata, "cached_content_token_count"): + cache_read_input_tokens = usage_metadata.cached_content_token_count or 0 + + if model_name: + return message_content, _create_usage( + model_name, + prompt_tokens, + completion_tokens, + cache_read_input_tokens, + cache_creation_input_tokens, + ) + + if HAS_ANTHROPIC: + from judgeval.tracer.llm.providers import ( + anthropic_Anthropic, + anthropic_AsyncAnthropic, + ) + + assert anthropic_Anthropic is not None, "Anthropic client not found" + assert anthropic_AsyncAnthropic is not None, "Anthropic async client not found" + if isinstance(client, anthropic_Anthropic) or isinstance( + client, anthropic_AsyncAnthropic + ): + model_name = getattr(response, "model", "") or "" + usage = getattr(response, "usage", None) + prompt_tokens = ( + usage.input_tokens + if usage + and hasattr(usage, "input_tokens") + and usage.input_tokens is not None + else 0 + ) + completion_tokens = ( + usage.output_tokens + if usage + and hasattr(usage, "output_tokens") + and usage.output_tokens is not None + else 0 + ) + cache_read_input_tokens = ( + usage.cache_read_input_tokens + if usage + and hasattr(usage, "cache_read_input_tokens") + and usage.cache_read_input_tokens is not None + else 0 + ) + cache_creation_input_tokens = ( + usage.cache_creation_input_tokens + if usage + and hasattr(usage, "cache_creation_input_tokens") + and usage.cache_creation_input_tokens is not None + else 0 + ) + message_content = ( + response.content[0].text if hasattr(response, "content") else None + ) # type: ignore[attr-defined] + + if model_name: + return message_content, _create_usage( + model_name, + prompt_tokens, + completion_tokens, + cache_read_input_tokens, + cache_creation_input_tokens, + ) + + if HAS_GROQ: + from judgeval.tracer.llm.providers import groq_Groq, groq_AsyncGroq + + assert groq_Groq is not None, "Groq client not found" + assert groq_AsyncGroq is not None, "Groq async client not found" + if isinstance(client, groq_Groq) or isinstance(client, groq_AsyncGroq): + model_name = (response.model or "") if hasattr(response, "model") else "" + prompt_tokens = ( + response.usage.prompt_tokens + if hasattr(response.usage, "prompt_tokens") + and response.usage.prompt_tokens is not None + else 0 + ) # type: ignore[attr-defined] + completion_tokens = ( + response.usage.completion_tokens + if hasattr(response.usage, "completion_tokens") + and response.usage.completion_tokens is not None + else 0 + ) # type: ignore[attr-defined] + message_content = ( + response.choices[0].message.content + if hasattr(response, "choices") + else None + ) # type: ignore[attr-defined] + + if model_name: + model_name = "groq/" + model_name + return message_content, _create_usage( + model_name, + prompt_tokens, + completion_tokens, + cache_read_input_tokens, + cache_creation_input_tokens, + ) + + judgeval_logger.warning(f"Unsupported client type: {type(client)}") + return None, None + + +def _create_usage( + model_name: str, + prompt_tokens: int, + completion_tokens: int, + cache_read_input_tokens: int = 0, + cache_creation_input_tokens: int = 0, +) -> TraceUsage: + prompt_cost, completion_cost = cost_per_token( + model=model_name, + prompt_tokens=prompt_tokens, + completion_tokens=completion_tokens, + cache_read_input_tokens=cache_read_input_tokens, + cache_creation_input_tokens=cache_creation_input_tokens, + ) + total_cost_usd = ( + (prompt_cost + completion_cost) if prompt_cost and completion_cost else None + ) + return TraceUsage( + prompt_tokens=prompt_tokens, + completion_tokens=completion_tokens, + total_tokens=prompt_tokens + completion_tokens, + cache_read_input_tokens=cache_read_input_tokens, + cache_creation_input_tokens=cache_creation_input_tokens, + prompt_tokens_cost_usd=prompt_cost, + completion_tokens_cost_usd=completion_cost, + total_cost_usd=total_cost_usd, + model_name=model_name, + ) diff --git a/src/judgeval/common/tracer/providers.py b/src/judgeval/tracer/llm/providers.py similarity index 83% rename from src/judgeval/common/tracer/providers.py rename to src/judgeval/tracer/llm/providers.py index b1ffec1b..bc555069 100644 --- a/src/judgeval/common/tracer/providers.py +++ b/src/judgeval/tracer/llm/providers.py @@ -1,12 +1,7 @@ from __future__ import annotations -import logging from typing import Any, TypeAlias -logger = logging.getLogger(__name__) -# TODO: Have functions that assert and return the relevant exports when the client is installed. -# The method should raise if the user tries to access client information that doesnt exist. - HAS_OPENAI = False openai_OpenAI = None openai_AsyncOpenAI = None @@ -35,7 +30,7 @@ together_AsyncTogether = None try: - from together import Together, AsyncTogether + from together import Together, AsyncTogether # type: ignore[import-untyped] together_Together = Together together_AsyncTogether = AsyncTogether @@ -49,7 +44,7 @@ anthropic_AsyncAnthropic = None try: - from anthropic import Anthropic, AsyncAnthropic + from anthropic import Anthropic, AsyncAnthropic # type: ignore[import-untyped] anthropic_Anthropic = Anthropic anthropic_AsyncAnthropic = AsyncAnthropic @@ -63,8 +58,8 @@ google_genai_cleint_AsyncClient = None try: - from google.genai import Client - from google.genai.client import AsyncClient + from google.genai import Client # type: ignore[import-untyped] + from google.genai.client import AsyncClient # type: ignore[import-untyped] google_genai_Client = Client google_genai_AsyncClient = AsyncClient @@ -78,7 +73,7 @@ groq_AsyncGroq = None try: - from groq import Groq, AsyncGroq + from groq import Groq, AsyncGroq # type: ignore[import-untyped] groq_Groq = Groq groq_AsyncGroq = AsyncGroq diff --git a/src/judgeval/local_eval_queue.py b/src/judgeval/tracer/local_eval_queue.py similarity index 90% rename from src/judgeval/local_eval_queue.py rename to src/judgeval/tracer/local_eval_queue.py index 020d2f8b..d59133d7 100644 --- a/src/judgeval/local_eval_queue.py +++ b/src/judgeval/tracer/local_eval_queue.py @@ -10,12 +10,14 @@ from typing import Callable, List, Optional import time -from judgeval.common.logger import judgeval_logger -from judgeval.constants import MAX_CONCURRENT_EVALUATIONS +from judgeval.logger import judgeval_logger +from judgeval.env import JUDGMENT_MAX_CONCURRENT_EVALUATIONS from judgeval.data import ScoringResult from judgeval.data.evaluation_run import EvaluationRun from judgeval.utils.async_utils import safe_run_async from judgeval.scorers.score import a_execute_scoring +from judgeval.api import JudgmentSyncClient +from judgeval.env import JUDGMENT_API_KEY, JUDGMENT_ORG_ID class LocalEvaluationQueue: @@ -26,7 +28,9 @@ class LocalEvaluationQueue: """ def __init__( - self, max_concurrent: int = MAX_CONCURRENT_EVALUATIONS, num_workers: int = 4 + self, + max_concurrent: int = JUDGMENT_MAX_CONCURRENT_EVALUATIONS, + num_workers: int = 4, ): if num_workers <= 0: raise ValueError("num_workers must be a positive integer.") @@ -35,6 +39,10 @@ def __init__( self._num_workers = num_workers # Number of worker threads self._worker_threads: List[threading.Thread] = [] self._shutdown_event = threading.Event() + self._api_client = JudgmentSyncClient( + api_key=JUDGMENT_API_KEY, + organization_id=JUDGMENT_ORG_ID, + ) def enqueue(self, evaluation_run: EvaluationRun) -> None: """Add evaluation run to the queue.""" @@ -81,13 +89,8 @@ def run_all( def start_workers( self, - callback: Optional[Callable[[EvaluationRun, List[ScoringResult]], None]] = None, ) -> List[threading.Thread]: """Start multiple background threads to process runs in parallel. - - Args: - callback: Optional function called after each run with (run, results). - Returns: List of started worker threads. """ @@ -105,8 +108,10 @@ def _worker(worker_id: int) -> None: try: results = self._process_run(run) - if callback: - callback(run, results) + results_dict = [result.model_dump() for result in results] + self._api_client.log_eval_results( + payload={"results": results_dict, "run": run.model_dump()} + ) except Exception as exc: judgeval_logger.error( f"Worker {worker_id} error processing {run.eval_name}: {exc}" diff --git a/src/judgeval/tracer/managers.py b/src/judgeval/tracer/managers.py new file mode 100644 index 00000000..a63bd368 --- /dev/null +++ b/src/judgeval/tracer/managers.py @@ -0,0 +1,172 @@ +from __future__ import annotations + +from contextlib import asynccontextmanager, contextmanager +from typing import TYPE_CHECKING, Dict, Optional, List, Any +from judgeval.tracer.keys import AttributeKeys +import uuid +from judgeval.exceptions import JudgmentRuntimeError + +if TYPE_CHECKING: + from judgeval.tracer import Tracer + + +@contextmanager +def sync_span_context( + tracer: Tracer, + name: str, + span_attributes: Optional[Dict[str, str]] = None, +): + if span_attributes is None: + span_attributes = {} + + current_cost_context = tracer.get_current_cost_context() + + cost_context = {"cumulative_cost": 0.0} + + cost_token = current_cost_context.set(cost_context) + + try: + with tracer.get_tracer().start_as_current_span( + name=name, + attributes=span_attributes, + ) as span: + # Set initial cumulative cost attribute + span.set_attribute(AttributeKeys.JUDGMENT_CUMULATIVE_LLM_COST, 0.0) + yield span + finally: + current_cost_context.reset(cost_token) + child_cost = float(cost_context.get("cumulative_cost", 0.0)) + tracer.add_cost_to_current_context(child_cost) + + +@asynccontextmanager +async def async_span_context( + tracer: Tracer, name: str, span_attributes: Optional[Dict[str, str]] = None +): + if span_attributes is None: + span_attributes = {} + + current_cost_context = tracer.get_current_cost_context() + + cost_context = {"cumulative_cost": 0.0} + + cost_token = current_cost_context.set(cost_context) + + try: + with tracer.get_tracer().start_as_current_span( + name=name, + attributes=span_attributes, + ) as span: + span.set_attribute(AttributeKeys.JUDGMENT_CUMULATIVE_LLM_COST, 0.0) + yield span + finally: + current_cost_context.reset(cost_token) + child_cost = float(cost_context.get("cumulative_cost", 0.0)) + tracer.add_cost_to_current_context(child_cost) + + +def create_agent_context( + tracer: Tracer, + args: tuple, + class_name: Optional[str] = None, + identifier: Optional[str] = None, + track_state: bool = False, + track_attributes: Optional[List[str]] = None, + field_mappings: Optional[Dict[str, str]] = None, +): + """Create agent context and return token for cleanup""" + agent_id = str(uuid.uuid4()) + agent_context: Dict[str, Any] = {"agent_id": agent_id} + + if class_name: + agent_context["class_name"] = class_name + else: + agent_context["class_name"] = None + + agent_context["track_state"] = track_state + agent_context["track_attributes"] = track_attributes or [] + agent_context["field_mappings"] = field_mappings or {} + + instance = args[0] if args else None + agent_context["instance"] = instance + + if identifier: + if not class_name or not instance or not isinstance(instance, object): + raise JudgmentRuntimeError( + "'identifier' is set but no class name or instance is available. 'identifier' can only be specified when using the agent() decorator on a class method." + ) + if ( + instance + and hasattr(instance, identifier) + and not callable(getattr(instance, identifier)) + ): + instance_name = str(getattr(instance, identifier)) + agent_context["instance_name"] = instance_name + else: + raise JudgmentRuntimeError( + f"Attribute {identifier} does not exist for {class_name}. Check your agent() decorator." + ) + else: + agent_context["instance_name"] = None + + current_agent_context = tracer.get_current_agent_context().get() + if current_agent_context and "agent_id" in current_agent_context: + agent_context["parent_agent_id"] = current_agent_context["agent_id"] + else: + agent_context["parent_agent_id"] = None + + agent_context["is_agent_entry_point"] = True + token = tracer.get_current_agent_context().set(agent_context) + return token + + +@contextmanager +def sync_agent_context( + tracer: Tracer, + args: tuple, + class_name: Optional[str] = None, + identifier: Optional[str] = None, + track_state: bool = False, + track_attributes: Optional[List[str]] = None, + field_mappings: Optional[Dict[str, str]] = None, +): + """Context manager for synchronous agent context""" + token = create_agent_context( + tracer=tracer, + args=args, + class_name=class_name, + identifier=identifier, + track_state=track_state, + track_attributes=track_attributes, + field_mappings=field_mappings, + ) + try: + yield + finally: + tracer.get_current_agent_context().reset(token) + + +@asynccontextmanager +async def async_agent_context( + tracer: Tracer, + args: tuple, + class_name: Optional[str] = None, + identifier: Optional[str] = None, + track_state: bool = False, + track_attributes: Optional[List[str]] = None, + field_mappings: Optional[Dict[str, str]] = None, +): + """Context manager for asynchronous agent context""" + token = create_agent_context( + tracer=tracer, + args=args, + class_name=class_name, + identifier=identifier, + track_state=track_state, + track_attributes=track_attributes, + field_mappings=field_mappings, + ) + try: + yield + finally: + tracer.get_current_agent_context().reset(token) diff --git a/src/judgeval/tracer/processors/__init__.py b/src/judgeval/tracer/processors/__init__.py new file mode 100644 index 00000000..19c8d3e4 --- /dev/null +++ b/src/judgeval/tracer/processors/__init__.py @@ -0,0 +1,130 @@ +from typing import Optional +from opentelemetry.context import Context +from opentelemetry.sdk.trace import ReadableSpan, Span, SpanProcessor +from opentelemetry.sdk.trace.export import ( + BatchSpanProcessor, +) +from opentelemetry.trace import get_current_span +from judgeval.tracer.exporters import JudgmentSpanExporter +from judgeval.tracer.keys import AttributeKeys + + +class NoOpSpanProcessor(SpanProcessor): + def on_start(self, span: Span, parent_context: Optional[Context] = None) -> None: + pass + + def on_end(self, span: ReadableSpan) -> None: + pass + + def shutdown(self) -> None: + pass + + def force_flush(self, timeout_millis: int = 30000) -> bool: + return True + + +class JudgmentSpanProcessor(BatchSpanProcessor): + def __init__( + self, + endpoint: str, + api_key: str, + organization_id: str, + /, + *, + max_queue_size: int = 2**18, + export_timeout_millis: int = 30000, + ): + super().__init__( + JudgmentSpanExporter( + endpoint=endpoint, + api_key=api_key, + organization_id=organization_id, + ), + max_queue_size=max_queue_size, + export_timeout_millis=export_timeout_millis, + ) + self._span_update_ids: dict[tuple[int, int], int] = {} + + def emit_partial(self) -> None: + current_span = get_current_span() + if not current_span or not current_span.is_recording(): + return + + if not isinstance(current_span, ReadableSpan): + return + + span_context = current_span.get_span_context() + span_key = (span_context.trace_id, span_context.span_id) + + current_update_id = self._span_update_ids.get(span_key, 0) + self._span_update_ids[span_key] = current_update_id + 1 + + attributes = dict(current_span.attributes or {}) + attributes[AttributeKeys.JUDGMENT_UPDATE_ID] = current_update_id + partial_span = ReadableSpan( + name=current_span.name, + context=span_context, + parent=current_span.parent, + resource=current_span.resource, + attributes=attributes, + events=current_span.events, + links=current_span.links, + status=current_span.status, + kind=current_span.kind, + start_time=current_span.start_time, + end_time=None, + instrumentation_scope=current_span.instrumentation_scope, + ) + + super().on_end(partial_span) + + def on_end(self, span: ReadableSpan) -> None: + if span.end_time is not None and span.context: + span_key = (span.context.trace_id, span.context.span_id) + + # Create a new span with the final update_id set to 20 + attributes = dict(span.attributes or {}) + attributes[AttributeKeys.JUDGMENT_UPDATE_ID] = 20 + + final_span = ReadableSpan( + name=span.name, + context=span.context, + parent=span.parent, + resource=span.resource, + attributes=attributes, + events=span.events, + links=span.links, + status=span.status, + kind=span.kind, + start_time=span.start_time, + end_time=span.end_time, + instrumentation_scope=span.instrumentation_scope, + ) + + self._span_update_ids.pop(span_key, None) + super().on_end(final_span) + else: + super().on_end(span) + + +class NoOpJudgmentSpanProcessor(JudgmentSpanProcessor): + def __init__(self): + super().__init__("", "", "") + + def on_start(self, span: Span, parent_context: Optional[Context] = None) -> None: + pass + + def on_end(self, span: ReadableSpan) -> None: + pass + + def shutdown(self) -> None: + pass + + def force_flush(self, timeout_millis: int | None = 30000) -> bool: + return True + + def emit_partial(self) -> None: + pass + + +__all__ = ("NoOpSpanProcessor", "JudgmentSpanProcessor", "NoOpJudgmentSpanProcessor") diff --git a/src/judgeval/trainer/__init__.py b/src/judgeval/trainer/__init__.py new file mode 100644 index 00000000..acc9ad7d --- /dev/null +++ b/src/judgeval/trainer/__init__.py @@ -0,0 +1,5 @@ +from judgeval.trainer.trainer import JudgmentTrainer +from judgeval.trainer.config import TrainerConfig, ModelConfig +from judgeval.trainer.trainable_model import TrainableModel + +__all__ = ["JudgmentTrainer", "TrainerConfig", "ModelConfig", "TrainableModel"] diff --git a/src/judgeval/common/trainer/config.py b/src/judgeval/trainer/config.py similarity index 87% rename from src/judgeval/common/trainer/config.py rename to src/judgeval/trainer/config.py index aeb1d78c..e8a56c5f 100644 --- a/src/judgeval/common/trainer/config.py +++ b/src/judgeval/trainer/config.py @@ -1,7 +1,12 @@ +from __future__ import annotations + from dataclasses import dataclass -from typing import Optional, Dict, Any +from typing import Optional, Dict, Any, TYPE_CHECKING import json +if TYPE_CHECKING: + from fireworks.llm.llm_reinforcement_step import ReinforcementAcceleratorTypeLiteral + @dataclass class TrainerConfig: @@ -13,15 +18,13 @@ class TrainerConfig: base_model_name: str = "qwen2p5-7b-instruct" rft_provider: str = "fireworks" num_steps: int = 5 - num_generations_per_prompt: int = ( - 4 # Number of rollouts/generations per input prompt - ) - num_prompts_per_step: int = 4 # Number of input prompts to sample per training step + num_generations_per_prompt: int = 4 + num_prompts_per_step: int = 4 concurrency: int = 100 epochs: int = 1 learning_rate: float = 1e-5 accelerator_count: int = 1 - accelerator_type: str = "NVIDIA_A100_80GB" + accelerator_type: ReinforcementAcceleratorTypeLiteral = "NVIDIA_A100_80GB" temperature: float = 1.5 max_tokens: int = 50 enable_addons: bool = True @@ -87,7 +90,7 @@ def to_dict(self) -> Dict[str, Any]: } @classmethod - def from_dict(cls, data: Dict[str, Any]) -> "ModelConfig": + def from_dict(cls, data: Dict[str, Any]) -> ModelConfig: """Create ModelConfig from dictionary.""" return cls( base_model_name=data.get("base_model_name", "qwen2p5-7b-instruct"), @@ -107,7 +110,7 @@ def to_json(self) -> str: return json.dumps(self.to_dict(), indent=2) @classmethod - def from_json(cls, json_str: str) -> "ModelConfig": + def from_json(cls, json_str: str) -> ModelConfig: """Create ModelConfig from JSON string.""" data = json.loads(json_str) return cls.from_dict(data) @@ -118,7 +121,7 @@ def save_to_file(self, filepath: str): f.write(self.to_json()) @classmethod - def load_from_file(cls, filepath: str) -> "ModelConfig": + def load_from_file(cls, filepath: str) -> ModelConfig: """Load ModelConfig from a JSON file.""" with open(filepath, "r") as f: json_str = f.read() diff --git a/src/judgeval/common/trainer/console.py b/src/judgeval/trainer/console.py similarity index 88% rename from src/judgeval/common/trainer/console.py rename to src/judgeval/trainer/console.py index f6df453e..bc8f56f9 100644 --- a/src/judgeval/common/trainer/console.py +++ b/src/judgeval/trainer/console.py @@ -2,9 +2,10 @@ from typing import Optional import sys import os +from judgeval.utils.cache import use_once -# Detect if we're running in a Jupyter environment +@use_once def _is_jupyter_environment(): """Check if we're running in a Jupyter notebook or similar environment.""" try: @@ -22,28 +23,23 @@ def _is_jupyter_environment(): return False -# Check environment once at import time IS_JUPYTER = _is_jupyter_environment() if not IS_JUPYTER: - # Safe to use Rich in non-Jupyter environments try: from rich.console import Console from rich.spinner import Spinner from rich.live import Live from rich.text import Text - # Shared console instance for the trainer module to avoid conflicts shared_console = Console() RICH_AVAILABLE = True except ImportError: RICH_AVAILABLE = False else: - # In Jupyter, avoid Rich to prevent recursion issues RICH_AVAILABLE = False -# Fallback implementations for when Rich is not available or safe class SimpleSpinner: def __init__(self, name, text): self.text = text @@ -69,7 +65,6 @@ def safe_print(message, style=None): if RICH_AVAILABLE and not IS_JUPYTER: shared_console.print(message, style=style) else: - # Use simple print with emoji indicators for different styles if style == "green": print(f"✅ {message}") elif style == "yellow": @@ -97,7 +92,6 @@ def _spinner_progress( with Live(spinner, console=shared_console, refresh_per_second=10): yield else: - # Fallback for Jupyter or when Rich is not available print(f"🔄 {full_message}") try: yield @@ -120,7 +114,6 @@ def update_progress(progress_message: str): yield update_progress else: - # Fallback for Jupyter or when Rich is not available print(f"🔵 [Model] {message}") def update_progress(progress_message: str): diff --git a/src/judgeval/common/trainer/trainable_model.py b/src/judgeval/trainer/trainable_model.py similarity index 95% rename from src/judgeval/common/trainer/trainable_model.py rename to src/judgeval/trainer/trainable_model.py index a220b3ed..1e6da9f3 100644 --- a/src/judgeval/common/trainer/trainable_model.py +++ b/src/judgeval/trainer/trainable_model.py @@ -2,7 +2,7 @@ from .config import TrainerConfig, ModelConfig from typing import Optional, Dict, Any, Callable from .console import _model_spinner_progress, _print_model_progress -from judgeval.common.exceptions import JudgmentAPIError +from judgeval.exceptions import JudgmentRuntimeError class TrainableModel: @@ -14,6 +14,12 @@ class TrainableModel: abstracting away manual snapshot management from users. """ + config: TrainerConfig + current_step: int + _current_model: LLM + _tracer_wrapper_func: Optional[Callable] + _base_model: LLM + def __init__(self, config: TrainerConfig): """ Initialize the TrainableModel. @@ -24,13 +30,12 @@ def __init__(self, config: TrainerConfig): try: self.config = config self.current_step = 0 - self._current_model = None self._tracer_wrapper_func = None self._base_model = self._create_base_model() self._current_model = self._base_model except Exception as e: - raise JudgmentAPIError( + raise JudgmentRuntimeError( f"Failed to initialize TrainableModel: {str(e)}" ) from e @@ -80,7 +85,7 @@ def _create_base_model(self): _print_model_progress("Base model deployment ready") return base_model except Exception as e: - raise JudgmentAPIError( + raise JudgmentRuntimeError( f"Failed to create and deploy base model '{self.config.base_model_name}': {str(e)}" ) from e @@ -103,7 +108,7 @@ def _load_trained_model(self, model_name: str): if self._tracer_wrapper_func: self._tracer_wrapper_func(self._current_model) except Exception as e: - raise JudgmentAPIError( + raise JudgmentRuntimeError( f"Failed to load and deploy trained model '{model_name}': {str(e)}" ) from e @@ -150,7 +155,7 @@ def advance_to_next_step(self, step: int): if self._tracer_wrapper_func: self._tracer_wrapper_func(self._current_model) except Exception as e: - raise JudgmentAPIError( + raise JudgmentRuntimeError( f"Failed to advance to training step {step}: {str(e)}" ) from e @@ -176,7 +181,7 @@ def perform_reinforcement_step(self, dataset, step: int): accelerator_type=self.config.accelerator_type, ) except Exception as e: - raise JudgmentAPIError( + raise JudgmentRuntimeError( f"Failed to start reinforcement learning step {step + 1}: {str(e)}" ) from e diff --git a/src/judgeval/common/trainer/trainer.py b/src/judgeval/trainer/trainer.py similarity index 97% rename from src/judgeval/common/trainer/trainer.py rename to src/judgeval/trainer/trainer.py index 2ca0b092..db10d099 100644 --- a/src/judgeval/common/trainer/trainer.py +++ b/src/judgeval/trainer/trainer.py @@ -5,11 +5,11 @@ from .config import TrainerConfig, ModelConfig from .trainable_model import TrainableModel from judgeval.tracer import Tracer -from judgeval.judgment_client import JudgmentClient +from judgeval import JudgmentClient from judgeval.scorers import BaseScorer, APIScorerConfig from judgeval.data import Example from .console import _spinner_progress, _print_progress, _print_progress_update -from judgeval.common.exceptions import JudgmentAPIError +from judgeval.exceptions import JudgmentRuntimeError class JudgmentTrainer: @@ -49,7 +49,7 @@ def __init__( self.judgment_client = JudgmentClient() except Exception as e: - raise JudgmentAPIError( + raise JudgmentRuntimeError( f"Failed to initialize JudgmentTrainer: {str(e)}" ) from e @@ -246,7 +246,7 @@ async def run_reinforcement_learning( time.sleep(10) job = job.get() if job is None: - raise JudgmentAPIError( + raise JudgmentRuntimeError( "Training job was deleted while waiting for completion" ) @@ -294,8 +294,8 @@ async def train( return await self.run_reinforcement_learning( agent_function, scorers, prompts ) - except JudgmentAPIError: + except JudgmentRuntimeError: # Re-raise JudgmentAPIError as-is raise except Exception as e: - raise JudgmentAPIError(f"Training process failed: {str(e)}") from e + raise JudgmentRuntimeError(f"Training process failed: {str(e)}") from e diff --git a/src/judgeval/utils/alerts.py b/src/judgeval/utils/alerts.py deleted file mode 100644 index 5e68b350..00000000 --- a/src/judgeval/utils/alerts.py +++ /dev/null @@ -1,93 +0,0 @@ -""" -Handling alerts in Judgeval. -""" - -from enum import Enum -from typing import Dict, Any, List, Optional -from pydantic import BaseModel - - -class AlertStatus(str, Enum): - """Status of an alert evaluation.""" - - TRIGGERED = "triggered" - NOT_TRIGGERED = "not_triggered" - - -class AlertResult(BaseModel): - """ - Result of a rule evaluation. - - Attributes: - rule_name: Name of the rule that was evaluated - rule_id: Unique identifier of the rule - status: Status of the alert (triggered or not) - conditions_result: List of condition evaluation results - metadata: Dictionary containing example_id, timestamp, and other metadata - notification: Optional notification configuration for triggered alerts - combine_type: The combination type used ("all" or "any") - project_id: Optional project identifier - trace_span_id: Optional trace span identifier - """ - - rule_name: str - rule_id: Optional[str] = None # The unique identifier of the rule - status: AlertStatus - conditions_result: List[Dict[str, Any]] = [] - metadata: Dict[str, Any] = {} - notification: Optional[Any] = ( - None # NotificationConfig when triggered, None otherwise - ) - combine_type: Optional[str] = None # "all" or "any" - project_id: Optional[str] = None # Project identifier - trace_span_id: Optional[str] = None # Trace span identifier - - @property - def example_id(self) -> Optional[str]: - """Get example_id from metadata for backward compatibility""" - return self.metadata.get("example_id") - - @property - def timestamp(self) -> Optional[str]: - """Get timestamp from metadata for backward compatibility""" - return self.metadata.get("timestamp") - - @property - def conditions_results(self) -> List[Dict[str, Any]]: - """Backwards compatibility property for the conditions_result field""" - return self.conditions_result - - def model_dump(self, **kwargs): - """ - Convert the AlertResult to a dictionary for JSON serialization. - - Args: - **kwargs: Additional arguments to pass to Pydantic's model_dump - - Returns: - dict: Dictionary representation of the AlertResult - """ - data = ( - super().model_dump(**kwargs) - if hasattr(super(), "model_dump") - else super().dict(**kwargs) - ) - - # Handle the NotificationConfig object if it exists - if hasattr(self, "notification") and self.notification is not None: - if hasattr(self.notification, "model_dump"): - data["notification"] = self.notification.model_dump() - elif hasattr(self.notification, "dict"): - data["notification"] = self.notification.dict() - else: - # Manually convert the notification to a dictionary - notif = self.notification - data["notification"] = { - "enabled": notif.enabled, - "communication_methods": notif.communication_methods, - "email_addresses": notif.email_addresses, - "slack_channels": getattr(notif, "slack_channels", []), - "send_at": notif.send_at, - } - - return data diff --git a/src/judgeval/utils/async_utils.py b/src/judgeval/utils/async_utils.py index 943d111b..6fa3b738 100644 --- a/src/judgeval/utils/async_utils.py +++ b/src/judgeval/utils/async_utils.py @@ -5,7 +5,6 @@ from typing import Awaitable, TypeVar -# Generic type variable for coroutine return type T = TypeVar("T") @@ -14,8 +13,8 @@ def safe_run_async(coro: Awaitable[T]) -> T: # type: ignore[type-var] This helper handles two common situations: - 1. **No running event loop** – Simply delegates to ``asyncio.run``. - 2. **Existing running loop** – Executes the coroutine in a separate + 1. **No running event loop** - Simply delegates to ``asyncio.run``. + 2. **Existing running loop** - Executes the coroutine in a separate thread so that we don't attempt to nest event loops (which would raise ``RuntimeError``). diff --git a/src/judgeval/utils/cache.py b/src/judgeval/utils/cache.py new file mode 100644 index 00000000..0fc80438 --- /dev/null +++ b/src/judgeval/utils/cache.py @@ -0,0 +1,12 @@ +from functools import lru_cache +from typing import Callable, TypeVar + +T = TypeVar("T") + + +def use_once(func: Callable[..., T]) -> Callable[..., T]: + @lru_cache(maxsize=1) + def wrapper(*args, **kwargs): + return func(*args, **kwargs) + + return wrapper diff --git a/src/judgeval/utils/file_utils.py b/src/judgeval/utils/file_utils.py index 6670afa9..b3c4318e 100644 --- a/src/judgeval/utils/file_utils.py +++ b/src/judgeval/utils/file_utils.py @@ -1,12 +1,14 @@ +import importlib.util import yaml import orjson +from pathlib import Path from typing import List -from judgeval.common.logger import judgeval_logger +from judgeval.logger import judgeval_logger -from judgeval.data import Example +from judgeval.data.example import Example -def get_examples_from_yaml(file_path: str) -> List[Example] | None: +def get_examples_from_yaml(file_path: str) -> List[Example]: """ Adds examples from a YAML file. @@ -34,7 +36,7 @@ def get_examples_from_yaml(file_path: str) -> List[Example] | None: return new_examples -def get_examples_from_json(file_path: str) -> List[Example] | None: +def get_examples_from_json(file_path: str) -> List[Example]: """ Adds examples from a JSON file. @@ -64,3 +66,34 @@ def get_examples_from_json(file_path: str) -> List[Example] | None: new_examples = [Example(**e) for e in payload] return new_examples + + +def extract_scorer_name(scorer_file_path: str) -> str: + try: + spec = importlib.util.spec_from_file_location("scorer_module", scorer_file_path) + if spec is None or spec.loader is None: + raise ImportError(f"Could not load spec from {scorer_file_path}") + + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + + for attr_name in dir(module): + attr = getattr(module, attr_name) + if ( + isinstance(attr, type) + and any("Scorer" in str(base) for base in attr.__mro__) + and attr.__module__ == "scorer_module" + ): + try: + # Instantiate the scorer and get its name + scorer_instance = attr() + if hasattr(scorer_instance, "name"): + return scorer_instance.name + except Exception: + # Skip if instantiation fails + continue + + raise AttributeError("No scorer class found or could be instantiated") + except Exception as e: + judgeval_logger.warning(f"Could not extract scorer name: {e}") + return Path(scorer_file_path).stem diff --git a/src/judgeval/utils/guards.py b/src/judgeval/utils/guards.py new file mode 100644 index 00000000..6b80e0f0 --- /dev/null +++ b/src/judgeval/utils/guards.py @@ -0,0 +1,32 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing import TypeVar + + T = TypeVar("T") + + +def expect_exists(value: T | None, message: str) -> T: + if value is None: + raise ValueError(message) + + return value + + +def expect_api_key(api_key: str | None) -> str: + return expect_exists( + api_key, + "API Key is not set, please set JUDGMENT_API_KEY in the environment variables or pass it as `api_key`", + ) + + +def expect_organization_id(organization_id: str | None) -> str: + return expect_exists( + organization_id, + "Organization ID is not set, please set JUDGMENT_ORG_ID in the environment variables or pass it as `organization_id`", + ) + + +__all__ = ("expect_exists", "expect_api_key", "expect_organization_id") diff --git a/src/judgeval/utils/meta.py b/src/judgeval/utils/meta.py new file mode 100644 index 00000000..1ceb3e91 --- /dev/null +++ b/src/judgeval/utils/meta.py @@ -0,0 +1,14 @@ +from __future__ import annotations + + +class SingletonMeta(type): + """ + Metaclass for creating singleton classes. + """ + + _instances: dict[type, object] = {} + + def __call__(cls, *args, **kwargs): + if cls not in cls._instances: + cls._instances[cls] = super().__call__(*args, **kwargs) + return cls._instances[cls] diff --git a/src/judgeval/utils/requests.py b/src/judgeval/utils/requests.py deleted file mode 100644 index e7f544bb..00000000 --- a/src/judgeval/utils/requests.py +++ /dev/null @@ -1,50 +0,0 @@ -import requests as requests_original -from requests.adapters import HTTPAdapter -from urllib3.util.retry import Retry -from http import HTTPStatus - - -class RetrySession(requests_original.Session): - def __init__( - self, - retries=3, - backoff_factor=0.5, - status_forcelist=[HTTPStatus.BAD_GATEWAY, HTTPStatus.SERVICE_UNAVAILABLE], - default_timeout=(10, 60), # (connect_timeout, read_timeout) - ): - super().__init__() - - # Store default timeout - self.default_timeout = default_timeout - - retry_strategy = Retry( - total=retries, - read=retries, - connect=retries, - backoff_factor=backoff_factor, - status_forcelist=status_forcelist, - ) - - adapter = HTTPAdapter(max_retries=retry_strategy) - self.mount("http://", adapter) - self.mount("https://", adapter) - - def request(self, method, url, timeout=None, **kwargs): - """ - Override request method to add default timeout if not specified. - - Args: - method: HTTP method - url: Request URL - timeout: Timeout value. If None, uses default_timeout. - Can be a float (total timeout) or tuple (connect, read). - **kwargs: Other request arguments - """ - # Use default timeout if none specified - if timeout is None: - timeout = self.default_timeout - - return super().request(method, url, timeout=timeout, **kwargs) - - -requests = RetrySession() diff --git a/src/judgeval/common/api/json_encoder.py b/src/judgeval/utils/serialize.py similarity index 96% rename from src/judgeval/common/api/json_encoder.py rename to src/judgeval/utils/serialize.py index 82d5b170..574faedd 100644 --- a/src/judgeval/common/api/json_encoder.py +++ b/src/judgeval/utils/serialize.py @@ -17,6 +17,7 @@ from pydantic import BaseModel from pydantic.types import SecretBytes, SecretStr +import orjson """ @@ -60,7 +61,7 @@ def json_encoder( # Dataclasses if dataclasses.is_dataclass(obj): - obj_dict = dataclasses.asdict(obj) + obj_dict = dataclasses.asdict(obj) # type: ignore[arg-type] return json_encoder( obj_dict, ) @@ -239,3 +240,8 @@ def generate_encoders_by_class_tuples( # Mapping of encoders to a tuple of classes that they can encode encoders_by_class_tuples = generate_encoders_by_class_tuples(ENCODERS_BY_TYPE) + + +# Seralize arbitrary object to a json string +def safe_serialize(obj: Any) -> str: + return orjson.dumps(json_encoder(obj)).decode() diff --git a/src/judgeval/utils/url.py b/src/judgeval/utils/url.py new file mode 100644 index 00000000..55a0756f --- /dev/null +++ b/src/judgeval/utils/url.py @@ -0,0 +1,10 @@ +from urllib.parse import urljoin + +from judgeval.env import JUDGMENT_API_URL + + +def url_for(path: str, base: str = JUDGMENT_API_URL) -> str: + return urljoin(base, path) + + +__all__ = ("url_for",) diff --git a/src/judgeval/version_check.py b/src/judgeval/utils/version_check.py similarity index 95% rename from src/judgeval/version_check.py rename to src/judgeval/utils/version_check.py index 9322dd96..3c67801c 100644 --- a/src/judgeval/version_check.py +++ b/src/judgeval/utils/version_check.py @@ -1,7 +1,7 @@ import importlib.metadata from judgeval.utils.requests import requests import threading -from judgeval.common.logger import judgeval_logger +from judgeval.logger import judgeval_logger def check_latest_version(package_name: str = "judgeval"): diff --git a/src/judgeval/version.py b/src/judgeval/version.py new file mode 100644 index 00000000..4136efeb --- /dev/null +++ b/src/judgeval/version.py @@ -0,0 +1,5 @@ +__version__ = "0.0.0" + + +def get_version() -> str: + return __version__ diff --git a/src/judgeval/warnings.py b/src/judgeval/warnings.py new file mode 100644 index 00000000..066a8cd1 --- /dev/null +++ b/src/judgeval/warnings.py @@ -0,0 +1,4 @@ +from __future__ import annotations + + +class JudgmentWarning(Warning): ... diff --git a/src/main.py b/src/main.py new file mode 100644 index 00000000..88d6e11b --- /dev/null +++ b/src/main.py @@ -0,0 +1,35 @@ +import os +import time + +from opentelemetry.sdk.trace.export import BatchSpanProcessor, SimpleSpanProcessor +from judgeval.tracer import Tracer +from judgeval.tracer.exporters import InMemorySpanExporter +from judgeval.tracer.exporters.store import SpanStore + +store = SpanStore() + +tracer = Tracer( + project_name="errors", + processors=[SimpleSpanProcessor(InMemorySpanExporter(store=store))], +) + + +@tracer.observe +def foo(a: int): + input("Continue foo?") + return bar(3 * a) + + +@tracer.observe +def bar(a: int): + input("Continue bar?") + return a + 1 + + +@tracer.observe +def main(): + foo(10) + + +if __name__ == "__main__": + main() diff --git a/src/tests/test_encoder.py b/src/tests/test_encoder.py index 3760dcda..2eeef7db 100644 --- a/src/tests/test_encoder.py +++ b/src/tests/test_encoder.py @@ -1,7 +1,7 @@ from collections import defaultdict from pydantic import BaseModel -from judgeval.common.api.json_encoder import json_encoder +from judgeval.utils.serialize import json_encoder class SimpleModel(BaseModel): diff --git a/src/update_types.sh b/src/update_types.sh deleted file mode 100755 index 90e8d364..00000000 --- a/src/update_types.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/zsh - -# Make sure Judgment backend server is running on port 8000 -# This openapi_transform.py will get the relevant parts of the openapi.json file and save it to openapi_new.json -uv run judgeval/data/scripts/openapi_transform.py > judgeval/data/openapi_new.json - -# Then, datamodel-codegen will generate the judgment_types.py file based on the schema in openapi_new.json. -datamodel-codegen --input judgeval/data/openapi_new.json --output judgeval/data/judgment_types.py --use-annotated --output-model-type pydantic_v2.BaseModel - -# Post-process the generated file to fix mutable defaults -uv run judgeval/data/scripts/fix_default_factory.py judgeval/data/judgment_types.py - -# Remove the openapi_new.json file since it is no longer needed -rm judgeval/data/openapi_new.json \ No newline at end of file diff --git a/uv.lock b/uv.lock index 7a5d2532..a7b0c592 100644 --- a/uv.lock +++ b/uv.lock @@ -30,7 +30,7 @@ wheels = [ [[package]] name = "aiohttp" -version = "3.12.13" +version = "3.12.15" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohappyeyeballs" }, @@ -41,59 +41,59 @@ dependencies = [ { name = "propcache" }, { name = "yarl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/42/6e/ab88e7cb2a4058bed2f7870276454f85a7c56cd6da79349eb314fc7bbcaa/aiohttp-3.12.13.tar.gz", hash = "sha256:47e2da578528264a12e4e3dd8dd72a7289e5f812758fe086473fab037a10fcce", size = 7819160, upload-time = "2025-06-14T15:15:41.354Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/65/5566b49553bf20ffed6041c665a5504fb047cefdef1b701407b8ce1a47c4/aiohttp-3.12.13-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7c229b1437aa2576b99384e4be668af1db84b31a45305d02f61f5497cfa6f60c", size = 709401, upload-time = "2025-06-14T15:13:30.774Z" }, - { url = "https://files.pythonhosted.org/packages/14/b5/48e4cc61b54850bdfafa8fe0b641ab35ad53d8e5a65ab22b310e0902fa42/aiohttp-3.12.13-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:04076d8c63471e51e3689c93940775dc3d12d855c0c80d18ac5a1c68f0904358", size = 481669, upload-time = "2025-06-14T15:13:32.316Z" }, - { url = "https://files.pythonhosted.org/packages/04/4f/e3f95c8b2a20a0437d51d41d5ccc4a02970d8ad59352efb43ea2841bd08e/aiohttp-3.12.13-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:55683615813ce3601640cfaa1041174dc956d28ba0511c8cbd75273eb0587014", size = 469933, upload-time = "2025-06-14T15:13:34.104Z" }, - { url = "https://files.pythonhosted.org/packages/41/c9/c5269f3b6453b1cfbd2cfbb6a777d718c5f086a3727f576c51a468b03ae2/aiohttp-3.12.13-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:921bc91e602d7506d37643e77819cb0b840d4ebb5f8d6408423af3d3bf79a7b7", size = 1740128, upload-time = "2025-06-14T15:13:35.604Z" }, - { url = "https://files.pythonhosted.org/packages/6f/49/a3f76caa62773d33d0cfaa842bdf5789a78749dbfe697df38ab1badff369/aiohttp-3.12.13-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e72d17fe0974ddeae8ed86db297e23dba39c7ac36d84acdbb53df2e18505a013", size = 1688796, upload-time = "2025-06-14T15:13:37.125Z" }, - { url = "https://files.pythonhosted.org/packages/ad/e4/556fccc4576dc22bf18554b64cc873b1a3e5429a5bdb7bbef7f5d0bc7664/aiohttp-3.12.13-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0653d15587909a52e024a261943cf1c5bdc69acb71f411b0dd5966d065a51a47", size = 1787589, upload-time = "2025-06-14T15:13:38.745Z" }, - { url = "https://files.pythonhosted.org/packages/b9/3d/d81b13ed48e1a46734f848e26d55a7391708421a80336e341d2aef3b6db2/aiohttp-3.12.13-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a77b48997c66722c65e157c06c74332cdf9c7ad00494b85ec43f324e5c5a9b9a", size = 1826635, upload-time = "2025-06-14T15:13:40.733Z" }, - { url = "https://files.pythonhosted.org/packages/75/a5/472e25f347da88459188cdaadd1f108f6292f8a25e62d226e63f860486d1/aiohttp-3.12.13-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6946bae55fd36cfb8e4092c921075cde029c71c7cb571d72f1079d1e4e013bc", size = 1729095, upload-time = "2025-06-14T15:13:42.312Z" }, - { url = "https://files.pythonhosted.org/packages/b9/fe/322a78b9ac1725bfc59dfc301a5342e73d817592828e4445bd8f4ff83489/aiohttp-3.12.13-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f95db8c8b219bcf294a53742c7bda49b80ceb9d577c8e7aa075612b7f39ffb7", size = 1666170, upload-time = "2025-06-14T15:13:44.884Z" }, - { url = "https://files.pythonhosted.org/packages/7a/77/ec80912270e231d5e3839dbd6c065472b9920a159ec8a1895cf868c2708e/aiohttp-3.12.13-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:03d5eb3cfb4949ab4c74822fb3326cd9655c2b9fe22e4257e2100d44215b2e2b", size = 1714444, upload-time = "2025-06-14T15:13:46.401Z" }, - { url = "https://files.pythonhosted.org/packages/21/b2/fb5aedbcb2b58d4180e58500e7c23ff8593258c27c089abfbcc7db65bd40/aiohttp-3.12.13-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:6383dd0ffa15515283c26cbf41ac8e6705aab54b4cbb77bdb8935a713a89bee9", size = 1709604, upload-time = "2025-06-14T15:13:48.377Z" }, - { url = "https://files.pythonhosted.org/packages/e3/15/a94c05f7c4dc8904f80b6001ad6e07e035c58a8ebfcc15e6b5d58500c858/aiohttp-3.12.13-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6548a411bc8219b45ba2577716493aa63b12803d1e5dc70508c539d0db8dbf5a", size = 1689786, upload-time = "2025-06-14T15:13:50.401Z" }, - { url = "https://files.pythonhosted.org/packages/1d/fd/0d2e618388f7a7a4441eed578b626bda9ec6b5361cd2954cfc5ab39aa170/aiohttp-3.12.13-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:81b0fcbfe59a4ca41dc8f635c2a4a71e63f75168cc91026c61be665945739e2d", size = 1783389, upload-time = "2025-06-14T15:13:51.945Z" }, - { url = "https://files.pythonhosted.org/packages/a6/6b/6986d0c75996ef7e64ff7619b9b7449b1d1cbbe05c6755e65d92f1784fe9/aiohttp-3.12.13-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:6a83797a0174e7995e5edce9dcecc517c642eb43bc3cba296d4512edf346eee2", size = 1803853, upload-time = "2025-06-14T15:13:53.533Z" }, - { url = "https://files.pythonhosted.org/packages/21/65/cd37b38f6655d95dd07d496b6d2f3924f579c43fd64b0e32b547b9c24df5/aiohttp-3.12.13-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a5734d8469a5633a4e9ffdf9983ff7cdb512524645c7a3d4bc8a3de45b935ac3", size = 1716909, upload-time = "2025-06-14T15:13:55.148Z" }, - { url = "https://files.pythonhosted.org/packages/fd/20/2de7012427dc116714c38ca564467f6143aec3d5eca3768848d62aa43e62/aiohttp-3.12.13-cp311-cp311-win32.whl", hash = "sha256:fef8d50dfa482925bb6b4c208b40d8e9fa54cecba923dc65b825a72eed9a5dbd", size = 427036, upload-time = "2025-06-14T15:13:57.076Z" }, - { url = "https://files.pythonhosted.org/packages/f8/b6/98518bcc615ef998a64bef371178b9afc98ee25895b4f476c428fade2220/aiohttp-3.12.13-cp311-cp311-win_amd64.whl", hash = "sha256:9a27da9c3b5ed9d04c36ad2df65b38a96a37e9cfba6f1381b842d05d98e6afe9", size = 451427, upload-time = "2025-06-14T15:13:58.505Z" }, - { url = "https://files.pythonhosted.org/packages/b4/6a/ce40e329788013cd190b1d62bbabb2b6a9673ecb6d836298635b939562ef/aiohttp-3.12.13-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0aa580cf80558557285b49452151b9c69f2fa3ad94c5c9e76e684719a8791b73", size = 700491, upload-time = "2025-06-14T15:14:00.048Z" }, - { url = "https://files.pythonhosted.org/packages/28/d9/7150d5cf9163e05081f1c5c64a0cdf3c32d2f56e2ac95db2a28fe90eca69/aiohttp-3.12.13-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b103a7e414b57e6939cc4dece8e282cfb22043efd0c7298044f6594cf83ab347", size = 475104, upload-time = "2025-06-14T15:14:01.691Z" }, - { url = "https://files.pythonhosted.org/packages/f8/91/d42ba4aed039ce6e449b3e2db694328756c152a79804e64e3da5bc19dffc/aiohttp-3.12.13-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:78f64e748e9e741d2eccff9597d09fb3cd962210e5b5716047cbb646dc8fe06f", size = 467948, upload-time = "2025-06-14T15:14:03.561Z" }, - { url = "https://files.pythonhosted.org/packages/99/3b/06f0a632775946981d7c4e5a865cddb6e8dfdbaed2f56f9ade7bb4a1039b/aiohttp-3.12.13-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29c955989bf4c696d2ededc6b0ccb85a73623ae6e112439398935362bacfaaf6", size = 1714742, upload-time = "2025-06-14T15:14:05.558Z" }, - { url = "https://files.pythonhosted.org/packages/92/a6/2552eebad9ec5e3581a89256276009e6a974dc0793632796af144df8b740/aiohttp-3.12.13-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d640191016763fab76072c87d8854a19e8e65d7a6fcfcbf017926bdbbb30a7e5", size = 1697393, upload-time = "2025-06-14T15:14:07.194Z" }, - { url = "https://files.pythonhosted.org/packages/d8/9f/bd08fdde114b3fec7a021381b537b21920cdd2aa29ad48c5dffd8ee314f1/aiohttp-3.12.13-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4dc507481266b410dede95dd9f26c8d6f5a14315372cc48a6e43eac652237d9b", size = 1752486, upload-time = "2025-06-14T15:14:08.808Z" }, - { url = "https://files.pythonhosted.org/packages/f7/e1/affdea8723aec5bd0959171b5490dccd9a91fcc505c8c26c9f1dca73474d/aiohttp-3.12.13-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8a94daa873465d518db073bd95d75f14302e0208a08e8c942b2f3f1c07288a75", size = 1798643, upload-time = "2025-06-14T15:14:10.767Z" }, - { url = "https://files.pythonhosted.org/packages/f3/9d/666d856cc3af3a62ae86393baa3074cc1d591a47d89dc3bf16f6eb2c8d32/aiohttp-3.12.13-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:177f52420cde4ce0bb9425a375d95577fe082cb5721ecb61da3049b55189e4e6", size = 1718082, upload-time = "2025-06-14T15:14:12.38Z" }, - { url = "https://files.pythonhosted.org/packages/f3/ce/3c185293843d17be063dada45efd2712bb6bf6370b37104b4eda908ffdbd/aiohttp-3.12.13-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f7df1f620ec40f1a7fbcb99ea17d7326ea6996715e78f71a1c9a021e31b96b8", size = 1633884, upload-time = "2025-06-14T15:14:14.415Z" }, - { url = "https://files.pythonhosted.org/packages/3a/5b/f3413f4b238113be35dfd6794e65029250d4b93caa0974ca572217745bdb/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3062d4ad53b36e17796dce1c0d6da0ad27a015c321e663657ba1cc7659cfc710", size = 1694943, upload-time = "2025-06-14T15:14:16.48Z" }, - { url = "https://files.pythonhosted.org/packages/82/c8/0e56e8bf12081faca85d14a6929ad5c1263c146149cd66caa7bc12255b6d/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:8605e22d2a86b8e51ffb5253d9045ea73683d92d47c0b1438e11a359bdb94462", size = 1716398, upload-time = "2025-06-14T15:14:18.589Z" }, - { url = "https://files.pythonhosted.org/packages/ea/f3/33192b4761f7f9b2f7f4281365d925d663629cfaea093a64b658b94fc8e1/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:54fbbe6beafc2820de71ece2198458a711e224e116efefa01b7969f3e2b3ddae", size = 1657051, upload-time = "2025-06-14T15:14:20.223Z" }, - { url = "https://files.pythonhosted.org/packages/5e/0b/26ddd91ca8f84c48452431cb4c5dd9523b13bc0c9766bda468e072ac9e29/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:050bd277dfc3768b606fd4eae79dd58ceda67d8b0b3c565656a89ae34525d15e", size = 1736611, upload-time = "2025-06-14T15:14:21.988Z" }, - { url = "https://files.pythonhosted.org/packages/c3/8d/e04569aae853302648e2c138a680a6a2f02e374c5b6711732b29f1e129cc/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2637a60910b58f50f22379b6797466c3aa6ae28a6ab6404e09175ce4955b4e6a", size = 1764586, upload-time = "2025-06-14T15:14:23.979Z" }, - { url = "https://files.pythonhosted.org/packages/ac/98/c193c1d1198571d988454e4ed75adc21c55af247a9fda08236602921c8c8/aiohttp-3.12.13-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e986067357550d1aaa21cfe9897fa19e680110551518a5a7cf44e6c5638cb8b5", size = 1724197, upload-time = "2025-06-14T15:14:25.692Z" }, - { url = "https://files.pythonhosted.org/packages/e7/9e/07bb8aa11eec762c6b1ff61575eeeb2657df11ab3d3abfa528d95f3e9337/aiohttp-3.12.13-cp312-cp312-win32.whl", hash = "sha256:ac941a80aeea2aaae2875c9500861a3ba356f9ff17b9cb2dbfb5cbf91baaf5bf", size = 421771, upload-time = "2025-06-14T15:14:27.364Z" }, - { url = "https://files.pythonhosted.org/packages/52/66/3ce877e56ec0813069cdc9607cd979575859c597b6fb9b4182c6d5f31886/aiohttp-3.12.13-cp312-cp312-win_amd64.whl", hash = "sha256:671f41e6146a749b6c81cb7fd07f5a8356d46febdaaaf07b0e774ff04830461e", size = 447869, upload-time = "2025-06-14T15:14:29.05Z" }, - { url = "https://files.pythonhosted.org/packages/11/0f/db19abdf2d86aa1deec3c1e0e5ea46a587b97c07a16516b6438428b3a3f8/aiohttp-3.12.13-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d4a18e61f271127465bdb0e8ff36e8f02ac4a32a80d8927aa52371e93cd87938", size = 694910, upload-time = "2025-06-14T15:14:30.604Z" }, - { url = "https://files.pythonhosted.org/packages/d5/81/0ab551e1b5d7f1339e2d6eb482456ccbe9025605b28eed2b1c0203aaaade/aiohttp-3.12.13-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:532542cb48691179455fab429cdb0d558b5e5290b033b87478f2aa6af5d20ace", size = 472566, upload-time = "2025-06-14T15:14:32.275Z" }, - { url = "https://files.pythonhosted.org/packages/34/3f/6b7d336663337672d29b1f82d1f252ec1a040fe2d548f709d3f90fa2218a/aiohttp-3.12.13-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d7eea18b52f23c050ae9db5d01f3d264ab08f09e7356d6f68e3f3ac2de9dfabb", size = 464856, upload-time = "2025-06-14T15:14:34.132Z" }, - { url = "https://files.pythonhosted.org/packages/26/7f/32ca0f170496aa2ab9b812630fac0c2372c531b797e1deb3deb4cea904bd/aiohttp-3.12.13-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad7c8e5c25f2a26842a7c239de3f7b6bfb92304593ef997c04ac49fb703ff4d7", size = 1703683, upload-time = "2025-06-14T15:14:36.034Z" }, - { url = "https://files.pythonhosted.org/packages/ec/53/d5513624b33a811c0abea8461e30a732294112318276ce3dbf047dbd9d8b/aiohttp-3.12.13-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6af355b483e3fe9d7336d84539fef460120c2f6e50e06c658fe2907c69262d6b", size = 1684946, upload-time = "2025-06-14T15:14:38Z" }, - { url = "https://files.pythonhosted.org/packages/37/72/4c237dd127827b0247dc138d3ebd49c2ded6114c6991bbe969058575f25f/aiohttp-3.12.13-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a95cf9f097498f35c88e3609f55bb47b28a5ef67f6888f4390b3d73e2bac6177", size = 1737017, upload-time = "2025-06-14T15:14:39.951Z" }, - { url = "https://files.pythonhosted.org/packages/0d/67/8a7eb3afa01e9d0acc26e1ef847c1a9111f8b42b82955fcd9faeb84edeb4/aiohttp-3.12.13-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8ed8c38a1c584fe99a475a8f60eefc0b682ea413a84c6ce769bb19a7ff1c5ef", size = 1786390, upload-time = "2025-06-14T15:14:42.151Z" }, - { url = "https://files.pythonhosted.org/packages/48/19/0377df97dd0176ad23cd8cad4fd4232cfeadcec6c1b7f036315305c98e3f/aiohttp-3.12.13-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a0b9170d5d800126b5bc89d3053a2363406d6e327afb6afaeda2d19ee8bb103", size = 1708719, upload-time = "2025-06-14T15:14:44.039Z" }, - { url = "https://files.pythonhosted.org/packages/61/97/ade1982a5c642b45f3622255173e40c3eed289c169f89d00eeac29a89906/aiohttp-3.12.13-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:372feeace612ef8eb41f05ae014a92121a512bd5067db8f25101dd88a8db11da", size = 1622424, upload-time = "2025-06-14T15:14:45.945Z" }, - { url = "https://files.pythonhosted.org/packages/99/ab/00ad3eea004e1d07ccc406e44cfe2b8da5acb72f8c66aeeb11a096798868/aiohttp-3.12.13-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a946d3702f7965d81f7af7ea8fb03bb33fe53d311df48a46eeca17e9e0beed2d", size = 1675447, upload-time = "2025-06-14T15:14:47.911Z" }, - { url = "https://files.pythonhosted.org/packages/3f/fe/74e5ce8b2ccaba445fe0087abc201bfd7259431d92ae608f684fcac5d143/aiohttp-3.12.13-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a0c4725fae86555bbb1d4082129e21de7264f4ab14baf735278c974785cd2041", size = 1707110, upload-time = "2025-06-14T15:14:50.334Z" }, - { url = "https://files.pythonhosted.org/packages/ef/c4/39af17807f694f7a267bd8ab1fbacf16ad66740862192a6c8abac2bff813/aiohttp-3.12.13-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9b28ea2f708234f0a5c44eb6c7d9eb63a148ce3252ba0140d050b091b6e842d1", size = 1649706, upload-time = "2025-06-14T15:14:52.378Z" }, - { url = "https://files.pythonhosted.org/packages/38/e8/f5a0a5f44f19f171d8477059aa5f28a158d7d57fe1a46c553e231f698435/aiohttp-3.12.13-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d4f5becd2a5791829f79608c6f3dc745388162376f310eb9c142c985f9441cc1", size = 1725839, upload-time = "2025-06-14T15:14:54.617Z" }, - { url = "https://files.pythonhosted.org/packages/fd/ac/81acc594c7f529ef4419d3866913f628cd4fa9cab17f7bf410a5c3c04c53/aiohttp-3.12.13-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:60f2ce6b944e97649051d5f5cc0f439360690b73909230e107fd45a359d3e911", size = 1759311, upload-time = "2025-06-14T15:14:56.597Z" }, - { url = "https://files.pythonhosted.org/packages/38/0d/aabe636bd25c6ab7b18825e5a97d40024da75152bec39aa6ac8b7a677630/aiohttp-3.12.13-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:69fc1909857401b67bf599c793f2183fbc4804717388b0b888f27f9929aa41f3", size = 1708202, upload-time = "2025-06-14T15:14:58.598Z" }, - { url = "https://files.pythonhosted.org/packages/1f/ab/561ef2d8a223261683fb95a6283ad0d36cb66c87503f3a7dde7afe208bb2/aiohttp-3.12.13-cp313-cp313-win32.whl", hash = "sha256:7d7e68787a2046b0e44ba5587aa723ce05d711e3a3665b6b7545328ac8e3c0dd", size = 420794, upload-time = "2025-06-14T15:15:00.939Z" }, - { url = "https://files.pythonhosted.org/packages/9d/47/b11d0089875a23bff0abd3edb5516bcd454db3fefab8604f5e4b07bd6210/aiohttp-3.12.13-cp313-cp313-win_amd64.whl", hash = "sha256:5a178390ca90419bfd41419a809688c368e63c86bd725e1186dd97f6b89c2706", size = 446735, upload-time = "2025-06-14T15:15:02.858Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/9b/e7/d92a237d8802ca88483906c388f7c201bbe96cd80a165ffd0ac2f6a8d59f/aiohttp-3.12.15.tar.gz", hash = "sha256:4fc61385e9c98d72fcdf47e6dd81833f47b2f77c114c29cd64a361be57a763a2", size = 7823716, upload-time = "2025-07-29T05:52:32.215Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/20/19/9e86722ec8e835959bd97ce8c1efa78cf361fa4531fca372551abcc9cdd6/aiohttp-3.12.15-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:d3ce17ce0220383a0f9ea07175eeaa6aa13ae5a41f30bc61d84df17f0e9b1117", size = 711246, upload-time = "2025-07-29T05:50:15.937Z" }, + { url = "https://files.pythonhosted.org/packages/71/f9/0a31fcb1a7d4629ac9d8f01f1cb9242e2f9943f47f5d03215af91c3c1a26/aiohttp-3.12.15-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:010cc9bbd06db80fe234d9003f67e97a10fe003bfbedb40da7d71c1008eda0fe", size = 483515, upload-time = "2025-07-29T05:50:17.442Z" }, + { url = "https://files.pythonhosted.org/packages/62/6c/94846f576f1d11df0c2e41d3001000527c0fdf63fce7e69b3927a731325d/aiohttp-3.12.15-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3f9d7c55b41ed687b9d7165b17672340187f87a773c98236c987f08c858145a9", size = 471776, upload-time = "2025-07-29T05:50:19.568Z" }, + { url = "https://files.pythonhosted.org/packages/f8/6c/f766d0aaafcee0447fad0328da780d344489c042e25cd58fde566bf40aed/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc4fbc61bb3548d3b482f9ac7ddd0f18c67e4225aaa4e8552b9f1ac7e6bda9e5", size = 1741977, upload-time = "2025-07-29T05:50:21.665Z" }, + { url = "https://files.pythonhosted.org/packages/17/e5/fb779a05ba6ff44d7bc1e9d24c644e876bfff5abe5454f7b854cace1b9cc/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7fbc8a7c410bb3ad5d595bb7118147dfbb6449d862cc1125cf8867cb337e8728", size = 1690645, upload-time = "2025-07-29T05:50:23.333Z" }, + { url = "https://files.pythonhosted.org/packages/37/4e/a22e799c2035f5d6a4ad2cf8e7c1d1bd0923192871dd6e367dafb158b14c/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:74dad41b3458dbb0511e760fb355bb0b6689e0630de8a22b1b62a98777136e16", size = 1789437, upload-time = "2025-07-29T05:50:25.007Z" }, + { url = "https://files.pythonhosted.org/packages/28/e5/55a33b991f6433569babb56018b2fb8fb9146424f8b3a0c8ecca80556762/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b6f0af863cf17e6222b1735a756d664159e58855da99cfe965134a3ff63b0b0", size = 1828482, upload-time = "2025-07-29T05:50:26.693Z" }, + { url = "https://files.pythonhosted.org/packages/c6/82/1ddf0ea4f2f3afe79dffed5e8a246737cff6cbe781887a6a170299e33204/aiohttp-3.12.15-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5b7fe4972d48a4da367043b8e023fb70a04d1490aa7d68800e465d1b97e493b", size = 1730944, upload-time = "2025-07-29T05:50:28.382Z" }, + { url = "https://files.pythonhosted.org/packages/1b/96/784c785674117b4cb3877522a177ba1b5e4db9ce0fd519430b5de76eec90/aiohttp-3.12.15-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6443cca89553b7a5485331bc9bedb2342b08d073fa10b8c7d1c60579c4a7b9bd", size = 1668020, upload-time = "2025-07-29T05:50:30.032Z" }, + { url = "https://files.pythonhosted.org/packages/12/8a/8b75f203ea7e5c21c0920d84dd24a5c0e971fe1e9b9ebbf29ae7e8e39790/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6c5f40ec615e5264f44b4282ee27628cea221fcad52f27405b80abb346d9f3f8", size = 1716292, upload-time = "2025-07-29T05:50:31.983Z" }, + { url = "https://files.pythonhosted.org/packages/47/0b/a1451543475bb6b86a5cfc27861e52b14085ae232896a2654ff1231c0992/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:2abbb216a1d3a2fe86dbd2edce20cdc5e9ad0be6378455b05ec7f77361b3ab50", size = 1711451, upload-time = "2025-07-29T05:50:33.989Z" }, + { url = "https://files.pythonhosted.org/packages/55/fd/793a23a197cc2f0d29188805cfc93aa613407f07e5f9da5cd1366afd9d7c/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:db71ce547012a5420a39c1b744d485cfb823564d01d5d20805977f5ea1345676", size = 1691634, upload-time = "2025-07-29T05:50:35.846Z" }, + { url = "https://files.pythonhosted.org/packages/ca/bf/23a335a6670b5f5dfc6d268328e55a22651b440fca341a64fccf1eada0c6/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ced339d7c9b5030abad5854aa5413a77565e5b6e6248ff927d3e174baf3badf7", size = 1785238, upload-time = "2025-07-29T05:50:37.597Z" }, + { url = "https://files.pythonhosted.org/packages/57/4f/ed60a591839a9d85d40694aba5cef86dde9ee51ce6cca0bb30d6eb1581e7/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:7c7dd29c7b5bda137464dc9bfc738d7ceea46ff70309859ffde8c022e9b08ba7", size = 1805701, upload-time = "2025-07-29T05:50:39.591Z" }, + { url = "https://files.pythonhosted.org/packages/85/e0/444747a9455c5de188c0f4a0173ee701e2e325d4b2550e9af84abb20cdba/aiohttp-3.12.15-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:421da6fd326460517873274875c6c5a18ff225b40da2616083c5a34a7570b685", size = 1718758, upload-time = "2025-07-29T05:50:41.292Z" }, + { url = "https://files.pythonhosted.org/packages/36/ab/1006278d1ffd13a698e5dd4bfa01e5878f6bddefc296c8b62649753ff249/aiohttp-3.12.15-cp311-cp311-win32.whl", hash = "sha256:4420cf9d179ec8dfe4be10e7d0fe47d6d606485512ea2265b0d8c5113372771b", size = 428868, upload-time = "2025-07-29T05:50:43.063Z" }, + { url = "https://files.pythonhosted.org/packages/10/97/ad2b18700708452400278039272032170246a1bf8ec5d832772372c71f1a/aiohttp-3.12.15-cp311-cp311-win_amd64.whl", hash = "sha256:edd533a07da85baa4b423ee8839e3e91681c7bfa19b04260a469ee94b778bf6d", size = 453273, upload-time = "2025-07-29T05:50:44.613Z" }, + { url = "https://files.pythonhosted.org/packages/63/97/77cb2450d9b35f517d6cf506256bf4f5bda3f93a66b4ad64ba7fc917899c/aiohttp-3.12.15-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:802d3868f5776e28f7bf69d349c26fc0efadb81676d0afa88ed00d98a26340b7", size = 702333, upload-time = "2025-07-29T05:50:46.507Z" }, + { url = "https://files.pythonhosted.org/packages/83/6d/0544e6b08b748682c30b9f65640d006e51f90763b41d7c546693bc22900d/aiohttp-3.12.15-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2800614cd560287be05e33a679638e586a2d7401f4ddf99e304d98878c29444", size = 476948, upload-time = "2025-07-29T05:50:48.067Z" }, + { url = "https://files.pythonhosted.org/packages/3a/1d/c8c40e611e5094330284b1aea8a4b02ca0858f8458614fa35754cab42b9c/aiohttp-3.12.15-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8466151554b593909d30a0a125d638b4e5f3836e5aecde85b66b80ded1cb5b0d", size = 469787, upload-time = "2025-07-29T05:50:49.669Z" }, + { url = "https://files.pythonhosted.org/packages/38/7d/b76438e70319796bfff717f325d97ce2e9310f752a267bfdf5192ac6082b/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e5a495cb1be69dae4b08f35a6c4579c539e9b5706f606632102c0f855bcba7c", size = 1716590, upload-time = "2025-07-29T05:50:51.368Z" }, + { url = "https://files.pythonhosted.org/packages/79/b1/60370d70cdf8b269ee1444b390cbd72ce514f0d1cd1a715821c784d272c9/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:6404dfc8cdde35c69aaa489bb3542fb86ef215fc70277c892be8af540e5e21c0", size = 1699241, upload-time = "2025-07-29T05:50:53.628Z" }, + { url = "https://files.pythonhosted.org/packages/a3/2b/4968a7b8792437ebc12186db31523f541943e99bda8f30335c482bea6879/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3ead1c00f8521a5c9070fcb88f02967b1d8a0544e6d85c253f6968b785e1a2ab", size = 1754335, upload-time = "2025-07-29T05:50:55.394Z" }, + { url = "https://files.pythonhosted.org/packages/fb/c1/49524ed553f9a0bec1a11fac09e790f49ff669bcd14164f9fab608831c4d/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6990ef617f14450bc6b34941dba4f12d5613cbf4e33805932f853fbd1cf18bfb", size = 1800491, upload-time = "2025-07-29T05:50:57.202Z" }, + { url = "https://files.pythonhosted.org/packages/de/5e/3bf5acea47a96a28c121b167f5ef659cf71208b19e52a88cdfa5c37f1fcc/aiohttp-3.12.15-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd736ed420f4db2b8148b52b46b88ed038d0354255f9a73196b7bbce3ea97545", size = 1719929, upload-time = "2025-07-29T05:50:59.192Z" }, + { url = "https://files.pythonhosted.org/packages/39/94/8ae30b806835bcd1cba799ba35347dee6961a11bd507db634516210e91d8/aiohttp-3.12.15-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c5092ce14361a73086b90c6efb3948ffa5be2f5b6fbcf52e8d8c8b8848bb97c", size = 1635733, upload-time = "2025-07-29T05:51:01.394Z" }, + { url = "https://files.pythonhosted.org/packages/7a/46/06cdef71dd03acd9da7f51ab3a9107318aee12ad38d273f654e4f981583a/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aaa2234bb60c4dbf82893e934d8ee8dea30446f0647e024074237a56a08c01bd", size = 1696790, upload-time = "2025-07-29T05:51:03.657Z" }, + { url = "https://files.pythonhosted.org/packages/02/90/6b4cfaaf92ed98d0ec4d173e78b99b4b1a7551250be8937d9d67ecb356b4/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6d86a2fbdd14192e2f234a92d3b494dd4457e683ba07e5905a0b3ee25389ac9f", size = 1718245, upload-time = "2025-07-29T05:51:05.911Z" }, + { url = "https://files.pythonhosted.org/packages/2e/e6/2593751670fa06f080a846f37f112cbe6f873ba510d070136a6ed46117c6/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a041e7e2612041a6ddf1c6a33b883be6a421247c7afd47e885969ee4cc58bd8d", size = 1658899, upload-time = "2025-07-29T05:51:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/8f/28/c15bacbdb8b8eb5bf39b10680d129ea7410b859e379b03190f02fa104ffd/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5015082477abeafad7203757ae44299a610e89ee82a1503e3d4184e6bafdd519", size = 1738459, upload-time = "2025-07-29T05:51:09.56Z" }, + { url = "https://files.pythonhosted.org/packages/00/de/c269cbc4faa01fb10f143b1670633a8ddd5b2e1ffd0548f7aa49cb5c70e2/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:56822ff5ddfd1b745534e658faba944012346184fbfe732e0d6134b744516eea", size = 1766434, upload-time = "2025-07-29T05:51:11.423Z" }, + { url = "https://files.pythonhosted.org/packages/52/b0/4ff3abd81aa7d929b27d2e1403722a65fc87b763e3a97b3a2a494bfc63bc/aiohttp-3.12.15-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b2acbbfff69019d9014508c4ba0401822e8bae5a5fdc3b6814285b71231b60f3", size = 1726045, upload-time = "2025-07-29T05:51:13.689Z" }, + { url = "https://files.pythonhosted.org/packages/71/16/949225a6a2dd6efcbd855fbd90cf476052e648fb011aa538e3b15b89a57a/aiohttp-3.12.15-cp312-cp312-win32.whl", hash = "sha256:d849b0901b50f2185874b9a232f38e26b9b3d4810095a7572eacea939132d4e1", size = 423591, upload-time = "2025-07-29T05:51:15.452Z" }, + { url = "https://files.pythonhosted.org/packages/2b/d8/fa65d2a349fe938b76d309db1a56a75c4fb8cc7b17a398b698488a939903/aiohttp-3.12.15-cp312-cp312-win_amd64.whl", hash = "sha256:b390ef5f62bb508a9d67cb3bba9b8356e23b3996da7062f1a57ce1a79d2b3d34", size = 450266, upload-time = "2025-07-29T05:51:17.239Z" }, + { url = "https://files.pythonhosted.org/packages/f2/33/918091abcf102e39d15aba2476ad9e7bd35ddb190dcdd43a854000d3da0d/aiohttp-3.12.15-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9f922ffd05034d439dde1c77a20461cf4a1b0831e6caa26151fe7aa8aaebc315", size = 696741, upload-time = "2025-07-29T05:51:19.021Z" }, + { url = "https://files.pythonhosted.org/packages/b5/2a/7495a81e39a998e400f3ecdd44a62107254803d1681d9189be5c2e4530cd/aiohttp-3.12.15-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2ee8a8ac39ce45f3e55663891d4b1d15598c157b4d494a4613e704c8b43112cd", size = 474407, upload-time = "2025-07-29T05:51:21.165Z" }, + { url = "https://files.pythonhosted.org/packages/49/fc/a9576ab4be2dcbd0f73ee8675d16c707cfc12d5ee80ccf4015ba543480c9/aiohttp-3.12.15-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3eae49032c29d356b94eee45a3f39fdf4b0814b397638c2f718e96cfadf4c4e4", size = 466703, upload-time = "2025-07-29T05:51:22.948Z" }, + { url = "https://files.pythonhosted.org/packages/09/2f/d4bcc8448cf536b2b54eed48f19682031ad182faa3a3fee54ebe5b156387/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97752ff12cc12f46a9b20327104448042fce5c33a624f88c18f66f9368091c7", size = 1705532, upload-time = "2025-07-29T05:51:25.211Z" }, + { url = "https://files.pythonhosted.org/packages/f1/f3/59406396083f8b489261e3c011aa8aee9df360a96ac8fa5c2e7e1b8f0466/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:894261472691d6fe76ebb7fcf2e5870a2ac284c7406ddc95823c8598a1390f0d", size = 1686794, upload-time = "2025-07-29T05:51:27.145Z" }, + { url = "https://files.pythonhosted.org/packages/dc/71/164d194993a8d114ee5656c3b7ae9c12ceee7040d076bf7b32fb98a8c5c6/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5fa5d9eb82ce98959fc1031c28198b431b4d9396894f385cb63f1e2f3f20ca6b", size = 1738865, upload-time = "2025-07-29T05:51:29.366Z" }, + { url = "https://files.pythonhosted.org/packages/1c/00/d198461b699188a93ead39cb458554d9f0f69879b95078dce416d3209b54/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0fa751efb11a541f57db59c1dd821bec09031e01452b2b6217319b3a1f34f3d", size = 1788238, upload-time = "2025-07-29T05:51:31.285Z" }, + { url = "https://files.pythonhosted.org/packages/85/b8/9e7175e1fa0ac8e56baa83bf3c214823ce250d0028955dfb23f43d5e61fd/aiohttp-3.12.15-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5346b93e62ab51ee2a9d68e8f73c7cf96ffb73568a23e683f931e52450e4148d", size = 1710566, upload-time = "2025-07-29T05:51:33.219Z" }, + { url = "https://files.pythonhosted.org/packages/59/e4/16a8eac9df39b48ae102ec030fa9f726d3570732e46ba0c592aeeb507b93/aiohttp-3.12.15-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:049ec0360f939cd164ecbfd2873eaa432613d5e77d6b04535e3d1fbae5a9e645", size = 1624270, upload-time = "2025-07-29T05:51:35.195Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f8/cd84dee7b6ace0740908fd0af170f9fab50c2a41ccbc3806aabcb1050141/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b52dcf013b57464b6d1e51b627adfd69a8053e84b7103a7cd49c030f9ca44461", size = 1677294, upload-time = "2025-07-29T05:51:37.215Z" }, + { url = "https://files.pythonhosted.org/packages/ce/42/d0f1f85e50d401eccd12bf85c46ba84f947a84839c8a1c2c5f6e8ab1eb50/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:9b2af240143dd2765e0fb661fd0361a1b469cab235039ea57663cda087250ea9", size = 1708958, upload-time = "2025-07-29T05:51:39.328Z" }, + { url = "https://files.pythonhosted.org/packages/d5/6b/f6fa6c5790fb602538483aa5a1b86fcbad66244997e5230d88f9412ef24c/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ac77f709a2cde2cc71257ab2d8c74dd157c67a0558a0d2799d5d571b4c63d44d", size = 1651553, upload-time = "2025-07-29T05:51:41.356Z" }, + { url = "https://files.pythonhosted.org/packages/04/36/a6d36ad545fa12e61d11d1932eef273928b0495e6a576eb2af04297fdd3c/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:47f6b962246f0a774fbd3b6b7be25d59b06fdb2f164cf2513097998fc6a29693", size = 1727688, upload-time = "2025-07-29T05:51:43.452Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c8/f195e5e06608a97a4e52c5d41c7927301bf757a8e8bb5bbf8cef6c314961/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:760fb7db442f284996e39cf9915a94492e1896baac44f06ae551974907922b64", size = 1761157, upload-time = "2025-07-29T05:51:45.643Z" }, + { url = "https://files.pythonhosted.org/packages/05/6a/ea199e61b67f25ba688d3ce93f63b49b0a4e3b3d380f03971b4646412fc6/aiohttp-3.12.15-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad702e57dc385cae679c39d318def49aef754455f237499d5b99bea4ef582e51", size = 1710050, upload-time = "2025-07-29T05:51:48.203Z" }, + { url = "https://files.pythonhosted.org/packages/b4/2e/ffeb7f6256b33635c29dbed29a22a723ff2dd7401fff42ea60cf2060abfb/aiohttp-3.12.15-cp313-cp313-win32.whl", hash = "sha256:f813c3e9032331024de2eb2e32a88d86afb69291fbc37a3a3ae81cc9917fb3d0", size = 422647, upload-time = "2025-07-29T05:51:50.718Z" }, + { url = "https://files.pythonhosted.org/packages/1b/8e/78ee35774201f38d5e1ba079c9958f7629b1fd079459aea9467441dbfbf5/aiohttp-3.12.15-cp313-cp313-win_amd64.whl", hash = "sha256:1a649001580bdb37c6fdb1bebbd7e3bc688e8ec2b5c6f52edbb664662b17dc84", size = 449067, upload-time = "2025-07-29T05:51:52.549Z" }, ] [package.optional-dependencies] @@ -139,7 +139,7 @@ wheels = [ [[package]] name = "anthropic" -version = "0.57.1" +version = "0.64.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -150,9 +150,9 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d7/75/6261a1a8d92aed47e27d2fcfb3a411af73b1435e6ae1186da02b760565d0/anthropic-0.57.1.tar.gz", hash = "sha256:7815dd92245a70d21f65f356f33fc80c5072eada87fb49437767ea2918b2c4b0", size = 423775, upload-time = "2025-07-03T16:57:35.932Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/4f/f2b880cba1a76f3acc7d5eb2ae217632eac1b8cef5ed3027493545c59eba/anthropic-0.64.0.tar.gz", hash = "sha256:3d496c91a63dff64f451b3e8e4b238a9640bf87b0c11d0b74ddc372ba5a3fe58", size = 427893, upload-time = "2025-08-13T17:09:49.915Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/cf/ca0ba77805aec6171629a8b665c7dc224dab374539c3d27005b5d8c100a0/anthropic-0.57.1-py3-none-any.whl", hash = "sha256:33afc1f395af207d07ff1bffc0a3d1caac53c371793792569c5d2f09283ea306", size = 292779, upload-time = "2025-07-03T16:57:34.636Z" }, + { url = "https://files.pythonhosted.org/packages/a9/b2/2d268bcd5d6441df9dc0ebebc67107657edb8b0150d3fda1a5b81d1bec45/anthropic-0.64.0-py3-none-any.whl", hash = "sha256:6f5f7d913a6a95eb7f8e1bda4e75f76670e8acd8d4cd965e02e2a256b0429dd1", size = 297244, upload-time = "2025-08-13T17:09:47.908Z" }, ] [[package]] @@ -163,16 +163,16 @@ sdist = { url = "https://files.pythonhosted.org/packages/3e/38/7859ff46355f76f8d [[package]] name = "anyio" -version = "4.9.0" +version = "4.10.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "idna" }, { name = "sniffio" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload-time = "2025-03-17T00:02:54.77Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/b4/636b3b65173d3ce9a38ef5f0522789614e590dab6a8d505340a4efe4c567/anyio-4.10.0.tar.gz", hash = "sha256:3f3fae35c96039744587aa5b8371e7e8e603c0702999535961dd336026973ba6", size = 213252, upload-time = "2025-08-04T08:54:26.451Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload-time = "2025-03-17T00:02:52.713Z" }, + { url = "https://files.pythonhosted.org/packages/6f/12/e5e0282d673bb9746bacfb6e2dba8719989d3660cdb2ea79aee9a9651afb/anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1", size = 107213, upload-time = "2025-08-04T08:54:24.882Z" }, ] [[package]] @@ -202,69 +202,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e0/44/827b2a91a5816512fcaf3cc4ebc465ccd5d598c45cefa6703fcf4a79018f/attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1", size = 60752, upload-time = "2023-12-31T06:30:30.772Z" }, ] -[[package]] -name = "backoff" -version = "2.2.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/47/d7/5bbeb12c44d7c4f2fb5b56abce497eb5ed9f34d85701de869acedd602619/backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba", size = 17001, upload-time = "2022-10-05T19:19:32.061Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/df/73/b6e24bd22e6720ca8ee9a85a0c4a2971af8497d8f3193fa05390cbd46e09/backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8", size = 15148, upload-time = "2022-10-05T19:19:30.546Z" }, -] - -[[package]] -name = "bcrypt" -version = "4.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bb/5d/6d7433e0f3cd46ce0b43cd65e1db465ea024dbb8216fb2404e919c2ad77b/bcrypt-4.3.0.tar.gz", hash = "sha256:3a3fd2204178b6d2adcf09cb4f6426ffef54762577a7c9b54c159008cb288c18", size = 25697, upload-time = "2025-02-28T01:24:09.174Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bf/2c/3d44e853d1fe969d229bd58d39ae6902b3d924af0e2b5a60d17d4b809ded/bcrypt-4.3.0-cp313-cp313t-macosx_10_12_universal2.whl", hash = "sha256:f01e060f14b6b57bbb72fc5b4a83ac21c443c9a2ee708e04a10e9192f90a6281", size = 483719, upload-time = "2025-02-28T01:22:34.539Z" }, - { url = "https://files.pythonhosted.org/packages/a1/e2/58ff6e2a22eca2e2cff5370ae56dba29d70b1ea6fc08ee9115c3ae367795/bcrypt-4.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5eeac541cefd0bb887a371ef73c62c3cd78535e4887b310626036a7c0a817bb", size = 272001, upload-time = "2025-02-28T01:22:38.078Z" }, - { url = "https://files.pythonhosted.org/packages/37/1f/c55ed8dbe994b1d088309e366749633c9eb90d139af3c0a50c102ba68a1a/bcrypt-4.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59e1aa0e2cd871b08ca146ed08445038f42ff75968c7ae50d2fdd7860ade2180", size = 277451, upload-time = "2025-02-28T01:22:40.787Z" }, - { url = "https://files.pythonhosted.org/packages/d7/1c/794feb2ecf22fe73dcfb697ea7057f632061faceb7dcf0f155f3443b4d79/bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:0042b2e342e9ae3d2ed22727c1262f76cc4f345683b5c1715f0250cf4277294f", size = 272792, upload-time = "2025-02-28T01:22:43.144Z" }, - { url = "https://files.pythonhosted.org/packages/13/b7/0b289506a3f3598c2ae2bdfa0ea66969812ed200264e3f61df77753eee6d/bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74a8d21a09f5e025a9a23e7c0fd2c7fe8e7503e4d356c0a2c1486ba010619f09", size = 289752, upload-time = "2025-02-28T01:22:45.56Z" }, - { url = "https://files.pythonhosted.org/packages/dc/24/d0fb023788afe9e83cc118895a9f6c57e1044e7e1672f045e46733421fe6/bcrypt-4.3.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:0142b2cb84a009f8452c8c5a33ace5e3dfec4159e7735f5afe9a4d50a8ea722d", size = 277762, upload-time = "2025-02-28T01:22:47.023Z" }, - { url = "https://files.pythonhosted.org/packages/e4/38/cde58089492e55ac4ef6c49fea7027600c84fd23f7520c62118c03b4625e/bcrypt-4.3.0-cp313-cp313t-manylinux_2_34_aarch64.whl", hash = "sha256:12fa6ce40cde3f0b899729dbd7d5e8811cb892d31b6f7d0334a1f37748b789fd", size = 272384, upload-time = "2025-02-28T01:22:49.221Z" }, - { url = "https://files.pythonhosted.org/packages/de/6a/d5026520843490cfc8135d03012a413e4532a400e471e6188b01b2de853f/bcrypt-4.3.0-cp313-cp313t-manylinux_2_34_x86_64.whl", hash = "sha256:5bd3cca1f2aa5dbcf39e2aa13dd094ea181f48959e1071265de49cc2b82525af", size = 277329, upload-time = "2025-02-28T01:22:51.603Z" }, - { url = "https://files.pythonhosted.org/packages/b3/a3/4fc5255e60486466c389e28c12579d2829b28a527360e9430b4041df4cf9/bcrypt-4.3.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:335a420cfd63fc5bc27308e929bee231c15c85cc4c496610ffb17923abf7f231", size = 305241, upload-time = "2025-02-28T01:22:53.283Z" }, - { url = "https://files.pythonhosted.org/packages/c7/15/2b37bc07d6ce27cc94e5b10fd5058900eb8fb11642300e932c8c82e25c4a/bcrypt-4.3.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:0e30e5e67aed0187a1764911af023043b4542e70a7461ad20e837e94d23e1d6c", size = 309617, upload-time = "2025-02-28T01:22:55.461Z" }, - { url = "https://files.pythonhosted.org/packages/5f/1f/99f65edb09e6c935232ba0430c8c13bb98cb3194b6d636e61d93fe60ac59/bcrypt-4.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3b8d62290ebefd49ee0b3ce7500f5dbdcf13b81402c05f6dafab9a1e1b27212f", size = 335751, upload-time = "2025-02-28T01:22:57.81Z" }, - { url = "https://files.pythonhosted.org/packages/00/1b/b324030c706711c99769988fcb694b3cb23f247ad39a7823a78e361bdbb8/bcrypt-4.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:2ef6630e0ec01376f59a006dc72918b1bf436c3b571b80fa1968d775fa02fe7d", size = 355965, upload-time = "2025-02-28T01:22:59.181Z" }, - { url = "https://files.pythonhosted.org/packages/aa/dd/20372a0579dd915dfc3b1cd4943b3bca431866fcb1dfdfd7518c3caddea6/bcrypt-4.3.0-cp313-cp313t-win32.whl", hash = "sha256:7a4be4cbf241afee43f1c3969b9103a41b40bcb3a3f467ab19f891d9bc4642e4", size = 155316, upload-time = "2025-02-28T01:23:00.763Z" }, - { url = "https://files.pythonhosted.org/packages/6d/52/45d969fcff6b5577c2bf17098dc36269b4c02197d551371c023130c0f890/bcrypt-4.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5c1949bf259a388863ced887c7861da1df681cb2388645766c89fdfd9004c669", size = 147752, upload-time = "2025-02-28T01:23:02.908Z" }, - { url = "https://files.pythonhosted.org/packages/11/22/5ada0b9af72b60cbc4c9a399fdde4af0feaa609d27eb0adc61607997a3fa/bcrypt-4.3.0-cp38-abi3-macosx_10_12_universal2.whl", hash = "sha256:f81b0ed2639568bf14749112298f9e4e2b28853dab50a8b357e31798686a036d", size = 498019, upload-time = "2025-02-28T01:23:05.838Z" }, - { url = "https://files.pythonhosted.org/packages/b8/8c/252a1edc598dc1ce57905be173328eda073083826955ee3c97c7ff5ba584/bcrypt-4.3.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:864f8f19adbe13b7de11ba15d85d4a428c7e2f344bac110f667676a0ff84924b", size = 279174, upload-time = "2025-02-28T01:23:07.274Z" }, - { url = "https://files.pythonhosted.org/packages/29/5b/4547d5c49b85f0337c13929f2ccbe08b7283069eea3550a457914fc078aa/bcrypt-4.3.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e36506d001e93bffe59754397572f21bb5dc7c83f54454c990c74a468cd589e", size = 283870, upload-time = "2025-02-28T01:23:09.151Z" }, - { url = "https://files.pythonhosted.org/packages/be/21/7dbaf3fa1745cb63f776bb046e481fbababd7d344c5324eab47f5ca92dd2/bcrypt-4.3.0-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:842d08d75d9fe9fb94b18b071090220697f9f184d4547179b60734846461ed59", size = 279601, upload-time = "2025-02-28T01:23:11.461Z" }, - { url = "https://files.pythonhosted.org/packages/6d/64/e042fc8262e971347d9230d9abbe70d68b0a549acd8611c83cebd3eaec67/bcrypt-4.3.0-cp38-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7c03296b85cb87db865d91da79bf63d5609284fc0cab9472fdd8367bbd830753", size = 297660, upload-time = "2025-02-28T01:23:12.989Z" }, - { url = "https://files.pythonhosted.org/packages/50/b8/6294eb84a3fef3b67c69b4470fcdd5326676806bf2519cda79331ab3c3a9/bcrypt-4.3.0-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:62f26585e8b219cdc909b6a0069efc5e4267e25d4a3770a364ac58024f62a761", size = 284083, upload-time = "2025-02-28T01:23:14.5Z" }, - { url = "https://files.pythonhosted.org/packages/62/e6/baff635a4f2c42e8788fe1b1633911c38551ecca9a749d1052d296329da6/bcrypt-4.3.0-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:beeefe437218a65322fbd0069eb437e7c98137e08f22c4660ac2dc795c31f8bb", size = 279237, upload-time = "2025-02-28T01:23:16.686Z" }, - { url = "https://files.pythonhosted.org/packages/39/48/46f623f1b0c7dc2e5de0b8af5e6f5ac4cc26408ac33f3d424e5ad8da4a90/bcrypt-4.3.0-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:97eea7408db3a5bcce4a55d13245ab3fa566e23b4c67cd227062bb49e26c585d", size = 283737, upload-time = "2025-02-28T01:23:18.897Z" }, - { url = "https://files.pythonhosted.org/packages/49/8b/70671c3ce9c0fca4a6cc3cc6ccbaa7e948875a2e62cbd146e04a4011899c/bcrypt-4.3.0-cp38-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:191354ebfe305e84f344c5964c7cd5f924a3bfc5d405c75ad07f232b6dffb49f", size = 312741, upload-time = "2025-02-28T01:23:21.041Z" }, - { url = "https://files.pythonhosted.org/packages/27/fb/910d3a1caa2d249b6040a5caf9f9866c52114d51523ac2fb47578a27faee/bcrypt-4.3.0-cp38-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:41261d64150858eeb5ff43c753c4b216991e0ae16614a308a15d909503617732", size = 316472, upload-time = "2025-02-28T01:23:23.183Z" }, - { url = "https://files.pythonhosted.org/packages/dc/cf/7cf3a05b66ce466cfb575dbbda39718d45a609daa78500f57fa9f36fa3c0/bcrypt-4.3.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:33752b1ba962ee793fa2b6321404bf20011fe45b9afd2a842139de3011898fef", size = 343606, upload-time = "2025-02-28T01:23:25.361Z" }, - { url = "https://files.pythonhosted.org/packages/e3/b8/e970ecc6d7e355c0d892b7f733480f4aa8509f99b33e71550242cf0b7e63/bcrypt-4.3.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:50e6e80a4bfd23a25f5c05b90167c19030cf9f87930f7cb2eacb99f45d1c3304", size = 362867, upload-time = "2025-02-28T01:23:26.875Z" }, - { url = "https://files.pythonhosted.org/packages/a9/97/8d3118efd8354c555a3422d544163f40d9f236be5b96c714086463f11699/bcrypt-4.3.0-cp38-abi3-win32.whl", hash = "sha256:67a561c4d9fb9465ec866177e7aebcad08fe23aaf6fbd692a6fab69088abfc51", size = 160589, upload-time = "2025-02-28T01:23:28.381Z" }, - { url = "https://files.pythonhosted.org/packages/29/07/416f0b99f7f3997c69815365babbc2e8754181a4b1899d921b3c7d5b6f12/bcrypt-4.3.0-cp38-abi3-win_amd64.whl", hash = "sha256:584027857bc2843772114717a7490a37f68da563b3620f78a849bcb54dc11e62", size = 152794, upload-time = "2025-02-28T01:23:30.187Z" }, - { url = "https://files.pythonhosted.org/packages/6e/c1/3fa0e9e4e0bfd3fd77eb8b52ec198fd6e1fd7e9402052e43f23483f956dd/bcrypt-4.3.0-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:0d3efb1157edebfd9128e4e46e2ac1a64e0c1fe46fb023158a407c7892b0f8c3", size = 498969, upload-time = "2025-02-28T01:23:31.945Z" }, - { url = "https://files.pythonhosted.org/packages/ce/d4/755ce19b6743394787fbd7dff6bf271b27ee9b5912a97242e3caf125885b/bcrypt-4.3.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08bacc884fd302b611226c01014eca277d48f0a05187666bca23aac0dad6fe24", size = 279158, upload-time = "2025-02-28T01:23:34.161Z" }, - { url = "https://files.pythonhosted.org/packages/9b/5d/805ef1a749c965c46b28285dfb5cd272a7ed9fa971f970435a5133250182/bcrypt-4.3.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6746e6fec103fcd509b96bacdfdaa2fbde9a553245dbada284435173a6f1aef", size = 284285, upload-time = "2025-02-28T01:23:35.765Z" }, - { url = "https://files.pythonhosted.org/packages/ab/2b/698580547a4a4988e415721b71eb45e80c879f0fb04a62da131f45987b96/bcrypt-4.3.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:afe327968aaf13fc143a56a3360cb27d4ad0345e34da12c7290f1b00b8fe9a8b", size = 279583, upload-time = "2025-02-28T01:23:38.021Z" }, - { url = "https://files.pythonhosted.org/packages/f2/87/62e1e426418204db520f955ffd06f1efd389feca893dad7095bf35612eec/bcrypt-4.3.0-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d9af79d322e735b1fc33404b5765108ae0ff232d4b54666d46730f8ac1a43676", size = 297896, upload-time = "2025-02-28T01:23:39.575Z" }, - { url = "https://files.pythonhosted.org/packages/cb/c6/8fedca4c2ada1b6e889c52d2943b2f968d3427e5d65f595620ec4c06fa2f/bcrypt-4.3.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f1e3ffa1365e8702dc48c8b360fef8d7afeca482809c5e45e653af82ccd088c1", size = 284492, upload-time = "2025-02-28T01:23:40.901Z" }, - { url = "https://files.pythonhosted.org/packages/4d/4d/c43332dcaaddb7710a8ff5269fcccba97ed3c85987ddaa808db084267b9a/bcrypt-4.3.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:3004df1b323d10021fda07a813fd33e0fd57bef0e9a480bb143877f6cba996fe", size = 279213, upload-time = "2025-02-28T01:23:42.653Z" }, - { url = "https://files.pythonhosted.org/packages/dc/7f/1e36379e169a7df3a14a1c160a49b7b918600a6008de43ff20d479e6f4b5/bcrypt-4.3.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:531457e5c839d8caea9b589a1bcfe3756b0547d7814e9ce3d437f17da75c32b0", size = 284162, upload-time = "2025-02-28T01:23:43.964Z" }, - { url = "https://files.pythonhosted.org/packages/1c/0a/644b2731194b0d7646f3210dc4d80c7fee3ecb3a1f791a6e0ae6bb8684e3/bcrypt-4.3.0-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:17a854d9a7a476a89dcef6c8bd119ad23e0f82557afbd2c442777a16408e614f", size = 312856, upload-time = "2025-02-28T01:23:46.011Z" }, - { url = "https://files.pythonhosted.org/packages/dc/62/2a871837c0bb6ab0c9a88bf54de0fc021a6a08832d4ea313ed92a669d437/bcrypt-4.3.0-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6fb1fd3ab08c0cbc6826a2e0447610c6f09e983a281b919ed721ad32236b8b23", size = 316726, upload-time = "2025-02-28T01:23:47.575Z" }, - { url = "https://files.pythonhosted.org/packages/0c/a1/9898ea3faac0b156d457fd73a3cb9c2855c6fd063e44b8522925cdd8ce46/bcrypt-4.3.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e965a9c1e9a393b8005031ff52583cedc15b7884fce7deb8b0346388837d6cfe", size = 343664, upload-time = "2025-02-28T01:23:49.059Z" }, - { url = "https://files.pythonhosted.org/packages/40/f2/71b4ed65ce38982ecdda0ff20c3ad1b15e71949c78b2c053df53629ce940/bcrypt-4.3.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:79e70b8342a33b52b55d93b3a59223a844962bef479f6a0ea318ebbcadf71505", size = 363128, upload-time = "2025-02-28T01:23:50.399Z" }, - { url = "https://files.pythonhosted.org/packages/11/99/12f6a58eca6dea4be992d6c681b7ec9410a1d9f5cf368c61437e31daa879/bcrypt-4.3.0-cp39-abi3-win32.whl", hash = "sha256:b4d4e57f0a63fd0b358eb765063ff661328f69a04494427265950c71b992a39a", size = 160598, upload-time = "2025-02-28T01:23:51.775Z" }, - { url = "https://files.pythonhosted.org/packages/a9/cf/45fb5261ece3e6b9817d3d82b2f343a505fd58674a92577923bc500bd1aa/bcrypt-4.3.0-cp39-abi3-win_amd64.whl", hash = "sha256:e53e074b120f2877a35cc6c736b8eb161377caae8925c17688bd46ba56daaa5b", size = 152799, upload-time = "2025-02-28T01:23:53.139Z" }, - { url = "https://files.pythonhosted.org/packages/4c/b1/1289e21d710496b88340369137cc4c5f6ee036401190ea116a7b4ae6d32a/bcrypt-4.3.0-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a839320bf27d474e52ef8cb16449bb2ce0ba03ca9f44daba6d93fa1d8828e48a", size = 275103, upload-time = "2025-02-28T01:24:00.764Z" }, - { url = "https://files.pythonhosted.org/packages/94/41/19be9fe17e4ffc5d10b7b67f10e459fc4eee6ffe9056a88de511920cfd8d/bcrypt-4.3.0-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:bdc6a24e754a555d7316fa4774e64c6c3997d27ed2d1964d55920c7c227bc4ce", size = 280513, upload-time = "2025-02-28T01:24:02.243Z" }, - { url = "https://files.pythonhosted.org/packages/aa/73/05687a9ef89edebdd8ad7474c16d8af685eb4591c3c38300bb6aad4f0076/bcrypt-4.3.0-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:55a935b8e9a1d2def0626c4269db3fcd26728cbff1e84f0341465c31c4ee56d8", size = 274685, upload-time = "2025-02-28T01:24:04.512Z" }, - { url = "https://files.pythonhosted.org/packages/63/13/47bba97924ebe86a62ef83dc75b7c8a881d53c535f83e2c54c4bd701e05c/bcrypt-4.3.0-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:57967b7a28d855313a963aaea51bf6df89f833db4320da458e5b3c5ab6d4c938", size = 280110, upload-time = "2025-02-28T01:24:05.896Z" }, -] - [[package]] name = "betterproto-fw" version = "2.0.3" @@ -315,30 +252,61 @@ wheels = [ [[package]] name = "boto3" -version = "1.39.3" +version = "1.40.13" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, { name = "jmespath" }, { name = "s3transfer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/02/42/712a74bb86d06538c55067a35b8a82c57aa303eba95b2b1ee91c829288f4/boto3-1.39.3.tar.gz", hash = "sha256:0a367106497649ae3d8a7b571b8c3be01b7b935a0fe303d4cc2574ed03aecbb4", size = 111838, upload-time = "2025-07-03T19:26:00.988Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/f2/3f7e2b2d866cdce0fceca7aa19baba939291200b7087defc6063b183d7cc/boto3-1.40.13.tar.gz", hash = "sha256:7ff74221328868c5f7dcd7a9b959e7b882c1ab1b04cc9df868ea70de7d8bd004", size = 111961, upload-time = "2025-08-19T20:41:00.341Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/15/70/723d2ab259aeaed6c96e5c1857ebe7d474ed9aa8f487dea352c60f33798f/boto3-1.39.3-py3-none-any.whl", hash = "sha256:056cfa2440fe1a157a7c2be897c749c83e1a322144aa4dad889f2fca66571019", size = 139906, upload-time = "2025-07-03T19:25:58.803Z" }, + { url = "https://files.pythonhosted.org/packages/e4/63/43a2eaad011b0025ce12a2ffa6d1fea9a34d06b7d33bf32c54cc1eb2df65/boto3-1.40.13-py3-none-any.whl", hash = "sha256:b43110b6ad30e683fab769b4c5659b6b0606afa535627de831f7181306009d5e", size = 140075, upload-time = "2025-08-19T20:40:59.014Z" }, +] + +[[package]] +name = "boto3-stubs" +version = "1.40.13" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "botocore-stubs" }, + { name = "types-s3transfer" }, + { name = "typing-extensions", marker = "python_full_version < '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/db/6d/17a177a4609f301a71beafa87dd889f4c33e48c45f682d9753ea90277341/boto3_stubs-1.40.13.tar.gz", hash = "sha256:4a665589e9e4bedf8f4a107ba6f964c6fef3cab3cc63a3c46a21b8bec369002a", size = 101376, upload-time = "2025-08-19T20:45:27.69Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/90/61/e16115b938796bc5b67ecc72e7b082c16d762d27b77024d95bfe25f73999/boto3_stubs-1.40.13-py3-none-any.whl", hash = "sha256:e76b4280a0057a94a25b76d62688a7d97754afe380ec74e37932f1be375feac2", size = 70011, upload-time = "2025-08-19T20:45:22.276Z" }, +] + +[package.optional-dependencies] +s3 = [ + { name = "mypy-boto3-s3" }, ] [[package]] name = "botocore" -version = "1.39.3" +version = "1.40.13" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jmespath" }, { name = "python-dateutil" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/60/66/96e89cc261d75f0b8125436272c335c74d2a39df84504a0c3956adcd1301/botocore-1.39.3.tar.gz", hash = "sha256:da8f477e119f9f8a3aaa8b3c99d9c6856ed0a243680aa3a3fbbfc15a8d4093fb", size = 14132316, upload-time = "2025-07-03T19:25:49.502Z" } +sdist = { url = "https://files.pythonhosted.org/packages/8b/79/f8bd10ff414d0f2ecc2cbad09af384b962b22bb8a741da41e510c9e06bf1/botocore-1.40.13.tar.gz", hash = "sha256:d7003526a9169e8ab3566db2227cf0b85d9e689e9ff97f2803c629a8e4624fb5", size = 14349658, upload-time = "2025-08-19T20:40:49.97Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/53/e4/3698dbb037a44d82a501577c6e3824c19f4289f4afbcadb06793866250d8/botocore-1.39.3-py3-none-any.whl", hash = "sha256:66a81cfac18ad5e9f47696c73fdf44cdbd8f8ca51ab3fca1effca0aabf61f02f", size = 13791724, upload-time = "2025-07-03T19:25:44.026Z" }, + { url = "https://files.pythonhosted.org/packages/9a/d3/7132aef7b202f3256027b3ac4caff5878e01be4d16062c2a7a92c430f120/botocore-1.40.13-py3-none-any.whl", hash = "sha256:5bf7334d6a0a87a907d607b115710bede2440376cefa8c33b0d99571fb226d87", size = 14018709, upload-time = "2025-08-19T20:40:44.783Z" }, +] + +[[package]] +name = "botocore-stubs" +version = "1.38.46" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "types-awscrt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/05/45/27cabc7c3022dcb12de5098cc646b374065f5e72fae13600ff1756f365ee/botocore_stubs-1.38.46.tar.gz", hash = "sha256:a04e69766ab8bae338911c1897492f88d05cd489cd75f06e6eb4f135f9da8c7b", size = 42299, upload-time = "2025-06-29T22:58:24.765Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/84/06490071e26bab22ac79a684e98445df118adcf80c58c33ba5af184030f2/botocore_stubs-1.38.46-py3-none-any.whl", hash = "sha256:cc21d9a7dd994bdd90872db4664d817c4719b51cda8004fd507a4bf65b085a75", size = 66083, upload-time = "2025-06-29T22:58:22.234Z" }, ] [[package]] @@ -412,20 +380,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1c/fa/5408a03c041114ceab628ce21766a4ea882aa6f6f0a800e04ee3a30ec6b9/brotlicffi-1.1.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:994a4f0681bb6c6c3b0925530a1926b7a189d878e6e5e38fae8efa47c5d9c613", size = 366783, upload-time = "2023-09-14T14:22:07.096Z" }, ] -[[package]] -name = "build" -version = "1.2.2.post1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "os_name == 'nt'" }, - { name = "packaging" }, - { name = "pyproject-hooks" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/7d/46/aeab111f8e06793e4f0e421fcad593d547fb8313b50990f31681ee2fb1ad/build-1.2.2.post1.tar.gz", hash = "sha256:b36993e92ca9375a219c99e606a122ff365a760a2d4bba0caa09bd5278b608b7", size = 46701, upload-time = "2024-10-06T17:22:25.251Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/84/c2/80633736cd183ee4a62107413def345f7e6e3c01563dbca1417363cf957e/build-1.2.2.post1-py3-none-any.whl", hash = "sha256:1d61c0887fa860c01971625baae8bdd338e517b836a2f70dd1f7aa3a6b2fc5b5", size = 22950, upload-time = "2024-10-06T17:22:23.299Z" }, -] - [[package]] name = "cachetools" version = "5.5.2" @@ -437,11 +391,11 @@ wheels = [ [[package]] name = "certifi" -version = "2025.6.15" +version = "2025.8.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/73/f7/f14b46d4bcd21092d7d3ccef689615220d8a08fb25e564b65d20738e672e/certifi-2025.6.15.tar.gz", hash = "sha256:d747aa5a8b9bbbb1bb8c22bb13e22bd1f18e9796defa16bab421f7f7a317323b", size = 158753, upload-time = "2025-06-15T02:45:51.329Z" } +sdist = { url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407", size = 162386, upload-time = "2025-08-03T03:07:47.08Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/84/ae/320161bd181fc06471eed047ecce67b693fd7515b16d495d8932db763426/certifi-2025.6.15-py3-none-any.whl", hash = "sha256:2e0c7ce7cb5d8f8634ca55d2ba7e6ec2689a2fd6537d8dec1296a477a4910057", size = 157650, upload-time = "2025-06-15T02:45:49.977Z" }, + { url = "https://files.pythonhosted.org/packages/e5/48/1549795ba7742c948d2ad169c1c8cdbae65bc450d6cd753d124b17c8cd32/certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5", size = 161216, upload-time = "2025-08-03T03:07:45.777Z" }, ] [[package]] @@ -500,92 +454,55 @@ wheels = [ [[package]] name = "charset-normalizer" -version = "3.4.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e4/33/89c2ced2b67d1c2a61c19c6751aa8902d46ce3dacb23600a283619f5a12d/charset_normalizer-3.4.2.tar.gz", hash = "sha256:5baececa9ecba31eff645232d59845c07aa030f0c81ee70184a90d35099a0e63", size = 126367, upload-time = "2025-05-02T08:34:42.01Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/05/85/4c40d00dcc6284a1c1ad5de5e0996b06f39d8232f1031cd23c2f5c07ee86/charset_normalizer-3.4.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:be1e352acbe3c78727a16a455126d9ff83ea2dfdcbc83148d2982305a04714c2", size = 198794, upload-time = "2025-05-02T08:32:11.945Z" }, - { url = "https://files.pythonhosted.org/packages/41/d9/7a6c0b9db952598e97e93cbdfcb91bacd89b9b88c7c983250a77c008703c/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa88ca0b1932e93f2d961bf3addbb2db902198dca337d88c89e1559e066e7645", size = 142846, upload-time = "2025-05-02T08:32:13.946Z" }, - { url = "https://files.pythonhosted.org/packages/66/82/a37989cda2ace7e37f36c1a8ed16c58cf48965a79c2142713244bf945c89/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d524ba3f1581b35c03cb42beebab4a13e6cdad7b36246bd22541fa585a56cccd", size = 153350, upload-time = "2025-05-02T08:32:15.873Z" }, - { url = "https://files.pythonhosted.org/packages/df/68/a576b31b694d07b53807269d05ec3f6f1093e9545e8607121995ba7a8313/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28a1005facc94196e1fb3e82a3d442a9d9110b8434fc1ded7a24a2983c9888d8", size = 145657, upload-time = "2025-05-02T08:32:17.283Z" }, - { url = "https://files.pythonhosted.org/packages/92/9b/ad67f03d74554bed3aefd56fe836e1623a50780f7c998d00ca128924a499/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb20a30fe1175ecabed17cbf7812f7b804b8a315a25f24678bcdf120a90077f", size = 147260, upload-time = "2025-05-02T08:32:18.807Z" }, - { url = "https://files.pythonhosted.org/packages/a6/e6/8aebae25e328160b20e31a7e9929b1578bbdc7f42e66f46595a432f8539e/charset_normalizer-3.4.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f5d9ed7f254402c9e7d35d2f5972c9bbea9040e99cd2861bd77dc68263277c7", size = 149164, upload-time = "2025-05-02T08:32:20.333Z" }, - { url = "https://files.pythonhosted.org/packages/8b/f2/b3c2f07dbcc248805f10e67a0262c93308cfa149a4cd3d1fe01f593e5fd2/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:efd387a49825780ff861998cd959767800d54f8308936b21025326de4b5a42b9", size = 144571, upload-time = "2025-05-02T08:32:21.86Z" }, - { url = "https://files.pythonhosted.org/packages/60/5b/c3f3a94bc345bc211622ea59b4bed9ae63c00920e2e8f11824aa5708e8b7/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f0aa37f3c979cf2546b73e8222bbfa3dc07a641585340179d768068e3455e544", size = 151952, upload-time = "2025-05-02T08:32:23.434Z" }, - { url = "https://files.pythonhosted.org/packages/e2/4d/ff460c8b474122334c2fa394a3f99a04cf11c646da895f81402ae54f5c42/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e70e990b2137b29dc5564715de1e12701815dacc1d056308e2b17e9095372a82", size = 155959, upload-time = "2025-05-02T08:32:24.993Z" }, - { url = "https://files.pythonhosted.org/packages/a2/2b/b964c6a2fda88611a1fe3d4c400d39c66a42d6c169c924818c848f922415/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0c8c57f84ccfc871a48a47321cfa49ae1df56cd1d965a09abe84066f6853b9c0", size = 153030, upload-time = "2025-05-02T08:32:26.435Z" }, - { url = "https://files.pythonhosted.org/packages/59/2e/d3b9811db26a5ebf444bc0fa4f4be5aa6d76fc6e1c0fd537b16c14e849b6/charset_normalizer-3.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6b66f92b17849b85cad91259efc341dce9c1af48e2173bf38a85c6329f1033e5", size = 148015, upload-time = "2025-05-02T08:32:28.376Z" }, - { url = "https://files.pythonhosted.org/packages/90/07/c5fd7c11eafd561bb51220d600a788f1c8d77c5eef37ee49454cc5c35575/charset_normalizer-3.4.2-cp311-cp311-win32.whl", hash = "sha256:daac4765328a919a805fa5e2720f3e94767abd632ae410a9062dff5412bae65a", size = 98106, upload-time = "2025-05-02T08:32:30.281Z" }, - { url = "https://files.pythonhosted.org/packages/a8/05/5e33dbef7e2f773d672b6d79f10ec633d4a71cd96db6673625838a4fd532/charset_normalizer-3.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:e53efc7c7cee4c1e70661e2e112ca46a575f90ed9ae3fef200f2a25e954f4b28", size = 105402, upload-time = "2025-05-02T08:32:32.191Z" }, - { url = "https://files.pythonhosted.org/packages/d7/a4/37f4d6035c89cac7930395a35cc0f1b872e652eaafb76a6075943754f095/charset_normalizer-3.4.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0c29de6a1a95f24b9a1aa7aefd27d2487263f00dfd55a77719b530788f75cff7", size = 199936, upload-time = "2025-05-02T08:32:33.712Z" }, - { url = "https://files.pythonhosted.org/packages/ee/8a/1a5e33b73e0d9287274f899d967907cd0bf9c343e651755d9307e0dbf2b3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cddf7bd982eaa998934a91f69d182aec997c6c468898efe6679af88283b498d3", size = 143790, upload-time = "2025-05-02T08:32:35.768Z" }, - { url = "https://files.pythonhosted.org/packages/66/52/59521f1d8e6ab1482164fa21409c5ef44da3e9f653c13ba71becdd98dec3/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcbe676a55d7445b22c10967bceaaf0ee69407fbe0ece4d032b6eb8d4565982a", size = 153924, upload-time = "2025-05-02T08:32:37.284Z" }, - { url = "https://files.pythonhosted.org/packages/86/2d/fb55fdf41964ec782febbf33cb64be480a6b8f16ded2dbe8db27a405c09f/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d41c4d287cfc69060fa91cae9683eacffad989f1a10811995fa309df656ec214", size = 146626, upload-time = "2025-05-02T08:32:38.803Z" }, - { url = "https://files.pythonhosted.org/packages/8c/73/6ede2ec59bce19b3edf4209d70004253ec5f4e319f9a2e3f2f15601ed5f7/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e594135de17ab3866138f496755f302b72157d115086d100c3f19370839dd3a", size = 148567, upload-time = "2025-05-02T08:32:40.251Z" }, - { url = "https://files.pythonhosted.org/packages/09/14/957d03c6dc343c04904530b6bef4e5efae5ec7d7990a7cbb868e4595ee30/charset_normalizer-3.4.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf713fe9a71ef6fd5adf7a79670135081cd4431c2943864757f0fa3a65b1fafd", size = 150957, upload-time = "2025-05-02T08:32:41.705Z" }, - { url = "https://files.pythonhosted.org/packages/0d/c8/8174d0e5c10ccebdcb1b53cc959591c4c722a3ad92461a273e86b9f5a302/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a370b3e078e418187da8c3674eddb9d983ec09445c99a3a263c2011993522981", size = 145408, upload-time = "2025-05-02T08:32:43.709Z" }, - { url = "https://files.pythonhosted.org/packages/58/aa/8904b84bc8084ac19dc52feb4f5952c6df03ffb460a887b42615ee1382e8/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a955b438e62efdf7e0b7b52a64dc5c3396e2634baa62471768a64bc2adb73d5c", size = 153399, upload-time = "2025-05-02T08:32:46.197Z" }, - { url = "https://files.pythonhosted.org/packages/c2/26/89ee1f0e264d201cb65cf054aca6038c03b1a0c6b4ae998070392a3ce605/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7222ffd5e4de8e57e03ce2cef95a4c43c98fcb72ad86909abdfc2c17d227fc1b", size = 156815, upload-time = "2025-05-02T08:32:48.105Z" }, - { url = "https://files.pythonhosted.org/packages/fd/07/68e95b4b345bad3dbbd3a8681737b4338ff2c9df29856a6d6d23ac4c73cb/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bee093bf902e1d8fc0ac143c88902c3dfc8941f7ea1d6a8dd2bcb786d33db03d", size = 154537, upload-time = "2025-05-02T08:32:49.719Z" }, - { url = "https://files.pythonhosted.org/packages/77/1a/5eefc0ce04affb98af07bc05f3bac9094513c0e23b0562d64af46a06aae4/charset_normalizer-3.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dedb8adb91d11846ee08bec4c8236c8549ac721c245678282dcb06b221aab59f", size = 149565, upload-time = "2025-05-02T08:32:51.404Z" }, - { url = "https://files.pythonhosted.org/packages/37/a0/2410e5e6032a174c95e0806b1a6585eb21e12f445ebe239fac441995226a/charset_normalizer-3.4.2-cp312-cp312-win32.whl", hash = "sha256:db4c7bf0e07fc3b7d89ac2a5880a6a8062056801b83ff56d8464b70f65482b6c", size = 98357, upload-time = "2025-05-02T08:32:53.079Z" }, - { url = "https://files.pythonhosted.org/packages/6c/4f/c02d5c493967af3eda9c771ad4d2bbc8df6f99ddbeb37ceea6e8716a32bc/charset_normalizer-3.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:5a9979887252a82fefd3d3ed2a8e3b937a7a809f65dcb1e068b090e165bbe99e", size = 105776, upload-time = "2025-05-02T08:32:54.573Z" }, - { url = "https://files.pythonhosted.org/packages/ea/12/a93df3366ed32db1d907d7593a94f1fe6293903e3e92967bebd6950ed12c/charset_normalizer-3.4.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:926ca93accd5d36ccdabd803392ddc3e03e6d4cd1cf17deff3b989ab8e9dbcf0", size = 199622, upload-time = "2025-05-02T08:32:56.363Z" }, - { url = "https://files.pythonhosted.org/packages/04/93/bf204e6f344c39d9937d3c13c8cd5bbfc266472e51fc8c07cb7f64fcd2de/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eba9904b0f38a143592d9fc0e19e2df0fa2e41c3c3745554761c5f6447eedabf", size = 143435, upload-time = "2025-05-02T08:32:58.551Z" }, - { url = "https://files.pythonhosted.org/packages/22/2a/ea8a2095b0bafa6c5b5a55ffdc2f924455233ee7b91c69b7edfcc9e02284/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3fddb7e2c84ac87ac3a947cb4e66d143ca5863ef48e4a5ecb83bd48619e4634e", size = 153653, upload-time = "2025-05-02T08:33:00.342Z" }, - { url = "https://files.pythonhosted.org/packages/b6/57/1b090ff183d13cef485dfbe272e2fe57622a76694061353c59da52c9a659/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98f862da73774290f251b9df8d11161b6cf25b599a66baf087c1ffe340e9bfd1", size = 146231, upload-time = "2025-05-02T08:33:02.081Z" }, - { url = "https://files.pythonhosted.org/packages/e2/28/ffc026b26f441fc67bd21ab7f03b313ab3fe46714a14b516f931abe1a2d8/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c9379d65defcab82d07b2a9dfbfc2e95bc8fe0ebb1b176a3190230a3ef0e07c", size = 148243, upload-time = "2025-05-02T08:33:04.063Z" }, - { url = "https://files.pythonhosted.org/packages/c0/0f/9abe9bd191629c33e69e47c6ef45ef99773320e9ad8e9cb08b8ab4a8d4cb/charset_normalizer-3.4.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e635b87f01ebc977342e2697d05b56632f5f879a4f15955dfe8cef2448b51691", size = 150442, upload-time = "2025-05-02T08:33:06.418Z" }, - { url = "https://files.pythonhosted.org/packages/67/7c/a123bbcedca91d5916c056407f89a7f5e8fdfce12ba825d7d6b9954a1a3c/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1c95a1e2902a8b722868587c0e1184ad5c55631de5afc0eb96bc4b0d738092c0", size = 145147, upload-time = "2025-05-02T08:33:08.183Z" }, - { url = "https://files.pythonhosted.org/packages/ec/fe/1ac556fa4899d967b83e9893788e86b6af4d83e4726511eaaad035e36595/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ef8de666d6179b009dce7bcb2ad4c4a779f113f12caf8dc77f0162c29d20490b", size = 153057, upload-time = "2025-05-02T08:33:09.986Z" }, - { url = "https://files.pythonhosted.org/packages/2b/ff/acfc0b0a70b19e3e54febdd5301a98b72fa07635e56f24f60502e954c461/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:32fc0341d72e0f73f80acb0a2c94216bd704f4f0bce10aedea38f30502b271ff", size = 156454, upload-time = "2025-05-02T08:33:11.814Z" }, - { url = "https://files.pythonhosted.org/packages/92/08/95b458ce9c740d0645feb0e96cea1f5ec946ea9c580a94adfe0b617f3573/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:289200a18fa698949d2b39c671c2cc7a24d44096784e76614899a7ccf2574b7b", size = 154174, upload-time = "2025-05-02T08:33:13.707Z" }, - { url = "https://files.pythonhosted.org/packages/78/be/8392efc43487ac051eee6c36d5fbd63032d78f7728cb37aebcc98191f1ff/charset_normalizer-3.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4a476b06fbcf359ad25d34a057b7219281286ae2477cc5ff5e3f70a246971148", size = 149166, upload-time = "2025-05-02T08:33:15.458Z" }, - { url = "https://files.pythonhosted.org/packages/44/96/392abd49b094d30b91d9fbda6a69519e95802250b777841cf3bda8fe136c/charset_normalizer-3.4.2-cp313-cp313-win32.whl", hash = "sha256:aaeeb6a479c7667fbe1099af9617c83aaca22182d6cf8c53966491a0f1b7ffb7", size = 98064, upload-time = "2025-05-02T08:33:17.06Z" }, - { url = "https://files.pythonhosted.org/packages/e9/b0/0200da600134e001d91851ddc797809e2fe0ea72de90e09bec5a2fbdaccb/charset_normalizer-3.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:aa6af9e7d59f9c12b33ae4e9450619cf2488e2bbe9b44030905877f0b2324980", size = 105641, upload-time = "2025-05-02T08:33:18.753Z" }, - { url = "https://files.pythonhosted.org/packages/20/94/c5790835a017658cbfabd07f3bfb549140c3ac458cfc196323996b10095a/charset_normalizer-3.4.2-py3-none-any.whl", hash = "sha256:7f56930ab0abd1c45cd15be65cc741c28b1c9a34876ce8c17a2fa107810c0af0", size = 52626, upload-time = "2025-05-02T08:34:40.053Z" }, -] - -[[package]] -name = "chromadb" -version = "1.0.15" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "bcrypt" }, - { name = "build" }, - { name = "grpcio" }, - { name = "httpx" }, - { name = "importlib-resources" }, - { name = "jsonschema" }, - { name = "kubernetes" }, - { name = "mmh3" }, - { name = "numpy" }, - { name = "onnxruntime" }, - { name = "opentelemetry-api" }, - { name = "opentelemetry-exporter-otlp-proto-grpc" }, - { name = "opentelemetry-sdk" }, - { name = "orjson" }, - { name = "overrides" }, - { name = "posthog" }, - { name = "pybase64" }, - { name = "pydantic" }, - { name = "pypika" }, - { name = "pyyaml" }, - { name = "rich" }, - { name = "tenacity" }, - { name = "tokenizers" }, - { name = "tqdm" }, - { name = "typer" }, - { name = "typing-extensions" }, - { name = "uvicorn", extra = ["standard"] }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ad/e2/0653b2e539db5512d2200c759f1bc7f9ef5609fe47f3c7d24b82f62dc00f/chromadb-1.0.15.tar.gz", hash = "sha256:3e910da3f5414e2204f89c7beca1650847f2bf3bd71f11a2e40aad1eb31050aa", size = 1218840, upload-time = "2025-07-02T17:07:09.875Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/85/5a/866c6f0c2160cbc8dca0cf77b2fb391dcf435b32a58743da1bc1a08dc442/chromadb-1.0.15-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:51791553014297798b53df4e043e9c30f4e8bd157647971a6bb02b04bfa65f82", size = 18838820, upload-time = "2025-07-02T17:07:07.632Z" }, - { url = "https://files.pythonhosted.org/packages/e1/18/ff9b58ab5d334f5ecff7fdbacd6761bac467176708fa4d2500ae7c048af0/chromadb-1.0.15-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:48015803c0631c3a817befc276436dc084bb628c37fd4214047212afb2056291", size = 18057131, upload-time = "2025-07-02T17:07:05.15Z" }, - { url = "https://files.pythonhosted.org/packages/31/49/74e34cc5aeeb25aff2c0ede6790b3671e14c1b91574dd8f98d266a4c5aad/chromadb-1.0.15-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b73cd6fb32fcdd91c577cca16ea6112b691d72b441bb3f2140426d1e79e453a", size = 18595284, upload-time = "2025-07-02T17:06:59.102Z" }, - { url = "https://files.pythonhosted.org/packages/cb/33/190df917a057067e37f8b48d082d769bed8b3c0c507edefc7b6c6bb577d0/chromadb-1.0.15-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:479f1b401af9e7c20f50642ffb3376abbfd78e2b5b170429f7c79eff52e367db", size = 19526626, upload-time = "2025-07-02T17:07:02.163Z" }, - { url = "https://files.pythonhosted.org/packages/a1/30/6890da607358993f87a01e80bcce916b4d91515ce865f07dc06845cb472f/chromadb-1.0.15-cp39-abi3-win_amd64.whl", hash = "sha256:e0cb3b93fdc42b1786f151d413ef36299f30f783a30ce08bf0bfb12e552b4190", size = 19520490, upload-time = "2025-07-02T17:07:11.559Z" }, +version = "3.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/83/2d/5fd176ceb9b2fc619e63405525573493ca23441330fcdaee6bef9460e924/charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14", size = 122371, upload-time = "2025-08-09T07:57:28.46Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/b5/991245018615474a60965a7c9cd2b4efbaabd16d582a5547c47ee1c7730b/charset_normalizer-3.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b", size = 204483, upload-time = "2025-08-09T07:55:53.12Z" }, + { url = "https://files.pythonhosted.org/packages/c7/2a/ae245c41c06299ec18262825c1569c5d3298fc920e4ddf56ab011b417efd/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64", size = 145520, upload-time = "2025-08-09T07:55:54.712Z" }, + { url = "https://files.pythonhosted.org/packages/3a/a4/b3b6c76e7a635748c4421d2b92c7b8f90a432f98bda5082049af37ffc8e3/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91", size = 158876, upload-time = "2025-08-09T07:55:56.024Z" }, + { url = "https://files.pythonhosted.org/packages/e2/e6/63bb0e10f90a8243c5def74b5b105b3bbbfb3e7bb753915fe333fb0c11ea/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f", size = 156083, upload-time = "2025-08-09T07:55:57.582Z" }, + { url = "https://files.pythonhosted.org/packages/87/df/b7737ff046c974b183ea9aa111b74185ac8c3a326c6262d413bd5a1b8c69/charset_normalizer-3.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07", size = 150295, upload-time = "2025-08-09T07:55:59.147Z" }, + { url = "https://files.pythonhosted.org/packages/61/f1/190d9977e0084d3f1dc169acd060d479bbbc71b90bf3e7bf7b9927dec3eb/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30", size = 148379, upload-time = "2025-08-09T07:56:00.364Z" }, + { url = "https://files.pythonhosted.org/packages/4c/92/27dbe365d34c68cfe0ca76f1edd70e8705d82b378cb54ebbaeabc2e3029d/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14", size = 160018, upload-time = "2025-08-09T07:56:01.678Z" }, + { url = "https://files.pythonhosted.org/packages/99/04/baae2a1ea1893a01635d475b9261c889a18fd48393634b6270827869fa34/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c", size = 157430, upload-time = "2025-08-09T07:56:02.87Z" }, + { url = "https://files.pythonhosted.org/packages/2f/36/77da9c6a328c54d17b960c89eccacfab8271fdaaa228305330915b88afa9/charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae", size = 151600, upload-time = "2025-08-09T07:56:04.089Z" }, + { url = "https://files.pythonhosted.org/packages/64/d4/9eb4ff2c167edbbf08cdd28e19078bf195762e9bd63371689cab5ecd3d0d/charset_normalizer-3.4.3-cp311-cp311-win32.whl", hash = "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849", size = 99616, upload-time = "2025-08-09T07:56:05.658Z" }, + { url = "https://files.pythonhosted.org/packages/f4/9c/996a4a028222e7761a96634d1820de8a744ff4327a00ada9c8942033089b/charset_normalizer-3.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c", size = 107108, upload-time = "2025-08-09T07:56:07.176Z" }, + { url = "https://files.pythonhosted.org/packages/e9/5e/14c94999e418d9b87682734589404a25854d5f5d0408df68bc15b6ff54bb/charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1", size = 205655, upload-time = "2025-08-09T07:56:08.475Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a8/c6ec5d389672521f644505a257f50544c074cf5fc292d5390331cd6fc9c3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884", size = 146223, upload-time = "2025-08-09T07:56:09.708Z" }, + { url = "https://files.pythonhosted.org/packages/fc/eb/a2ffb08547f4e1e5415fb69eb7db25932c52a52bed371429648db4d84fb1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018", size = 159366, upload-time = "2025-08-09T07:56:11.326Z" }, + { url = "https://files.pythonhosted.org/packages/82/10/0fd19f20c624b278dddaf83b8464dcddc2456cb4b02bb902a6da126b87a1/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392", size = 157104, upload-time = "2025-08-09T07:56:13.014Z" }, + { url = "https://files.pythonhosted.org/packages/16/ab/0233c3231af734f5dfcf0844aa9582d5a1466c985bbed6cedab85af9bfe3/charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f", size = 151830, upload-time = "2025-08-09T07:56:14.428Z" }, + { url = "https://files.pythonhosted.org/packages/ae/02/e29e22b4e02839a0e4a06557b1999d0a47db3567e82989b5bb21f3fbbd9f/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154", size = 148854, upload-time = "2025-08-09T07:56:16.051Z" }, + { url = "https://files.pythonhosted.org/packages/05/6b/e2539a0a4be302b481e8cafb5af8792da8093b486885a1ae4d15d452bcec/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491", size = 160670, upload-time = "2025-08-09T07:56:17.314Z" }, + { url = "https://files.pythonhosted.org/packages/31/e7/883ee5676a2ef217a40ce0bffcc3d0dfbf9e64cbcfbdf822c52981c3304b/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93", size = 158501, upload-time = "2025-08-09T07:56:18.641Z" }, + { url = "https://files.pythonhosted.org/packages/c1/35/6525b21aa0db614cf8b5792d232021dca3df7f90a1944db934efa5d20bb1/charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f", size = 153173, upload-time = "2025-08-09T07:56:20.289Z" }, + { url = "https://files.pythonhosted.org/packages/50/ee/f4704bad8201de513fdc8aac1cabc87e38c5818c93857140e06e772b5892/charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37", size = 99822, upload-time = "2025-08-09T07:56:21.551Z" }, + { url = "https://files.pythonhosted.org/packages/39/f5/3b3836ca6064d0992c58c7561c6b6eee1b3892e9665d650c803bd5614522/charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc", size = 107543, upload-time = "2025-08-09T07:56:23.115Z" }, + { url = "https://files.pythonhosted.org/packages/65/ca/2135ac97709b400c7654b4b764daf5c5567c2da45a30cdd20f9eefe2d658/charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe", size = 205326, upload-time = "2025-08-09T07:56:24.721Z" }, + { url = "https://files.pythonhosted.org/packages/71/11/98a04c3c97dd34e49c7d247083af03645ca3730809a5509443f3c37f7c99/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8", size = 146008, upload-time = "2025-08-09T07:56:26.004Z" }, + { url = "https://files.pythonhosted.org/packages/60/f5/4659a4cb3c4ec146bec80c32d8bb16033752574c20b1252ee842a95d1a1e/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9", size = 159196, upload-time = "2025-08-09T07:56:27.25Z" }, + { url = "https://files.pythonhosted.org/packages/86/9e/f552f7a00611f168b9a5865a1414179b2c6de8235a4fa40189f6f79a1753/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31", size = 156819, upload-time = "2025-08-09T07:56:28.515Z" }, + { url = "https://files.pythonhosted.org/packages/7e/95/42aa2156235cbc8fa61208aded06ef46111c4d3f0de233107b3f38631803/charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f", size = 151350, upload-time = "2025-08-09T07:56:29.716Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a9/3865b02c56f300a6f94fc631ef54f0a8a29da74fb45a773dfd3dcd380af7/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927", size = 148644, upload-time = "2025-08-09T07:56:30.984Z" }, + { url = "https://files.pythonhosted.org/packages/77/d9/cbcf1a2a5c7d7856f11e7ac2d782aec12bdfea60d104e60e0aa1c97849dc/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9", size = 160468, upload-time = "2025-08-09T07:56:32.252Z" }, + { url = "https://files.pythonhosted.org/packages/f6/42/6f45efee8697b89fda4d50580f292b8f7f9306cb2971d4b53f8914e4d890/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5", size = 158187, upload-time = "2025-08-09T07:56:33.481Z" }, + { url = "https://files.pythonhosted.org/packages/70/99/f1c3bdcfaa9c45b3ce96f70b14f070411366fa19549c1d4832c935d8e2c3/charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc", size = 152699, upload-time = "2025-08-09T07:56:34.739Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ad/b0081f2f99a4b194bcbb1934ef3b12aa4d9702ced80a37026b7607c72e58/charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce", size = 99580, upload-time = "2025-08-09T07:56:35.981Z" }, + { url = "https://files.pythonhosted.org/packages/9a/8f/ae790790c7b64f925e5c953b924aaa42a243fb778fed9e41f147b2a5715a/charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef", size = 107366, upload-time = "2025-08-09T07:56:37.339Z" }, + { url = "https://files.pythonhosted.org/packages/8e/91/b5a06ad970ddc7a0e513112d40113e834638f4ca1120eb727a249fb2715e/charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15", size = 204342, upload-time = "2025-08-09T07:56:38.687Z" }, + { url = "https://files.pythonhosted.org/packages/ce/ec/1edc30a377f0a02689342f214455c3f6c2fbedd896a1d2f856c002fc3062/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db", size = 145995, upload-time = "2025-08-09T07:56:40.048Z" }, + { url = "https://files.pythonhosted.org/packages/17/e5/5e67ab85e6d22b04641acb5399c8684f4d37caf7558a53859f0283a650e9/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d", size = 158640, upload-time = "2025-08-09T07:56:41.311Z" }, + { url = "https://files.pythonhosted.org/packages/f1/e5/38421987f6c697ee3722981289d554957c4be652f963d71c5e46a262e135/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096", size = 156636, upload-time = "2025-08-09T07:56:43.195Z" }, + { url = "https://files.pythonhosted.org/packages/a0/e4/5a075de8daa3ec0745a9a3b54467e0c2967daaaf2cec04c845f73493e9a1/charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa", size = 150939, upload-time = "2025-08-09T07:56:44.819Z" }, + { url = "https://files.pythonhosted.org/packages/02/f7/3611b32318b30974131db62b4043f335861d4d9b49adc6d57c1149cc49d4/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049", size = 148580, upload-time = "2025-08-09T07:56:46.684Z" }, + { url = "https://files.pythonhosted.org/packages/7e/61/19b36f4bd67f2793ab6a99b979b4e4f3d8fc754cbdffb805335df4337126/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0", size = 159870, upload-time = "2025-08-09T07:56:47.941Z" }, + { url = "https://files.pythonhosted.org/packages/06/57/84722eefdd338c04cf3030ada66889298eaedf3e7a30a624201e0cbe424a/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92", size = 157797, upload-time = "2025-08-09T07:56:49.756Z" }, + { url = "https://files.pythonhosted.org/packages/72/2a/aff5dd112b2f14bcc3462c312dce5445806bfc8ab3a7328555da95330e4b/charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16", size = 152224, upload-time = "2025-08-09T07:56:51.369Z" }, + { url = "https://files.pythonhosted.org/packages/b7/8c/9839225320046ed279c6e839d51f028342eb77c91c89b8ef2549f951f3ec/charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce", size = 100086, upload-time = "2025-08-09T07:56:52.722Z" }, + { url = "https://files.pythonhosted.org/packages/ee/7a/36fbcf646e41f710ce0a563c1c9a343c6edf9be80786edeb15b6f62e17db/charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c", size = 107400, upload-time = "2025-08-09T07:56:55.172Z" }, + { url = "https://files.pythonhosted.org/packages/8a/1f/f041989e93b001bc4e44bb1669ccdcf54d3f00e628229a85b08d330615c5/charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a", size = 53175, upload-time = "2025-08-09T07:57:26.864Z" }, ] [[package]] @@ -618,118 +535,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] -[[package]] -name = "coloredlogs" -version = "15.0.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "humanfriendly" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cc/c7/eed8f27100517e8c0e6b923d5f0845d0cb99763da6fdee00478f91db7325/coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0", size = 278520, upload-time = "2021-06-11T10:22:45.202Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a7/06/3d6badcf13db419e25b07041d9c7b4a2c331d3f4e7134445ec5df57714cd/coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934", size = 46018, upload-time = "2021-06-11T10:22:42.561Z" }, -] - -[[package]] -name = "coverage" -version = "7.9.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/04/b7/c0465ca253df10a9e8dae0692a4ae6e9726d245390aaef92360e1d6d3832/coverage-7.9.2.tar.gz", hash = "sha256:997024fa51e3290264ffd7492ec97d0690293ccd2b45a6cd7d82d945a4a80c8b", size = 813556, upload-time = "2025-07-03T10:54:15.101Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/39/40/916786453bcfafa4c788abee4ccd6f592b5b5eca0cd61a32a4e5a7ef6e02/coverage-7.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a7a56a2964a9687b6aba5b5ced6971af308ef6f79a91043c05dd4ee3ebc3e9ba", size = 212152, upload-time = "2025-07-03T10:52:53.562Z" }, - { url = "https://files.pythonhosted.org/packages/9f/66/cc13bae303284b546a030762957322bbbff1ee6b6cb8dc70a40f8a78512f/coverage-7.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:123d589f32c11d9be7fe2e66d823a236fe759b0096f5db3fb1b75b2fa414a4fa", size = 212540, upload-time = "2025-07-03T10:52:55.196Z" }, - { url = "https://files.pythonhosted.org/packages/0f/3c/d56a764b2e5a3d43257c36af4a62c379df44636817bb5f89265de4bf8bd7/coverage-7.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:333b2e0ca576a7dbd66e85ab402e35c03b0b22f525eed82681c4b866e2e2653a", size = 245097, upload-time = "2025-07-03T10:52:56.509Z" }, - { url = "https://files.pythonhosted.org/packages/b1/46/bd064ea8b3c94eb4ca5d90e34d15b806cba091ffb2b8e89a0d7066c45791/coverage-7.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:326802760da234baf9f2f85a39e4a4b5861b94f6c8d95251f699e4f73b1835dc", size = 242812, upload-time = "2025-07-03T10:52:57.842Z" }, - { url = "https://files.pythonhosted.org/packages/43/02/d91992c2b29bc7afb729463bc918ebe5f361be7f1daae93375a5759d1e28/coverage-7.9.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19e7be4cfec248df38ce40968c95d3952fbffd57b400d4b9bb580f28179556d2", size = 244617, upload-time = "2025-07-03T10:52:59.239Z" }, - { url = "https://files.pythonhosted.org/packages/b7/4f/8fadff6bf56595a16d2d6e33415841b0163ac660873ed9a4e9046194f779/coverage-7.9.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0b4a4cb73b9f2b891c1788711408ef9707666501ba23684387277ededab1097c", size = 244263, upload-time = "2025-07-03T10:53:00.601Z" }, - { url = "https://files.pythonhosted.org/packages/9b/d2/e0be7446a2bba11739edb9f9ba4eff30b30d8257370e237418eb44a14d11/coverage-7.9.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:2c8937fa16c8c9fbbd9f118588756e7bcdc7e16a470766a9aef912dd3f117dbd", size = 242314, upload-time = "2025-07-03T10:53:01.932Z" }, - { url = "https://files.pythonhosted.org/packages/9d/7d/dcbac9345000121b8b57a3094c2dfcf1ccc52d8a14a40c1d4bc89f936f80/coverage-7.9.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:42da2280c4d30c57a9b578bafd1d4494fa6c056d4c419d9689e66d775539be74", size = 242904, upload-time = "2025-07-03T10:53:03.478Z" }, - { url = "https://files.pythonhosted.org/packages/41/58/11e8db0a0c0510cf31bbbdc8caf5d74a358b696302a45948d7c768dfd1cf/coverage-7.9.2-cp311-cp311-win32.whl", hash = "sha256:14fa8d3da147f5fdf9d298cacc18791818f3f1a9f542c8958b80c228320e90c6", size = 214553, upload-time = "2025-07-03T10:53:05.174Z" }, - { url = "https://files.pythonhosted.org/packages/3a/7d/751794ec8907a15e257136e48dc1021b1f671220ecccfd6c4eaf30802714/coverage-7.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:549cab4892fc82004f9739963163fd3aac7a7b0df430669b75b86d293d2df2a7", size = 215441, upload-time = "2025-07-03T10:53:06.472Z" }, - { url = "https://files.pythonhosted.org/packages/62/5b/34abcedf7b946c1c9e15b44f326cb5b0da852885312b30e916f674913428/coverage-7.9.2-cp311-cp311-win_arm64.whl", hash = "sha256:c2667a2b913e307f06aa4e5677f01a9746cd08e4b35e14ebcde6420a9ebb4c62", size = 213873, upload-time = "2025-07-03T10:53:07.699Z" }, - { url = "https://files.pythonhosted.org/packages/53/d7/7deefc6fd4f0f1d4c58051f4004e366afc9e7ab60217ac393f247a1de70a/coverage-7.9.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ae9eb07f1cfacd9cfe8eaee6f4ff4b8a289a668c39c165cd0c8548484920ffc0", size = 212344, upload-time = "2025-07-03T10:53:09.3Z" }, - { url = "https://files.pythonhosted.org/packages/95/0c/ee03c95d32be4d519e6a02e601267769ce2e9a91fc8faa1b540e3626c680/coverage-7.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9ce85551f9a1119f02adc46d3014b5ee3f765deac166acf20dbb851ceb79b6f3", size = 212580, upload-time = "2025-07-03T10:53:11.52Z" }, - { url = "https://files.pythonhosted.org/packages/8b/9f/826fa4b544b27620086211b87a52ca67592622e1f3af9e0a62c87aea153a/coverage-7.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8f6389ac977c5fb322e0e38885fbbf901743f79d47f50db706e7644dcdcb6e1", size = 246383, upload-time = "2025-07-03T10:53:13.134Z" }, - { url = "https://files.pythonhosted.org/packages/7f/b3/4477aafe2a546427b58b9c540665feff874f4db651f4d3cb21b308b3a6d2/coverage-7.9.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff0d9eae8cdfcd58fe7893b88993723583a6ce4dfbfd9f29e001922544f95615", size = 243400, upload-time = "2025-07-03T10:53:14.614Z" }, - { url = "https://files.pythonhosted.org/packages/f8/c2/efffa43778490c226d9d434827702f2dfbc8041d79101a795f11cbb2cf1e/coverage-7.9.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fae939811e14e53ed8a9818dad51d434a41ee09df9305663735f2e2d2d7d959b", size = 245591, upload-time = "2025-07-03T10:53:15.872Z" }, - { url = "https://files.pythonhosted.org/packages/c6/e7/a59888e882c9a5f0192d8627a30ae57910d5d449c80229b55e7643c078c4/coverage-7.9.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:31991156251ec202c798501e0a42bbdf2169dcb0f137b1f5c0f4267f3fc68ef9", size = 245402, upload-time = "2025-07-03T10:53:17.124Z" }, - { url = "https://files.pythonhosted.org/packages/92/a5/72fcd653ae3d214927edc100ce67440ed8a0a1e3576b8d5e6d066ed239db/coverage-7.9.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d0d67963f9cbfc7c7f96d4ac74ed60ecbebd2ea6eeb51887af0f8dce205e545f", size = 243583, upload-time = "2025-07-03T10:53:18.781Z" }, - { url = "https://files.pythonhosted.org/packages/5c/f5/84e70e4df28f4a131d580d7d510aa1ffd95037293da66fd20d446090a13b/coverage-7.9.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:49b752a2858b10580969ec6af6f090a9a440a64a301ac1528d7ca5f7ed497f4d", size = 244815, upload-time = "2025-07-03T10:53:20.168Z" }, - { url = "https://files.pythonhosted.org/packages/39/e7/d73d7cbdbd09fdcf4642655ae843ad403d9cbda55d725721965f3580a314/coverage-7.9.2-cp312-cp312-win32.whl", hash = "sha256:88d7598b8ee130f32f8a43198ee02edd16d7f77692fa056cb779616bbea1b355", size = 214719, upload-time = "2025-07-03T10:53:21.521Z" }, - { url = "https://files.pythonhosted.org/packages/9f/d6/7486dcc3474e2e6ad26a2af2db7e7c162ccd889c4c68fa14ea8ec189c9e9/coverage-7.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:9dfb070f830739ee49d7c83e4941cc767e503e4394fdecb3b54bfdac1d7662c0", size = 215509, upload-time = "2025-07-03T10:53:22.853Z" }, - { url = "https://files.pythonhosted.org/packages/b7/34/0439f1ae2593b0346164d907cdf96a529b40b7721a45fdcf8b03c95fcd90/coverage-7.9.2-cp312-cp312-win_arm64.whl", hash = "sha256:4e2c058aef613e79df00e86b6d42a641c877211384ce5bd07585ed7ba71ab31b", size = 213910, upload-time = "2025-07-03T10:53:24.472Z" }, - { url = "https://files.pythonhosted.org/packages/94/9d/7a8edf7acbcaa5e5c489a646226bed9591ee1c5e6a84733c0140e9ce1ae1/coverage-7.9.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:985abe7f242e0d7bba228ab01070fde1d6c8fa12f142e43debe9ed1dde686038", size = 212367, upload-time = "2025-07-03T10:53:25.811Z" }, - { url = "https://files.pythonhosted.org/packages/e8/9e/5cd6f130150712301f7e40fb5865c1bc27b97689ec57297e568d972eec3c/coverage-7.9.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82c3939264a76d44fde7f213924021ed31f55ef28111a19649fec90c0f109e6d", size = 212632, upload-time = "2025-07-03T10:53:27.075Z" }, - { url = "https://files.pythonhosted.org/packages/a8/de/6287a2c2036f9fd991c61cefa8c64e57390e30c894ad3aa52fac4c1e14a8/coverage-7.9.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae5d563e970dbe04382f736ec214ef48103d1b875967c89d83c6e3f21706d5b3", size = 245793, upload-time = "2025-07-03T10:53:28.408Z" }, - { url = "https://files.pythonhosted.org/packages/06/cc/9b5a9961d8160e3cb0b558c71f8051fe08aa2dd4b502ee937225da564ed1/coverage-7.9.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdd612e59baed2a93c8843c9a7cb902260f181370f1d772f4842987535071d14", size = 243006, upload-time = "2025-07-03T10:53:29.754Z" }, - { url = "https://files.pythonhosted.org/packages/49/d9/4616b787d9f597d6443f5588619c1c9f659e1f5fc9eebf63699eb6d34b78/coverage-7.9.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:256ea87cb2a1ed992bcdfc349d8042dcea1b80436f4ddf6e246d6bee4b5d73b6", size = 244990, upload-time = "2025-07-03T10:53:31.098Z" }, - { url = "https://files.pythonhosted.org/packages/48/83/801cdc10f137b2d02b005a761661649ffa60eb173dcdaeb77f571e4dc192/coverage-7.9.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f44ae036b63c8ea432f610534a2668b0c3aee810e7037ab9d8ff6883de480f5b", size = 245157, upload-time = "2025-07-03T10:53:32.717Z" }, - { url = "https://files.pythonhosted.org/packages/c8/a4/41911ed7e9d3ceb0ffb019e7635468df7499f5cc3edca5f7dfc078e9c5ec/coverage-7.9.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:82d76ad87c932935417a19b10cfe7abb15fd3f923cfe47dbdaa74ef4e503752d", size = 243128, upload-time = "2025-07-03T10:53:34.009Z" }, - { url = "https://files.pythonhosted.org/packages/10/41/344543b71d31ac9cb00a664d5d0c9ef134a0fe87cb7d8430003b20fa0b7d/coverage-7.9.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:619317bb86de4193debc712b9e59d5cffd91dc1d178627ab2a77b9870deb2868", size = 244511, upload-time = "2025-07-03T10:53:35.434Z" }, - { url = "https://files.pythonhosted.org/packages/d5/81/3b68c77e4812105e2a060f6946ba9e6f898ddcdc0d2bfc8b4b152a9ae522/coverage-7.9.2-cp313-cp313-win32.whl", hash = "sha256:0a07757de9feb1dfafd16ab651e0f628fd7ce551604d1bf23e47e1ddca93f08a", size = 214765, upload-time = "2025-07-03T10:53:36.787Z" }, - { url = "https://files.pythonhosted.org/packages/06/a2/7fac400f6a346bb1a4004eb2a76fbff0e242cd48926a2ce37a22a6a1d917/coverage-7.9.2-cp313-cp313-win_amd64.whl", hash = "sha256:115db3d1f4d3f35f5bb021e270edd85011934ff97c8797216b62f461dd69374b", size = 215536, upload-time = "2025-07-03T10:53:38.188Z" }, - { url = "https://files.pythonhosted.org/packages/08/47/2c6c215452b4f90d87017e61ea0fd9e0486bb734cb515e3de56e2c32075f/coverage-7.9.2-cp313-cp313-win_arm64.whl", hash = "sha256:48f82f889c80af8b2a7bb6e158d95a3fbec6a3453a1004d04e4f3b5945a02694", size = 213943, upload-time = "2025-07-03T10:53:39.492Z" }, - { url = "https://files.pythonhosted.org/packages/a3/46/e211e942b22d6af5e0f323faa8a9bc7c447a1cf1923b64c47523f36ed488/coverage-7.9.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:55a28954545f9d2f96870b40f6c3386a59ba8ed50caf2d949676dac3ecab99f5", size = 213088, upload-time = "2025-07-03T10:53:40.874Z" }, - { url = "https://files.pythonhosted.org/packages/d2/2f/762551f97e124442eccd907bf8b0de54348635b8866a73567eb4e6417acf/coverage-7.9.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:cdef6504637731a63c133bb2e6f0f0214e2748495ec15fe42d1e219d1b133f0b", size = 213298, upload-time = "2025-07-03T10:53:42.218Z" }, - { url = "https://files.pythonhosted.org/packages/7a/b7/76d2d132b7baf7360ed69be0bcab968f151fa31abe6d067f0384439d9edb/coverage-7.9.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcd5ebe66c7a97273d5d2ddd4ad0ed2e706b39630ed4b53e713d360626c3dbb3", size = 256541, upload-time = "2025-07-03T10:53:43.823Z" }, - { url = "https://files.pythonhosted.org/packages/a0/17/392b219837d7ad47d8e5974ce5f8dc3deb9f99a53b3bd4d123602f960c81/coverage-7.9.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9303aed20872d7a3c9cb39c5d2b9bdbe44e3a9a1aecb52920f7e7495410dfab8", size = 252761, upload-time = "2025-07-03T10:53:45.19Z" }, - { url = "https://files.pythonhosted.org/packages/d5/77/4256d3577fe1b0daa8d3836a1ebe68eaa07dd2cbaf20cf5ab1115d6949d4/coverage-7.9.2-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc18ea9e417a04d1920a9a76fe9ebd2f43ca505b81994598482f938d5c315f46", size = 254917, upload-time = "2025-07-03T10:53:46.931Z" }, - { url = "https://files.pythonhosted.org/packages/53/99/fc1a008eef1805e1ddb123cf17af864743354479ea5129a8f838c433cc2c/coverage-7.9.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6406cff19880aaaadc932152242523e892faff224da29e241ce2fca329866584", size = 256147, upload-time = "2025-07-03T10:53:48.289Z" }, - { url = "https://files.pythonhosted.org/packages/92/c0/f63bf667e18b7f88c2bdb3160870e277c4874ced87e21426128d70aa741f/coverage-7.9.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:2d0d4f6ecdf37fcc19c88fec3e2277d5dee740fb51ffdd69b9579b8c31e4232e", size = 254261, upload-time = "2025-07-03T10:53:49.99Z" }, - { url = "https://files.pythonhosted.org/packages/8c/32/37dd1c42ce3016ff8ec9e4b607650d2e34845c0585d3518b2a93b4830c1a/coverage-7.9.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c33624f50cf8de418ab2b4d6ca9eda96dc45b2c4231336bac91454520e8d1fac", size = 255099, upload-time = "2025-07-03T10:53:51.354Z" }, - { url = "https://files.pythonhosted.org/packages/da/2e/af6b86f7c95441ce82f035b3affe1cd147f727bbd92f563be35e2d585683/coverage-7.9.2-cp313-cp313t-win32.whl", hash = "sha256:1df6b76e737c6a92210eebcb2390af59a141f9e9430210595251fbaf02d46926", size = 215440, upload-time = "2025-07-03T10:53:52.808Z" }, - { url = "https://files.pythonhosted.org/packages/4d/bb/8a785d91b308867f6b2e36e41c569b367c00b70c17f54b13ac29bcd2d8c8/coverage-7.9.2-cp313-cp313t-win_amd64.whl", hash = "sha256:f5fd54310b92741ebe00d9c0d1d7b2b27463952c022da6d47c175d246a98d1bd", size = 216537, upload-time = "2025-07-03T10:53:54.273Z" }, - { url = "https://files.pythonhosted.org/packages/1d/a0/a6bffb5e0f41a47279fd45a8f3155bf193f77990ae1c30f9c224b61cacb0/coverage-7.9.2-cp313-cp313t-win_arm64.whl", hash = "sha256:c48c2375287108c887ee87d13b4070a381c6537d30e8487b24ec721bf2a781cb", size = 214398, upload-time = "2025-07-03T10:53:56.715Z" }, - { url = "https://files.pythonhosted.org/packages/d7/85/f8bbefac27d286386961c25515431482a425967e23d3698b75a250872924/coverage-7.9.2-pp39.pp310.pp311-none-any.whl", hash = "sha256:8a1166db2fb62473285bcb092f586e081e92656c7dfa8e9f62b4d39d7e6b5050", size = 204013, upload-time = "2025-07-03T10:54:12.084Z" }, - { url = "https://files.pythonhosted.org/packages/3c/38/bbe2e63902847cf79036ecc75550d0698af31c91c7575352eb25190d0fb3/coverage-7.9.2-py3-none-any.whl", hash = "sha256:e425cd5b00f6fc0ed7cdbd766c70be8baab4b7839e4d4fe5fac48581dd968ea4", size = 204005, upload-time = "2025-07-03T10:54:13.491Z" }, -] - -[package.optional-dependencies] -toml = [ - { name = "tomli", marker = "python_full_version <= '3.11'" }, -] - -[[package]] -name = "cryptography" -version = "45.0.5" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/95/1e/49527ac611af559665f71cbb8f92b332b5ec9c6fbc4e88b0f8e92f5e85df/cryptography-45.0.5.tar.gz", hash = "sha256:72e76caa004ab63accdf26023fccd1d087f6d90ec6048ff33ad0445abf7f605a", size = 744903, upload-time = "2025-07-02T13:06:25.941Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/fb/09e28bc0c46d2c547085e60897fea96310574c70fb21cd58a730a45f3403/cryptography-45.0.5-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:101ee65078f6dd3e5a028d4f19c07ffa4dd22cce6a20eaa160f8b5219911e7d8", size = 7043092, upload-time = "2025-07-02T13:05:01.514Z" }, - { url = "https://files.pythonhosted.org/packages/b1/05/2194432935e29b91fb649f6149c1a4f9e6d3d9fc880919f4ad1bcc22641e/cryptography-45.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3a264aae5f7fbb089dbc01e0242d3b67dffe3e6292e1f5182122bdf58e65215d", size = 4205926, upload-time = "2025-07-02T13:05:04.741Z" }, - { url = "https://files.pythonhosted.org/packages/07/8b/9ef5da82350175e32de245646b1884fc01124f53eb31164c77f95a08d682/cryptography-45.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e74d30ec9c7cb2f404af331d5b4099a9b322a8a6b25c4632755c8757345baac5", size = 4429235, upload-time = "2025-07-02T13:05:07.084Z" }, - { url = "https://files.pythonhosted.org/packages/7c/e1/c809f398adde1994ee53438912192d92a1d0fc0f2d7582659d9ef4c28b0c/cryptography-45.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3af26738f2db354aafe492fb3869e955b12b2ef2e16908c8b9cb928128d42c57", size = 4209785, upload-time = "2025-07-02T13:05:09.321Z" }, - { url = "https://files.pythonhosted.org/packages/d0/8b/07eb6bd5acff58406c5e806eff34a124936f41a4fb52909ffa4d00815f8c/cryptography-45.0.5-cp311-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e6c00130ed423201c5bc5544c23359141660b07999ad82e34e7bb8f882bb78e0", size = 3893050, upload-time = "2025-07-02T13:05:11.069Z" }, - { url = "https://files.pythonhosted.org/packages/ec/ef/3333295ed58d900a13c92806b67e62f27876845a9a908c939f040887cca9/cryptography-45.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:dd420e577921c8c2d31289536c386aaa30140b473835e97f83bc71ea9d2baf2d", size = 4457379, upload-time = "2025-07-02T13:05:13.32Z" }, - { url = "https://files.pythonhosted.org/packages/d9/9d/44080674dee514dbb82b21d6fa5d1055368f208304e2ab1828d85c9de8f4/cryptography-45.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:d05a38884db2ba215218745f0781775806bde4f32e07b135348355fe8e4991d9", size = 4209355, upload-time = "2025-07-02T13:05:15.017Z" }, - { url = "https://files.pythonhosted.org/packages/c9/d8/0749f7d39f53f8258e5c18a93131919ac465ee1f9dccaf1b3f420235e0b5/cryptography-45.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:ad0caded895a00261a5b4aa9af828baede54638754b51955a0ac75576b831b27", size = 4456087, upload-time = "2025-07-02T13:05:16.945Z" }, - { url = "https://files.pythonhosted.org/packages/09/d7/92acac187387bf08902b0bf0699816f08553927bdd6ba3654da0010289b4/cryptography-45.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9024beb59aca9d31d36fcdc1604dd9bbeed0a55bface9f1908df19178e2f116e", size = 4332873, upload-time = "2025-07-02T13:05:18.743Z" }, - { url = "https://files.pythonhosted.org/packages/03/c2/840e0710da5106a7c3d4153c7215b2736151bba60bf4491bdb421df5056d/cryptography-45.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:91098f02ca81579c85f66df8a588c78f331ca19089763d733e34ad359f474174", size = 4564651, upload-time = "2025-07-02T13:05:21.382Z" }, - { url = "https://files.pythonhosted.org/packages/2e/92/cc723dd6d71e9747a887b94eb3827825c6c24b9e6ce2bb33b847d31d5eaa/cryptography-45.0.5-cp311-abi3-win32.whl", hash = "sha256:926c3ea71a6043921050eaa639137e13dbe7b4ab25800932a8498364fc1abec9", size = 2929050, upload-time = "2025-07-02T13:05:23.39Z" }, - { url = "https://files.pythonhosted.org/packages/1f/10/197da38a5911a48dd5389c043de4aec4b3c94cb836299b01253940788d78/cryptography-45.0.5-cp311-abi3-win_amd64.whl", hash = "sha256:b85980d1e345fe769cfc57c57db2b59cff5464ee0c045d52c0df087e926fbe63", size = 3403224, upload-time = "2025-07-02T13:05:25.202Z" }, - { url = "https://files.pythonhosted.org/packages/fe/2b/160ce8c2765e7a481ce57d55eba1546148583e7b6f85514472b1d151711d/cryptography-45.0.5-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:f3562c2f23c612f2e4a6964a61d942f891d29ee320edb62ff48ffb99f3de9ae8", size = 7017143, upload-time = "2025-07-02T13:05:27.229Z" }, - { url = "https://files.pythonhosted.org/packages/c2/e7/2187be2f871c0221a81f55ee3105d3cf3e273c0a0853651d7011eada0d7e/cryptography-45.0.5-cp37-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:3fcfbefc4a7f332dece7272a88e410f611e79458fab97b5efe14e54fe476f4fd", size = 4197780, upload-time = "2025-07-02T13:05:29.299Z" }, - { url = "https://files.pythonhosted.org/packages/b9/cf/84210c447c06104e6be9122661159ad4ce7a8190011669afceeaea150524/cryptography-45.0.5-cp37-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:460f8c39ba66af7db0545a8c6f2eabcbc5a5528fc1cf6c3fa9a1e44cec33385e", size = 4420091, upload-time = "2025-07-02T13:05:31.221Z" }, - { url = "https://files.pythonhosted.org/packages/3e/6a/cb8b5c8bb82fafffa23aeff8d3a39822593cee6e2f16c5ca5c2ecca344f7/cryptography-45.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:9b4cf6318915dccfe218e69bbec417fdd7c7185aa7aab139a2c0beb7468c89f0", size = 4198711, upload-time = "2025-07-02T13:05:33.062Z" }, - { url = "https://files.pythonhosted.org/packages/04/f7/36d2d69df69c94cbb2473871926daf0f01ad8e00fe3986ac3c1e8c4ca4b3/cryptography-45.0.5-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:2089cc8f70a6e454601525e5bf2779e665d7865af002a5dec8d14e561002e135", size = 3883299, upload-time = "2025-07-02T13:05:34.94Z" }, - { url = "https://files.pythonhosted.org/packages/82/c7/f0ea40f016de72f81288e9fe8d1f6748036cb5ba6118774317a3ffc6022d/cryptography-45.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:0027d566d65a38497bc37e0dd7c2f8ceda73597d2ac9ba93810204f56f52ebc7", size = 4450558, upload-time = "2025-07-02T13:05:37.288Z" }, - { url = "https://files.pythonhosted.org/packages/06/ae/94b504dc1a3cdf642d710407c62e86296f7da9e66f27ab12a1ee6fdf005b/cryptography-45.0.5-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:be97d3a19c16a9be00edf79dca949c8fa7eff621763666a145f9f9535a5d7f42", size = 4198020, upload-time = "2025-07-02T13:05:39.102Z" }, - { url = "https://files.pythonhosted.org/packages/05/2b/aaf0adb845d5dabb43480f18f7ca72e94f92c280aa983ddbd0bcd6ecd037/cryptography-45.0.5-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:7760c1c2e1a7084153a0f68fab76e754083b126a47d0117c9ed15e69e2103492", size = 4449759, upload-time = "2025-07-02T13:05:41.398Z" }, - { url = "https://files.pythonhosted.org/packages/91/e4/f17e02066de63e0100a3a01b56f8f1016973a1d67551beaf585157a86b3f/cryptography-45.0.5-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6ff8728d8d890b3dda5765276d1bc6fb099252915a2cd3aff960c4c195745dd0", size = 4319991, upload-time = "2025-07-02T13:05:43.64Z" }, - { url = "https://files.pythonhosted.org/packages/f2/2e/e2dbd629481b499b14516eed933f3276eb3239f7cee2dcfa4ee6b44d4711/cryptography-45.0.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7259038202a47fdecee7e62e0fd0b0738b6daa335354396c6ddebdbe1206af2a", size = 4554189, upload-time = "2025-07-02T13:05:46.045Z" }, - { url = "https://files.pythonhosted.org/packages/f8/ea/a78a0c38f4c8736287b71c2ea3799d173d5ce778c7d6e3c163a95a05ad2a/cryptography-45.0.5-cp37-abi3-win32.whl", hash = "sha256:1e1da5accc0c750056c556a93c3e9cb828970206c68867712ca5805e46dc806f", size = 2911769, upload-time = "2025-07-02T13:05:48.329Z" }, - { url = "https://files.pythonhosted.org/packages/79/b3/28ac139109d9005ad3f6b6f8976ffede6706a6478e21c889ce36c840918e/cryptography-45.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:90cb0a7bb35959f37e23303b7eed0a32280510030daba3f7fdfbb65defde6a97", size = 3390016, upload-time = "2025-07-02T13:05:50.811Z" }, - { url = "https://files.pythonhosted.org/packages/c0/71/9bdbcfd58d6ff5084687fe722c58ac718ebedbc98b9f8f93781354e6d286/cryptography-45.0.5-pp311-pypy311_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8c4a6ff8a30e9e3d38ac0539e9a9e02540ab3f827a3394f8852432f6b0ea152e", size = 3587878, upload-time = "2025-07-02T13:06:06.339Z" }, - { url = "https://files.pythonhosted.org/packages/f0/63/83516cfb87f4a8756eaa4203f93b283fda23d210fc14e1e594bd5f20edb6/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:bd4c45986472694e5121084c6ebbd112aa919a25e783b87eb95953c9573906d6", size = 4152447, upload-time = "2025-07-02T13:06:08.345Z" }, - { url = "https://files.pythonhosted.org/packages/22/11/d2823d2a5a0bd5802b3565437add16f5c8ce1f0778bf3822f89ad2740a38/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:982518cd64c54fcada9d7e5cf28eabd3ee76bd03ab18e08a48cad7e8b6f31b18", size = 4386778, upload-time = "2025-07-02T13:06:10.263Z" }, - { url = "https://files.pythonhosted.org/packages/5f/38/6bf177ca6bce4fe14704ab3e93627c5b0ca05242261a2e43ef3168472540/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_34_aarch64.whl", hash = "sha256:12e55281d993a793b0e883066f590c1ae1e802e3acb67f8b442e721e475e6463", size = 4151627, upload-time = "2025-07-02T13:06:13.097Z" }, - { url = "https://files.pythonhosted.org/packages/38/6a/69fc67e5266bff68a91bcb81dff8fb0aba4d79a78521a08812048913e16f/cryptography-45.0.5-pp311-pypy311_pp73-manylinux_2_34_x86_64.whl", hash = "sha256:5aa1e32983d4443e310f726ee4b071ab7569f58eedfdd65e9675484a4eb67bd1", size = 4385593, upload-time = "2025-07-02T13:06:15.689Z" }, - { url = "https://files.pythonhosted.org/packages/f6/34/31a1604c9a9ade0fdab61eb48570e09a796f4d9836121266447b0eaf7feb/cryptography-45.0.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:e357286c1b76403dd384d938f93c46b2b058ed4dfcdce64a770f0537ed3feb6f", size = 3331106, upload-time = "2025-07-02T13:06:18.058Z" }, -] - [[package]] name = "dataclasses-json" version = "0.6.7" @@ -745,7 +550,7 @@ wheels = [ [[package]] name = "datamodel-code-generator" -version = "0.31.2" +version = "0.33.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "argcomplete" }, @@ -759,9 +564,9 @@ dependencies = [ { name = "pyyaml" }, { name = "tomli", marker = "python_full_version < '3.12'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/07/40/26399980314bedb3a8ec6a31da646d190cd945192410ad3ab6eabe0a1c73/datamodel_code_generator-0.31.2.tar.gz", hash = "sha256:47887b8aa6fd69865e07e2893c1e76e34dae753b9a97f1020357af8337bc4cdb", size = 453381, upload-time = "2025-06-22T17:40:56.491Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/a0/3f81c5c0c31d6f25f459e370e04553810f096e5dbd3cf7eda2a67709cd78/datamodel_code_generator-0.33.0.tar.gz", hash = "sha256:7635ef788201d69bd3e98ba88ce6afe479400dc2737fe9d5e21f87408f352c08", size = 458695, upload-time = "2025-08-14T13:50:36.965Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/82/9bda726b117967b8056315e51125633ec7a0d2f862c202aed01c3fde2e9f/datamodel_code_generator-0.31.2-py3-none-any.whl", hash = "sha256:78f200a9e673ee4d041e96e82a835273baf4ce15e446d46501c0433d82af3ef5", size = 119369, upload-time = "2025-06-22T17:40:54.615Z" }, + { url = "https://files.pythonhosted.org/packages/3b/d0/acd7a19dad4dc4118d2b6ba06df9a4a3725729e91fa3f2c63a765d4e7d44/datamodel_code_generator-0.33.0-py3-none-any.whl", hash = "sha256:e229264aa612b2d5bb4901bcd6c520a799ae0d5c19262577a0f876eb48afaaa3", size = 121148, upload-time = "2025-08-14T13:50:35.306Z" }, ] [[package]] @@ -799,11 +604,11 @@ wheels = [ [[package]] name = "distlib" -version = "0.3.9" +version = "0.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0d/dd/1bec4c5ddb504ca60fc29472f3d27e8d4da1257a854e1d96742f15c1d02d/distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403", size = 613923, upload-time = "2024-10-09T18:35:47.551Z" } +sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973, upload-time = "2024-10-09T18:35:44.272Z" }, + { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, ] [[package]] @@ -816,12 +621,14 @@ wheels = [ ] [[package]] -name = "durationpy" -version = "0.10" +name = "dotenv" +version = "0.9.9" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9d/a4/e44218c2b394e31a6dd0d6b095c4e1f32d0be54c2a4b250032d717647bab/durationpy-0.10.tar.gz", hash = "sha256:1fa6893409a6e739c9c72334fc65cca1f355dbdd93405d30f726deb5bde42fba", size = 3335, upload-time = "2025-05-17T13:52:37.26Z" } +dependencies = [ + { name = "python-dotenv" }, +] wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/0d/9feae160378a3553fa9a339b0e9c1a048e147a4127210e286ef18b730f03/durationpy-0.10-py3-none-any.whl", hash = "sha256:3b41e1b601234296b4fb368338fdcd3e13e0b4fb5b67345948f4f2bf9868b286", size = 3922, upload-time = "2025-05-17T13:52:36.463Z" }, + { url = "https://files.pythonhosted.org/packages/b2/b7/545d2c10c1fc15e48653c91efde329a790f2eecfbbf2bd16003b5db2bab0/dotenv-0.9.9-py2.py3-none-any.whl", hash = "sha256:29cf74a087b31dafdb5a446b6d7e11cbce8ed2741540e2339c69fbef92c94ce9", size = 1892, upload-time = "2025-02-19T22:15:01.647Z" }, ] [[package]] @@ -858,11 +665,11 @@ wheels = [ [[package]] name = "filelock" -version = "3.18.0" +version = "3.19.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075, upload-time = "2025-03-14T07:11:40.47Z" } +sdist = { url = "https://files.pythonhosted.org/packages/40/bb/0ab3e58d22305b6f5440629d20683af28959bf793d98d11950e305c1c326/filelock-3.19.1.tar.gz", hash = "sha256:66eda1888b0171c998b35be2bcc0f6d75c388a7ce20c3f3f37aa8e96c2dddf58", size = 17687, upload-time = "2025-08-14T16:56:03.016Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215, upload-time = "2025-03-14T07:11:39.145Z" }, + { url = "https://files.pythonhosted.org/packages/42/14/42b2651a2f46b022ccd948bca9f2d5af0fd8929c4eec235b8d6d844fbe67/filelock-3.19.1-py3-none-any.whl", hash = "sha256:d38e30481def20772f5baf097c122c3babc4fcdb7e14e57049eb9d88c6dc017d", size = 15988, upload-time = "2025-08-14T16:56:01.633Z" }, ] [[package]] @@ -894,15 +701,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/99/db/b14b94a7b68c269043121b69c4a60cefe7953c3bc2e5445d20f141b88370/fireworks_ai-0.19.18-py3-none-any.whl", hash = "sha256:2654fc1c25b76d7c9ae50d4ccd0e2e083700bff7cb9f62570dbd39942a100f0d", size = 570265, upload-time = "2025-08-06T22:49:05.358Z" }, ] -[[package]] -name = "flatbuffers" -version = "25.2.10" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e4/30/eb5dce7994fc71a2f685d98ec33cc660c0a5887db5610137e60d8cbc4489/flatbuffers-25.2.10.tar.gz", hash = "sha256:97e451377a41262f8d9bd4295cc836133415cc03d8cb966410a4af92eb00d26e", size = 22170, upload-time = "2025-02-11T04:26:46.257Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b8/25/155f9f080d5e4bc0082edfda032ea2bc2b8fab3f4d25d46c1e9dd22a1a89/flatbuffers-25.2.10-py2.py3-none-any.whl", hash = "sha256:ebba5f4d5ea615af3f7fd70fc310636fbb2bbd1f566ac0a23d98dd412de50051", size = 30953, upload-time = "2025-02-11T04:26:44.484Z" }, -] - [[package]] name = "frozenlist" version = "1.7.0" @@ -1019,7 +817,7 @@ wheels = [ [[package]] name = "google-genai" -version = "1.24.0" +version = "1.31.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -1031,9 +829,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "websockets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8d/cf/37ac8cd4752e28e547b8a52765fe48a2ada2d0d286ea03f46e4d8c69ff4f/google_genai-1.24.0.tar.gz", hash = "sha256:bc896e30ad26d05a2af3d17c2ba10ea214a94f1c0cdb93d5c004dc038774e75a", size = 226740, upload-time = "2025-07-01T22:14:24.365Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/1b/da30fa6e2966942d7028a58eb7aa7d04544dcc3aa66194365b2e0adac570/google_genai-1.31.0.tar.gz", hash = "sha256:8572b47aa684357c3e5e10d290ec772c65414114939e3ad2955203e27cd2fcbc", size = 233482, upload-time = "2025-08-18T23:40:21.733Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/30/28/a35f64fc02e599808101617a21d447d241dadeba2aac1f4dc2d1179b8218/google_genai-1.24.0-py3-none-any.whl", hash = "sha256:98be8c51632576289ecc33cd84bcdaf4356ef0bef04ac7578660c49175af22b9", size = 226065, upload-time = "2025-07-01T22:14:23.177Z" }, + { url = "https://files.pythonhosted.org/packages/41/27/1525bc9cbec58660f0842ebcbfe910a1dde908c2672373804879666e0bb8/google_genai-1.31.0-py3-none-any.whl", hash = "sha256:5c6959bcf862714e8ed0922db3aaf41885bacf6318751b3421bf1e459f78892f", size = 231876, upload-time = "2025-08-18T23:40:20.385Z" }, ] [[package]] @@ -1050,7 +848,7 @@ wheels = [ [[package]] name = "groq" -version = "0.30.0" +version = "0.31.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -1060,47 +858,47 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a9/b1/72ca20dc9b977b7f604648e8944c77b267bddeb90d8e16bda0cf0e397844/groq-0.30.0.tar.gz", hash = "sha256:919466e48fcbebef08fed3f71debb0f96b0ea8d2ec77842c384aa843019f6e2c", size = 134928, upload-time = "2025-07-11T20:28:36.583Z" } +sdist = { url = "https://files.pythonhosted.org/packages/66/a2/77fd1460e7d55859219223719aa44ae8902a3a1ad333cd5faf330eb0b894/groq-0.31.0.tar.gz", hash = "sha256:182252e9bf0d696df607c137cbafa851d2c84aaf94bcfe9165c0bc231043490c", size = 136237, upload-time = "2025-08-05T23:14:01.183Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/19/b8/5b90edf9fbd795597220e3d1b5534d845e69a73ffe1fdeb967443ed2a6cf/groq-0.30.0-py3-none-any.whl", hash = "sha256:6d9609a7778ba56432f45c1bac21b005f02c6c0aca9c1c094e65536f162c1e83", size = 131056, upload-time = "2025-07-11T20:28:35.591Z" }, + { url = "https://files.pythonhosted.org/packages/ab/f8/14672d69a91495f43462c5490067eeafc30346e81bda1a62848e897f9bc3/groq-0.31.0-py3-none-any.whl", hash = "sha256:5e3c7ec9728b7cccf913da982a9b5ebb46dc18a070b35e12a3d6a1e12d6b0f7f", size = 131365, upload-time = "2025-08-05T23:13:59.768Z" }, ] [[package]] name = "grpcio" -version = "1.73.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/79/e8/b43b851537da2e2f03fa8be1aef207e5cbfb1a2e014fbb6b40d24c177cd3/grpcio-1.73.1.tar.gz", hash = "sha256:7fce2cd1c0c1116cf3850564ebfc3264fba75d3c74a7414373f1238ea365ef87", size = 12730355, upload-time = "2025-06-26T01:53:24.622Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/41/921565815e871d84043e73e2c0e748f0318dab6fa9be872cd042778f14a9/grpcio-1.73.1-cp311-cp311-linux_armv7l.whl", hash = "sha256:ba2cea9f7ae4bc21f42015f0ec98f69ae4179848ad744b210e7685112fa507a1", size = 5363853, upload-time = "2025-06-26T01:52:05.5Z" }, - { url = "https://files.pythonhosted.org/packages/b0/cc/9c51109c71d068e4d474becf5f5d43c9d63038cec1b74112978000fa72f4/grpcio-1.73.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:d74c3f4f37b79e746271aa6cdb3a1d7e4432aea38735542b23adcabaaee0c097", size = 10621476, upload-time = "2025-06-26T01:52:07.211Z" }, - { url = "https://files.pythonhosted.org/packages/8f/d3/33d738a06f6dbd4943f4d377468f8299941a7c8c6ac8a385e4cef4dd3c93/grpcio-1.73.1-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:5b9b1805a7d61c9e90541cbe8dfe0a593dfc8c5c3a43fe623701b6a01b01d710", size = 5807903, upload-time = "2025-06-26T01:52:09.466Z" }, - { url = "https://files.pythonhosted.org/packages/5d/47/36deacd3c967b74e0265f4c608983e897d8bb3254b920f8eafdf60e4ad7e/grpcio-1.73.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3215f69a0670a8cfa2ab53236d9e8026bfb7ead5d4baabe7d7dc11d30fda967", size = 6448172, upload-time = "2025-06-26T01:52:11.459Z" }, - { url = "https://files.pythonhosted.org/packages/0e/64/12d6dc446021684ee1428ea56a3f3712048a18beeadbdefa06e6f8814a6e/grpcio-1.73.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc5eccfd9577a5dc7d5612b2ba90cca4ad14c6d949216c68585fdec9848befb1", size = 6044226, upload-time = "2025-06-26T01:52:12.987Z" }, - { url = "https://files.pythonhosted.org/packages/72/4b/6bae2d88a006000f1152d2c9c10ffd41d0131ca1198e0b661101c2e30ab9/grpcio-1.73.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dc7d7fd520614fce2e6455ba89791458020a39716951c7c07694f9dbae28e9c0", size = 6135690, upload-time = "2025-06-26T01:52:14.92Z" }, - { url = "https://files.pythonhosted.org/packages/38/64/02c83b5076510784d1305025e93e0d78f53bb6a0213c8c84cfe8a00c5c48/grpcio-1.73.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:105492124828911f85127e4825d1c1234b032cb9d238567876b5515d01151379", size = 6775867, upload-time = "2025-06-26T01:52:16.446Z" }, - { url = "https://files.pythonhosted.org/packages/42/72/a13ff7ba6c68ccffa35dacdc06373a76c0008fd75777cba84d7491956620/grpcio-1.73.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:610e19b04f452ba6f402ac9aa94eb3d21fbc94553368008af634812c4a85a99e", size = 6308380, upload-time = "2025-06-26T01:52:18.417Z" }, - { url = "https://files.pythonhosted.org/packages/65/ae/d29d948021faa0070ec33245c1ae354e2aefabd97e6a9a7b6dcf0fb8ef6b/grpcio-1.73.1-cp311-cp311-win32.whl", hash = "sha256:d60588ab6ba0ac753761ee0e5b30a29398306401bfbceffe7d68ebb21193f9d4", size = 3679139, upload-time = "2025-06-26T01:52:20.171Z" }, - { url = "https://files.pythonhosted.org/packages/af/66/e1bbb0c95ea222947f0829b3db7692c59b59bcc531df84442e413fa983d9/grpcio-1.73.1-cp311-cp311-win_amd64.whl", hash = "sha256:6957025a4608bb0a5ff42abd75bfbb2ed99eda29d5992ef31d691ab54b753643", size = 4342558, upload-time = "2025-06-26T01:52:22.137Z" }, - { url = "https://files.pythonhosted.org/packages/b8/41/456caf570c55d5ac26f4c1f2db1f2ac1467d5bf3bcd660cba3e0a25b195f/grpcio-1.73.1-cp312-cp312-linux_armv7l.whl", hash = "sha256:921b25618b084e75d424a9f8e6403bfeb7abef074bb6c3174701e0f2542debcf", size = 5334621, upload-time = "2025-06-26T01:52:23.602Z" }, - { url = "https://files.pythonhosted.org/packages/2a/c2/9a15e179e49f235bb5e63b01590658c03747a43c9775e20c4e13ca04f4c4/grpcio-1.73.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:277b426a0ed341e8447fbf6c1d6b68c952adddf585ea4685aa563de0f03df887", size = 10601131, upload-time = "2025-06-26T01:52:25.691Z" }, - { url = "https://files.pythonhosted.org/packages/0c/1d/1d39e90ef6348a0964caa7c5c4d05f3bae2c51ab429eb7d2e21198ac9b6d/grpcio-1.73.1-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:96c112333309493c10e118d92f04594f9055774757f5d101b39f8150f8c25582", size = 5759268, upload-time = "2025-06-26T01:52:27.631Z" }, - { url = "https://files.pythonhosted.org/packages/8a/2b/2dfe9ae43de75616177bc576df4c36d6401e0959833b2e5b2d58d50c1f6b/grpcio-1.73.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f48e862aed925ae987eb7084409a80985de75243389dc9d9c271dd711e589918", size = 6409791, upload-time = "2025-06-26T01:52:29.711Z" }, - { url = "https://files.pythonhosted.org/packages/6e/66/e8fe779b23b5a26d1b6949e5c70bc0a5fd08f61a6ec5ac7760d589229511/grpcio-1.73.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83a6c2cce218e28f5040429835fa34a29319071079e3169f9543c3fbeff166d2", size = 6003728, upload-time = "2025-06-26T01:52:31.352Z" }, - { url = "https://files.pythonhosted.org/packages/a9/39/57a18fcef567784108c4fc3f5441cb9938ae5a51378505aafe81e8e15ecc/grpcio-1.73.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:65b0458a10b100d815a8426b1442bd17001fdb77ea13665b2f7dc9e8587fdc6b", size = 6103364, upload-time = "2025-06-26T01:52:33.028Z" }, - { url = "https://files.pythonhosted.org/packages/c5/46/28919d2aa038712fc399d02fa83e998abd8c1f46c2680c5689deca06d1b2/grpcio-1.73.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:0a9f3ea8dce9eae9d7cb36827200133a72b37a63896e0e61a9d5ec7d61a59ab1", size = 6749194, upload-time = "2025-06-26T01:52:34.734Z" }, - { url = "https://files.pythonhosted.org/packages/3d/56/3898526f1fad588c5d19a29ea0a3a4996fb4fa7d7c02dc1be0c9fd188b62/grpcio-1.73.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:de18769aea47f18e782bf6819a37c1c528914bfd5683b8782b9da356506190c8", size = 6283902, upload-time = "2025-06-26T01:52:36.503Z" }, - { url = "https://files.pythonhosted.org/packages/dc/64/18b77b89c5870d8ea91818feb0c3ffb5b31b48d1b0ee3e0f0d539730fea3/grpcio-1.73.1-cp312-cp312-win32.whl", hash = "sha256:24e06a5319e33041e322d32c62b1e728f18ab8c9dbc91729a3d9f9e3ed336642", size = 3668687, upload-time = "2025-06-26T01:52:38.678Z" }, - { url = "https://files.pythonhosted.org/packages/3c/52/302448ca6e52f2a77166b2e2ed75f5d08feca4f2145faf75cb768cccb25b/grpcio-1.73.1-cp312-cp312-win_amd64.whl", hash = "sha256:303c8135d8ab176f8038c14cc10d698ae1db9c480f2b2823f7a987aa2a4c5646", size = 4334887, upload-time = "2025-06-26T01:52:40.743Z" }, - { url = "https://files.pythonhosted.org/packages/37/bf/4ca20d1acbefabcaba633ab17f4244cbbe8eca877df01517207bd6655914/grpcio-1.73.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:b310824ab5092cf74750ebd8a8a8981c1810cb2b363210e70d06ef37ad80d4f9", size = 5335615, upload-time = "2025-06-26T01:52:42.896Z" }, - { url = "https://files.pythonhosted.org/packages/75/ed/45c345f284abec5d4f6d77cbca9c52c39b554397eb7de7d2fcf440bcd049/grpcio-1.73.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:8f5a6df3fba31a3485096ac85b2e34b9666ffb0590df0cd044f58694e6a1f6b5", size = 10595497, upload-time = "2025-06-26T01:52:44.695Z" }, - { url = "https://files.pythonhosted.org/packages/a4/75/bff2c2728018f546d812b755455014bc718f8cdcbf5c84f1f6e5494443a8/grpcio-1.73.1-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:052e28fe9c41357da42250a91926a3e2f74c046575c070b69659467ca5aa976b", size = 5765321, upload-time = "2025-06-26T01:52:46.871Z" }, - { url = "https://files.pythonhosted.org/packages/70/3b/14e43158d3b81a38251b1d231dfb45a9b492d872102a919fbf7ba4ac20cd/grpcio-1.73.1-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c0bf15f629b1497436596b1cbddddfa3234273490229ca29561209778ebe182", size = 6415436, upload-time = "2025-06-26T01:52:49.134Z" }, - { url = "https://files.pythonhosted.org/packages/e5/3f/81d9650ca40b54338336fd360f36773be8cb6c07c036e751d8996eb96598/grpcio-1.73.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ab860d5bfa788c5a021fba264802e2593688cd965d1374d31d2b1a34cacd854", size = 6007012, upload-time = "2025-06-26T01:52:51.076Z" }, - { url = "https://files.pythonhosted.org/packages/55/f4/59edf5af68d684d0f4f7ad9462a418ac517201c238551529098c9aa28cb0/grpcio-1.73.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:ad1d958c31cc91ab050bd8a91355480b8e0683e21176522bacea225ce51163f2", size = 6105209, upload-time = "2025-06-26T01:52:52.773Z" }, - { url = "https://files.pythonhosted.org/packages/e4/a8/700d034d5d0786a5ba14bfa9ce974ed4c976936c2748c2bd87aa50f69b36/grpcio-1.73.1-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:f43ffb3bd415c57224c7427bfb9e6c46a0b6e998754bfa0d00f408e1873dcbb5", size = 6753655, upload-time = "2025-06-26T01:52:55.064Z" }, - { url = "https://files.pythonhosted.org/packages/1f/29/efbd4ac837c23bc48e34bbaf32bd429f0dc9ad7f80721cdb4622144c118c/grpcio-1.73.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:686231cdd03a8a8055f798b2b54b19428cdf18fa1549bee92249b43607c42668", size = 6287288, upload-time = "2025-06-26T01:52:57.33Z" }, - { url = "https://files.pythonhosted.org/packages/d8/61/c6045d2ce16624bbe18b5d169c1a5ce4d6c3a47bc9d0e5c4fa6a50ed1239/grpcio-1.73.1-cp313-cp313-win32.whl", hash = "sha256:89018866a096e2ce21e05eabed1567479713ebe57b1db7cbb0f1e3b896793ba4", size = 3668151, upload-time = "2025-06-26T01:52:59.405Z" }, - { url = "https://files.pythonhosted.org/packages/c2/d7/77ac689216daee10de318db5aa1b88d159432dc76a130948a56b3aa671a2/grpcio-1.73.1-cp313-cp313-win_amd64.whl", hash = "sha256:4a68f8c9966b94dff693670a5cf2b54888a48a5011c5d9ce2295a1a1465ee84f", size = 4335747, upload-time = "2025-06-26T01:53:01.233Z" }, +version = "1.74.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/38/b4/35feb8f7cab7239c5b94bd2db71abb3d6adb5f335ad8f131abb6060840b6/grpcio-1.74.0.tar.gz", hash = "sha256:80d1f4fbb35b0742d3e3d3bb654b7381cd5f015f8497279a1e9c21ba623e01b1", size = 12756048, upload-time = "2025-07-24T18:54:23.039Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/77/b2f06db9f240a5abeddd23a0e49eae2b6ac54d85f0e5267784ce02269c3b/grpcio-1.74.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:69e1a8180868a2576f02356565f16635b99088da7df3d45aaa7e24e73a054e31", size = 5487368, upload-time = "2025-07-24T18:53:03.548Z" }, + { url = "https://files.pythonhosted.org/packages/48/99/0ac8678a819c28d9a370a663007581744a9f2a844e32f0fa95e1ddda5b9e/grpcio-1.74.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:8efe72fde5500f47aca1ef59495cb59c885afe04ac89dd11d810f2de87d935d4", size = 10999804, upload-time = "2025-07-24T18:53:05.095Z" }, + { url = "https://files.pythonhosted.org/packages/45/c6/a2d586300d9e14ad72e8dc211c7aecb45fe9846a51e558c5bca0c9102c7f/grpcio-1.74.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:a8f0302f9ac4e9923f98d8e243939a6fb627cd048f5cd38595c97e38020dffce", size = 5987667, upload-time = "2025-07-24T18:53:07.157Z" }, + { url = "https://files.pythonhosted.org/packages/c9/57/5f338bf56a7f22584e68d669632e521f0de460bb3749d54533fc3d0fca4f/grpcio-1.74.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f609a39f62a6f6f05c7512746798282546358a37ea93c1fcbadf8b2fed162e3", size = 6655612, upload-time = "2025-07-24T18:53:09.244Z" }, + { url = "https://files.pythonhosted.org/packages/82/ea/a4820c4c44c8b35b1903a6c72a5bdccec92d0840cf5c858c498c66786ba5/grpcio-1.74.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c98e0b7434a7fa4e3e63f250456eaef52499fba5ae661c58cc5b5477d11e7182", size = 6219544, upload-time = "2025-07-24T18:53:11.221Z" }, + { url = "https://files.pythonhosted.org/packages/a4/17/0537630a921365928f5abb6d14c79ba4dcb3e662e0dbeede8af4138d9dcf/grpcio-1.74.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:662456c4513e298db6d7bd9c3b8df6f75f8752f0ba01fb653e252ed4a59b5a5d", size = 6334863, upload-time = "2025-07-24T18:53:12.925Z" }, + { url = "https://files.pythonhosted.org/packages/e2/a6/85ca6cb9af3f13e1320d0a806658dca432ff88149d5972df1f7b51e87127/grpcio-1.74.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3d14e3c4d65e19d8430a4e28ceb71ace4728776fd6c3ce34016947474479683f", size = 7019320, upload-time = "2025-07-24T18:53:15.002Z" }, + { url = "https://files.pythonhosted.org/packages/4f/a7/fe2beab970a1e25d2eff108b3cf4f7d9a53c185106377a3d1989216eba45/grpcio-1.74.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1bf949792cee20d2078323a9b02bacbbae002b9e3b9e2433f2741c15bdeba1c4", size = 6514228, upload-time = "2025-07-24T18:53:16.999Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c2/2f9c945c8a248cebc3ccda1b7a1bf1775b9d7d59e444dbb18c0014e23da6/grpcio-1.74.0-cp311-cp311-win32.whl", hash = "sha256:55b453812fa7c7ce2f5c88be3018fb4a490519b6ce80788d5913f3f9d7da8c7b", size = 3817216, upload-time = "2025-07-24T18:53:20.564Z" }, + { url = "https://files.pythonhosted.org/packages/ff/d1/a9cf9c94b55becda2199299a12b9feef0c79946b0d9d34c989de6d12d05d/grpcio-1.74.0-cp311-cp311-win_amd64.whl", hash = "sha256:86ad489db097141a907c559988c29718719aa3e13370d40e20506f11b4de0d11", size = 4495380, upload-time = "2025-07-24T18:53:22.058Z" }, + { url = "https://files.pythonhosted.org/packages/4c/5d/e504d5d5c4469823504f65687d6c8fb97b7f7bf0b34873b7598f1df24630/grpcio-1.74.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:8533e6e9c5bd630ca98062e3a1326249e6ada07d05acf191a77bc33f8948f3d8", size = 5445551, upload-time = "2025-07-24T18:53:23.641Z" }, + { url = "https://files.pythonhosted.org/packages/43/01/730e37056f96f2f6ce9f17999af1556df62ee8dab7fa48bceeaab5fd3008/grpcio-1.74.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:2918948864fec2a11721d91568effffbe0a02b23ecd57f281391d986847982f6", size = 10979810, upload-time = "2025-07-24T18:53:25.349Z" }, + { url = "https://files.pythonhosted.org/packages/79/3d/09fd100473ea5c47083889ca47ffd356576173ec134312f6aa0e13111dee/grpcio-1.74.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:60d2d48b0580e70d2e1954d0d19fa3c2e60dd7cbed826aca104fff518310d1c5", size = 5941946, upload-time = "2025-07-24T18:53:27.387Z" }, + { url = "https://files.pythonhosted.org/packages/8a/99/12d2cca0a63c874c6d3d195629dcd85cdf5d6f98a30d8db44271f8a97b93/grpcio-1.74.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3601274bc0523f6dc07666c0e01682c94472402ac2fd1226fd96e079863bfa49", size = 6621763, upload-time = "2025-07-24T18:53:29.193Z" }, + { url = "https://files.pythonhosted.org/packages/9d/2c/930b0e7a2f1029bbc193443c7bc4dc2a46fedb0203c8793dcd97081f1520/grpcio-1.74.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:176d60a5168d7948539def20b2a3adcce67d72454d9ae05969a2e73f3a0feee7", size = 6180664, upload-time = "2025-07-24T18:53:30.823Z" }, + { url = "https://files.pythonhosted.org/packages/db/d5/ff8a2442180ad0867717e670f5ec42bfd8d38b92158ad6bcd864e6d4b1ed/grpcio-1.74.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e759f9e8bc908aaae0412642afe5416c9f983a80499448fcc7fab8692ae044c3", size = 6301083, upload-time = "2025-07-24T18:53:32.454Z" }, + { url = "https://files.pythonhosted.org/packages/b0/ba/b361d390451a37ca118e4ec7dccec690422e05bc85fba2ec72b06cefec9f/grpcio-1.74.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:9e7c4389771855a92934b2846bd807fc25a3dfa820fd912fe6bd8136026b2707", size = 6994132, upload-time = "2025-07-24T18:53:34.506Z" }, + { url = "https://files.pythonhosted.org/packages/3b/0c/3a5fa47d2437a44ced74141795ac0251bbddeae74bf81df3447edd767d27/grpcio-1.74.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cce634b10aeab37010449124814b05a62fb5f18928ca878f1bf4750d1f0c815b", size = 6489616, upload-time = "2025-07-24T18:53:36.217Z" }, + { url = "https://files.pythonhosted.org/packages/ae/95/ab64703b436d99dc5217228babc76047d60e9ad14df129e307b5fec81fd0/grpcio-1.74.0-cp312-cp312-win32.whl", hash = "sha256:885912559974df35d92219e2dc98f51a16a48395f37b92865ad45186f294096c", size = 3807083, upload-time = "2025-07-24T18:53:37.911Z" }, + { url = "https://files.pythonhosted.org/packages/84/59/900aa2445891fc47a33f7d2f76e00ca5d6ae6584b20d19af9c06fa09bf9a/grpcio-1.74.0-cp312-cp312-win_amd64.whl", hash = "sha256:42f8fee287427b94be63d916c90399ed310ed10aadbf9e2e5538b3e497d269bc", size = 4490123, upload-time = "2025-07-24T18:53:39.528Z" }, + { url = "https://files.pythonhosted.org/packages/d4/d8/1004a5f468715221450e66b051c839c2ce9a985aa3ee427422061fcbb6aa/grpcio-1.74.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:2bc2d7d8d184e2362b53905cb1708c84cb16354771c04b490485fa07ce3a1d89", size = 5449488, upload-time = "2025-07-24T18:53:41.174Z" }, + { url = "https://files.pythonhosted.org/packages/94/0e/33731a03f63740d7743dced423846c831d8e6da808fcd02821a4416df7fa/grpcio-1.74.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:c14e803037e572c177ba54a3e090d6eb12efd795d49327c5ee2b3bddb836bf01", size = 10974059, upload-time = "2025-07-24T18:53:43.066Z" }, + { url = "https://files.pythonhosted.org/packages/0d/c6/3d2c14d87771a421205bdca991467cfe473ee4c6a1231c1ede5248c62ab8/grpcio-1.74.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:f6ec94f0e50eb8fa1744a731088b966427575e40c2944a980049798b127a687e", size = 5945647, upload-time = "2025-07-24T18:53:45.269Z" }, + { url = "https://files.pythonhosted.org/packages/c5/83/5a354c8aaff58594eef7fffebae41a0f8995a6258bbc6809b800c33d4c13/grpcio-1.74.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:566b9395b90cc3d0d0c6404bc8572c7c18786ede549cdb540ae27b58afe0fb91", size = 6626101, upload-time = "2025-07-24T18:53:47.015Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ca/4fdc7bf59bf6994aa45cbd4ef1055cd65e2884de6113dbd49f75498ddb08/grpcio-1.74.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1ea6176d7dfd5b941ea01c2ec34de9531ba494d541fe2057c904e601879f249", size = 6182562, upload-time = "2025-07-24T18:53:48.967Z" }, + { url = "https://files.pythonhosted.org/packages/fd/48/2869e5b2c1922583686f7ae674937986807c2f676d08be70d0a541316270/grpcio-1.74.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:64229c1e9cea079420527fa8ac45d80fc1e8d3f94deaa35643c381fa8d98f362", size = 6303425, upload-time = "2025-07-24T18:53:50.847Z" }, + { url = "https://files.pythonhosted.org/packages/a6/0e/bac93147b9a164f759497bc6913e74af1cb632c733c7af62c0336782bd38/grpcio-1.74.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:0f87bddd6e27fc776aacf7ebfec367b6d49cad0455123951e4488ea99d9b9b8f", size = 6996533, upload-time = "2025-07-24T18:53:52.747Z" }, + { url = "https://files.pythonhosted.org/packages/84/35/9f6b2503c1fd86d068b46818bbd7329db26a87cdd8c01e0d1a9abea1104c/grpcio-1.74.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:3b03d8f2a07f0fea8c8f74deb59f8352b770e3900d143b3d1475effcb08eec20", size = 6491489, upload-time = "2025-07-24T18:53:55.06Z" }, + { url = "https://files.pythonhosted.org/packages/75/33/a04e99be2a82c4cbc4039eb3a76f6c3632932b9d5d295221389d10ac9ca7/grpcio-1.74.0-cp313-cp313-win32.whl", hash = "sha256:b6a73b2ba83e663b2480a90b82fdae6a7aa6427f62bf43b29912c0cfd1aa2bfa", size = 3805811, upload-time = "2025-07-24T18:53:56.798Z" }, + { url = "https://files.pythonhosted.org/packages/34/80/de3eb55eb581815342d097214bed4c59e806b05f1b3110df03b2280d6dfd/grpcio-1.74.0-cp313-cp313-win_amd64.whl", hash = "sha256:fd3c71aeee838299c5887230b8a1822795325ddfea635edd82954c1eaa831e24", size = 4489214, upload-time = "2025-07-24T18:53:59.771Z" }, ] [[package]] @@ -1155,17 +953,17 @@ wheels = [ [[package]] name = "hf-xet" -version = "1.1.5" +version = "1.1.8" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ed/d4/7685999e85945ed0d7f0762b686ae7015035390de1161dcea9d5276c134c/hf_xet-1.1.5.tar.gz", hash = "sha256:69ebbcfd9ec44fdc2af73441619eeb06b94ee34511bbcf57cd423820090f5694", size = 495969, upload-time = "2025-06-20T21:48:38.007Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7a/49/91010b59debc7c862a5fd426d343134dd9a68778dbe570234b6495a4e204/hf_xet-1.1.8.tar.gz", hash = "sha256:62a0043e441753bbc446dcb5a3fe40a4d03f5fb9f13589ef1df9ab19252beb53", size = 484065, upload-time = "2025-08-18T22:01:03.584Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/00/89/a1119eebe2836cb25758e7661d6410d3eae982e2b5e974bcc4d250be9012/hf_xet-1.1.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:f52c2fa3635b8c37c7764d8796dfa72706cc4eded19d638331161e82b0792e23", size = 2687929, upload-time = "2025-06-20T21:48:32.284Z" }, - { url = "https://files.pythonhosted.org/packages/de/5f/2c78e28f309396e71ec8e4e9304a6483dcbc36172b5cea8f291994163425/hf_xet-1.1.5-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:9fa6e3ee5d61912c4a113e0708eaaef987047616465ac7aa30f7121a48fc1af8", size = 2556338, upload-time = "2025-06-20T21:48:30.079Z" }, - { url = "https://files.pythonhosted.org/packages/6d/2f/6cad7b5fe86b7652579346cb7f85156c11761df26435651cbba89376cd2c/hf_xet-1.1.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc874b5c843e642f45fd85cda1ce599e123308ad2901ead23d3510a47ff506d1", size = 3102894, upload-time = "2025-06-20T21:48:28.114Z" }, - { url = "https://files.pythonhosted.org/packages/d0/54/0fcf2b619720a26fbb6cc941e89f2472a522cd963a776c089b189559447f/hf_xet-1.1.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:dbba1660e5d810bd0ea77c511a99e9242d920790d0e63c0e4673ed36c4022d18", size = 3002134, upload-time = "2025-06-20T21:48:25.906Z" }, - { url = "https://files.pythonhosted.org/packages/f3/92/1d351ac6cef7c4ba8c85744d37ffbfac2d53d0a6c04d2cabeba614640a78/hf_xet-1.1.5-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:ab34c4c3104133c495785d5d8bba3b1efc99de52c02e759cf711a91fd39d3a14", size = 3171009, upload-time = "2025-06-20T21:48:33.987Z" }, - { url = "https://files.pythonhosted.org/packages/c9/65/4b2ddb0e3e983f2508528eb4501288ae2f84963586fbdfae596836d5e57a/hf_xet-1.1.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:83088ecea236d5113de478acb2339f92c95b4fb0462acaa30621fac02f5a534a", size = 3279245, upload-time = "2025-06-20T21:48:36.051Z" }, - { url = "https://files.pythonhosted.org/packages/f0/55/ef77a85ee443ae05a9e9cba1c9f0dd9241eb42da2aeba1dc50f51154c81a/hf_xet-1.1.5-cp37-abi3-win_amd64.whl", hash = "sha256:73e167d9807d166596b4b2f0b585c6d5bd84a26dea32843665a8b58f6edba245", size = 2738931, upload-time = "2025-06-20T21:48:39.482Z" }, + { url = "https://files.pythonhosted.org/packages/9c/91/5814db3a0d4a65fb6a87f0931ae28073b87f06307701fe66e7c41513bfb4/hf_xet-1.1.8-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:3d5f82e533fc51c7daad0f9b655d9c7811b5308e5890236828bd1dd3ed8fea74", size = 2752357, upload-time = "2025-08-18T22:00:58.777Z" }, + { url = "https://files.pythonhosted.org/packages/70/72/ce898516e97341a7a9d450609e130e108643389110261eaee6deb1ba8545/hf_xet-1.1.8-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:8e2dba5896bca3ab61d0bef4f01a1647004de59640701b37e37eaa57087bbd9d", size = 2613142, upload-time = "2025-08-18T22:00:57.252Z" }, + { url = "https://files.pythonhosted.org/packages/b7/d6/13af5f916cef795ac2b5e4cc1de31f2e0e375f4475d50799915835f301c2/hf_xet-1.1.8-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfe5700bc729be3d33d4e9a9b5cc17a951bf8c7ada7ba0c9198a6ab2053b7453", size = 3175859, upload-time = "2025-08-18T22:00:55.978Z" }, + { url = "https://files.pythonhosted.org/packages/4c/ed/34a193c9d1d72b7c3901b3b5153b1be9b2736b832692e1c3f167af537102/hf_xet-1.1.8-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:09e86514c3c4284ed8a57d6b0f3d089f9836a0af0a1ceb3c9dd664f1f3eaefef", size = 3074178, upload-time = "2025-08-18T22:00:54.147Z" }, + { url = "https://files.pythonhosted.org/packages/4a/1b/de6817b4bf65385280252dff5c9cceeedfbcb27ddb93923639323c1034a4/hf_xet-1.1.8-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:4a9b99ab721d385b83f4fc8ee4e0366b0b59dce03b5888a86029cc0ca634efbf", size = 3238122, upload-time = "2025-08-18T22:01:00.546Z" }, + { url = "https://files.pythonhosted.org/packages/b7/13/874c85c7ed519ec101deb654f06703d9e5e68d34416730f64c4755ada36a/hf_xet-1.1.8-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:25b9d43333bbef39aeae1616789ec329c21401a7fe30969d538791076227b591", size = 3344325, upload-time = "2025-08-18T22:01:02.013Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d3/0aaf279f4f3dea58e99401b92c31c0f752924ba0e6c7d7bb07b1dbd7f35e/hf_xet-1.1.8-cp37-abi3-win_amd64.whl", hash = "sha256:4171f31d87b13da4af1ed86c98cf763292e4720c088b4957cf9d564f92904ca9", size = 2801689, upload-time = "2025-08-18T22:01:04.81Z" }, ] [[package]] @@ -1190,35 +988,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, ] -[[package]] -name = "httptools" -version = "0.6.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a7/9a/ce5e1f7e131522e6d3426e8e7a490b3a01f39a6696602e1c4f33f9e94277/httptools-0.6.4.tar.gz", hash = "sha256:4e93eee4add6493b59a5c514da98c939b244fce4a0d8879cd3f466562f4b7d5c", size = 240639, upload-time = "2024-10-16T19:45:08.902Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7b/26/bb526d4d14c2774fe07113ca1db7255737ffbb119315839af2065abfdac3/httptools-0.6.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f47f8ed67cc0ff862b84a1189831d1d33c963fb3ce1ee0c65d3b0cbe7b711069", size = 199029, upload-time = "2024-10-16T19:44:18.427Z" }, - { url = "https://files.pythonhosted.org/packages/a6/17/3e0d3e9b901c732987a45f4f94d4e2c62b89a041d93db89eafb262afd8d5/httptools-0.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0614154d5454c21b6410fdf5262b4a3ddb0f53f1e1721cfd59d55f32138c578a", size = 103492, upload-time = "2024-10-16T19:44:19.515Z" }, - { url = "https://files.pythonhosted.org/packages/b7/24/0fe235d7b69c42423c7698d086d4db96475f9b50b6ad26a718ef27a0bce6/httptools-0.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8787367fbdfccae38e35abf7641dafc5310310a5987b689f4c32cc8cc3ee975", size = 462891, upload-time = "2024-10-16T19:44:21.067Z" }, - { url = "https://files.pythonhosted.org/packages/b1/2f/205d1f2a190b72da6ffb5f41a3736c26d6fa7871101212b15e9b5cd8f61d/httptools-0.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40b0f7fe4fd38e6a507bdb751db0379df1e99120c65fbdc8ee6c1d044897a636", size = 459788, upload-time = "2024-10-16T19:44:22.958Z" }, - { url = "https://files.pythonhosted.org/packages/6e/4c/d09ce0eff09057a206a74575ae8f1e1e2f0364d20e2442224f9e6612c8b9/httptools-0.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40a5ec98d3f49904b9fe36827dcf1aadfef3b89e2bd05b0e35e94f97c2b14721", size = 433214, upload-time = "2024-10-16T19:44:24.513Z" }, - { url = "https://files.pythonhosted.org/packages/3e/d2/84c9e23edbccc4a4c6f96a1b8d99dfd2350289e94f00e9ccc7aadde26fb5/httptools-0.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dacdd3d10ea1b4ca9df97a0a303cbacafc04b5cd375fa98732678151643d4988", size = 434120, upload-time = "2024-10-16T19:44:26.295Z" }, - { url = "https://files.pythonhosted.org/packages/d0/46/4d8e7ba9581416de1c425b8264e2cadd201eb709ec1584c381f3e98f51c1/httptools-0.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:288cd628406cc53f9a541cfaf06041b4c71d751856bab45e3702191f931ccd17", size = 88565, upload-time = "2024-10-16T19:44:29.188Z" }, - { url = "https://files.pythonhosted.org/packages/bb/0e/d0b71465c66b9185f90a091ab36389a7352985fe857e352801c39d6127c8/httptools-0.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:df017d6c780287d5c80601dafa31f17bddb170232d85c066604d8558683711a2", size = 200683, upload-time = "2024-10-16T19:44:30.175Z" }, - { url = "https://files.pythonhosted.org/packages/e2/b8/412a9bb28d0a8988de3296e01efa0bd62068b33856cdda47fe1b5e890954/httptools-0.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:85071a1e8c2d051b507161f6c3e26155b5c790e4e28d7f236422dbacc2a9cc44", size = 104337, upload-time = "2024-10-16T19:44:31.786Z" }, - { url = "https://files.pythonhosted.org/packages/9b/01/6fb20be3196ffdc8eeec4e653bc2a275eca7f36634c86302242c4fbb2760/httptools-0.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69422b7f458c5af875922cdb5bd586cc1f1033295aa9ff63ee196a87519ac8e1", size = 508796, upload-time = "2024-10-16T19:44:32.825Z" }, - { url = "https://files.pythonhosted.org/packages/f7/d8/b644c44acc1368938317d76ac991c9bba1166311880bcc0ac297cb9d6bd7/httptools-0.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16e603a3bff50db08cd578d54f07032ca1631450ceb972c2f834c2b860c28ea2", size = 510837, upload-time = "2024-10-16T19:44:33.974Z" }, - { url = "https://files.pythonhosted.org/packages/52/d8/254d16a31d543073a0e57f1c329ca7378d8924e7e292eda72d0064987486/httptools-0.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec4f178901fa1834d4a060320d2f3abc5c9e39766953d038f1458cb885f47e81", size = 485289, upload-time = "2024-10-16T19:44:35.111Z" }, - { url = "https://files.pythonhosted.org/packages/5f/3c/4aee161b4b7a971660b8be71a92c24d6c64372c1ab3ae7f366b3680df20f/httptools-0.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f9eb89ecf8b290f2e293325c646a211ff1c2493222798bb80a530c5e7502494f", size = 489779, upload-time = "2024-10-16T19:44:36.253Z" }, - { url = "https://files.pythonhosted.org/packages/12/b7/5cae71a8868e555f3f67a50ee7f673ce36eac970f029c0c5e9d584352961/httptools-0.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:db78cb9ca56b59b016e64b6031eda5653be0589dba2b1b43453f6e8b405a0970", size = 88634, upload-time = "2024-10-16T19:44:37.357Z" }, - { url = "https://files.pythonhosted.org/packages/94/a3/9fe9ad23fd35f7de6b91eeb60848986058bd8b5a5c1e256f5860a160cc3e/httptools-0.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ade273d7e767d5fae13fa637f4d53b6e961fb7fd93c7797562663f0171c26660", size = 197214, upload-time = "2024-10-16T19:44:38.738Z" }, - { url = "https://files.pythonhosted.org/packages/ea/d9/82d5e68bab783b632023f2fa31db20bebb4e89dfc4d2293945fd68484ee4/httptools-0.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:856f4bc0478ae143bad54a4242fccb1f3f86a6e1be5548fecfd4102061b3a083", size = 102431, upload-time = "2024-10-16T19:44:39.818Z" }, - { url = "https://files.pythonhosted.org/packages/96/c1/cb499655cbdbfb57b577734fde02f6fa0bbc3fe9fb4d87b742b512908dff/httptools-0.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:322d20ea9cdd1fa98bd6a74b77e2ec5b818abdc3d36695ab402a0de8ef2865a3", size = 473121, upload-time = "2024-10-16T19:44:41.189Z" }, - { url = "https://files.pythonhosted.org/packages/af/71/ee32fd358f8a3bb199b03261f10921716990808a675d8160b5383487a317/httptools-0.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d87b29bd4486c0093fc64dea80231f7c7f7eb4dc70ae394d70a495ab8436071", size = 473805, upload-time = "2024-10-16T19:44:42.384Z" }, - { url = "https://files.pythonhosted.org/packages/8a/0a/0d4df132bfca1507114198b766f1737d57580c9ad1cf93c1ff673e3387be/httptools-0.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:342dd6946aa6bda4b8f18c734576106b8a31f2fe31492881a9a160ec84ff4bd5", size = 448858, upload-time = "2024-10-16T19:44:43.959Z" }, - { url = "https://files.pythonhosted.org/packages/1e/6a/787004fdef2cabea27bad1073bf6a33f2437b4dbd3b6fb4a9d71172b1c7c/httptools-0.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b36913ba52008249223042dca46e69967985fb4051951f94357ea681e1f5dc0", size = 452042, upload-time = "2024-10-16T19:44:45.071Z" }, - { url = "https://files.pythonhosted.org/packages/4d/dc/7decab5c404d1d2cdc1bb330b1bf70e83d6af0396fd4fc76fc60c0d522bf/httptools-0.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:28908df1b9bb8187393d5b5db91435ccc9c8e891657f9cbb42a2541b44c82fc8", size = 87682, upload-time = "2024-10-16T19:44:46.46Z" }, -] - [[package]] name = "httpx" version = "0.28.1" @@ -1260,7 +1029,7 @@ wheels = [ [[package]] name = "huggingface-hub" -version = "0.33.2" +version = "0.34.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "filelock" }, @@ -1272,21 +1041,9 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fa/42/8a95c5632080ae312c0498744b2b852195e10b05a20b1be11c5141092f4c/huggingface_hub-0.33.2.tar.gz", hash = "sha256:84221defaec8fa09c090390cd68c78b88e3c4c2b7befba68d3dc5aacbc3c2c5f", size = 426637, upload-time = "2025-07-02T06:26:05.156Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/44/f4/5f3f22e762ad1965f01122b42dae5bf0e009286e2dba601ce1d0dba72424/huggingface_hub-0.33.2-py3-none-any.whl", hash = "sha256:3749498bfa91e8cde2ddc2c1db92c79981f40e66434c20133b39e5928ac9bcc5", size = 515373, upload-time = "2025-07-02T06:26:03.072Z" }, -] - -[[package]] -name = "humanfriendly" -version = "10.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyreadline3", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cc/3f/2c29224acb2e2df4d2046e4c73ee2662023c58ff5b113c4c1adac0886c43/humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc", size = 360702, upload-time = "2021-09-17T21:40:43.31Z" } +sdist = { url = "https://files.pythonhosted.org/packages/45/c9/bdbe19339f76d12985bc03572f330a01a93c04dffecaaea3061bdd7fb892/huggingface_hub-0.34.4.tar.gz", hash = "sha256:a4228daa6fb001be3f4f4bdaf9a0db00e1739235702848df00885c9b5742c85c", size = 459768, upload-time = "2025-08-08T09:14:52.365Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f0/0f/310fb31e39e2d734ccaa2c0fb981ee41f7bd5056ce9bc29b2248bd569169/humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477", size = 86794, upload-time = "2021-09-17T21:40:39.897Z" }, + { url = "https://files.pythonhosted.org/packages/39/7b/bb06b061991107cd8783f300adff3e7b7f284e330fd82f507f2a1417b11d/huggingface_hub-0.34.4-py3-none-any.whl", hash = "sha256:9b365d781739c93ff90c359844221beef048403f1bc1f1c123c191257c3c890a", size = 561452, upload-time = "2025-08-08T09:14:50.159Z" }, ] [[package]] @@ -1314,11 +1071,11 @@ wheels = [ [[package]] name = "identify" -version = "2.6.12" +version = "2.6.13" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/88/d193a27416618628a5eea64e3223acd800b40749a96ffb322a9b55a49ed1/identify-2.6.12.tar.gz", hash = "sha256:d8de45749f1efb108badef65ee8386f0f7bb19a7f26185f74de6367bffbaf0e6", size = 99254, upload-time = "2025-05-23T20:37:53.3Z" } +sdist = { url = "https://files.pythonhosted.org/packages/82/ca/ffbabe3635bb839aa36b3a893c91a9b0d368cb4d8073e03a12896970af82/identify-2.6.13.tar.gz", hash = "sha256:da8d6c828e773620e13bfa86ea601c5a5310ba4bcd65edf378198b56a1f9fb32", size = 99243, upload-time = "2025-08-09T19:35:00.6Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/cd/18f8da995b658420625f7ef13f037be53ae04ec5ad33f9b718240dcfd48c/identify-2.6.12-py2.py3-none-any.whl", hash = "sha256:ad9672d5a72e0d2ff7c5c8809b62dfa60458626352fb0eb7b55e69bdc45334a2", size = 99145, upload-time = "2025-05-23T20:37:51.495Z" }, + { url = "https://files.pythonhosted.org/packages/e7/ce/461b60a3ee109518c055953729bf9ed089a04db895d47e95444071dcdef2/identify-2.6.13-py2.py3-none-any.whl", hash = "sha256:60381139b3ae39447482ecc406944190f690d4a2997f2584062089848361b33b", size = 99153, upload-time = "2025-08-09T19:34:59.1Z" }, ] [[package]] @@ -1342,15 +1099,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/20/b0/36bd937216ec521246249be3bf9855081de4c5e06a0c9b4219dbeda50373/importlib_metadata-8.7.0-py3-none-any.whl", hash = "sha256:e5dd1551894c77868a30651cef00984d50e1002d06942a7101d34870c5f02afd", size = 27656, upload-time = "2025-04-27T15:29:00.214Z" }, ] -[[package]] -name = "importlib-resources" -version = "6.5.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cf/8c/f834fbf984f691b4f7ff60f50b514cc3de5cc08abfc3295564dd89c5e2e7/importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c", size = 44693, upload-time = "2025-01-03T18:51:56.698Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec", size = 37461, upload-time = "2025-01-03T18:51:54.306Z" }, -] - [[package]] name = "inflect" version = "7.5.0" @@ -1486,7 +1234,7 @@ wheels = [ [[package]] name = "jsonschema" -version = "4.24.0" +version = "4.25.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, @@ -1494,9 +1242,9 @@ dependencies = [ { name = "referencing" }, { name = "rpds-py" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bf/d3/1cf5326b923a53515d8f3a2cd442e6d7e94fcc444716e879ea70a0ce3177/jsonschema-4.24.0.tar.gz", hash = "sha256:0b4e8069eb12aedfa881333004bccaec24ecef5a8a6a4b6df142b2cc9599d196", size = 353480, upload-time = "2025-05-26T18:48:10.459Z" } +sdist = { url = "https://files.pythonhosted.org/packages/74/69/f7185de793a29082a9f3c7728268ffb31cb5095131a9c139a74078e27336/jsonschema-4.25.1.tar.gz", hash = "sha256:e4a9655ce0da0c0b67a085847e00a3a51449e1157f4f75e9fb5aa545e122eb85", size = 357342, upload-time = "2025-08-18T17:03:50.038Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/3d/023389198f69c722d039351050738d6755376c8fd343e91dc493ea485905/jsonschema-4.24.0-py3-none-any.whl", hash = "sha256:a462455f19f5faf404a7902952b6f0e3ce868f3ee09a359b05eca6673bd8412d", size = 88709, upload-time = "2025-05-26T18:48:08.417Z" }, + { url = "https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63", size = 90040, upload-time = "2025-08-18T17:03:48.373Z" }, ] [[package]] @@ -1518,19 +1266,20 @@ source = { editable = "." } dependencies = [ { name = "boto3" }, { name = "click" }, - { name = "fireworks-ai" }, + { name = "dotenv" }, + { name = "httpx" }, { name = "langchain-anthropic" }, { name = "langchain-core" }, { name = "langchain-huggingface" }, { name = "langchain-openai" }, { name = "litellm" }, { name = "nest-asyncio" }, - { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-otlp" }, { name = "opentelemetry-sdk" }, + { name = "opentelemetry-semantic-conventions" }, { name = "orjson" }, - { name = "python-dotenv" }, - { name = "requests" }, - { name = "rich" }, + { name = "pre-commit" }, + { name = "pytest" }, { name = "typer" }, ] @@ -1541,55 +1290,39 @@ langchain = [ { name = "langchain-huggingface" }, { name = "langchain-openai" }, ] +s3 = [ + { name = "boto3" }, +] +trainer = [ + { name = "fireworks-ai" }, +] [package.dev-dependencies] dev = [ { name = "anthropic" }, - { name = "chromadb" }, + { name = "boto3-stubs", extra = ["s3"] }, { name = "datamodel-code-generator" }, { name = "google-genai" }, { name = "groq" }, + { name = "langchain-core" }, { name = "langgraph" }, - { name = "lxml-stubs" }, { name = "mypy" }, { name = "openai" }, - { name = "pandas-stubs" }, + { name = "opentelemetry-instrumentation-openai" }, { name = "pre-commit" }, - { name = "pytest" }, - { name = "pytest-asyncio" }, - { name = "pytest-cov" }, - { name = "pytest-mock" }, - { name = "tavily-python" }, + { name = "ruff" }, { name = "together" }, - { name = "types-beautifulsoup4" }, - { name = "types-cachetools" }, - { name = "types-cffi" }, - { name = "types-defusedxml" }, - { name = "types-greenlet" }, - { name = "types-jsonschema" }, - { name = "types-objgraph" }, - { name = "types-pexpect" }, - { name = "types-protobuf" }, - { name = "types-psutil" }, - { name = "types-pyasn1" }, - { name = "types-pygments" }, - { name = "types-pyopenssl" }, { name = "types-pyyaml" }, - { name = "types-regex" }, - { name = "types-reportlab" }, - { name = "types-requests" }, - { name = "types-simplejson" }, - { name = "types-tensorflow" }, - { name = "types-tqdm" }, - { name = "types-tree-sitter-languages" }, - { name = "types-xmltodict" }, ] [package.metadata] requires-dist = [ - { name = "boto3" }, + { name = "boto3", specifier = ">=1.40.11" }, + { name = "boto3", marker = "extra == 's3'", specifier = ">=1.40.11" }, { name = "click", specifier = "<8.2.0" }, - { name = "fireworks-ai", specifier = ">=0.19.18" }, + { name = "dotenv" }, + { name = "fireworks-ai", marker = "extra == 'trainer'", specifier = ">=0.19.18" }, + { name = "httpx", specifier = ">=0.28.1" }, { name = "langchain-anthropic" }, { name = "langchain-anthropic", marker = "extra == 'langchain'" }, { name = "langchain-core" }, @@ -1600,98 +1333,51 @@ requires-dist = [ { name = "langchain-openai", marker = "extra == 'langchain'" }, { name = "litellm", specifier = ">=1.61.15" }, { name = "nest-asyncio", specifier = ">=1.6.0" }, - { name = "opentelemetry-api", specifier = ">=1.34.1" }, - { name = "opentelemetry-sdk", specifier = ">=1.34.1" }, + { name = "opentelemetry-exporter-otlp", specifier = ">=1.36.0" }, + { name = "opentelemetry-sdk", specifier = ">=1.36.0" }, + { name = "opentelemetry-semantic-conventions", specifier = ">=0.57b0" }, { name = "orjson", specifier = ">=3.9.0" }, - { name = "python-dotenv" }, - { name = "requests" }, - { name = "rich" }, + { name = "pre-commit", specifier = ">=4.3.0" }, + { name = "pytest", specifier = ">=8.4.1" }, { name = "typer", specifier = ">=0.9.0" }, ] -provides-extras = ["langchain"] +provides-extras = ["langchain", "s3", "trainer"] [package.metadata.requires-dev] dev = [ - { name = "anthropic" }, - { name = "chromadb", specifier = ">=1.0.12" }, - { name = "datamodel-code-generator", specifier = ">=0.31.2" }, - { name = "google-genai" }, - { name = "groq" }, - { name = "langgraph", specifier = ">=0.4.3" }, - { name = "lxml-stubs", specifier = ">=0.5.1" }, - { name = "mypy", specifier = ">=1.17.0" }, - { name = "openai" }, - { name = "pandas-stubs", specifier = ">=2.3.0.250703" }, + { name = "anthropic", specifier = ">=0.61.0" }, + { name = "boto3-stubs", extras = ["s3"], specifier = ">=1.40.11" }, + { name = "datamodel-code-generator", specifier = ">=0.32.0" }, + { name = "google-genai", specifier = ">=1.28.0" }, + { name = "groq", specifier = ">=0.30.0" }, + { name = "langchain-core", specifier = ">=0.3.72" }, + { name = "langgraph", specifier = ">=0.6.4" }, + { name = "mypy", specifier = ">=1.17.1" }, + { name = "openai", specifier = ">=1.78.1" }, + { name = "opentelemetry-instrumentation-openai", specifier = ">=0.44.1" }, { name = "pre-commit", specifier = ">=4.2.0" }, - { name = "pytest", specifier = ">=8.4.0" }, - { name = "pytest-asyncio", specifier = ">=1.0.0" }, - { name = "pytest-cov", specifier = ">=6.1.1" }, - { name = "pytest-mock", specifier = ">=3.14.1" }, - { name = "tavily-python", specifier = ">=0.7.5" }, - { name = "together" }, - { name = "types-beautifulsoup4", specifier = ">=4.12.0.20250516" }, - { name = "types-cachetools", specifier = ">=6.1.0.20250717" }, - { name = "types-cffi", specifier = ">=1.17.0.20250523" }, - { name = "types-defusedxml", specifier = ">=0.7.0.20250708" }, - { name = "types-greenlet", specifier = ">=3.2.0.20250417" }, - { name = "types-jsonschema", specifier = ">=4.24.0.20250708" }, - { name = "types-objgraph", specifier = ">=3.6.0.20240907" }, - { name = "types-pexpect", specifier = ">=4.9.0.20250516" }, - { name = "types-protobuf", specifier = ">=6.30.2.20250703" }, - { name = "types-psutil", specifier = ">=7.0.0.20250601" }, - { name = "types-pyasn1", specifier = ">=0.6.0.20250516" }, - { name = "types-pygments", specifier = ">=2.19.0.20250715" }, - { name = "types-pyopenssl", specifier = ">=24.1.0.20240722" }, + { name = "ruff", specifier = ">=0.9.1,<0.10.0" }, + { name = "together", specifier = ">=1.5.21" }, { name = "types-pyyaml", specifier = ">=6.0.12.20250516" }, - { name = "types-regex", specifier = ">=2024.11.6.20250403" }, - { name = "types-reportlab", specifier = ">=4.4.1.20250602" }, - { name = "types-requests", specifier = ">=2.32.4.20250611" }, - { name = "types-simplejson", specifier = ">=3.20.0.20250326" }, - { name = "types-tensorflow", specifier = ">=2.18.0.20250516" }, - { name = "types-tqdm", specifier = ">=4.67.0.20250516" }, - { name = "types-tree-sitter-languages", specifier = ">=1.10.0.20250530" }, - { name = "types-xmltodict", specifier = ">=0.14.0.20241009" }, -] - -[[package]] -name = "kubernetes" -version = "33.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "certifi" }, - { name = "durationpy" }, - { name = "google-auth" }, - { name = "oauthlib" }, - { name = "python-dateutil" }, - { name = "pyyaml" }, - { name = "requests" }, - { name = "requests-oauthlib" }, - { name = "six" }, - { name = "urllib3" }, - { name = "websocket-client" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ae/52/19ebe8004c243fdfa78268a96727c71e08f00ff6fe69a301d0b7fcbce3c2/kubernetes-33.1.0.tar.gz", hash = "sha256:f64d829843a54c251061a8e7a14523b521f2dc5c896cf6d65ccf348648a88993", size = 1036779, upload-time = "2025-06-09T21:57:58.521Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/89/43/d9bebfc3db7dea6ec80df5cb2aad8d274dd18ec2edd6c4f21f32c237cbbb/kubernetes-33.1.0-py2.py3-none-any.whl", hash = "sha256:544de42b24b64287f7e0aa9513c93cb503f7f40eea39b20f66810011a86eabc5", size = 1941335, upload-time = "2025-06-09T21:57:56.327Z" }, ] [[package]] name = "langchain-anthropic" -version = "0.3.17" +version = "0.3.19" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anthropic" }, { name = "langchain-core" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e6/f7/991cefafa798c4f868637ea5733a62d15932a15d031898fcb3462253d60f/langchain_anthropic-0.3.17.tar.gz", hash = "sha256:f2c2a0382ed7992204d790ff8538448f5243f4dbb1e798256ef790c9a69033e4", size = 55831, upload-time = "2025-07-03T17:41:53.292Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/ab/bdaefa42fdab238efff45eb28c6cd74c011979092408decdae22c0bf7e66/langchain_anthropic-0.3.19.tar.gz", hash = "sha256:e62259382586ee5c44e9a9459d00b74a7e191550e5fadfad28f0daa5d143d745", size = 281502, upload-time = "2025-08-18T18:33:36.811Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6c/8e/055c65bdb3c240297676d57d705bee6d472f47ace6e28b009f0faf5c6970/langchain_anthropic-0.3.17-py3-none-any.whl", hash = "sha256:6df784615b93aab0336fbd6a50ca2bd16a704ef01c9488c36a4fa7aad2faf2d6", size = 29239, upload-time = "2025-07-03T17:41:52.202Z" }, + { url = "https://files.pythonhosted.org/packages/a2/69/64473db52d02715f3815df3b25c9816b5801a58762a5ae62a3e5b84169a0/langchain_anthropic-0.3.19-py3-none-any.whl", hash = "sha256:5b5372ef7e10ee32b4308b4d9e1ed623c360b7d0a233c017e5209ad8118d5ab7", size = 31775, upload-time = "2025-08-18T18:33:35.596Z" }, ] [[package]] name = "langchain-core" -version = "0.3.68" +version = "0.3.74" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jsonpatch" }, @@ -1702,23 +1388,23 @@ dependencies = [ { name = "tenacity" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/23/20/f5b18a17bfbe3416177e702ab2fd230b7d168abb17be31fb48f43f0bb772/langchain_core-0.3.68.tar.gz", hash = "sha256:312e1932ac9aa2eaf111b70fdc171776fa571d1a86c1f873dcac88a094b19c6f", size = 563041, upload-time = "2025-07-03T17:02:28.704Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/c6/5d755a0f1f4857abbe5ea6f5907ed0e2b5df52bf4dde0a0fd768290e3084/langchain_core-0.3.74.tar.gz", hash = "sha256:ff604441aeade942fbcc0a3860a592daba7671345230c2078ba2eb5f82b6ba76", size = 569553, upload-time = "2025-08-07T20:47:05.094Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/da/c89be0a272993bfcb762b2a356b9f55de507784c2755ad63caec25d183bf/langchain_core-0.3.68-py3-none-any.whl", hash = "sha256:5e5c1fbef419590537c91b8c2d86af896fbcbaf0d5ed7fdcdd77f7d8f3467ba0", size = 441405, upload-time = "2025-07-03T17:02:27.115Z" }, + { url = "https://files.pythonhosted.org/packages/4d/26/545283681ac0379d31c7ad0bac5f195e1982092d76c65ca048db9e3cec0e/langchain_core-0.3.74-py3-none-any.whl", hash = "sha256:088338b5bc2f6a66892f9afc777992c24ee3188f41cbc603d09181e34a228ce7", size = 443453, upload-time = "2025-08-07T20:47:03.853Z" }, ] [[package]] name = "langchain-huggingface" -version = "0.3.0" +version = "0.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "huggingface-hub" }, { name = "langchain-core" }, { name = "tokenizers" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3d/72/5f32c8e6db4d94c00252318aa7910102134b4bd6b2af1b13f2042f7f95f6/langchain_huggingface-0.3.0.tar.gz", hash = "sha256:d1cf93fb4d4eafc8b44edcd5fba904b27cbd3fda6cdef0e2321fbcf4efcda822", size = 24823, upload-time = "2025-06-10T20:50:44.739Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/15/f832ae485707bf52f9a8f055db389850de06c46bc6e3e4420a0ef105fbbf/langchain_huggingface-0.3.1.tar.gz", hash = "sha256:0a145534ce65b5a723c8562c456100a92513bbbf212e6d8c93fdbae174b41341", size = 25154, upload-time = "2025-07-22T17:22:26.77Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/da/7446c2eeacd420cb975ceb49c6feca7be40cf8ed3686a128ca78410c148f/langchain_huggingface-0.3.0-py3-none-any.whl", hash = "sha256:aab85d57e649c805d2f2a9f8d72d87b5d12c45dd4831309ac9c37753ddb237ed", size = 27359, upload-time = "2025-06-10T20:50:43.506Z" }, + { url = "https://files.pythonhosted.org/packages/bf/26/7c5d4b4d3e1a7385863acc49fb6f96c55ccf941a750991d18e3f6a69a14a/langchain_huggingface-0.3.1-py3-none-any.whl", hash = "sha256:de10a692dc812885696fbaab607d28ac86b833b0f305bccd5d82d60336b07b7d", size = 27609, upload-time = "2025-07-22T17:22:25.282Z" }, ] [[package]] @@ -1737,7 +1423,7 @@ wheels = [ [[package]] name = "langgraph" -version = "0.6.3" +version = "0.6.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "langchain-core" }, @@ -1747,9 +1433,9 @@ dependencies = [ { name = "pydantic" }, { name = "xxhash" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7d/d8/ecedf01b3e90ad288343eeb06d91d8511e049ee0b16d7f3c146baeed2a49/langgraph-0.6.3.tar.gz", hash = "sha256:0d2280d295133dc34ccc12eae1ad4d82ee847b009c59801de418068fa9248e1b", size = 452277, upload-time = "2025-08-03T11:21:35.74Z" } +sdist = { url = "https://files.pythonhosted.org/packages/24/ea/ce85312450e98a01d0974da404fa7572f2a90ec01013f8d2dfd94b714b67/langgraph-0.6.5.tar.gz", hash = "sha256:59639927997457fe04f802b39f0e7179cedf8db1cf85f33db764de02ae23c2f0", size = 455213, upload-time = "2025-08-13T23:42:35.063Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bb/93/7836486f6c18b0b11e6bde5bc0f318947e610637aa5231081bd4adf79eb9/langgraph-0.6.3-py3-none-any.whl", hash = "sha256:733efd8c59b9915e582619da40f2ab5ebb121319a4d7718adef82d6db07547eb", size = 152463, upload-time = "2025-08-03T11:21:34.002Z" }, + { url = "https://files.pythonhosted.org/packages/b4/09/93774886995cccb9110280887f7b489e5b951bbc928f078a9788254290be/langgraph-0.6.5-py3-none-any.whl", hash = "sha256:042b2ee7af6f308659520eea5ba6def50f2d109475691666045850d0661b1082", size = 153154, upload-time = "2025-08-13T23:42:33.263Z" }, ] [[package]] @@ -1767,33 +1453,33 @@ wheels = [ [[package]] name = "langgraph-prebuilt" -version = "0.6.3" +version = "0.6.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "langchain-core" }, { name = "langgraph-checkpoint" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2d/6a/7f662e24eb89bf74fcf2fd109f350ec023373d196b16d12b54e66961f145/langgraph_prebuilt-0.6.3.tar.gz", hash = "sha256:5e1ca7ba98f53ce98400f34bdb0afe47f71d0167c4108b11d4aeed4c6d4a1d3d", size = 125368, upload-time = "2025-08-03T11:16:24.789Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/21/9b198d11732101ee8cdf30af98d0b4f11254c768de15173e57f5260fd14b/langgraph_prebuilt-0.6.4.tar.gz", hash = "sha256:e9e53b906ee5df46541d1dc5303239e815d3ec551e52bb03dd6463acc79ec28f", size = 125695, upload-time = "2025-08-07T18:17:57.333Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/27/049a9e07d1d75c39e0109445e93c9df8bc4a8c51730655be561ecdf04dee/langgraph_prebuilt-0.6.3-py3-none-any.whl", hash = "sha256:cea830fc73d1a6fb871c5c6739e894bffcb7b7a07343198b56f263d3113ae8d6", size = 28917, upload-time = "2025-08-03T11:16:23.695Z" }, + { url = "https://files.pythonhosted.org/packages/0a/7f/973b0d9729d9693d6e5b4bc5f3ae41138d194cb7b16b0ed230020beeb13a/langgraph_prebuilt-0.6.4-py3-none-any.whl", hash = "sha256:819f31d88b84cb2729ff1b79db2d51e9506b8fb7aaacfc0d359d4fe16e717344", size = 28025, upload-time = "2025-08-07T18:17:56.493Z" }, ] [[package]] name = "langgraph-sdk" -version = "0.2.0" +version = "0.2.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "httpx" }, { name = "orjson" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2a/3e/3dc45dc7682c9940db9edaf8773d2e157397c5bd6881f6806808afd8731e/langgraph_sdk-0.2.0.tar.gz", hash = "sha256:cd8b5f6595e5571be5cbffd04cf936978ab8f5d1005517c99715947ef871e246", size = 72510, upload-time = "2025-07-22T17:31:06.745Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4d/3a/ea929b5b3827615802f020abdaa6d4a6f9d59ab764f65559fa6f87a6dda6/langgraph_sdk-0.2.2.tar.gz", hash = "sha256:9484e8071953df75d7aaf9845d82db3595e485af7d5dcc235c9b32c52362e1fc", size = 77981, upload-time = "2025-08-18T19:25:42.596Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a5/03/a8ab0e8ea74be6058cb48bb1d85485b5c65d6ea183e3ee1aa8ca1ac73b3e/langgraph_sdk-0.2.0-py3-none-any.whl", hash = "sha256:150722264f225c4d47bbe7394676be102fdbf04c4400a0dd1bd41a70c6430cc7", size = 50569, upload-time = "2025-07-22T17:31:04.582Z" }, + { url = "https://files.pythonhosted.org/packages/01/0d/dfa633c6b85e973e7d4383e9b92603b7e910e89768411daeb7777bfbae04/langgraph_sdk-0.2.2-py3-none-any.whl", hash = "sha256:1afbec01ade166f8b6ce18782875415422eb70dcb82852aeaa373e6152db4b82", size = 52017, upload-time = "2025-08-18T19:25:40.567Z" }, ] [[package]] name = "langsmith" -version = "0.4.4" +version = "0.4.14" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "httpx" }, @@ -1804,14 +1490,14 @@ dependencies = [ { name = "requests-toolbelt" }, { name = "zstandard" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/20/c8/8d2e0fc438d2d3d8d4300f7684ea30a754344ed00d7ba9cc2705241d2a5f/langsmith-0.4.4.tar.gz", hash = "sha256:70c53bbff24a7872e88e6fa0af98270f4986a6e364f9e85db1cc5636defa4d66", size = 352105, upload-time = "2025-06-27T19:20:36.207Z" } +sdist = { url = "https://files.pythonhosted.org/packages/85/b0/1def3c6d12eb5e412213e39f1ba4ac64a47ec3102cf42a3a1ff86af1402d/langsmith-0.4.14.tar.gz", hash = "sha256:4d29c7a9c85b20ba813ab9c855407bccdf5eb4f397f512ffa89959b2a2cb83ed", size = 921872, upload-time = "2025-08-12T20:39:43.704Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/33/a3337eb70d795495a299a1640d7a75f17fb917155a64309b96106e7b9452/langsmith-0.4.4-py3-none-any.whl", hash = "sha256:014c68329bd085bd6c770a6405c61bb6881f82eb554ce8c4d1984b0035fd1716", size = 367687, upload-time = "2025-06-27T19:20:33.839Z" }, + { url = "https://files.pythonhosted.org/packages/9e/08/3f0fb3e2f7cc6fd91c4d06d7abc6607425a66973bee79d04018bac41dd4f/langsmith-0.4.14-py3-none-any.whl", hash = "sha256:b6d070ac425196947d2a98126fb0e35f3b8c001a2e6e5b7049dd1c56f0767d0b", size = 373249, upload-time = "2025-08-12T20:39:41.992Z" }, ] [[package]] name = "litellm" -version = "1.61.15" +version = "1.75.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, @@ -1826,30 +1512,21 @@ dependencies = [ { name = "tiktoken" }, { name = "tokenizers" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/23/3a/3e299244bb048bbe63e6342a34b5974ab551b24ca0cbafdd5aaf81c0c81d/litellm-1.61.15.tar.gz", hash = "sha256:f10ff155d8bca6cb0e2a1cc8d9aedbe6b390f73b30b9603e9cadb66b11472d44", size = 6561672, upload-time = "2025-02-23T07:24:01.411Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ac/b8/2908528ce280a1554162bffaec47514c71a01d76867e0b3fefd096c9cd00/litellm-1.61.15-py3-none-any.whl", hash = "sha256:977fd4e37491dd5adf14ed7c4d55bd099dd5ee7d40b8b8af5eba515d448013bb", size = 6871062, upload-time = "2025-02-23T07:23:57.868Z" }, -] - -[[package]] -name = "lxml-stubs" -version = "0.5.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/99/da/1a3a3e5d159b249fc2970d73437496b908de8e4716a089c69591b4ffa6fd/lxml-stubs-0.5.1.tar.gz", hash = "sha256:e0ec2aa1ce92d91278b719091ce4515c12adc1d564359dfaf81efa7d4feab79d", size = 14778, upload-time = "2024-01-10T09:37:46.521Z" } +sdist = { url = "https://files.pythonhosted.org/packages/09/98/ea40c48fda5121af00e44c9c6d01a0cd8cb9987bb0ce91c6add917d9db9d/litellm-1.75.3.tar.gz", hash = "sha256:a6a0f33884f35a9391a9a4363043114d7f2513ab2e5c2e1fa54c56d695663764", size = 10104437, upload-time = "2025-08-08T14:58:09.423Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/c9/e0f8e4e6e8a69e5959b06499582dca6349db6769cc7fdfb8a02a7c75a9ae/lxml_stubs-0.5.1-py3-none-any.whl", hash = "sha256:1f689e5dbc4b9247cb09ae820c7d34daeb1fdbd1db06123814b856dae7787272", size = 13584, upload-time = "2024-01-10T09:37:44.931Z" }, + { url = "https://files.pythonhosted.org/packages/dd/1e/8ef7e7ac7d33f900ae44e9e3a33d668783034e414aa4d7191ae3e4068ec5/litellm-1.75.3-py3-none-any.whl", hash = "sha256:0ff3752b1f1c07f8a4b9a364b1595e2147ae640f1e77cd8312e6f6a5ca0f34ec", size = 8870578, upload-time = "2025-08-08T14:58:06.766Z" }, ] [[package]] name = "markdown-it-py" -version = "3.0.0" +version = "4.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mdurl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596, upload-time = "2023-06-03T06:41:14.443Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528, upload-time = "2023-06-03T06:41:11.019Z" }, + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, ] [[package]] @@ -1914,7 +1591,7 @@ wheels = [ [[package]] name = "mcp" -version = "1.12.4" +version = "1.13.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -1929,9 +1606,9 @@ dependencies = [ { name = "starlette" }, { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/31/88/f6cb7e7c260cd4b4ce375f2b1614b33ce401f63af0f49f7141a2e9bf0a45/mcp-1.12.4.tar.gz", hash = "sha256:0765585e9a3a5916a3c3ab8659330e493adc7bd8b2ca6120c2d7a0c43e034ca5", size = 431148, upload-time = "2025-08-07T20:31:18.082Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/a8/564c094de5d6199f727f5d9f5672dbec3b00dfafd0f67bf52d995eaa5951/mcp-1.13.0.tar.gz", hash = "sha256:70452f56f74662a94eb72ac5feb93997b35995e389b3a3a574e078bed2aa9ab3", size = 434709, upload-time = "2025-08-14T15:03:58.58Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ad/68/316cbc54b7163fa22571dcf42c9cc46562aae0a021b974e0a8141e897200/mcp-1.12.4-py3-none-any.whl", hash = "sha256:7aa884648969fab8e78b89399d59a683202972e12e6bc9a1c88ce7eda7743789", size = 160145, upload-time = "2025-08-07T20:31:15.69Z" }, + { url = "https://files.pythonhosted.org/packages/8b/6b/46b8bcefc2ee9e2d2e8d2bd25f1c2512f5a879fac4619d716b194d6e7ccc/mcp-1.13.0-py3-none-any.whl", hash = "sha256:8b1a002ebe6e17e894ec74d1943cc09aa9d23cb931bf58d49ab2e9fa6bb17e4b", size = 160226, upload-time = "2025-08-14T15:03:56.641Z" }, ] [[package]] @@ -1945,58 +1622,98 @@ wheels = [ [[package]] name = "mmh3" -version = "5.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/47/1b/1fc6888c74cbd8abad1292dde2ddfcf8fc059e114c97dd6bf16d12f36293/mmh3-5.1.0.tar.gz", hash = "sha256:136e1e670500f177f49ec106a4ebf0adf20d18d96990cc36ea492c651d2b406c", size = 33728, upload-time = "2025-01-25T08:39:43.386Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/56/09/fda7af7fe65928262098382e3bf55950cfbf67d30bf9e47731bf862161e9/mmh3-5.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0b529dcda3f951ff363a51d5866bc6d63cf57f1e73e8961f864ae5010647079d", size = 56098, upload-time = "2025-01-25T08:38:22.917Z" }, - { url = "https://files.pythonhosted.org/packages/0c/ab/84c7bc3f366d6f3bd8b5d9325a10c367685bc17c26dac4c068e2001a4671/mmh3-5.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4db1079b3ace965e562cdfc95847312f9273eb2ad3ebea983435c8423e06acd7", size = 40513, upload-time = "2025-01-25T08:38:25.079Z" }, - { url = "https://files.pythonhosted.org/packages/4f/21/25ea58ca4a652bdc83d1528bec31745cce35802381fb4fe3c097905462d2/mmh3-5.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:22d31e3a0ff89b8eb3b826d6fc8e19532998b2aa6b9143698043a1268da413e1", size = 40112, upload-time = "2025-01-25T08:38:25.947Z" }, - { url = "https://files.pythonhosted.org/packages/bd/78/4f12f16ae074ddda6f06745254fdb50f8cf3c85b0bbf7eaca58bed84bf58/mmh3-5.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2139bfbd354cd6cb0afed51c4b504f29bcd687a3b1460b7e89498329cc28a894", size = 102632, upload-time = "2025-01-25T08:38:26.939Z" }, - { url = "https://files.pythonhosted.org/packages/48/11/8f09dc999cf2a09b6138d8d7fc734efb7b7bfdd9adb9383380941caadff0/mmh3-5.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c8105c6a435bc2cd6ea2ef59558ab1a2976fd4a4437026f562856d08996673a", size = 108884, upload-time = "2025-01-25T08:38:29.159Z" }, - { url = "https://files.pythonhosted.org/packages/bd/91/e59a66538a3364176f6c3f7620eee0ab195bfe26f89a95cbcc7a1fb04b28/mmh3-5.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57730067174a7f36fcd6ce012fe359bd5510fdaa5fe067bc94ed03e65dafb769", size = 106835, upload-time = "2025-01-25T08:38:33.04Z" }, - { url = "https://files.pythonhosted.org/packages/25/14/b85836e21ab90e5cddb85fe79c494ebd8f81d96a87a664c488cc9277668b/mmh3-5.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bde80eb196d7fdc765a318604ded74a4378f02c5b46c17aa48a27d742edaded2", size = 93688, upload-time = "2025-01-25T08:38:34.987Z" }, - { url = "https://files.pythonhosted.org/packages/ac/aa/8bc964067df9262740c95e4cde2d19f149f2224f426654e14199a9e47df6/mmh3-5.1.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9c8eddcb441abddeb419c16c56fd74b3e2df9e57f7aa2903221996718435c7a", size = 101569, upload-time = "2025-01-25T08:38:35.983Z" }, - { url = "https://files.pythonhosted.org/packages/70/b6/1fb163cbf919046a64717466c00edabebece3f95c013853fec76dbf2df92/mmh3-5.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:99e07e4acafbccc7a28c076a847fb060ffc1406036bc2005acb1b2af620e53c3", size = 98483, upload-time = "2025-01-25T08:38:38.198Z" }, - { url = "https://files.pythonhosted.org/packages/70/49/ba64c050dd646060f835f1db6b2cd60a6485f3b0ea04976e7a29ace7312e/mmh3-5.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9e25ba5b530e9a7d65f41a08d48f4b3fedc1e89c26486361166a5544aa4cad33", size = 96496, upload-time = "2025-01-25T08:38:39.257Z" }, - { url = "https://files.pythonhosted.org/packages/9e/07/f2751d6a0b535bb865e1066e9c6b80852571ef8d61bce7eb44c18720fbfc/mmh3-5.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bb9bf7475b4d99156ce2f0cf277c061a17560c8c10199c910a680869a278ddc7", size = 105109, upload-time = "2025-01-25T08:38:40.395Z" }, - { url = "https://files.pythonhosted.org/packages/b7/02/30360a5a66f7abba44596d747cc1e6fb53136b168eaa335f63454ab7bb79/mmh3-5.1.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2a1b0878dd281ea3003368ab53ff6f568e175f1b39f281df1da319e58a19c23a", size = 98231, upload-time = "2025-01-25T08:38:42.141Z" }, - { url = "https://files.pythonhosted.org/packages/8c/60/8526b0c750ff4d7ae1266e68b795f14b97758a1d9fcc19f6ecabf9c55656/mmh3-5.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:25f565093ac8b8aefe0f61f8f95c9a9d11dd69e6a9e9832ff0d293511bc36258", size = 97548, upload-time = "2025-01-25T08:38:43.402Z" }, - { url = "https://files.pythonhosted.org/packages/6d/4c/26e1222aca65769280d5427a1ce5875ef4213449718c8f03958d0bf91070/mmh3-5.1.0-cp311-cp311-win32.whl", hash = "sha256:1e3554d8792387eac73c99c6eaea0b3f884e7130eb67986e11c403e4f9b6d372", size = 40810, upload-time = "2025-01-25T08:38:45.143Z" }, - { url = "https://files.pythonhosted.org/packages/98/d5/424ba95062d1212ea615dc8debc8d57983f2242d5e6b82e458b89a117a1e/mmh3-5.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:8ad777a48197882492af50bf3098085424993ce850bdda406a358b6ab74be759", size = 41476, upload-time = "2025-01-25T08:38:46.029Z" }, - { url = "https://files.pythonhosted.org/packages/bd/08/0315ccaf087ba55bb19a6dd3b1e8acd491e74ce7f5f9c4aaa06a90d66441/mmh3-5.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:f29dc4efd99bdd29fe85ed6c81915b17b2ef2cf853abf7213a48ac6fb3eaabe1", size = 38880, upload-time = "2025-01-25T08:38:47.035Z" }, - { url = "https://files.pythonhosted.org/packages/f4/47/e5f452bdf16028bfd2edb4e2e35d0441e4a4740f30e68ccd4cfd2fb2c57e/mmh3-5.1.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:45712987367cb9235026e3cbf4334670522a97751abfd00b5bc8bfa022c3311d", size = 56152, upload-time = "2025-01-25T08:38:47.902Z" }, - { url = "https://files.pythonhosted.org/packages/60/38/2132d537dc7a7fdd8d2e98df90186c7fcdbd3f14f95502a24ba443c92245/mmh3-5.1.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b1020735eb35086ab24affbea59bb9082f7f6a0ad517cb89f0fc14f16cea4dae", size = 40564, upload-time = "2025-01-25T08:38:48.839Z" }, - { url = "https://files.pythonhosted.org/packages/c0/2a/c52cf000581bfb8d94794f58865658e7accf2fa2e90789269d4ae9560b16/mmh3-5.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:babf2a78ce5513d120c358722a2e3aa7762d6071cd10cede026f8b32452be322", size = 40104, upload-time = "2025-01-25T08:38:49.773Z" }, - { url = "https://files.pythonhosted.org/packages/83/33/30d163ce538c54fc98258db5621447e3ab208d133cece5d2577cf913e708/mmh3-5.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4f47f58cd5cbef968c84a7c1ddc192fef0a36b48b0b8a3cb67354531aa33b00", size = 102634, upload-time = "2025-01-25T08:38:51.5Z" }, - { url = "https://files.pythonhosted.org/packages/94/5c/5a18acb6ecc6852be2d215c3d811aa61d7e425ab6596be940877355d7f3e/mmh3-5.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2044a601c113c981f2c1e14fa33adc9b826c9017034fe193e9eb49a6882dbb06", size = 108888, upload-time = "2025-01-25T08:38:52.542Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f6/11c556324c64a92aa12f28e221a727b6e082e426dc502e81f77056f6fc98/mmh3-5.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c94d999c9f2eb2da44d7c2826d3fbffdbbbbcde8488d353fee7c848ecc42b968", size = 106968, upload-time = "2025-01-25T08:38:54.286Z" }, - { url = "https://files.pythonhosted.org/packages/5d/61/ca0c196a685aba7808a5c00246f17b988a9c4f55c594ee0a02c273e404f3/mmh3-5.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a015dcb24fa0c7a78f88e9419ac74f5001c1ed6a92e70fd1803f74afb26a4c83", size = 93771, upload-time = "2025-01-25T08:38:55.576Z" }, - { url = "https://files.pythonhosted.org/packages/b4/55/0927c33528710085ee77b808d85bbbafdb91a1db7c8eaa89cac16d6c513e/mmh3-5.1.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:457da019c491a2d20e2022c7d4ce723675e4c081d9efc3b4d8b9f28a5ea789bd", size = 101726, upload-time = "2025-01-25T08:38:56.654Z" }, - { url = "https://files.pythonhosted.org/packages/49/39/a92c60329fa470f41c18614a93c6cd88821412a12ee78c71c3f77e1cfc2d/mmh3-5.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:71408579a570193a4ac9c77344d68ddefa440b00468a0b566dcc2ba282a9c559", size = 98523, upload-time = "2025-01-25T08:38:57.662Z" }, - { url = "https://files.pythonhosted.org/packages/81/90/26adb15345af8d9cf433ae1b6adcf12e0a4cad1e692de4fa9f8e8536c5ae/mmh3-5.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8b3a04bc214a6e16c81f02f855e285c6df274a2084787eeafaa45f2fbdef1b63", size = 96628, upload-time = "2025-01-25T08:38:59.505Z" }, - { url = "https://files.pythonhosted.org/packages/8a/4d/340d1e340df972a13fd4ec84c787367f425371720a1044220869c82364e9/mmh3-5.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:832dae26a35514f6d3c1e267fa48e8de3c7b978afdafa0529c808ad72e13ada3", size = 105190, upload-time = "2025-01-25T08:39:00.483Z" }, - { url = "https://files.pythonhosted.org/packages/d3/7c/65047d1cccd3782d809936db446430fc7758bda9def5b0979887e08302a2/mmh3-5.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:bf658a61fc92ef8a48945ebb1076ef4ad74269e353fffcb642dfa0890b13673b", size = 98439, upload-time = "2025-01-25T08:39:01.484Z" }, - { url = "https://files.pythonhosted.org/packages/72/d2/3c259d43097c30f062050f7e861075099404e8886b5d4dd3cebf180d6e02/mmh3-5.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3313577453582b03383731b66447cdcdd28a68f78df28f10d275d7d19010c1df", size = 97780, upload-time = "2025-01-25T08:39:02.444Z" }, - { url = "https://files.pythonhosted.org/packages/29/29/831ea8d4abe96cdb3e28b79eab49cac7f04f9c6b6e36bfc686197ddba09d/mmh3-5.1.0-cp312-cp312-win32.whl", hash = "sha256:1d6508504c531ab86c4424b5a5ff07c1132d063863339cf92f6657ff7a580f76", size = 40835, upload-time = "2025-01-25T08:39:03.369Z" }, - { url = "https://files.pythonhosted.org/packages/12/dd/7cbc30153b73f08eeac43804c1dbc770538a01979b4094edbe1a4b8eb551/mmh3-5.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:aa75981fcdf3f21759d94f2c81b6a6e04a49dfbcdad88b152ba49b8e20544776", size = 41509, upload-time = "2025-01-25T08:39:04.284Z" }, - { url = "https://files.pythonhosted.org/packages/80/9d/627375bab4c90dd066093fc2c9a26b86f87e26d980dbf71667b44cbee3eb/mmh3-5.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:a4c1a76808dfea47f7407a0b07aaff9087447ef6280716fd0783409b3088bb3c", size = 38888, upload-time = "2025-01-25T08:39:05.174Z" }, - { url = "https://files.pythonhosted.org/packages/05/06/a098a42870db16c0a54a82c56a5bdc873de3165218cd5b3ca59dbc0d31a7/mmh3-5.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a523899ca29cfb8a5239618474a435f3d892b22004b91779fcb83504c0d5b8c", size = 56165, upload-time = "2025-01-25T08:39:06.887Z" }, - { url = "https://files.pythonhosted.org/packages/5a/65/eaada79a67fde1f43e1156d9630e2fb70655e1d3f4e8f33d7ffa31eeacfd/mmh3-5.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:17cef2c3a6ca2391ca7171a35ed574b5dab8398163129a3e3a4c05ab85a4ff40", size = 40569, upload-time = "2025-01-25T08:39:07.945Z" }, - { url = "https://files.pythonhosted.org/packages/36/7e/2b6c43ed48be583acd68e34d16f19209a9f210e4669421b0321e326d8554/mmh3-5.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:52e12895b30110f3d89dae59a888683cc886ed0472dd2eca77497edef6161997", size = 40104, upload-time = "2025-01-25T08:39:09.598Z" }, - { url = "https://files.pythonhosted.org/packages/11/2b/1f9e962fdde8e41b0f43d22c8ba719588de8952f9376df7d73a434827590/mmh3-5.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0d6719045cda75c3f40397fc24ab67b18e0cb8f69d3429ab4c39763c4c608dd", size = 102497, upload-time = "2025-01-25T08:39:10.512Z" }, - { url = "https://files.pythonhosted.org/packages/46/94/d6c5c3465387ba077cccdc028ab3eec0d86eed1eebe60dcf4d15294056be/mmh3-5.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d19fa07d303a91f8858982c37e6939834cb11893cb3ff20e6ee6fa2a7563826a", size = 108834, upload-time = "2025-01-25T08:39:11.568Z" }, - { url = "https://files.pythonhosted.org/packages/34/1e/92c212bb81796b69dddfd50a8a8f4b26ab0d38fdaf1d3e8628a67850543b/mmh3-5.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:31b47a620d622fbde8ca1ca0435c5d25de0ac57ab507209245e918128e38e676", size = 106936, upload-time = "2025-01-25T08:39:12.638Z" }, - { url = "https://files.pythonhosted.org/packages/f4/41/f2f494bbff3aad5ffd2085506255049de76cde51ddac84058e32768acc79/mmh3-5.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00f810647c22c179b6821079f7aa306d51953ac893587ee09cf1afb35adf87cb", size = 93709, upload-time = "2025-01-25T08:39:14.071Z" }, - { url = "https://files.pythonhosted.org/packages/9e/a9/a2cc4a756d73d9edf4fb85c76e16fd56b0300f8120fd760c76b28f457730/mmh3-5.1.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6128b610b577eed1e89ac7177ab0c33d06ade2aba93f5c89306032306b5f1c6", size = 101623, upload-time = "2025-01-25T08:39:15.507Z" }, - { url = "https://files.pythonhosted.org/packages/5e/6f/b9d735533b6a56b2d56333ff89be6a55ac08ba7ff33465feb131992e33eb/mmh3-5.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1e550a45d2ff87a1c11b42015107f1778c93f4c6f8e731bf1b8fa770321b8cc4", size = 98521, upload-time = "2025-01-25T08:39:16.77Z" }, - { url = "https://files.pythonhosted.org/packages/99/47/dff2b54fac0d421c1e6ecbd2d9c85b2d0e6f6ee0d10b115d9364116a511e/mmh3-5.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:785ae09276342f79fd8092633e2d52c0f7c44d56e8cfda8274ccc9b76612dba2", size = 96696, upload-time = "2025-01-25T08:39:17.805Z" }, - { url = "https://files.pythonhosted.org/packages/be/43/9e205310f47c43ddf1575bb3a1769c36688f30f1ac105e0f0c878a29d2cd/mmh3-5.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0f4be3703a867ef976434afd3661a33884abe73ceb4ee436cac49d3b4c2aaa7b", size = 105234, upload-time = "2025-01-25T08:39:18.908Z" }, - { url = "https://files.pythonhosted.org/packages/6b/44/90b11fd2b67dcb513f5bfe9b476eb6ca2d5a221c79b49884dc859100905e/mmh3-5.1.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e513983830c4ff1f205ab97152a0050cf7164f1b4783d702256d39c637b9d107", size = 98449, upload-time = "2025-01-25T08:39:20.719Z" }, - { url = "https://files.pythonhosted.org/packages/f0/d0/25c4b0c7b8e49836541059b28e034a4cccd0936202800d43a1cc48495ecb/mmh3-5.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b9135c300535c828c0bae311b659f33a31c941572eae278568d1a953c4a57b59", size = 97796, upload-time = "2025-01-25T08:39:22.453Z" }, - { url = "https://files.pythonhosted.org/packages/23/fa/cbbb7fcd0e287a715f1cd28a10de94c0535bd94164e38b852abc18da28c6/mmh3-5.1.0-cp313-cp313-win32.whl", hash = "sha256:c65dbd12885a5598b70140d24de5839551af5a99b29f9804bb2484b29ef07692", size = 40828, upload-time = "2025-01-25T08:39:23.372Z" }, - { url = "https://files.pythonhosted.org/packages/09/33/9fb90ef822f7b734955a63851907cf72f8a3f9d8eb3c5706bfa6772a2a77/mmh3-5.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:10db7765201fc65003fa998faa067417ef6283eb5f9bba8f323c48fd9c33e91f", size = 41504, upload-time = "2025-01-25T08:39:24.286Z" }, - { url = "https://files.pythonhosted.org/packages/16/71/4ad9a42f2772793a03cb698f0fc42499f04e6e8d2560ba2f7da0fb059a8e/mmh3-5.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:b22fe2e54be81f6c07dcb36b96fa250fb72effe08aa52fbb83eade6e1e2d5fd7", size = 38890, upload-time = "2025-01-25T08:39:25.28Z" }, +version = "5.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/af/f28c2c2f51f31abb4725f9a64bc7863d5f491f6539bd26aee2a1d21a649e/mmh3-5.2.0.tar.gz", hash = "sha256:1efc8fec8478e9243a78bb993422cf79f8ff85cb4cf6b79647480a31e0d950a8", size = 33582, upload-time = "2025-07-29T07:43:48.49Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/87/399567b3796e134352e11a8b973cd470c06b2ecfad5468fe580833be442b/mmh3-5.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7901c893e704ee3c65f92d39b951f8f34ccf8e8566768c58103fb10e55afb8c1", size = 56107, upload-time = "2025-07-29T07:41:57.07Z" }, + { url = "https://files.pythonhosted.org/packages/c3/09/830af30adf8678955b247d97d3d9543dd2fd95684f3cd41c0cd9d291da9f/mmh3-5.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5f5536b1cbfa72318ab3bfc8a8188b949260baed186b75f0abc75b95d8c051", size = 40635, upload-time = "2025-07-29T07:41:57.903Z" }, + { url = "https://files.pythonhosted.org/packages/07/14/eaba79eef55b40d653321765ac5e8f6c9ac38780b8a7c2a2f8df8ee0fb72/mmh3-5.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cedac4f4054b8f7859e5aed41aaa31ad03fce6851901a7fdc2af0275ac533c10", size = 40078, upload-time = "2025-07-29T07:41:58.772Z" }, + { url = "https://files.pythonhosted.org/packages/bb/26/83a0f852e763f81b2265d446b13ed6d49ee49e1fc0c47b9655977e6f3d81/mmh3-5.2.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:eb756caf8975882630ce4e9fbbeb9d3401242a72528230422c9ab3a0d278e60c", size = 97262, upload-time = "2025-07-29T07:41:59.678Z" }, + { url = "https://files.pythonhosted.org/packages/00/7d/b7133b10d12239aeaebf6878d7eaf0bf7d3738c44b4aba3c564588f6d802/mmh3-5.2.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:097e13c8b8a66c5753c6968b7640faefe85d8e38992703c1f666eda6ef4c3762", size = 103118, upload-time = "2025-07-29T07:42:01.197Z" }, + { url = "https://files.pythonhosted.org/packages/7b/3e/62f0b5dce2e22fd5b7d092aba285abd7959ea2b17148641e029f2eab1ffa/mmh3-5.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a7c0c7845566b9686480e6a7e9044db4afb60038d5fabd19227443f0104eeee4", size = 106072, upload-time = "2025-07-29T07:42:02.601Z" }, + { url = "https://files.pythonhosted.org/packages/66/84/ea88bb816edfe65052c757a1c3408d65c4201ddbd769d4a287b0f1a628b2/mmh3-5.2.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:61ac226af521a572700f863d6ecddc6ece97220ce7174e311948ff8c8919a363", size = 112925, upload-time = "2025-07-29T07:42:03.632Z" }, + { url = "https://files.pythonhosted.org/packages/2e/13/c9b1c022807db575fe4db806f442d5b5784547e2e82cff36133e58ea31c7/mmh3-5.2.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:582f9dbeefe15c32a5fa528b79b088b599a1dfe290a4436351c6090f90ddebb8", size = 120583, upload-time = "2025-07-29T07:42:04.991Z" }, + { url = "https://files.pythonhosted.org/packages/8a/5f/0e2dfe1a38f6a78788b7eb2b23432cee24623aeabbc907fed07fc17d6935/mmh3-5.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2ebfc46b39168ab1cd44670a32ea5489bcbc74a25795c61b6d888c5c2cf654ed", size = 99127, upload-time = "2025-07-29T07:42:05.929Z" }, + { url = "https://files.pythonhosted.org/packages/77/27/aefb7d663b67e6a0c4d61a513c83e39ba2237e8e4557fa7122a742a23de5/mmh3-5.2.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1556e31e4bd0ac0c17eaf220be17a09c171d7396919c3794274cb3415a9d3646", size = 98544, upload-time = "2025-07-29T07:42:06.87Z" }, + { url = "https://files.pythonhosted.org/packages/ab/97/a21cc9b1a7c6e92205a1b5fa030cdf62277d177570c06a239eca7bd6dd32/mmh3-5.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:81df0dae22cd0da87f1c978602750f33d17fb3d21fb0f326c89dc89834fea79b", size = 106262, upload-time = "2025-07-29T07:42:07.804Z" }, + { url = "https://files.pythonhosted.org/packages/43/18/db19ae82ea63c8922a880e1498a75342311f8aa0c581c4dd07711473b5f7/mmh3-5.2.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:eba01ec3bd4a49b9ac5ca2bc6a73ff5f3af53374b8556fcc2966dd2af9eb7779", size = 109824, upload-time = "2025-07-29T07:42:08.735Z" }, + { url = "https://files.pythonhosted.org/packages/9f/f5/41dcf0d1969125fc6f61d8618b107c79130b5af50b18a4651210ea52ab40/mmh3-5.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e9a011469b47b752e7d20de296bb34591cdfcbe76c99c2e863ceaa2aa61113d2", size = 97255, upload-time = "2025-07-29T07:42:09.706Z" }, + { url = "https://files.pythonhosted.org/packages/32/b3/cce9eaa0efac1f0e735bb178ef9d1d2887b4927fe0ec16609d5acd492dda/mmh3-5.2.0-cp311-cp311-win32.whl", hash = "sha256:bc44fc2b886243d7c0d8daeb37864e16f232e5b56aaec27cc781d848264cfd28", size = 40779, upload-time = "2025-07-29T07:42:10.546Z" }, + { url = "https://files.pythonhosted.org/packages/7c/e9/3fa0290122e6d5a7041b50ae500b8a9f4932478a51e48f209a3879fe0b9b/mmh3-5.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:8ebf241072cf2777a492d0e09252f8cc2b3edd07dfdb9404b9757bffeb4f2cee", size = 41549, upload-time = "2025-07-29T07:42:11.399Z" }, + { url = "https://files.pythonhosted.org/packages/3a/54/c277475b4102588e6f06b2e9095ee758dfe31a149312cdbf62d39a9f5c30/mmh3-5.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:b5f317a727bba0e633a12e71228bc6a4acb4f471a98b1c003163b917311ea9a9", size = 39336, upload-time = "2025-07-29T07:42:12.209Z" }, + { url = "https://files.pythonhosted.org/packages/bf/6a/d5aa7edb5c08e0bd24286c7d08341a0446f9a2fbbb97d96a8a6dd81935ee/mmh3-5.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:384eda9361a7bf83a85e09447e1feafe081034af9dd428893701b959230d84be", size = 56141, upload-time = "2025-07-29T07:42:13.456Z" }, + { url = "https://files.pythonhosted.org/packages/08/49/131d0fae6447bc4a7299ebdb1a6fb9d08c9f8dcf97d75ea93e8152ddf7ab/mmh3-5.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2c9da0d568569cc87315cb063486d761e38458b8ad513fedd3dc9263e1b81bcd", size = 40681, upload-time = "2025-07-29T07:42:14.306Z" }, + { url = "https://files.pythonhosted.org/packages/8f/6f/9221445a6bcc962b7f5ff3ba18ad55bba624bacdc7aa3fc0a518db7da8ec/mmh3-5.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:86d1be5d63232e6eb93c50881aea55ff06eb86d8e08f9b5417c8c9b10db9db96", size = 40062, upload-time = "2025-07-29T07:42:15.08Z" }, + { url = "https://files.pythonhosted.org/packages/1e/d4/6bb2d0fef81401e0bb4c297d1eb568b767de4ce6fc00890bc14d7b51ecc4/mmh3-5.2.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bf7bee43e17e81671c447e9c83499f53d99bf440bc6d9dc26a841e21acfbe094", size = 97333, upload-time = "2025-07-29T07:42:16.436Z" }, + { url = "https://files.pythonhosted.org/packages/44/e0/ccf0daff8134efbb4fbc10a945ab53302e358c4b016ada9bf97a6bdd50c1/mmh3-5.2.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7aa18cdb58983ee660c9c400b46272e14fa253c675ed963d3812487f8ca42037", size = 103310, upload-time = "2025-07-29T07:42:17.796Z" }, + { url = "https://files.pythonhosted.org/packages/02/63/1965cb08a46533faca0e420e06aff8bbaf9690a6f0ac6ae6e5b2e4544687/mmh3-5.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae9d032488fcec32d22be6542d1a836f00247f40f320844dbb361393b5b22773", size = 106178, upload-time = "2025-07-29T07:42:19.281Z" }, + { url = "https://files.pythonhosted.org/packages/c2/41/c883ad8e2c234013f27f92061200afc11554ea55edd1bcf5e1accd803a85/mmh3-5.2.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e1861fb6b1d0453ed7293200139c0a9011eeb1376632e048e3766945b13313c5", size = 113035, upload-time = "2025-07-29T07:42:20.356Z" }, + { url = "https://files.pythonhosted.org/packages/df/b5/1ccade8b1fa625d634a18bab7bf08a87457e09d5ec8cf83ca07cbea9d400/mmh3-5.2.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:99bb6a4d809aa4e528ddfe2c85dd5239b78b9dd14be62cca0329db78505e7b50", size = 120784, upload-time = "2025-07-29T07:42:21.377Z" }, + { url = "https://files.pythonhosted.org/packages/77/1c/919d9171fcbdcdab242e06394464ccf546f7d0f3b31e0d1e3a630398782e/mmh3-5.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1f8d8b627799f4e2fcc7c034fed8f5f24dc7724ff52f69838a3d6d15f1ad4765", size = 99137, upload-time = "2025-07-29T07:42:22.344Z" }, + { url = "https://files.pythonhosted.org/packages/66/8a/1eebef5bd6633d36281d9fc83cf2e9ba1ba0e1a77dff92aacab83001cee4/mmh3-5.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b5995088dd7023d2d9f310a0c67de5a2b2e06a570ecfd00f9ff4ab94a67cde43", size = 98664, upload-time = "2025-07-29T07:42:23.269Z" }, + { url = "https://files.pythonhosted.org/packages/13/41/a5d981563e2ee682b21fb65e29cc0f517a6734a02b581359edd67f9d0360/mmh3-5.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:1a5f4d2e59d6bba8ef01b013c472741835ad961e7c28f50c82b27c57748744a4", size = 106459, upload-time = "2025-07-29T07:42:24.238Z" }, + { url = "https://files.pythonhosted.org/packages/24/31/342494cd6ab792d81e083680875a2c50fa0c5df475ebf0b67784f13e4647/mmh3-5.2.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:fd6e6c3d90660d085f7e73710eab6f5545d4854b81b0135a3526e797009dbda3", size = 110038, upload-time = "2025-07-29T07:42:25.629Z" }, + { url = "https://files.pythonhosted.org/packages/28/44/efda282170a46bb4f19c3e2b90536513b1d821c414c28469a227ca5a1789/mmh3-5.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c4a2f3d83879e3de2eb8cbf562e71563a8ed15ee9b9c2e77ca5d9f73072ac15c", size = 97545, upload-time = "2025-07-29T07:42:27.04Z" }, + { url = "https://files.pythonhosted.org/packages/68/8f/534ae319c6e05d714f437e7206f78c17e66daca88164dff70286b0e8ea0c/mmh3-5.2.0-cp312-cp312-win32.whl", hash = "sha256:2421b9d665a0b1ad724ec7332fb5a98d075f50bc51a6ff854f3a1882bd650d49", size = 40805, upload-time = "2025-07-29T07:42:28.032Z" }, + { url = "https://files.pythonhosted.org/packages/b8/f6/f6abdcfefcedab3c964868048cfe472764ed358c2bf6819a70dd4ed4ed3a/mmh3-5.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:72d80005b7634a3a2220f81fbeb94775ebd12794623bb2e1451701ea732b4aa3", size = 41597, upload-time = "2025-07-29T07:42:28.894Z" }, + { url = "https://files.pythonhosted.org/packages/15/fd/f7420e8cbce45c259c770cac5718badf907b302d3a99ec587ba5ce030237/mmh3-5.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:3d6bfd9662a20c054bc216f861fa330c2dac7c81e7fb8307b5e32ab5b9b4d2e0", size = 39350, upload-time = "2025-07-29T07:42:29.794Z" }, + { url = "https://files.pythonhosted.org/packages/d8/fa/27f6ab93995ef6ad9f940e96593c5dd24744d61a7389532b0fec03745607/mmh3-5.2.0-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:e79c00eba78f7258e5b354eccd4d7907d60317ced924ea4a5f2e9d83f5453065", size = 40874, upload-time = "2025-07-29T07:42:30.662Z" }, + { url = "https://files.pythonhosted.org/packages/11/9c/03d13bcb6a03438bc8cac3d2e50f80908d159b31a4367c2e1a7a077ded32/mmh3-5.2.0-cp313-cp313-android_21_x86_64.whl", hash = "sha256:956127e663d05edbeec54df38885d943dfa27406594c411139690485128525de", size = 42012, upload-time = "2025-07-29T07:42:31.539Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/0865d9765408a7d504f1789944e678f74e0888b96a766d578cb80b040999/mmh3-5.2.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:c3dca4cb5b946ee91b3d6bb700d137b1cd85c20827f89fdf9c16258253489044", size = 39197, upload-time = "2025-07-29T07:42:32.374Z" }, + { url = "https://files.pythonhosted.org/packages/3e/12/76c3207bd186f98b908b6706c2317abb73756d23a4e68ea2bc94825b9015/mmh3-5.2.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:e651e17bfde5840e9e4174b01e9e080ce49277b70d424308b36a7969d0d1af73", size = 39840, upload-time = "2025-07-29T07:42:33.227Z" }, + { url = "https://files.pythonhosted.org/packages/5d/0d/574b6cce5555c9f2b31ea189ad44986755eb14e8862db28c8b834b8b64dc/mmh3-5.2.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:9f64bf06f4bf623325fda3a6d02d36cd69199b9ace99b04bb2d7fd9f89688504", size = 40644, upload-time = "2025-07-29T07:42:34.099Z" }, + { url = "https://files.pythonhosted.org/packages/52/82/3731f8640b79c46707f53ed72034a58baad400be908c87b0088f1f89f986/mmh3-5.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ddc63328889bcaee77b743309e5c7d2d52cee0d7d577837c91b6e7cc9e755e0b", size = 56153, upload-time = "2025-07-29T07:42:35.031Z" }, + { url = "https://files.pythonhosted.org/packages/4f/34/e02dca1d4727fd9fdeaff9e2ad6983e1552804ce1d92cc796e5b052159bb/mmh3-5.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bb0fdc451fb6d86d81ab8f23d881b8d6e37fc373a2deae1c02d27002d2ad7a05", size = 40684, upload-time = "2025-07-29T07:42:35.914Z" }, + { url = "https://files.pythonhosted.org/packages/8f/36/3dee40767356e104967e6ed6d102ba47b0b1ce2a89432239b95a94de1b89/mmh3-5.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b29044e1ffdb84fe164d0a7ea05c7316afea93c00f8ed9449cf357c36fc4f814", size = 40057, upload-time = "2025-07-29T07:42:36.755Z" }, + { url = "https://files.pythonhosted.org/packages/31/58/228c402fccf76eb39a0a01b8fc470fecf21965584e66453b477050ee0e99/mmh3-5.2.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:58981d6ea9646dbbf9e59a30890cbf9f610df0e4a57dbfe09215116fd90b0093", size = 97344, upload-time = "2025-07-29T07:42:37.675Z" }, + { url = "https://files.pythonhosted.org/packages/34/82/fc5ce89006389a6426ef28e326fc065b0fbaaed230373b62d14c889f47ea/mmh3-5.2.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:7e5634565367b6d98dc4aa2983703526ef556b3688ba3065edb4b9b90ede1c54", size = 103325, upload-time = "2025-07-29T07:42:38.591Z" }, + { url = "https://files.pythonhosted.org/packages/09/8c/261e85777c6aee1ebd53f2f17e210e7481d5b0846cd0b4a5c45f1e3761b8/mmh3-5.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0271ac12415afd3171ab9a3c7cbfc71dee2c68760a7dc9d05bf8ed6ddfa3a7a", size = 106240, upload-time = "2025-07-29T07:42:39.563Z" }, + { url = "https://files.pythonhosted.org/packages/70/73/2f76b3ad8a3d431824e9934403df36c0ddacc7831acf82114bce3c4309c8/mmh3-5.2.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:45b590e31bc552c6f8e2150ff1ad0c28dd151e9f87589e7eaf508fbdd8e8e908", size = 113060, upload-time = "2025-07-29T07:42:40.585Z" }, + { url = "https://files.pythonhosted.org/packages/9f/b9/7ea61a34e90e50a79a9d87aa1c0b8139a7eaf4125782b34b7d7383472633/mmh3-5.2.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bdde97310d59604f2a9119322f61b31546748499a21b44f6715e8ced9308a6c5", size = 120781, upload-time = "2025-07-29T07:42:41.618Z" }, + { url = "https://files.pythonhosted.org/packages/0f/5b/ae1a717db98c7894a37aeedbd94b3f99e6472a836488f36b6849d003485b/mmh3-5.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fc9c5f280438cf1c1a8f9abb87dc8ce9630a964120cfb5dd50d1e7ce79690c7a", size = 99174, upload-time = "2025-07-29T07:42:42.587Z" }, + { url = "https://files.pythonhosted.org/packages/e3/de/000cce1d799fceebb6d4487ae29175dd8e81b48e314cba7b4da90bcf55d7/mmh3-5.2.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:c903e71fd8debb35ad2a4184c1316b3cb22f64ce517b4e6747f25b0a34e41266", size = 98734, upload-time = "2025-07-29T07:42:43.996Z" }, + { url = "https://files.pythonhosted.org/packages/79/19/0dc364391a792b72fbb22becfdeacc5add85cc043cd16986e82152141883/mmh3-5.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:eed4bba7ff8a0d37106ba931ab03bdd3915fbb025bcf4e1f0aa02bc8114960c5", size = 106493, upload-time = "2025-07-29T07:42:45.07Z" }, + { url = "https://files.pythonhosted.org/packages/3c/b1/bc8c28e4d6e807bbb051fefe78e1156d7f104b89948742ad310612ce240d/mmh3-5.2.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:1fdb36b940e9261aff0b5177c5b74a36936b902f473180f6c15bde26143681a9", size = 110089, upload-time = "2025-07-29T07:42:46.122Z" }, + { url = "https://files.pythonhosted.org/packages/3b/a2/d20f3f5c95e9c511806686c70d0a15479cc3941c5f322061697af1c1ff70/mmh3-5.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7303aab41e97adcf010a09efd8f1403e719e59b7705d5e3cfed3dd7571589290", size = 97571, upload-time = "2025-07-29T07:42:47.18Z" }, + { url = "https://files.pythonhosted.org/packages/7b/23/665296fce4f33488deec39a750ffd245cfc07aafb0e3ef37835f91775d14/mmh3-5.2.0-cp313-cp313-win32.whl", hash = "sha256:03e08c6ebaf666ec1e3d6ea657a2d363bb01effd1a9acfe41f9197decaef0051", size = 40806, upload-time = "2025-07-29T07:42:48.166Z" }, + { url = "https://files.pythonhosted.org/packages/59/b0/92e7103f3b20646e255b699e2d0327ce53a3f250e44367a99dc8be0b7c7a/mmh3-5.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:7fddccd4113e7b736706e17a239a696332360cbaddf25ae75b57ba1acce65081", size = 41600, upload-time = "2025-07-29T07:42:49.371Z" }, + { url = "https://files.pythonhosted.org/packages/99/22/0b2bd679a84574647de538c5b07ccaa435dbccc37815067fe15b90fe8dad/mmh3-5.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:fa0c966ee727aad5406d516375593c5f058c766b21236ab8985693934bb5085b", size = 39349, upload-time = "2025-07-29T07:42:50.268Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ca/a20db059a8a47048aaf550da14a145b56e9c7386fb8280d3ce2962dcebf7/mmh3-5.2.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:e5015f0bb6eb50008bed2d4b1ce0f2a294698a926111e4bb202c0987b4f89078", size = 39209, upload-time = "2025-07-29T07:42:51.559Z" }, + { url = "https://files.pythonhosted.org/packages/98/dd/e5094799d55c7482d814b979a0fd608027d0af1b274bfb4c3ea3e950bfd5/mmh3-5.2.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:e0f3ed828d709f5b82d8bfe14f8856120718ec4bd44a5b26102c3030a1e12501", size = 39843, upload-time = "2025-07-29T07:42:52.536Z" }, + { url = "https://files.pythonhosted.org/packages/f4/6b/7844d7f832c85400e7cc89a1348e4e1fdd38c5a38415bb5726bbb8fcdb6c/mmh3-5.2.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:f35727c5118aba95f0397e18a1a5b8405425581bfe53e821f0fb444cbdc2bc9b", size = 40648, upload-time = "2025-07-29T07:42:53.392Z" }, + { url = "https://files.pythonhosted.org/packages/1f/bf/71f791f48a21ff3190ba5225807cbe4f7223360e96862c376e6e3fb7efa7/mmh3-5.2.0-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3bc244802ccab5220008cb712ca1508cb6a12f0eb64ad62997156410579a1770", size = 56164, upload-time = "2025-07-29T07:42:54.267Z" }, + { url = "https://files.pythonhosted.org/packages/70/1f/f87e3d34d83032b4f3f0f528c6d95a98290fcacf019da61343a49dccfd51/mmh3-5.2.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ff3d50dc3fe8a98059f99b445dfb62792b5d006c5e0b8f03c6de2813b8376110", size = 40692, upload-time = "2025-07-29T07:42:55.234Z" }, + { url = "https://files.pythonhosted.org/packages/a6/e2/db849eaed07117086f3452feca8c839d30d38b830ac59fe1ce65af8be5ad/mmh3-5.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:37a358cc881fe796e099c1db6ce07ff757f088827b4e8467ac52b7a7ffdca647", size = 40068, upload-time = "2025-07-29T07:42:56.158Z" }, + { url = "https://files.pythonhosted.org/packages/df/6b/209af927207af77425b044e32f77f49105a0b05d82ff88af6971d8da4e19/mmh3-5.2.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b9a87025121d1c448f24f27ff53a5fe7b6ef980574b4a4f11acaabe702420d63", size = 97367, upload-time = "2025-07-29T07:42:57.037Z" }, + { url = "https://files.pythonhosted.org/packages/ca/e0/78adf4104c425606a9ce33fb351f790c76a6c2314969c4a517d1ffc92196/mmh3-5.2.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:1ba55d6ca32eeef8b2625e1e4bfc3b3db52bc63014bd7e5df8cc11bf2b036b12", size = 103306, upload-time = "2025-07-29T07:42:58.522Z" }, + { url = "https://files.pythonhosted.org/packages/a3/79/c2b89f91b962658b890104745b1b6c9ce38d50a889f000b469b91eeb1b9e/mmh3-5.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9ff37ba9f15637e424c2ab57a1a590c52897c845b768e4e0a4958084ec87f22", size = 106312, upload-time = "2025-07-29T07:42:59.552Z" }, + { url = "https://files.pythonhosted.org/packages/4b/14/659d4095528b1a209be90934778c5ffe312177d51e365ddcbca2cac2ec7c/mmh3-5.2.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a094319ec0db52a04af9fdc391b4d39a1bc72bc8424b47c4411afb05413a44b5", size = 113135, upload-time = "2025-07-29T07:43:00.745Z" }, + { url = "https://files.pythonhosted.org/packages/8d/6f/cd7734a779389a8a467b5c89a48ff476d6f2576e78216a37551a97e9e42a/mmh3-5.2.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c5584061fd3da584659b13587f26c6cad25a096246a481636d64375d0c1f6c07", size = 120775, upload-time = "2025-07-29T07:43:02.124Z" }, + { url = "https://files.pythonhosted.org/packages/1d/ca/8256e3b96944408940de3f9291d7e38a283b5761fe9614d4808fcf27bd62/mmh3-5.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ecbfc0437ddfdced5e7822d1ce4855c9c64f46819d0fdc4482c53f56c707b935", size = 99178, upload-time = "2025-07-29T07:43:03.182Z" }, + { url = "https://files.pythonhosted.org/packages/8a/32/39e2b3cf06b6e2eb042c984dab8680841ac2a0d3ca6e0bea30db1f27b565/mmh3-5.2.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:7b986d506a8e8ea345791897ba5d8ba0d9d8820cd4fc3e52dbe6de19388de2e7", size = 98738, upload-time = "2025-07-29T07:43:04.207Z" }, + { url = "https://files.pythonhosted.org/packages/61/d3/7bbc8e0e8cf65ebbe1b893ffa0467b7ecd1bd07c3bbf6c9db4308ada22ec/mmh3-5.2.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:38d899a156549da8ef6a9f1d6f7ef231228d29f8f69bce2ee12f5fba6d6fd7c5", size = 106510, upload-time = "2025-07-29T07:43:05.656Z" }, + { url = "https://files.pythonhosted.org/packages/10/99/b97e53724b52374e2f3859046f0eb2425192da356cb19784d64bc17bb1cf/mmh3-5.2.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d86651fa45799530885ba4dab3d21144486ed15285e8784181a0ab37a4552384", size = 110053, upload-time = "2025-07-29T07:43:07.204Z" }, + { url = "https://files.pythonhosted.org/packages/ac/62/3688c7d975ed195155671df68788c83fed6f7909b6ec4951724c6860cb97/mmh3-5.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c463d7c1c4cfc9d751efeaadd936bbba07b5b0ed81a012b3a9f5a12f0872bd6e", size = 97546, upload-time = "2025-07-29T07:43:08.226Z" }, + { url = "https://files.pythonhosted.org/packages/ca/3b/c6153250f03f71a8b7634cded82939546cdfba02e32f124ff51d52c6f991/mmh3-5.2.0-cp314-cp314-win32.whl", hash = "sha256:bb4fe46bdc6104fbc28db7a6bacb115ee6368ff993366bbd8a2a7f0076e6f0c0", size = 41422, upload-time = "2025-07-29T07:43:09.216Z" }, + { url = "https://files.pythonhosted.org/packages/74/01/a27d98bab083a435c4c07e9d1d720d4c8a578bf4c270bae373760b1022be/mmh3-5.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:7c7f0b342fd06044bedd0b6e72177ddc0076f54fd89ee239447f8b271d919d9b", size = 42135, upload-time = "2025-07-29T07:43:10.183Z" }, + { url = "https://files.pythonhosted.org/packages/cb/c9/dbba5507e95429b8b380e2ba091eff5c20a70a59560934dff0ad8392b8c8/mmh3-5.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:3193752fc05ea72366c2b63ff24b9a190f422e32d75fdeae71087c08fff26115", size = 39879, upload-time = "2025-07-29T07:43:11.106Z" }, + { url = "https://files.pythonhosted.org/packages/b5/d1/c8c0ef839c17258b9de41b84f663574fabcf8ac2007b7416575e0f65ff6e/mmh3-5.2.0-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:69fc339d7202bea69ef9bd7c39bfdf9fdabc8e6822a01eba62fb43233c1b3932", size = 57696, upload-time = "2025-07-29T07:43:11.989Z" }, + { url = "https://files.pythonhosted.org/packages/2f/55/95e2b9ff201e89f9fe37036037ab61a6c941942b25cdb7b6a9df9b931993/mmh3-5.2.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:12da42c0a55c9d86ab566395324213c319c73ecb0c239fad4726324212b9441c", size = 41421, upload-time = "2025-07-29T07:43:13.269Z" }, + { url = "https://files.pythonhosted.org/packages/77/79/9be23ad0b7001a4b22752e7693be232428ecc0a35068a4ff5c2f14ef8b20/mmh3-5.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f7f9034c7cf05ddfaac8d7a2e63a3c97a840d4615d0a0e65ba8bdf6f8576e3be", size = 40853, upload-time = "2025-07-29T07:43:14.888Z" }, + { url = "https://files.pythonhosted.org/packages/ac/1b/96b32058eda1c1dee8264900c37c359a7325c1f11f5ff14fd2be8e24eff9/mmh3-5.2.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:11730eeb16dfcf9674fdea9bb6b8e6dd9b40813b7eb839bc35113649eef38aeb", size = 109694, upload-time = "2025-07-29T07:43:15.816Z" }, + { url = "https://files.pythonhosted.org/packages/8d/6f/a2ae44cd7dad697b6dea48390cbc977b1e5ca58fda09628cbcb2275af064/mmh3-5.2.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:932a6eec1d2e2c3c9e630d10f7128d80e70e2d47fe6b8c7ea5e1afbd98733e65", size = 117438, upload-time = "2025-07-29T07:43:16.865Z" }, + { url = "https://files.pythonhosted.org/packages/a0/08/bfb75451c83f05224a28afeaf3950c7b793c0b71440d571f8e819cfb149a/mmh3-5.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3ca975c51c5028947bbcfc24966517aac06a01d6c921e30f7c5383c195f87991", size = 120409, upload-time = "2025-07-29T07:43:18.207Z" }, + { url = "https://files.pythonhosted.org/packages/9f/ea/8b118b69b2ff8df568f742387d1a159bc654a0f78741b31437dd047ea28e/mmh3-5.2.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5b0b58215befe0f0e120b828f7645e97719bbba9f23b69e268ed0ac7adde8645", size = 125909, upload-time = "2025-07-29T07:43:19.39Z" }, + { url = "https://files.pythonhosted.org/packages/3e/11/168cc0b6a30650032e351a3b89b8a47382da541993a03af91e1ba2501234/mmh3-5.2.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:29c2b9ce61886809d0492a274a5a53047742dea0f703f9c4d5d223c3ea6377d3", size = 135331, upload-time = "2025-07-29T07:43:20.435Z" }, + { url = "https://files.pythonhosted.org/packages/31/05/e3a9849b1c18a7934c64e831492c99e67daebe84a8c2f2c39a7096a830e3/mmh3-5.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a367d4741ac0103f8198c82f429bccb9359f543ca542b06a51f4f0332e8de279", size = 110085, upload-time = "2025-07-29T07:43:21.92Z" }, + { url = "https://files.pythonhosted.org/packages/d9/d5/a96bcc306e3404601418b2a9a370baec92af84204528ba659fdfe34c242f/mmh3-5.2.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:5a5dba98e514fb26241868f6eb90a7f7ca0e039aed779342965ce24ea32ba513", size = 111195, upload-time = "2025-07-29T07:43:23.066Z" }, + { url = "https://files.pythonhosted.org/packages/af/29/0fd49801fec5bff37198684e0849b58e0dab3a2a68382a357cfffb0fafc3/mmh3-5.2.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:941603bfd75a46023807511c1ac2f1b0f39cccc393c15039969806063b27e6db", size = 116919, upload-time = "2025-07-29T07:43:24.178Z" }, + { url = "https://files.pythonhosted.org/packages/2d/04/4f3c32b0a2ed762edca45d8b46568fc3668e34f00fb1e0a3b5451ec1281c/mmh3-5.2.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:132dd943451a7c7546978863d2f5a64977928410782e1a87d583cb60eb89e667", size = 123160, upload-time = "2025-07-29T07:43:25.26Z" }, + { url = "https://files.pythonhosted.org/packages/91/76/3d29eaa38821730633d6a240d36fa8ad2807e9dfd432c12e1a472ed211eb/mmh3-5.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f698733a8a494466432d611a8f0d1e026f5286dee051beea4b3c3146817e35d5", size = 110206, upload-time = "2025-07-29T07:43:26.699Z" }, + { url = "https://files.pythonhosted.org/packages/44/1c/ccf35892684d3a408202e296e56843743e0b4fb1629e59432ea88cdb3909/mmh3-5.2.0-cp314-cp314t-win32.whl", hash = "sha256:6d541038b3fc360ec538fc116de87462627944765a6750308118f8b509a8eec7", size = 41970, upload-time = "2025-07-29T07:43:27.666Z" }, + { url = "https://files.pythonhosted.org/packages/75/b2/b9e4f1e5adb5e21eb104588fcee2cd1eaa8308255173481427d5ecc4284e/mmh3-5.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:e912b19cf2378f2967d0c08e86ff4c6c360129887f678e27e4dde970d21b3f4d", size = 43063, upload-time = "2025-07-29T07:43:28.582Z" }, + { url = "https://files.pythonhosted.org/packages/6a/fc/0e61d9a4e29c8679356795a40e48f647b4aad58d71bfc969f0f8f56fb912/mmh3-5.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:e7884931fe5e788163e7b3c511614130c2c59feffdc21112290a194487efb2e9", size = 40455, upload-time = "2025-07-29T07:43:29.563Z" }, ] [[package]] @@ -2008,94 +1725,85 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2b/9f/7ba6f94fc1e9ac3d2b853fdff3035fb2fa5afbed898c4a72b8a020610594/more_itertools-10.7.0-py3-none-any.whl", hash = "sha256:d43980384673cb07d2f7d2d918c616b30c659c089ee23953f601d6609c67510e", size = 65278, upload-time = "2025-04-22T14:17:40.49Z" }, ] -[[package]] -name = "mpmath" -version = "1.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" }, -] - [[package]] name = "multidict" -version = "6.6.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3d/2c/5dad12e82fbdf7470f29bff2171484bf07cb3b16ada60a6589af8f376440/multidict-6.6.3.tar.gz", hash = "sha256:798a9eb12dab0a6c2e29c1de6f3468af5cb2da6053a20dfa3344907eed0937cc", size = 101006, upload-time = "2025-06-30T15:53:46.929Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/08/f0/1a39863ced51f639c81a5463fbfa9eb4df59c20d1a8769ab9ef4ca57ae04/multidict-6.6.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:18f4eba0cbac3546b8ae31e0bbc55b02c801ae3cbaf80c247fcdd89b456ff58c", size = 76445, upload-time = "2025-06-30T15:51:24.01Z" }, - { url = "https://files.pythonhosted.org/packages/c9/0e/a7cfa451c7b0365cd844e90b41e21fab32edaa1e42fc0c9f68461ce44ed7/multidict-6.6.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ef43b5dd842382329e4797c46f10748d8c2b6e0614f46b4afe4aee9ac33159df", size = 44610, upload-time = "2025-06-30T15:51:25.158Z" }, - { url = "https://files.pythonhosted.org/packages/c6/bb/a14a4efc5ee748cc1904b0748be278c31b9295ce5f4d2ef66526f410b94d/multidict-6.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bf9bd1fd5eec01494e0f2e8e446a74a85d5e49afb63d75a9934e4a5423dba21d", size = 44267, upload-time = "2025-06-30T15:51:26.326Z" }, - { url = "https://files.pythonhosted.org/packages/c2/f8/410677d563c2d55e063ef74fe578f9d53fe6b0a51649597a5861f83ffa15/multidict-6.6.3-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:5bd8d6f793a787153956cd35e24f60485bf0651c238e207b9a54f7458b16d539", size = 230004, upload-time = "2025-06-30T15:51:27.491Z" }, - { url = "https://files.pythonhosted.org/packages/fd/df/2b787f80059314a98e1ec6a4cc7576244986df3e56b3c755e6fc7c99e038/multidict-6.6.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bf99b4daf908c73856bd87ee0a2499c3c9a3d19bb04b9c6025e66af3fd07462", size = 247196, upload-time = "2025-06-30T15:51:28.762Z" }, - { url = "https://files.pythonhosted.org/packages/05/f2/f9117089151b9a8ab39f9019620d10d9718eec2ac89e7ca9d30f3ec78e96/multidict-6.6.3-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0b9e59946b49dafaf990fd9c17ceafa62976e8471a14952163d10a7a630413a9", size = 225337, upload-time = "2025-06-30T15:51:30.025Z" }, - { url = "https://files.pythonhosted.org/packages/93/2d/7115300ec5b699faa152c56799b089a53ed69e399c3c2d528251f0aeda1a/multidict-6.6.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e2db616467070d0533832d204c54eea6836a5e628f2cb1e6dfd8cd6ba7277cb7", size = 257079, upload-time = "2025-06-30T15:51:31.716Z" }, - { url = "https://files.pythonhosted.org/packages/15/ea/ff4bab367623e39c20d3b07637225c7688d79e4f3cc1f3b9f89867677f9a/multidict-6.6.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7394888236621f61dcdd25189b2768ae5cc280f041029a5bcf1122ac63df79f9", size = 255461, upload-time = "2025-06-30T15:51:33.029Z" }, - { url = "https://files.pythonhosted.org/packages/74/07/2c9246cda322dfe08be85f1b8739646f2c4c5113a1422d7a407763422ec4/multidict-6.6.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f114d8478733ca7388e7c7e0ab34b72547476b97009d643644ac33d4d3fe1821", size = 246611, upload-time = "2025-06-30T15:51:34.47Z" }, - { url = "https://files.pythonhosted.org/packages/a8/62/279c13d584207d5697a752a66ffc9bb19355a95f7659140cb1b3cf82180e/multidict-6.6.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cdf22e4db76d323bcdc733514bf732e9fb349707c98d341d40ebcc6e9318ef3d", size = 243102, upload-time = "2025-06-30T15:51:36.525Z" }, - { url = "https://files.pythonhosted.org/packages/69/cc/e06636f48c6d51e724a8bc8d9e1db5f136fe1df066d7cafe37ef4000f86a/multidict-6.6.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:e995a34c3d44ab511bfc11aa26869b9d66c2d8c799fa0e74b28a473a692532d6", size = 238693, upload-time = "2025-06-30T15:51:38.278Z" }, - { url = "https://files.pythonhosted.org/packages/89/a4/66c9d8fb9acf3b226cdd468ed009537ac65b520aebdc1703dd6908b19d33/multidict-6.6.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:766a4a5996f54361d8d5a9050140aa5362fe48ce51c755a50c0bc3706460c430", size = 246582, upload-time = "2025-06-30T15:51:39.709Z" }, - { url = "https://files.pythonhosted.org/packages/cf/01/c69e0317be556e46257826d5449feb4e6aa0d18573e567a48a2c14156f1f/multidict-6.6.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:3893a0d7d28a7fe6ca7a1f760593bc13038d1d35daf52199d431b61d2660602b", size = 253355, upload-time = "2025-06-30T15:51:41.013Z" }, - { url = "https://files.pythonhosted.org/packages/c0/da/9cc1da0299762d20e626fe0042e71b5694f9f72d7d3f9678397cbaa71b2b/multidict-6.6.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:934796c81ea996e61914ba58064920d6cad5d99140ac3167901eb932150e2e56", size = 247774, upload-time = "2025-06-30T15:51:42.291Z" }, - { url = "https://files.pythonhosted.org/packages/e6/91/b22756afec99cc31105ddd4a52f95ab32b1a4a58f4d417979c570c4a922e/multidict-6.6.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9ed948328aec2072bc00f05d961ceadfd3e9bfc2966c1319aeaf7b7c21219183", size = 242275, upload-time = "2025-06-30T15:51:43.642Z" }, - { url = "https://files.pythonhosted.org/packages/be/f1/adcc185b878036a20399d5be5228f3cbe7f823d78985d101d425af35c800/multidict-6.6.3-cp311-cp311-win32.whl", hash = "sha256:9f5b28c074c76afc3e4c610c488e3493976fe0e596dd3db6c8ddfbb0134dcac5", size = 41290, upload-time = "2025-06-30T15:51:45.264Z" }, - { url = "https://files.pythonhosted.org/packages/e0/d4/27652c1c6526ea6b4f5ddd397e93f4232ff5de42bea71d339bc6a6cc497f/multidict-6.6.3-cp311-cp311-win_amd64.whl", hash = "sha256:bc7f6fbc61b1c16050a389c630da0b32fc6d4a3d191394ab78972bf5edc568c2", size = 45942, upload-time = "2025-06-30T15:51:46.377Z" }, - { url = "https://files.pythonhosted.org/packages/16/18/23f4932019804e56d3c2413e237f866444b774b0263bcb81df2fdecaf593/multidict-6.6.3-cp311-cp311-win_arm64.whl", hash = "sha256:d4e47d8faffaae822fb5cba20937c048d4f734f43572e7079298a6c39fb172cb", size = 42880, upload-time = "2025-06-30T15:51:47.561Z" }, - { url = "https://files.pythonhosted.org/packages/0e/a0/6b57988ea102da0623ea814160ed78d45a2645e4bbb499c2896d12833a70/multidict-6.6.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:056bebbeda16b2e38642d75e9e5310c484b7c24e3841dc0fb943206a72ec89d6", size = 76514, upload-time = "2025-06-30T15:51:48.728Z" }, - { url = "https://files.pythonhosted.org/packages/07/7a/d1e92665b0850c6c0508f101f9cf0410c1afa24973e1115fe9c6a185ebf7/multidict-6.6.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e5f481cccb3c5c5e5de5d00b5141dc589c1047e60d07e85bbd7dea3d4580d63f", size = 45394, upload-time = "2025-06-30T15:51:49.986Z" }, - { url = "https://files.pythonhosted.org/packages/52/6f/dd104490e01be6ef8bf9573705d8572f8c2d2c561f06e3826b081d9e6591/multidict-6.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:10bea2ee839a759ee368b5a6e47787f399b41e70cf0c20d90dfaf4158dfb4e55", size = 43590, upload-time = "2025-06-30T15:51:51.331Z" }, - { url = "https://files.pythonhosted.org/packages/44/fe/06e0e01b1b0611e6581b7fd5a85b43dacc08b6cea3034f902f383b0873e5/multidict-6.6.3-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:2334cfb0fa9549d6ce2c21af2bfbcd3ac4ec3646b1b1581c88e3e2b1779ec92b", size = 237292, upload-time = "2025-06-30T15:51:52.584Z" }, - { url = "https://files.pythonhosted.org/packages/ce/71/4f0e558fb77696b89c233c1ee2d92f3e1d5459070a0e89153c9e9e804186/multidict-6.6.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8fee016722550a2276ca2cb5bb624480e0ed2bd49125b2b73b7010b9090e888", size = 258385, upload-time = "2025-06-30T15:51:53.913Z" }, - { url = "https://files.pythonhosted.org/packages/e3/25/cca0e68228addad24903801ed1ab42e21307a1b4b6dd2cf63da5d3ae082a/multidict-6.6.3-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5511cb35f5c50a2db21047c875eb42f308c5583edf96bd8ebf7d770a9d68f6d", size = 242328, upload-time = "2025-06-30T15:51:55.672Z" }, - { url = "https://files.pythonhosted.org/packages/6e/a3/46f2d420d86bbcb8fe660b26a10a219871a0fbf4d43cb846a4031533f3e0/multidict-6.6.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:712b348f7f449948e0a6c4564a21c7db965af900973a67db432d724619b3c680", size = 268057, upload-time = "2025-06-30T15:51:57.037Z" }, - { url = "https://files.pythonhosted.org/packages/9e/73/1c743542fe00794a2ec7466abd3f312ccb8fad8dff9f36d42e18fb1ec33e/multidict-6.6.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e4e15d2138ee2694e038e33b7c3da70e6b0ad8868b9f8094a72e1414aeda9c1a", size = 269341, upload-time = "2025-06-30T15:51:59.111Z" }, - { url = "https://files.pythonhosted.org/packages/a4/11/6ec9dcbe2264b92778eeb85407d1df18812248bf3506a5a1754bc035db0c/multidict-6.6.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8df25594989aebff8a130f7899fa03cbfcc5d2b5f4a461cf2518236fe6f15961", size = 256081, upload-time = "2025-06-30T15:52:00.533Z" }, - { url = "https://files.pythonhosted.org/packages/9b/2b/631b1e2afeb5f1696846d747d36cda075bfdc0bc7245d6ba5c319278d6c4/multidict-6.6.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:159ca68bfd284a8860f8d8112cf0521113bffd9c17568579e4d13d1f1dc76b65", size = 253581, upload-time = "2025-06-30T15:52:02.43Z" }, - { url = "https://files.pythonhosted.org/packages/bf/0e/7e3b93f79efeb6111d3bf9a1a69e555ba1d07ad1c11bceb56b7310d0d7ee/multidict-6.6.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e098c17856a8c9ade81b4810888c5ad1914099657226283cab3062c0540b0643", size = 250750, upload-time = "2025-06-30T15:52:04.26Z" }, - { url = "https://files.pythonhosted.org/packages/ad/9e/086846c1d6601948e7de556ee464a2d4c85e33883e749f46b9547d7b0704/multidict-6.6.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:67c92ed673049dec52d7ed39f8cf9ebbadf5032c774058b4406d18c8f8fe7063", size = 251548, upload-time = "2025-06-30T15:52:06.002Z" }, - { url = "https://files.pythonhosted.org/packages/8c/7b/86ec260118e522f1a31550e87b23542294880c97cfbf6fb18cc67b044c66/multidict-6.6.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:bd0578596e3a835ef451784053cfd327d607fc39ea1a14812139339a18a0dbc3", size = 262718, upload-time = "2025-06-30T15:52:07.707Z" }, - { url = "https://files.pythonhosted.org/packages/8c/bd/22ce8f47abb0be04692c9fc4638508b8340987b18691aa7775d927b73f72/multidict-6.6.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:346055630a2df2115cd23ae271910b4cae40f4e336773550dca4889b12916e75", size = 259603, upload-time = "2025-06-30T15:52:09.58Z" }, - { url = "https://files.pythonhosted.org/packages/07/9c/91b7ac1691be95cd1f4a26e36a74b97cda6aa9820632d31aab4410f46ebd/multidict-6.6.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:555ff55a359302b79de97e0468e9ee80637b0de1fce77721639f7cd9440b3a10", size = 251351, upload-time = "2025-06-30T15:52:10.947Z" }, - { url = "https://files.pythonhosted.org/packages/6f/5c/4d7adc739884f7a9fbe00d1eac8c034023ef8bad71f2ebe12823ca2e3649/multidict-6.6.3-cp312-cp312-win32.whl", hash = "sha256:73ab034fb8d58ff85c2bcbadc470efc3fafeea8affcf8722855fb94557f14cc5", size = 41860, upload-time = "2025-06-30T15:52:12.334Z" }, - { url = "https://files.pythonhosted.org/packages/6a/a3/0fbc7afdf7cb1aa12a086b02959307848eb6bcc8f66fcb66c0cb57e2a2c1/multidict-6.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:04cbcce84f63b9af41bad04a54d4cc4e60e90c35b9e6ccb130be2d75b71f8c17", size = 45982, upload-time = "2025-06-30T15:52:13.6Z" }, - { url = "https://files.pythonhosted.org/packages/b8/95/8c825bd70ff9b02462dc18d1295dd08d3e9e4eb66856d292ffa62cfe1920/multidict-6.6.3-cp312-cp312-win_arm64.whl", hash = "sha256:0f1130b896ecb52d2a1e615260f3ea2af55fa7dc3d7c3003ba0c3121a759b18b", size = 43210, upload-time = "2025-06-30T15:52:14.893Z" }, - { url = "https://files.pythonhosted.org/packages/52/1d/0bebcbbb4f000751fbd09957257903d6e002943fc668d841a4cf2fb7f872/multidict-6.6.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:540d3c06d48507357a7d57721e5094b4f7093399a0106c211f33540fdc374d55", size = 75843, upload-time = "2025-06-30T15:52:16.155Z" }, - { url = "https://files.pythonhosted.org/packages/07/8f/cbe241b0434cfe257f65c2b1bcf9e8d5fb52bc708c5061fb29b0fed22bdf/multidict-6.6.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9c19cea2a690f04247d43f366d03e4eb110a0dc4cd1bbeee4d445435428ed35b", size = 45053, upload-time = "2025-06-30T15:52:17.429Z" }, - { url = "https://files.pythonhosted.org/packages/32/d2/0b3b23f9dbad5b270b22a3ac3ea73ed0a50ef2d9a390447061178ed6bdb8/multidict-6.6.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7af039820cfd00effec86bda5d8debef711a3e86a1d3772e85bea0f243a4bd65", size = 43273, upload-time = "2025-06-30T15:52:19.346Z" }, - { url = "https://files.pythonhosted.org/packages/fd/fe/6eb68927e823999e3683bc49678eb20374ba9615097d085298fd5b386564/multidict-6.6.3-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:500b84f51654fdc3944e936f2922114349bf8fdcac77c3092b03449f0e5bc2b3", size = 237124, upload-time = "2025-06-30T15:52:20.773Z" }, - { url = "https://files.pythonhosted.org/packages/e7/ab/320d8507e7726c460cb77117848b3834ea0d59e769f36fdae495f7669929/multidict-6.6.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3fc723ab8a5c5ed6c50418e9bfcd8e6dceba6c271cee6728a10a4ed8561520c", size = 256892, upload-time = "2025-06-30T15:52:22.242Z" }, - { url = "https://files.pythonhosted.org/packages/76/60/38ee422db515ac69834e60142a1a69111ac96026e76e8e9aa347fd2e4591/multidict-6.6.3-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:94c47ea3ade005b5976789baaed66d4de4480d0a0bf31cef6edaa41c1e7b56a6", size = 240547, upload-time = "2025-06-30T15:52:23.736Z" }, - { url = "https://files.pythonhosted.org/packages/27/fb/905224fde2dff042b030c27ad95a7ae744325cf54b890b443d30a789b80e/multidict-6.6.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dbc7cf464cc6d67e83e136c9f55726da3a30176f020a36ead246eceed87f1cd8", size = 266223, upload-time = "2025-06-30T15:52:25.185Z" }, - { url = "https://files.pythonhosted.org/packages/76/35/dc38ab361051beae08d1a53965e3e1a418752fc5be4d3fb983c5582d8784/multidict-6.6.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:900eb9f9da25ada070f8ee4a23f884e0ee66fe4e1a38c3af644256a508ad81ca", size = 267262, upload-time = "2025-06-30T15:52:26.969Z" }, - { url = "https://files.pythonhosted.org/packages/1f/a3/0a485b7f36e422421b17e2bbb5a81c1af10eac1d4476f2ff92927c730479/multidict-6.6.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c6df517cf177da5d47ab15407143a89cd1a23f8b335f3a28d57e8b0a3dbb884", size = 254345, upload-time = "2025-06-30T15:52:28.467Z" }, - { url = "https://files.pythonhosted.org/packages/b4/59/bcdd52c1dab7c0e0d75ff19cac751fbd5f850d1fc39172ce809a74aa9ea4/multidict-6.6.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4ef421045f13879e21c994b36e728d8e7d126c91a64b9185810ab51d474f27e7", size = 252248, upload-time = "2025-06-30T15:52:29.938Z" }, - { url = "https://files.pythonhosted.org/packages/bb/a4/2d96aaa6eae8067ce108d4acee6f45ced5728beda55c0f02ae1072c730d1/multidict-6.6.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:6c1e61bb4f80895c081790b6b09fa49e13566df8fbff817da3f85b3a8192e36b", size = 250115, upload-time = "2025-06-30T15:52:31.416Z" }, - { url = "https://files.pythonhosted.org/packages/25/d2/ed9f847fa5c7d0677d4f02ea2c163d5e48573de3f57bacf5670e43a5ffaa/multidict-6.6.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e5e8523bb12d7623cd8300dbd91b9e439a46a028cd078ca695eb66ba31adee3c", size = 249649, upload-time = "2025-06-30T15:52:32.996Z" }, - { url = "https://files.pythonhosted.org/packages/1f/af/9155850372563fc550803d3f25373308aa70f59b52cff25854086ecb4a79/multidict-6.6.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:ef58340cc896219e4e653dade08fea5c55c6df41bcc68122e3be3e9d873d9a7b", size = 261203, upload-time = "2025-06-30T15:52:34.521Z" }, - { url = "https://files.pythonhosted.org/packages/36/2f/c6a728f699896252cf309769089568a33c6439626648843f78743660709d/multidict-6.6.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fc9dc435ec8699e7b602b94fe0cd4703e69273a01cbc34409af29e7820f777f1", size = 258051, upload-time = "2025-06-30T15:52:35.999Z" }, - { url = "https://files.pythonhosted.org/packages/d0/60/689880776d6b18fa2b70f6cc74ff87dd6c6b9b47bd9cf74c16fecfaa6ad9/multidict-6.6.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9e864486ef4ab07db5e9cb997bad2b681514158d6954dd1958dfb163b83d53e6", size = 249601, upload-time = "2025-06-30T15:52:37.473Z" }, - { url = "https://files.pythonhosted.org/packages/75/5e/325b11f2222a549019cf2ef879c1f81f94a0d40ace3ef55cf529915ba6cc/multidict-6.6.3-cp313-cp313-win32.whl", hash = "sha256:5633a82fba8e841bc5c5c06b16e21529573cd654f67fd833650a215520a6210e", size = 41683, upload-time = "2025-06-30T15:52:38.927Z" }, - { url = "https://files.pythonhosted.org/packages/b1/ad/cf46e73f5d6e3c775cabd2a05976547f3f18b39bee06260369a42501f053/multidict-6.6.3-cp313-cp313-win_amd64.whl", hash = "sha256:e93089c1570a4ad54c3714a12c2cef549dc9d58e97bcded193d928649cab78e9", size = 45811, upload-time = "2025-06-30T15:52:40.207Z" }, - { url = "https://files.pythonhosted.org/packages/c5/c9/2e3fe950db28fb7c62e1a5f46e1e38759b072e2089209bc033c2798bb5ec/multidict-6.6.3-cp313-cp313-win_arm64.whl", hash = "sha256:c60b401f192e79caec61f166da9c924e9f8bc65548d4246842df91651e83d600", size = 43056, upload-time = "2025-06-30T15:52:41.575Z" }, - { url = "https://files.pythonhosted.org/packages/3a/58/aaf8114cf34966e084a8cc9517771288adb53465188843d5a19862cb6dc3/multidict-6.6.3-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:02fd8f32d403a6ff13864b0851f1f523d4c988051eea0471d4f1fd8010f11134", size = 82811, upload-time = "2025-06-30T15:52:43.281Z" }, - { url = "https://files.pythonhosted.org/packages/71/af/5402e7b58a1f5b987a07ad98f2501fdba2a4f4b4c30cf114e3ce8db64c87/multidict-6.6.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f3aa090106b1543f3f87b2041eef3c156c8da2aed90c63a2fbed62d875c49c37", size = 48304, upload-time = "2025-06-30T15:52:45.026Z" }, - { url = "https://files.pythonhosted.org/packages/39/65/ab3c8cafe21adb45b24a50266fd747147dec7847425bc2a0f6934b3ae9ce/multidict-6.6.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e924fb978615a5e33ff644cc42e6aa241effcf4f3322c09d4f8cebde95aff5f8", size = 46775, upload-time = "2025-06-30T15:52:46.459Z" }, - { url = "https://files.pythonhosted.org/packages/49/ba/9fcc1b332f67cc0c0c8079e263bfab6660f87fe4e28a35921771ff3eea0d/multidict-6.6.3-cp313-cp313t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:b9fe5a0e57c6dbd0e2ce81ca66272282c32cd11d31658ee9553849d91289e1c1", size = 229773, upload-time = "2025-06-30T15:52:47.88Z" }, - { url = "https://files.pythonhosted.org/packages/a4/14/0145a251f555f7c754ce2dcbcd012939bbd1f34f066fa5d28a50e722a054/multidict-6.6.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b24576f208793ebae00280c59927c3b7c2a3b1655e443a25f753c4611bc1c373", size = 250083, upload-time = "2025-06-30T15:52:49.366Z" }, - { url = "https://files.pythonhosted.org/packages/9e/d4/d5c0bd2bbb173b586c249a151a26d2fb3ec7d53c96e42091c9fef4e1f10c/multidict-6.6.3-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:135631cb6c58eac37d7ac0df380294fecdc026b28837fa07c02e459c7fb9c54e", size = 228980, upload-time = "2025-06-30T15:52:50.903Z" }, - { url = "https://files.pythonhosted.org/packages/21/32/c9a2d8444a50ec48c4733ccc67254100c10e1c8ae8e40c7a2d2183b59b97/multidict-6.6.3-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:274d416b0df887aef98f19f21578653982cfb8a05b4e187d4a17103322eeaf8f", size = 257776, upload-time = "2025-06-30T15:52:52.764Z" }, - { url = "https://files.pythonhosted.org/packages/68/d0/14fa1699f4ef629eae08ad6201c6b476098f5efb051b296f4c26be7a9fdf/multidict-6.6.3-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e252017a817fad7ce05cafbe5711ed40faeb580e63b16755a3a24e66fa1d87c0", size = 256882, upload-time = "2025-06-30T15:52:54.596Z" }, - { url = "https://files.pythonhosted.org/packages/da/88/84a27570fbe303c65607d517a5f147cd2fc046c2d1da02b84b17b9bdc2aa/multidict-6.6.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e4cc8d848cd4fe1cdee28c13ea79ab0ed37fc2e89dd77bac86a2e7959a8c3bc", size = 247816, upload-time = "2025-06-30T15:52:56.175Z" }, - { url = "https://files.pythonhosted.org/packages/1c/60/dca352a0c999ce96a5d8b8ee0b2b9f729dcad2e0b0c195f8286269a2074c/multidict-6.6.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9e236a7094b9c4c1b7585f6b9cca34b9d833cf079f7e4c49e6a4a6ec9bfdc68f", size = 245341, upload-time = "2025-06-30T15:52:57.752Z" }, - { url = "https://files.pythonhosted.org/packages/50/ef/433fa3ed06028f03946f3993223dada70fb700f763f70c00079533c34578/multidict-6.6.3-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:e0cb0ab69915c55627c933f0b555a943d98ba71b4d1c57bc0d0a66e2567c7471", size = 235854, upload-time = "2025-06-30T15:52:59.74Z" }, - { url = "https://files.pythonhosted.org/packages/1b/1f/487612ab56fbe35715320905215a57fede20de7db40a261759690dc80471/multidict-6.6.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:81ef2f64593aba09c5212a3d0f8c906a0d38d710a011f2f42759704d4557d3f2", size = 243432, upload-time = "2025-06-30T15:53:01.602Z" }, - { url = "https://files.pythonhosted.org/packages/da/6f/ce8b79de16cd885c6f9052c96a3671373d00c59b3ee635ea93e6e81b8ccf/multidict-6.6.3-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:b9cbc60010de3562545fa198bfc6d3825df430ea96d2cc509c39bd71e2e7d648", size = 252731, upload-time = "2025-06-30T15:53:03.517Z" }, - { url = "https://files.pythonhosted.org/packages/bb/fe/a2514a6aba78e5abefa1624ca85ae18f542d95ac5cde2e3815a9fbf369aa/multidict-6.6.3-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:70d974eaaa37211390cd02ef93b7e938de564bbffa866f0b08d07e5e65da783d", size = 247086, upload-time = "2025-06-30T15:53:05.48Z" }, - { url = "https://files.pythonhosted.org/packages/8c/22/b788718d63bb3cce752d107a57c85fcd1a212c6c778628567c9713f9345a/multidict-6.6.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3713303e4a6663c6d01d648a68f2848701001f3390a030edaaf3fc949c90bf7c", size = 243338, upload-time = "2025-06-30T15:53:07.522Z" }, - { url = "https://files.pythonhosted.org/packages/22/d6/fdb3d0670819f2228f3f7d9af613d5e652c15d170c83e5f1c94fbc55a25b/multidict-6.6.3-cp313-cp313t-win32.whl", hash = "sha256:639ecc9fe7cd73f2495f62c213e964843826f44505a3e5d82805aa85cac6f89e", size = 47812, upload-time = "2025-06-30T15:53:09.263Z" }, - { url = "https://files.pythonhosted.org/packages/b6/d6/a9d2c808f2c489ad199723197419207ecbfbc1776f6e155e1ecea9c883aa/multidict-6.6.3-cp313-cp313t-win_amd64.whl", hash = "sha256:9f97e181f344a0ef3881b573d31de8542cc0dbc559ec68c8f8b5ce2c2e91646d", size = 53011, upload-time = "2025-06-30T15:53:11.038Z" }, - { url = "https://files.pythonhosted.org/packages/f2/40/b68001cba8188dd267590a111f9661b6256debc327137667e832bf5d66e8/multidict-6.6.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ce8b7693da41a3c4fde5871c738a81490cea5496c671d74374c8ab889e1834fb", size = 45254, upload-time = "2025-06-30T15:53:12.421Z" }, - { url = "https://files.pythonhosted.org/packages/d8/30/9aec301e9772b098c1f5c0ca0279237c9766d94b97802e9888010c64b0ed/multidict-6.6.3-py3-none-any.whl", hash = "sha256:8db10f29c7541fc5da4defd8cd697e1ca429db743fa716325f236079b96f775a", size = 12313, upload-time = "2025-06-30T15:53:45.437Z" }, +version = "6.6.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/69/7f/0652e6ed47ab288e3756ea9c0df8b14950781184d4bd7883f4d87dd41245/multidict-6.6.4.tar.gz", hash = "sha256:d2d4e4787672911b48350df02ed3fa3fffdc2f2e8ca06dd6afdf34189b76a9dd", size = 101843, upload-time = "2025-08-11T12:08:48.217Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6b/7f/90a7f01e2d005d6653c689039977f6856718c75c5579445effb7e60923d1/multidict-6.6.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c7a0e9b561e6460484318a7612e725df1145d46b0ef57c6b9866441bf6e27e0c", size = 76472, upload-time = "2025-08-11T12:06:29.006Z" }, + { url = "https://files.pythonhosted.org/packages/54/a3/bed07bc9e2bb302ce752f1dabc69e884cd6a676da44fb0e501b246031fdd/multidict-6.6.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6bf2f10f70acc7a2446965ffbc726e5fc0b272c97a90b485857e5c70022213eb", size = 44634, upload-time = "2025-08-11T12:06:30.374Z" }, + { url = "https://files.pythonhosted.org/packages/a7/4b/ceeb4f8f33cf81277da464307afeaf164fb0297947642585884f5cad4f28/multidict-6.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66247d72ed62d5dd29752ffc1d3b88f135c6a8de8b5f63b7c14e973ef5bda19e", size = 44282, upload-time = "2025-08-11T12:06:31.958Z" }, + { url = "https://files.pythonhosted.org/packages/03/35/436a5da8702b06866189b69f655ffdb8f70796252a8772a77815f1812679/multidict-6.6.4-cp311-cp311-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:105245cc6b76f51e408451a844a54e6823bbd5a490ebfe5bdfc79798511ceded", size = 229696, upload-time = "2025-08-11T12:06:33.087Z" }, + { url = "https://files.pythonhosted.org/packages/b6/0e/915160be8fecf1fca35f790c08fb74ca684d752fcba62c11daaf3d92c216/multidict-6.6.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cbbc54e58b34c3bae389ef00046be0961f30fef7cb0dd9c7756aee376a4f7683", size = 246665, upload-time = "2025-08-11T12:06:34.448Z" }, + { url = "https://files.pythonhosted.org/packages/08/ee/2f464330acd83f77dcc346f0b1a0eaae10230291450887f96b204b8ac4d3/multidict-6.6.4-cp311-cp311-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:56c6b3652f945c9bc3ac6c8178cd93132b8d82dd581fcbc3a00676c51302bc1a", size = 225485, upload-time = "2025-08-11T12:06:35.672Z" }, + { url = "https://files.pythonhosted.org/packages/71/cc/9a117f828b4d7fbaec6adeed2204f211e9caf0a012692a1ee32169f846ae/multidict-6.6.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b95494daf857602eccf4c18ca33337dd2be705bccdb6dddbfc9d513e6addb9d9", size = 257318, upload-time = "2025-08-11T12:06:36.98Z" }, + { url = "https://files.pythonhosted.org/packages/25/77/62752d3dbd70e27fdd68e86626c1ae6bccfebe2bb1f84ae226363e112f5a/multidict-6.6.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:e5b1413361cef15340ab9dc61523e653d25723e82d488ef7d60a12878227ed50", size = 254689, upload-time = "2025-08-11T12:06:38.233Z" }, + { url = "https://files.pythonhosted.org/packages/00/6e/fac58b1072a6fc59af5e7acb245e8754d3e1f97f4f808a6559951f72a0d4/multidict-6.6.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e167bf899c3d724f9662ef00b4f7fef87a19c22b2fead198a6f68b263618df52", size = 246709, upload-time = "2025-08-11T12:06:39.517Z" }, + { url = "https://files.pythonhosted.org/packages/01/ef/4698d6842ef5e797c6db7744b0081e36fb5de3d00002cc4c58071097fac3/multidict-6.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aaea28ba20a9026dfa77f4b80369e51cb767c61e33a2d4043399c67bd95fb7c6", size = 243185, upload-time = "2025-08-11T12:06:40.796Z" }, + { url = "https://files.pythonhosted.org/packages/aa/c9/d82e95ae1d6e4ef396934e9b0e942dfc428775f9554acf04393cce66b157/multidict-6.6.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8c91cdb30809a96d9ecf442ec9bc45e8cfaa0f7f8bdf534e082c2443a196727e", size = 237838, upload-time = "2025-08-11T12:06:42.595Z" }, + { url = "https://files.pythonhosted.org/packages/57/cf/f94af5c36baaa75d44fab9f02e2a6bcfa0cd90acb44d4976a80960759dbc/multidict-6.6.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:1a0ccbfe93ca114c5d65a2471d52d8829e56d467c97b0e341cf5ee45410033b3", size = 246368, upload-time = "2025-08-11T12:06:44.304Z" }, + { url = "https://files.pythonhosted.org/packages/4a/fe/29f23460c3d995f6a4b678cb2e9730e7277231b981f0b234702f0177818a/multidict-6.6.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:55624b3f321d84c403cb7d8e6e982f41ae233d85f85db54ba6286f7295dc8a9c", size = 253339, upload-time = "2025-08-11T12:06:45.597Z" }, + { url = "https://files.pythonhosted.org/packages/29/b6/fd59449204426187b82bf8a75f629310f68c6adc9559dc922d5abe34797b/multidict-6.6.4-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:4a1fb393a2c9d202cb766c76208bd7945bc194eba8ac920ce98c6e458f0b524b", size = 246933, upload-time = "2025-08-11T12:06:46.841Z" }, + { url = "https://files.pythonhosted.org/packages/19/52/d5d6b344f176a5ac3606f7a61fb44dc746e04550e1a13834dff722b8d7d6/multidict-6.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:43868297a5759a845fa3a483fb4392973a95fb1de891605a3728130c52b8f40f", size = 242225, upload-time = "2025-08-11T12:06:48.588Z" }, + { url = "https://files.pythonhosted.org/packages/ec/d3/5b2281ed89ff4d5318d82478a2a2450fcdfc3300da48ff15c1778280ad26/multidict-6.6.4-cp311-cp311-win32.whl", hash = "sha256:ed3b94c5e362a8a84d69642dbeac615452e8af9b8eb825b7bc9f31a53a1051e2", size = 41306, upload-time = "2025-08-11T12:06:49.95Z" }, + { url = "https://files.pythonhosted.org/packages/74/7d/36b045c23a1ab98507aefd44fd8b264ee1dd5e5010543c6fccf82141ccef/multidict-6.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:d8c112f7a90d8ca5d20213aa41eac690bb50a76da153e3afb3886418e61cb22e", size = 46029, upload-time = "2025-08-11T12:06:51.082Z" }, + { url = "https://files.pythonhosted.org/packages/0f/5e/553d67d24432c5cd52b49047f2d248821843743ee6d29a704594f656d182/multidict-6.6.4-cp311-cp311-win_arm64.whl", hash = "sha256:3bb0eae408fa1996d87247ca0d6a57b7fc1dcf83e8a5c47ab82c558c250d4adf", size = 43017, upload-time = "2025-08-11T12:06:52.243Z" }, + { url = "https://files.pythonhosted.org/packages/05/f6/512ffd8fd8b37fb2680e5ac35d788f1d71bbaf37789d21a820bdc441e565/multidict-6.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0ffb87be160942d56d7b87b0fdf098e81ed565add09eaa1294268c7f3caac4c8", size = 76516, upload-time = "2025-08-11T12:06:53.393Z" }, + { url = "https://files.pythonhosted.org/packages/99/58/45c3e75deb8855c36bd66cc1658007589662ba584dbf423d01df478dd1c5/multidict-6.6.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d191de6cbab2aff5de6c5723101705fd044b3e4c7cfd587a1929b5028b9714b3", size = 45394, upload-time = "2025-08-11T12:06:54.555Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ca/e8c4472a93a26e4507c0b8e1f0762c0d8a32de1328ef72fd704ef9cc5447/multidict-6.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:38a0956dd92d918ad5feff3db8fcb4a5eb7dba114da917e1a88475619781b57b", size = 43591, upload-time = "2025-08-11T12:06:55.672Z" }, + { url = "https://files.pythonhosted.org/packages/05/51/edf414f4df058574a7265034d04c935aa84a89e79ce90fcf4df211f47b16/multidict-6.6.4-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:6865f6d3b7900ae020b495d599fcf3765653bc927951c1abb959017f81ae8287", size = 237215, upload-time = "2025-08-11T12:06:57.213Z" }, + { url = "https://files.pythonhosted.org/packages/c8/45/8b3d6dbad8cf3252553cc41abea09ad527b33ce47a5e199072620b296902/multidict-6.6.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a2088c126b6f72db6c9212ad827d0ba088c01d951cee25e758c450da732c138", size = 258299, upload-time = "2025-08-11T12:06:58.946Z" }, + { url = "https://files.pythonhosted.org/packages/3c/e8/8ca2e9a9f5a435fc6db40438a55730a4bf4956b554e487fa1b9ae920f825/multidict-6.6.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0f37bed7319b848097085d7d48116f545985db988e2256b2e6f00563a3416ee6", size = 242357, upload-time = "2025-08-11T12:07:00.301Z" }, + { url = "https://files.pythonhosted.org/packages/0f/84/80c77c99df05a75c28490b2af8f7cba2a12621186e0a8b0865d8e745c104/multidict-6.6.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:01368e3c94032ba6ca0b78e7ccb099643466cf24f8dc8eefcfdc0571d56e58f9", size = 268369, upload-time = "2025-08-11T12:07:01.638Z" }, + { url = "https://files.pythonhosted.org/packages/0d/e9/920bfa46c27b05fb3e1ad85121fd49f441492dca2449c5bcfe42e4565d8a/multidict-6.6.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8fe323540c255db0bffee79ad7f048c909f2ab0edb87a597e1c17da6a54e493c", size = 269341, upload-time = "2025-08-11T12:07:02.943Z" }, + { url = "https://files.pythonhosted.org/packages/af/65/753a2d8b05daf496f4a9c367fe844e90a1b2cac78e2be2c844200d10cc4c/multidict-6.6.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8eb3025f17b0a4c3cd08cda49acf312a19ad6e8a4edd9dbd591e6506d999402", size = 256100, upload-time = "2025-08-11T12:07:04.564Z" }, + { url = "https://files.pythonhosted.org/packages/09/54/655be13ae324212bf0bc15d665a4e34844f34c206f78801be42f7a0a8aaa/multidict-6.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bbc14f0365534d35a06970d6a83478b249752e922d662dc24d489af1aa0d1be7", size = 253584, upload-time = "2025-08-11T12:07:05.914Z" }, + { url = "https://files.pythonhosted.org/packages/5c/74/ab2039ecc05264b5cec73eb018ce417af3ebb384ae9c0e9ed42cb33f8151/multidict-6.6.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:75aa52fba2d96bf972e85451b99d8e19cc37ce26fd016f6d4aa60da9ab2b005f", size = 251018, upload-time = "2025-08-11T12:07:08.301Z" }, + { url = "https://files.pythonhosted.org/packages/af/0a/ccbb244ac848e56c6427f2392741c06302bbfba49c0042f1eb3c5b606497/multidict-6.6.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:4fefd4a815e362d4f011919d97d7b4a1e566f1dde83dc4ad8cfb5b41de1df68d", size = 251477, upload-time = "2025-08-11T12:07:10.248Z" }, + { url = "https://files.pythonhosted.org/packages/0e/b0/0ed49bba775b135937f52fe13922bc64a7eaf0a3ead84a36e8e4e446e096/multidict-6.6.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:db9801fe021f59a5b375ab778973127ca0ac52429a26e2fd86aa9508f4d26eb7", size = 263575, upload-time = "2025-08-11T12:07:11.928Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d9/7fb85a85e14de2e44dfb6a24f03c41e2af8697a6df83daddb0e9b7569f73/multidict-6.6.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a650629970fa21ac1fb06ba25dabfc5b8a2054fcbf6ae97c758aa956b8dba802", size = 259649, upload-time = "2025-08-11T12:07:13.244Z" }, + { url = "https://files.pythonhosted.org/packages/03/9e/b3a459bcf9b6e74fa461a5222a10ff9b544cb1cd52fd482fb1b75ecda2a2/multidict-6.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:452ff5da78d4720d7516a3a2abd804957532dd69296cb77319c193e3ffb87e24", size = 251505, upload-time = "2025-08-11T12:07:14.57Z" }, + { url = "https://files.pythonhosted.org/packages/86/a2/8022f78f041dfe6d71e364001a5cf987c30edfc83c8a5fb7a3f0974cff39/multidict-6.6.4-cp312-cp312-win32.whl", hash = "sha256:8c2fcb12136530ed19572bbba61b407f655e3953ba669b96a35036a11a485793", size = 41888, upload-time = "2025-08-11T12:07:15.904Z" }, + { url = "https://files.pythonhosted.org/packages/c7/eb/d88b1780d43a56db2cba24289fa744a9d216c1a8546a0dc3956563fd53ea/multidict-6.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:047d9425860a8c9544fed1b9584f0c8bcd31bcde9568b047c5e567a1025ecd6e", size = 46072, upload-time = "2025-08-11T12:07:17.045Z" }, + { url = "https://files.pythonhosted.org/packages/9f/16/b929320bf5750e2d9d4931835a4c638a19d2494a5b519caaaa7492ebe105/multidict-6.6.4-cp312-cp312-win_arm64.whl", hash = "sha256:14754eb72feaa1e8ae528468f24250dd997b8e2188c3d2f593f9eba259e4b364", size = 43222, upload-time = "2025-08-11T12:07:18.328Z" }, + { url = "https://files.pythonhosted.org/packages/3a/5d/e1db626f64f60008320aab00fbe4f23fc3300d75892a3381275b3d284580/multidict-6.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f46a6e8597f9bd71b31cc708195d42b634c8527fecbcf93febf1052cacc1f16e", size = 75848, upload-time = "2025-08-11T12:07:19.912Z" }, + { url = "https://files.pythonhosted.org/packages/4c/aa/8b6f548d839b6c13887253af4e29c939af22a18591bfb5d0ee6f1931dae8/multidict-6.6.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:22e38b2bc176c5eb9c0a0e379f9d188ae4cd8b28c0f53b52bce7ab0a9e534657", size = 45060, upload-time = "2025-08-11T12:07:21.163Z" }, + { url = "https://files.pythonhosted.org/packages/eb/c6/f5e97e5d99a729bc2aa58eb3ebfa9f1e56a9b517cc38c60537c81834a73f/multidict-6.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5df8afd26f162da59e218ac0eefaa01b01b2e6cd606cffa46608f699539246da", size = 43269, upload-time = "2025-08-11T12:07:22.392Z" }, + { url = "https://files.pythonhosted.org/packages/dc/31/d54eb0c62516776f36fe67f84a732f97e0b0e12f98d5685bebcc6d396910/multidict-6.6.4-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:49517449b58d043023720aa58e62b2f74ce9b28f740a0b5d33971149553d72aa", size = 237158, upload-time = "2025-08-11T12:07:23.636Z" }, + { url = "https://files.pythonhosted.org/packages/c4/1c/8a10c1c25b23156e63b12165a929d8eb49a6ed769fdbefb06e6f07c1e50d/multidict-6.6.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae9408439537c5afdca05edd128a63f56a62680f4b3c234301055d7a2000220f", size = 257076, upload-time = "2025-08-11T12:07:25.049Z" }, + { url = "https://files.pythonhosted.org/packages/ad/86/90e20b5771d6805a119e483fd3d1e8393e745a11511aebca41f0da38c3e2/multidict-6.6.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:87a32d20759dc52a9e850fe1061b6e41ab28e2998d44168a8a341b99ded1dba0", size = 240694, upload-time = "2025-08-11T12:07:26.458Z" }, + { url = "https://files.pythonhosted.org/packages/e7/49/484d3e6b535bc0555b52a0a26ba86e4d8d03fd5587d4936dc59ba7583221/multidict-6.6.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:52e3c8d43cdfff587ceedce9deb25e6ae77daba560b626e97a56ddcad3756879", size = 266350, upload-time = "2025-08-11T12:07:27.94Z" }, + { url = "https://files.pythonhosted.org/packages/bf/b4/aa4c5c379b11895083d50021e229e90c408d7d875471cb3abf721e4670d6/multidict-6.6.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ad8850921d3a8d8ff6fbef790e773cecfc260bbfa0566998980d3fa8f520bc4a", size = 267250, upload-time = "2025-08-11T12:07:29.303Z" }, + { url = "https://files.pythonhosted.org/packages/80/e5/5e22c5bf96a64bdd43518b1834c6d95a4922cc2066b7d8e467dae9b6cee6/multidict-6.6.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:497a2954adc25c08daff36f795077f63ad33e13f19bfff7736e72c785391534f", size = 254900, upload-time = "2025-08-11T12:07:30.764Z" }, + { url = "https://files.pythonhosted.org/packages/17/38/58b27fed927c07035abc02befacab42491e7388ca105e087e6e0215ead64/multidict-6.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:024ce601f92d780ca1617ad4be5ac15b501cc2414970ffa2bb2bbc2bd5a68fa5", size = 252355, upload-time = "2025-08-11T12:07:32.205Z" }, + { url = "https://files.pythonhosted.org/packages/d0/a1/dad75d23a90c29c02b5d6f3d7c10ab36c3197613be5d07ec49c7791e186c/multidict-6.6.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a693fc5ed9bdd1c9e898013e0da4dcc640de7963a371c0bd458e50e046bf6438", size = 250061, upload-time = "2025-08-11T12:07:33.623Z" }, + { url = "https://files.pythonhosted.org/packages/b8/1a/ac2216b61c7f116edab6dc3378cca6c70dc019c9a457ff0d754067c58b20/multidict-6.6.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:190766dac95aab54cae5b152a56520fd99298f32a1266d66d27fdd1b5ac00f4e", size = 249675, upload-time = "2025-08-11T12:07:34.958Z" }, + { url = "https://files.pythonhosted.org/packages/d4/79/1916af833b800d13883e452e8e0977c065c4ee3ab7a26941fbfdebc11895/multidict-6.6.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:34d8f2a5ffdceab9dcd97c7a016deb2308531d5f0fced2bb0c9e1df45b3363d7", size = 261247, upload-time = "2025-08-11T12:07:36.588Z" }, + { url = "https://files.pythonhosted.org/packages/c5/65/d1f84fe08ac44a5fc7391cbc20a7cedc433ea616b266284413fd86062f8c/multidict-6.6.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:59e8d40ab1f5a8597abcef00d04845155a5693b5da00d2c93dbe88f2050f2812", size = 257960, upload-time = "2025-08-11T12:07:39.735Z" }, + { url = "https://files.pythonhosted.org/packages/13/b5/29ec78057d377b195ac2c5248c773703a6b602e132a763e20ec0457e7440/multidict-6.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:467fe64138cfac771f0e949b938c2e1ada2b5af22f39692aa9258715e9ea613a", size = 250078, upload-time = "2025-08-11T12:07:41.525Z" }, + { url = "https://files.pythonhosted.org/packages/c4/0e/7e79d38f70a872cae32e29b0d77024bef7834b0afb406ddae6558d9e2414/multidict-6.6.4-cp313-cp313-win32.whl", hash = "sha256:14616a30fe6d0a48d0a48d1a633ab3b8bec4cf293aac65f32ed116f620adfd69", size = 41708, upload-time = "2025-08-11T12:07:43.405Z" }, + { url = "https://files.pythonhosted.org/packages/9d/34/746696dffff742e97cd6a23da953e55d0ea51fa601fa2ff387b3edcfaa2c/multidict-6.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:40cd05eaeb39e2bc8939451f033e57feaa2ac99e07dbca8afe2be450a4a3b6cf", size = 45912, upload-time = "2025-08-11T12:07:45.082Z" }, + { url = "https://files.pythonhosted.org/packages/c7/87/3bac136181e271e29170d8d71929cdeddeb77f3e8b6a0c08da3a8e9da114/multidict-6.6.4-cp313-cp313-win_arm64.whl", hash = "sha256:f6eb37d511bfae9e13e82cb4d1af36b91150466f24d9b2b8a9785816deb16605", size = 43076, upload-time = "2025-08-11T12:07:46.746Z" }, + { url = "https://files.pythonhosted.org/packages/64/94/0a8e63e36c049b571c9ae41ee301ada29c3fee9643d9c2548d7d558a1d99/multidict-6.6.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:6c84378acd4f37d1b507dfa0d459b449e2321b3ba5f2338f9b085cf7a7ba95eb", size = 82812, upload-time = "2025-08-11T12:07:48.402Z" }, + { url = "https://files.pythonhosted.org/packages/25/1a/be8e369dfcd260d2070a67e65dd3990dd635cbd735b98da31e00ea84cd4e/multidict-6.6.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0e0558693063c75f3d952abf645c78f3c5dfdd825a41d8c4d8156fc0b0da6e7e", size = 48313, upload-time = "2025-08-11T12:07:49.679Z" }, + { url = "https://files.pythonhosted.org/packages/26/5a/dd4ade298674b2f9a7b06a32c94ffbc0497354df8285f27317c66433ce3b/multidict-6.6.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3f8e2384cb83ebd23fd07e9eada8ba64afc4c759cd94817433ab8c81ee4b403f", size = 46777, upload-time = "2025-08-11T12:07:51.318Z" }, + { url = "https://files.pythonhosted.org/packages/89/db/98aa28bc7e071bfba611ac2ae803c24e96dd3a452b4118c587d3d872c64c/multidict-6.6.4-cp313-cp313t-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:f996b87b420995a9174b2a7c1a8daf7db4750be6848b03eb5e639674f7963773", size = 229321, upload-time = "2025-08-11T12:07:52.965Z" }, + { url = "https://files.pythonhosted.org/packages/c7/bc/01ddda2a73dd9d167bd85d0e8ef4293836a8f82b786c63fb1a429bc3e678/multidict-6.6.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc356250cffd6e78416cf5b40dc6a74f1edf3be8e834cf8862d9ed5265cf9b0e", size = 249954, upload-time = "2025-08-11T12:07:54.423Z" }, + { url = "https://files.pythonhosted.org/packages/06/78/6b7c0f020f9aa0acf66d0ab4eb9f08375bac9a50ff5e3edb1c4ccd59eafc/multidict-6.6.4-cp313-cp313t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:dadf95aa862714ea468a49ad1e09fe00fcc9ec67d122f6596a8d40caf6cec7d0", size = 228612, upload-time = "2025-08-11T12:07:55.914Z" }, + { url = "https://files.pythonhosted.org/packages/00/44/3faa416f89b2d5d76e9d447296a81521e1c832ad6e40b92f990697b43192/multidict-6.6.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7dd57515bebffd8ebd714d101d4c434063322e4fe24042e90ced41f18b6d3395", size = 257528, upload-time = "2025-08-11T12:07:57.371Z" }, + { url = "https://files.pythonhosted.org/packages/05/5f/77c03b89af0fcb16f018f668207768191fb9dcfb5e3361a5e706a11db2c9/multidict-6.6.4-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:967af5f238ebc2eb1da4e77af5492219fbd9b4b812347da39a7b5f5c72c0fa45", size = 256329, upload-time = "2025-08-11T12:07:58.844Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e9/ed750a2a9afb4f8dc6f13dc5b67b514832101b95714f1211cd42e0aafc26/multidict-6.6.4-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2a4c6875c37aae9794308ec43e3530e4aa0d36579ce38d89979bbf89582002bb", size = 247928, upload-time = "2025-08-11T12:08:01.037Z" }, + { url = "https://files.pythonhosted.org/packages/1f/b5/e0571bc13cda277db7e6e8a532791d4403dacc9850006cb66d2556e649c0/multidict-6.6.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:7f683a551e92bdb7fac545b9c6f9fa2aebdeefa61d607510b3533286fcab67f5", size = 245228, upload-time = "2025-08-11T12:08:02.96Z" }, + { url = "https://files.pythonhosted.org/packages/f3/a3/69a84b0eccb9824491f06368f5b86e72e4af54c3067c37c39099b6687109/multidict-6.6.4-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:3ba5aaf600edaf2a868a391779f7a85d93bed147854925f34edd24cc70a3e141", size = 235869, upload-time = "2025-08-11T12:08:04.746Z" }, + { url = "https://files.pythonhosted.org/packages/a9/9d/28802e8f9121a6a0804fa009debf4e753d0a59969ea9f70be5f5fdfcb18f/multidict-6.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:580b643b7fd2c295d83cad90d78419081f53fd532d1f1eb67ceb7060f61cff0d", size = 243446, upload-time = "2025-08-11T12:08:06.332Z" }, + { url = "https://files.pythonhosted.org/packages/38/ea/6c98add069b4878c1d66428a5f5149ddb6d32b1f9836a826ac764b9940be/multidict-6.6.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:37b7187197da6af3ee0b044dbc9625afd0c885f2800815b228a0e70f9a7f473d", size = 252299, upload-time = "2025-08-11T12:08:07.931Z" }, + { url = "https://files.pythonhosted.org/packages/3a/09/8fe02d204473e14c0af3affd50af9078839dfca1742f025cca765435d6b4/multidict-6.6.4-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e1b93790ed0bc26feb72e2f08299691ceb6da5e9e14a0d13cc74f1869af327a0", size = 246926, upload-time = "2025-08-11T12:08:09.467Z" }, + { url = "https://files.pythonhosted.org/packages/37/3d/7b1e10d774a6df5175ecd3c92bff069e77bed9ec2a927fdd4ff5fe182f67/multidict-6.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a506a77ddee1efcca81ecbeae27ade3e09cdf21a8ae854d766c2bb4f14053f92", size = 243383, upload-time = "2025-08-11T12:08:10.981Z" }, + { url = "https://files.pythonhosted.org/packages/50/b0/a6fae46071b645ae98786ab738447de1ef53742eaad949f27e960864bb49/multidict-6.6.4-cp313-cp313t-win32.whl", hash = "sha256:f93b2b2279883d1d0a9e1bd01f312d6fc315c5e4c1f09e112e4736e2f650bc4e", size = 47775, upload-time = "2025-08-11T12:08:12.439Z" }, + { url = "https://files.pythonhosted.org/packages/b2/0a/2436550b1520091af0600dff547913cb2d66fbac27a8c33bc1b1bccd8d98/multidict-6.6.4-cp313-cp313t-win_amd64.whl", hash = "sha256:6d46a180acdf6e87cc41dc15d8f5c2986e1e8739dc25dbb7dac826731ef381a4", size = 53100, upload-time = "2025-08-11T12:08:13.823Z" }, + { url = "https://files.pythonhosted.org/packages/97/ea/43ac51faff934086db9c072a94d327d71b7d8b40cd5dcb47311330929ef0/multidict-6.6.4-cp313-cp313t-win_arm64.whl", hash = "sha256:756989334015e3335d087a27331659820d53ba432befdef6a718398b0a8493ad", size = 45501, upload-time = "2025-08-11T12:08:15.173Z" }, + { url = "https://files.pythonhosted.org/packages/fd/69/b547032297c7e63ba2af494edba695d781af8a0c6e89e4d06cf848b21d80/multidict-6.6.4-py3-none-any.whl", hash = "sha256:27d8f8e125c07cb954e54d75d04905a9bba8a439c1d84aca94949d4d03d8601c", size = 12313, upload-time = "2025-08-11T12:08:46.891Z" }, ] [[package]] @@ -2116,34 +1824,52 @@ wheels = [ [[package]] name = "mypy" -version = "1.17.0" +version = "1.17.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mypy-extensions" }, { name = "pathspec" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1e/e3/034322d5a779685218ed69286c32faa505247f1f096251ef66c8fd203b08/mypy-1.17.0.tar.gz", hash = "sha256:e5d7ccc08ba089c06e2f5629c660388ef1fee708444f1dee0b9203fa031dee03", size = 3352114, upload-time = "2025-07-14T20:34:30.181Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/24/82efb502b0b0f661c49aa21cfe3e1999ddf64bf5500fc03b5a1536a39d39/mypy-1.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9d4fe5c72fd262d9c2c91c1117d16aac555e05f5beb2bae6a755274c6eec42be", size = 10914150, upload-time = "2025-07-14T20:31:51.985Z" }, - { url = "https://files.pythonhosted.org/packages/03/96/8ef9a6ff8cedadff4400e2254689ca1dc4b420b92c55255b44573de10c54/mypy-1.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d96b196e5c16f41b4f7736840e8455958e832871990c7ba26bf58175e357ed61", size = 10039845, upload-time = "2025-07-14T20:32:30.527Z" }, - { url = "https://files.pythonhosted.org/packages/df/32/7ce359a56be779d38021d07941cfbb099b41411d72d827230a36203dbb81/mypy-1.17.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:73a0ff2dd10337ceb521c080d4147755ee302dcde6e1a913babd59473904615f", size = 11837246, upload-time = "2025-07-14T20:32:01.28Z" }, - { url = "https://files.pythonhosted.org/packages/82/16/b775047054de4d8dbd668df9137707e54b07fe18c7923839cd1e524bf756/mypy-1.17.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:24cfcc1179c4447854e9e406d3af0f77736d631ec87d31c6281ecd5025df625d", size = 12571106, upload-time = "2025-07-14T20:34:26.942Z" }, - { url = "https://files.pythonhosted.org/packages/a1/cf/fa33eaf29a606102c8d9ffa45a386a04c2203d9ad18bf4eef3e20c43ebc8/mypy-1.17.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3c56f180ff6430e6373db7a1d569317675b0a451caf5fef6ce4ab365f5f2f6c3", size = 12759960, upload-time = "2025-07-14T20:33:42.882Z" }, - { url = "https://files.pythonhosted.org/packages/94/75/3f5a29209f27e739ca57e6350bc6b783a38c7621bdf9cac3ab8a08665801/mypy-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:eafaf8b9252734400f9b77df98b4eee3d2eecab16104680d51341c75702cad70", size = 9503888, upload-time = "2025-07-14T20:32:34.392Z" }, - { url = "https://files.pythonhosted.org/packages/12/e9/e6824ed620bbf51d3bf4d6cbbe4953e83eaf31a448d1b3cfb3620ccb641c/mypy-1.17.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f986f1cab8dbec39ba6e0eaa42d4d3ac6686516a5d3dccd64be095db05ebc6bb", size = 11086395, upload-time = "2025-07-14T20:34:11.452Z" }, - { url = "https://files.pythonhosted.org/packages/ba/51/a4afd1ae279707953be175d303f04a5a7bd7e28dc62463ad29c1c857927e/mypy-1.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:51e455a54d199dd6e931cd7ea987d061c2afbaf0960f7f66deef47c90d1b304d", size = 10120052, upload-time = "2025-07-14T20:33:09.897Z" }, - { url = "https://files.pythonhosted.org/packages/8a/71/19adfeac926ba8205f1d1466d0d360d07b46486bf64360c54cb5a2bd86a8/mypy-1.17.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3204d773bab5ff4ebbd1f8efa11b498027cd57017c003ae970f310e5b96be8d8", size = 11861806, upload-time = "2025-07-14T20:32:16.028Z" }, - { url = "https://files.pythonhosted.org/packages/0b/64/d6120eca3835baf7179e6797a0b61d6c47e0bc2324b1f6819d8428d5b9ba/mypy-1.17.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1051df7ec0886fa246a530ae917c473491e9a0ba6938cfd0ec2abc1076495c3e", size = 12744371, upload-time = "2025-07-14T20:33:33.503Z" }, - { url = "https://files.pythonhosted.org/packages/1f/dc/56f53b5255a166f5bd0f137eed960e5065f2744509dfe69474ff0ba772a5/mypy-1.17.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f773c6d14dcc108a5b141b4456b0871df638eb411a89cd1c0c001fc4a9d08fc8", size = 12914558, upload-time = "2025-07-14T20:33:56.961Z" }, - { url = "https://files.pythonhosted.org/packages/69/ac/070bad311171badc9add2910e7f89271695a25c136de24bbafc7eded56d5/mypy-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:1619a485fd0e9c959b943c7b519ed26b712de3002d7de43154a489a2d0fd817d", size = 9585447, upload-time = "2025-07-14T20:32:20.594Z" }, - { url = "https://files.pythonhosted.org/packages/be/7b/5f8ab461369b9e62157072156935cec9d272196556bdc7c2ff5f4c7c0f9b/mypy-1.17.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2c41aa59211e49d717d92b3bb1238c06d387c9325d3122085113c79118bebb06", size = 11070019, upload-time = "2025-07-14T20:32:07.99Z" }, - { url = "https://files.pythonhosted.org/packages/9c/f8/c49c9e5a2ac0badcc54beb24e774d2499748302c9568f7f09e8730e953fa/mypy-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0e69db1fb65b3114f98c753e3930a00514f5b68794ba80590eb02090d54a5d4a", size = 10114457, upload-time = "2025-07-14T20:33:47.285Z" }, - { url = "https://files.pythonhosted.org/packages/89/0c/fb3f9c939ad9beed3e328008b3fb90b20fda2cddc0f7e4c20dbefefc3b33/mypy-1.17.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:03ba330b76710f83d6ac500053f7727270b6b8553b0423348ffb3af6f2f7b889", size = 11857838, upload-time = "2025-07-14T20:33:14.462Z" }, - { url = "https://files.pythonhosted.org/packages/4c/66/85607ab5137d65e4f54d9797b77d5a038ef34f714929cf8ad30b03f628df/mypy-1.17.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:037bc0f0b124ce46bfde955c647f3e395c6174476a968c0f22c95a8d2f589bba", size = 12731358, upload-time = "2025-07-14T20:32:25.579Z" }, - { url = "https://files.pythonhosted.org/packages/73/d0/341dbbfb35ce53d01f8f2969facbb66486cee9804048bf6c01b048127501/mypy-1.17.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c38876106cb6132259683632b287238858bd58de267d80defb6f418e9ee50658", size = 12917480, upload-time = "2025-07-14T20:34:21.868Z" }, - { url = "https://files.pythonhosted.org/packages/64/63/70c8b7dbfc520089ac48d01367a97e8acd734f65bd07813081f508a8c94c/mypy-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:d30ba01c0f151998f367506fab31c2ac4527e6a7b2690107c7a7f9e3cb419a9c", size = 9589666, upload-time = "2025-07-14T20:34:16.841Z" }, - { url = "https://files.pythonhosted.org/packages/e3/fc/ee058cc4316f219078464555873e99d170bde1d9569abd833300dbeb484a/mypy-1.17.0-py3-none-any.whl", hash = "sha256:15d9d0018237ab058e5de3d8fce61b6fa72cc59cc78fd91f1b474bce12abf496", size = 2283195, upload-time = "2025-07-14T20:31:54.753Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/8e/22/ea637422dedf0bf36f3ef238eab4e455e2a0dcc3082b5cc067615347ab8e/mypy-1.17.1.tar.gz", hash = "sha256:25e01ec741ab5bb3eec8ba9cdb0f769230368a22c959c4937360efb89b7e9f01", size = 3352570, upload-time = "2025-07-31T07:54:19.204Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/cf/eadc80c4e0a70db1c08921dcc220357ba8ab2faecb4392e3cebeb10edbfa/mypy-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ad37544be07c5d7fba814eb370e006df58fed8ad1ef33ed1649cb1889ba6ff58", size = 10921009, upload-time = "2025-07-31T07:53:23.037Z" }, + { url = "https://files.pythonhosted.org/packages/5d/c1/c869d8c067829ad30d9bdae051046561552516cfb3a14f7f0347b7d973ee/mypy-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:064e2ff508e5464b4bd807a7c1625bc5047c5022b85c70f030680e18f37273a5", size = 10047482, upload-time = "2025-07-31T07:53:26.151Z" }, + { url = "https://files.pythonhosted.org/packages/98/b9/803672bab3fe03cee2e14786ca056efda4bb511ea02dadcedde6176d06d0/mypy-1.17.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:70401bbabd2fa1aa7c43bb358f54037baf0586f41e83b0ae67dd0534fc64edfd", size = 11832883, upload-time = "2025-07-31T07:53:47.948Z" }, + { url = "https://files.pythonhosted.org/packages/88/fb/fcdac695beca66800918c18697b48833a9a6701de288452b6715a98cfee1/mypy-1.17.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e92bdc656b7757c438660f775f872a669b8ff374edc4d18277d86b63edba6b8b", size = 12566215, upload-time = "2025-07-31T07:54:04.031Z" }, + { url = "https://files.pythonhosted.org/packages/7f/37/a932da3d3dace99ee8eb2043b6ab03b6768c36eb29a02f98f46c18c0da0e/mypy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c1fdf4abb29ed1cb091cf432979e162c208a5ac676ce35010373ff29247bcad5", size = 12751956, upload-time = "2025-07-31T07:53:36.263Z" }, + { url = "https://files.pythonhosted.org/packages/8c/cf/6438a429e0f2f5cab8bc83e53dbebfa666476f40ee322e13cac5e64b79e7/mypy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:ff2933428516ab63f961644bc49bc4cbe42bbffb2cd3b71cc7277c07d16b1a8b", size = 9507307, upload-time = "2025-07-31T07:53:59.734Z" }, + { url = "https://files.pythonhosted.org/packages/17/a2/7034d0d61af8098ec47902108553122baa0f438df8a713be860f7407c9e6/mypy-1.17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:69e83ea6553a3ba79c08c6e15dbd9bfa912ec1e493bf75489ef93beb65209aeb", size = 11086295, upload-time = "2025-07-31T07:53:28.124Z" }, + { url = "https://files.pythonhosted.org/packages/14/1f/19e7e44b594d4b12f6ba8064dbe136505cec813549ca3e5191e40b1d3cc2/mypy-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b16708a66d38abb1e6b5702f5c2c87e133289da36f6a1d15f6a5221085c6403", size = 10112355, upload-time = "2025-07-31T07:53:21.121Z" }, + { url = "https://files.pythonhosted.org/packages/5b/69/baa33927e29e6b4c55d798a9d44db5d394072eef2bdc18c3e2048c9ed1e9/mypy-1.17.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:89e972c0035e9e05823907ad5398c5a73b9f47a002b22359b177d40bdaee7056", size = 11875285, upload-time = "2025-07-31T07:53:55.293Z" }, + { url = "https://files.pythonhosted.org/packages/90/13/f3a89c76b0a41e19490b01e7069713a30949d9a6c147289ee1521bcea245/mypy-1.17.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03b6d0ed2b188e35ee6d5c36b5580cffd6da23319991c49ab5556c023ccf1341", size = 12737895, upload-time = "2025-07-31T07:53:43.623Z" }, + { url = "https://files.pythonhosted.org/packages/23/a1/c4ee79ac484241301564072e6476c5a5be2590bc2e7bfd28220033d2ef8f/mypy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c837b896b37cd103570d776bda106eabb8737aa6dd4f248451aecf53030cdbeb", size = 12931025, upload-time = "2025-07-31T07:54:17.125Z" }, + { url = "https://files.pythonhosted.org/packages/89/b8/7409477be7919a0608900e6320b155c72caab4fef46427c5cc75f85edadd/mypy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:665afab0963a4b39dff7c1fa563cc8b11ecff7910206db4b2e64dd1ba25aed19", size = 9584664, upload-time = "2025-07-31T07:54:12.842Z" }, + { url = "https://files.pythonhosted.org/packages/5b/82/aec2fc9b9b149f372850291827537a508d6c4d3664b1750a324b91f71355/mypy-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:93378d3203a5c0800c6b6d850ad2f19f7a3cdf1a3701d3416dbf128805c6a6a7", size = 11075338, upload-time = "2025-07-31T07:53:38.873Z" }, + { url = "https://files.pythonhosted.org/packages/07/ac/ee93fbde9d2242657128af8c86f5d917cd2887584cf948a8e3663d0cd737/mypy-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:15d54056f7fe7a826d897789f53dd6377ec2ea8ba6f776dc83c2902b899fee81", size = 10113066, upload-time = "2025-07-31T07:54:14.707Z" }, + { url = "https://files.pythonhosted.org/packages/5a/68/946a1e0be93f17f7caa56c45844ec691ca153ee8b62f21eddda336a2d203/mypy-1.17.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:209a58fed9987eccc20f2ca94afe7257a8f46eb5df1fb69958650973230f91e6", size = 11875473, upload-time = "2025-07-31T07:53:14.504Z" }, + { url = "https://files.pythonhosted.org/packages/9f/0f/478b4dce1cb4f43cf0f0d00fba3030b21ca04a01b74d1cd272a528cf446f/mypy-1.17.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:099b9a5da47de9e2cb5165e581f158e854d9e19d2e96b6698c0d64de911dd849", size = 12744296, upload-time = "2025-07-31T07:53:03.896Z" }, + { url = "https://files.pythonhosted.org/packages/ca/70/afa5850176379d1b303f992a828de95fc14487429a7139a4e0bdd17a8279/mypy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ffadfbe6994d724c5a1bb6123a7d27dd68fc9c059561cd33b664a79578e14", size = 12914657, upload-time = "2025-07-31T07:54:08.576Z" }, + { url = "https://files.pythonhosted.org/packages/53/f9/4a83e1c856a3d9c8f6edaa4749a4864ee98486e9b9dbfbc93842891029c2/mypy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:9a2b7d9180aed171f033c9f2fc6c204c1245cf60b0cb61cf2e7acc24eea78e0a", size = 9593320, upload-time = "2025-07-31T07:53:01.341Z" }, + { url = "https://files.pythonhosted.org/packages/38/56/79c2fac86da57c7d8c48622a05873eaab40b905096c33597462713f5af90/mypy-1.17.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:15a83369400454c41ed3a118e0cc58bd8123921a602f385cb6d6ea5df050c733", size = 11040037, upload-time = "2025-07-31T07:54:10.942Z" }, + { url = "https://files.pythonhosted.org/packages/4d/c3/adabe6ff53638e3cad19e3547268482408323b1e68bf082c9119000cd049/mypy-1.17.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:55b918670f692fc9fba55c3298d8a3beae295c5cded0a55dccdc5bbead814acd", size = 10131550, upload-time = "2025-07-31T07:53:41.307Z" }, + { url = "https://files.pythonhosted.org/packages/b8/c5/2e234c22c3bdeb23a7817af57a58865a39753bde52c74e2c661ee0cfc640/mypy-1.17.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:62761474061feef6f720149d7ba876122007ddc64adff5ba6f374fda35a018a0", size = 11872963, upload-time = "2025-07-31T07:53:16.878Z" }, + { url = "https://files.pythonhosted.org/packages/ab/26/c13c130f35ca8caa5f2ceab68a247775648fdcd6c9a18f158825f2bc2410/mypy-1.17.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c49562d3d908fd49ed0938e5423daed8d407774a479b595b143a3d7f87cdae6a", size = 12710189, upload-time = "2025-07-31T07:54:01.962Z" }, + { url = "https://files.pythonhosted.org/packages/82/df/c7d79d09f6de8383fe800521d066d877e54d30b4fb94281c262be2df84ef/mypy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:397fba5d7616a5bc60b45c7ed204717eaddc38f826e3645402c426057ead9a91", size = 12900322, upload-time = "2025-07-31T07:53:10.551Z" }, + { url = "https://files.pythonhosted.org/packages/b8/98/3d5a48978b4f708c55ae832619addc66d677f6dc59f3ebad71bae8285ca6/mypy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:9d6b20b97d373f41617bd0708fd46aa656059af57f2ef72aa8c7d6a2b73b74ed", size = 9751879, upload-time = "2025-07-31T07:52:56.683Z" }, + { url = "https://files.pythonhosted.org/packages/1d/f3/8fcd2af0f5b806f6cf463efaffd3c9548a28f84220493ecd38d127b6b66d/mypy-1.17.1-py3-none-any.whl", hash = "sha256:a9f52c0351c21fe24c21d8c0eb1f62967b262d6729393397b6f443c3b773c3b9", size = 2283411, upload-time = "2025-07-31T07:53:24.664Z" }, +] + +[[package]] +name = "mypy-boto3-s3" +version = "1.40.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions", marker = "python_full_version < '3.12'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/d7/b2100702d2f200fdb3468e419c729790bd8543ee0af6f6d63d8dfdab4e28/mypy_boto3_s3-1.40.0.tar.gz", hash = "sha256:99a4a27f04d62fe0b31032f274f2e19889fa66424413617a9416873c48567f1d", size = 75924, upload-time = "2025-07-31T19:50:01.979Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/4f/4d32cd202d8c8c7e11e44dd288f66b8985e6ee4402b9a0891b7b94ff6cc6/mypy_boto3_s3-1.40.0-py3-none-any.whl", hash = "sha256:5736b7780d57a156312d8d136462c207671d0236b0355704b5754496bb712bc8", size = 82710, upload-time = "2025-07-31T19:49:59.713Z" }, ] [[package]] @@ -2175,69 +1901,83 @@ wheels = [ [[package]] name = "numpy" -version = "2.3.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2e/19/d7c972dfe90a353dbd3efbbe1d14a5951de80c99c9dc1b93cd998d51dc0f/numpy-2.3.1.tar.gz", hash = "sha256:1ec9ae20a4226da374362cca3c62cd753faf2f951440b0e3b98e93c235441d2b", size = 20390372, upload-time = "2025-06-21T12:28:33.469Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b0/c7/87c64d7ab426156530676000c94784ef55676df2f13b2796f97722464124/numpy-2.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6ea9e48336a402551f52cd8f593343699003d2353daa4b72ce8d34f66b722070", size = 21199346, upload-time = "2025-06-21T11:47:47.57Z" }, - { url = "https://files.pythonhosted.org/packages/58/0e/0966c2f44beeac12af8d836e5b5f826a407cf34c45cb73ddcdfce9f5960b/numpy-2.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ccb7336eaf0e77c1635b232c141846493a588ec9ea777a7c24d7166bb8533ae", size = 14361143, upload-time = "2025-06-21T11:48:10.766Z" }, - { url = "https://files.pythonhosted.org/packages/7d/31/6e35a247acb1bfc19226791dfc7d4c30002cd4e620e11e58b0ddf836fe52/numpy-2.3.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0bb3a4a61e1d327e035275d2a993c96fa786e4913aa089843e6a2d9dd205c66a", size = 5378989, upload-time = "2025-06-21T11:48:19.998Z" }, - { url = "https://files.pythonhosted.org/packages/b0/25/93b621219bb6f5a2d4e713a824522c69ab1f06a57cd571cda70e2e31af44/numpy-2.3.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:e344eb79dab01f1e838ebb67aab09965fb271d6da6b00adda26328ac27d4a66e", size = 6912890, upload-time = "2025-06-21T11:48:31.376Z" }, - { url = "https://files.pythonhosted.org/packages/ef/60/6b06ed98d11fb32e27fb59468b42383f3877146d3ee639f733776b6ac596/numpy-2.3.1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:467db865b392168ceb1ef1ffa6f5a86e62468c43e0cfb4ab6da667ede10e58db", size = 14569032, upload-time = "2025-06-21T11:48:52.563Z" }, - { url = "https://files.pythonhosted.org/packages/75/c9/9bec03675192077467a9c7c2bdd1f2e922bd01d3a69b15c3a0fdcd8548f6/numpy-2.3.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:afed2ce4a84f6b0fc6c1ce734ff368cbf5a5e24e8954a338f3bdffa0718adffb", size = 16930354, upload-time = "2025-06-21T11:49:17.473Z" }, - { url = "https://files.pythonhosted.org/packages/6a/e2/5756a00cabcf50a3f527a0c968b2b4881c62b1379223931853114fa04cda/numpy-2.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0025048b3c1557a20bc80d06fdeb8cc7fc193721484cca82b2cfa072fec71a93", size = 15879605, upload-time = "2025-06-21T11:49:41.161Z" }, - { url = "https://files.pythonhosted.org/packages/ff/86/a471f65f0a86f1ca62dcc90b9fa46174dd48f50214e5446bc16a775646c5/numpy-2.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:a5ee121b60aa509679b682819c602579e1df14a5b07fe95671c8849aad8f2115", size = 18666994, upload-time = "2025-06-21T11:50:08.516Z" }, - { url = "https://files.pythonhosted.org/packages/43/a6/482a53e469b32be6500aaf61cfafd1de7a0b0d484babf679209c3298852e/numpy-2.3.1-cp311-cp311-win32.whl", hash = "sha256:a8b740f5579ae4585831b3cf0e3b0425c667274f82a484866d2adf9570539369", size = 6603672, upload-time = "2025-06-21T11:50:19.584Z" }, - { url = "https://files.pythonhosted.org/packages/6b/fb/bb613f4122c310a13ec67585c70e14b03bfc7ebabd24f4d5138b97371d7c/numpy-2.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:d4580adadc53311b163444f877e0789f1c8861e2698f6b2a4ca852fda154f3ff", size = 13024015, upload-time = "2025-06-21T11:50:39.139Z" }, - { url = "https://files.pythonhosted.org/packages/51/58/2d842825af9a0c041aca246dc92eb725e1bc5e1c9ac89712625db0c4e11c/numpy-2.3.1-cp311-cp311-win_arm64.whl", hash = "sha256:ec0bdafa906f95adc9a0c6f26a4871fa753f25caaa0e032578a30457bff0af6a", size = 10456989, upload-time = "2025-06-21T11:50:55.616Z" }, - { url = "https://files.pythonhosted.org/packages/c6/56/71ad5022e2f63cfe0ca93559403d0edef14aea70a841d640bd13cdba578e/numpy-2.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2959d8f268f3d8ee402b04a9ec4bb7604555aeacf78b360dc4ec27f1d508177d", size = 20896664, upload-time = "2025-06-21T12:15:30.845Z" }, - { url = "https://files.pythonhosted.org/packages/25/65/2db52ba049813670f7f987cc5db6dac9be7cd95e923cc6832b3d32d87cef/numpy-2.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:762e0c0c6b56bdedfef9a8e1d4538556438288c4276901ea008ae44091954e29", size = 14131078, upload-time = "2025-06-21T12:15:52.23Z" }, - { url = "https://files.pythonhosted.org/packages/57/dd/28fa3c17b0e751047ac928c1e1b6990238faad76e9b147e585b573d9d1bd/numpy-2.3.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:867ef172a0976aaa1f1d1b63cf2090de8b636a7674607d514505fb7276ab08fc", size = 5112554, upload-time = "2025-06-21T12:16:01.434Z" }, - { url = "https://files.pythonhosted.org/packages/c9/fc/84ea0cba8e760c4644b708b6819d91784c290288c27aca916115e3311d17/numpy-2.3.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:4e602e1b8682c2b833af89ba641ad4176053aaa50f5cacda1a27004352dde943", size = 6646560, upload-time = "2025-06-21T12:16:11.895Z" }, - { url = "https://files.pythonhosted.org/packages/61/b2/512b0c2ddec985ad1e496b0bd853eeb572315c0f07cd6997473ced8f15e2/numpy-2.3.1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:8e333040d069eba1652fb08962ec5b76af7f2c7bce1df7e1418c8055cf776f25", size = 14260638, upload-time = "2025-06-21T12:16:32.611Z" }, - { url = "https://files.pythonhosted.org/packages/6e/45/c51cb248e679a6c6ab14b7a8e3ead3f4a3fe7425fc7a6f98b3f147bec532/numpy-2.3.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:e7cbf5a5eafd8d230a3ce356d892512185230e4781a361229bd902ff403bc660", size = 16632729, upload-time = "2025-06-21T12:16:57.439Z" }, - { url = "https://files.pythonhosted.org/packages/e4/ff/feb4be2e5c09a3da161b412019caf47183099cbea1132fd98061808c2df2/numpy-2.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5f1b8f26d1086835f442286c1d9b64bb3974b0b1e41bb105358fd07d20872952", size = 15565330, upload-time = "2025-06-21T12:17:20.638Z" }, - { url = "https://files.pythonhosted.org/packages/bc/6d/ceafe87587101e9ab0d370e4f6e5f3f3a85b9a697f2318738e5e7e176ce3/numpy-2.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ee8340cb48c9b7a5899d1149eece41ca535513a9698098edbade2a8e7a84da77", size = 18361734, upload-time = "2025-06-21T12:17:47.938Z" }, - { url = "https://files.pythonhosted.org/packages/2b/19/0fb49a3ea088be691f040c9bf1817e4669a339d6e98579f91859b902c636/numpy-2.3.1-cp312-cp312-win32.whl", hash = "sha256:e772dda20a6002ef7061713dc1e2585bc1b534e7909b2030b5a46dae8ff077ab", size = 6320411, upload-time = "2025-06-21T12:17:58.475Z" }, - { url = "https://files.pythonhosted.org/packages/b1/3e/e28f4c1dd9e042eb57a3eb652f200225e311b608632bc727ae378623d4f8/numpy-2.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:cfecc7822543abdea6de08758091da655ea2210b8ffa1faf116b940693d3df76", size = 12734973, upload-time = "2025-06-21T12:18:17.601Z" }, - { url = "https://files.pythonhosted.org/packages/04/a8/8a5e9079dc722acf53522b8f8842e79541ea81835e9b5483388701421073/numpy-2.3.1-cp312-cp312-win_arm64.whl", hash = "sha256:7be91b2239af2658653c5bb6f1b8bccafaf08226a258caf78ce44710a0160d30", size = 10191491, upload-time = "2025-06-21T12:18:33.585Z" }, - { url = "https://files.pythonhosted.org/packages/d4/bd/35ad97006d8abff8631293f8ea6adf07b0108ce6fec68da3c3fcca1197f2/numpy-2.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25a1992b0a3fdcdaec9f552ef10d8103186f5397ab45e2d25f8ac51b1a6b97e8", size = 20889381, upload-time = "2025-06-21T12:19:04.103Z" }, - { url = "https://files.pythonhosted.org/packages/f1/4f/df5923874d8095b6062495b39729178eef4a922119cee32a12ee1bd4664c/numpy-2.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7dea630156d39b02a63c18f508f85010230409db5b2927ba59c8ba4ab3e8272e", size = 14152726, upload-time = "2025-06-21T12:19:25.599Z" }, - { url = "https://files.pythonhosted.org/packages/8c/0f/a1f269b125806212a876f7efb049b06c6f8772cf0121139f97774cd95626/numpy-2.3.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:bada6058dd886061f10ea15f230ccf7dfff40572e99fef440a4a857c8728c9c0", size = 5105145, upload-time = "2025-06-21T12:19:34.782Z" }, - { url = "https://files.pythonhosted.org/packages/6d/63/a7f7fd5f375b0361682f6ffbf686787e82b7bbd561268e4f30afad2bb3c0/numpy-2.3.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:a894f3816eb17b29e4783e5873f92faf55b710c2519e5c351767c51f79d8526d", size = 6639409, upload-time = "2025-06-21T12:19:45.228Z" }, - { url = "https://files.pythonhosted.org/packages/bf/0d/1854a4121af895aab383f4aa233748f1df4671ef331d898e32426756a8a6/numpy-2.3.1-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:18703df6c4a4fee55fd3d6e5a253d01c5d33a295409b03fda0c86b3ca2ff41a1", size = 14257630, upload-time = "2025-06-21T12:20:06.544Z" }, - { url = "https://files.pythonhosted.org/packages/50/30/af1b277b443f2fb08acf1c55ce9d68ee540043f158630d62cef012750f9f/numpy-2.3.1-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:5902660491bd7a48b2ec16c23ccb9124b8abfd9583c5fdfa123fe6b421e03de1", size = 16627546, upload-time = "2025-06-21T12:20:31.002Z" }, - { url = "https://files.pythonhosted.org/packages/6e/ec/3b68220c277e463095342d254c61be8144c31208db18d3fd8ef02712bcd6/numpy-2.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:36890eb9e9d2081137bd78d29050ba63b8dab95dff7912eadf1185e80074b2a0", size = 15562538, upload-time = "2025-06-21T12:20:54.322Z" }, - { url = "https://files.pythonhosted.org/packages/77/2b/4014f2bcc4404484021c74d4c5ee8eb3de7e3f7ac75f06672f8dcf85140a/numpy-2.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a780033466159c2270531e2b8ac063704592a0bc62ec4a1b991c7c40705eb0e8", size = 18360327, upload-time = "2025-06-21T12:21:21.053Z" }, - { url = "https://files.pythonhosted.org/packages/40/8d/2ddd6c9b30fcf920837b8672f6c65590c7d92e43084c25fc65edc22e93ca/numpy-2.3.1-cp313-cp313-win32.whl", hash = "sha256:39bff12c076812595c3a306f22bfe49919c5513aa1e0e70fac756a0be7c2a2b8", size = 6312330, upload-time = "2025-06-21T12:25:07.447Z" }, - { url = "https://files.pythonhosted.org/packages/dd/c8/beaba449925988d415efccb45bf977ff8327a02f655090627318f6398c7b/numpy-2.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:8d5ee6eec45f08ce507a6570e06f2f879b374a552087a4179ea7838edbcbfa42", size = 12731565, upload-time = "2025-06-21T12:25:26.444Z" }, - { url = "https://files.pythonhosted.org/packages/0b/c3/5c0c575d7ec78c1126998071f58facfc124006635da75b090805e642c62e/numpy-2.3.1-cp313-cp313-win_arm64.whl", hash = "sha256:0c4d9e0a8368db90f93bd192bfa771ace63137c3488d198ee21dfb8e7771916e", size = 10190262, upload-time = "2025-06-21T12:25:42.196Z" }, - { url = "https://files.pythonhosted.org/packages/ea/19/a029cd335cf72f79d2644dcfc22d90f09caa86265cbbde3b5702ccef6890/numpy-2.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:b0b5397374f32ec0649dd98c652a1798192042e715df918c20672c62fb52d4b8", size = 20987593, upload-time = "2025-06-21T12:21:51.664Z" }, - { url = "https://files.pythonhosted.org/packages/25/91/8ea8894406209107d9ce19b66314194675d31761fe2cb3c84fe2eeae2f37/numpy-2.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:c5bdf2015ccfcee8253fb8be695516ac4457c743473a43290fd36eba6a1777eb", size = 14300523, upload-time = "2025-06-21T12:22:13.583Z" }, - { url = "https://files.pythonhosted.org/packages/a6/7f/06187b0066eefc9e7ce77d5f2ddb4e314a55220ad62dd0bfc9f2c44bac14/numpy-2.3.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d70f20df7f08b90a2062c1f07737dd340adccf2068d0f1b9b3d56e2038979fee", size = 5227993, upload-time = "2025-06-21T12:22:22.53Z" }, - { url = "https://files.pythonhosted.org/packages/e8/ec/a926c293c605fa75e9cfb09f1e4840098ed46d2edaa6e2152ee35dc01ed3/numpy-2.3.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:2fb86b7e58f9ac50e1e9dd1290154107e47d1eef23a0ae9145ded06ea606f992", size = 6736652, upload-time = "2025-06-21T12:22:33.629Z" }, - { url = "https://files.pythonhosted.org/packages/e3/62/d68e52fb6fde5586650d4c0ce0b05ff3a48ad4df4ffd1b8866479d1d671d/numpy-2.3.1-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:23ab05b2d241f76cb883ce8b9a93a680752fbfcbd51c50eff0b88b979e471d8c", size = 14331561, upload-time = "2025-06-21T12:22:55.056Z" }, - { url = "https://files.pythonhosted.org/packages/fc/ec/b74d3f2430960044bdad6900d9f5edc2dc0fb8bf5a0be0f65287bf2cbe27/numpy-2.3.1-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:ce2ce9e5de4703a673e705183f64fd5da5bf36e7beddcb63a25ee2286e71ca48", size = 16693349, upload-time = "2025-06-21T12:23:20.53Z" }, - { url = "https://files.pythonhosted.org/packages/0d/15/def96774b9d7eb198ddadfcbd20281b20ebb510580419197e225f5c55c3e/numpy-2.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c4913079974eeb5c16ccfd2b1f09354b8fed7e0d6f2cab933104a09a6419b1ee", size = 15642053, upload-time = "2025-06-21T12:23:43.697Z" }, - { url = "https://files.pythonhosted.org/packages/2b/57/c3203974762a759540c6ae71d0ea2341c1fa41d84e4971a8e76d7141678a/numpy-2.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:010ce9b4f00d5c036053ca684c77441f2f2c934fd23bee058b4d6f196efd8280", size = 18434184, upload-time = "2025-06-21T12:24:10.708Z" }, - { url = "https://files.pythonhosted.org/packages/22/8a/ccdf201457ed8ac6245187850aff4ca56a79edbea4829f4e9f14d46fa9a5/numpy-2.3.1-cp313-cp313t-win32.whl", hash = "sha256:6269b9edfe32912584ec496d91b00b6d34282ca1d07eb10e82dfc780907d6c2e", size = 6440678, upload-time = "2025-06-21T12:24:21.596Z" }, - { url = "https://files.pythonhosted.org/packages/f1/7e/7f431d8bd8eb7e03d79294aed238b1b0b174b3148570d03a8a8a8f6a0da9/numpy-2.3.1-cp313-cp313t-win_amd64.whl", hash = "sha256:2a809637460e88a113e186e87f228d74ae2852a2e0c44de275263376f17b5bdc", size = 12870697, upload-time = "2025-06-21T12:24:40.644Z" }, - { url = "https://files.pythonhosted.org/packages/d4/ca/af82bf0fad4c3e573c6930ed743b5308492ff19917c7caaf2f9b6f9e2e98/numpy-2.3.1-cp313-cp313t-win_arm64.whl", hash = "sha256:eccb9a159db9aed60800187bc47a6d3451553f0e1b08b068d8b277ddfbb9b244", size = 10260376, upload-time = "2025-06-21T12:24:56.884Z" }, - { url = "https://files.pythonhosted.org/packages/e8/34/facc13b9b42ddca30498fc51f7f73c3d0f2be179943a4b4da8686e259740/numpy-2.3.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ad506d4b09e684394c42c966ec1527f6ebc25da7f4da4b1b056606ffe446b8a3", size = 21070637, upload-time = "2025-06-21T12:26:12.518Z" }, - { url = "https://files.pythonhosted.org/packages/65/b6/41b705d9dbae04649b529fc9bd3387664c3281c7cd78b404a4efe73dcc45/numpy-2.3.1-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:ebb8603d45bc86bbd5edb0d63e52c5fd9e7945d3a503b77e486bd88dde67a19b", size = 5304087, upload-time = "2025-06-21T12:26:22.294Z" }, - { url = "https://files.pythonhosted.org/packages/7a/b4/fe3ac1902bff7a4934a22d49e1c9d71a623204d654d4cc43c6e8fe337fcb/numpy-2.3.1-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:15aa4c392ac396e2ad3d0a2680c0f0dee420f9fed14eef09bdb9450ee6dcb7b7", size = 6817588, upload-time = "2025-06-21T12:26:32.939Z" }, - { url = "https://files.pythonhosted.org/packages/ae/ee/89bedf69c36ace1ac8f59e97811c1f5031e179a37e4821c3a230bf750142/numpy-2.3.1-pp311-pypy311_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c6e0bf9d1a2f50d2b65a7cf56db37c095af17b59f6c132396f7c6d5dd76484df", size = 14399010, upload-time = "2025-06-21T12:26:54.086Z" }, - { url = "https://files.pythonhosted.org/packages/15/08/e00e7070ede29b2b176165eba18d6f9784d5349be3c0c1218338e79c27fd/numpy-2.3.1-pp311-pypy311_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:eabd7e8740d494ce2b4ea0ff05afa1b7b291e978c0ae075487c51e8bd93c0c68", size = 16752042, upload-time = "2025-06-21T12:27:19.018Z" }, - { url = "https://files.pythonhosted.org/packages/48/6b/1c6b515a83d5564b1698a61efa245727c8feecf308f4091f565988519d20/numpy-2.3.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:e610832418a2bc09d974cc9fecebfa51e9532d6190223bc5ef6a7402ebf3b5cb", size = 12927246, upload-time = "2025-06-21T12:27:38.618Z" }, -] - -[[package]] -name = "oauthlib" -version = "3.3.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0b/5f/19930f824ffeb0ad4372da4812c50edbd1434f678c90c2733e1188edfc63/oauthlib-3.3.1.tar.gz", hash = "sha256:0f0f8aa759826a193cf66c12ea1af1637f87b9b4622d46e866952bb022e538c9", size = 185918, upload-time = "2025-06-19T22:48:08.269Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/be/9c/92789c596b8df838baa98fa71844d84283302f7604ed565dafe5a6b5041a/oauthlib-3.3.1-py3-none-any.whl", hash = "sha256:88119c938d2b8fb88561af5f6ee0eec8cc8d552b7bb1f712743136eb7523b7a1", size = 160065, upload-time = "2025-06-19T22:48:06.508Z" }, +version = "2.3.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/37/7d/3fec4199c5ffb892bed55cff901e4f39a58c81df9c44c280499e92cad264/numpy-2.3.2.tar.gz", hash = "sha256:e0486a11ec30cdecb53f184d496d1c6a20786c81e55e41640270130056f8ee48", size = 20489306, upload-time = "2025-07-24T21:32:07.553Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/96/26/1320083986108998bd487e2931eed2aeedf914b6e8905431487543ec911d/numpy-2.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:852ae5bed3478b92f093e30f785c98e0cb62fa0a939ed057c31716e18a7a22b9", size = 21259016, upload-time = "2025-07-24T20:24:35.214Z" }, + { url = "https://files.pythonhosted.org/packages/c4/2b/792b341463fa93fc7e55abbdbe87dac316c5b8cb5e94fb7a59fb6fa0cda5/numpy-2.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7a0e27186e781a69959d0230dd9909b5e26024f8da10683bd6344baea1885168", size = 14451158, upload-time = "2025-07-24T20:24:58.397Z" }, + { url = "https://files.pythonhosted.org/packages/b7/13/e792d7209261afb0c9f4759ffef6135b35c77c6349a151f488f531d13595/numpy-2.3.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:f0a1a8476ad77a228e41619af2fa9505cf69df928e9aaa165746584ea17fed2b", size = 5379817, upload-time = "2025-07-24T20:25:07.746Z" }, + { url = "https://files.pythonhosted.org/packages/49/ce/055274fcba4107c022b2113a213c7287346563f48d62e8d2a5176ad93217/numpy-2.3.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cbc95b3813920145032412f7e33d12080f11dc776262df1712e1638207dde9e8", size = 6913606, upload-time = "2025-07-24T20:25:18.84Z" }, + { url = "https://files.pythonhosted.org/packages/17/f2/e4d72e6bc5ff01e2ab613dc198d560714971900c03674b41947e38606502/numpy-2.3.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f75018be4980a7324edc5930fe39aa391d5734531b1926968605416ff58c332d", size = 14589652, upload-time = "2025-07-24T20:25:40.356Z" }, + { url = "https://files.pythonhosted.org/packages/c8/b0/fbeee3000a51ebf7222016e2939b5c5ecf8000a19555d04a18f1e02521b8/numpy-2.3.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20b8200721840f5621b7bd03f8dcd78de33ec522fc40dc2641aa09537df010c3", size = 16938816, upload-time = "2025-07-24T20:26:05.721Z" }, + { url = "https://files.pythonhosted.org/packages/a9/ec/2f6c45c3484cc159621ea8fc000ac5a86f1575f090cac78ac27193ce82cd/numpy-2.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f91e5c028504660d606340a084db4b216567ded1056ea2b4be4f9d10b67197f", size = 16370512, upload-time = "2025-07-24T20:26:30.545Z" }, + { url = "https://files.pythonhosted.org/packages/b5/01/dd67cf511850bd7aefd6347aaae0956ed415abea741ae107834aae7d6d4e/numpy-2.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:fb1752a3bb9a3ad2d6b090b88a9a0ae1cd6f004ef95f75825e2f382c183b2097", size = 18884947, upload-time = "2025-07-24T20:26:58.24Z" }, + { url = "https://files.pythonhosted.org/packages/a7/17/2cf60fd3e6a61d006778735edf67a222787a8c1a7842aed43ef96d777446/numpy-2.3.2-cp311-cp311-win32.whl", hash = "sha256:4ae6863868aaee2f57503c7a5052b3a2807cf7a3914475e637a0ecd366ced220", size = 6599494, upload-time = "2025-07-24T20:27:09.786Z" }, + { url = "https://files.pythonhosted.org/packages/d5/03/0eade211c504bda872a594f045f98ddcc6caef2b7c63610946845e304d3f/numpy-2.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:240259d6564f1c65424bcd10f435145a7644a65a6811cfc3201c4a429ba79170", size = 13087889, upload-time = "2025-07-24T20:27:29.558Z" }, + { url = "https://files.pythonhosted.org/packages/13/32/2c7979d39dafb2a25087e12310fc7f3b9d3c7d960df4f4bc97955ae0ce1d/numpy-2.3.2-cp311-cp311-win_arm64.whl", hash = "sha256:4209f874d45f921bde2cff1ffcd8a3695f545ad2ffbef6d3d3c6768162efab89", size = 10459560, upload-time = "2025-07-24T20:27:46.803Z" }, + { url = "https://files.pythonhosted.org/packages/00/6d/745dd1c1c5c284d17725e5c802ca4d45cfc6803519d777f087b71c9f4069/numpy-2.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bc3186bea41fae9d8e90c2b4fb5f0a1f5a690682da79b92574d63f56b529080b", size = 20956420, upload-time = "2025-07-24T20:28:18.002Z" }, + { url = "https://files.pythonhosted.org/packages/bc/96/e7b533ea5740641dd62b07a790af5d9d8fec36000b8e2d0472bd7574105f/numpy-2.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f4f0215edb189048a3c03bd5b19345bdfa7b45a7a6f72ae5945d2a28272727f", size = 14184660, upload-time = "2025-07-24T20:28:39.522Z" }, + { url = "https://files.pythonhosted.org/packages/2b/53/102c6122db45a62aa20d1b18c9986f67e6b97e0d6fbc1ae13e3e4c84430c/numpy-2.3.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b1224a734cd509f70816455c3cffe13a4f599b1bf7130f913ba0e2c0b2006c0", size = 5113382, upload-time = "2025-07-24T20:28:48.544Z" }, + { url = "https://files.pythonhosted.org/packages/2b/21/376257efcbf63e624250717e82b4fae93d60178f09eb03ed766dbb48ec9c/numpy-2.3.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3dcf02866b977a38ba3ec10215220609ab9667378a9e2150615673f3ffd6c73b", size = 6647258, upload-time = "2025-07-24T20:28:59.104Z" }, + { url = "https://files.pythonhosted.org/packages/91/ba/f4ebf257f08affa464fe6036e13f2bf9d4642a40228781dc1235da81be9f/numpy-2.3.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:572d5512df5470f50ada8d1972c5f1082d9a0b7aa5944db8084077570cf98370", size = 14281409, upload-time = "2025-07-24T20:40:30.298Z" }, + { url = "https://files.pythonhosted.org/packages/59/ef/f96536f1df42c668cbacb727a8c6da7afc9c05ece6d558927fb1722693e1/numpy-2.3.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8145dd6d10df13c559d1e4314df29695613575183fa2e2d11fac4c208c8a1f73", size = 16641317, upload-time = "2025-07-24T20:40:56.625Z" }, + { url = "https://files.pythonhosted.org/packages/f6/a7/af813a7b4f9a42f498dde8a4c6fcbff8100eed00182cc91dbaf095645f38/numpy-2.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:103ea7063fa624af04a791c39f97070bf93b96d7af7eb23530cd087dc8dbe9dc", size = 16056262, upload-time = "2025-07-24T20:41:20.797Z" }, + { url = "https://files.pythonhosted.org/packages/8b/5d/41c4ef8404caaa7f05ed1cfb06afe16a25895260eacbd29b4d84dff2920b/numpy-2.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc927d7f289d14f5e037be917539620603294454130b6de200091e23d27dc9be", size = 18579342, upload-time = "2025-07-24T20:41:50.753Z" }, + { url = "https://files.pythonhosted.org/packages/a1/4f/9950e44c5a11636f4a3af6e825ec23003475cc9a466edb7a759ed3ea63bd/numpy-2.3.2-cp312-cp312-win32.whl", hash = "sha256:d95f59afe7f808c103be692175008bab926b59309ade3e6d25009e9a171f7036", size = 6320610, upload-time = "2025-07-24T20:42:01.551Z" }, + { url = "https://files.pythonhosted.org/packages/7c/2f/244643a5ce54a94f0a9a2ab578189c061e4a87c002e037b0829dd77293b6/numpy-2.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:9e196ade2400c0c737d93465327d1ae7c06c7cb8a1756121ebf54b06ca183c7f", size = 12786292, upload-time = "2025-07-24T20:42:20.738Z" }, + { url = "https://files.pythonhosted.org/packages/54/cd/7b5f49d5d78db7badab22d8323c1b6ae458fbf86c4fdfa194ab3cd4eb39b/numpy-2.3.2-cp312-cp312-win_arm64.whl", hash = "sha256:ee807923782faaf60d0d7331f5e86da7d5e3079e28b291973c545476c2b00d07", size = 10194071, upload-time = "2025-07-24T20:42:36.657Z" }, + { url = "https://files.pythonhosted.org/packages/1c/c0/c6bb172c916b00700ed3bf71cb56175fd1f7dbecebf8353545d0b5519f6c/numpy-2.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c8d9727f5316a256425892b043736d63e89ed15bbfe6556c5ff4d9d4448ff3b3", size = 20949074, upload-time = "2025-07-24T20:43:07.813Z" }, + { url = "https://files.pythonhosted.org/packages/20/4e/c116466d22acaf4573e58421c956c6076dc526e24a6be0903219775d862e/numpy-2.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:efc81393f25f14d11c9d161e46e6ee348637c0a1e8a54bf9dedc472a3fae993b", size = 14177311, upload-time = "2025-07-24T20:43:29.335Z" }, + { url = "https://files.pythonhosted.org/packages/78/45/d4698c182895af189c463fc91d70805d455a227261d950e4e0f1310c2550/numpy-2.3.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:dd937f088a2df683cbb79dda9a772b62a3e5a8a7e76690612c2737f38c6ef1b6", size = 5106022, upload-time = "2025-07-24T20:43:37.999Z" }, + { url = "https://files.pythonhosted.org/packages/9f/76/3e6880fef4420179309dba72a8c11f6166c431cf6dee54c577af8906f914/numpy-2.3.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:11e58218c0c46c80509186e460d79fbdc9ca1eb8d8aee39d8f2dc768eb781089", size = 6640135, upload-time = "2025-07-24T20:43:49.28Z" }, + { url = "https://files.pythonhosted.org/packages/34/fa/87ff7f25b3c4ce9085a62554460b7db686fef1e0207e8977795c7b7d7ba1/numpy-2.3.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5ad4ebcb683a1f99f4f392cc522ee20a18b2bb12a2c1c42c3d48d5a1adc9d3d2", size = 14278147, upload-time = "2025-07-24T20:44:10.328Z" }, + { url = "https://files.pythonhosted.org/packages/1d/0f/571b2c7a3833ae419fe69ff7b479a78d313581785203cc70a8db90121b9a/numpy-2.3.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:938065908d1d869c7d75d8ec45f735a034771c6ea07088867f713d1cd3bbbe4f", size = 16635989, upload-time = "2025-07-24T20:44:34.88Z" }, + { url = "https://files.pythonhosted.org/packages/24/5a/84ae8dca9c9a4c592fe11340b36a86ffa9fd3e40513198daf8a97839345c/numpy-2.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:66459dccc65d8ec98cc7df61307b64bf9e08101f9598755d42d8ae65d9a7a6ee", size = 16053052, upload-time = "2025-07-24T20:44:58.872Z" }, + { url = "https://files.pythonhosted.org/packages/57/7c/e5725d99a9133b9813fcf148d3f858df98511686e853169dbaf63aec6097/numpy-2.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a7af9ed2aa9ec5950daf05bb11abc4076a108bd3c7db9aa7251d5f107079b6a6", size = 18577955, upload-time = "2025-07-24T20:45:26.714Z" }, + { url = "https://files.pythonhosted.org/packages/ae/11/7c546fcf42145f29b71e4d6f429e96d8d68e5a7ba1830b2e68d7418f0bbd/numpy-2.3.2-cp313-cp313-win32.whl", hash = "sha256:906a30249315f9c8e17b085cc5f87d3f369b35fedd0051d4a84686967bdbbd0b", size = 6311843, upload-time = "2025-07-24T20:49:24.444Z" }, + { url = "https://files.pythonhosted.org/packages/aa/6f/a428fd1cb7ed39b4280d057720fed5121b0d7754fd2a9768640160f5517b/numpy-2.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:c63d95dc9d67b676e9108fe0d2182987ccb0f11933c1e8959f42fa0da8d4fa56", size = 12782876, upload-time = "2025-07-24T20:49:43.227Z" }, + { url = "https://files.pythonhosted.org/packages/65/85/4ea455c9040a12595fb6c43f2c217257c7b52dd0ba332c6a6c1d28b289fe/numpy-2.3.2-cp313-cp313-win_arm64.whl", hash = "sha256:b05a89f2fb84d21235f93de47129dd4f11c16f64c87c33f5e284e6a3a54e43f2", size = 10192786, upload-time = "2025-07-24T20:49:59.443Z" }, + { url = "https://files.pythonhosted.org/packages/80/23/8278f40282d10c3f258ec3ff1b103d4994bcad78b0cba9208317f6bb73da/numpy-2.3.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4e6ecfeddfa83b02318f4d84acf15fbdbf9ded18e46989a15a8b6995dfbf85ab", size = 21047395, upload-time = "2025-07-24T20:45:58.821Z" }, + { url = "https://files.pythonhosted.org/packages/1f/2d/624f2ce4a5df52628b4ccd16a4f9437b37c35f4f8a50d00e962aae6efd7a/numpy-2.3.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:508b0eada3eded10a3b55725b40806a4b855961040180028f52580c4729916a2", size = 14300374, upload-time = "2025-07-24T20:46:20.207Z" }, + { url = "https://files.pythonhosted.org/packages/f6/62/ff1e512cdbb829b80a6bd08318a58698867bca0ca2499d101b4af063ee97/numpy-2.3.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:754d6755d9a7588bdc6ac47dc4ee97867271b17cee39cb87aef079574366db0a", size = 5228864, upload-time = "2025-07-24T20:46:30.58Z" }, + { url = "https://files.pythonhosted.org/packages/7d/8e/74bc18078fff03192d4032cfa99d5a5ca937807136d6f5790ce07ca53515/numpy-2.3.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a9f66e7d2b2d7712410d3bc5684149040ef5f19856f20277cd17ea83e5006286", size = 6737533, upload-time = "2025-07-24T20:46:46.111Z" }, + { url = "https://files.pythonhosted.org/packages/19/ea/0731efe2c9073ccca5698ef6a8c3667c4cf4eea53fcdcd0b50140aba03bc/numpy-2.3.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de6ea4e5a65d5a90c7d286ddff2b87f3f4ad61faa3db8dabe936b34c2275b6f8", size = 14352007, upload-time = "2025-07-24T20:47:07.1Z" }, + { url = "https://files.pythonhosted.org/packages/cf/90/36be0865f16dfed20f4bc7f75235b963d5939707d4b591f086777412ff7b/numpy-2.3.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3ef07ec8cbc8fc9e369c8dcd52019510c12da4de81367d8b20bc692aa07573a", size = 16701914, upload-time = "2025-07-24T20:47:32.459Z" }, + { url = "https://files.pythonhosted.org/packages/94/30/06cd055e24cb6c38e5989a9e747042b4e723535758e6153f11afea88c01b/numpy-2.3.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:27c9f90e7481275c7800dc9c24b7cc40ace3fdb970ae4d21eaff983a32f70c91", size = 16132708, upload-time = "2025-07-24T20:47:58.129Z" }, + { url = "https://files.pythonhosted.org/packages/9a/14/ecede608ea73e58267fd7cb78f42341b3b37ba576e778a1a06baffbe585c/numpy-2.3.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:07b62978075b67eee4065b166d000d457c82a1efe726cce608b9db9dd66a73a5", size = 18651678, upload-time = "2025-07-24T20:48:25.402Z" }, + { url = "https://files.pythonhosted.org/packages/40/f3/2fe6066b8d07c3685509bc24d56386534c008b462a488b7f503ba82b8923/numpy-2.3.2-cp313-cp313t-win32.whl", hash = "sha256:c771cfac34a4f2c0de8e8c97312d07d64fd8f8ed45bc9f5726a7e947270152b5", size = 6441832, upload-time = "2025-07-24T20:48:37.181Z" }, + { url = "https://files.pythonhosted.org/packages/0b/ba/0937d66d05204d8f28630c9c60bc3eda68824abde4cf756c4d6aad03b0c6/numpy-2.3.2-cp313-cp313t-win_amd64.whl", hash = "sha256:72dbebb2dcc8305c431b2836bcc66af967df91be793d63a24e3d9b741374c450", size = 12927049, upload-time = "2025-07-24T20:48:56.24Z" }, + { url = "https://files.pythonhosted.org/packages/e9/ed/13542dd59c104d5e654dfa2ac282c199ba64846a74c2c4bcdbc3a0f75df1/numpy-2.3.2-cp313-cp313t-win_arm64.whl", hash = "sha256:72c6df2267e926a6d5286b0a6d556ebe49eae261062059317837fda12ddf0c1a", size = 10262935, upload-time = "2025-07-24T20:49:13.136Z" }, + { url = "https://files.pythonhosted.org/packages/c9/7c/7659048aaf498f7611b783e000c7268fcc4dcf0ce21cd10aad7b2e8f9591/numpy-2.3.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:448a66d052d0cf14ce9865d159bfc403282c9bc7bb2a31b03cc18b651eca8b1a", size = 20950906, upload-time = "2025-07-24T20:50:30.346Z" }, + { url = "https://files.pythonhosted.org/packages/80/db/984bea9d4ddf7112a04cfdfb22b1050af5757864cfffe8e09e44b7f11a10/numpy-2.3.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:546aaf78e81b4081b2eba1d105c3b34064783027a06b3ab20b6eba21fb64132b", size = 14185607, upload-time = "2025-07-24T20:50:51.923Z" }, + { url = "https://files.pythonhosted.org/packages/e4/76/b3d6f414f4eca568f469ac112a3b510938d892bc5a6c190cb883af080b77/numpy-2.3.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:87c930d52f45df092f7578889711a0768094debf73cfcde105e2d66954358125", size = 5114110, upload-time = "2025-07-24T20:51:01.041Z" }, + { url = "https://files.pythonhosted.org/packages/9e/d2/6f5e6826abd6bca52392ed88fe44a4b52aacb60567ac3bc86c67834c3a56/numpy-2.3.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:8dc082ea901a62edb8f59713c6a7e28a85daddcb67454c839de57656478f5b19", size = 6642050, upload-time = "2025-07-24T20:51:11.64Z" }, + { url = "https://files.pythonhosted.org/packages/c4/43/f12b2ade99199e39c73ad182f103f9d9791f48d885c600c8e05927865baf/numpy-2.3.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:af58de8745f7fa9ca1c0c7c943616c6fe28e75d0c81f5c295810e3c83b5be92f", size = 14296292, upload-time = "2025-07-24T20:51:33.488Z" }, + { url = "https://files.pythonhosted.org/packages/5d/f9/77c07d94bf110a916b17210fac38680ed8734c236bfed9982fd8524a7b47/numpy-2.3.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed5527c4cf10f16c6d0b6bee1f89958bccb0ad2522c8cadc2efd318bcd545f5", size = 16638913, upload-time = "2025-07-24T20:51:58.517Z" }, + { url = "https://files.pythonhosted.org/packages/9b/d1/9d9f2c8ea399cc05cfff8a7437453bd4e7d894373a93cdc46361bbb49a7d/numpy-2.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:095737ed986e00393ec18ec0b21b47c22889ae4b0cd2d5e88342e08b01141f58", size = 16071180, upload-time = "2025-07-24T20:52:22.827Z" }, + { url = "https://files.pythonhosted.org/packages/4c/41/82e2c68aff2a0c9bf315e47d61951099fed65d8cb2c8d9dc388cb87e947e/numpy-2.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5e40e80299607f597e1a8a247ff8d71d79c5b52baa11cc1cce30aa92d2da6e0", size = 18576809, upload-time = "2025-07-24T20:52:51.015Z" }, + { url = "https://files.pythonhosted.org/packages/14/14/4b4fd3efb0837ed252d0f583c5c35a75121038a8c4e065f2c259be06d2d8/numpy-2.3.2-cp314-cp314-win32.whl", hash = "sha256:7d6e390423cc1f76e1b8108c9b6889d20a7a1f59d9a60cac4a050fa734d6c1e2", size = 6366410, upload-time = "2025-07-24T20:56:44.949Z" }, + { url = "https://files.pythonhosted.org/packages/11/9e/b4c24a6b8467b61aced5c8dc7dcfce23621baa2e17f661edb2444a418040/numpy-2.3.2-cp314-cp314-win_amd64.whl", hash = "sha256:b9d0878b21e3918d76d2209c924ebb272340da1fb51abc00f986c258cd5e957b", size = 12918821, upload-time = "2025-07-24T20:57:06.479Z" }, + { url = "https://files.pythonhosted.org/packages/0e/0f/0dc44007c70b1007c1cef86b06986a3812dd7106d8f946c09cfa75782556/numpy-2.3.2-cp314-cp314-win_arm64.whl", hash = "sha256:2738534837c6a1d0c39340a190177d7d66fdf432894f469728da901f8f6dc910", size = 10477303, upload-time = "2025-07-24T20:57:22.879Z" }, + { url = "https://files.pythonhosted.org/packages/8b/3e/075752b79140b78ddfc9c0a1634d234cfdbc6f9bbbfa6b7504e445ad7d19/numpy-2.3.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:4d002ecf7c9b53240be3bb69d80f86ddbd34078bae04d87be81c1f58466f264e", size = 21047524, upload-time = "2025-07-24T20:53:22.086Z" }, + { url = "https://files.pythonhosted.org/packages/fe/6d/60e8247564a72426570d0e0ea1151b95ce5bd2f1597bb878a18d32aec855/numpy-2.3.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:293b2192c6bcce487dbc6326de5853787f870aeb6c43f8f9c6496db5b1781e45", size = 14300519, upload-time = "2025-07-24T20:53:44.053Z" }, + { url = "https://files.pythonhosted.org/packages/4d/73/d8326c442cd428d47a067070c3ac6cc3b651a6e53613a1668342a12d4479/numpy-2.3.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:0a4f2021a6da53a0d580d6ef5db29947025ae8b35b3250141805ea9a32bbe86b", size = 5228972, upload-time = "2025-07-24T20:53:53.81Z" }, + { url = "https://files.pythonhosted.org/packages/34/2e/e71b2d6dad075271e7079db776196829019b90ce3ece5c69639e4f6fdc44/numpy-2.3.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9c144440db4bf3bb6372d2c3e49834cc0ff7bb4c24975ab33e01199e645416f2", size = 6737439, upload-time = "2025-07-24T20:54:04.742Z" }, + { url = "https://files.pythonhosted.org/packages/15/b0/d004bcd56c2c5e0500ffc65385eb6d569ffd3363cb5e593ae742749b2daa/numpy-2.3.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f92d6c2a8535dc4fe4419562294ff957f83a16ebdec66df0805e473ffaad8bd0", size = 14352479, upload-time = "2025-07-24T20:54:25.819Z" }, + { url = "https://files.pythonhosted.org/packages/11/e3/285142fcff8721e0c99b51686426165059874c150ea9ab898e12a492e291/numpy-2.3.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cefc2219baa48e468e3db7e706305fcd0c095534a192a08f31e98d83a7d45fb0", size = 16702805, upload-time = "2025-07-24T20:54:50.814Z" }, + { url = "https://files.pythonhosted.org/packages/33/c3/33b56b0e47e604af2c7cd065edca892d180f5899599b76830652875249a3/numpy-2.3.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:76c3e9501ceb50b2ff3824c3589d5d1ab4ac857b0ee3f8f49629d0de55ecf7c2", size = 16133830, upload-time = "2025-07-24T20:55:17.306Z" }, + { url = "https://files.pythonhosted.org/packages/6e/ae/7b1476a1f4d6a48bc669b8deb09939c56dd2a439db1ab03017844374fb67/numpy-2.3.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:122bf5ed9a0221b3419672493878ba4967121514b1d7d4656a7580cd11dddcbf", size = 18652665, upload-time = "2025-07-24T20:55:46.665Z" }, + { url = "https://files.pythonhosted.org/packages/14/ba/5b5c9978c4bb161034148ade2de9db44ec316fab89ce8c400db0e0c81f86/numpy-2.3.2-cp314-cp314t-win32.whl", hash = "sha256:6f1ae3dcb840edccc45af496f312528c15b1f79ac318169d094e85e4bb35fdf1", size = 6514777, upload-time = "2025-07-24T20:55:57.66Z" }, + { url = "https://files.pythonhosted.org/packages/eb/46/3dbaf0ae7c17cdc46b9f662c56da2054887b8d9e737c1476f335c83d33db/numpy-2.3.2-cp314-cp314t-win_amd64.whl", hash = "sha256:087ffc25890d89a43536f75c5fe8770922008758e8eeeef61733957041ed2f9b", size = 13111856, upload-time = "2025-07-24T20:56:17.318Z" }, + { url = "https://files.pythonhosted.org/packages/c1/9e/1652778bce745a67b5fe05adde60ed362d38eb17d919a540e813d30f6874/numpy-2.3.2-cp314-cp314t-win_arm64.whl", hash = "sha256:092aeb3449833ea9c0bf0089d70c29ae480685dd2377ec9cdbbb620257f84631", size = 10544226, upload-time = "2025-07-24T20:56:34.509Z" }, + { url = "https://files.pythonhosted.org/packages/cf/ea/50ebc91d28b275b23b7128ef25c3d08152bc4068f42742867e07a870a42a/numpy-2.3.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:14a91ebac98813a49bc6aa1a0dfc09513dcec1d97eaf31ca21a87221a1cdcb15", size = 21130338, upload-time = "2025-07-24T20:57:54.37Z" }, + { url = "https://files.pythonhosted.org/packages/9f/57/cdd5eac00dd5f137277355c318a955c0d8fb8aa486020c22afd305f8b88f/numpy-2.3.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:71669b5daae692189540cffc4c439468d35a3f84f0c88b078ecd94337f6cb0ec", size = 14375776, upload-time = "2025-07-24T20:58:16.303Z" }, + { url = "https://files.pythonhosted.org/packages/83/85/27280c7f34fcd305c2209c0cdca4d70775e4859a9eaa92f850087f8dea50/numpy-2.3.2-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:69779198d9caee6e547adb933941ed7520f896fd9656834c300bdf4dd8642712", size = 5304882, upload-time = "2025-07-24T20:58:26.199Z" }, + { url = "https://files.pythonhosted.org/packages/48/b4/6500b24d278e15dd796f43824e69939d00981d37d9779e32499e823aa0aa/numpy-2.3.2-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2c3271cc4097beb5a60f010bcc1cc204b300bb3eafb4399376418a83a1c6373c", size = 6818405, upload-time = "2025-07-24T20:58:37.341Z" }, + { url = "https://files.pythonhosted.org/packages/9b/c9/142c1e03f199d202da8e980c2496213509291b6024fd2735ad28ae7065c7/numpy-2.3.2-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8446acd11fe3dc1830568c941d44449fd5cb83068e5c70bd5a470d323d448296", size = 14419651, upload-time = "2025-07-24T20:58:59.048Z" }, + { url = "https://files.pythonhosted.org/packages/8b/95/8023e87cbea31a750a6c00ff9427d65ebc5fef104a136bfa69f76266d614/numpy-2.3.2-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aa098a5ab53fa407fded5870865c6275a5cd4101cfdef8d6fafc48286a96e981", size = 16760166, upload-time = "2025-07-24T21:28:56.38Z" }, + { url = "https://files.pythonhosted.org/packages/78/e3/6690b3f85a05506733c7e90b577e4762517404ea78bab2ca3a5cb1aeb78d/numpy-2.3.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:6936aff90dda378c09bea075af0d9c675fe3a977a9d2402f95a87f440f59f619", size = 12977811, upload-time = "2025-07-24T21:29:18.234Z" }, ] [[package]] @@ -2253,35 +1993,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e3/94/1843518e420fa3ed6919835845df698c7e27e183cb997394e4a670973a65/omegaconf-2.3.0-py3-none-any.whl", hash = "sha256:7b4df175cdb08ba400f45cae3bdcae7ba8365db4d165fc65fd04b050ab63b46b", size = 79500, upload-time = "2022-12-08T20:59:19.686Z" }, ] -[[package]] -name = "onnxruntime" -version = "1.22.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "coloredlogs" }, - { name = "flatbuffers" }, - { name = "numpy" }, - { name = "packaging" }, - { name = "protobuf" }, - { name = "sympy" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/08/c008711d1b92ff1272f4fea0fbee57723171f161d42e5c680625535280af/onnxruntime-1.22.0-cp311-cp311-macosx_13_0_universal2.whl", hash = "sha256:8d6725c5b9a681d8fe72f2960c191a96c256367887d076b08466f52b4e0991df", size = 34282151, upload-time = "2025-05-09T20:25:59.246Z" }, - { url = "https://files.pythonhosted.org/packages/3e/8b/22989f6b59bc4ad1324f07a945c80b9ab825f0a581ad7a6064b93716d9b7/onnxruntime-1.22.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fef17d665a917866d1f68f09edc98223b9a27e6cb167dec69da4c66484ad12fd", size = 14446302, upload-time = "2025-05-09T20:25:44.299Z" }, - { url = "https://files.pythonhosted.org/packages/7a/d5/aa83d084d05bc8f6cf8b74b499c77431ffd6b7075c761ec48ec0c161a47f/onnxruntime-1.22.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b978aa63a9a22095479c38371a9b359d4c15173cbb164eaad5f2cd27d666aa65", size = 16393496, upload-time = "2025-05-09T20:26:11.588Z" }, - { url = "https://files.pythonhosted.org/packages/89/a5/1c6c10322201566015183b52ef011dfa932f5dd1b278de8d75c3b948411d/onnxruntime-1.22.0-cp311-cp311-win_amd64.whl", hash = "sha256:03d3ef7fb11adf154149d6e767e21057e0e577b947dd3f66190b212528e1db31", size = 12691517, upload-time = "2025-05-12T21:26:13.354Z" }, - { url = "https://files.pythonhosted.org/packages/4d/de/9162872c6e502e9ac8c99a98a8738b2fab408123d11de55022ac4f92562a/onnxruntime-1.22.0-cp312-cp312-macosx_13_0_universal2.whl", hash = "sha256:f3c0380f53c1e72a41b3f4d6af2ccc01df2c17844072233442c3a7e74851ab97", size = 34298046, upload-time = "2025-05-09T20:26:02.399Z" }, - { url = "https://files.pythonhosted.org/packages/03/79/36f910cd9fc96b444b0e728bba14607016079786adf032dae61f7c63b4aa/onnxruntime-1.22.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c8601128eaef79b636152aea76ae6981b7c9fc81a618f584c15d78d42b310f1c", size = 14443220, upload-time = "2025-05-09T20:25:47.078Z" }, - { url = "https://files.pythonhosted.org/packages/8c/60/16d219b8868cc8e8e51a68519873bdb9f5f24af080b62e917a13fff9989b/onnxruntime-1.22.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6964a975731afc19dc3418fad8d4e08c48920144ff590149429a5ebe0d15fb3c", size = 16406377, upload-time = "2025-05-09T20:26:14.478Z" }, - { url = "https://files.pythonhosted.org/packages/36/b4/3f1c71ce1d3d21078a6a74c5483bfa2b07e41a8d2b8fb1e9993e6a26d8d3/onnxruntime-1.22.0-cp312-cp312-win_amd64.whl", hash = "sha256:c0d534a43d1264d1273c2d4f00a5a588fa98d21117a3345b7104fa0bbcaadb9a", size = 12692233, upload-time = "2025-05-12T21:26:16.963Z" }, - { url = "https://files.pythonhosted.org/packages/a9/65/5cb5018d5b0b7cba820d2c4a1d1b02d40df538d49138ba36a509457e4df6/onnxruntime-1.22.0-cp313-cp313-macosx_13_0_universal2.whl", hash = "sha256:fe7c051236aae16d8e2e9ffbfc1e115a0cc2450e873a9c4cb75c0cc96c1dae07", size = 34298715, upload-time = "2025-05-09T20:26:05.634Z" }, - { url = "https://files.pythonhosted.org/packages/e1/89/1dfe1b368831d1256b90b95cb8d11da8ab769febd5c8833ec85ec1f79d21/onnxruntime-1.22.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6a6bbed10bc5e770c04d422893d3045b81acbbadc9fb759a2cd1ca00993da919", size = 14443266, upload-time = "2025-05-09T20:25:49.479Z" }, - { url = "https://files.pythonhosted.org/packages/1e/70/342514ade3a33ad9dd505dcee96ff1f0e7be6d0e6e9c911fe0f1505abf42/onnxruntime-1.22.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9fe45ee3e756300fccfd8d61b91129a121d3d80e9d38e01f03ff1295badc32b8", size = 16406707, upload-time = "2025-05-09T20:26:17.454Z" }, - { url = "https://files.pythonhosted.org/packages/3e/89/2f64e250945fa87140fb917ba377d6d0e9122e029c8512f389a9b7f953f4/onnxruntime-1.22.0-cp313-cp313-win_amd64.whl", hash = "sha256:5a31d84ef82b4b05d794a4ce8ba37b0d9deb768fd580e36e17b39e0b4840253b", size = 12691777, upload-time = "2025-05-12T21:26:20.19Z" }, - { url = "https://files.pythonhosted.org/packages/9f/48/d61d5f1ed098161edd88c56cbac49207d7b7b149e613d2cd7e33176c63b3/onnxruntime-1.22.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a2ac5bd9205d831541db4e508e586e764a74f14efdd3f89af7fd20e1bf4a1ed", size = 14454003, upload-time = "2025-05-09T20:25:52.287Z" }, - { url = "https://files.pythonhosted.org/packages/c3/16/873b955beda7bada5b0d798d3a601b2ff210e44ad5169f6d405b93892103/onnxruntime-1.22.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64845709f9e8a2809e8e009bc4c8f73b788cee9c6619b7d9930344eae4c9cd36", size = 16427482, upload-time = "2025-05-09T20:26:20.376Z" }, -] - [[package]] name = "openai" version = "1.78.1" @@ -2303,32 +2014,45 @@ wheels = [ [[package]] name = "opentelemetry-api" -version = "1.34.1" +version = "1.36.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "importlib-metadata" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4d/5e/94a8cb759e4e409022229418294e098ca7feca00eb3c467bb20cbd329bda/opentelemetry_api-1.34.1.tar.gz", hash = "sha256:64f0bd06d42824843731d05beea88d4d4b6ae59f9fe347ff7dfa2cc14233bbb3", size = 64987, upload-time = "2025-06-10T08:55:19.818Z" } +sdist = { url = "https://files.pythonhosted.org/packages/27/d2/c782c88b8afbf961d6972428821c302bd1e9e7bc361352172f0ca31296e2/opentelemetry_api-1.36.0.tar.gz", hash = "sha256:9a72572b9c416d004d492cbc6e61962c0501eaf945ece9b5a0f56597d8348aa0", size = 64780, upload-time = "2025-07-29T15:12:06.02Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a5/3a/2ba85557e8dc024c0842ad22c570418dc02c36cbd1ab4b832a93edf071b8/opentelemetry_api-1.34.1-py3-none-any.whl", hash = "sha256:b7df4cb0830d5a6c29ad0c0691dbae874d8daefa934b8b1d642de48323d32a8c", size = 65767, upload-time = "2025-06-10T08:54:56.717Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ee/6b08dde0a022c463b88f55ae81149584b125a42183407dc1045c486cc870/opentelemetry_api-1.36.0-py3-none-any.whl", hash = "sha256:02f20bcacf666e1333b6b1f04e647dc1d5111f86b8e510238fcc56d7762cda8c", size = 65564, upload-time = "2025-07-29T15:11:47.998Z" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp" +version = "1.36.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-exporter-otlp-proto-grpc" }, + { name = "opentelemetry-exporter-otlp-proto-http" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/95/7f/d31294ac28d567a14aefd855756bab79fed69c5a75df712f228f10c47e04/opentelemetry_exporter_otlp-1.36.0.tar.gz", hash = "sha256:72f166ea5a8923ac42889337f903e93af57db8893de200369b07401e98e4e06b", size = 6144, upload-time = "2025-07-29T15:12:07.153Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/a2/8966111a285124f3d6156a663ddf2aeddd52843c1a3d6b56cbd9b6c3fd0e/opentelemetry_exporter_otlp-1.36.0-py3-none-any.whl", hash = "sha256:de93b7c45bcc78296998775d52add7c63729e83ef2cd6560730a6b336d7f6494", size = 7018, upload-time = "2025-07-29T15:11:50.498Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.34.1" +version = "1.36.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-proto" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/86/f0/ff235936ee40db93360233b62da932d4fd9e8d103cd090c6bcb9afaf5f01/opentelemetry_exporter_otlp_proto_common-1.34.1.tar.gz", hash = "sha256:b59a20a927facd5eac06edaf87a07e49f9e4a13db487b7d8a52b37cb87710f8b", size = 20817, upload-time = "2025-06-10T08:55:22.55Z" } +sdist = { url = "https://files.pythonhosted.org/packages/34/da/7747e57eb341c59886052d733072bc878424bf20f1d8cf203d508bbece5b/opentelemetry_exporter_otlp_proto_common-1.36.0.tar.gz", hash = "sha256:6c496ccbcbe26b04653cecadd92f73659b814c6e3579af157d8716e5f9f25cbf", size = 20302, upload-time = "2025-07-29T15:12:07.71Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/72/e8/8b292a11cc8d8d87ec0c4089ae21b6a58af49ca2e51fa916435bc922fdc7/opentelemetry_exporter_otlp_proto_common-1.34.1-py3-none-any.whl", hash = "sha256:8e2019284bf24d3deebbb6c59c71e6eef3307cd88eff8c633e061abba33f7e87", size = 18834, upload-time = "2025-06-10T08:55:00.806Z" }, + { url = "https://files.pythonhosted.org/packages/d0/ed/22290dca7db78eb32e0101738366b5bbda00d0407f00feffb9bf8c3fdf87/opentelemetry_exporter_otlp_proto_common-1.36.0-py3-none-any.whl", hash = "sha256:0fc002a6ed63eac235ada9aa7056e5492e9a71728214a61745f6ad04b923f840", size = 18349, upload-time = "2025-07-29T15:11:51.327Z" }, ] [[package]] name = "opentelemetry-exporter-otlp-proto-grpc" -version = "1.34.1" +version = "1.36.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "googleapis-common-protos" }, @@ -2339,101 +2063,169 @@ dependencies = [ { name = "opentelemetry-sdk" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/41/f7/bb63837a3edb9ca857aaf5760796874e7cecddc88a2571b0992865a48fb6/opentelemetry_exporter_otlp_proto_grpc-1.34.1.tar.gz", hash = "sha256:7c841b90caa3aafcfc4fee58487a6c71743c34c6dc1787089d8b0578bbd794dd", size = 22566, upload-time = "2025-06-10T08:55:23.214Z" } +sdist = { url = "https://files.pythonhosted.org/packages/72/6f/6c1b0bdd0446e5532294d1d41bf11fbaea39c8a2423a4cdfe4fe6b708127/opentelemetry_exporter_otlp_proto_grpc-1.36.0.tar.gz", hash = "sha256:b281afbf7036b325b3588b5b6c8bb175069e3978d1bd24071f4a59d04c1e5bbf", size = 23822, upload-time = "2025-07-29T15:12:08.292Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/67/5f6bd188d66d0fd8e81e681bbf5822e53eb150034e2611dd2b935d3ab61a/opentelemetry_exporter_otlp_proto_grpc-1.36.0-py3-none-any.whl", hash = "sha256:734e841fc6a5d6f30e7be4d8053adb703c70ca80c562ae24e8083a28fadef211", size = 18828, upload-time = "2025-07-29T15:11:52.235Z" }, +] + +[[package]] +name = "opentelemetry-exporter-otlp-proto-http" +version = "1.36.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "googleapis-common-protos" }, + { name = "opentelemetry-api" }, + { name = "opentelemetry-exporter-otlp-proto-common" }, + { name = "opentelemetry-proto" }, + { name = "opentelemetry-sdk" }, + { name = "requests" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/25/85/6632e7e5700ba1ce5b8a065315f92c1e6d787ccc4fb2bdab15139eaefc82/opentelemetry_exporter_otlp_proto_http-1.36.0.tar.gz", hash = "sha256:dd3637f72f774b9fc9608ab1ac479f8b44d09b6fb5b2f3df68a24ad1da7d356e", size = 16213, upload-time = "2025-07-29T15:12:08.932Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/41/a680d38b34f8f5ddbd78ed9f0042e1cc712d58ec7531924d71cb1e6c629d/opentelemetry_exporter_otlp_proto_http-1.36.0-py3-none-any.whl", hash = "sha256:3d769f68e2267e7abe4527f70deb6f598f40be3ea34c6adc35789bea94a32902", size = 18752, upload-time = "2025-07-29T15:11:53.164Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation" +version = "0.57b0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "packaging" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/12/37/cf17cf28f945a3aca5a038cfbb45ee01317d4f7f3a0e5209920883fe9b08/opentelemetry_instrumentation-0.57b0.tar.gz", hash = "sha256:f2a30135ba77cdea2b0e1df272f4163c154e978f57214795d72f40befd4fcf05", size = 30807, upload-time = "2025-07-29T15:42:44.746Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d0/6f/f20cd1542959f43fb26a5bf9bb18cd81a1ea0700e8870c8f369bd07f5c65/opentelemetry_instrumentation-0.57b0-py3-none-any.whl", hash = "sha256:9109280f44882e07cec2850db28210b90600ae9110b42824d196de357cbddf7e", size = 32460, upload-time = "2025-07-29T15:41:40.883Z" }, +] + +[[package]] +name = "opentelemetry-instrumentation-openai" +version = "0.45.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "opentelemetry-api" }, + { name = "opentelemetry-instrumentation" }, + { name = "opentelemetry-semantic-conventions" }, + { name = "opentelemetry-semantic-conventions-ai" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f6/51/9b446fbedde1fe17ea3ef5bdea2b3bcfc676663cf30c00d5bc41cfe6cef6/opentelemetry_instrumentation_openai-0.45.6.tar.gz", hash = "sha256:1781a43a85ebec186a97475de64ad1415e46e93e285b425748bc0e516a1ec6e1", size = 24581, upload-time = "2025-08-18T08:03:25.474Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/42/0a4dd47e7ef54edf670c81fc06a83d68ea42727b82126a1df9dd0477695d/opentelemetry_exporter_otlp_proto_grpc-1.34.1-py3-none-any.whl", hash = "sha256:04bb8b732b02295be79f8a86a4ad28fae3d4ddb07307a98c7aa6f331de18cca6", size = 18615, upload-time = "2025-06-10T08:55:02.214Z" }, + { url = "https://files.pythonhosted.org/packages/45/9b/dc1f7b32260a396b76620244805d5960242635e13b1c02b3ed38ce0ea80c/opentelemetry_instrumentation_openai-0.45.6-py3-none-any.whl", hash = "sha256:1aac8a53fa4565f2ea048048e43c7472e1426fb71ab67281fd36f9de0e63b6ef", size = 34373, upload-time = "2025-08-18T08:02:58.345Z" }, ] [[package]] name = "opentelemetry-proto" -version = "1.34.1" +version = "1.36.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/66/b3/c3158dd012463bb7c0eb7304a85a6f63baeeb5b4c93a53845cf89f848c7e/opentelemetry_proto-1.34.1.tar.gz", hash = "sha256:16286214e405c211fc774187f3e4bbb1351290b8dfb88e8948af209ce85b719e", size = 34344, upload-time = "2025-06-10T08:55:32.25Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/02/f6556142301d136e3b7e95ab8ea6a5d9dc28d879a99f3dd673b5f97dca06/opentelemetry_proto-1.36.0.tar.gz", hash = "sha256:0f10b3c72f74c91e0764a5ec88fd8f1c368ea5d9c64639fb455e2854ef87dd2f", size = 46152, upload-time = "2025-07-29T15:12:15.717Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/28/ab/4591bfa54e946350ce8b3f28e5c658fe9785e7cd11e9c11b1671a867822b/opentelemetry_proto-1.34.1-py3-none-any.whl", hash = "sha256:eb4bb5ac27f2562df2d6857fc557b3a481b5e298bc04f94cc68041f00cebcbd2", size = 55692, upload-time = "2025-06-10T08:55:14.904Z" }, + { url = "https://files.pythonhosted.org/packages/b3/57/3361e06136225be8180e879199caea520f38026f8071366241ac458beb8d/opentelemetry_proto-1.36.0-py3-none-any.whl", hash = "sha256:151b3bf73a09f94afc658497cf77d45a565606f62ce0c17acb08cd9937ca206e", size = 72537, upload-time = "2025-07-29T15:12:02.243Z" }, ] [[package]] name = "opentelemetry-sdk" -version = "1.34.1" +version = "1.36.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "opentelemetry-semantic-conventions" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6f/41/fe20f9036433da8e0fcef568984da4c1d1c771fa072ecd1a4d98779dccdd/opentelemetry_sdk-1.34.1.tar.gz", hash = "sha256:8091db0d763fcd6098d4781bbc80ff0971f94e260739aa6afe6fd379cdf3aa4d", size = 159441, upload-time = "2025-06-10T08:55:33.028Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/85/8567a966b85a2d3f971c4d42f781c305b2b91c043724fa08fd37d158e9dc/opentelemetry_sdk-1.36.0.tar.gz", hash = "sha256:19c8c81599f51b71670661ff7495c905d8fdf6976e41622d5245b791b06fa581", size = 162557, upload-time = "2025-07-29T15:12:16.76Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/07/1b/def4fe6aa73f483cabf4c748f4c25070d5f7604dcc8b52e962983491b29e/opentelemetry_sdk-1.34.1-py3-none-any.whl", hash = "sha256:308effad4059562f1d92163c61c8141df649da24ce361827812c40abb2a1e96e", size = 118477, upload-time = "2025-06-10T08:55:16.02Z" }, + { url = "https://files.pythonhosted.org/packages/0b/59/7bed362ad1137ba5886dac8439e84cd2df6d087be7c09574ece47ae9b22c/opentelemetry_sdk-1.36.0-py3-none-any.whl", hash = "sha256:19fe048b42e98c5c1ffe85b569b7073576ad4ce0bcb6e9b4c6a39e890a6c45fb", size = 119995, upload-time = "2025-07-29T15:12:03.181Z" }, ] [[package]] name = "opentelemetry-semantic-conventions" -version = "0.55b1" +version = "0.57b0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "opentelemetry-api" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5d/f0/f33458486da911f47c4aa6db9bda308bb80f3236c111bf848bd870c16b16/opentelemetry_semantic_conventions-0.55b1.tar.gz", hash = "sha256:ef95b1f009159c28d7a7849f5cbc71c4c34c845bb514d66adfdf1b3fff3598b3", size = 119829, upload-time = "2025-06-10T08:55:33.881Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/31/67dfa252ee88476a29200b0255bda8dfc2cf07b56ad66dc9a6221f7dc787/opentelemetry_semantic_conventions-0.57b0.tar.gz", hash = "sha256:609a4a79c7891b4620d64c7aac6898f872d790d75f22019913a660756f27ff32", size = 124225, upload-time = "2025-07-29T15:12:17.873Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/75/7d591371c6c39c73de5ce5da5a2cc7b72d1d1cd3f8f4638f553c01c37b11/opentelemetry_semantic_conventions-0.57b0-py3-none-any.whl", hash = "sha256:757f7e76293294f124c827e514c2a3144f191ef175b069ce8d1211e1e38e9e78", size = 201627, upload-time = "2025-07-29T15:12:04.174Z" }, +] + +[[package]] +name = "opentelemetry-semantic-conventions-ai" +version = "0.4.12" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2e/e3/8d9eb40475a8626accba9afb615183bad64f1da8ca0cec29333d566c4d7f/opentelemetry_semantic_conventions_ai-0.4.12.tar.gz", hash = "sha256:d5285a16d9ac856163a27f9387e8c644b555f35370b50eb2b1d0676e5daad1b4", size = 5166, upload-time = "2025-07-28T11:27:39.908Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/89/267b0af1b1d0ba828f0e60642b6a5116ac1fd917cde7fc02821627029bd1/opentelemetry_semantic_conventions-0.55b1-py3-none-any.whl", hash = "sha256:5da81dfdf7d52e3d37f8fe88d5e771e191de924cfff5f550ab0b8f7b2409baed", size = 196223, upload-time = "2025-06-10T08:55:17.638Z" }, + { url = "https://files.pythonhosted.org/packages/4e/11/f6a71924081ee95691259c17fdafc4b8a52fe1072284809cb251c0de6f21/opentelemetry_semantic_conventions_ai-0.4.12-py3-none-any.whl", hash = "sha256:89a37ef99354f63c72d060d78343de426bb0df408f5795d325f0e34336f41e7e", size = 6030, upload-time = "2025-07-28T11:27:38.962Z" }, ] [[package]] name = "orjson" -version = "3.10.18" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/81/0b/fea456a3ffe74e70ba30e01ec183a9b26bec4d497f61dcfce1b601059c60/orjson-3.10.18.tar.gz", hash = "sha256:e8da3947d92123eda795b68228cafe2724815621fe35e8e320a9e9593a4bcd53", size = 5422810, upload-time = "2025-04-29T23:30:08.423Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/97/c7/c54a948ce9a4278794f669a353551ce7db4ffb656c69a6e1f2264d563e50/orjson-3.10.18-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:e0a183ac3b8e40471e8d843105da6fbe7c070faab023be3b08188ee3f85719b8", size = 248929, upload-time = "2025-04-29T23:28:30.716Z" }, - { url = "https://files.pythonhosted.org/packages/9e/60/a9c674ef1dd8ab22b5b10f9300e7e70444d4e3cda4b8258d6c2488c32143/orjson-3.10.18-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:5ef7c164d9174362f85238d0cd4afdeeb89d9e523e4651add6a5d458d6f7d42d", size = 133364, upload-time = "2025-04-29T23:28:32.392Z" }, - { url = "https://files.pythonhosted.org/packages/c1/4e/f7d1bdd983082216e414e6d7ef897b0c2957f99c545826c06f371d52337e/orjson-3.10.18-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afd14c5d99cdc7bf93f22b12ec3b294931518aa019e2a147e8aa2f31fd3240f7", size = 136995, upload-time = "2025-04-29T23:28:34.024Z" }, - { url = "https://files.pythonhosted.org/packages/17/89/46b9181ba0ea251c9243b0c8ce29ff7c9796fa943806a9c8b02592fce8ea/orjson-3.10.18-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b672502323b6cd133c4af6b79e3bea36bad2d16bca6c1f645903fce83909a7a", size = 132894, upload-time = "2025-04-29T23:28:35.318Z" }, - { url = "https://files.pythonhosted.org/packages/ca/dd/7bce6fcc5b8c21aef59ba3c67f2166f0a1a9b0317dcca4a9d5bd7934ecfd/orjson-3.10.18-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:51f8c63be6e070ec894c629186b1c0fe798662b8687f3d9fdfa5e401c6bd7679", size = 137016, upload-time = "2025-04-29T23:28:36.674Z" }, - { url = "https://files.pythonhosted.org/packages/1c/4a/b8aea1c83af805dcd31c1f03c95aabb3e19a016b2a4645dd822c5686e94d/orjson-3.10.18-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f9478ade5313d724e0495d167083c6f3be0dd2f1c9c8a38db9a9e912cdaf947", size = 138290, upload-time = "2025-04-29T23:28:38.3Z" }, - { url = "https://files.pythonhosted.org/packages/36/d6/7eb05c85d987b688707f45dcf83c91abc2251e0dd9fb4f7be96514f838b1/orjson-3.10.18-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:187aefa562300a9d382b4b4eb9694806e5848b0cedf52037bb5c228c61bb66d4", size = 142829, upload-time = "2025-04-29T23:28:39.657Z" }, - { url = "https://files.pythonhosted.org/packages/d2/78/ddd3ee7873f2b5f90f016bc04062713d567435c53ecc8783aab3a4d34915/orjson-3.10.18-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9da552683bc9da222379c7a01779bddd0ad39dd699dd6300abaf43eadee38334", size = 132805, upload-time = "2025-04-29T23:28:40.969Z" }, - { url = "https://files.pythonhosted.org/packages/8c/09/c8e047f73d2c5d21ead9c180203e111cddeffc0848d5f0f974e346e21c8e/orjson-3.10.18-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e450885f7b47a0231979d9c49b567ed1c4e9f69240804621be87c40bc9d3cf17", size = 135008, upload-time = "2025-04-29T23:28:42.284Z" }, - { url = "https://files.pythonhosted.org/packages/0c/4b/dccbf5055ef8fb6eda542ab271955fc1f9bf0b941a058490293f8811122b/orjson-3.10.18-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:5e3c9cc2ba324187cd06287ca24f65528f16dfc80add48dc99fa6c836bb3137e", size = 413419, upload-time = "2025-04-29T23:28:43.673Z" }, - { url = "https://files.pythonhosted.org/packages/8a/f3/1eac0c5e2d6d6790bd2025ebfbefcbd37f0d097103d76f9b3f9302af5a17/orjson-3.10.18-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:50ce016233ac4bfd843ac5471e232b865271d7d9d44cf9d33773bcd883ce442b", size = 153292, upload-time = "2025-04-29T23:28:45.573Z" }, - { url = "https://files.pythonhosted.org/packages/1f/b4/ef0abf64c8f1fabf98791819ab502c2c8c1dc48b786646533a93637d8999/orjson-3.10.18-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b3ceff74a8f7ffde0b2785ca749fc4e80e4315c0fd887561144059fb1c138aa7", size = 137182, upload-time = "2025-04-29T23:28:47.229Z" }, - { url = "https://files.pythonhosted.org/packages/a9/a3/6ea878e7b4a0dc5c888d0370d7752dcb23f402747d10e2257478d69b5e63/orjson-3.10.18-cp311-cp311-win32.whl", hash = "sha256:fdba703c722bd868c04702cac4cb8c6b8ff137af2623bc0ddb3b3e6a2c8996c1", size = 142695, upload-time = "2025-04-29T23:28:48.564Z" }, - { url = "https://files.pythonhosted.org/packages/79/2a/4048700a3233d562f0e90d5572a849baa18ae4e5ce4c3ba6247e4ece57b0/orjson-3.10.18-cp311-cp311-win_amd64.whl", hash = "sha256:c28082933c71ff4bc6ccc82a454a2bffcef6e1d7379756ca567c772e4fb3278a", size = 134603, upload-time = "2025-04-29T23:28:50.442Z" }, - { url = "https://files.pythonhosted.org/packages/03/45/10d934535a4993d27e1c84f1810e79ccf8b1b7418cef12151a22fe9bb1e1/orjson-3.10.18-cp311-cp311-win_arm64.whl", hash = "sha256:a6c7c391beaedd3fa63206e5c2b7b554196f14debf1ec9deb54b5d279b1b46f5", size = 131400, upload-time = "2025-04-29T23:28:51.838Z" }, - { url = "https://files.pythonhosted.org/packages/21/1a/67236da0916c1a192d5f4ccbe10ec495367a726996ceb7614eaa687112f2/orjson-3.10.18-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:50c15557afb7f6d63bc6d6348e0337a880a04eaa9cd7c9d569bcb4e760a24753", size = 249184, upload-time = "2025-04-29T23:28:53.612Z" }, - { url = "https://files.pythonhosted.org/packages/b3/bc/c7f1db3b1d094dc0c6c83ed16b161a16c214aaa77f311118a93f647b32dc/orjson-3.10.18-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:356b076f1662c9813d5fa56db7d63ccceef4c271b1fb3dd522aca291375fcf17", size = 133279, upload-time = "2025-04-29T23:28:55.055Z" }, - { url = "https://files.pythonhosted.org/packages/af/84/664657cd14cc11f0d81e80e64766c7ba5c9b7fc1ec304117878cc1b4659c/orjson-3.10.18-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:559eb40a70a7494cd5beab2d73657262a74a2c59aff2068fdba8f0424ec5b39d", size = 136799, upload-time = "2025-04-29T23:28:56.828Z" }, - { url = "https://files.pythonhosted.org/packages/9a/bb/f50039c5bb05a7ab024ed43ba25d0319e8722a0ac3babb0807e543349978/orjson-3.10.18-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f3c29eb9a81e2fbc6fd7ddcfba3e101ba92eaff455b8d602bf7511088bbc0eae", size = 132791, upload-time = "2025-04-29T23:28:58.751Z" }, - { url = "https://files.pythonhosted.org/packages/93/8c/ee74709fc072c3ee219784173ddfe46f699598a1723d9d49cbc78d66df65/orjson-3.10.18-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6612787e5b0756a171c7d81ba245ef63a3533a637c335aa7fcb8e665f4a0966f", size = 137059, upload-time = "2025-04-29T23:29:00.129Z" }, - { url = "https://files.pythonhosted.org/packages/6a/37/e6d3109ee004296c80426b5a62b47bcadd96a3deab7443e56507823588c5/orjson-3.10.18-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ac6bd7be0dcab5b702c9d43d25e70eb456dfd2e119d512447468f6405b4a69c", size = 138359, upload-time = "2025-04-29T23:29:01.704Z" }, - { url = "https://files.pythonhosted.org/packages/4f/5d/387dafae0e4691857c62bd02839a3bf3fa648eebd26185adfac58d09f207/orjson-3.10.18-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9f72f100cee8dde70100406d5c1abba515a7df926d4ed81e20a9730c062fe9ad", size = 142853, upload-time = "2025-04-29T23:29:03.576Z" }, - { url = "https://files.pythonhosted.org/packages/27/6f/875e8e282105350b9a5341c0222a13419758545ae32ad6e0fcf5f64d76aa/orjson-3.10.18-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9dca85398d6d093dd41dc0983cbf54ab8e6afd1c547b6b8a311643917fbf4e0c", size = 133131, upload-time = "2025-04-29T23:29:05.753Z" }, - { url = "https://files.pythonhosted.org/packages/48/b2/73a1f0b4790dcb1e5a45f058f4f5dcadc8a85d90137b50d6bbc6afd0ae50/orjson-3.10.18-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:22748de2a07fcc8781a70edb887abf801bb6142e6236123ff93d12d92db3d406", size = 134834, upload-time = "2025-04-29T23:29:07.35Z" }, - { url = "https://files.pythonhosted.org/packages/56/f5/7ed133a5525add9c14dbdf17d011dd82206ca6840811d32ac52a35935d19/orjson-3.10.18-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:3a83c9954a4107b9acd10291b7f12a6b29e35e8d43a414799906ea10e75438e6", size = 413368, upload-time = "2025-04-29T23:29:09.301Z" }, - { url = "https://files.pythonhosted.org/packages/11/7c/439654221ed9c3324bbac7bdf94cf06a971206b7b62327f11a52544e4982/orjson-3.10.18-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:303565c67a6c7b1f194c94632a4a39918e067bd6176a48bec697393865ce4f06", size = 153359, upload-time = "2025-04-29T23:29:10.813Z" }, - { url = "https://files.pythonhosted.org/packages/48/e7/d58074fa0cc9dd29a8fa2a6c8d5deebdfd82c6cfef72b0e4277c4017563a/orjson-3.10.18-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:86314fdb5053a2f5a5d881f03fca0219bfdf832912aa88d18676a5175c6916b5", size = 137466, upload-time = "2025-04-29T23:29:12.26Z" }, - { url = "https://files.pythonhosted.org/packages/57/4d/fe17581cf81fb70dfcef44e966aa4003360e4194d15a3f38cbffe873333a/orjson-3.10.18-cp312-cp312-win32.whl", hash = "sha256:187ec33bbec58c76dbd4066340067d9ece6e10067bb0cc074a21ae3300caa84e", size = 142683, upload-time = "2025-04-29T23:29:13.865Z" }, - { url = "https://files.pythonhosted.org/packages/e6/22/469f62d25ab5f0f3aee256ea732e72dc3aab6d73bac777bd6277955bceef/orjson-3.10.18-cp312-cp312-win_amd64.whl", hash = "sha256:f9f94cf6d3f9cd720d641f8399e390e7411487e493962213390d1ae45c7814fc", size = 134754, upload-time = "2025-04-29T23:29:15.338Z" }, - { url = "https://files.pythonhosted.org/packages/10/b0/1040c447fac5b91bc1e9c004b69ee50abb0c1ffd0d24406e1350c58a7fcb/orjson-3.10.18-cp312-cp312-win_arm64.whl", hash = "sha256:3d600be83fe4514944500fa8c2a0a77099025ec6482e8087d7659e891f23058a", size = 131218, upload-time = "2025-04-29T23:29:17.324Z" }, - { url = "https://files.pythonhosted.org/packages/04/f0/8aedb6574b68096f3be8f74c0b56d36fd94bcf47e6c7ed47a7bd1474aaa8/orjson-3.10.18-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:69c34b9441b863175cc6a01f2935de994025e773f814412030f269da4f7be147", size = 249087, upload-time = "2025-04-29T23:29:19.083Z" }, - { url = "https://files.pythonhosted.org/packages/bc/f7/7118f965541aeac6844fcb18d6988e111ac0d349c9b80cda53583e758908/orjson-3.10.18-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:1ebeda919725f9dbdb269f59bc94f861afbe2a27dce5608cdba2d92772364d1c", size = 133273, upload-time = "2025-04-29T23:29:20.602Z" }, - { url = "https://files.pythonhosted.org/packages/fb/d9/839637cc06eaf528dd8127b36004247bf56e064501f68df9ee6fd56a88ee/orjson-3.10.18-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5adf5f4eed520a4959d29ea80192fa626ab9a20b2ea13f8f6dc58644f6927103", size = 136779, upload-time = "2025-04-29T23:29:22.062Z" }, - { url = "https://files.pythonhosted.org/packages/2b/6d/f226ecfef31a1f0e7d6bf9a31a0bbaf384c7cbe3fce49cc9c2acc51f902a/orjson-3.10.18-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7592bb48a214e18cd670974f289520f12b7aed1fa0b2e2616b8ed9e069e08595", size = 132811, upload-time = "2025-04-29T23:29:23.602Z" }, - { url = "https://files.pythonhosted.org/packages/73/2d/371513d04143c85b681cf8f3bce743656eb5b640cb1f461dad750ac4b4d4/orjson-3.10.18-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f872bef9f042734110642b7a11937440797ace8c87527de25e0c53558b579ccc", size = 137018, upload-time = "2025-04-29T23:29:25.094Z" }, - { url = "https://files.pythonhosted.org/packages/69/cb/a4d37a30507b7a59bdc484e4a3253c8141bf756d4e13fcc1da760a0b00cb/orjson-3.10.18-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0315317601149c244cb3ecef246ef5861a64824ccbcb8018d32c66a60a84ffbc", size = 138368, upload-time = "2025-04-29T23:29:26.609Z" }, - { url = "https://files.pythonhosted.org/packages/1e/ae/cd10883c48d912d216d541eb3db8b2433415fde67f620afe6f311f5cd2ca/orjson-3.10.18-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e0da26957e77e9e55a6c2ce2e7182a36a6f6b180ab7189315cb0995ec362e049", size = 142840, upload-time = "2025-04-29T23:29:28.153Z" }, - { url = "https://files.pythonhosted.org/packages/6d/4c/2bda09855c6b5f2c055034c9eda1529967b042ff8d81a05005115c4e6772/orjson-3.10.18-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb70d489bc79b7519e5803e2cc4c72343c9dc1154258adf2f8925d0b60da7c58", size = 133135, upload-time = "2025-04-29T23:29:29.726Z" }, - { url = "https://files.pythonhosted.org/packages/13/4a/35971fd809a8896731930a80dfff0b8ff48eeb5d8b57bb4d0d525160017f/orjson-3.10.18-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e9e86a6af31b92299b00736c89caf63816f70a4001e750bda179e15564d7a034", size = 134810, upload-time = "2025-04-29T23:29:31.269Z" }, - { url = "https://files.pythonhosted.org/packages/99/70/0fa9e6310cda98365629182486ff37a1c6578e34c33992df271a476ea1cd/orjson-3.10.18-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:c382a5c0b5931a5fc5405053d36c1ce3fd561694738626c77ae0b1dfc0242ca1", size = 413491, upload-time = "2025-04-29T23:29:33.315Z" }, - { url = "https://files.pythonhosted.org/packages/32/cb/990a0e88498babddb74fb97855ae4fbd22a82960e9b06eab5775cac435da/orjson-3.10.18-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8e4b2ae732431127171b875cb2668f883e1234711d3c147ffd69fe5be51a8012", size = 153277, upload-time = "2025-04-29T23:29:34.946Z" }, - { url = "https://files.pythonhosted.org/packages/92/44/473248c3305bf782a384ed50dd8bc2d3cde1543d107138fd99b707480ca1/orjson-3.10.18-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2d808e34ddb24fc29a4d4041dcfafbae13e129c93509b847b14432717d94b44f", size = 137367, upload-time = "2025-04-29T23:29:36.52Z" }, - { url = "https://files.pythonhosted.org/packages/ad/fd/7f1d3edd4ffcd944a6a40e9f88af2197b619c931ac4d3cfba4798d4d3815/orjson-3.10.18-cp313-cp313-win32.whl", hash = "sha256:ad8eacbb5d904d5591f27dee4031e2c1db43d559edb8f91778efd642d70e6bea", size = 142687, upload-time = "2025-04-29T23:29:38.292Z" }, - { url = "https://files.pythonhosted.org/packages/4b/03/c75c6ad46be41c16f4cfe0352a2d1450546f3c09ad2c9d341110cd87b025/orjson-3.10.18-cp313-cp313-win_amd64.whl", hash = "sha256:aed411bcb68bf62e85588f2a7e03a6082cc42e5a2796e06e72a962d7c6310b52", size = 134794, upload-time = "2025-04-29T23:29:40.349Z" }, - { url = "https://files.pythonhosted.org/packages/c2/28/f53038a5a72cc4fd0b56c1eafb4ef64aec9685460d5ac34de98ca78b6e29/orjson-3.10.18-cp313-cp313-win_arm64.whl", hash = "sha256:f54c1385a0e6aba2f15a40d703b858bedad36ded0491e55d35d905b2c34a4cc3", size = 131186, upload-time = "2025-04-29T23:29:41.922Z" }, +version = "3.11.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/1d/5e0ae38788bdf0721326695e65fdf41405ed535f633eb0df0f06f57552fa/orjson-3.11.2.tar.gz", hash = "sha256:91bdcf5e69a8fd8e8bdb3de32b31ff01d2bd60c1e8d5fe7d5afabdcf19920309", size = 5470739, upload-time = "2025-08-12T15:12:28.626Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/7d/e295df1ac9920cbb19fb4c1afa800e86f175cb657143aa422337270a4782/orjson-3.11.2-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:888b64ef7eaeeff63f773881929434a5834a6a140a63ad45183d59287f07fc6a", size = 226502, upload-time = "2025-08-12T15:10:42.284Z" }, + { url = "https://files.pythonhosted.org/packages/65/21/ffb0f10ea04caf418fb4e7ad1fda4b9ab3179df9d7a33b69420f191aadd5/orjson-3.11.2-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:83387cc8b26c9fa0ae34d1ea8861a7ae6cff8fb3e346ab53e987d085315a728e", size = 115999, upload-time = "2025-08-12T15:10:43.738Z" }, + { url = "https://files.pythonhosted.org/packages/90/d5/8da1e252ac3353d92e6f754ee0c85027c8a2cda90b6899da2be0df3ef83d/orjson-3.11.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7e35f003692c216d7ee901b6b916b5734d6fc4180fcaa44c52081f974c08e17", size = 111563, upload-time = "2025-08-12T15:10:45.301Z" }, + { url = "https://files.pythonhosted.org/packages/4f/81/baabc32e52c570b0e4e1044b1bd2ccbec965e0de3ba2c13082255efa2006/orjson-3.11.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4a0a4c29ae90b11d0c00bcc31533854d89f77bde2649ec602f512a7e16e00640", size = 116222, upload-time = "2025-08-12T15:10:46.92Z" }, + { url = "https://files.pythonhosted.org/packages/8d/b7/da2ad55ad80b49b560dce894c961477d0e76811ee6e614b301de9f2f8728/orjson-3.11.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:585d712b1880f68370108bc5534a257b561672d1592fae54938738fe7f6f1e33", size = 118594, upload-time = "2025-08-12T15:10:48.488Z" }, + { url = "https://files.pythonhosted.org/packages/61/be/014f7eab51449f3c894aa9bbda2707b5340c85650cb7d0db4ec9ae280501/orjson-3.11.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d08e342a7143f8a7c11f1c4033efe81acbd3c98c68ba1b26b96080396019701f", size = 120700, upload-time = "2025-08-12T15:10:49.811Z" }, + { url = "https://files.pythonhosted.org/packages/cf/ae/c217903a30c51341868e2d8c318c59a8413baa35af54d7845071c8ccd6fe/orjson-3.11.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:29c0f84fc50398773a702732c87cd622737bf11c0721e6db3041ac7802a686fb", size = 123433, upload-time = "2025-08-12T15:10:51.06Z" }, + { url = "https://files.pythonhosted.org/packages/57/c2/b3c346f78b1ff2da310dd300cb0f5d32167f872b4d3bb1ad122c889d97b0/orjson-3.11.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:140f84e3c8d4c142575898c91e3981000afebf0333df753a90b3435d349a5fe5", size = 121061, upload-time = "2025-08-12T15:10:52.381Z" }, + { url = "https://files.pythonhosted.org/packages/00/c8/c97798f6010327ffc75ad21dd6bca11ea2067d1910777e798c2849f1c68f/orjson-3.11.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96304a2b7235e0f3f2d9363ddccdbfb027d27338722fe469fe656832a017602e", size = 119410, upload-time = "2025-08-12T15:10:53.692Z" }, + { url = "https://files.pythonhosted.org/packages/37/fd/df720f7c0e35694617b7f95598b11a2cb0374661d8389703bea17217da53/orjson-3.11.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:3d7612bb227d5d9582f1f50a60bd55c64618fc22c4a32825d233a4f2771a428a", size = 392294, upload-time = "2025-08-12T15:10:55.079Z" }, + { url = "https://files.pythonhosted.org/packages/ba/52/0120d18f60ab0fe47531d520372b528a45c9a25dcab500f450374421881c/orjson-3.11.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a134587d18fe493befc2defffef2a8d27cfcada5696cb7234de54a21903ae89a", size = 134134, upload-time = "2025-08-12T15:10:56.568Z" }, + { url = "https://files.pythonhosted.org/packages/ec/10/1f967671966598366de42f07e92b0fc694ffc66eafa4b74131aeca84915f/orjson-3.11.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0b84455e60c4bc12c1e4cbaa5cfc1acdc7775a9da9cec040e17232f4b05458bd", size = 123745, upload-time = "2025-08-12T15:10:57.907Z" }, + { url = "https://files.pythonhosted.org/packages/43/eb/76081238671461cfd0f47e0c24f408ffa66184237d56ef18c33e86abb612/orjson-3.11.2-cp311-cp311-win32.whl", hash = "sha256:f0660efeac223f0731a70884e6914a5f04d613b5ae500744c43f7bf7b78f00f9", size = 124393, upload-time = "2025-08-12T15:10:59.267Z" }, + { url = "https://files.pythonhosted.org/packages/26/76/cc598c1811ba9ba935171267b02e377fc9177489efce525d478a2999d9cc/orjson-3.11.2-cp311-cp311-win_amd64.whl", hash = "sha256:955811c8405251d9e09cbe8606ad8fdef49a451bcf5520095a5ed38c669223d8", size = 119561, upload-time = "2025-08-12T15:11:00.559Z" }, + { url = "https://files.pythonhosted.org/packages/d8/17/c48011750f0489006f7617b0a3cebc8230f36d11a34e7e9aca2085f07792/orjson-3.11.2-cp311-cp311-win_arm64.whl", hash = "sha256:2e4d423a6f838552e3a6d9ec734b729f61f88b1124fd697eab82805ea1a2a97d", size = 114186, upload-time = "2025-08-12T15:11:01.931Z" }, + { url = "https://files.pythonhosted.org/packages/40/02/46054ebe7996a8adee9640dcad7d39d76c2000dc0377efa38e55dc5cbf78/orjson-3.11.2-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:901d80d349d8452162b3aa1afb82cec5bee79a10550660bc21311cc61a4c5486", size = 226528, upload-time = "2025-08-12T15:11:03.317Z" }, + { url = "https://files.pythonhosted.org/packages/e2/c6/6b6f0b4d8aea1137436546b990f71be2cd8bd870aa2f5aa14dba0fcc95dc/orjson-3.11.2-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:cf3bd3967a360e87ee14ed82cb258b7f18c710dacf3822fb0042a14313a673a1", size = 115931, upload-time = "2025-08-12T15:11:04.759Z" }, + { url = "https://files.pythonhosted.org/packages/ae/05/4205cc97c30e82a293dd0d149b1a89b138ebe76afeca66fc129fa2aa4e6a/orjson-3.11.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26693dde66910078229a943e80eeb99fdce6cd2c26277dc80ead9f3ab97d2131", size = 111382, upload-time = "2025-08-12T15:11:06.468Z" }, + { url = "https://files.pythonhosted.org/packages/50/c7/b8a951a93caa821f9272a7c917115d825ae2e4e8768f5ddf37968ec9de01/orjson-3.11.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4ad4c8acb50a28211c33fc7ef85ddf5cb18d4636a5205fd3fa2dce0411a0e30c", size = 116271, upload-time = "2025-08-12T15:11:07.845Z" }, + { url = "https://files.pythonhosted.org/packages/17/03/1006c7f8782d5327439e26d9b0ec66500ea7b679d4bbb6b891d2834ab3ee/orjson-3.11.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:994181e7f1725bb5f2d481d7d228738e0743b16bf319ca85c29369c65913df14", size = 119086, upload-time = "2025-08-12T15:11:09.329Z" }, + { url = "https://files.pythonhosted.org/packages/44/61/57d22bc31f36a93878a6f772aea76b2184102c6993dea897656a66d18c74/orjson-3.11.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dbb79a0476393c07656b69c8e763c3cc925fa8e1d9e9b7d1f626901bb5025448", size = 120724, upload-time = "2025-08-12T15:11:10.674Z" }, + { url = "https://files.pythonhosted.org/packages/78/a9/4550e96b4c490c83aea697d5347b8f7eb188152cd7b5a38001055ca5b379/orjson-3.11.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:191ed27a1dddb305083d8716af413d7219f40ec1d4c9b0e977453b4db0d6fb6c", size = 123577, upload-time = "2025-08-12T15:11:12.015Z" }, + { url = "https://files.pythonhosted.org/packages/3a/86/09b8cb3ebd513d708ef0c92d36ac3eebda814c65c72137b0a82d6d688fc4/orjson-3.11.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0afb89f16f07220183fd00f5f297328ed0a68d8722ad1b0c8dcd95b12bc82804", size = 121195, upload-time = "2025-08-12T15:11:13.399Z" }, + { url = "https://files.pythonhosted.org/packages/37/68/7b40b39ac2c1c644d4644e706d0de6c9999764341cd85f2a9393cb387661/orjson-3.11.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6ab6e6b4e93b1573a026b6ec16fca9541354dd58e514b62c558b58554ae04307", size = 119234, upload-time = "2025-08-12T15:11:15.134Z" }, + { url = "https://files.pythonhosted.org/packages/40/7c/bb6e7267cd80c19023d44d8cbc4ea4ed5429fcd4a7eb9950f50305697a28/orjson-3.11.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:9cb23527efb61fb75527df55d20ee47989c4ee34e01a9c98ee9ede232abf6219", size = 392250, upload-time = "2025-08-12T15:11:16.604Z" }, + { url = "https://files.pythonhosted.org/packages/64/f2/6730ace05583dbca7c1b406d59f4266e48cd0d360566e71482420fb849fc/orjson-3.11.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a4dd1268e4035af21b8a09e4adf2e61f87ee7bf63b86d7bb0a237ac03fad5b45", size = 134572, upload-time = "2025-08-12T15:11:18.205Z" }, + { url = "https://files.pythonhosted.org/packages/96/0f/7d3e03a30d5aac0432882b539a65b8c02cb6dd4221ddb893babf09c424cc/orjson-3.11.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ff8b155b145eaf5a9d94d2c476fbe18d6021de93cf36c2ae2c8c5b775763f14e", size = 123869, upload-time = "2025-08-12T15:11:19.554Z" }, + { url = "https://files.pythonhosted.org/packages/45/80/1513265eba6d4a960f078f4b1d2bff94a571ab2d28c6f9835e03dfc65cc6/orjson-3.11.2-cp312-cp312-win32.whl", hash = "sha256:ae3bb10279d57872f9aba68c9931aa71ed3b295fa880f25e68da79e79453f46e", size = 124430, upload-time = "2025-08-12T15:11:20.914Z" }, + { url = "https://files.pythonhosted.org/packages/fb/61/eadf057b68a332351eeb3d89a4cc538d14f31cd8b5ec1b31a280426ccca2/orjson-3.11.2-cp312-cp312-win_amd64.whl", hash = "sha256:d026e1967239ec11a2559b4146a61d13914504b396f74510a1c4d6b19dfd8732", size = 119598, upload-time = "2025-08-12T15:11:22.372Z" }, + { url = "https://files.pythonhosted.org/packages/6b/3f/7f4b783402143d965ab7e9a2fc116fdb887fe53bdce7d3523271cd106098/orjson-3.11.2-cp312-cp312-win_arm64.whl", hash = "sha256:59f8d5ad08602711af9589375be98477d70e1d102645430b5a7985fdbf613b36", size = 114052, upload-time = "2025-08-12T15:11:23.762Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f3/0dd6b4750eb556ae4e2c6a9cb3e219ec642e9c6d95f8ebe5dc9020c67204/orjson-3.11.2-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:a079fdba7062ab396380eeedb589afb81dc6683f07f528a03b6f7aae420a0219", size = 226419, upload-time = "2025-08-12T15:11:25.517Z" }, + { url = "https://files.pythonhosted.org/packages/44/d5/e67f36277f78f2af8a4690e0c54da6b34169812f807fd1b4bfc4dbcf9558/orjson-3.11.2-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:6a5f62ebbc530bb8bb4b1ead103647b395ba523559149b91a6c545f7cd4110ad", size = 115803, upload-time = "2025-08-12T15:11:27.357Z" }, + { url = "https://files.pythonhosted.org/packages/24/37/ff8bc86e0dacc48f07c2b6e20852f230bf4435611bab65e3feae2b61f0ae/orjson-3.11.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7df6c7b8b0931feb3420b72838c3e2ba98c228f7aa60d461bc050cf4ca5f7b2", size = 111337, upload-time = "2025-08-12T15:11:28.805Z" }, + { url = "https://files.pythonhosted.org/packages/b9/25/37d4d3e8079ea9784ea1625029988e7f4594ce50d4738b0c1e2bf4a9e201/orjson-3.11.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6f59dfea7da1fced6e782bb3699718088b1036cb361f36c6e4dd843c5111aefe", size = 116222, upload-time = "2025-08-12T15:11:30.18Z" }, + { url = "https://files.pythonhosted.org/packages/b7/32/a63fd9c07fce3b4193dcc1afced5dd4b0f3a24e27556604e9482b32189c9/orjson-3.11.2-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edf49146520fef308c31aa4c45b9925fd9c7584645caca7c0c4217d7900214ae", size = 119020, upload-time = "2025-08-12T15:11:31.59Z" }, + { url = "https://files.pythonhosted.org/packages/b4/b6/400792b8adc3079a6b5d649264a3224d6342436d9fac9a0ed4abc9dc4596/orjson-3.11.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50995bbeb5d41a32ad15e023305807f561ac5dcd9bd41a12c8d8d1d2c83e44e6", size = 120721, upload-time = "2025-08-12T15:11:33.035Z" }, + { url = "https://files.pythonhosted.org/packages/40/f3/31ab8f8c699eb9e65af8907889a0b7fef74c1d2b23832719a35da7bb0c58/orjson-3.11.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2cc42960515076eb639b705f105712b658c525863d89a1704d984b929b0577d1", size = 123574, upload-time = "2025-08-12T15:11:34.433Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a6/ce4287c412dff81878f38d06d2c80845709c60012ca8daf861cb064b4574/orjson-3.11.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c56777cab2a7b2a8ea687fedafb84b3d7fdafae382165c31a2adf88634c432fa", size = 121225, upload-time = "2025-08-12T15:11:36.133Z" }, + { url = "https://files.pythonhosted.org/packages/69/b0/7a881b2aef4fed0287d2a4fbb029d01ed84fa52b4a68da82bdee5e50598e/orjson-3.11.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:07349e88025b9b5c783077bf7a9f401ffbfb07fd20e86ec6fc5b7432c28c2c5e", size = 119201, upload-time = "2025-08-12T15:11:37.642Z" }, + { url = "https://files.pythonhosted.org/packages/cf/98/a325726b37f7512ed6338e5e65035c3c6505f4e628b09a5daf0419f054ea/orjson-3.11.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:45841fbb79c96441a8c58aa29ffef570c5df9af91f0f7a9572e5505e12412f15", size = 392193, upload-time = "2025-08-12T15:11:39.153Z" }, + { url = "https://files.pythonhosted.org/packages/cb/4f/a7194f98b0ce1d28190e0c4caa6d091a3fc8d0107ad2209f75c8ba398984/orjson-3.11.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:13d8d8db6cd8d89d4d4e0f4161acbbb373a4d2a4929e862d1d2119de4aa324ac", size = 134548, upload-time = "2025-08-12T15:11:40.768Z" }, + { url = "https://files.pythonhosted.org/packages/e8/5e/b84caa2986c3f472dc56343ddb0167797a708a8d5c3be043e1e2677b55df/orjson-3.11.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51da1ee2178ed09c00d09c1b953e45846bbc16b6420965eb7a913ba209f606d8", size = 123798, upload-time = "2025-08-12T15:11:42.164Z" }, + { url = "https://files.pythonhosted.org/packages/9c/5b/e398449080ce6b4c8fcadad57e51fa16f65768e1b142ba90b23ac5d10801/orjson-3.11.2-cp313-cp313-win32.whl", hash = "sha256:51dc033df2e4a4c91c0ba4f43247de99b3cbf42ee7a42ee2b2b2f76c8b2f2cb5", size = 124402, upload-time = "2025-08-12T15:11:44.036Z" }, + { url = "https://files.pythonhosted.org/packages/b3/66/429e4608e124debfc4790bfc37131f6958e59510ba3b542d5fc163be8e5f/orjson-3.11.2-cp313-cp313-win_amd64.whl", hash = "sha256:29d91d74942b7436f29b5d1ed9bcfc3f6ef2d4f7c4997616509004679936650d", size = 119498, upload-time = "2025-08-12T15:11:45.864Z" }, + { url = "https://files.pythonhosted.org/packages/7b/04/f8b5f317cce7ad3580a9ad12d7e2df0714dfa8a83328ecddd367af802f5b/orjson-3.11.2-cp313-cp313-win_arm64.whl", hash = "sha256:4ca4fb5ac21cd1e48028d4f708b1bb13e39c42d45614befd2ead004a8bba8535", size = 114051, upload-time = "2025-08-12T15:11:47.555Z" }, + { url = "https://files.pythonhosted.org/packages/74/83/2c363022b26c3c25b3708051a19d12f3374739bb81323f05b284392080c0/orjson-3.11.2-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:3dcba7101ea6a8d4ef060746c0f2e7aa8e2453a1012083e1ecce9726d7554cb7", size = 226406, upload-time = "2025-08-12T15:11:49.445Z" }, + { url = "https://files.pythonhosted.org/packages/b0/a7/aa3c973de0b33fc93b4bd71691665ffdfeae589ea9d0625584ab10a7d0f5/orjson-3.11.2-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:15d17bdb76a142e1f55d91913e012e6e6769659daa6bfef3ef93f11083137e81", size = 115788, upload-time = "2025-08-12T15:11:50.992Z" }, + { url = "https://files.pythonhosted.org/packages/ef/f2/e45f233dfd09fdbb052ec46352363dca3906618e1a2b264959c18f809d0b/orjson-3.11.2-cp314-cp314-manylinux_2_34_aarch64.whl", hash = "sha256:53c9e81768c69d4b66b8876ec3c8e431c6e13477186d0db1089d82622bccd19f", size = 111318, upload-time = "2025-08-12T15:11:52.495Z" }, + { url = "https://files.pythonhosted.org/packages/3e/23/cf5a73c4da6987204cbbf93167f353ff0c5013f7c5e5ef845d4663a366da/orjson-3.11.2-cp314-cp314-manylinux_2_34_x86_64.whl", hash = "sha256:d4f13af59a7b84c1ca6b8a7ab70d608f61f7c44f9740cd42409e6ae7b6c8d8b7", size = 121231, upload-time = "2025-08-12T15:11:53.941Z" }, + { url = "https://files.pythonhosted.org/packages/40/1d/47468a398ae68a60cc21e599144e786e035bb12829cb587299ecebc088f1/orjson-3.11.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bde64aa469b5ee46cc960ed241fae3721d6a8801dacb2ca3466547a2535951e4", size = 119204, upload-time = "2025-08-12T15:11:55.409Z" }, + { url = "https://files.pythonhosted.org/packages/4d/d9/f99433d89b288b5bc8836bffb32a643f805e673cf840ef8bab6e73ced0d1/orjson-3.11.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:b5ca86300aeb383c8fa759566aca065878d3d98c3389d769b43f0a2e84d52c5f", size = 392237, upload-time = "2025-08-12T15:11:57.18Z" }, + { url = "https://files.pythonhosted.org/packages/d4/dc/1b9d80d40cebef603325623405136a29fb7d08c877a728c0943dd066c29a/orjson-3.11.2-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:24e32a558ebed73a6a71c8f1cbc163a7dd5132da5270ff3d8eeb727f4b6d1bc7", size = 134578, upload-time = "2025-08-12T15:11:58.844Z" }, + { url = "https://files.pythonhosted.org/packages/45/b3/72e7a4c5b6485ef4e83ef6aba7f1dd041002bad3eb5d1d106ca5b0fc02c6/orjson-3.11.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e36319a5d15b97e4344110517450396845cc6789aed712b1fbf83c1bd95792f6", size = 123799, upload-time = "2025-08-12T15:12:00.352Z" }, + { url = "https://files.pythonhosted.org/packages/c8/3e/a3d76b392e7acf9b34dc277171aad85efd6accc75089bb35b4c614990ea9/orjson-3.11.2-cp314-cp314-win32.whl", hash = "sha256:40193ada63fab25e35703454d65b6afc71dbc65f20041cb46c6d91709141ef7f", size = 124461, upload-time = "2025-08-12T15:12:01.854Z" }, + { url = "https://files.pythonhosted.org/packages/fb/e3/75c6a596ff8df9e4a5894813ff56695f0a218e6ea99420b4a645c4f7795d/orjson-3.11.2-cp314-cp314-win_amd64.whl", hash = "sha256:7c8ac5f6b682d3494217085cf04dadae66efee45349ad4ee2a1da3c97e2305a8", size = 119494, upload-time = "2025-08-12T15:12:03.337Z" }, + { url = "https://files.pythonhosted.org/packages/5b/3d/9e74742fc261c5ca473c96bb3344d03995869e1dc6402772c60afb97736a/orjson-3.11.2-cp314-cp314-win_arm64.whl", hash = "sha256:21cf261e8e79284242e4cb1e5924df16ae28255184aafeff19be1405f6d33f67", size = 114046, upload-time = "2025-08-12T15:12:04.87Z" }, ] [[package]] @@ -2468,22 +2260,13 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/12/ad/f4e1a36a6d1714afb7ffb74b3ababdcb96529cf4e7a216f9f7c8eda837b6/ormsgpack-1.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:534d18acb805c75e5fba09598bf40abe1851c853247e61dda0c01f772234da69", size = 121399, upload-time = "2025-05-24T19:07:40.854Z" }, ] -[[package]] -name = "overrides" -version = "7.7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/36/86/b585f53236dec60aba864e050778b25045f857e17f6e5ea0ae95fe80edd2/overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a", size = 22812, upload-time = "2024-01-27T21:01:33.423Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49", size = 17832, upload-time = "2024-01-27T21:01:31.393Z" }, -] - [[package]] name = "packaging" -version = "24.2" +version = "25.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950, upload-time = "2024-11-08T09:47:47.202Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451, upload-time = "2024-11-08T09:47:44.722Z" }, + { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, ] [[package]] @@ -2527,19 +2310,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d5/f9/07086f5b0f2a19872554abeea7658200824f5835c58a106fa8f2ae96a46c/pandas-2.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5db9637dbc24b631ff3707269ae4559bce4b7fd75c1c4d7e13f40edc42df4444", size = 13189044, upload-time = "2025-07-07T19:19:39.999Z" }, ] -[[package]] -name = "pandas-stubs" -version = "2.3.0.250703" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, - { name = "types-pytz" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ec/df/c1c51c5cec087b8f4d04669308b700e9648745a77cdd0c8c5e16520703ca/pandas_stubs-2.3.0.250703.tar.gz", hash = "sha256:fb6a8478327b16ed65c46b1541de74f5c5947f3601850caf3e885e0140584717", size = 103910, upload-time = "2025-07-02T17:49:11.667Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/75/cb/09d5f9bf7c8659af134ae0ffc1a349038a5d0ff93e45aedc225bde2872a3/pandas_stubs-2.3.0.250703-py3-none-any.whl", hash = "sha256:a9265fc69909f0f7a9cabc5f596d86c9d531499fed86b7838fd3278285d76b81", size = 154719, upload-time = "2025-07-02T17:49:10.697Z" }, -] - [[package]] name = "pathspec" version = "0.12.1" @@ -2651,25 +2421,9 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] -[[package]] -name = "posthog" -version = "5.4.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "backoff" }, - { name = "distro" }, - { name = "python-dateutil" }, - { name = "requests" }, - { name = "six" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/48/20/60ae67bb9d82f00427946218d49e2e7e80fb41c15dc5019482289ec9ce8d/posthog-5.4.0.tar.gz", hash = "sha256:701669261b8d07cdde0276e5bc096b87f9e200e3b9589c5ebff14df658c5893c", size = 88076, upload-time = "2025-06-20T23:19:23.485Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4f/98/e480cab9a08d1c09b1c59a93dade92c1bb7544826684ff2acbfd10fcfbd4/posthog-5.4.0-py3-none-any.whl", hash = "sha256:284dfa302f64353484420b52d4ad81ff5c2c2d1d607c4e2db602ac72761831bd", size = 105364, upload-time = "2025-06-20T23:19:22.001Z" }, -] - [[package]] name = "pre-commit" -version = "4.2.0" +version = "4.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cfgv" }, @@ -2678,9 +2432,9 @@ dependencies = [ { name = "pyyaml" }, { name = "virtualenv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/08/39/679ca9b26c7bb2999ff122d50faa301e49af82ca9c066ec061cfbc0c6784/pre_commit-4.2.0.tar.gz", hash = "sha256:601283b9757afd87d40c4c4a9b2b5de9637a8ea02eaff7adc2d0fb4e04841146", size = 193424, upload-time = "2025-03-18T21:35:20.987Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ff/29/7cf5bbc236333876e4b41f56e06857a87937ce4bf91e117a6991a2dbb02a/pre_commit-4.3.0.tar.gz", hash = "sha256:499fe450cc9d42e9d58e606262795ecb64dd05438943c62b66f6a8673da30b16", size = 193792, upload-time = "2025-08-09T18:56:14.651Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/74/a88bf1b1efeae488a0c0b7bdf71429c313722d1fc0f377537fbe554e6180/pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd", size = 220707, upload-time = "2025-03-18T21:35:19.343Z" }, + { url = "https://files.pythonhosted.org/packages/5b/a5/987a405322d78a73b66e39e4a90e4ef156fd7141bf71df987e50717c321b/pre_commit-4.3.0-py2.py3-none-any.whl", hash = "sha256:2b0747ad7e6e967169136edffee14c16e148a778a54e4f967921aa1ebf2308d8", size = 220965, upload-time = "2025-08-09T18:56:13.192Z" }, ] [[package]] @@ -2827,88 +2581,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259, upload-time = "2025-03-28T02:41:19.028Z" }, ] -[[package]] -name = "pybase64" -version = "1.4.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/38/32/5d25a15256d2e80d1e92be821f19fc49190e65a90ea86733cb5af2285449/pybase64-1.4.1.tar.gz", hash = "sha256:03fc365c601671add4f9e0713c2bc2485fa4ab2b32f0d3bb060bd7e069cdaa43", size = 136836, upload-time = "2025-03-02T11:13:57.109Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ff/74/6f60bddbc6badd9a821e590f960fcf55b2008842b724552e062273d2f3a2/pybase64-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a230b64474f02075608d81fc19073c86cb4e63111d5c94f8bf77a3f2c0569956", size = 38068, upload-time = "2025-03-02T11:10:41.74Z" }, - { url = "https://files.pythonhosted.org/packages/0e/ce/1e56414745cb92ed0b22fd640af1d559d8161c28d26e288da7bcd2836f93/pybase64-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26ebcd7ccadde46ab35b16fee6f3b9478142833a164e10040b942ad5ccc8c4c0", size = 31485, upload-time = "2025-03-02T11:10:42.943Z" }, - { url = "https://files.pythonhosted.org/packages/96/38/f561708ec3740ac7f0395122672d663cc525295a1021a0b9c16aba19115b/pybase64-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f033501b08bbfc89a725f9a283b485348df2cb7acb8c41ca52ccfa76785d9343", size = 59642, upload-time = "2025-03-02T11:10:44.016Z" }, - { url = "https://files.pythonhosted.org/packages/43/70/71ed3d6d8905079668e75c6eeaa2e5c6fd4c33b0f8d4672e9ec99bb4925a/pybase64-1.4.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f6634d77e2f4b559daf30234f2dc679de9de3ba88effbdc0354a68b3aa2d29d3", size = 56464, upload-time = "2025-03-02T11:10:45.116Z" }, - { url = "https://files.pythonhosted.org/packages/60/53/1558b2d756896f15ea6396e2791bb710a9f289a3e2a24db5bfcf203d54e6/pybase64-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e1837488c7aa9bc7ba7bb0449908e57ecfe444e3c7347a905a87450c7e523e00", size = 59197, upload-time = "2025-03-02T11:10:47.009Z" }, - { url = "https://files.pythonhosted.org/packages/0d/ae/300cb522d7f7eb543165843d28db4046909a8aabe110afa50cdab0947c9d/pybase64-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80e85e5ca298d3a9916c47e6fb0c47ebe5bf7996eac6983c887027b378e9bcae", size = 59803, upload-time = "2025-03-02T11:10:48.163Z" }, - { url = "https://files.pythonhosted.org/packages/b0/b4/355f03c656bb331e623466bc6be4307efd2c41cfe58fdbf869cfb126a70c/pybase64-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:480c0c444eb07e4855d2eeab3f91a70331b75862d7a3dce0e6d4caddbfb4c09b", size = 68444, upload-time = "2025-03-02T11:10:49.32Z" }, - { url = "https://files.pythonhosted.org/packages/6f/4b/8d0730e9507026e05a7e34daddcac3d548cf8ce51cda858d033b142fed4d/pybase64-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97e25723ecf7c439f650192d43699aab0a22850dca9cc6d60377c42bb4df7812", size = 71184, upload-time = "2025-03-02T11:10:51.147Z" }, - { url = "https://files.pythonhosted.org/packages/53/95/4e7cda0cd38e5e38697fcb62ede30c42ed8f5a2427adc73296d2746ec12c/pybase64-1.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:82efee94d6bd93f7787afc42f260fa0b60e24c8dc7f172bd45cfe99fa39567ff", size = 58479, upload-time = "2025-03-02T11:10:52.908Z" }, - { url = "https://files.pythonhosted.org/packages/26/ed/cac0892746795de07b2e71f48e651af597ccb8b52ba36ac2afaa07e7da55/pybase64-1.4.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:c15765be7921914d0dad0a2fb57c35a1811e1cbe2d1e47c39e0c66ed7db52898", size = 52148, upload-time = "2025-03-02T11:10:54.079Z" }, - { url = "https://files.pythonhosted.org/packages/7e/ca/8eaae3ee3c0e7b8a827c00ca5d850a9188e0cab9575764ae3638cce6ff78/pybase64-1.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d1dcddfa521fb6cbab0385032d43f0ca13212459abd6efc381b6e9847e9fbd79", size = 68801, upload-time = "2025-03-02T11:10:55.416Z" }, - { url = "https://files.pythonhosted.org/packages/c7/55/a847b02b2c17a6353e7156f995a44bdd26b326332851fb35ee3a5dfedf82/pybase64-1.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:bd1de051b9b032d84e799af498b44499e90122a095da7dad89c2873518473c67", size = 57857, upload-time = "2025-03-02T11:10:56.607Z" }, - { url = "https://files.pythonhosted.org/packages/6e/6d/7562e73ab1dbf7d735e1a2da6be06a4bdb3bb8ddfecf3c29f25288528bb7/pybase64-1.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:bf8213e6b8c658df2971c5a56df42202d7f89d5d6312d066d49923cc98a39299", size = 56075, upload-time = "2025-03-02T11:10:57.796Z" }, - { url = "https://files.pythonhosted.org/packages/99/a4/795935ad7ef2d066c082a9c852b8dd658f2c61a2de1742b46c576665edd5/pybase64-1.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7d83ab7822da5740f1d17c72fb451e9468e72976b89cfb9eb4f6a5b66491b5dc", size = 70710, upload-time = "2025-03-02T11:10:58.947Z" }, - { url = "https://files.pythonhosted.org/packages/13/16/b487ba1382fca5451cb18552333999a52c47d5e561d41b1ba17bf3bbf407/pybase64-1.4.1-cp311-cp311-win32.whl", hash = "sha256:7726e655134132dde59bddabcd74d140f818eeecc70d149267267d5e29335193", size = 34200, upload-time = "2025-03-02T11:11:00.841Z" }, - { url = "https://files.pythonhosted.org/packages/ea/a4/354cfd978a145cbeacba73f70266687f3dd34e1df1cdeb882c23153697a3/pybase64-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:9d5202cd4a8a0cd1b28c11730cf5da3c014450ad03732b5da03fac89b7693ec2", size = 36417, upload-time = "2025-03-02T11:11:02.006Z" }, - { url = "https://files.pythonhosted.org/packages/19/6c/5a576f95c79aa28a4b476ec84afe751ac0cab23572d9fd000b93adab6c76/pybase64-1.4.1-cp311-cp311-win_arm64.whl", hash = "sha256:72808de9aab43112deb04003e5e0d060c7cb1a60c3dcf74bbf61a9d7c596c5af", size = 29638, upload-time = "2025-03-02T11:11:03.635Z" }, - { url = "https://files.pythonhosted.org/packages/a6/a9/43bac4f39401f7241d233ddaf9e6561860b2466798cfb83b9e7dbf89bc1b/pybase64-1.4.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:bbdcf77e424c91389f22bf10158851ce05c602c50a74ccf5943ee3f5ef4ba489", size = 38152, upload-time = "2025-03-02T11:11:07.576Z" }, - { url = "https://files.pythonhosted.org/packages/1e/bb/d0ae801e31a5052dbb1744a45318f822078dd4ce4cc7f49bfe97e7768f7e/pybase64-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:af41e2e6015f980d15eae0df0c365df94c7587790aea236ba0bf48c65a9fa04e", size = 31488, upload-time = "2025-03-02T11:11:09.758Z" }, - { url = "https://files.pythonhosted.org/packages/be/34/bf4119a88b2ad0536a8ed9d66ce4d70ff8152eac00ef8a27e5ae35da4328/pybase64-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ac21c1943a15552347305943b1d0d6298fb64a98b67c750cb8fb2c190cdefd4", size = 59734, upload-time = "2025-03-02T11:11:11.493Z" }, - { url = "https://files.pythonhosted.org/packages/99/1c/1901547adc7d4f24bdcb2f75cb7dcd3975bff42f39da37d4bd218c608c60/pybase64-1.4.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:65567e8f4f31cf6e1a8cc570723cc6b18adda79b4387a18f8d93c157ff5f1979", size = 56529, upload-time = "2025-03-02T11:11:12.657Z" }, - { url = "https://files.pythonhosted.org/packages/c5/1e/1993e4b9a03e94fc53552285e3998079d864fff332798bf30c25afdac8f3/pybase64-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:988e987f8cfe2dfde7475baf5f12f82b2f454841aef3a174b694a57a92d5dfb0", size = 59114, upload-time = "2025-03-02T11:11:13.972Z" }, - { url = "https://files.pythonhosted.org/packages/c5/f6/061fee5b7ba38b8824dd95752ab7115cf183ffbd3330d5fc1734a47b0f9e/pybase64-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:92b2305ac2442b451e19d42c4650c3bb090d6aa9abd87c0c4d700267d8fa96b1", size = 60095, upload-time = "2025-03-02T11:11:15.182Z" }, - { url = "https://files.pythonhosted.org/packages/37/da/ccfe5d1a9f1188cd703390522e96a31045c5b93af84df04a98e69ada5c8b/pybase64-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1ff80e03357b09dab016f41b4c75cf06e9b19cda7f898e4f3681028a3dff29b", size = 68431, upload-time = "2025-03-02T11:11:17.059Z" }, - { url = "https://files.pythonhosted.org/packages/c3/d3/8ca4b0695876b52c0073a3557a65850b6d5c723333b5a271ab10a1085852/pybase64-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2cdda297e668e118f6b9ba804e858ff49e3dd945d01fdd147de90445fd08927d", size = 71417, upload-time = "2025-03-02T11:11:19.178Z" }, - { url = "https://files.pythonhosted.org/packages/94/34/5f8f72d1b7b4ddb64c48d60160f3f4f03cfd0bfd2e7068d4558499d948ed/pybase64-1.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:51a24d21a21a959eb8884f24346a6480c4bd624aa7976c9761504d847a2f9364", size = 58429, upload-time = "2025-03-02T11:11:20.351Z" }, - { url = "https://files.pythonhosted.org/packages/95/b7/edf53af308c6e8aada1e6d6a0a3789176af8cbae37a2ce084eb9da87bf33/pybase64-1.4.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b19e169ea1b8a15a03d3a379116eb7b17740803e89bc6eb3efcc74f532323cf7", size = 52228, upload-time = "2025-03-02T11:11:21.632Z" }, - { url = "https://files.pythonhosted.org/packages/0c/bf/c9df141e24a259f38a38bdda5a3b63206f13e612ecbd3880fa10625e0294/pybase64-1.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8a9f1b614efd41240c9bb2cf66031aa7a2c3c092c928f9d429511fe18d4a3fd1", size = 68632, upload-time = "2025-03-02T11:11:23.56Z" }, - { url = "https://files.pythonhosted.org/packages/e9/ae/1aec72325a3c48f7776cc55a3bab8b168eb77aea821253da8b9f09713734/pybase64-1.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d9947b5e289e2c5b018ddc2aee2b9ed137b8aaaba7edfcb73623e576a2407740", size = 57682, upload-time = "2025-03-02T11:11:25.656Z" }, - { url = "https://files.pythonhosted.org/packages/4d/7a/7ad2799c0b3c4e2f7b993e1636468445c30870ca5485110b589b8921808d/pybase64-1.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:ba4184ea43aa88a5ab8d6d15db284689765c7487ff3810764d8d823b545158e6", size = 56308, upload-time = "2025-03-02T11:11:26.803Z" }, - { url = "https://files.pythonhosted.org/packages/be/01/6008a4fbda0c4308dab00b95aedde8748032d7620bd95b686619c66917fe/pybase64-1.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4471257628785296efb2d50077fb9dfdbd4d2732c3487795224dd2644216fb07", size = 70784, upload-time = "2025-03-02T11:11:28.427Z" }, - { url = "https://files.pythonhosted.org/packages/27/31/913365a4f0e2922ec369ddaa3a1d6c11059acbe54531b003653efa007a48/pybase64-1.4.1-cp312-cp312-win32.whl", hash = "sha256:614561297ad14de315dd27381fd6ec3ea4de0d8206ba4c7678449afaff8a2009", size = 34271, upload-time = "2025-03-02T11:11:30.585Z" }, - { url = "https://files.pythonhosted.org/packages/d9/98/4d514d3e4c04819d80bccf9ea7b30d1cfc701832fa5ffca168f585004488/pybase64-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:35635db0d64fcbe9b3fad265314c052c47dc9bcef8dea17493ea8e3c15b2b972", size = 36496, upload-time = "2025-03-02T11:11:32.552Z" }, - { url = "https://files.pythonhosted.org/packages/c4/61/01353bc9c461e7b36d692daca3eee9616d8936ea6d8a64255ef7ec9ac307/pybase64-1.4.1-cp312-cp312-win_arm64.whl", hash = "sha256:b4ccb438c4208ff41a260b70994c30a8631051f3b025cdca48be586b068b8f49", size = 29692, upload-time = "2025-03-02T11:11:33.735Z" }, - { url = "https://files.pythonhosted.org/packages/4b/1a/4e243ba702c07df3df3ba1795cfb02cf7a4242c53fc574b06a2bfa4f8478/pybase64-1.4.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d1c38d9c4a7c132d45859af8d5364d3ce90975a42bd5995d18d174fb57621973", size = 38149, upload-time = "2025-03-02T11:11:35.537Z" }, - { url = "https://files.pythonhosted.org/packages/9c/35/3eae81bc8688a83f8b5bb84979d88e2cc3c3279a3b870a506f277d746c56/pybase64-1.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ab0b93ea93cf1f56ca4727d678a9c0144c2653e9de4e93e789a92b4e098c07d9", size = 31485, upload-time = "2025-03-02T11:11:36.656Z" }, - { url = "https://files.pythonhosted.org/packages/48/55/d99b9ff8083573bbf97fc433bbc20e2efb612792025f3bad0868c96c37ce/pybase64-1.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:644f393e9bb7f3bacc5cbd3534d02e1b660b258fc8315ecae74d2e23265e5c1f", size = 59738, upload-time = "2025-03-02T11:11:38.468Z" }, - { url = "https://files.pythonhosted.org/packages/63/3c/051512b9e139a11585447b286ede5ac3b284ce5df85de37eb8cff57d90f8/pybase64-1.4.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff172a4dacbd964e5edcf1c2152dae157aabf856508aed15276f46d04a22128e", size = 56239, upload-time = "2025-03-02T11:11:39.718Z" }, - { url = "https://files.pythonhosted.org/packages/af/11/f40c5cca587274d50baee88540a7839576204cb425fe2f73a752ea48ae74/pybase64-1.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b2ab7b4535abc72d40114540cae32c9e07d76ffba132bdd5d4fff5fe340c5801", size = 59137, upload-time = "2025-03-02T11:11:41.524Z" }, - { url = "https://files.pythonhosted.org/packages/1a/a9/ace9f6d0926962c083671d7df247de442ef63cd06bd134f7c8251aab5c51/pybase64-1.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da66eb7cfb641486944fb0b95ab138e691ab78503115022caf992b6c89b10396", size = 60109, upload-time = "2025-03-02T11:11:42.699Z" }, - { url = "https://files.pythonhosted.org/packages/88/9c/d4e308b4b4e3b513bc084fc71b4e2dd00d21d4cd245a9a28144d2f6b03c9/pybase64-1.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:678f573ea1d06183b32d0336044fb5db60396333599dffcce28ffa3b68319fc0", size = 68391, upload-time = "2025-03-02T11:11:43.898Z" }, - { url = "https://files.pythonhosted.org/packages/53/87/e184bf982a3272f1021f417e5a18fac406e042c606950e9082fc3b0cec30/pybase64-1.4.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4bccdf340c2a1d3dd1f41528f192265ddce7f8df1ee4f7b5b9163cdba0fe0ccb", size = 71438, upload-time = "2025-03-02T11:11:45.112Z" }, - { url = "https://files.pythonhosted.org/packages/2f/7f/d6e6a72db055eb2dc01ab877d8ee39d05cb665403433ff922fb95d1003ad/pybase64-1.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1ddf6366c34eb78931fd8a47c00cb886ba187a5ff8e6dbffe1d9dae4754b6c28", size = 58437, upload-time = "2025-03-02T11:11:47.034Z" }, - { url = "https://files.pythonhosted.org/packages/71/ef/c9051f2c0128194b861f3cd3b2d211b8d4d21ed2be354aa669fe29a059d8/pybase64-1.4.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:500afcb717a84e262c68f0baf9c56abaf97e2f058ba80c5546a9ed21ff4b705f", size = 52267, upload-time = "2025-03-02T11:11:48.448Z" }, - { url = "https://files.pythonhosted.org/packages/12/92/ae30a54eaa437989839c4f2404c1f004d7383c0f46d6ebb83546d587d2a7/pybase64-1.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d2de043312a1e7f15ee6d2b7d9e39ee6afe24f144e2248cce942b6be357b70d8", size = 68659, upload-time = "2025-03-02T11:11:49.615Z" }, - { url = "https://files.pythonhosted.org/packages/2b/65/d94788a35904f21694c4c581bcee2e165bec2408cc6fbed85a7fef5959ae/pybase64-1.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c36e214c25fb8dd4f3ecdaa0ff90073b793056e0065cc0a1e1e5525a6866a1ad", size = 57727, upload-time = "2025-03-02T11:11:50.843Z" }, - { url = "https://files.pythonhosted.org/packages/d0/97/8db416066b7917909c38346c03a8f3e6d4fc8a1dc98636408156514269ad/pybase64-1.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:8ec003224f6e36e8e607a1bb8df182b367c87ca7135788ffe89173c7d5085005", size = 56302, upload-time = "2025-03-02T11:11:52.547Z" }, - { url = "https://files.pythonhosted.org/packages/70/0b/98f0601391befe0f19aa8cbda821c62d95056a94cc41d452fe893d205523/pybase64-1.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c536c6ed161e6fb19f6acd6074f29a4c78cb41c9155c841d56aec1a4d20d5894", size = 70779, upload-time = "2025-03-02T11:11:53.735Z" }, - { url = "https://files.pythonhosted.org/packages/cc/07/116119c5b20688c052697f677cf56f05aa766535ff7691aba38447d4a0d8/pybase64-1.4.1-cp313-cp313-win32.whl", hash = "sha256:1d34872e5aa2eff9dc54cedaf36038bbfbd5a3440fdf0bdc5b3c81c54ef151ea", size = 34266, upload-time = "2025-03-02T11:11:54.892Z" }, - { url = "https://files.pythonhosted.org/packages/c0/f5/a7eed9f3692209a9869a28bdd92deddf8cbffb06b40954f89f4577e5c96e/pybase64-1.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:8b7765515d7e0a48ddfde914dc2b1782234ac188ce3fab173b078a6e82ec7017", size = 36488, upload-time = "2025-03-02T11:11:56.063Z" }, - { url = "https://files.pythonhosted.org/packages/5d/8a/0d65c4dcda06487305035f24888ffed219897c03fb7834635d5d5e27dae1/pybase64-1.4.1-cp313-cp313-win_arm64.whl", hash = "sha256:7fb782f3ceb30e24dc4d8d99c1221a381917bffaf85d29542f0f25b51829987c", size = 29690, upload-time = "2025-03-02T11:11:57.702Z" }, - { url = "https://files.pythonhosted.org/packages/a3/83/646d65fafe5e6edbdaf4c9548efb2e1dd7784caddbde3ff8a843dd942b0f/pybase64-1.4.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2a98d323e97444a38db38e022ccaf1d3e053b1942455790a93f29086c687855f", size = 38506, upload-time = "2025-03-02T11:11:58.936Z" }, - { url = "https://files.pythonhosted.org/packages/87/14/dbf7fbbe91d71c8044fefe20d22480ad64097e2ba424944de512550e12a4/pybase64-1.4.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:19ef58d36b9b32024768fcedb024f32c05eb464128c75c07cac2b50c9ed47f4a", size = 31894, upload-time = "2025-03-02T11:12:00.762Z" }, - { url = "https://files.pythonhosted.org/packages/bd/5d/f8a47da2a5f8b599297b307d3bd0293adedc4e135be310620f061906070f/pybase64-1.4.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:04fee0f5c174212868fde97b109db8fac8249b306a00ea323531ee61c7b0f398", size = 65212, upload-time = "2025-03-02T11:12:01.911Z" }, - { url = "https://files.pythonhosted.org/packages/90/95/ad9869c7cdcce3e8ada619dab5f9f2eff315ffb001704a3718c1597a2119/pybase64-1.4.1-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47737ff9eabc14b7553de6bc6395d67c5be80afcdbd25180285d13e089e40888", size = 60300, upload-time = "2025-03-02T11:12:03.071Z" }, - { url = "https://files.pythonhosted.org/packages/c2/91/4d8268b2488ae10c485cba04ecc23a5a7bdfb47ce9b876017b11ea0249a2/pybase64-1.4.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0d8b5888cc239654fe68a0db196a18575ffc8b1c8c8f670c2971a44e3b7fe682", size = 63773, upload-time = "2025-03-02T11:12:04.231Z" }, - { url = "https://files.pythonhosted.org/packages/ae/1a/8afd27facc0723b1d69231da8c59a2343feb255f5db16f8b8765ddf1600b/pybase64-1.4.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a1af8d387dbce05944b65a618639918804b2d4438fed32bb7f06d9c90dbed01", size = 64684, upload-time = "2025-03-02T11:12:05.409Z" }, - { url = "https://files.pythonhosted.org/packages/cc/cd/422c74397210051125419fc8e425506ff27c04665459e18c8f7b037a754b/pybase64-1.4.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b0093c52bd099b80e422ad8cddf6f2c1ac1b09cb0922cca04891d736c2ad647", size = 72880, upload-time = "2025-03-02T11:12:06.652Z" }, - { url = "https://files.pythonhosted.org/packages/04/c1/c4f02f1d5f8e8a3d75715a3dd04196dde9e263e471470d099a26e91ebe2f/pybase64-1.4.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15e54f9b2a1686f5bbdc4ac8440b6f6145d9699fd53aa30f347931f3063b0915", size = 75344, upload-time = "2025-03-02T11:12:07.816Z" }, - { url = "https://files.pythonhosted.org/packages/6e/0b/013006ca984f0472476cf7c0540db2e2b1f997d52977b15842a7681ab79c/pybase64-1.4.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:3a0fdcf13f986c82f7ef04a1cd1163c70f39662d6f02aa4e7b448dacb966b39f", size = 63439, upload-time = "2025-03-02T11:12:09.669Z" }, - { url = "https://files.pythonhosted.org/packages/8a/d5/7848543b3c8dcc5396be574109acbe16706e6a9b4dbd9fc4e22f211668a9/pybase64-1.4.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:ac03f8eba72dd6da15dc25bb3e1b440ad21f5cb7ee2e6ffbbae4bd1b206bb503", size = 56004, upload-time = "2025-03-02T11:12:10.981Z" }, - { url = "https://files.pythonhosted.org/packages/63/58/70de1efb1b6f21d7aaea33578868214f82925d969e2091f7de3175a10092/pybase64-1.4.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:ea835272570aa811e08ae17612632b057623a9b27265d44288db666c02b438dc", size = 72460, upload-time = "2025-03-02T11:12:13.122Z" }, - { url = "https://files.pythonhosted.org/packages/90/0d/aa52dd1b1f25b98b1d94cc0522f864b03de55aa115de67cb6dbbddec4f46/pybase64-1.4.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:8f52c4c29a35381f3ae06d520144a0707132f2cbfb53bc907b74811734bc4ef3", size = 62295, upload-time = "2025-03-02T11:12:15.004Z" }, - { url = "https://files.pythonhosted.org/packages/39/cf/4d378a330249c937676ee8eab7992ec700ade362f35db36c15922b33b1c8/pybase64-1.4.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:fa5cdabcb4d21b7e56d0b2edd7ed6fa933ac3535be30c2a9cf0a2e270c5369c8", size = 60604, upload-time = "2025-03-02T11:12:16.23Z" }, - { url = "https://files.pythonhosted.org/packages/15/45/e3f23929018d0aada84246ddd398843050971af614da67450bb20f45f880/pybase64-1.4.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8db9acf239bb71a888748bc9ffc12c97c1079393a38bc180c0548330746ece94", size = 74500, upload-time = "2025-03-02T11:12:17.48Z" }, - { url = "https://files.pythonhosted.org/packages/8d/98/6d2adaec318cae6ee968a10df0a7e870f17ee385ef623bcb2ab63fa11b59/pybase64-1.4.1-cp313-cp313t-win32.whl", hash = "sha256:bc06186cfa9a43e871fdca47c1379bdf1cfe964bd94a47f0919a1ffab195b39e", size = 34543, upload-time = "2025-03-02T11:12:18.625Z" }, - { url = "https://files.pythonhosted.org/packages/8e/e7/1823de02d2c23324cf1142e9dce53b032085cee06c3f982806040f975ce7/pybase64-1.4.1-cp313-cp313t-win_amd64.whl", hash = "sha256:02c3647d270af1a3edd35e485bb7ccfe82180b8347c49e09973466165c03d7aa", size = 36909, upload-time = "2025-03-02T11:12:20.122Z" }, - { url = "https://files.pythonhosted.org/packages/43/6a/8ec0e4461bf89ef0499ef6c746b081f3520a1e710aeb58730bae693e0681/pybase64-1.4.1-cp313-cp313t-win_arm64.whl", hash = "sha256:4b3635e5873707906e72963c447a67969cfc6bac055432a57a91d7a4d5164fdf", size = 29961, upload-time = "2025-03-02T11:12:21.908Z" }, - { url = "https://files.pythonhosted.org/packages/a8/3e/90633da698742bfd11a1d6301295e9974c2f9e0e510aaae8cdd26cd10880/pybase64-1.4.1-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e8c28700ccf55348a7a4ad3554e6b4c5b83c640bfaa272fee6b4d0030566fe05", size = 38056, upload-time = "2025-03-02T11:13:30.6Z" }, - { url = "https://files.pythonhosted.org/packages/b1/02/79bdf96a780c3d1f4e9f1b583525247f3a33afebbba1e12e57fb28c395e7/pybase64-1.4.1-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:eb09bd829d4fef567505212b6bb87cd7a42b5aa2a3b83fc2bd61a188db7793e0", size = 31352, upload-time = "2025-03-02T11:13:32.395Z" }, - { url = "https://files.pythonhosted.org/packages/ce/d0/4f8135c2459724a834a70481f6bb8af3e89ff527c9b5cff0b799321e29d6/pybase64-1.4.1-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc9504c4c2e893e0a6c1cc80bce51907e3461288289f630eab22b5735eba1104", size = 35262, upload-time = "2025-03-02T11:13:33.55Z" }, - { url = "https://files.pythonhosted.org/packages/21/c6/45ace9c84ccc9d51002c5bcfe8c50e7660f064e2bc272a30c7802036f1f3/pybase64-1.4.1-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:45a785a3d29faf0309910d96e13c34870adb4ae43ea262868c6cf6a311936f37", size = 40968, upload-time = "2025-03-02T11:13:34.748Z" }, - { url = "https://files.pythonhosted.org/packages/9d/d5/1bf0b5354ca404ba096e99e2634c27836c212affe722bd2ade7103fd3c48/pybase64-1.4.1-pp311-pypy311_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10e2cb40869fe703484ba89ae50e05d63a169f7c42db59e29f8af0890c50515d", size = 41107, upload-time = "2025-03-02T11:13:35.996Z" }, - { url = "https://files.pythonhosted.org/packages/0b/d7/0987f3d1c8196ad9affea9102c135a45342e1fa5affb849bf31bd633d000/pybase64-1.4.1-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:1a18644fb3e940ed622738f2ee14d9a2811bb542ffd3f85c3fb661130675ac4f", size = 36817, upload-time = "2025-03-02T11:13:37.624Z" }, -] - [[package]] name = "pycares" version = "4.10.0" @@ -3071,30 +2743,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, ] -[[package]] -name = "pypika" -version = "0.48.9" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c7/2c/94ed7b91db81d61d7096ac8f2d325ec562fc75e35f3baea8749c85b28784/PyPika-0.48.9.tar.gz", hash = "sha256:838836a61747e7c8380cd1b7ff638694b7a7335345d0f559b04b2cd832ad5378", size = 67259, upload-time = "2022-03-15T11:22:57.066Z" } - -[[package]] -name = "pyproject-hooks" -version = "1.2.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/82/28175b2414effca1cdac8dc99f76d660e7a4fb0ceefa4b4ab8f5f6742925/pyproject_hooks-1.2.0.tar.gz", hash = "sha256:1e859bd5c40fae9448642dd871adf459e5e2084186e8d2c2a79a824c970da1f8", size = 19228, upload-time = "2024-09-29T09:24:13.293Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl", hash = "sha256:9e5c6bfa8dcc30091c74b0cf803c81fdd29d94f01992a7707bc97babb1141913", size = 10216, upload-time = "2024-09-29T09:24:11.978Z" }, -] - -[[package]] -name = "pyreadline3" -version = "3.5.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0f/49/4cea918a08f02817aabae639e3d0ac046fef9f9180518a3ad394e22da148/pyreadline3-3.5.4.tar.gz", hash = "sha256:8d57d53039a1c75adba8e50dd3d992b28143480816187ea5efbd5c78e6c885b7", size = 99839, upload-time = "2024-09-19T02:40:10.062Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/dc/491b7661614ab97483abf2056be1deee4dc2490ecbf7bff9ab5cdbac86e1/pyreadline3-3.5.4-py3-none-any.whl", hash = "sha256:eaf8e6cc3c49bcccf145fc6067ba8643d1df34d604a1ec0eccbf7a18e6d3fae6", size = 83178, upload-time = "2024-09-19T02:40:08.598Z" }, -] - [[package]] name = "pytest" version = "8.4.1" @@ -3111,44 +2759,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/29/16/c8a903f4c4dffe7a12843191437d7cd8e32751d5de349d45d3fe69544e87/pytest-8.4.1-py3-none-any.whl", hash = "sha256:539c70ba6fcead8e78eebbf1115e8b589e7565830d7d006a8723f19ac8a0afb7", size = 365474, upload-time = "2025-06-18T05:48:03.955Z" }, ] -[[package]] -name = "pytest-asyncio" -version = "1.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pytest" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d0/d4/14f53324cb1a6381bef29d698987625d80052bb33932d8e7cbf9b337b17c/pytest_asyncio-1.0.0.tar.gz", hash = "sha256:d15463d13f4456e1ead2594520216b225a16f781e144f8fdf6c5bb4667c48b3f", size = 46960, upload-time = "2025-05-26T04:54:40.484Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/30/05/ce271016e351fddc8399e546f6e23761967ee09c8c568bbfbecb0c150171/pytest_asyncio-1.0.0-py3-none-any.whl", hash = "sha256:4f024da9f1ef945e680dc68610b52550e36590a67fd31bb3b4943979a1f90ef3", size = 15976, upload-time = "2025-05-26T04:54:39.035Z" }, -] - -[[package]] -name = "pytest-cov" -version = "6.2.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "coverage", extra = ["toml"] }, - { name = "pluggy" }, - { name = "pytest" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/18/99/668cade231f434aaa59bbfbf49469068d2ddd945000621d3d165d2e7dd7b/pytest_cov-6.2.1.tar.gz", hash = "sha256:25cc6cc0a5358204b8108ecedc51a9b57b34cc6b8c967cc2c01a4e00d8a67da2", size = 69432, upload-time = "2025-06-12T10:47:47.684Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bc/16/4ea354101abb1287856baa4af2732be351c7bee728065aed451b678153fd/pytest_cov-6.2.1-py3-none-any.whl", hash = "sha256:f5bc4c23f42f1cdd23c70b1dab1bbaef4fc505ba950d53e0081d0730dd7e86d5", size = 24644, upload-time = "2025-06-12T10:47:45.932Z" }, -] - -[[package]] -name = "pytest-mock" -version = "3.14.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pytest" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/71/28/67172c96ba684058a4d24ffe144d64783d2a270d0af0d9e792737bddc75c/pytest_mock-3.14.1.tar.gz", hash = "sha256:159e9edac4c451ce77a5cdb9fc5d1100708d2dd4ba3c3df572f14097351af80e", size = 33241, upload-time = "2025-05-26T13:58:45.167Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b2/05/77b60e520511c53d1c1ca75f1930c7dd8e971d0c4379b7f4b3f9644685ba/pytest_mock-3.14.1-py3-none-any.whl", hash = "sha256:178aefcd11307d874b4cd3100344e7e2d888d9791a6a1d9bfe90fbc1b74fd1d0", size = 9923, upload-time = "2025-05-26T13:58:43.487Z" }, -] - [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -3163,11 +2773,11 @@ wheels = [ [[package]] name = "python-dotenv" -version = "1.0.1" +version = "1.1.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115, upload-time = "2024-01-23T06:33:00.505Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/b0/4bc07ccd3572a2f9df7e6782f52b0c6c90dcbb803ac4a167702d7d0dfe1e/python_dotenv-1.1.1.tar.gz", hash = "sha256:a8a6399716257f45be6a007360200409fce5cda2661e3dec71d23dc15f6189ab", size = 41978, upload-time = "2025-06-24T04:21:07.341Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863, upload-time = "2024-01-23T06:32:58.246Z" }, + { url = "https://files.pythonhosted.org/packages/5f/ed/539768cf28c661b5b068d66d96a2f155c4971a5d55684a514c1a0e0dec2f/python_dotenv-1.1.1-py3-none-any.whl", hash = "sha256:31f23644fe2602f88ff55e1f5c79ba497e01224ee7737937930c448e4d0e24dc", size = 20556, upload-time = "2025-06-24T04:21:06.073Z" }, ] [[package]] @@ -3258,60 +2868,71 @@ wheels = [ [[package]] name = "regex" -version = "2024.11.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8e/5f/bd69653fbfb76cf8604468d3b4ec4c403197144c7bfe0e6a5fc9e02a07cb/regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519", size = 399494, upload-time = "2024-11-06T20:12:31.635Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/58/58/7e4d9493a66c88a7da6d205768119f51af0f684fe7be7bac8328e217a52c/regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638", size = 482669, upload-time = "2024-11-06T20:09:31.064Z" }, - { url = "https://files.pythonhosted.org/packages/34/4c/8f8e631fcdc2ff978609eaeef1d6994bf2f028b59d9ac67640ed051f1218/regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7", size = 287684, upload-time = "2024-11-06T20:09:32.915Z" }, - { url = "https://files.pythonhosted.org/packages/c5/1b/f0e4d13e6adf866ce9b069e191f303a30ab1277e037037a365c3aad5cc9c/regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20", size = 284589, upload-time = "2024-11-06T20:09:35.504Z" }, - { url = "https://files.pythonhosted.org/packages/25/4d/ab21047f446693887f25510887e6820b93f791992994f6498b0318904d4a/regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114", size = 792121, upload-time = "2024-11-06T20:09:37.701Z" }, - { url = "https://files.pythonhosted.org/packages/45/ee/c867e15cd894985cb32b731d89576c41a4642a57850c162490ea34b78c3b/regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3", size = 831275, upload-time = "2024-11-06T20:09:40.371Z" }, - { url = "https://files.pythonhosted.org/packages/b3/12/b0f480726cf1c60f6536fa5e1c95275a77624f3ac8fdccf79e6727499e28/regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f", size = 818257, upload-time = "2024-11-06T20:09:43.059Z" }, - { url = "https://files.pythonhosted.org/packages/bf/ce/0d0e61429f603bac433910d99ef1a02ce45a8967ffbe3cbee48599e62d88/regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0", size = 792727, upload-time = "2024-11-06T20:09:48.19Z" }, - { url = "https://files.pythonhosted.org/packages/e4/c1/243c83c53d4a419c1556f43777ccb552bccdf79d08fda3980e4e77dd9137/regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55", size = 780667, upload-time = "2024-11-06T20:09:49.828Z" }, - { url = "https://files.pythonhosted.org/packages/c5/f4/75eb0dd4ce4b37f04928987f1d22547ddaf6c4bae697623c1b05da67a8aa/regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89", size = 776963, upload-time = "2024-11-06T20:09:51.819Z" }, - { url = "https://files.pythonhosted.org/packages/16/5d/95c568574e630e141a69ff8a254c2f188b4398e813c40d49228c9bbd9875/regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d", size = 784700, upload-time = "2024-11-06T20:09:53.982Z" }, - { url = "https://files.pythonhosted.org/packages/8e/b5/f8495c7917f15cc6fee1e7f395e324ec3e00ab3c665a7dc9d27562fd5290/regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34", size = 848592, upload-time = "2024-11-06T20:09:56.222Z" }, - { url = "https://files.pythonhosted.org/packages/1c/80/6dd7118e8cb212c3c60b191b932dc57db93fb2e36fb9e0e92f72a5909af9/regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d", size = 852929, upload-time = "2024-11-06T20:09:58.642Z" }, - { url = "https://files.pythonhosted.org/packages/11/9b/5a05d2040297d2d254baf95eeeb6df83554e5e1df03bc1a6687fc4ba1f66/regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45", size = 781213, upload-time = "2024-11-06T20:10:00.867Z" }, - { url = "https://files.pythonhosted.org/packages/26/b7/b14e2440156ab39e0177506c08c18accaf2b8932e39fb092074de733d868/regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9", size = 261734, upload-time = "2024-11-06T20:10:03.361Z" }, - { url = "https://files.pythonhosted.org/packages/80/32/763a6cc01d21fb3819227a1cc3f60fd251c13c37c27a73b8ff4315433a8e/regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60", size = 274052, upload-time = "2024-11-06T20:10:05.179Z" }, - { url = "https://files.pythonhosted.org/packages/ba/30/9a87ce8336b172cc232a0db89a3af97929d06c11ceaa19d97d84fa90a8f8/regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a", size = 483781, upload-time = "2024-11-06T20:10:07.07Z" }, - { url = "https://files.pythonhosted.org/packages/01/e8/00008ad4ff4be8b1844786ba6636035f7ef926db5686e4c0f98093612add/regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9", size = 288455, upload-time = "2024-11-06T20:10:09.117Z" }, - { url = "https://files.pythonhosted.org/packages/60/85/cebcc0aff603ea0a201667b203f13ba75d9fc8668fab917ac5b2de3967bc/regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2", size = 284759, upload-time = "2024-11-06T20:10:11.155Z" }, - { url = "https://files.pythonhosted.org/packages/94/2b/701a4b0585cb05472a4da28ee28fdfe155f3638f5e1ec92306d924e5faf0/regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4", size = 794976, upload-time = "2024-11-06T20:10:13.24Z" }, - { url = "https://files.pythonhosted.org/packages/4b/bf/fa87e563bf5fee75db8915f7352e1887b1249126a1be4813837f5dbec965/regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577", size = 833077, upload-time = "2024-11-06T20:10:15.37Z" }, - { url = "https://files.pythonhosted.org/packages/a1/56/7295e6bad94b047f4d0834e4779491b81216583c00c288252ef625c01d23/regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3", size = 823160, upload-time = "2024-11-06T20:10:19.027Z" }, - { url = "https://files.pythonhosted.org/packages/fb/13/e3b075031a738c9598c51cfbc4c7879e26729c53aa9cca59211c44235314/regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e", size = 796896, upload-time = "2024-11-06T20:10:21.85Z" }, - { url = "https://files.pythonhosted.org/packages/24/56/0b3f1b66d592be6efec23a795b37732682520b47c53da5a32c33ed7d84e3/regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe", size = 783997, upload-time = "2024-11-06T20:10:24.329Z" }, - { url = "https://files.pythonhosted.org/packages/f9/a1/eb378dada8b91c0e4c5f08ffb56f25fcae47bf52ad18f9b2f33b83e6d498/regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e", size = 781725, upload-time = "2024-11-06T20:10:28.067Z" }, - { url = "https://files.pythonhosted.org/packages/83/f2/033e7dec0cfd6dda93390089864732a3409246ffe8b042e9554afa9bff4e/regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29", size = 789481, upload-time = "2024-11-06T20:10:31.612Z" }, - { url = "https://files.pythonhosted.org/packages/83/23/15d4552ea28990a74e7696780c438aadd73a20318c47e527b47a4a5a596d/regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39", size = 852896, upload-time = "2024-11-06T20:10:34.054Z" }, - { url = "https://files.pythonhosted.org/packages/e3/39/ed4416bc90deedbfdada2568b2cb0bc1fdb98efe11f5378d9892b2a88f8f/regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51", size = 860138, upload-time = "2024-11-06T20:10:36.142Z" }, - { url = "https://files.pythonhosted.org/packages/93/2d/dd56bb76bd8e95bbce684326302f287455b56242a4f9c61f1bc76e28360e/regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad", size = 787692, upload-time = "2024-11-06T20:10:38.394Z" }, - { url = "https://files.pythonhosted.org/packages/0b/55/31877a249ab7a5156758246b9c59539abbeba22461b7d8adc9e8475ff73e/regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54", size = 262135, upload-time = "2024-11-06T20:10:40.367Z" }, - { url = "https://files.pythonhosted.org/packages/38/ec/ad2d7de49a600cdb8dd78434a1aeffe28b9d6fc42eb36afab4a27ad23384/regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b", size = 273567, upload-time = "2024-11-06T20:10:43.467Z" }, - { url = "https://files.pythonhosted.org/packages/90/73/bcb0e36614601016552fa9344544a3a2ae1809dc1401b100eab02e772e1f/regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84", size = 483525, upload-time = "2024-11-06T20:10:45.19Z" }, - { url = "https://files.pythonhosted.org/packages/0f/3f/f1a082a46b31e25291d830b369b6b0c5576a6f7fb89d3053a354c24b8a83/regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4", size = 288324, upload-time = "2024-11-06T20:10:47.177Z" }, - { url = "https://files.pythonhosted.org/packages/09/c9/4e68181a4a652fb3ef5099e077faf4fd2a694ea6e0f806a7737aff9e758a/regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0", size = 284617, upload-time = "2024-11-06T20:10:49.312Z" }, - { url = "https://files.pythonhosted.org/packages/fc/fd/37868b75eaf63843165f1d2122ca6cb94bfc0271e4428cf58c0616786dce/regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0", size = 795023, upload-time = "2024-11-06T20:10:51.102Z" }, - { url = "https://files.pythonhosted.org/packages/c4/7c/d4cd9c528502a3dedb5c13c146e7a7a539a3853dc20209c8e75d9ba9d1b2/regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7", size = 833072, upload-time = "2024-11-06T20:10:52.926Z" }, - { url = "https://files.pythonhosted.org/packages/4f/db/46f563a08f969159c5a0f0e722260568425363bea43bb7ae370becb66a67/regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7", size = 823130, upload-time = "2024-11-06T20:10:54.828Z" }, - { url = "https://files.pythonhosted.org/packages/db/60/1eeca2074f5b87df394fccaa432ae3fc06c9c9bfa97c5051aed70e6e00c2/regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c", size = 796857, upload-time = "2024-11-06T20:10:56.634Z" }, - { url = "https://files.pythonhosted.org/packages/10/db/ac718a08fcee981554d2f7bb8402f1faa7e868c1345c16ab1ebec54b0d7b/regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3", size = 784006, upload-time = "2024-11-06T20:10:59.369Z" }, - { url = "https://files.pythonhosted.org/packages/c2/41/7da3fe70216cea93144bf12da2b87367590bcf07db97604edeea55dac9ad/regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07", size = 781650, upload-time = "2024-11-06T20:11:02.042Z" }, - { url = "https://files.pythonhosted.org/packages/a7/d5/880921ee4eec393a4752e6ab9f0fe28009435417c3102fc413f3fe81c4e5/regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e", size = 789545, upload-time = "2024-11-06T20:11:03.933Z" }, - { url = "https://files.pythonhosted.org/packages/dc/96/53770115e507081122beca8899ab7f5ae28ae790bfcc82b5e38976df6a77/regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6", size = 853045, upload-time = "2024-11-06T20:11:06.497Z" }, - { url = "https://files.pythonhosted.org/packages/31/d3/1372add5251cc2d44b451bd94f43b2ec78e15a6e82bff6a290ef9fd8f00a/regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4", size = 860182, upload-time = "2024-11-06T20:11:09.06Z" }, - { url = "https://files.pythonhosted.org/packages/ed/e3/c446a64984ea9f69982ba1a69d4658d5014bc7a0ea468a07e1a1265db6e2/regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d", size = 787733, upload-time = "2024-11-06T20:11:11.256Z" }, - { url = "https://files.pythonhosted.org/packages/2b/f1/e40c8373e3480e4f29f2692bd21b3e05f296d3afebc7e5dcf21b9756ca1c/regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff", size = 262122, upload-time = "2024-11-06T20:11:13.161Z" }, - { url = "https://files.pythonhosted.org/packages/45/94/bc295babb3062a731f52621cdc992d123111282e291abaf23faa413443ea/regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a", size = 273545, upload-time = "2024-11-06T20:11:15Z" }, +version = "2025.7.34" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/de/e13fa6dc61d78b30ba47481f99933a3b49a57779d625c392d8036770a60d/regex-2025.7.34.tar.gz", hash = "sha256:9ead9765217afd04a86822dfcd4ed2747dfe426e887da413b15ff0ac2457e21a", size = 400714, upload-time = "2025-07-31T00:21:16.262Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0d/85/f497b91577169472f7c1dc262a5ecc65e39e146fc3a52c571e5daaae4b7d/regex-2025.7.34-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:da304313761b8500b8e175eb2040c4394a875837d5635f6256d6fa0377ad32c8", size = 484594, upload-time = "2025-07-31T00:19:13.927Z" }, + { url = "https://files.pythonhosted.org/packages/1c/c5/ad2a5c11ce9e6257fcbfd6cd965d07502f6054aaa19d50a3d7fd991ec5d1/regex-2025.7.34-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:35e43ebf5b18cd751ea81455b19acfdec402e82fe0dc6143edfae4c5c4b3909a", size = 289294, upload-time = "2025-07-31T00:19:15.395Z" }, + { url = "https://files.pythonhosted.org/packages/8e/01/83ffd9641fcf5e018f9b51aa922c3e538ac9439424fda3df540b643ecf4f/regex-2025.7.34-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:96bbae4c616726f4661fe7bcad5952e10d25d3c51ddc388189d8864fbc1b3c68", size = 285933, upload-time = "2025-07-31T00:19:16.704Z" }, + { url = "https://files.pythonhosted.org/packages/77/20/5edab2e5766f0259bc1da7381b07ce6eb4401b17b2254d02f492cd8a81a8/regex-2025.7.34-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9feab78a1ffa4f2b1e27b1bcdaad36f48c2fed4870264ce32f52a393db093c78", size = 792335, upload-time = "2025-07-31T00:19:18.561Z" }, + { url = "https://files.pythonhosted.org/packages/30/bd/744d3ed8777dce8487b2606b94925e207e7c5931d5870f47f5b643a4580a/regex-2025.7.34-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f14b36e6d4d07f1a5060f28ef3b3561c5d95eb0651741474ce4c0a4c56ba8719", size = 858605, upload-time = "2025-07-31T00:19:20.204Z" }, + { url = "https://files.pythonhosted.org/packages/99/3d/93754176289718d7578c31d151047e7b8acc7a8c20e7706716f23c49e45e/regex-2025.7.34-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85c3a958ef8b3d5079c763477e1f09e89d13ad22198a37e9d7b26b4b17438b33", size = 905780, upload-time = "2025-07-31T00:19:21.876Z" }, + { url = "https://files.pythonhosted.org/packages/ee/2e/c689f274a92deffa03999a430505ff2aeace408fd681a90eafa92fdd6930/regex-2025.7.34-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:37555e4ae0b93358fa7c2d240a4291d4a4227cc7c607d8f85596cdb08ec0a083", size = 798868, upload-time = "2025-07-31T00:19:23.222Z" }, + { url = "https://files.pythonhosted.org/packages/0d/9e/39673688805d139b33b4a24851a71b9978d61915c4d72b5ffda324d0668a/regex-2025.7.34-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ee38926f31f1aa61b0232a3a11b83461f7807661c062df9eb88769d86e6195c3", size = 781784, upload-time = "2025-07-31T00:19:24.59Z" }, + { url = "https://files.pythonhosted.org/packages/18/bd/4c1cab12cfabe14beaa076523056b8ab0c882a8feaf0a6f48b0a75dab9ed/regex-2025.7.34-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a664291c31cae9c4a30589bd8bc2ebb56ef880c9c6264cb7643633831e606a4d", size = 852837, upload-time = "2025-07-31T00:19:25.911Z" }, + { url = "https://files.pythonhosted.org/packages/cb/21/663d983cbb3bba537fc213a579abbd0f263fb28271c514123f3c547ab917/regex-2025.7.34-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f3e5c1e0925e77ec46ddc736b756a6da50d4df4ee3f69536ffb2373460e2dafd", size = 844240, upload-time = "2025-07-31T00:19:27.688Z" }, + { url = "https://files.pythonhosted.org/packages/8e/2d/9beeeb913bc5d32faa913cf8c47e968da936af61ec20af5d269d0f84a100/regex-2025.7.34-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d428fc7731dcbb4e2ffe43aeb8f90775ad155e7db4347a639768bc6cd2df881a", size = 787139, upload-time = "2025-07-31T00:19:29.475Z" }, + { url = "https://files.pythonhosted.org/packages/eb/f5/9b9384415fdc533551be2ba805dd8c4621873e5df69c958f403bfd3b2b6e/regex-2025.7.34-cp311-cp311-win32.whl", hash = "sha256:e154a7ee7fa18333ad90b20e16ef84daaeac61877c8ef942ec8dfa50dc38b7a1", size = 264019, upload-time = "2025-07-31T00:19:31.129Z" }, + { url = "https://files.pythonhosted.org/packages/18/9d/e069ed94debcf4cc9626d652a48040b079ce34c7e4fb174f16874958d485/regex-2025.7.34-cp311-cp311-win_amd64.whl", hash = "sha256:24257953d5c1d6d3c129ab03414c07fc1a47833c9165d49b954190b2b7f21a1a", size = 276047, upload-time = "2025-07-31T00:19:32.497Z" }, + { url = "https://files.pythonhosted.org/packages/fd/cf/3bafbe9d1fd1db77355e7fbbbf0d0cfb34501a8b8e334deca14f94c7b315/regex-2025.7.34-cp311-cp311-win_arm64.whl", hash = "sha256:3157aa512b9e606586900888cd469a444f9b898ecb7f8931996cb715f77477f0", size = 268362, upload-time = "2025-07-31T00:19:34.094Z" }, + { url = "https://files.pythonhosted.org/packages/ff/f0/31d62596c75a33f979317658e8d261574785c6cd8672c06741ce2e2e2070/regex-2025.7.34-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7f7211a746aced993bef487de69307a38c5ddd79257d7be83f7b202cb59ddb50", size = 485492, upload-time = "2025-07-31T00:19:35.57Z" }, + { url = "https://files.pythonhosted.org/packages/d8/16/b818d223f1c9758c3434be89aa1a01aae798e0e0df36c1f143d1963dd1ee/regex-2025.7.34-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fb31080f2bd0681484b275461b202b5ad182f52c9ec606052020fe13eb13a72f", size = 290000, upload-time = "2025-07-31T00:19:37.175Z" }, + { url = "https://files.pythonhosted.org/packages/cd/70/69506d53397b4bd6954061bae75677ad34deb7f6ca3ba199660d6f728ff5/regex-2025.7.34-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0200a5150c4cf61e407038f4b4d5cdad13e86345dac29ff9dab3d75d905cf130", size = 286072, upload-time = "2025-07-31T00:19:38.612Z" }, + { url = "https://files.pythonhosted.org/packages/b0/73/536a216d5f66084fb577bb0543b5cb7de3272eb70a157f0c3a542f1c2551/regex-2025.7.34-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:739a74970e736df0773788377969c9fea3876c2fc13d0563f98e5503e5185f46", size = 797341, upload-time = "2025-07-31T00:19:40.119Z" }, + { url = "https://files.pythonhosted.org/packages/26/af/733f8168449e56e8f404bb807ea7189f59507cbea1b67a7bbcd92f8bf844/regex-2025.7.34-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4fef81b2f7ea6a2029161ed6dea9ae13834c28eb5a95b8771828194a026621e4", size = 862556, upload-time = "2025-07-31T00:19:41.556Z" }, + { url = "https://files.pythonhosted.org/packages/19/dd/59c464d58c06c4f7d87de4ab1f590e430821345a40c5d345d449a636d15f/regex-2025.7.34-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ea74cf81fe61a7e9d77989050d0089a927ab758c29dac4e8e1b6c06fccf3ebf0", size = 910762, upload-time = "2025-07-31T00:19:43Z" }, + { url = "https://files.pythonhosted.org/packages/37/a8/b05ccf33ceca0815a1e253693b2c86544932ebcc0049c16b0fbdf18b688b/regex-2025.7.34-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e4636a7f3b65a5f340ed9ddf53585c42e3ff37101d383ed321bfe5660481744b", size = 801892, upload-time = "2025-07-31T00:19:44.645Z" }, + { url = "https://files.pythonhosted.org/packages/5f/9a/b993cb2e634cc22810afd1652dba0cae156c40d4864285ff486c73cd1996/regex-2025.7.34-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6cef962d7834437fe8d3da6f9bfc6f93f20f218266dcefec0560ed7765f5fe01", size = 786551, upload-time = "2025-07-31T00:19:46.127Z" }, + { url = "https://files.pythonhosted.org/packages/2d/79/7849d67910a0de4e26834b5bb816e028e35473f3d7ae563552ea04f58ca2/regex-2025.7.34-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:cbe1698e5b80298dbce8df4d8d1182279fbdaf1044e864cbc9d53c20e4a2be77", size = 856457, upload-time = "2025-07-31T00:19:47.562Z" }, + { url = "https://files.pythonhosted.org/packages/91/c6/de516bc082524b27e45cb4f54e28bd800c01efb26d15646a65b87b13a91e/regex-2025.7.34-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:32b9f9bcf0f605eb094b08e8da72e44badabb63dde6b83bd530580b488d1c6da", size = 848902, upload-time = "2025-07-31T00:19:49.312Z" }, + { url = "https://files.pythonhosted.org/packages/7d/22/519ff8ba15f732db099b126f039586bd372da6cd4efb810d5d66a5daeda1/regex-2025.7.34-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:524c868ba527eab4e8744a9287809579f54ae8c62fbf07d62aacd89f6026b282", size = 788038, upload-time = "2025-07-31T00:19:50.794Z" }, + { url = "https://files.pythonhosted.org/packages/3f/7d/aabb467d8f57d8149895d133c88eb809a1a6a0fe262c1d508eb9dfabb6f9/regex-2025.7.34-cp312-cp312-win32.whl", hash = "sha256:d600e58ee6d036081c89696d2bdd55d507498a7180df2e19945c6642fac59588", size = 264417, upload-time = "2025-07-31T00:19:52.292Z" }, + { url = "https://files.pythonhosted.org/packages/3b/39/bd922b55a4fc5ad5c13753274e5b536f5b06ec8eb9747675668491c7ab7a/regex-2025.7.34-cp312-cp312-win_amd64.whl", hash = "sha256:9a9ab52a466a9b4b91564437b36417b76033e8778e5af8f36be835d8cb370d62", size = 275387, upload-time = "2025-07-31T00:19:53.593Z" }, + { url = "https://files.pythonhosted.org/packages/f7/3c/c61d2fdcecb754a40475a3d1ef9a000911d3e3fc75c096acf44b0dfb786a/regex-2025.7.34-cp312-cp312-win_arm64.whl", hash = "sha256:c83aec91af9c6fbf7c743274fd952272403ad9a9db05fe9bfc9df8d12b45f176", size = 268482, upload-time = "2025-07-31T00:19:55.183Z" }, + { url = "https://files.pythonhosted.org/packages/15/16/b709b2119975035169a25aa8e4940ca177b1a2e25e14f8d996d09130368e/regex-2025.7.34-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c3c9740a77aeef3f5e3aaab92403946a8d34437db930a0280e7e81ddcada61f5", size = 485334, upload-time = "2025-07-31T00:19:56.58Z" }, + { url = "https://files.pythonhosted.org/packages/94/a6/c09136046be0595f0331bc58a0e5f89c2d324cf734e0b0ec53cf4b12a636/regex-2025.7.34-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:69ed3bc611540f2ea70a4080f853741ec698be556b1df404599f8724690edbcd", size = 289942, upload-time = "2025-07-31T00:19:57.943Z" }, + { url = "https://files.pythonhosted.org/packages/36/91/08fc0fd0f40bdfb0e0df4134ee37cfb16e66a1044ac56d36911fd01c69d2/regex-2025.7.34-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d03c6f9dcd562c56527c42b8530aad93193e0b3254a588be1f2ed378cdfdea1b", size = 285991, upload-time = "2025-07-31T00:19:59.837Z" }, + { url = "https://files.pythonhosted.org/packages/be/2f/99dc8f6f756606f0c214d14c7b6c17270b6bbe26d5c1f05cde9dbb1c551f/regex-2025.7.34-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6164b1d99dee1dfad33f301f174d8139d4368a9fb50bf0a3603b2eaf579963ad", size = 797415, upload-time = "2025-07-31T00:20:01.668Z" }, + { url = "https://files.pythonhosted.org/packages/62/cf/2fcdca1110495458ba4e95c52ce73b361cf1cafd8a53b5c31542cde9a15b/regex-2025.7.34-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1e4f4f62599b8142362f164ce776f19d79bdd21273e86920a7b604a4275b4f59", size = 862487, upload-time = "2025-07-31T00:20:03.142Z" }, + { url = "https://files.pythonhosted.org/packages/90/38/899105dd27fed394e3fae45607c1983e138273ec167e47882fc401f112b9/regex-2025.7.34-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:72a26dcc6a59c057b292f39d41465d8233a10fd69121fa24f8f43ec6294e5415", size = 910717, upload-time = "2025-07-31T00:20:04.727Z" }, + { url = "https://files.pythonhosted.org/packages/ee/f6/4716198dbd0bcc9c45625ac4c81a435d1c4d8ad662e8576dac06bab35b17/regex-2025.7.34-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5273fddf7a3e602695c92716c420c377599ed3c853ea669c1fe26218867002f", size = 801943, upload-time = "2025-07-31T00:20:07.1Z" }, + { url = "https://files.pythonhosted.org/packages/40/5d/cff8896d27e4e3dd11dd72ac78797c7987eb50fe4debc2c0f2f1682eb06d/regex-2025.7.34-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c1844be23cd40135b3a5a4dd298e1e0c0cb36757364dd6cdc6025770363e06c1", size = 786664, upload-time = "2025-07-31T00:20:08.818Z" }, + { url = "https://files.pythonhosted.org/packages/10/29/758bf83cf7b4c34f07ac3423ea03cee3eb3176941641e4ccc05620f6c0b8/regex-2025.7.34-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dde35e2afbbe2272f8abee3b9fe6772d9b5a07d82607b5788e8508974059925c", size = 856457, upload-time = "2025-07-31T00:20:10.328Z" }, + { url = "https://files.pythonhosted.org/packages/d7/30/c19d212b619963c5b460bfed0ea69a092c6a43cba52a973d46c27b3e2975/regex-2025.7.34-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:f3f6e8e7af516a7549412ce57613e859c3be27d55341a894aacaa11703a4c31a", size = 849008, upload-time = "2025-07-31T00:20:11.823Z" }, + { url = "https://files.pythonhosted.org/packages/9e/b8/3c35da3b12c87e3cc00010ef6c3a4ae787cff0bc381aa3d251def219969a/regex-2025.7.34-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:469142fb94a869beb25b5f18ea87646d21def10fbacb0bcb749224f3509476f0", size = 788101, upload-time = "2025-07-31T00:20:13.729Z" }, + { url = "https://files.pythonhosted.org/packages/47/80/2f46677c0b3c2b723b2c358d19f9346e714113865da0f5f736ca1a883bde/regex-2025.7.34-cp313-cp313-win32.whl", hash = "sha256:da7507d083ee33ccea1310447410c27ca11fb9ef18c95899ca57ff60a7e4d8f1", size = 264401, upload-time = "2025-07-31T00:20:15.233Z" }, + { url = "https://files.pythonhosted.org/packages/be/fa/917d64dd074682606a003cba33585c28138c77d848ef72fc77cbb1183849/regex-2025.7.34-cp313-cp313-win_amd64.whl", hash = "sha256:9d644de5520441e5f7e2db63aec2748948cc39ed4d7a87fd5db578ea4043d997", size = 275368, upload-time = "2025-07-31T00:20:16.711Z" }, + { url = "https://files.pythonhosted.org/packages/65/cd/f94383666704170a2154a5df7b16be28f0c27a266bffcd843e58bc84120f/regex-2025.7.34-cp313-cp313-win_arm64.whl", hash = "sha256:7bf1c5503a9f2cbd2f52d7e260acb3131b07b6273c470abb78568174fe6bde3f", size = 268482, upload-time = "2025-07-31T00:20:18.189Z" }, + { url = "https://files.pythonhosted.org/packages/ac/23/6376f3a23cf2f3c00514b1cdd8c990afb4dfbac3cb4a68b633c6b7e2e307/regex-2025.7.34-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:8283afe7042d8270cecf27cca558873168e771183d4d593e3c5fe5f12402212a", size = 485385, upload-time = "2025-07-31T00:20:19.692Z" }, + { url = "https://files.pythonhosted.org/packages/73/5b/6d4d3a0b4d312adbfd6d5694c8dddcf1396708976dd87e4d00af439d962b/regex-2025.7.34-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:6c053f9647e3421dd2f5dff8172eb7b4eec129df9d1d2f7133a4386319b47435", size = 289788, upload-time = "2025-07-31T00:20:21.941Z" }, + { url = "https://files.pythonhosted.org/packages/92/71/5862ac9913746e5054d01cb9fb8125b3d0802c0706ef547cae1e7f4428fa/regex-2025.7.34-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a16dd56bbcb7d10e62861c3cd000290ddff28ea142ffb5eb3470f183628011ac", size = 286136, upload-time = "2025-07-31T00:20:26.146Z" }, + { url = "https://files.pythonhosted.org/packages/27/df/5b505dc447eb71278eba10d5ec940769ca89c1af70f0468bfbcb98035dc2/regex-2025.7.34-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69c593ff5a24c0d5c1112b0df9b09eae42b33c014bdca7022d6523b210b69f72", size = 797753, upload-time = "2025-07-31T00:20:27.919Z" }, + { url = "https://files.pythonhosted.org/packages/86/38/3e3dc953d13998fa047e9a2414b556201dbd7147034fbac129392363253b/regex-2025.7.34-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:98d0ce170fcde1a03b5df19c5650db22ab58af375aaa6ff07978a85c9f250f0e", size = 863263, upload-time = "2025-07-31T00:20:29.803Z" }, + { url = "https://files.pythonhosted.org/packages/68/e5/3ff66b29dde12f5b874dda2d9dec7245c2051f2528d8c2a797901497f140/regex-2025.7.34-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d72765a4bff8c43711d5b0f5b452991a9947853dfa471972169b3cc0ba1d0751", size = 910103, upload-time = "2025-07-31T00:20:31.313Z" }, + { url = "https://files.pythonhosted.org/packages/9e/fe/14176f2182125977fba3711adea73f472a11f3f9288c1317c59cd16ad5e6/regex-2025.7.34-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4494f8fd95a77eb434039ad8460e64d57baa0434f1395b7da44015bef650d0e4", size = 801709, upload-time = "2025-07-31T00:20:33.323Z" }, + { url = "https://files.pythonhosted.org/packages/5a/0d/80d4e66ed24f1ba876a9e8e31b709f9fd22d5c266bf5f3ab3c1afe683d7d/regex-2025.7.34-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4f42b522259c66e918a0121a12429b2abcf696c6f967fa37bdc7b72e61469f98", size = 786726, upload-time = "2025-07-31T00:20:35.252Z" }, + { url = "https://files.pythonhosted.org/packages/12/75/c3ebb30e04a56c046f5c85179dc173818551037daae2c0c940c7b19152cb/regex-2025.7.34-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:aaef1f056d96a0a5d53ad47d019d5b4c66fe4be2da87016e0d43b7242599ffc7", size = 857306, upload-time = "2025-07-31T00:20:37.12Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b2/a4dc5d8b14f90924f27f0ac4c4c4f5e195b723be98adecc884f6716614b6/regex-2025.7.34-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:656433e5b7dccc9bc0da6312da8eb897b81f5e560321ec413500e5367fcd5d47", size = 848494, upload-time = "2025-07-31T00:20:38.818Z" }, + { url = "https://files.pythonhosted.org/packages/0d/21/9ac6e07a4c5e8646a90b56b61f7e9dac11ae0747c857f91d3d2bc7c241d9/regex-2025.7.34-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e91eb2c62c39705e17b4d42d4b86c4e86c884c0d15d9c5a47d0835f8387add8e", size = 787850, upload-time = "2025-07-31T00:20:40.478Z" }, + { url = "https://files.pythonhosted.org/packages/be/6c/d51204e28e7bc54f9a03bb799b04730d7e54ff2718862b8d4e09e7110a6a/regex-2025.7.34-cp314-cp314-win32.whl", hash = "sha256:f978ddfb6216028c8f1d6b0f7ef779949498b64117fc35a939022f67f810bdcb", size = 269730, upload-time = "2025-07-31T00:20:42.253Z" }, + { url = "https://files.pythonhosted.org/packages/74/52/a7e92d02fa1fdef59d113098cb9f02c5d03289a0e9f9e5d4d6acccd10677/regex-2025.7.34-cp314-cp314-win_amd64.whl", hash = "sha256:4b7dc33b9b48fb37ead12ffc7bdb846ac72f99a80373c4da48f64b373a7abeae", size = 278640, upload-time = "2025-07-31T00:20:44.42Z" }, + { url = "https://files.pythonhosted.org/packages/d1/78/a815529b559b1771080faa90c3ab401730661f99d495ab0071649f139ebd/regex-2025.7.34-cp314-cp314-win_arm64.whl", hash = "sha256:4b8c4d39f451e64809912c82392933d80fe2e4a87eeef8859fcc5380d0173c64", size = 271757, upload-time = "2025-07-31T00:20:46.355Z" }, ] [[package]] name = "requests" -version = "2.32.4" +version = "2.32.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, @@ -3319,22 +2940,9 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e1/0a/929373653770d8a0d7ea76c37de6e41f11eb07559b103b1c02cafb3f7cf8/requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422", size = 135258, upload-time = "2025-06-09T16:43:07.34Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c", size = 64847, upload-time = "2025-06-09T16:43:05.728Z" }, -] - -[[package]] -name = "requests-oauthlib" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "oauthlib" }, - { name = "requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/42/f2/05f29bc3913aea15eb670be136045bf5c5bbf4b99ecb839da9b422bb2c85/requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9", size = 55650, upload-time = "2024-03-22T20:32:29.939Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/5d/63d4ae3b9daea098d5d6f5da83984853c1bbacd5dc826764b249fe119d24/requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36", size = 24179, upload-time = "2024-03-22T20:32:28.055Z" }, + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, ] [[package]] @@ -3380,116 +2988,123 @@ wheels = [ [[package]] name = "rich" -version = "14.0.0" +version = "14.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a1/53/830aa4c3066a8ab0ae9a9955976fb770fe9c6102117c8ec4ab3ea62d89e8/rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725", size = 224078, upload-time = "2025-03-30T14:15:14.23Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fe/75/af448d8e52bf1d8fa6a9d089ca6c07ff4453d86c65c145d0a300bb073b9b/rich-14.1.0.tar.gz", hash = "sha256:e497a48b844b0320d45007cdebfeaeed8db2a4f4bcf49f15e455cfc4af11eaa8", size = 224441, upload-time = "2025-07-25T07:32:58.125Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/9b/63f4c7ebc259242c89b3acafdb37b41d1185c07ff0011164674e9076b491/rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0", size = 243229, upload-time = "2025-03-30T14:15:12.283Z" }, + { url = "https://files.pythonhosted.org/packages/e3/30/3c4d035596d3cf444529e0b2953ad0466f6049528a879d27534700580395/rich-14.1.0-py3-none-any.whl", hash = "sha256:536f5f1785986d6dbdea3c75205c473f970777b4a0d6c6dd1b696aa05a3fa04f", size = 243368, upload-time = "2025-07-25T07:32:56.73Z" }, ] [[package]] name = "rpds-py" -version = "0.26.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a5/aa/4456d84bbb54adc6a916fb10c9b374f78ac840337644e4a5eda229c81275/rpds_py-0.26.0.tar.gz", hash = "sha256:20dae58a859b0906f0685642e591056f1e787f3a8b39c8e8749a45dc7d26bdb0", size = 27385, upload-time = "2025-07-01T15:57:13.958Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/09/4c/4ee8f7e512030ff79fda1df3243c88d70fc874634e2dbe5df13ba4210078/rpds_py-0.26.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:9e8cb77286025bdb21be2941d64ac6ca016130bfdcd228739e8ab137eb4406ed", size = 372610, upload-time = "2025-07-01T15:53:58.844Z" }, - { url = "https://files.pythonhosted.org/packages/fa/9d/3dc16be00f14fc1f03c71b1d67c8df98263ab2710a2fbd65a6193214a527/rpds_py-0.26.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5e09330b21d98adc8ccb2dbb9fc6cb434e8908d4c119aeaa772cb1caab5440a0", size = 358032, upload-time = "2025-07-01T15:53:59.985Z" }, - { url = "https://files.pythonhosted.org/packages/e7/5a/7f1bf8f045da2866324a08ae80af63e64e7bfaf83bd31f865a7b91a58601/rpds_py-0.26.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c9c1b92b774b2e68d11193dc39620d62fd8ab33f0a3c77ecdabe19c179cdbc1", size = 381525, upload-time = "2025-07-01T15:54:01.162Z" }, - { url = "https://files.pythonhosted.org/packages/45/8a/04479398c755a066ace10e3d158866beb600867cacae194c50ffa783abd0/rpds_py-0.26.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:824e6d3503ab990d7090768e4dfd9e840837bae057f212ff9f4f05ec6d1975e7", size = 397089, upload-time = "2025-07-01T15:54:02.319Z" }, - { url = "https://files.pythonhosted.org/packages/72/88/9203f47268db488a1b6d469d69c12201ede776bb728b9d9f29dbfd7df406/rpds_py-0.26.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ad7fd2258228bf288f2331f0a6148ad0186b2e3643055ed0db30990e59817a6", size = 514255, upload-time = "2025-07-01T15:54:03.38Z" }, - { url = "https://files.pythonhosted.org/packages/f5/b4/01ce5d1e853ddf81fbbd4311ab1eff0b3cf162d559288d10fd127e2588b5/rpds_py-0.26.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0dc23bbb3e06ec1ea72d515fb572c1fea59695aefbffb106501138762e1e915e", size = 402283, upload-time = "2025-07-01T15:54:04.923Z" }, - { url = "https://files.pythonhosted.org/packages/34/a2/004c99936997bfc644d590a9defd9e9c93f8286568f9c16cdaf3e14429a7/rpds_py-0.26.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d80bf832ac7b1920ee29a426cdca335f96a2b5caa839811803e999b41ba9030d", size = 383881, upload-time = "2025-07-01T15:54:06.482Z" }, - { url = "https://files.pythonhosted.org/packages/05/1b/ef5fba4a8f81ce04c427bfd96223f92f05e6cd72291ce9d7523db3b03a6c/rpds_py-0.26.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0919f38f5542c0a87e7b4afcafab6fd2c15386632d249e9a087498571250abe3", size = 415822, upload-time = "2025-07-01T15:54:07.605Z" }, - { url = "https://files.pythonhosted.org/packages/16/80/5c54195aec456b292f7bd8aa61741c8232964063fd8a75fdde9c1e982328/rpds_py-0.26.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d422b945683e409000c888e384546dbab9009bb92f7c0b456e217988cf316107", size = 558347, upload-time = "2025-07-01T15:54:08.591Z" }, - { url = "https://files.pythonhosted.org/packages/f2/1c/1845c1b1fd6d827187c43afe1841d91678d7241cbdb5420a4c6de180a538/rpds_py-0.26.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:77a7711fa562ba2da1aa757e11024ad6d93bad6ad7ede5afb9af144623e5f76a", size = 587956, upload-time = "2025-07-01T15:54:09.963Z" }, - { url = "https://files.pythonhosted.org/packages/2e/ff/9e979329dd131aa73a438c077252ddabd7df6d1a7ad7b9aacf6261f10faa/rpds_py-0.26.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:238e8c8610cb7c29460e37184f6799547f7e09e6a9bdbdab4e8edb90986a2318", size = 554363, upload-time = "2025-07-01T15:54:11.073Z" }, - { url = "https://files.pythonhosted.org/packages/00/8b/d78cfe034b71ffbe72873a136e71acc7a831a03e37771cfe59f33f6de8a2/rpds_py-0.26.0-cp311-cp311-win32.whl", hash = "sha256:893b022bfbdf26d7bedb083efeea624e8550ca6eb98bf7fea30211ce95b9201a", size = 220123, upload-time = "2025-07-01T15:54:12.382Z" }, - { url = "https://files.pythonhosted.org/packages/94/c1/3c8c94c7dd3905dbfde768381ce98778500a80db9924731d87ddcdb117e9/rpds_py-0.26.0-cp311-cp311-win_amd64.whl", hash = "sha256:87a5531de9f71aceb8af041d72fc4cab4943648d91875ed56d2e629bef6d4c03", size = 231732, upload-time = "2025-07-01T15:54:13.434Z" }, - { url = "https://files.pythonhosted.org/packages/67/93/e936fbed1b734eabf36ccb5d93c6a2e9246fbb13c1da011624b7286fae3e/rpds_py-0.26.0-cp311-cp311-win_arm64.whl", hash = "sha256:de2713f48c1ad57f89ac25b3cb7daed2156d8e822cf0eca9b96a6f990718cc41", size = 221917, upload-time = "2025-07-01T15:54:14.559Z" }, - { url = "https://files.pythonhosted.org/packages/ea/86/90eb87c6f87085868bd077c7a9938006eb1ce19ed4d06944a90d3560fce2/rpds_py-0.26.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:894514d47e012e794f1350f076c427d2347ebf82f9b958d554d12819849a369d", size = 363933, upload-time = "2025-07-01T15:54:15.734Z" }, - { url = "https://files.pythonhosted.org/packages/63/78/4469f24d34636242c924626082b9586f064ada0b5dbb1e9d096ee7a8e0c6/rpds_py-0.26.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc921b96fa95a097add244da36a1d9e4f3039160d1d30f1b35837bf108c21136", size = 350447, upload-time = "2025-07-01T15:54:16.922Z" }, - { url = "https://files.pythonhosted.org/packages/ad/91/c448ed45efdfdade82348d5e7995e15612754826ea640afc20915119734f/rpds_py-0.26.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e1157659470aa42a75448b6e943c895be8c70531c43cb78b9ba990778955582", size = 384711, upload-time = "2025-07-01T15:54:18.101Z" }, - { url = "https://files.pythonhosted.org/packages/ec/43/e5c86fef4be7f49828bdd4ecc8931f0287b1152c0bb0163049b3218740e7/rpds_py-0.26.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:521ccf56f45bb3a791182dc6b88ae5f8fa079dd705ee42138c76deb1238e554e", size = 400865, upload-time = "2025-07-01T15:54:19.295Z" }, - { url = "https://files.pythonhosted.org/packages/55/34/e00f726a4d44f22d5c5fe2e5ddd3ac3d7fd3f74a175607781fbdd06fe375/rpds_py-0.26.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9def736773fd56b305c0eef698be5192c77bfa30d55a0e5885f80126c4831a15", size = 517763, upload-time = "2025-07-01T15:54:20.858Z" }, - { url = "https://files.pythonhosted.org/packages/52/1c/52dc20c31b147af724b16104500fba13e60123ea0334beba7b40e33354b4/rpds_py-0.26.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cdad4ea3b4513b475e027be79e5a0ceac8ee1c113a1a11e5edc3c30c29f964d8", size = 406651, upload-time = "2025-07-01T15:54:22.508Z" }, - { url = "https://files.pythonhosted.org/packages/2e/77/87d7bfabfc4e821caa35481a2ff6ae0b73e6a391bb6b343db2c91c2b9844/rpds_py-0.26.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82b165b07f416bdccf5c84546a484cc8f15137ca38325403864bfdf2b5b72f6a", size = 386079, upload-time = "2025-07-01T15:54:23.987Z" }, - { url = "https://files.pythonhosted.org/packages/e3/d4/7f2200c2d3ee145b65b3cddc4310d51f7da6a26634f3ac87125fd789152a/rpds_py-0.26.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d04cab0a54b9dba4d278fe955a1390da3cf71f57feb78ddc7cb67cbe0bd30323", size = 421379, upload-time = "2025-07-01T15:54:25.073Z" }, - { url = "https://files.pythonhosted.org/packages/ae/13/9fdd428b9c820869924ab62236b8688b122baa22d23efdd1c566938a39ba/rpds_py-0.26.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:79061ba1a11b6a12743a2b0f72a46aa2758613d454aa6ba4f5a265cc48850158", size = 562033, upload-time = "2025-07-01T15:54:26.225Z" }, - { url = "https://files.pythonhosted.org/packages/f3/e1/b69686c3bcbe775abac3a4c1c30a164a2076d28df7926041f6c0eb5e8d28/rpds_py-0.26.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f405c93675d8d4c5ac87364bb38d06c988e11028a64b52a47158a355079661f3", size = 591639, upload-time = "2025-07-01T15:54:27.424Z" }, - { url = "https://files.pythonhosted.org/packages/5c/c9/1e3d8c8863c84a90197ac577bbc3d796a92502124c27092413426f670990/rpds_py-0.26.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dafd4c44b74aa4bed4b250f1aed165b8ef5de743bcca3b88fc9619b6087093d2", size = 557105, upload-time = "2025-07-01T15:54:29.93Z" }, - { url = "https://files.pythonhosted.org/packages/9f/c5/90c569649057622959f6dcc40f7b516539608a414dfd54b8d77e3b201ac0/rpds_py-0.26.0-cp312-cp312-win32.whl", hash = "sha256:3da5852aad63fa0c6f836f3359647870e21ea96cf433eb393ffa45263a170d44", size = 223272, upload-time = "2025-07-01T15:54:31.128Z" }, - { url = "https://files.pythonhosted.org/packages/7d/16/19f5d9f2a556cfed454eebe4d354c38d51c20f3db69e7b4ce6cff904905d/rpds_py-0.26.0-cp312-cp312-win_amd64.whl", hash = "sha256:cf47cfdabc2194a669dcf7a8dbba62e37a04c5041d2125fae0233b720da6f05c", size = 234995, upload-time = "2025-07-01T15:54:32.195Z" }, - { url = "https://files.pythonhosted.org/packages/83/f0/7935e40b529c0e752dfaa7880224771b51175fce08b41ab4a92eb2fbdc7f/rpds_py-0.26.0-cp312-cp312-win_arm64.whl", hash = "sha256:20ab1ae4fa534f73647aad289003f1104092890849e0266271351922ed5574f8", size = 223198, upload-time = "2025-07-01T15:54:33.271Z" }, - { url = "https://files.pythonhosted.org/packages/6a/67/bb62d0109493b12b1c6ab00de7a5566aa84c0e44217c2d94bee1bd370da9/rpds_py-0.26.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:696764a5be111b036256c0b18cd29783fab22154690fc698062fc1b0084b511d", size = 363917, upload-time = "2025-07-01T15:54:34.755Z" }, - { url = "https://files.pythonhosted.org/packages/4b/f3/34e6ae1925a5706c0f002a8d2d7f172373b855768149796af87bd65dcdb9/rpds_py-0.26.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1e6c15d2080a63aaed876e228efe4f814bc7889c63b1e112ad46fdc8b368b9e1", size = 350073, upload-time = "2025-07-01T15:54:36.292Z" }, - { url = "https://files.pythonhosted.org/packages/75/83/1953a9d4f4e4de7fd0533733e041c28135f3c21485faaef56a8aadbd96b5/rpds_py-0.26.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:390e3170babf42462739a93321e657444f0862c6d722a291accc46f9d21ed04e", size = 384214, upload-time = "2025-07-01T15:54:37.469Z" }, - { url = "https://files.pythonhosted.org/packages/48/0e/983ed1b792b3322ea1d065e67f4b230f3b96025f5ce3878cc40af09b7533/rpds_py-0.26.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7da84c2c74c0f5bc97d853d9e17bb83e2dcafcff0dc48286916001cc114379a1", size = 400113, upload-time = "2025-07-01T15:54:38.954Z" }, - { url = "https://files.pythonhosted.org/packages/69/7f/36c0925fff6f660a80be259c5b4f5e53a16851f946eb080351d057698528/rpds_py-0.26.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c5fe114a6dd480a510b6d3661d09d67d1622c4bf20660a474507aaee7eeeee9", size = 515189, upload-time = "2025-07-01T15:54:40.57Z" }, - { url = "https://files.pythonhosted.org/packages/13/45/cbf07fc03ba7a9b54662c9badb58294ecfb24f828b9732970bd1a431ed5c/rpds_py-0.26.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3100b3090269f3a7ea727b06a6080d4eb7439dca4c0e91a07c5d133bb1727ea7", size = 406998, upload-time = "2025-07-01T15:54:43.025Z" }, - { url = "https://files.pythonhosted.org/packages/6c/b0/8fa5e36e58657997873fd6a1cf621285ca822ca75b4b3434ead047daa307/rpds_py-0.26.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c03c9b0c64afd0320ae57de4c982801271c0c211aa2d37f3003ff5feb75bb04", size = 385903, upload-time = "2025-07-01T15:54:44.752Z" }, - { url = "https://files.pythonhosted.org/packages/4b/f7/b25437772f9f57d7a9fbd73ed86d0dcd76b4c7c6998348c070d90f23e315/rpds_py-0.26.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5963b72ccd199ade6ee493723d18a3f21ba7d5b957017607f815788cef50eaf1", size = 419785, upload-time = "2025-07-01T15:54:46.043Z" }, - { url = "https://files.pythonhosted.org/packages/a7/6b/63ffa55743dfcb4baf2e9e77a0b11f7f97ed96a54558fcb5717a4b2cd732/rpds_py-0.26.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9da4e873860ad5bab3291438525cae80169daecbfafe5657f7f5fb4d6b3f96b9", size = 561329, upload-time = "2025-07-01T15:54:47.64Z" }, - { url = "https://files.pythonhosted.org/packages/2f/07/1f4f5e2886c480a2346b1e6759c00278b8a69e697ae952d82ae2e6ee5db0/rpds_py-0.26.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5afaddaa8e8c7f1f7b4c5c725c0070b6eed0228f705b90a1732a48e84350f4e9", size = 590875, upload-time = "2025-07-01T15:54:48.9Z" }, - { url = "https://files.pythonhosted.org/packages/cc/bc/e6639f1b91c3a55f8c41b47d73e6307051b6e246254a827ede730624c0f8/rpds_py-0.26.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4916dc96489616a6f9667e7526af8fa693c0fdb4f3acb0e5d9f4400eb06a47ba", size = 556636, upload-time = "2025-07-01T15:54:50.619Z" }, - { url = "https://files.pythonhosted.org/packages/05/4c/b3917c45566f9f9a209d38d9b54a1833f2bb1032a3e04c66f75726f28876/rpds_py-0.26.0-cp313-cp313-win32.whl", hash = "sha256:2a343f91b17097c546b93f7999976fd6c9d5900617aa848c81d794e062ab302b", size = 222663, upload-time = "2025-07-01T15:54:52.023Z" }, - { url = "https://files.pythonhosted.org/packages/e0/0b/0851bdd6025775aaa2365bb8de0697ee2558184c800bfef8d7aef5ccde58/rpds_py-0.26.0-cp313-cp313-win_amd64.whl", hash = "sha256:0a0b60701f2300c81b2ac88a5fb893ccfa408e1c4a555a77f908a2596eb875a5", size = 234428, upload-time = "2025-07-01T15:54:53.692Z" }, - { url = "https://files.pythonhosted.org/packages/ed/e8/a47c64ed53149c75fb581e14a237b7b7cd18217e969c30d474d335105622/rpds_py-0.26.0-cp313-cp313-win_arm64.whl", hash = "sha256:257d011919f133a4746958257f2c75238e3ff54255acd5e3e11f3ff41fd14256", size = 222571, upload-time = "2025-07-01T15:54:54.822Z" }, - { url = "https://files.pythonhosted.org/packages/89/bf/3d970ba2e2bcd17d2912cb42874107390f72873e38e79267224110de5e61/rpds_py-0.26.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:529c8156d7506fba5740e05da8795688f87119cce330c244519cf706a4a3d618", size = 360475, upload-time = "2025-07-01T15:54:56.228Z" }, - { url = "https://files.pythonhosted.org/packages/82/9f/283e7e2979fc4ec2d8ecee506d5a3675fce5ed9b4b7cb387ea5d37c2f18d/rpds_py-0.26.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f53ec51f9d24e9638a40cabb95078ade8c99251945dad8d57bf4aabe86ecee35", size = 346692, upload-time = "2025-07-01T15:54:58.561Z" }, - { url = "https://files.pythonhosted.org/packages/e3/03/7e50423c04d78daf391da3cc4330bdb97042fc192a58b186f2d5deb7befd/rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab504c4d654e4a29558eaa5bb8cea5fdc1703ea60a8099ffd9c758472cf913f", size = 379415, upload-time = "2025-07-01T15:54:59.751Z" }, - { url = "https://files.pythonhosted.org/packages/57/00/d11ee60d4d3b16808432417951c63df803afb0e0fc672b5e8d07e9edaaae/rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd0641abca296bc1a00183fe44f7fced8807ed49d501f188faa642d0e4975b83", size = 391783, upload-time = "2025-07-01T15:55:00.898Z" }, - { url = "https://files.pythonhosted.org/packages/08/b3/1069c394d9c0d6d23c5b522e1f6546b65793a22950f6e0210adcc6f97c3e/rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69b312fecc1d017b5327afa81d4da1480f51c68810963a7336d92203dbb3d4f1", size = 512844, upload-time = "2025-07-01T15:55:02.201Z" }, - { url = "https://files.pythonhosted.org/packages/08/3b/c4fbf0926800ed70b2c245ceca99c49f066456755f5d6eb8863c2c51e6d0/rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c741107203954f6fc34d3066d213d0a0c40f7bb5aafd698fb39888af277c70d8", size = 402105, upload-time = "2025-07-01T15:55:03.698Z" }, - { url = "https://files.pythonhosted.org/packages/1c/b0/db69b52ca07413e568dae9dc674627a22297abb144c4d6022c6d78f1e5cc/rpds_py-0.26.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc3e55a7db08dc9a6ed5fb7103019d2c1a38a349ac41901f9f66d7f95750942f", size = 383440, upload-time = "2025-07-01T15:55:05.398Z" }, - { url = "https://files.pythonhosted.org/packages/4c/e1/c65255ad5b63903e56b3bb3ff9dcc3f4f5c3badde5d08c741ee03903e951/rpds_py-0.26.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9e851920caab2dbcae311fd28f4313c6953993893eb5c1bb367ec69d9a39e7ed", size = 412759, upload-time = "2025-07-01T15:55:08.316Z" }, - { url = "https://files.pythonhosted.org/packages/e4/22/bb731077872377a93c6e93b8a9487d0406c70208985831034ccdeed39c8e/rpds_py-0.26.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dfbf280da5f876d0b00c81f26bedce274e72a678c28845453885a9b3c22ae632", size = 556032, upload-time = "2025-07-01T15:55:09.52Z" }, - { url = "https://files.pythonhosted.org/packages/e0/8b/393322ce7bac5c4530fb96fc79cc9ea2f83e968ff5f6e873f905c493e1c4/rpds_py-0.26.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:1cc81d14ddfa53d7f3906694d35d54d9d3f850ef8e4e99ee68bc0d1e5fed9a9c", size = 585416, upload-time = "2025-07-01T15:55:11.216Z" }, - { url = "https://files.pythonhosted.org/packages/49/ae/769dc372211835bf759319a7aae70525c6eb523e3371842c65b7ef41c9c6/rpds_py-0.26.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dca83c498b4650a91efcf7b88d669b170256bf8017a5db6f3e06c2bf031f57e0", size = 554049, upload-time = "2025-07-01T15:55:13.004Z" }, - { url = "https://files.pythonhosted.org/packages/6b/f9/4c43f9cc203d6ba44ce3146246cdc38619d92c7bd7bad4946a3491bd5b70/rpds_py-0.26.0-cp313-cp313t-win32.whl", hash = "sha256:4d11382bcaf12f80b51d790dee295c56a159633a8e81e6323b16e55d81ae37e9", size = 218428, upload-time = "2025-07-01T15:55:14.486Z" }, - { url = "https://files.pythonhosted.org/packages/7e/8b/9286b7e822036a4a977f2f1e851c7345c20528dbd56b687bb67ed68a8ede/rpds_py-0.26.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ff110acded3c22c033e637dd8896e411c7d3a11289b2edf041f86663dbc791e9", size = 231524, upload-time = "2025-07-01T15:55:15.745Z" }, - { url = "https://files.pythonhosted.org/packages/55/07/029b7c45db910c74e182de626dfdae0ad489a949d84a468465cd0ca36355/rpds_py-0.26.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:da619979df60a940cd434084355c514c25cf8eb4cf9a508510682f6c851a4f7a", size = 364292, upload-time = "2025-07-01T15:55:17.001Z" }, - { url = "https://files.pythonhosted.org/packages/13/d1/9b3d3f986216b4d1f584878dca15ce4797aaf5d372d738974ba737bf68d6/rpds_py-0.26.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ea89a2458a1a75f87caabefe789c87539ea4e43b40f18cff526052e35bbb4fdf", size = 350334, upload-time = "2025-07-01T15:55:18.922Z" }, - { url = "https://files.pythonhosted.org/packages/18/98/16d5e7bc9ec715fa9668731d0cf97f6b032724e61696e2db3d47aeb89214/rpds_py-0.26.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feac1045b3327a45944e7dcbeb57530339f6b17baff154df51ef8b0da34c8c12", size = 384875, upload-time = "2025-07-01T15:55:20.399Z" }, - { url = "https://files.pythonhosted.org/packages/f9/13/aa5e2b1ec5ab0e86a5c464d53514c0467bec6ba2507027d35fc81818358e/rpds_py-0.26.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b818a592bd69bfe437ee8368603d4a2d928c34cffcdf77c2e761a759ffd17d20", size = 399993, upload-time = "2025-07-01T15:55:21.729Z" }, - { url = "https://files.pythonhosted.org/packages/17/03/8021810b0e97923abdbab6474c8b77c69bcb4b2c58330777df9ff69dc559/rpds_py-0.26.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a8b0dd8648709b62d9372fc00a57466f5fdeefed666afe3fea5a6c9539a0331", size = 516683, upload-time = "2025-07-01T15:55:22.918Z" }, - { url = "https://files.pythonhosted.org/packages/dc/b1/da8e61c87c2f3d836954239fdbbfb477bb7b54d74974d8f6fcb34342d166/rpds_py-0.26.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6d3498ad0df07d81112aa6ec6c95a7e7b1ae00929fb73e7ebee0f3faaeabad2f", size = 408825, upload-time = "2025-07-01T15:55:24.207Z" }, - { url = "https://files.pythonhosted.org/packages/38/bc/1fc173edaaa0e52c94b02a655db20697cb5fa954ad5a8e15a2c784c5cbdd/rpds_py-0.26.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24a4146ccb15be237fdef10f331c568e1b0e505f8c8c9ed5d67759dac58ac246", size = 387292, upload-time = "2025-07-01T15:55:25.554Z" }, - { url = "https://files.pythonhosted.org/packages/7c/eb/3a9bb4bd90867d21916f253caf4f0d0be7098671b6715ad1cead9fe7bab9/rpds_py-0.26.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a9a63785467b2d73635957d32a4f6e73d5e4df497a16a6392fa066b753e87387", size = 420435, upload-time = "2025-07-01T15:55:27.798Z" }, - { url = "https://files.pythonhosted.org/packages/cd/16/e066dcdb56f5632713445271a3f8d3d0b426d51ae9c0cca387799df58b02/rpds_py-0.26.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:de4ed93a8c91debfd5a047be327b7cc8b0cc6afe32a716bbbc4aedca9e2a83af", size = 562410, upload-time = "2025-07-01T15:55:29.057Z" }, - { url = "https://files.pythonhosted.org/packages/60/22/ddbdec7eb82a0dc2e455be44c97c71c232983e21349836ce9f272e8a3c29/rpds_py-0.26.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:caf51943715b12af827696ec395bfa68f090a4c1a1d2509eb4e2cb69abbbdb33", size = 590724, upload-time = "2025-07-01T15:55:30.719Z" }, - { url = "https://files.pythonhosted.org/packages/2c/b4/95744085e65b7187d83f2fcb0bef70716a1ea0a9e5d8f7f39a86e5d83424/rpds_py-0.26.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4a59e5bc386de021f56337f757301b337d7ab58baa40174fb150accd480bc953", size = 558285, upload-time = "2025-07-01T15:55:31.981Z" }, - { url = "https://files.pythonhosted.org/packages/37/37/6309a75e464d1da2559446f9c811aa4d16343cebe3dbb73701e63f760caa/rpds_py-0.26.0-cp314-cp314-win32.whl", hash = "sha256:92c8db839367ef16a662478f0a2fe13e15f2227da3c1430a782ad0f6ee009ec9", size = 223459, upload-time = "2025-07-01T15:55:33.312Z" }, - { url = "https://files.pythonhosted.org/packages/d9/6f/8e9c11214c46098b1d1391b7e02b70bb689ab963db3b19540cba17315291/rpds_py-0.26.0-cp314-cp314-win_amd64.whl", hash = "sha256:b0afb8cdd034150d4d9f53926226ed27ad15b7f465e93d7468caaf5eafae0d37", size = 236083, upload-time = "2025-07-01T15:55:34.933Z" }, - { url = "https://files.pythonhosted.org/packages/47/af/9c4638994dd623d51c39892edd9d08e8be8220a4b7e874fa02c2d6e91955/rpds_py-0.26.0-cp314-cp314-win_arm64.whl", hash = "sha256:ca3f059f4ba485d90c8dc75cb5ca897e15325e4e609812ce57f896607c1c0867", size = 223291, upload-time = "2025-07-01T15:55:36.202Z" }, - { url = "https://files.pythonhosted.org/packages/4d/db/669a241144460474aab03e254326b32c42def83eb23458a10d163cb9b5ce/rpds_py-0.26.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:5afea17ab3a126006dc2f293b14ffc7ef3c85336cf451564a0515ed7648033da", size = 361445, upload-time = "2025-07-01T15:55:37.483Z" }, - { url = "https://files.pythonhosted.org/packages/3b/2d/133f61cc5807c6c2fd086a46df0eb8f63a23f5df8306ff9f6d0fd168fecc/rpds_py-0.26.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:69f0c0a3df7fd3a7eec50a00396104bb9a843ea6d45fcc31c2d5243446ffd7a7", size = 347206, upload-time = "2025-07-01T15:55:38.828Z" }, - { url = "https://files.pythonhosted.org/packages/05/bf/0e8fb4c05f70273469eecf82f6ccf37248558526a45321644826555db31b/rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:801a71f70f9813e82d2513c9a96532551fce1e278ec0c64610992c49c04c2dad", size = 380330, upload-time = "2025-07-01T15:55:40.175Z" }, - { url = "https://files.pythonhosted.org/packages/d4/a8/060d24185d8b24d3923322f8d0ede16df4ade226a74e747b8c7c978e3dd3/rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:df52098cde6d5e02fa75c1f6244f07971773adb4a26625edd5c18fee906fa84d", size = 392254, upload-time = "2025-07-01T15:55:42.015Z" }, - { url = "https://files.pythonhosted.org/packages/b9/7b/7c2e8a9ee3e6bc0bae26bf29f5219955ca2fbb761dca996a83f5d2f773fe/rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bc596b30f86dc6f0929499c9e574601679d0341a0108c25b9b358a042f51bca", size = 516094, upload-time = "2025-07-01T15:55:43.603Z" }, - { url = "https://files.pythonhosted.org/packages/75/d6/f61cafbed8ba1499b9af9f1777a2a199cd888f74a96133d8833ce5eaa9c5/rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9dfbe56b299cf5875b68eb6f0ebaadc9cac520a1989cac0db0765abfb3709c19", size = 402889, upload-time = "2025-07-01T15:55:45.275Z" }, - { url = "https://files.pythonhosted.org/packages/92/19/c8ac0a8a8df2dd30cdec27f69298a5c13e9029500d6d76718130f5e5be10/rpds_py-0.26.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac64f4b2bdb4ea622175c9ab7cf09444e412e22c0e02e906978b3b488af5fde8", size = 384301, upload-time = "2025-07-01T15:55:47.098Z" }, - { url = "https://files.pythonhosted.org/packages/41/e1/6b1859898bc292a9ce5776016c7312b672da00e25cec74d7beced1027286/rpds_py-0.26.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:181ef9b6bbf9845a264f9aa45c31836e9f3c1f13be565d0d010e964c661d1e2b", size = 412891, upload-time = "2025-07-01T15:55:48.412Z" }, - { url = "https://files.pythonhosted.org/packages/ef/b9/ceb39af29913c07966a61367b3c08b4f71fad841e32c6b59a129d5974698/rpds_py-0.26.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:49028aa684c144ea502a8e847d23aed5e4c2ef7cadfa7d5eaafcb40864844b7a", size = 557044, upload-time = "2025-07-01T15:55:49.816Z" }, - { url = "https://files.pythonhosted.org/packages/2f/27/35637b98380731a521f8ec4f3fd94e477964f04f6b2f8f7af8a2d889a4af/rpds_py-0.26.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:e5d524d68a474a9688336045bbf76cb0def88549c1b2ad9dbfec1fb7cfbe9170", size = 585774, upload-time = "2025-07-01T15:55:51.192Z" }, - { url = "https://files.pythonhosted.org/packages/52/d9/3f0f105420fecd18551b678c9a6ce60bd23986098b252a56d35781b3e7e9/rpds_py-0.26.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c1851f429b822831bd2edcbe0cfd12ee9ea77868f8d3daf267b189371671c80e", size = 554886, upload-time = "2025-07-01T15:55:52.541Z" }, - { url = "https://files.pythonhosted.org/packages/6b/c5/347c056a90dc8dd9bc240a08c527315008e1b5042e7a4cf4ac027be9d38a/rpds_py-0.26.0-cp314-cp314t-win32.whl", hash = "sha256:7bdb17009696214c3b66bb3590c6d62e14ac5935e53e929bcdbc5a495987a84f", size = 219027, upload-time = "2025-07-01T15:55:53.874Z" }, - { url = "https://files.pythonhosted.org/packages/75/04/5302cea1aa26d886d34cadbf2dc77d90d7737e576c0065f357b96dc7a1a6/rpds_py-0.26.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f14440b9573a6f76b4ee4770c13f0b5921f71dde3b6fcb8dabbefd13b7fe05d7", size = 232821, upload-time = "2025-07-01T15:55:55.167Z" }, - { url = "https://files.pythonhosted.org/packages/51/f2/b5c85b758a00c513bb0389f8fc8e61eb5423050c91c958cdd21843faa3e6/rpds_py-0.26.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f61a9326f80ca59214d1cceb0a09bb2ece5b2563d4e0cd37bfd5515c28510674", size = 373505, upload-time = "2025-07-01T15:56:34.716Z" }, - { url = "https://files.pythonhosted.org/packages/23/e0/25db45e391251118e915e541995bb5f5ac5691a3b98fb233020ba53afc9b/rpds_py-0.26.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:183f857a53bcf4b1b42ef0f57ca553ab56bdd170e49d8091e96c51c3d69ca696", size = 359468, upload-time = "2025-07-01T15:56:36.219Z" }, - { url = "https://files.pythonhosted.org/packages/0b/73/dd5ee6075bb6491be3a646b301dfd814f9486d924137a5098e61f0487e16/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:941c1cfdf4799d623cf3aa1d326a6b4fdb7a5799ee2687f3516738216d2262fb", size = 382680, upload-time = "2025-07-01T15:56:37.644Z" }, - { url = "https://files.pythonhosted.org/packages/2f/10/84b522ff58763a5c443f5bcedc1820240e454ce4e620e88520f04589e2ea/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72a8d9564a717ee291f554eeb4bfeafe2309d5ec0aa6c475170bdab0f9ee8e88", size = 397035, upload-time = "2025-07-01T15:56:39.241Z" }, - { url = "https://files.pythonhosted.org/packages/06/ea/8667604229a10a520fcbf78b30ccc278977dcc0627beb7ea2c96b3becef0/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:511d15193cbe013619dd05414c35a7dedf2088fcee93c6bbb7c77859765bd4e8", size = 514922, upload-time = "2025-07-01T15:56:40.645Z" }, - { url = "https://files.pythonhosted.org/packages/24/e6/9ed5b625c0661c4882fc8cdf302bf8e96c73c40de99c31e0b95ed37d508c/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aea1f9741b603a8d8fedb0ed5502c2bc0accbc51f43e2ad1337fe7259c2b77a5", size = 402822, upload-time = "2025-07-01T15:56:42.137Z" }, - { url = "https://files.pythonhosted.org/packages/8a/58/212c7b6fd51946047fb45d3733da27e2fa8f7384a13457c874186af691b1/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4019a9d473c708cf2f16415688ef0b4639e07abaa569d72f74745bbeffafa2c7", size = 384336, upload-time = "2025-07-01T15:56:44.239Z" }, - { url = "https://files.pythonhosted.org/packages/aa/f5/a40ba78748ae8ebf4934d4b88e77b98497378bc2c24ba55ebe87a4e87057/rpds_py-0.26.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:093d63b4b0f52d98ebae33b8c50900d3d67e0666094b1be7a12fffd7f65de74b", size = 416871, upload-time = "2025-07-01T15:56:46.284Z" }, - { url = "https://files.pythonhosted.org/packages/d5/a6/33b1fc0c9f7dcfcfc4a4353daa6308b3ece22496ceece348b3e7a7559a09/rpds_py-0.26.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:2abe21d8ba64cded53a2a677e149ceb76dcf44284202d737178afe7ba540c1eb", size = 559439, upload-time = "2025-07-01T15:56:48.549Z" }, - { url = "https://files.pythonhosted.org/packages/71/2d/ceb3f9c12f8cfa56d34995097f6cd99da1325642c60d1b6680dd9df03ed8/rpds_py-0.26.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:4feb7511c29f8442cbbc28149a92093d32e815a28aa2c50d333826ad2a20fdf0", size = 588380, upload-time = "2025-07-01T15:56:50.086Z" }, - { url = "https://files.pythonhosted.org/packages/c8/ed/9de62c2150ca8e2e5858acf3f4f4d0d180a38feef9fdab4078bea63d8dba/rpds_py-0.26.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:e99685fc95d386da368013e7fb4269dd39c30d99f812a8372d62f244f662709c", size = 555334, upload-time = "2025-07-01T15:56:51.703Z" }, +version = "0.27.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1e/d9/991a0dee12d9fc53ed027e26a26a64b151d77252ac477e22666b9688bc16/rpds_py-0.27.0.tar.gz", hash = "sha256:8b23cf252f180cda89220b378d917180f29d313cd6a07b2431c0d3b776aae86f", size = 27420, upload-time = "2025-08-07T08:26:39.624Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/c1/49d515434c1752e40f5e35b985260cf27af052593378580a2f139a5be6b8/rpds_py-0.27.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:dbc2ab5d10544eb485baa76c63c501303b716a5c405ff2469a1d8ceffaabf622", size = 371577, upload-time = "2025-08-07T08:23:25.379Z" }, + { url = "https://files.pythonhosted.org/packages/e1/6d/bf2715b2fee5087fa13b752b5fd573f1a93e4134c74d275f709e38e54fe7/rpds_py-0.27.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7ec85994f96a58cf7ed288caa344b7fe31fd1d503bdf13d7331ead5f70ab60d5", size = 354959, upload-time = "2025-08-07T08:23:26.767Z" }, + { url = "https://files.pythonhosted.org/packages/a3/5c/e7762808c746dd19733a81373c10da43926f6a6adcf4920a21119697a60a/rpds_py-0.27.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:190d7285cd3bb6d31d37a0534d7359c1ee191eb194c511c301f32a4afa5a1dd4", size = 381485, upload-time = "2025-08-07T08:23:27.869Z" }, + { url = "https://files.pythonhosted.org/packages/40/51/0d308eb0b558309ca0598bcba4243f52c4cd20e15fe991b5bd75824f2e61/rpds_py-0.27.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c10d92fb6d7fd827e44055fcd932ad93dac6a11e832d51534d77b97d1d85400f", size = 396816, upload-time = "2025-08-07T08:23:29.424Z" }, + { url = "https://files.pythonhosted.org/packages/5c/aa/2d585ec911d78f66458b2c91252134ca0c7c70f687a72c87283173dc0c96/rpds_py-0.27.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dd2c1d27ebfe6a015cfa2005b7fe8c52d5019f7bbdd801bc6f7499aab9ae739e", size = 514950, upload-time = "2025-08-07T08:23:30.576Z" }, + { url = "https://files.pythonhosted.org/packages/0b/ef/aced551cc1148179557aed84343073adadf252c91265263ee6203458a186/rpds_py-0.27.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4790c9d5dd565ddb3e9f656092f57268951398cef52e364c405ed3112dc7c7c1", size = 402132, upload-time = "2025-08-07T08:23:32.428Z" }, + { url = "https://files.pythonhosted.org/packages/4b/ac/cf644803d8d417653fe2b3604186861d62ea6afaef1b2284045741baef17/rpds_py-0.27.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4300e15e7d03660f04be84a125d1bdd0e6b2f674bc0723bc0fd0122f1a4585dc", size = 383660, upload-time = "2025-08-07T08:23:33.829Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ec/caf47c55ce02b76cbaeeb2d3b36a73da9ca2e14324e3d75cf72b59dcdac5/rpds_py-0.27.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:59195dc244fc183209cf8a93406889cadde47dfd2f0a6b137783aa9c56d67c85", size = 401730, upload-time = "2025-08-07T08:23:34.97Z" }, + { url = "https://files.pythonhosted.org/packages/0b/71/c1f355afdcd5b99ffc253422aa4bdcb04ccf1491dcd1bda3688a0c07fd61/rpds_py-0.27.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fae4a01ef8c4cb2bbe92ef2063149596907dc4a881a8d26743b3f6b304713171", size = 416122, upload-time = "2025-08-07T08:23:36.062Z" }, + { url = "https://files.pythonhosted.org/packages/38/0f/f4b5b1eda724ed0e04d2b26d8911cdc131451a7ee4c4c020a1387e5c6ded/rpds_py-0.27.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e3dc8d4ede2dbae6c0fc2b6c958bf51ce9fd7e9b40c0f5b8835c3fde44f5807d", size = 558771, upload-time = "2025-08-07T08:23:37.478Z" }, + { url = "https://files.pythonhosted.org/packages/93/c0/5f8b834db2289ab48d5cffbecbb75e35410103a77ac0b8da36bf9544ec1c/rpds_py-0.27.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c3782fb753aa825b4ccabc04292e07897e2fd941448eabf666856c5530277626", size = 587876, upload-time = "2025-08-07T08:23:38.662Z" }, + { url = "https://files.pythonhosted.org/packages/d2/dd/1a1df02ab8eb970115cff2ae31a6f73916609b900dc86961dc382b8c2e5e/rpds_py-0.27.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:887ab1f12b0d227e9260558a4a2320024b20102207ada65c43e1ffc4546df72e", size = 554359, upload-time = "2025-08-07T08:23:39.897Z" }, + { url = "https://files.pythonhosted.org/packages/a1/e4/95a014ab0d51ab6e3bebbdb476a42d992d2bbf9c489d24cff9fda998e925/rpds_py-0.27.0-cp311-cp311-win32.whl", hash = "sha256:5d6790ff400254137b81b8053b34417e2c46921e302d655181d55ea46df58cf7", size = 218084, upload-time = "2025-08-07T08:23:41.086Z" }, + { url = "https://files.pythonhosted.org/packages/49/78/f8d5b71ec65a0376b0de31efcbb5528ce17a9b7fdd19c3763303ccfdedec/rpds_py-0.27.0-cp311-cp311-win_amd64.whl", hash = "sha256:e24d8031a2c62f34853756d9208eeafa6b940a1efcbfe36e8f57d99d52bb7261", size = 230085, upload-time = "2025-08-07T08:23:42.143Z" }, + { url = "https://files.pythonhosted.org/packages/e7/d3/84429745184091e06b4cc70f8597408e314c2d2f7f5e13249af9ffab9e3d/rpds_py-0.27.0-cp311-cp311-win_arm64.whl", hash = "sha256:08680820d23df1df0a0260f714d12966bc6c42d02e8055a91d61e03f0c47dda0", size = 222112, upload-time = "2025-08-07T08:23:43.233Z" }, + { url = "https://files.pythonhosted.org/packages/cd/17/e67309ca1ac993fa1888a0d9b2f5ccc1f67196ace32e76c9f8e1dbbbd50c/rpds_py-0.27.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:19c990fdf5acecbf0623e906ae2e09ce1c58947197f9bced6bbd7482662231c4", size = 362611, upload-time = "2025-08-07T08:23:44.773Z" }, + { url = "https://files.pythonhosted.org/packages/93/2e/28c2fb84aa7aa5d75933d1862d0f7de6198ea22dfd9a0cca06e8a4e7509e/rpds_py-0.27.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6c27a7054b5224710fcfb1a626ec3ff4f28bcb89b899148c72873b18210e446b", size = 347680, upload-time = "2025-08-07T08:23:46.014Z" }, + { url = "https://files.pythonhosted.org/packages/44/3e/9834b4c8f4f5fe936b479e623832468aa4bd6beb8d014fecaee9eac6cdb1/rpds_py-0.27.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09965b314091829b378b60607022048953e25f0b396c2b70e7c4c81bcecf932e", size = 384600, upload-time = "2025-08-07T08:23:48Z" }, + { url = "https://files.pythonhosted.org/packages/19/78/744123c7b38865a965cd9e6f691fde7ef989a00a256fa8bf15b75240d12f/rpds_py-0.27.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:14f028eb47f59e9169bfdf9f7ceafd29dd64902141840633683d0bad5b04ff34", size = 400697, upload-time = "2025-08-07T08:23:49.407Z" }, + { url = "https://files.pythonhosted.org/packages/32/97/3c3d32fe7daee0a1f1a678b6d4dfb8c4dcf88197fa2441f9da7cb54a8466/rpds_py-0.27.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6168af0be75bba990a39f9431cdfae5f0ad501f4af32ae62e8856307200517b8", size = 517781, upload-time = "2025-08-07T08:23:50.557Z" }, + { url = "https://files.pythonhosted.org/packages/b2/be/28f0e3e733680aa13ecec1212fc0f585928a206292f14f89c0b8a684cad1/rpds_py-0.27.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ab47fe727c13c09d0e6f508e3a49e545008e23bf762a245b020391b621f5b726", size = 406449, upload-time = "2025-08-07T08:23:51.732Z" }, + { url = "https://files.pythonhosted.org/packages/95/ae/5d15c83e337c082d0367053baeb40bfba683f42459f6ebff63a2fd7e5518/rpds_py-0.27.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fa01b3d5e3b7d97efab65bd3d88f164e289ec323a8c033c5c38e53ee25c007e", size = 386150, upload-time = "2025-08-07T08:23:52.822Z" }, + { url = "https://files.pythonhosted.org/packages/bf/65/944e95f95d5931112829e040912b25a77b2e7ed913ea5fe5746aa5c1ce75/rpds_py-0.27.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:6c135708e987f46053e0a1246a206f53717f9fadfba27174a9769ad4befba5c3", size = 406100, upload-time = "2025-08-07T08:23:54.339Z" }, + { url = "https://files.pythonhosted.org/packages/21/a4/1664b83fae02894533cd11dc0b9f91d673797c2185b7be0f7496107ed6c5/rpds_py-0.27.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc327f4497b7087d06204235199daf208fd01c82d80465dc5efa4ec9df1c5b4e", size = 421345, upload-time = "2025-08-07T08:23:55.832Z" }, + { url = "https://files.pythonhosted.org/packages/7c/26/b7303941c2b0823bfb34c71378249f8beedce57301f400acb04bb345d025/rpds_py-0.27.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7e57906e38583a2cba67046a09c2637e23297618dc1f3caddbc493f2be97c93f", size = 561891, upload-time = "2025-08-07T08:23:56.951Z" }, + { url = "https://files.pythonhosted.org/packages/9b/c8/48623d64d4a5a028fa99576c768a6159db49ab907230edddc0b8468b998b/rpds_py-0.27.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f4f69d7a4300fbf91efb1fb4916421bd57804c01ab938ab50ac9c4aa2212f03", size = 591756, upload-time = "2025-08-07T08:23:58.146Z" }, + { url = "https://files.pythonhosted.org/packages/b3/51/18f62617e8e61cc66334c9fb44b1ad7baae3438662098efbc55fb3fda453/rpds_py-0.27.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b4c4fbbcff474e1e5f38be1bf04511c03d492d42eec0babda5d03af3b5589374", size = 557088, upload-time = "2025-08-07T08:23:59.6Z" }, + { url = "https://files.pythonhosted.org/packages/bd/4c/e84c3a276e2496a93d245516be6b49e20499aa8ca1c94d59fada0d79addc/rpds_py-0.27.0-cp312-cp312-win32.whl", hash = "sha256:27bac29bbbf39601b2aab474daf99dbc8e7176ca3389237a23944b17f8913d97", size = 221926, upload-time = "2025-08-07T08:24:00.695Z" }, + { url = "https://files.pythonhosted.org/packages/83/89/9d0fbcef64340db0605eb0a0044f258076f3ae0a3b108983b2c614d96212/rpds_py-0.27.0-cp312-cp312-win_amd64.whl", hash = "sha256:8a06aa1197ec0281eb1d7daf6073e199eb832fe591ffa329b88bae28f25f5fe5", size = 233235, upload-time = "2025-08-07T08:24:01.846Z" }, + { url = "https://files.pythonhosted.org/packages/c9/b0/e177aa9f39cbab060f96de4a09df77d494f0279604dc2f509263e21b05f9/rpds_py-0.27.0-cp312-cp312-win_arm64.whl", hash = "sha256:e14aab02258cb776a108107bd15f5b5e4a1bbaa61ef33b36693dfab6f89d54f9", size = 223315, upload-time = "2025-08-07T08:24:03.337Z" }, + { url = "https://files.pythonhosted.org/packages/81/d2/dfdfd42565a923b9e5a29f93501664f5b984a802967d48d49200ad71be36/rpds_py-0.27.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:443d239d02d9ae55b74015234f2cd8eb09e59fbba30bf60baeb3123ad4c6d5ff", size = 362133, upload-time = "2025-08-07T08:24:04.508Z" }, + { url = "https://files.pythonhosted.org/packages/ac/4a/0a2e2460c4b66021d349ce9f6331df1d6c75d7eea90df9785d333a49df04/rpds_py-0.27.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b8a7acf04fda1f30f1007f3cc96d29d8cf0a53e626e4e1655fdf4eabc082d367", size = 347128, upload-time = "2025-08-07T08:24:05.695Z" }, + { url = "https://files.pythonhosted.org/packages/35/8d/7d1e4390dfe09d4213b3175a3f5a817514355cb3524593380733204f20b9/rpds_py-0.27.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d0f92b78cfc3b74a42239fdd8c1266f4715b573204c234d2f9fc3fc7a24f185", size = 384027, upload-time = "2025-08-07T08:24:06.841Z" }, + { url = "https://files.pythonhosted.org/packages/c1/65/78499d1a62172891c8cd45de737b2a4b84a414b6ad8315ab3ac4945a5b61/rpds_py-0.27.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ce4ed8e0c7dbc5b19352b9c2c6131dd23b95fa8698b5cdd076307a33626b72dc", size = 399973, upload-time = "2025-08-07T08:24:08.143Z" }, + { url = "https://files.pythonhosted.org/packages/10/a1/1c67c1d8cc889107b19570bb01f75cf49852068e95e6aee80d22915406fc/rpds_py-0.27.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fde355b02934cc6b07200cc3b27ab0c15870a757d1a72fd401aa92e2ea3c6bfe", size = 515295, upload-time = "2025-08-07T08:24:09.711Z" }, + { url = "https://files.pythonhosted.org/packages/df/27/700ec88e748436b6c7c4a2262d66e80f8c21ab585d5e98c45e02f13f21c0/rpds_py-0.27.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13bbc4846ae4c993f07c93feb21a24d8ec637573d567a924b1001e81c8ae80f9", size = 406737, upload-time = "2025-08-07T08:24:11.182Z" }, + { url = "https://files.pythonhosted.org/packages/33/cc/6b0ee8f0ba3f2df2daac1beda17fde5cf10897a7d466f252bd184ef20162/rpds_py-0.27.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be0744661afbc4099fef7f4e604e7f1ea1be1dd7284f357924af12a705cc7d5c", size = 385898, upload-time = "2025-08-07T08:24:12.798Z" }, + { url = "https://files.pythonhosted.org/packages/e8/7e/c927b37d7d33c0a0ebf249cc268dc2fcec52864c1b6309ecb960497f2285/rpds_py-0.27.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:069e0384a54f427bd65d7fda83b68a90606a3835901aaff42185fcd94f5a9295", size = 405785, upload-time = "2025-08-07T08:24:14.906Z" }, + { url = "https://files.pythonhosted.org/packages/5b/d2/8ed50746d909dcf402af3fa58b83d5a590ed43e07251d6b08fad1a535ba6/rpds_py-0.27.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4bc262ace5a1a7dc3e2eac2fa97b8257ae795389f688b5adf22c5db1e2431c43", size = 419760, upload-time = "2025-08-07T08:24:16.129Z" }, + { url = "https://files.pythonhosted.org/packages/d3/60/2b2071aee781cb3bd49f94d5d35686990b925e9b9f3e3d149235a6f5d5c1/rpds_py-0.27.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2fe6e18e5c8581f0361b35ae575043c7029d0a92cb3429e6e596c2cdde251432", size = 561201, upload-time = "2025-08-07T08:24:17.645Z" }, + { url = "https://files.pythonhosted.org/packages/98/1f/27b67304272521aaea02be293fecedce13fa351a4e41cdb9290576fc6d81/rpds_py-0.27.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d93ebdb82363d2e7bec64eecdc3632b59e84bd270d74fe5be1659f7787052f9b", size = 591021, upload-time = "2025-08-07T08:24:18.999Z" }, + { url = "https://files.pythonhosted.org/packages/db/9b/a2fadf823164dd085b1f894be6443b0762a54a7af6f36e98e8fcda69ee50/rpds_py-0.27.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0954e3a92e1d62e83a54ea7b3fdc9efa5d61acef8488a8a3d31fdafbfb00460d", size = 556368, upload-time = "2025-08-07T08:24:20.54Z" }, + { url = "https://files.pythonhosted.org/packages/24/f3/6d135d46a129cda2e3e6d4c5e91e2cc26ea0428c6cf152763f3f10b6dd05/rpds_py-0.27.0-cp313-cp313-win32.whl", hash = "sha256:2cff9bdd6c7b906cc562a505c04a57d92e82d37200027e8d362518df427f96cd", size = 221236, upload-time = "2025-08-07T08:24:22.144Z" }, + { url = "https://files.pythonhosted.org/packages/c5/44/65d7494f5448ecc755b545d78b188440f81da98b50ea0447ab5ebfdf9bd6/rpds_py-0.27.0-cp313-cp313-win_amd64.whl", hash = "sha256:dc79d192fb76fc0c84f2c58672c17bbbc383fd26c3cdc29daae16ce3d927e8b2", size = 232634, upload-time = "2025-08-07T08:24:23.642Z" }, + { url = "https://files.pythonhosted.org/packages/70/d9/23852410fadab2abb611733933401de42a1964ce6600a3badae35fbd573e/rpds_py-0.27.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b3a5c8089eed498a3af23ce87a80805ff98f6ef8f7bdb70bd1b7dae5105f6ac", size = 222783, upload-time = "2025-08-07T08:24:25.098Z" }, + { url = "https://files.pythonhosted.org/packages/15/75/03447917f78512b34463f4ef11066516067099a0c466545655503bed0c77/rpds_py-0.27.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:90fb790138c1a89a2e58c9282fe1089638401f2f3b8dddd758499041bc6e0774", size = 359154, upload-time = "2025-08-07T08:24:26.249Z" }, + { url = "https://files.pythonhosted.org/packages/6b/fc/4dac4fa756451f2122ddaf136e2c6aeb758dc6fdbe9ccc4bc95c98451d50/rpds_py-0.27.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:010c4843a3b92b54373e3d2291a7447d6c3fc29f591772cc2ea0e9f5c1da434b", size = 343909, upload-time = "2025-08-07T08:24:27.405Z" }, + { url = "https://files.pythonhosted.org/packages/7b/81/723c1ed8e6f57ed9d8c0c07578747a2d3d554aaefc1ab89f4e42cfeefa07/rpds_py-0.27.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9ce7a9e967afc0a2af7caa0d15a3e9c1054815f73d6a8cb9225b61921b419bd", size = 379340, upload-time = "2025-08-07T08:24:28.714Z" }, + { url = "https://files.pythonhosted.org/packages/98/16/7e3740413de71818ce1997df82ba5f94bae9fff90c0a578c0e24658e6201/rpds_py-0.27.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aa0bf113d15e8abdfee92aa4db86761b709a09954083afcb5bf0f952d6065fdb", size = 391655, upload-time = "2025-08-07T08:24:30.223Z" }, + { url = "https://files.pythonhosted.org/packages/e0/63/2a9f510e124d80660f60ecce07953f3f2d5f0b96192c1365443859b9c87f/rpds_py-0.27.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb91d252b35004a84670dfeafadb042528b19842a0080d8b53e5ec1128e8f433", size = 513017, upload-time = "2025-08-07T08:24:31.446Z" }, + { url = "https://files.pythonhosted.org/packages/2c/4e/cf6ff311d09776c53ea1b4f2e6700b9d43bb4e99551006817ade4bbd6f78/rpds_py-0.27.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:db8a6313dbac934193fc17fe7610f70cd8181c542a91382531bef5ed785e5615", size = 402058, upload-time = "2025-08-07T08:24:32.613Z" }, + { url = "https://files.pythonhosted.org/packages/88/11/5e36096d474cb10f2a2d68b22af60a3bc4164fd8db15078769a568d9d3ac/rpds_py-0.27.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce96ab0bdfcef1b8c371ada2100767ace6804ea35aacce0aef3aeb4f3f499ca8", size = 383474, upload-time = "2025-08-07T08:24:33.767Z" }, + { url = "https://files.pythonhosted.org/packages/db/a2/3dff02805b06058760b5eaa6d8cb8db3eb3e46c9e452453ad5fc5b5ad9fe/rpds_py-0.27.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:7451ede3560086abe1aa27dcdcf55cd15c96b56f543fb12e5826eee6f721f858", size = 400067, upload-time = "2025-08-07T08:24:35.021Z" }, + { url = "https://files.pythonhosted.org/packages/67/87/eed7369b0b265518e21ea836456a4ed4a6744c8c12422ce05bce760bb3cf/rpds_py-0.27.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:32196b5a99821476537b3f7732432d64d93a58d680a52c5e12a190ee0135d8b5", size = 412085, upload-time = "2025-08-07T08:24:36.267Z" }, + { url = "https://files.pythonhosted.org/packages/8b/48/f50b2ab2fbb422fbb389fe296e70b7a6b5ea31b263ada5c61377e710a924/rpds_py-0.27.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a029be818059870664157194e46ce0e995082ac49926f1423c1f058534d2aaa9", size = 555928, upload-time = "2025-08-07T08:24:37.573Z" }, + { url = "https://files.pythonhosted.org/packages/98/41/b18eb51045d06887666c3560cd4bbb6819127b43d758f5adb82b5f56f7d1/rpds_py-0.27.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3841f66c1ffdc6cebce8aed64e36db71466f1dc23c0d9a5592e2a782a3042c79", size = 585527, upload-time = "2025-08-07T08:24:39.391Z" }, + { url = "https://files.pythonhosted.org/packages/be/03/a3dd6470fc76499959b00ae56295b76b4bdf7c6ffc60d62006b1217567e1/rpds_py-0.27.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:42894616da0fc0dcb2ec08a77896c3f56e9cb2f4b66acd76fc8992c3557ceb1c", size = 554211, upload-time = "2025-08-07T08:24:40.6Z" }, + { url = "https://files.pythonhosted.org/packages/bf/d1/ee5fd1be395a07423ac4ca0bcc05280bf95db2b155d03adefeb47d5ebf7e/rpds_py-0.27.0-cp313-cp313t-win32.whl", hash = "sha256:b1fef1f13c842a39a03409e30ca0bf87b39a1e2a305a9924deadb75a43105d23", size = 216624, upload-time = "2025-08-07T08:24:42.204Z" }, + { url = "https://files.pythonhosted.org/packages/1c/94/4814c4c858833bf46706f87349c37ca45e154da7dbbec9ff09f1abeb08cc/rpds_py-0.27.0-cp313-cp313t-win_amd64.whl", hash = "sha256:183f5e221ba3e283cd36fdfbe311d95cd87699a083330b4f792543987167eff1", size = 230007, upload-time = "2025-08-07T08:24:43.329Z" }, + { url = "https://files.pythonhosted.org/packages/0e/a5/8fffe1c7dc7c055aa02df310f9fb71cfc693a4d5ccc5de2d3456ea5fb022/rpds_py-0.27.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:f3cd110e02c5bf17d8fb562f6c9df5c20e73029d587cf8602a2da6c5ef1e32cb", size = 362595, upload-time = "2025-08-07T08:24:44.478Z" }, + { url = "https://files.pythonhosted.org/packages/bc/c7/4e4253fd2d4bb0edbc0b0b10d9f280612ca4f0f990e3c04c599000fe7d71/rpds_py-0.27.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8d0e09cf4863c74106b5265c2c310f36146e2b445ff7b3018a56799f28f39f6f", size = 347252, upload-time = "2025-08-07T08:24:45.678Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c8/3d1a954d30f0174dd6baf18b57c215da03cf7846a9d6e0143304e784cddc/rpds_py-0.27.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64f689ab822f9b5eb6dfc69893b4b9366db1d2420f7db1f6a2adf2a9ca15ad64", size = 384886, upload-time = "2025-08-07T08:24:46.86Z" }, + { url = "https://files.pythonhosted.org/packages/e0/52/3c5835f2df389832b28f9276dd5395b5a965cea34226e7c88c8fbec2093c/rpds_py-0.27.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e36c80c49853b3ffda7aa1831bf175c13356b210c73128c861f3aa93c3cc4015", size = 399716, upload-time = "2025-08-07T08:24:48.174Z" }, + { url = "https://files.pythonhosted.org/packages/40/73/176e46992461a1749686a2a441e24df51ff86b99c2d34bf39f2a5273b987/rpds_py-0.27.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6de6a7f622860af0146cb9ee148682ff4d0cea0b8fd3ad51ce4d40efb2f061d0", size = 517030, upload-time = "2025-08-07T08:24:49.52Z" }, + { url = "https://files.pythonhosted.org/packages/79/2a/7266c75840e8c6e70effeb0d38922a45720904f2cd695e68a0150e5407e2/rpds_py-0.27.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4045e2fc4b37ec4b48e8907a5819bdd3380708c139d7cc358f03a3653abedb89", size = 408448, upload-time = "2025-08-07T08:24:50.727Z" }, + { url = "https://files.pythonhosted.org/packages/e6/5f/a7efc572b8e235093dc6cf39f4dbc8a7f08e65fdbcec7ff4daeb3585eef1/rpds_py-0.27.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9da162b718b12c4219eeeeb68a5b7552fbc7aadedf2efee440f88b9c0e54b45d", size = 387320, upload-time = "2025-08-07T08:24:52.004Z" }, + { url = "https://files.pythonhosted.org/packages/a2/eb/9ff6bc92efe57cf5a2cb74dee20453ba444b6fdc85275d8c99e0d27239d1/rpds_py-0.27.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:0665be515767dc727ffa5f74bd2ef60b0ff85dad6bb8f50d91eaa6b5fb226f51", size = 407414, upload-time = "2025-08-07T08:24:53.664Z" }, + { url = "https://files.pythonhosted.org/packages/fb/bd/3b9b19b00d5c6e1bd0f418c229ab0f8d3b110ddf7ec5d9d689ef783d0268/rpds_py-0.27.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:203f581accef67300a942e49a37d74c12ceeef4514874c7cede21b012613ca2c", size = 420766, upload-time = "2025-08-07T08:24:55.917Z" }, + { url = "https://files.pythonhosted.org/packages/17/6b/521a7b1079ce16258c70805166e3ac6ec4ee2139d023fe07954dc9b2d568/rpds_py-0.27.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7873b65686a6471c0037139aa000d23fe94628e0daaa27b6e40607c90e3f5ec4", size = 562409, upload-time = "2025-08-07T08:24:57.17Z" }, + { url = "https://files.pythonhosted.org/packages/8b/bf/65db5bfb14ccc55e39de8419a659d05a2a9cd232f0a699a516bb0991da7b/rpds_py-0.27.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:249ab91ceaa6b41abc5f19513cb95b45c6f956f6b89f1fe3d99c81255a849f9e", size = 590793, upload-time = "2025-08-07T08:24:58.388Z" }, + { url = "https://files.pythonhosted.org/packages/db/b8/82d368b378325191ba7aae8f40f009b78057b598d4394d1f2cdabaf67b3f/rpds_py-0.27.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d2f184336bc1d6abfaaa1262ed42739c3789b1e3a65a29916a615307d22ffd2e", size = 558178, upload-time = "2025-08-07T08:24:59.756Z" }, + { url = "https://files.pythonhosted.org/packages/f6/ff/f270bddbfbc3812500f8131b1ebbd97afd014cd554b604a3f73f03133a36/rpds_py-0.27.0-cp314-cp314-win32.whl", hash = "sha256:d3c622c39f04d5751408f5b801ecb527e6e0a471b367f420a877f7a660d583f6", size = 222355, upload-time = "2025-08-07T08:25:01.027Z" }, + { url = "https://files.pythonhosted.org/packages/bf/20/fdab055b1460c02ed356a0e0b0a78c1dd32dc64e82a544f7b31c9ac643dc/rpds_py-0.27.0-cp314-cp314-win_amd64.whl", hash = "sha256:cf824aceaeffff029ccfba0da637d432ca71ab21f13e7f6f5179cd88ebc77a8a", size = 234007, upload-time = "2025-08-07T08:25:02.268Z" }, + { url = "https://files.pythonhosted.org/packages/4d/a8/694c060005421797a3be4943dab8347c76c2b429a9bef68fb2c87c9e70c7/rpds_py-0.27.0-cp314-cp314-win_arm64.whl", hash = "sha256:86aca1616922b40d8ac1b3073a1ead4255a2f13405e5700c01f7c8d29a03972d", size = 223527, upload-time = "2025-08-07T08:25:03.45Z" }, + { url = "https://files.pythonhosted.org/packages/1e/f9/77f4c90f79d2c5ca8ce6ec6a76cb4734ee247de6b3a4f337e289e1f00372/rpds_py-0.27.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:341d8acb6724c0c17bdf714319c393bb27f6d23d39bc74f94221b3e59fc31828", size = 359469, upload-time = "2025-08-07T08:25:04.648Z" }, + { url = "https://files.pythonhosted.org/packages/c0/22/b97878d2f1284286fef4172069e84b0b42b546ea7d053e5fb7adb9ac6494/rpds_py-0.27.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6b96b0b784fe5fd03beffff2b1533dc0d85e92bab8d1b2c24ef3a5dc8fac5669", size = 343960, upload-time = "2025-08-07T08:25:05.863Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b0/dfd55b5bb480eda0578ae94ef256d3061d20b19a0f5e18c482f03e65464f/rpds_py-0.27.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0c431bfb91478d7cbe368d0a699978050d3b112d7f1d440a41e90faa325557fd", size = 380201, upload-time = "2025-08-07T08:25:07.513Z" }, + { url = "https://files.pythonhosted.org/packages/28/22/e1fa64e50d58ad2b2053077e3ec81a979147c43428de9e6de68ddf6aff4e/rpds_py-0.27.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:20e222a44ae9f507d0f2678ee3dd0c45ec1e930f6875d99b8459631c24058aec", size = 392111, upload-time = "2025-08-07T08:25:09.149Z" }, + { url = "https://files.pythonhosted.org/packages/49/f9/43ab7a43e97aedf6cea6af70fdcbe18abbbc41d4ae6cdec1bfc23bbad403/rpds_py-0.27.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:184f0d7b342967f6cda94a07d0e1fae177d11d0b8f17d73e06e36ac02889f303", size = 515863, upload-time = "2025-08-07T08:25:10.431Z" }, + { url = "https://files.pythonhosted.org/packages/38/9b/9bd59dcc636cd04d86a2d20ad967770bf348f5eb5922a8f29b547c074243/rpds_py-0.27.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a00c91104c173c9043bc46f7b30ee5e6d2f6b1149f11f545580f5d6fdff42c0b", size = 402398, upload-time = "2025-08-07T08:25:11.819Z" }, + { url = "https://files.pythonhosted.org/packages/71/bf/f099328c6c85667aba6b66fa5c35a8882db06dcd462ea214be72813a0dd2/rpds_py-0.27.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7a37dd208f0d658e0487522078b1ed68cd6bce20ef4b5a915d2809b9094b410", size = 384665, upload-time = "2025-08-07T08:25:13.194Z" }, + { url = "https://files.pythonhosted.org/packages/a9/c5/9c1f03121ece6634818490bd3c8be2c82a70928a19de03467fb25a3ae2a8/rpds_py-0.27.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:92f3b3ec3e6008a1fe00b7c0946a170f161ac00645cde35e3c9a68c2475e8156", size = 400405, upload-time = "2025-08-07T08:25:14.417Z" }, + { url = "https://files.pythonhosted.org/packages/b5/b8/e25d54af3e63ac94f0c16d8fe143779fe71ff209445a0c00d0f6984b6b2c/rpds_py-0.27.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a1b3db5fae5cbce2131b7420a3f83553d4d89514c03d67804ced36161fe8b6b2", size = 413179, upload-time = "2025-08-07T08:25:15.664Z" }, + { url = "https://files.pythonhosted.org/packages/f9/d1/406b3316433fe49c3021546293a04bc33f1478e3ec7950215a7fce1a1208/rpds_py-0.27.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5355527adaa713ab693cbce7c1e0ec71682f599f61b128cf19d07e5c13c9b1f1", size = 556895, upload-time = "2025-08-07T08:25:17.061Z" }, + { url = "https://files.pythonhosted.org/packages/5f/bc/3697c0c21fcb9a54d46ae3b735eb2365eea0c2be076b8f770f98e07998de/rpds_py-0.27.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:fcc01c57ce6e70b728af02b2401c5bc853a9e14eb07deda30624374f0aebfe42", size = 585464, upload-time = "2025-08-07T08:25:18.406Z" }, + { url = "https://files.pythonhosted.org/packages/63/09/ee1bb5536f99f42c839b177d552f6114aa3142d82f49cef49261ed28dbe0/rpds_py-0.27.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3001013dae10f806380ba739d40dee11db1ecb91684febb8406a87c2ded23dae", size = 555090, upload-time = "2025-08-07T08:25:20.461Z" }, + { url = "https://files.pythonhosted.org/packages/7d/2c/363eada9e89f7059199d3724135a86c47082cbf72790d6ba2f336d146ddb/rpds_py-0.27.0-cp314-cp314t-win32.whl", hash = "sha256:0f401c369186a5743694dd9fc08cba66cf70908757552e1f714bfc5219c655b5", size = 218001, upload-time = "2025-08-07T08:25:21.761Z" }, + { url = "https://files.pythonhosted.org/packages/e2/3f/d6c216ed5199c9ef79e2a33955601f454ed1e7420a93b89670133bca5ace/rpds_py-0.27.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8a1dca5507fa1337f75dcd5070218b20bc68cf8844271c923c1b79dfcbc20391", size = 230993, upload-time = "2025-08-07T08:25:23.34Z" }, + { url = "https://files.pythonhosted.org/packages/59/64/72ab5b911fdcc48058359b0e786e5363e3fde885156116026f1a2ba9a5b5/rpds_py-0.27.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e6491658dd2569f05860bad645569145c8626ac231877b0fb2d5f9bcb7054089", size = 371658, upload-time = "2025-08-07T08:26:02.369Z" }, + { url = "https://files.pythonhosted.org/packages/6c/4b/90ff04b4da055db53d8fea57640d8d5d55456343a1ec9a866c0ecfe10fd1/rpds_py-0.27.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:bec77545d188f8bdd29d42bccb9191682a46fb2e655e3d1fb446d47c55ac3b8d", size = 355529, upload-time = "2025-08-07T08:26:03.83Z" }, + { url = "https://files.pythonhosted.org/packages/a4/be/527491fb1afcd86fc5ce5812eb37bc70428ee017d77fee20de18155c3937/rpds_py-0.27.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25a4aebf8ca02bbb90a9b3e7a463bbf3bee02ab1c446840ca07b1695a68ce424", size = 382822, upload-time = "2025-08-07T08:26:05.52Z" }, + { url = "https://files.pythonhosted.org/packages/e0/a5/dcdb8725ce11e6d0913e6fcf782a13f4b8a517e8acc70946031830b98441/rpds_py-0.27.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:44524b96481a4c9b8e6c46d6afe43fa1fb485c261e359fbe32b63ff60e3884d8", size = 397233, upload-time = "2025-08-07T08:26:07.179Z" }, + { url = "https://files.pythonhosted.org/packages/33/f9/0947920d1927e9f144660590cc38cadb0795d78fe0d9aae0ef71c1513b7c/rpds_py-0.27.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45d04a73c54b6a5fd2bab91a4b5bc8b426949586e61340e212a8484919183859", size = 514892, upload-time = "2025-08-07T08:26:08.622Z" }, + { url = "https://files.pythonhosted.org/packages/1d/ed/d1343398c1417c68f8daa1afce56ef6ce5cc587daaf98e29347b00a80ff2/rpds_py-0.27.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:343cf24de9ed6c728abefc5d5c851d5de06497caa7ac37e5e65dd572921ed1b5", size = 402733, upload-time = "2025-08-07T08:26:10.433Z" }, + { url = "https://files.pythonhosted.org/packages/1d/0b/646f55442cd14014fb64d143428f25667a100f82092c90087b9ea7101c74/rpds_py-0.27.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7aed8118ae20515974650d08eb724150dc2e20c2814bcc307089569995e88a14", size = 384447, upload-time = "2025-08-07T08:26:11.847Z" }, + { url = "https://files.pythonhosted.org/packages/4b/15/0596ef7529828e33a6c81ecf5013d1dd33a511a3e0be0561f83079cda227/rpds_py-0.27.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:af9d4fd79ee1cc8e7caf693ee02737daabfc0fcf2773ca0a4735b356c8ad6f7c", size = 402502, upload-time = "2025-08-07T08:26:13.537Z" }, + { url = "https://files.pythonhosted.org/packages/c3/8d/986af3c42f8454a6cafff8729d99fb178ae9b08a9816325ac7a8fa57c0c0/rpds_py-0.27.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f0396e894bd1e66c74ecbc08b4f6a03dc331140942c4b1d345dd131b68574a60", size = 416651, upload-time = "2025-08-07T08:26:14.923Z" }, + { url = "https://files.pythonhosted.org/packages/e9/9a/b4ec3629b7b447e896eec574469159b5b60b7781d3711c914748bf32de05/rpds_py-0.27.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:59714ab0a5af25d723d8e9816638faf7f4254234decb7d212715c1aa71eee7be", size = 559460, upload-time = "2025-08-07T08:26:16.295Z" }, + { url = "https://files.pythonhosted.org/packages/61/63/d1e127b40c3e4733b3a6f26ae7a063cdf2bc1caa5272c89075425c7d397a/rpds_py-0.27.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:88051c3b7d5325409f433c5a40328fcb0685fc04e5db49ff936e910901d10114", size = 588072, upload-time = "2025-08-07T08:26:17.776Z" }, + { url = "https://files.pythonhosted.org/packages/04/7e/8ffc71a8f6833d9c9fb999f5b0ee736b8b159fd66968e05c7afc2dbcd57e/rpds_py-0.27.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:181bc29e59e5e5e6e9d63b143ff4d5191224d355e246b5a48c88ce6b35c4e466", size = 555083, upload-time = "2025-08-07T08:26:19.301Z" }, ] [[package]] @@ -3531,14 +3146,14 @@ wheels = [ [[package]] name = "s3transfer" -version = "0.13.0" +version = "0.13.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ed/5d/9dcc100abc6711e8247af5aa561fc07c4a046f72f659c3adea9a449e191a/s3transfer-0.13.0.tar.gz", hash = "sha256:f5e6db74eb7776a37208001113ea7aa97695368242b364d73e91c981ac522177", size = 150232, upload-time = "2025-05-22T19:24:50.245Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/05/d52bf1e65044b4e5e27d4e63e8d1579dbdec54fce685908ae09bc3720030/s3transfer-0.13.1.tar.gz", hash = "sha256:c3fdba22ba1bd367922f27ec8032d6a1cf5f10c934fb5d68cf60fd5a23d936cf", size = 150589, upload-time = "2025-07-18T19:22:42.31Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/17/22bf8155aa0ea2305eefa3a6402e040df7ebe512d1310165eda1e233c3f8/s3transfer-0.13.0-py3-none-any.whl", hash = "sha256:0148ef34d6dd964d0d8cf4311b2b21c474693e57c2e069ec708ce043d2b527be", size = 85152, upload-time = "2025-05-22T19:24:48.703Z" }, + { url = "https://files.pythonhosted.org/packages/6d/4f/d073e09df851cfa251ef7840007d04db3293a0482ce607d2b993926089be/s3transfer-0.13.1-py3-none-any.whl", hash = "sha256:a981aa7429be23fe6dfc13e80e4020057cbab622b08c0315288758d67cabc724", size = 85308, upload-time = "2025-07-18T19:22:40.947Z" }, ] [[package]] @@ -3593,18 +3208,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/f7/1f/b876b1f83aef204198a42dc101613fefccb32258e5428b5f9259677864b4/starlette-0.47.2-py3-none-any.whl", hash = "sha256:c5847e96134e5c5371ee9fac6fdf1a67336d5815e09eb2a01fdb57a351ef915b", size = 72984, upload-time = "2025-07-20T17:31:56.738Z" }, ] -[[package]] -name = "sympy" -version = "1.14.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mpmath" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" }, -] - [[package]] name = "tabulate" version = "0.9.0" @@ -3614,62 +3217,48 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/40/44/4a5f08c96eb108af5cb50b41f76142f0afa346dfa99d5296fe7202a11854/tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f", size = 35252, upload-time = "2022-10-06T17:21:44.262Z" }, ] -[[package]] -name = "tavily-python" -version = "0.7.9" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "httpx" }, - { name = "requests" }, - { name = "tiktoken" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ad/c1/5956e9711313a1bcaa3b6462b378014998ce394bd7cd6eb43a975d430bc7/tavily_python-0.7.9.tar.gz", hash = "sha256:61aa13ca89e2e40d645042c8d27afc478b27846fb79bb21d4f683ed28f173dc7", size = 19173, upload-time = "2025-07-01T22:44:01.759Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/b4/14305cbf1e82ee51c74b1e1906ee70f4a2e62719dc8a8614f1fa562af376/tavily_python-0.7.9-py3-none-any.whl", hash = "sha256:6d70ea86e2ccba061d0ea98c81922784a01c186960304d44436304f114f22372", size = 15666, upload-time = "2025-07-01T22:43:59.25Z" }, -] - [[package]] name = "tenacity" -version = "8.5.0" +version = "9.1.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a3/4d/6a19536c50b849338fcbe9290d562b52cbdcf30d8963d3588a68a4107df1/tenacity-8.5.0.tar.gz", hash = "sha256:8bc6c0c8a09b31e6cad13c47afbed1a567518250a9a171418582ed8d9c20ca78", size = 47309, upload-time = "2024-07-05T07:25:31.836Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/d4/2b0cd0fe285e14b36db076e78c93766ff1d529d70408bd1d2a5a84f1d929/tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb", size = 48036, upload-time = "2025-04-02T08:25:09.966Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/3f/8ba87d9e287b9d385a02a7114ddcef61b26f86411e121c9003eb509a1773/tenacity-8.5.0-py3-none-any.whl", hash = "sha256:b594c2a5945830c267ce6b79a166228323ed52718f30302c1359836112346687", size = 28165, upload-time = "2024-07-05T07:25:29.591Z" }, + { url = "https://files.pythonhosted.org/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248, upload-time = "2025-04-02T08:25:07.678Z" }, ] [[package]] name = "tiktoken" -version = "0.9.0" +version = "0.11.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "regex" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ea/cf/756fedf6981e82897f2d570dd25fa597eb3f4459068ae0572d7e888cfd6f/tiktoken-0.9.0.tar.gz", hash = "sha256:d02a5ca6a938e0490e1ff957bc48c8b078c88cb83977be1625b1fd8aac792c5d", size = 35991, upload-time = "2025-02-14T06:03:01.003Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/ae/4613a59a2a48e761c5161237fc850eb470b4bb93696db89da51b79a871f1/tiktoken-0.9.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f32cc56168eac4851109e9b5d327637f15fd662aa30dd79f964b7c39fbadd26e", size = 1065987, upload-time = "2025-02-14T06:02:14.174Z" }, - { url = "https://files.pythonhosted.org/packages/3f/86/55d9d1f5b5a7e1164d0f1538a85529b5fcba2b105f92db3622e5d7de6522/tiktoken-0.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:45556bc41241e5294063508caf901bf92ba52d8ef9222023f83d2483a3055348", size = 1009155, upload-time = "2025-02-14T06:02:15.384Z" }, - { url = "https://files.pythonhosted.org/packages/03/58/01fb6240df083b7c1916d1dcb024e2b761213c95d576e9f780dfb5625a76/tiktoken-0.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03935988a91d6d3216e2ec7c645afbb3d870b37bcb67ada1943ec48678e7ee33", size = 1142898, upload-time = "2025-02-14T06:02:16.666Z" }, - { url = "https://files.pythonhosted.org/packages/b1/73/41591c525680cd460a6becf56c9b17468d3711b1df242c53d2c7b2183d16/tiktoken-0.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b3d80aad8d2c6b9238fc1a5524542087c52b860b10cbf952429ffb714bc1136", size = 1197535, upload-time = "2025-02-14T06:02:18.595Z" }, - { url = "https://files.pythonhosted.org/packages/7d/7c/1069f25521c8f01a1a182f362e5c8e0337907fae91b368b7da9c3e39b810/tiktoken-0.9.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b2a21133be05dc116b1d0372af051cd2c6aa1d2188250c9b553f9fa49301b336", size = 1259548, upload-time = "2025-02-14T06:02:20.729Z" }, - { url = "https://files.pythonhosted.org/packages/6f/07/c67ad1724b8e14e2b4c8cca04b15da158733ac60136879131db05dda7c30/tiktoken-0.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:11a20e67fdf58b0e2dea7b8654a288e481bb4fc0289d3ad21291f8d0849915fb", size = 893895, upload-time = "2025-02-14T06:02:22.67Z" }, - { url = "https://files.pythonhosted.org/packages/cf/e5/21ff33ecfa2101c1bb0f9b6df750553bd873b7fb532ce2cb276ff40b197f/tiktoken-0.9.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e88f121c1c22b726649ce67c089b90ddda8b9662545a8aeb03cfef15967ddd03", size = 1065073, upload-time = "2025-02-14T06:02:24.768Z" }, - { url = "https://files.pythonhosted.org/packages/8e/03/a95e7b4863ee9ceec1c55983e4cc9558bcfd8f4f80e19c4f8a99642f697d/tiktoken-0.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a6600660f2f72369acb13a57fb3e212434ed38b045fd8cc6cdd74947b4b5d210", size = 1008075, upload-time = "2025-02-14T06:02:26.92Z" }, - { url = "https://files.pythonhosted.org/packages/40/10/1305bb02a561595088235a513ec73e50b32e74364fef4de519da69bc8010/tiktoken-0.9.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95e811743b5dfa74f4b227927ed86cbc57cad4df859cb3b643be797914e41794", size = 1140754, upload-time = "2025-02-14T06:02:28.124Z" }, - { url = "https://files.pythonhosted.org/packages/1b/40/da42522018ca496432ffd02793c3a72a739ac04c3794a4914570c9bb2925/tiktoken-0.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99376e1370d59bcf6935c933cb9ba64adc29033b7e73f5f7569f3aad86552b22", size = 1196678, upload-time = "2025-02-14T06:02:29.845Z" }, - { url = "https://files.pythonhosted.org/packages/5c/41/1e59dddaae270ba20187ceb8aa52c75b24ffc09f547233991d5fd822838b/tiktoken-0.9.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:badb947c32739fb6ddde173e14885fb3de4d32ab9d8c591cbd013c22b4c31dd2", size = 1259283, upload-time = "2025-02-14T06:02:33.838Z" }, - { url = "https://files.pythonhosted.org/packages/5b/64/b16003419a1d7728d0d8c0d56a4c24325e7b10a21a9dd1fc0f7115c02f0a/tiktoken-0.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:5a62d7a25225bafed786a524c1b9f0910a1128f4232615bf3f8257a73aaa3b16", size = 894897, upload-time = "2025-02-14T06:02:36.265Z" }, - { url = "https://files.pythonhosted.org/packages/7a/11/09d936d37f49f4f494ffe660af44acd2d99eb2429d60a57c71318af214e0/tiktoken-0.9.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2b0e8e05a26eda1249e824156d537015480af7ae222ccb798e5234ae0285dbdb", size = 1064919, upload-time = "2025-02-14T06:02:37.494Z" }, - { url = "https://files.pythonhosted.org/packages/80/0e/f38ba35713edb8d4197ae602e80837d574244ced7fb1b6070b31c29816e0/tiktoken-0.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:27d457f096f87685195eea0165a1807fae87b97b2161fe8c9b1df5bd74ca6f63", size = 1007877, upload-time = "2025-02-14T06:02:39.516Z" }, - { url = "https://files.pythonhosted.org/packages/fe/82/9197f77421e2a01373e27a79dd36efdd99e6b4115746ecc553318ecafbf0/tiktoken-0.9.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cf8ded49cddf825390e36dd1ad35cd49589e8161fdcb52aa25f0583e90a3e01", size = 1140095, upload-time = "2025-02-14T06:02:41.791Z" }, - { url = "https://files.pythonhosted.org/packages/f2/bb/4513da71cac187383541facd0291c4572b03ec23c561de5811781bbd988f/tiktoken-0.9.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc156cb314119a8bb9748257a2eaebd5cc0753b6cb491d26694ed42fc7cb3139", size = 1195649, upload-time = "2025-02-14T06:02:43Z" }, - { url = "https://files.pythonhosted.org/packages/fa/5c/74e4c137530dd8504e97e3a41729b1103a4ac29036cbfd3250b11fd29451/tiktoken-0.9.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cd69372e8c9dd761f0ab873112aba55a0e3e506332dd9f7522ca466e817b1b7a", size = 1258465, upload-time = "2025-02-14T06:02:45.046Z" }, - { url = "https://files.pythonhosted.org/packages/de/a8/8f499c179ec900783ffe133e9aab10044481679bb9aad78436d239eee716/tiktoken-0.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:5ea0edb6f83dc56d794723286215918c1cde03712cbbafa0348b33448faf5b95", size = 894669, upload-time = "2025-02-14T06:02:47.341Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/a7/86/ad0155a37c4f310935d5ac0b1ccf9bdb635dcb906e0a9a26b616dd55825a/tiktoken-0.11.0.tar.gz", hash = "sha256:3c518641aee1c52247c2b97e74d8d07d780092af79d5911a6ab5e79359d9b06a", size = 37648, upload-time = "2025-08-08T23:58:08.495Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/91/912b459799a025d2842566fe1e902f7f50d54a1ce8a0f236ab36b5bd5846/tiktoken-0.11.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:4ae374c46afadad0f501046db3da1b36cd4dfbfa52af23c998773682446097cf", size = 1059743, upload-time = "2025-08-08T23:57:37.516Z" }, + { url = "https://files.pythonhosted.org/packages/8c/e9/6faa6870489ce64f5f75dcf91512bf35af5864583aee8fcb0dcb593121f5/tiktoken-0.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:25a512ff25dc6c85b58f5dd4f3d8c674dc05f96b02d66cdacf628d26a4e4866b", size = 999334, upload-time = "2025-08-08T23:57:38.595Z" }, + { url = "https://files.pythonhosted.org/packages/a1/3e/a05d1547cf7db9dc75d1461cfa7b556a3b48e0516ec29dfc81d984a145f6/tiktoken-0.11.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2130127471e293d385179c1f3f9cd445070c0772be73cdafb7cec9a3684c0458", size = 1129402, upload-time = "2025-08-08T23:57:39.627Z" }, + { url = "https://files.pythonhosted.org/packages/34/9a/db7a86b829e05a01fd4daa492086f708e0a8b53952e1dbc9d380d2b03677/tiktoken-0.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21e43022bf2c33f733ea9b54f6a3f6b4354b909f5a73388fb1b9347ca54a069c", size = 1184046, upload-time = "2025-08-08T23:57:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/9d/bb/52edc8e078cf062ed749248f1454e9e5cfd09979baadb830b3940e522015/tiktoken-0.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:adb4e308eb64380dc70fa30493e21c93475eaa11669dea313b6bbf8210bfd013", size = 1244691, upload-time = "2025-08-08T23:57:42.251Z" }, + { url = "https://files.pythonhosted.org/packages/60/d9/884b6cd7ae2570ecdcaffa02b528522b18fef1cbbfdbcaa73799807d0d3b/tiktoken-0.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:ece6b76bfeeb61a125c44bbefdfccc279b5288e6007fbedc0d32bfec602df2f2", size = 884392, upload-time = "2025-08-08T23:57:43.628Z" }, + { url = "https://files.pythonhosted.org/packages/e7/9e/eceddeffc169fc75fe0fd4f38471309f11cb1906f9b8aa39be4f5817df65/tiktoken-0.11.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fd9e6b23e860973cf9526544e220b223c60badf5b62e80a33509d6d40e6c8f5d", size = 1055199, upload-time = "2025-08-08T23:57:45.076Z" }, + { url = "https://files.pythonhosted.org/packages/4f/cf/5f02bfefffdc6b54e5094d2897bc80efd43050e5b09b576fd85936ee54bf/tiktoken-0.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6a76d53cee2da71ee2731c9caa747398762bda19d7f92665e882fef229cb0b5b", size = 996655, upload-time = "2025-08-08T23:57:46.304Z" }, + { url = "https://files.pythonhosted.org/packages/65/8e/c769b45ef379bc360c9978c4f6914c79fd432400a6733a8afc7ed7b0726a/tiktoken-0.11.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ef72aab3ea240646e642413cb363b73869fed4e604dcfd69eec63dc54d603e8", size = 1128867, upload-time = "2025-08-08T23:57:47.438Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2d/4d77f6feb9292bfdd23d5813e442b3bba883f42d0ac78ef5fdc56873f756/tiktoken-0.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f929255c705efec7a28bf515e29dc74220b2f07544a8c81b8d69e8efc4578bd", size = 1183308, upload-time = "2025-08-08T23:57:48.566Z" }, + { url = "https://files.pythonhosted.org/packages/7a/65/7ff0a65d3bb0fc5a1fb6cc71b03e0f6e71a68c5eea230d1ff1ba3fd6df49/tiktoken-0.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:61f1d15822e4404953d499fd1dcc62817a12ae9fb1e4898033ec8fe3915fdf8e", size = 1244301, upload-time = "2025-08-08T23:57:49.642Z" }, + { url = "https://files.pythonhosted.org/packages/f5/6e/5b71578799b72e5bdcef206a214c3ce860d999d579a3b56e74a6c8989ee2/tiktoken-0.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:45927a71ab6643dfd3ef57d515a5db3d199137adf551f66453be098502838b0f", size = 884282, upload-time = "2025-08-08T23:57:50.759Z" }, + { url = "https://files.pythonhosted.org/packages/cc/cd/a9034bcee638716d9310443818d73c6387a6a96db93cbcb0819b77f5b206/tiktoken-0.11.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a5f3f25ffb152ee7fec78e90a5e5ea5b03b4ea240beed03305615847f7a6ace2", size = 1055339, upload-time = "2025-08-08T23:57:51.802Z" }, + { url = "https://files.pythonhosted.org/packages/f1/91/9922b345f611b4e92581f234e64e9661e1c524875c8eadd513c4b2088472/tiktoken-0.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7dc6e9ad16a2a75b4c4be7208055a1f707c9510541d94d9cc31f7fbdc8db41d8", size = 997080, upload-time = "2025-08-08T23:57:53.442Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9d/49cd047c71336bc4b4af460ac213ec1c457da67712bde59b892e84f1859f/tiktoken-0.11.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5a0517634d67a8a48fd4a4ad73930c3022629a85a217d256a6e9b8b47439d1e4", size = 1128501, upload-time = "2025-08-08T23:57:54.808Z" }, + { url = "https://files.pythonhosted.org/packages/52/d5/a0dcdb40dd2ea357e83cb36258967f0ae96f5dd40c722d6e382ceee6bba9/tiktoken-0.11.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7fb4effe60574675118b73c6fbfd3b5868e5d7a1f570d6cc0d18724b09ecf318", size = 1182743, upload-time = "2025-08-08T23:57:56.307Z" }, + { url = "https://files.pythonhosted.org/packages/3b/17/a0fc51aefb66b7b5261ca1314afa83df0106b033f783f9a7bcbe8e741494/tiktoken-0.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:94f984c9831fd32688aef4348803b0905d4ae9c432303087bae370dc1381a2b8", size = 1244057, upload-time = "2025-08-08T23:57:57.628Z" }, + { url = "https://files.pythonhosted.org/packages/50/79/bcf350609f3a10f09fe4fc207f132085e497fdd3612f3925ab24d86a0ca0/tiktoken-0.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:2177ffda31dec4023356a441793fed82f7af5291120751dee4d696414f54db0c", size = 883901, upload-time = "2025-08-08T23:57:59.359Z" }, ] [[package]] name = "together" -version = "1.5.19" +version = "1.5.25" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, @@ -3685,34 +3274,34 @@ dependencies = [ { name = "tqdm" }, { name = "typer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d1/7f/b21fa39747045792192f088929a85e2eb2c3d7b7ff360e6bd200076e16a1/together-1.5.19.tar.gz", hash = "sha256:ce9eecef17b84bae00761bb8a12b4302bfaf81519aff051e05bcf93f15d38200", size = 67448, upload-time = "2025-07-08T16:03:46.325Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f4/3c/3367694809b9e2b2168d6ecf920d13ec9e320eac24b172ba7c4fd66f82f4/together-1.5.25.tar.gz", hash = "sha256:cfa24c20d095723561272b0ce240fe20a76f0688955a48ebfb6a7e01edb1a72c", size = 75648, upload-time = "2025-08-15T00:26:16.29Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/73/d3/acd2c1a012c3207961d7607cd9ff5ce169b67e7b441763f32272867c9192/together-1.5.19-py3-none-any.whl", hash = "sha256:47bf51b6ae79847bada63f29927ae30d41a44807a01e9e4e41a4e1e3be9d88c2", size = 91601, upload-time = "2025-07-08T16:03:44.948Z" }, + { url = "https://files.pythonhosted.org/packages/b9/4f/a9f6366dce7e8729dcd67db73f1387531fb407c6f9693db60c3c7cb258e1/together-1.5.25-py3-none-any.whl", hash = "sha256:17ebb1530c43fde053b2bd04842f94e1bc4cbbc1dc195ea254be6f52037a3c8f", size = 103273, upload-time = "2025-08-15T00:26:14.156Z" }, ] [[package]] name = "tokenizers" -version = "0.21.2" +version = "0.21.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "huggingface-hub" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ab/2d/b0fce2b8201635f60e8c95990080f58461cc9ca3d5026de2e900f38a7f21/tokenizers-0.21.2.tar.gz", hash = "sha256:fdc7cffde3e2113ba0e6cc7318c40e3438a4d74bbc62bf04bcc63bdfb082ac77", size = 351545, upload-time = "2025-06-24T10:24:52.449Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c2/2f/402986d0823f8d7ca139d969af2917fefaa9b947d1fb32f6168c509f2492/tokenizers-0.21.4.tar.gz", hash = "sha256:fa23f85fbc9a02ec5c6978da172cdcbac23498c3ca9f3645c5c68740ac007880", size = 351253, upload-time = "2025-07-28T15:48:54.325Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1d/cc/2936e2d45ceb130a21d929743f1e9897514691bec123203e10837972296f/tokenizers-0.21.2-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:342b5dfb75009f2255ab8dec0041287260fed5ce00c323eb6bab639066fef8ec", size = 2875206, upload-time = "2025-06-24T10:24:42.755Z" }, - { url = "https://files.pythonhosted.org/packages/6c/e6/33f41f2cc7861faeba8988e7a77601407bf1d9d28fc79c5903f8f77df587/tokenizers-0.21.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:126df3205d6f3a93fea80c7a8a266a78c1bd8dd2fe043386bafdd7736a23e45f", size = 2732655, upload-time = "2025-06-24T10:24:41.56Z" }, - { url = "https://files.pythonhosted.org/packages/33/2b/1791eb329c07122a75b01035b1a3aa22ad139f3ce0ece1b059b506d9d9de/tokenizers-0.21.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4a32cd81be21168bd0d6a0f0962d60177c447a1aa1b1e48fa6ec9fc728ee0b12", size = 3019202, upload-time = "2025-06-24T10:24:31.791Z" }, - { url = "https://files.pythonhosted.org/packages/05/15/fd2d8104faa9f86ac68748e6f7ece0b5eb7983c7efc3a2c197cb98c99030/tokenizers-0.21.2-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8bd8999538c405133c2ab999b83b17c08b7fc1b48c1ada2469964605a709ef91", size = 2934539, upload-time = "2025-06-24T10:24:34.567Z" }, - { url = "https://files.pythonhosted.org/packages/a5/2e/53e8fd053e1f3ffbe579ca5f9546f35ac67cf0039ed357ad7ec57f5f5af0/tokenizers-0.21.2-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5e9944e61239b083a41cf8fc42802f855e1dca0f499196df37a8ce219abac6eb", size = 3248665, upload-time = "2025-06-24T10:24:39.024Z" }, - { url = "https://files.pythonhosted.org/packages/00/15/79713359f4037aa8f4d1f06ffca35312ac83629da062670e8830917e2153/tokenizers-0.21.2-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:514cd43045c5d546f01142ff9c79a96ea69e4b5cda09e3027708cb2e6d5762ab", size = 3451305, upload-time = "2025-06-24T10:24:36.133Z" }, - { url = "https://files.pythonhosted.org/packages/38/5f/959f3a8756fc9396aeb704292777b84f02a5c6f25c3fc3ba7530db5feb2c/tokenizers-0.21.2-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b1b9405822527ec1e0f7d8d2fdb287a5730c3a6518189c968254a8441b21faae", size = 3214757, upload-time = "2025-06-24T10:24:37.784Z" }, - { url = "https://files.pythonhosted.org/packages/c5/74/f41a432a0733f61f3d21b288de6dfa78f7acff309c6f0f323b2833e9189f/tokenizers-0.21.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fed9a4d51c395103ad24f8e7eb976811c57fbec2af9f133df471afcd922e5020", size = 3121887, upload-time = "2025-06-24T10:24:40.293Z" }, - { url = "https://files.pythonhosted.org/packages/3c/6a/bc220a11a17e5d07b0dfb3b5c628621d4dcc084bccd27cfaead659963016/tokenizers-0.21.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2c41862df3d873665ec78b6be36fcc30a26e3d4902e9dd8608ed61d49a48bc19", size = 9091965, upload-time = "2025-06-24T10:24:44.431Z" }, - { url = "https://files.pythonhosted.org/packages/6c/bd/ac386d79c4ef20dc6f39c4706640c24823dca7ebb6f703bfe6b5f0292d88/tokenizers-0.21.2-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:ed21dc7e624e4220e21758b2e62893be7101453525e3d23264081c9ef9a6d00d", size = 9053372, upload-time = "2025-06-24T10:24:46.455Z" }, - { url = "https://files.pythonhosted.org/packages/63/7b/5440bf203b2a5358f074408f7f9c42884849cd9972879e10ee6b7a8c3b3d/tokenizers-0.21.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:0e73770507e65a0e0e2a1affd6b03c36e3bc4377bd10c9ccf51a82c77c0fe365", size = 9298632, upload-time = "2025-06-24T10:24:48.446Z" }, - { url = "https://files.pythonhosted.org/packages/a4/d2/faa1acac3f96a7427866e94ed4289949b2524f0c1878512516567d80563c/tokenizers-0.21.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:106746e8aa9014a12109e58d540ad5465b4c183768ea96c03cbc24c44d329958", size = 9470074, upload-time = "2025-06-24T10:24:50.378Z" }, - { url = "https://files.pythonhosted.org/packages/d8/a5/896e1ef0707212745ae9f37e84c7d50269411aef2e9ccd0de63623feecdf/tokenizers-0.21.2-cp39-abi3-win32.whl", hash = "sha256:cabda5a6d15d620b6dfe711e1af52205266d05b379ea85a8a301b3593c60e962", size = 2330115, upload-time = "2025-06-24T10:24:55.069Z" }, - { url = "https://files.pythonhosted.org/packages/13/c3/cc2755ee10be859c4338c962a35b9a663788c0c0b50c0bdd8078fb6870cf/tokenizers-0.21.2-cp39-abi3-win_amd64.whl", hash = "sha256:58747bb898acdb1007f37a7bbe614346e98dc28708ffb66a3fd50ce169ac6c98", size = 2509918, upload-time = "2025-06-24T10:24:53.71Z" }, + { url = "https://files.pythonhosted.org/packages/98/c6/fdb6f72bf6454f52eb4a2510be7fb0f614e541a2554d6210e370d85efff4/tokenizers-0.21.4-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:2ccc10a7c3bcefe0f242867dc914fc1226ee44321eb618cfe3019b5df3400133", size = 2863987, upload-time = "2025-07-28T15:48:44.877Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a6/28975479e35ddc751dc1ddc97b9b69bf7fcf074db31548aab37f8116674c/tokenizers-0.21.4-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:5e2f601a8e0cd5be5cc7506b20a79112370b9b3e9cb5f13f68ab11acd6ca7d60", size = 2732457, upload-time = "2025-07-28T15:48:43.265Z" }, + { url = "https://files.pythonhosted.org/packages/aa/8f/24f39d7b5c726b7b0be95dca04f344df278a3fe3a4deb15a975d194cbb32/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39b376f5a1aee67b4d29032ee85511bbd1b99007ec735f7f35c8a2eb104eade5", size = 3012624, upload-time = "2025-07-28T13:22:43.895Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/26358925717687a58cb74d7a508de96649544fad5778f0cd9827398dc499/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2107ad649e2cda4488d41dfd031469e9da3fcbfd6183e74e4958fa729ffbf9c6", size = 2939681, upload-time = "2025-07-28T13:22:47.499Z" }, + { url = "https://files.pythonhosted.org/packages/99/6f/cc300fea5db2ab5ddc2c8aea5757a27b89c84469899710c3aeddc1d39801/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c73012da95afafdf235ba80047699df4384fdc481527448a078ffd00e45a7d9", size = 3247445, upload-time = "2025-07-28T15:48:39.711Z" }, + { url = "https://files.pythonhosted.org/packages/be/bf/98cb4b9c3c4afd8be89cfa6423704337dc20b73eb4180397a6e0d456c334/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f23186c40395fc390d27f519679a58023f368a0aad234af145e0f39ad1212732", size = 3428014, upload-time = "2025-07-28T13:22:49.569Z" }, + { url = "https://files.pythonhosted.org/packages/75/c7/96c1cc780e6ca7f01a57c13235dd05b7bc1c0f3588512ebe9d1331b5f5ae/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc88bb34e23a54cc42713d6d98af5f1bf79c07653d24fe984d2d695ba2c922a2", size = 3193197, upload-time = "2025-07-28T13:22:51.471Z" }, + { url = "https://files.pythonhosted.org/packages/f2/90/273b6c7ec78af547694eddeea9e05de771278bd20476525ab930cecaf7d8/tokenizers-0.21.4-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51b7eabb104f46c1c50b486520555715457ae833d5aee9ff6ae853d1130506ff", size = 3115426, upload-time = "2025-07-28T15:48:41.439Z" }, + { url = "https://files.pythonhosted.org/packages/91/43/c640d5a07e95f1cf9d2c92501f20a25f179ac53a4f71e1489a3dcfcc67ee/tokenizers-0.21.4-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:714b05b2e1af1288bd1bc56ce496c4cebb64a20d158ee802887757791191e6e2", size = 9089127, upload-time = "2025-07-28T15:48:46.472Z" }, + { url = "https://files.pythonhosted.org/packages/44/a1/dd23edd6271d4dca788e5200a807b49ec3e6987815cd9d0a07ad9c96c7c2/tokenizers-0.21.4-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:1340ff877ceedfa937544b7d79f5b7becf33a4cfb58f89b3b49927004ef66f78", size = 9055243, upload-time = "2025-07-28T15:48:48.539Z" }, + { url = "https://files.pythonhosted.org/packages/21/2b/b410d6e9021c4b7ddb57248304dc817c4d4970b73b6ee343674914701197/tokenizers-0.21.4-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:3c1f4317576e465ac9ef0d165b247825a2a4078bcd01cba6b54b867bdf9fdd8b", size = 9298237, upload-time = "2025-07-28T15:48:50.443Z" }, + { url = "https://files.pythonhosted.org/packages/b7/0a/42348c995c67e2e6e5c89ffb9cfd68507cbaeb84ff39c49ee6e0a6dd0fd2/tokenizers-0.21.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:c212aa4e45ec0bb5274b16b6f31dd3f1c41944025c2358faaa5782c754e84c24", size = 9461980, upload-time = "2025-07-28T15:48:52.325Z" }, + { url = "https://files.pythonhosted.org/packages/3d/d3/dacccd834404cd71b5c334882f3ba40331ad2120e69ded32cf5fda9a7436/tokenizers-0.21.4-cp39-abi3-win32.whl", hash = "sha256:6c42a930bc5f4c47f4ea775c91de47d27910881902b0f20e4990ebe045a415d0", size = 2329871, upload-time = "2025-07-28T15:48:56.841Z" }, + { url = "https://files.pythonhosted.org/packages/41/f2/fd673d979185f5dcbac4be7d09461cbb99751554ffb6718d0013af8604cb/tokenizers-0.21.4-cp39-abi3-win_amd64.whl", hash = "sha256:475d807a5c3eb72c59ad9b5fcdb254f6e17f53dfcbb9903233b0dfa9c943b597", size = 2507568, upload-time = "2025-07-28T15:48:55.456Z" }, ] [[package]] @@ -3775,35 +3364,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, ] -[[package]] -name = "tree-sitter" -version = "0.24.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a7/a2/698b9d31d08ad5558f8bfbfe3a0781bd4b1f284e89bde3ad18e05101a892/tree-sitter-0.24.0.tar.gz", hash = "sha256:abd95af65ca2f4f7eca356343391ed669e764f37748b5352946f00f7fc78e734", size = 168304, upload-time = "2025-01-17T05:06:38.115Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/66/08/82aaf7cbea7286ee2a0b43e9b75cb93ac6ac132991b7d3c26ebe5e5235a3/tree_sitter-0.24.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:de0fb7c18c6068cacff46250c0a0473e8fc74d673e3e86555f131c2c1346fb13", size = 140733, upload-time = "2025-01-17T05:05:56.307Z" }, - { url = "https://files.pythonhosted.org/packages/8c/bd/1a84574911c40734d80327495e6e218e8f17ef318dd62bb66b55c1e969f5/tree_sitter-0.24.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a7c9c89666dea2ce2b2bf98e75f429d2876c569fab966afefdcd71974c6d8538", size = 134243, upload-time = "2025-01-17T05:05:58.706Z" }, - { url = "https://files.pythonhosted.org/packages/46/c1/c2037af2c44996d7bde84eb1c9e42308cc84b547dd6da7f8a8bea33007e1/tree_sitter-0.24.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ddb113e6b8b3e3b199695b1492a47d87d06c538e63050823d90ef13cac585fd", size = 562030, upload-time = "2025-01-17T05:05:59.825Z" }, - { url = "https://files.pythonhosted.org/packages/4c/aa/2fb4d81886df958e6ec7e370895f7106d46d0bbdcc531768326124dc8972/tree_sitter-0.24.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01ea01a7003b88b92f7f875da6ba9d5d741e0c84bb1bd92c503c0eecd0ee6409", size = 575585, upload-time = "2025-01-17T05:06:01.045Z" }, - { url = "https://files.pythonhosted.org/packages/e3/3c/5f997ce34c0d1b744e0f0c0757113bdfc173a2e3dadda92c751685cfcbd1/tree_sitter-0.24.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:464fa5b2cac63608915a9de8a6efd67a4da1929e603ea86abaeae2cb1fe89921", size = 578203, upload-time = "2025-01-17T05:06:02.255Z" }, - { url = "https://files.pythonhosted.org/packages/d5/1f/f2bc7fa7c3081653ea4f2639e06ff0af4616c47105dbcc0746137da7620d/tree_sitter-0.24.0-cp311-cp311-win_amd64.whl", hash = "sha256:3b1f3cbd9700e1fba0be2e7d801527e37c49fc02dc140714669144ef6ab58dce", size = 120147, upload-time = "2025-01-17T05:06:05.233Z" }, - { url = "https://files.pythonhosted.org/packages/c0/4c/9add771772c4d72a328e656367ca948e389432548696a3819b69cdd6f41e/tree_sitter-0.24.0-cp311-cp311-win_arm64.whl", hash = "sha256:f3f08a2ca9f600b3758792ba2406971665ffbad810847398d180c48cee174ee2", size = 108302, upload-time = "2025-01-17T05:06:07.487Z" }, - { url = "https://files.pythonhosted.org/packages/e9/57/3a590f287b5aa60c07d5545953912be3d252481bf5e178f750db75572bff/tree_sitter-0.24.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:14beeff5f11e223c37be7d5d119819880601a80d0399abe8c738ae2288804afc", size = 140788, upload-time = "2025-01-17T05:06:08.492Z" }, - { url = "https://files.pythonhosted.org/packages/61/0b/fc289e0cba7dbe77c6655a4dd949cd23c663fd62a8b4d8f02f97e28d7fe5/tree_sitter-0.24.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:26a5b130f70d5925d67b47db314da209063664585a2fd36fa69e0717738efaf4", size = 133945, upload-time = "2025-01-17T05:06:12.39Z" }, - { url = "https://files.pythonhosted.org/packages/86/d7/80767238308a137e0b5b5c947aa243e3c1e3e430e6d0d5ae94b9a9ffd1a2/tree_sitter-0.24.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fc5c3c26d83c9d0ecb4fc4304fba35f034b7761d35286b936c1db1217558b4e", size = 564819, upload-time = "2025-01-17T05:06:13.549Z" }, - { url = "https://files.pythonhosted.org/packages/bf/b3/6c5574f4b937b836601f5fb556b24804b0a6341f2eb42f40c0e6464339f4/tree_sitter-0.24.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:772e1bd8c0931c866b848d0369b32218ac97c24b04790ec4b0e409901945dd8e", size = 579303, upload-time = "2025-01-17T05:06:16.685Z" }, - { url = "https://files.pythonhosted.org/packages/0a/f4/bd0ddf9abe242ea67cca18a64810f8af230fc1ea74b28bb702e838ccd874/tree_sitter-0.24.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:24a8dd03b0d6b8812425f3b84d2f4763322684e38baf74e5bb766128b5633dc7", size = 581054, upload-time = "2025-01-17T05:06:19.439Z" }, - { url = "https://files.pythonhosted.org/packages/8c/1c/ff23fa4931b6ef1bbeac461b904ca7e49eaec7e7e5398584e3eef836ec96/tree_sitter-0.24.0-cp312-cp312-win_amd64.whl", hash = "sha256:f9e8b1605ab60ed43803100f067eed71b0b0e6c1fb9860a262727dbfbbb74751", size = 120221, upload-time = "2025-01-17T05:06:20.654Z" }, - { url = "https://files.pythonhosted.org/packages/b2/2a/9979c626f303177b7612a802237d0533155bf1e425ff6f73cc40f25453e2/tree_sitter-0.24.0-cp312-cp312-win_arm64.whl", hash = "sha256:f733a83d8355fc95561582b66bbea92ffd365c5d7a665bc9ebd25e049c2b2abb", size = 108234, upload-time = "2025-01-17T05:06:21.713Z" }, - { url = "https://files.pythonhosted.org/packages/61/cd/2348339c85803330ce38cee1c6cbbfa78a656b34ff58606ebaf5c9e83bd0/tree_sitter-0.24.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0d4a6416ed421c4210f0ca405a4834d5ccfbb8ad6692d4d74f7773ef68f92071", size = 140781, upload-time = "2025-01-17T05:06:22.82Z" }, - { url = "https://files.pythonhosted.org/packages/8b/a3/1ea9d8b64e8dcfcc0051028a9c84a630301290995cd6e947bf88267ef7b1/tree_sitter-0.24.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e0992d483677e71d5c5d37f30dfb2e3afec2f932a9c53eec4fca13869b788c6c", size = 133928, upload-time = "2025-01-17T05:06:25.146Z" }, - { url = "https://files.pythonhosted.org/packages/fe/ae/55c1055609c9428a4aedf4b164400ab9adb0b1bf1538b51f4b3748a6c983/tree_sitter-0.24.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57277a12fbcefb1c8b206186068d456c600dbfbc3fd6c76968ee22614c5cd5ad", size = 564497, upload-time = "2025-01-17T05:06:27.53Z" }, - { url = "https://files.pythonhosted.org/packages/ce/d0/f2ffcd04882c5aa28d205a787353130cbf84b2b8a977fd211bdc3b399ae3/tree_sitter-0.24.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25fa22766d63f73716c6fec1a31ee5cf904aa429484256bd5fdf5259051ed74", size = 578917, upload-time = "2025-01-17T05:06:31.057Z" }, - { url = "https://files.pythonhosted.org/packages/af/82/aebe78ea23a2b3a79324993d4915f3093ad1af43d7c2208ee90be9273273/tree_sitter-0.24.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7d5d9537507e1c8c5fa9935b34f320bfec4114d675e028f3ad94f11cf9db37b9", size = 581148, upload-time = "2025-01-17T05:06:32.409Z" }, - { url = "https://files.pythonhosted.org/packages/a1/b4/6b0291a590c2b0417cfdb64ccb8ea242f270a46ed429c641fbc2bfab77e0/tree_sitter-0.24.0-cp313-cp313-win_amd64.whl", hash = "sha256:f58bb4956917715ec4d5a28681829a8dad5c342cafd4aea269f9132a83ca9b34", size = 120207, upload-time = "2025-01-17T05:06:34.841Z" }, - { url = "https://files.pythonhosted.org/packages/a8/18/542fd844b75272630229c9939b03f7db232c71a9d82aadc59c596319ea6a/tree_sitter-0.24.0-cp313-cp313-win_arm64.whl", hash = "sha256:23641bd25dcd4bb0b6fa91b8fb3f46cc9f1c9f475efe4d536d3f1f688d1b84c8", size = 108232, upload-time = "2025-01-17T05:06:35.831Z" }, -] - [[package]] name = "typeguard" version = "4.4.4" @@ -3818,7 +3378,7 @@ wheels = [ [[package]] name = "typer" -version = "0.15.3" +version = "0.15.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, @@ -3826,273 +3386,36 @@ dependencies = [ { name = "shellingham" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/98/1a/5f36851f439884bcfe8539f6a20ff7516e7b60f319bbaf69a90dc35cc2eb/typer-0.15.3.tar.gz", hash = "sha256:818873625d0569653438316567861899f7e9972f2e6e0c16dab608345ced713c", size = 101641, upload-time = "2025-04-28T21:40:59.204Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/48/20/9d953de6f4367163d23ec823200eb3ecb0050a2609691e512c8b95827a9b/typer-0.15.3-py3-none-any.whl", hash = "sha256:c86a65ad77ca531f03de08d1b9cb67cd09ad02ddddf4b34745b5008f43b239bd", size = 45253, upload-time = "2025-04-28T21:40:56.269Z" }, -] - -[[package]] -name = "types-beautifulsoup4" -version = "4.12.0.20250516" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "types-html5lib" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6d/d1/32b410f6d65eda94d3dfb0b3d0ca151f12cb1dc4cef731dcf7cbfd8716ff/types_beautifulsoup4-4.12.0.20250516.tar.gz", hash = "sha256:aa19dd73b33b70d6296adf92da8ab8a0c945c507e6fb7d5db553415cc77b417e", size = 16628, upload-time = "2025-05-16T03:09:09.93Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7c/79/d84de200a80085b32f12c5820d4fd0addcbe7ba6dce8c1c9d8605e833c8e/types_beautifulsoup4-4.12.0.20250516-py3-none-any.whl", hash = "sha256:5923399d4a1ba9cc8f0096fe334cc732e130269541d66261bb42ab039c0376ee", size = 16879, upload-time = "2025-05-16T03:09:09.051Z" }, -] - -[[package]] -name = "types-cachetools" -version = "6.1.0.20250717" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5a/14/e98ea3b3fda81787659268bbf09dec56961c39db060fdca74cb521df0515/types_cachetools-6.1.0.20250717.tar.gz", hash = "sha256:4acc8e25de9f5f84dd176ea81dcffa7cb24393869bb2e59e692dfd0139a1e66f", size = 9105, upload-time = "2025-07-17T03:20:48.482Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/61/bb/554208964e901e9e1992a7ea0bcab1590a4b2e94d20a9e6200826110ec89/types_cachetools-6.1.0.20250717-py3-none-any.whl", hash = "sha256:bba4b8d42262460d24e570097d2d9040e60311934603caa642efd971f3658ed0", size = 8940, upload-time = "2025-07-17T03:20:47.375Z" }, -] - -[[package]] -name = "types-cffi" -version = "1.17.0.20250523" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "types-setuptools" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/f7/5f/ac80a2f55757019e5d4809d17544569c47a623565258ca1a836ba951d53f/types_cffi-1.17.0.20250523.tar.gz", hash = "sha256:e7110f314c65590533adae1b30763be08ca71ad856a1ae3fe9b9d8664d49ec22", size = 16858, upload-time = "2025-05-23T03:05:40.983Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f1/86/e26e6ae4dfcbf6031b8422c22cf3a9eb2b6d127770406e7645b6248d8091/types_cffi-1.17.0.20250523-py3-none-any.whl", hash = "sha256:e98c549d8e191f6220e440f9f14315d6775a21a0e588c32c20476be885b2fad9", size = 20010, upload-time = "2025-05-23T03:05:39.136Z" }, -] - -[[package]] -name = "types-defusedxml" -version = "0.7.0.20250708" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b9/4b/79d046a7211e110afd885be04bb9423546df2a662ed28251512d60e51fb6/types_defusedxml-0.7.0.20250708.tar.gz", hash = "sha256:7b785780cc11c18a1af086308bf94bf53a0907943a1d145dbe00189bef323cb8", size = 10541, upload-time = "2025-07-08T03:14:33.325Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/24/f8/870de7fbd5fee5643f05061db948df6bd574a05a42aee91e37ad47c999ef/types_defusedxml-0.7.0.20250708-py3-none-any.whl", hash = "sha256:cc426cbc31c61a0f1b1c2ad9b9ef9ef846645f28fd708cd7727a6353b5c52e54", size = 13478, upload-time = "2025-07-08T03:14:32.633Z" }, -] - -[[package]] -name = "types-docutils" -version = "0.21.0.20250715" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1c/22/95cc688902751d289df35012b7a80257ecc77aa7fba8558431776d3f9bb6/types_docutils-0.21.0.20250715.tar.gz", hash = "sha256:f7beb7d136cfbbc456e4a33f45eb367c7a5338b591c57a6555c821f85238a7fb", size = 44342, upload-time = "2025-07-15T03:23:20.22Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/06/48dd8ab72d7abc4cf1caa7619cd978ff7c4f5217dd08d413cbc640c46769/types_docutils-0.21.0.20250715-py3-none-any.whl", hash = "sha256:f254ab9aa46cfd4dce07034dc224db25a55452380ed3df156d9ce5589811c261", size = 73240, upload-time = "2025-07-15T03:23:18.903Z" }, -] - -[[package]] -name = "types-greenlet" -version = "3.2.0.20250417" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ad/a3/0a2583e1542bd79cfdfd3ffc0407c9f0de5f62642cff13f4d191e1291e40/types_greenlet-3.2.0.20250417.tar.gz", hash = "sha256:eb006afcf281ec5756a75c1fd4a6c8a7be5d0cc09b2e82c4856c764760cfa0e3", size = 8785, upload-time = "2025-04-17T02:58:13.007Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/89/c527e6c848739be8ceb5c44eb8208c52ea3515c6cf6406aa61932887bf58/typer-0.15.4.tar.gz", hash = "sha256:89507b104f9b6a0730354f27c39fae5b63ccd0c95b1ce1f1a6ba0cfd329997c3", size = 101559, upload-time = "2025-05-14T16:34:57.704Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/ce/61b00f3ffc1ab468d6d994c0880f1470bc0ee9b198dae9fbd773a0029c9f/types_greenlet-3.2.0.20250417-py3-none-any.whl", hash = "sha256:7798b9fdf19d718a62e2d63351e112e7bee622898c6e6cec539296c3dec27808", size = 8819, upload-time = "2025-04-17T02:58:11.816Z" }, + { url = "https://files.pythonhosted.org/packages/c9/62/d4ba7afe2096d5659ec3db8b15d8665bdcb92a3c6ff0b95e99895b335a9c/typer-0.15.4-py3-none-any.whl", hash = "sha256:eb0651654dcdea706780c466cf06d8f174405a659ffff8f163cfbfee98c0e173", size = 45258, upload-time = "2025-05-14T16:34:55.583Z" }, ] [[package]] -name = "types-html5lib" -version = "1.1.11.20250708" +name = "types-awscrt" +version = "0.27.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d4/3b/1f5ba4358cfc1421cced5cdb9d2b08b4b99e4f9a41da88ce079f6d1a7bf1/types_html5lib-1.1.11.20250708.tar.gz", hash = "sha256:24321720fdbac71cee50d5a4bec9b7448495b7217974cffe3fcf1ede4eef7afe", size = 16799, upload-time = "2025-07-08T03:13:53.14Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/50/5fc23cf647eee23acdd337c8150861d39980cf11f33dd87f78e87d2a4bad/types_html5lib-1.1.11.20250708-py3-none-any.whl", hash = "sha256:bb898066b155de7081cb182179e2ded31b9e0e234605e2cb46536894e68a6954", size = 22913, upload-time = "2025-07-08T03:13:52.098Z" }, -] - -[[package]] -name = "types-jsonschema" -version = "4.24.0.20250708" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "referencing" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8f/0b/38e3c411173be89cc34e8994ef5c76d57420126dd440d06e041a5a8f6a56/types_jsonschema-4.24.0.20250708.tar.gz", hash = "sha256:a910e4944681cbb1b18a93ffb502e09910db788314312fc763df08d8ac2aadb7", size = 15465, upload-time = "2025-07-08T03:14:57.774Z" } +sdist = { url = "https://files.pythonhosted.org/packages/56/ce/5d84526a39f44c420ce61b16654193f8437d74b54f21597ea2ac65d89954/types_awscrt-0.27.6.tar.gz", hash = "sha256:9d3f1865a93b8b2c32f137514ac88cb048b5bc438739945ba19d972698995bfb", size = 16937, upload-time = "2025-08-13T01:54:54.659Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/06/d8935777e6620d3c3f05cb044abe66e9f7cf71bb70e3de3ed24dc327cbb0/types_jsonschema-4.24.0.20250708-py3-none-any.whl", hash = "sha256:d574aa3421d178a8435cc898cf4cf5e5e8c8f37b949c8e3ceeff06da433a18bf", size = 15766, upload-time = "2025-07-08T03:14:56.997Z" }, -] - -[[package]] -name = "types-objgraph" -version = "3.6.0.20240907" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/22/48/ba0ec63d392904eee34ef1cbde2d8798f79a3663950e42fbbc25fd1bd6f7/types-objgraph-3.6.0.20240907.tar.gz", hash = "sha256:2e3dee675843ae387889731550b0ddfed06e9420946cf78a4bca565b5fc53634", size = 2928, upload-time = "2024-09-07T02:35:21.214Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/16/c9/6d647a947f3937b19bcc6d52262921ddad60d90060ff66511a4bd7e990c5/types_objgraph-3.6.0.20240907-py3-none-any.whl", hash = "sha256:67207633a9b5789ee1911d740b269c3371081b79c0d8f68b00e7b8539f5c43f5", size = 3314, upload-time = "2024-09-07T02:35:19.865Z" }, -] - -[[package]] -name = "types-pexpect" -version = "4.9.0.20250516" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/92/a3/3943fcb94c12af29a88c346b588f1eda180b8b99aeb388a046b25072732c/types_pexpect-4.9.0.20250516.tar.gz", hash = "sha256:7baed9ee566fa24034a567cbec56a5cff189a021344e84383b14937b35d83881", size = 13285, upload-time = "2025-05-16T03:08:33.327Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/d4/3128ae3365b46b9c4a33202af79b0e0d9d4308a6348a3317ce2331fea6cb/types_pexpect-4.9.0.20250516-py3-none-any.whl", hash = "sha256:84cbd7ae9da577c0d2629d4e4fd53cf074cd012296e01fd4fa1031e01973c28a", size = 17081, upload-time = "2025-05-16T03:08:32.127Z" }, -] - -[[package]] -name = "types-protobuf" -version = "6.30.2.20250703" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/dc/54/d63ce1eee8e93c4d710bbe2c663ec68e3672cf4f2fca26eecd20981c0c5d/types_protobuf-6.30.2.20250703.tar.gz", hash = "sha256:609a974754bbb71fa178fc641f51050395e8e1849f49d0420a6281ed8d1ddf46", size = 62300, upload-time = "2025-07-03T03:14:05.74Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/2b/5d0377c3d6e0f49d4847ad2c40629593fee4a5c9ec56eba26a15c708fbc0/types_protobuf-6.30.2.20250703-py3-none-any.whl", hash = "sha256:fa5aff9036e9ef432d703abbdd801b436a249b6802e4df5ef74513e272434e57", size = 76489, upload-time = "2025-07-03T03:14:04.453Z" }, -] - -[[package]] -name = "types-psutil" -version = "7.0.0.20250601" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c8/af/767b92be7de4105f5e2e87a53aac817164527c4a802119ad5b4e23028f7c/types_psutil-7.0.0.20250601.tar.gz", hash = "sha256:71fe9c4477a7e3d4f1233862f0877af87bff057ff398f04f4e5c0ca60aded197", size = 20297, upload-time = "2025-06-01T03:25:16.698Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8d/85/864c663a924a34e0d87bd10ead4134bb4ab6269fa02daaa5dd644ac478c5/types_psutil-7.0.0.20250601-py3-none-any.whl", hash = "sha256:0c372e2d1b6529938a080a6ba4a9358e3dfc8526d82fabf40c1ef9325e4ca52e", size = 23106, upload-time = "2025-06-01T03:25:15.386Z" }, -] - -[[package]] -name = "types-pyasn1" -version = "0.6.0.20250516" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/01/9b/5c6e6690da87e9fec317925d4a9500a8d61c2e2c1ec39de46736f76a29e8/types_pyasn1-0.6.0.20250516.tar.gz", hash = "sha256:1a9b35a4f033cd70c384a5043a3407b2cc07afc95900732b66e0d38426c7541d", size = 17153, upload-time = "2025-05-16T03:07:23.961Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/56/0d10029d4d943226f044cec3f3d1f9ad54acb4884ee0b78e5d7b66c07d9d/types_pyasn1-0.6.0.20250516-py3-none-any.whl", hash = "sha256:b9925e4e22e09eed758b93b6f2a7881b89d842c2373dd11c09b173567d170142", size = 24093, upload-time = "2025-05-16T03:07:22.759Z" }, -] - -[[package]] -name = "types-pygments" -version = "2.19.0.20250715" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "types-docutils" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cb/e6/5ee8339d4122dccb08cf6b2d766221784960325767cf32a4b9e0aca08cd8/types_pygments-2.19.0.20250715.tar.gz", hash = "sha256:896e45cb5331492be0937b0e9cef976547dee5c507d546a1351289f3cea6eaac", size = 18491, upload-time = "2025-07-15T03:23:28.244Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ed/48/1b924b3cfbde33b296197615fa5aea5612b59369e5127e8302daec1ea165/types_pygments-2.19.0.20250715-py3-none-any.whl", hash = "sha256:90b45a484123727e125ffddf0379c627d88401dce768a2555038cb5be98b3f2c", size = 25451, upload-time = "2025-07-15T03:23:26.951Z" }, -] - -[[package]] -name = "types-pyopenssl" -version = "24.1.0.20240722" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cryptography" }, - { name = "types-cffi" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/93/29/47a346550fd2020dac9a7a6d033ea03fccb92fa47c726056618cc889745e/types-pyOpenSSL-24.1.0.20240722.tar.gz", hash = "sha256:47913b4678a01d879f503a12044468221ed8576263c1540dcb0484ca21b08c39", size = 8458, upload-time = "2024-07-22T02:32:22.558Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/98/05/c868a850b6fbb79c26f5f299b768ee0adc1f9816d3461dcf4287916f655b/types_pyOpenSSL-24.1.0.20240722-py3-none-any.whl", hash = "sha256:6a7a5d2ec042537934cfb4c9d4deb0e16c4c6250b09358df1f083682fe6fda54", size = 7499, upload-time = "2024-07-22T02:32:21.232Z" }, -] - -[[package]] -name = "types-pytz" -version = "2025.2.0.20250516" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/bd/72/b0e711fd90409f5a76c75349055d3eb19992c110f0d2d6aabbd6cfbc14bf/types_pytz-2025.2.0.20250516.tar.gz", hash = "sha256:e1216306f8c0d5da6dafd6492e72eb080c9a166171fa80dd7a1990fd8be7a7b3", size = 10940, upload-time = "2025-05-16T03:07:01.91Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/ba/e205cd11c1c7183b23c97e4bcd1de7bc0633e2e867601c32ecfc6ad42675/types_pytz-2025.2.0.20250516-py3-none-any.whl", hash = "sha256:e0e0c8a57e2791c19f718ed99ab2ba623856b11620cb6b637e5f62ce285a7451", size = 10136, upload-time = "2025-05-16T03:07:01.075Z" }, + { url = "https://files.pythonhosted.org/packages/ac/af/e3d20e3e81d235b3964846adf46a334645a8a9b25a0d3d472743eb079552/types_awscrt-0.27.6-py3-none-any.whl", hash = "sha256:18aced46da00a57f02eb97637a32e5894dc5aa3dc6a905ba3e5ed85b9f3c526b", size = 39626, upload-time = "2025-08-13T01:54:53.454Z" }, ] [[package]] name = "types-pyyaml" -version = "6.0.12.20250516" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4e/22/59e2aeb48ceeee1f7cd4537db9568df80d62bdb44a7f9e743502ea8aab9c/types_pyyaml-6.0.12.20250516.tar.gz", hash = "sha256:9f21a70216fc0fa1b216a8176db5f9e0af6eb35d2f2932acb87689d03a5bf6ba", size = 17378, upload-time = "2025-05-16T03:08:04.897Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/99/5f/e0af6f7f6a260d9af67e1db4f54d732abad514252a7a378a6c4d17dd1036/types_pyyaml-6.0.12.20250516-py3-none-any.whl", hash = "sha256:8478208feaeb53a34cb5d970c56a7cd76b72659442e733e268a94dc72b2d0530", size = 20312, upload-time = "2025-05-16T03:08:04.019Z" }, -] - -[[package]] -name = "types-regex" -version = "2024.11.6.20250403" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c7/75/012b90c8557d3abb3b58a9073a94d211c8f75c9b2e26bf0d8af7ecf7bc78/types_regex-2024.11.6.20250403.tar.gz", hash = "sha256:3fdf2a70bbf830de4b3a28e9649a52d43dabb57cdb18fbfe2252eefb53666665", size = 12394, upload-time = "2025-04-03T02:54:35.379Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/61/49/67200c4708f557be6aa4ecdb1fa212d67a10558c5240251efdc799cca22f/types_regex-2024.11.6.20250403-py3-none-any.whl", hash = "sha256:e22c0f67d73f4b4af6086a340f387b6f7d03bed8a0bb306224b75c51a29b0001", size = 10396, upload-time = "2025-04-03T02:54:34.555Z" }, -] - -[[package]] -name = "types-reportlab" -version = "4.4.1.20250602" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cd/ff/9b43c06f572bd0de79ce2c23795216746e7f990ad63a8902ab09882423c9/types_reportlab-4.4.1.20250602.tar.gz", hash = "sha256:ef26555384e9114e751ac6587740adcb4f241844cf103dd6f6f141d5254c1644", size = 68380, upload-time = "2025-06-02T03:14:44.544Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/54/ec7fa9b4ff7cd68ba338468bf1bb44d6a10c24acb489ff5100aaaa4b67f3/types_reportlab-4.4.1.20250602-py3-none-any.whl", hash = "sha256:35f23b5a3526fed8cbd5d611673551761280d129ec702467c1d11dd08b8fb971", size = 109189, upload-time = "2025-06-02T03:14:43.295Z" }, -] - -[[package]] -name = "types-requests" -version = "2.32.4.20250611" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "urllib3" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/6d/7f/73b3a04a53b0fd2a911d4ec517940ecd6600630b559e4505cc7b68beb5a0/types_requests-2.32.4.20250611.tar.gz", hash = "sha256:741c8777ed6425830bf51e54d6abe245f79b4dcb9019f1622b773463946bf826", size = 23118, upload-time = "2025-06-11T03:11:41.272Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/ea/0be9258c5a4fa1ba2300111aa5a0767ee6d18eb3fd20e91616c12082284d/types_requests-2.32.4.20250611-py3-none-any.whl", hash = "sha256:ad2fe5d3b0cb3c2c902c8815a70e7fb2302c4b8c1f77bdcd738192cdb3878072", size = 20643, upload-time = "2025-06-11T03:11:40.186Z" }, -] - -[[package]] -name = "types-setuptools" -version = "80.9.0.20250529" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/79/66/1b276526aad4696a9519919e637801f2c103419d2c248a6feb2729e034d1/types_setuptools-80.9.0.20250529.tar.gz", hash = "sha256:79e088ba0cba2186c8d6499cbd3e143abb142d28a44b042c28d3148b1e353c91", size = 41337, upload-time = "2025-05-29T03:07:34.487Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1b/d8/83790d67ec771bf029a45ff1bd1aedbb738d8aa58c09dd0cc3033eea0e69/types_setuptools-80.9.0.20250529-py3-none-any.whl", hash = "sha256:00dfcedd73e333a430e10db096e4d46af93faf9314f832f13b6bbe3d6757e95f", size = 63263, upload-time = "2025-05-29T03:07:33.064Z" }, -] - -[[package]] -name = "types-simplejson" -version = "3.20.0.20250326" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/14/e26fc55e1ea56f9ea470917d3e2f8240e6d043ca914181021d04115ae0f7/types_simplejson-3.20.0.20250326.tar.gz", hash = "sha256:b2689bc91e0e672d7a5a947b4cb546b76ae7ddc2899c6678e72a10bf96cd97d2", size = 10489, upload-time = "2025-03-26T02:53:35.825Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/76/bf/d3f3a5ba47fd18115e8446d39f025b85905d2008677c29ee4d03b4cddd57/types_simplejson-3.20.0.20250326-py3-none-any.whl", hash = "sha256:db1ddea7b8f7623b27a137578f22fc6c618db8c83ccfb1828ca0d2f0ec11efa7", size = 10462, upload-time = "2025-03-26T02:53:35.036Z" }, -] - -[[package]] -name = "types-tensorflow" -version = "2.18.0.20250516" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy" }, - { name = "types-protobuf" }, - { name = "types-requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/4b/18/b726d886e7af565c4439d2c8d32e510651be40807e2a66aaea2ed75d7c82/types_tensorflow-2.18.0.20250516.tar.gz", hash = "sha256:5777e1848e52b1f4a87b44ce1ec738b7407a744669bab87ec0f5f1e0ce6bd1fe", size = 257705, upload-time = "2025-05-16T03:09:41.222Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/96/fd/0d8fbc7172fa7cca345c61a949952df8906f6da161dfbb4305c670aeabad/types_tensorflow-2.18.0.20250516-py3-none-any.whl", hash = "sha256:e8681f8c2a60f87f562df1472790c1e930895e7e463c4c65d1be98d8d908e45e", size = 329211, upload-time = "2025-05-16T03:09:40.111Z" }, -] - -[[package]] -name = "types-tqdm" -version = "4.67.0.20250516" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "types-requests" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bd/07/eb40de2dc2ff2d1a53180330981b1bdb42313ab4e1b11195d8d64c878b3c/types_tqdm-4.67.0.20250516.tar.gz", hash = "sha256:230ccab8a332d34f193fc007eb132a6ef54b4512452e718bf21ae0a7caeb5a6b", size = 17232, upload-time = "2025-05-16T03:09:52.091Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/92/df621429f098fc573a63a8ba348e731c3051b397df0cff278f8887f28d24/types_tqdm-4.67.0.20250516-py3-none-any.whl", hash = "sha256:1dd9b2c65273f2342f37e5179bc6982df86b6669b3376efc12aef0a29e35d36d", size = 24032, upload-time = "2025-05-16T03:09:51.226Z" }, -] - -[[package]] -name = "types-tree-sitter-languages" -version = "1.10.0.20250530" +version = "6.0.12.20250809" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "tree-sitter" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/e6/ea/c487b80120f909fe31115fbc9dfc49a3d9f7c7d5dc2193e507e717355d7e/types_tree_sitter_languages-1.10.0.20250530.tar.gz", hash = "sha256:c7350aeff95f84a7fb0ac90ddb615233db1459a3d9305bab304075462ffe810c", size = 7939, upload-time = "2025-05-30T17:05:41.235Z" } +sdist = { url = "https://files.pythonhosted.org/packages/36/21/52ffdbddea3c826bc2758d811ccd7f766912de009c5cf096bd5ebba44680/types_pyyaml-6.0.12.20250809.tar.gz", hash = "sha256:af4a1aca028f18e75297da2ee0da465f799627370d74073e96fee876524f61b5", size = 17385, upload-time = "2025-08-09T03:14:34.867Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/70/4f/83922e3cd6415b399c2c83ce54966af143e423e1e5585decd90af2994716/types_tree_sitter_languages-1.10.0.20250530-py3-none-any.whl", hash = "sha256:06be82a29befc1b891b31672e91b979a0d1d8f148d12715172efbd380c520af3", size = 8042, upload-time = "2025-05-30T17:05:40.123Z" }, + { url = "https://files.pythonhosted.org/packages/35/3e/0346d09d6e338401ebf406f12eaf9d0b54b315b86f1ec29e34f1a0aedae9/types_pyyaml-6.0.12.20250809-py3-none-any.whl", hash = "sha256:032b6003b798e7de1a1ddfeefee32fac6486bdfe4845e0ae0e7fb3ee4512b52f", size = 20277, upload-time = "2025-08-09T03:14:34.055Z" }, ] [[package]] -name = "types-xmltodict" -version = "0.14.0.20241009" +name = "types-s3transfer" +version = "0.13.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/05/28/f1c86d869d74342553c74bd88d6f615427b544fd06f30fdaa68cb4369cd1/types-xmltodict-0.14.0.20241009.tar.gz", hash = "sha256:9224c2422c5b6359cf826685b4ee50b14dc2cb9134561ab793ef6b03dd7108e1", size = 3224, upload-time = "2024-10-09T02:43:18.364Z" } +sdist = { url = "https://files.pythonhosted.org/packages/42/c1/45038f259d6741c252801044e184fec4dbaeff939a58f6160d7c32bf4975/types_s3transfer-0.13.0.tar.gz", hash = "sha256:203dadcb9865c2f68fb44bc0440e1dc05b79197ba4a641c0976c26c9af75ef52", size = 14175, upload-time = "2025-05-28T02:16:07.614Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/15/5e/6d81048a06f956ccec74c61fe5c30784e6a865876dc4ab02e99099e4ee0c/types_xmltodict-0.14.0.20241009-py3-none-any.whl", hash = "sha256:92812e17ffa9171416b35806cb5f4ed3f8f52b6724b2c555e4733e902ef4afd0", size = 2859, upload-time = "2024-10-09T02:43:16.966Z" }, + { url = "https://files.pythonhosted.org/packages/c8/5d/6bbe4bf6a79fb727945291aef88b5ecbdba857a603f1bbcf1a6be0d3f442/types_s3transfer-0.13.0-py3-none-any.whl", hash = "sha256:79c8375cbf48a64bff7654c02df1ec4b20d74f8c5672fc13e382f593ca5565b3", size = 19588, upload-time = "2025-05-28T02:16:06.709Z" }, ] [[package]] @@ -4160,148 +3483,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d2/e2/dc81b1bd1dcfe91735810265e9d26bc8ec5da45b4c0f6237e286819194c3/uvicorn-0.35.0-py3-none-any.whl", hash = "sha256:197535216b25ff9b785e29a0b79199f55222193d47f820816e7da751e9bc8d4a", size = 66406, upload-time = "2025-06-28T16:15:44.816Z" }, ] -[package.optional-dependencies] -standard = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "httptools" }, - { name = "python-dotenv" }, - { name = "pyyaml" }, - { name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" }, - { name = "watchfiles" }, - { name = "websockets" }, -] - -[[package]] -name = "uvloop" -version = "0.21.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/c0/854216d09d33c543f12a44b393c402e89a920b1a0a7dc634c42de91b9cf6/uvloop-0.21.0.tar.gz", hash = "sha256:3bf12b0fda68447806a7ad847bfa591613177275d35b6724b1ee573faa3704e3", size = 2492741, upload-time = "2024-10-14T23:38:35.489Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/57/a7/4cf0334105c1160dd6819f3297f8700fda7fc30ab4f61fbf3e725acbc7cc/uvloop-0.21.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c0f3fa6200b3108919f8bdabb9a7f87f20e7097ea3c543754cabc7d717d95cf8", size = 1447410, upload-time = "2024-10-14T23:37:33.612Z" }, - { url = "https://files.pythonhosted.org/packages/8c/7c/1517b0bbc2dbe784b563d6ab54f2ef88c890fdad77232c98ed490aa07132/uvloop-0.21.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0878c2640cf341b269b7e128b1a5fed890adc4455513ca710d77d5e93aa6d6a0", size = 805476, upload-time = "2024-10-14T23:37:36.11Z" }, - { url = "https://files.pythonhosted.org/packages/ee/ea/0bfae1aceb82a503f358d8d2fa126ca9dbdb2ba9c7866974faec1cb5875c/uvloop-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9fb766bb57b7388745d8bcc53a359b116b8a04c83a2288069809d2b3466c37e", size = 3960855, upload-time = "2024-10-14T23:37:37.683Z" }, - { url = "https://files.pythonhosted.org/packages/8a/ca/0864176a649838b838f36d44bf31c451597ab363b60dc9e09c9630619d41/uvloop-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a375441696e2eda1c43c44ccb66e04d61ceeffcd76e4929e527b7fa401b90fb", size = 3973185, upload-time = "2024-10-14T23:37:40.226Z" }, - { url = "https://files.pythonhosted.org/packages/30/bf/08ad29979a936d63787ba47a540de2132169f140d54aa25bc8c3df3e67f4/uvloop-0.21.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:baa0e6291d91649c6ba4ed4b2f982f9fa165b5bbd50a9e203c416a2797bab3c6", size = 3820256, upload-time = "2024-10-14T23:37:42.839Z" }, - { url = "https://files.pythonhosted.org/packages/da/e2/5cf6ef37e3daf2f06e651aae5ea108ad30df3cb269102678b61ebf1fdf42/uvloop-0.21.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4509360fcc4c3bd2c70d87573ad472de40c13387f5fda8cb58350a1d7475e58d", size = 3937323, upload-time = "2024-10-14T23:37:45.337Z" }, - { url = "https://files.pythonhosted.org/packages/8c/4c/03f93178830dc7ce8b4cdee1d36770d2f5ebb6f3d37d354e061eefc73545/uvloop-0.21.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:359ec2c888397b9e592a889c4d72ba3d6befba8b2bb01743f72fffbde663b59c", size = 1471284, upload-time = "2024-10-14T23:37:47.833Z" }, - { url = "https://files.pythonhosted.org/packages/43/3e/92c03f4d05e50f09251bd8b2b2b584a2a7f8fe600008bcc4523337abe676/uvloop-0.21.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f7089d2dc73179ce5ac255bdf37c236a9f914b264825fdaacaded6990a7fb4c2", size = 821349, upload-time = "2024-10-14T23:37:50.149Z" }, - { url = "https://files.pythonhosted.org/packages/a6/ef/a02ec5da49909dbbfb1fd205a9a1ac4e88ea92dcae885e7c961847cd51e2/uvloop-0.21.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:baa4dcdbd9ae0a372f2167a207cd98c9f9a1ea1188a8a526431eef2f8116cc8d", size = 4580089, upload-time = "2024-10-14T23:37:51.703Z" }, - { url = "https://files.pythonhosted.org/packages/06/a7/b4e6a19925c900be9f98bec0a75e6e8f79bb53bdeb891916609ab3958967/uvloop-0.21.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:86975dca1c773a2c9864f4c52c5a55631038e387b47eaf56210f873887b6c8dc", size = 4693770, upload-time = "2024-10-14T23:37:54.122Z" }, - { url = "https://files.pythonhosted.org/packages/ce/0c/f07435a18a4b94ce6bd0677d8319cd3de61f3a9eeb1e5f8ab4e8b5edfcb3/uvloop-0.21.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:461d9ae6660fbbafedd07559c6a2e57cd553b34b0065b6550685f6653a98c1cb", size = 4451321, upload-time = "2024-10-14T23:37:55.766Z" }, - { url = "https://files.pythonhosted.org/packages/8f/eb/f7032be105877bcf924709c97b1bf3b90255b4ec251f9340cef912559f28/uvloop-0.21.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:183aef7c8730e54c9a3ee3227464daed66e37ba13040bb3f350bc2ddc040f22f", size = 4659022, upload-time = "2024-10-14T23:37:58.195Z" }, - { url = "https://files.pythonhosted.org/packages/3f/8d/2cbef610ca21539f0f36e2b34da49302029e7c9f09acef0b1c3b5839412b/uvloop-0.21.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:bfd55dfcc2a512316e65f16e503e9e450cab148ef11df4e4e679b5e8253a5281", size = 1468123, upload-time = "2024-10-14T23:38:00.688Z" }, - { url = "https://files.pythonhosted.org/packages/93/0d/b0038d5a469f94ed8f2b2fce2434a18396d8fbfb5da85a0a9781ebbdec14/uvloop-0.21.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:787ae31ad8a2856fc4e7c095341cccc7209bd657d0e71ad0dc2ea83c4a6fa8af", size = 819325, upload-time = "2024-10-14T23:38:02.309Z" }, - { url = "https://files.pythonhosted.org/packages/50/94/0a687f39e78c4c1e02e3272c6b2ccdb4e0085fda3b8352fecd0410ccf915/uvloop-0.21.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ee4d4ef48036ff6e5cfffb09dd192c7a5027153948d85b8da7ff705065bacc6", size = 4582806, upload-time = "2024-10-14T23:38:04.711Z" }, - { url = "https://files.pythonhosted.org/packages/d2/19/f5b78616566ea68edd42aacaf645adbf71fbd83fc52281fba555dc27e3f1/uvloop-0.21.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3df876acd7ec037a3d005b3ab85a7e4110422e4d9c1571d4fc89b0fc41b6816", size = 4701068, upload-time = "2024-10-14T23:38:06.385Z" }, - { url = "https://files.pythonhosted.org/packages/47/57/66f061ee118f413cd22a656de622925097170b9380b30091b78ea0c6ea75/uvloop-0.21.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd53ecc9a0f3d87ab847503c2e1552b690362e005ab54e8a48ba97da3924c0dc", size = 4454428, upload-time = "2024-10-14T23:38:08.416Z" }, - { url = "https://files.pythonhosted.org/packages/63/9a/0962b05b308494e3202d3f794a6e85abe471fe3cafdbcf95c2e8c713aabd/uvloop-0.21.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a5c39f217ab3c663dc699c04cbd50c13813e31d917642d459fdcec07555cc553", size = 4660018, upload-time = "2024-10-14T23:38:10.888Z" }, -] - [[package]] name = "virtualenv" -version = "20.31.2" +version = "20.34.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, { name = "filelock" }, { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/56/2c/444f465fb2c65f40c3a104fd0c495184c4f2336d65baf398e3c75d72ea94/virtualenv-20.31.2.tar.gz", hash = "sha256:e10c0a9d02835e592521be48b332b6caee6887f332c111aa79a09b9e79efc2af", size = 6076316, upload-time = "2025-05-08T17:58:23.811Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/14/37fcdba2808a6c615681cd216fecae00413c9dab44fb2e57805ecf3eaee3/virtualenv-20.34.0.tar.gz", hash = "sha256:44815b2c9dee7ed86e387b842a84f20b93f7f417f95886ca1996a72a4138eb1a", size = 6003808, upload-time = "2025-08-13T14:24:07.464Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/40/b1c265d4b2b62b58576588510fc4d1fe60a86319c8de99fd8e9fec617d2c/virtualenv-20.31.2-py3-none-any.whl", hash = "sha256:36efd0d9650ee985f0cad72065001e66d49a6f24eb44d98980f630686243cf11", size = 6057982, upload-time = "2025-05-08T17:58:21.15Z" }, -] - -[[package]] -name = "watchfiles" -version = "1.1.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "anyio" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/2a/9a/d451fcc97d029f5812e898fd30a53fd8c15c7bbd058fd75cfc6beb9bd761/watchfiles-1.1.0.tar.gz", hash = "sha256:693ed7ec72cbfcee399e92c895362b6e66d63dac6b91e2c11ae03d10d503e575", size = 94406, upload-time = "2025-06-15T19:06:59.42Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/8b/78/7401154b78ab484ccaaeef970dc2af0cb88b5ba8a1b415383da444cdd8d3/watchfiles-1.1.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:c9649dfc57cc1f9835551deb17689e8d44666315f2e82d337b9f07bd76ae3aa2", size = 405751, upload-time = "2025-06-15T19:05:07.679Z" }, - { url = "https://files.pythonhosted.org/packages/76/63/e6c3dbc1f78d001589b75e56a288c47723de28c580ad715eb116639152b5/watchfiles-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:406520216186b99374cdb58bc48e34bb74535adec160c8459894884c983a149c", size = 397313, upload-time = "2025-06-15T19:05:08.764Z" }, - { url = "https://files.pythonhosted.org/packages/6c/a2/8afa359ff52e99af1632f90cbf359da46184207e893a5f179301b0c8d6df/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb45350fd1dc75cd68d3d72c47f5b513cb0578da716df5fba02fff31c69d5f2d", size = 450792, upload-time = "2025-06-15T19:05:09.869Z" }, - { url = "https://files.pythonhosted.org/packages/1d/bf/7446b401667f5c64972a57a0233be1104157fc3abf72c4ef2666c1bd09b2/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:11ee4444250fcbeb47459a877e5e80ed994ce8e8d20283857fc128be1715dac7", size = 458196, upload-time = "2025-06-15T19:05:11.91Z" }, - { url = "https://files.pythonhosted.org/packages/58/2f/501ddbdfa3fa874ea5597c77eeea3d413579c29af26c1091b08d0c792280/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bda8136e6a80bdea23e5e74e09df0362744d24ffb8cd59c4a95a6ce3d142f79c", size = 484788, upload-time = "2025-06-15T19:05:13.373Z" }, - { url = "https://files.pythonhosted.org/packages/61/1e/9c18eb2eb5c953c96bc0e5f626f0e53cfef4bd19bd50d71d1a049c63a575/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b915daeb2d8c1f5cee4b970f2e2c988ce6514aace3c9296e58dd64dc9aa5d575", size = 597879, upload-time = "2025-06-15T19:05:14.725Z" }, - { url = "https://files.pythonhosted.org/packages/8b/6c/1467402e5185d89388b4486745af1e0325007af0017c3384cc786fff0542/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed8fc66786de8d0376f9f913c09e963c66e90ced9aa11997f93bdb30f7c872a8", size = 477447, upload-time = "2025-06-15T19:05:15.775Z" }, - { url = "https://files.pythonhosted.org/packages/2b/a1/ec0a606bde4853d6c4a578f9391eeb3684a9aea736a8eb217e3e00aa89a1/watchfiles-1.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe4371595edf78c41ef8ac8df20df3943e13defd0efcb732b2e393b5a8a7a71f", size = 453145, upload-time = "2025-06-15T19:05:17.17Z" }, - { url = "https://files.pythonhosted.org/packages/90/b9/ef6f0c247a6a35d689fc970dc7f6734f9257451aefb30def5d100d6246a5/watchfiles-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b7c5f6fe273291f4d414d55b2c80d33c457b8a42677ad14b4b47ff025d0893e4", size = 626539, upload-time = "2025-06-15T19:05:18.557Z" }, - { url = "https://files.pythonhosted.org/packages/34/44/6ffda5537085106ff5aaa762b0d130ac6c75a08015dd1621376f708c94de/watchfiles-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7738027989881e70e3723c75921f1efa45225084228788fc59ea8c6d732eb30d", size = 624472, upload-time = "2025-06-15T19:05:19.588Z" }, - { url = "https://files.pythonhosted.org/packages/c3/e3/71170985c48028fa3f0a50946916a14055e741db11c2e7bc2f3b61f4d0e3/watchfiles-1.1.0-cp311-cp311-win32.whl", hash = "sha256:622d6b2c06be19f6e89b1d951485a232e3b59618def88dbeda575ed8f0d8dbf2", size = 279348, upload-time = "2025-06-15T19:05:20.856Z" }, - { url = "https://files.pythonhosted.org/packages/89/1b/3e39c68b68a7a171070f81fc2561d23ce8d6859659406842a0e4bebf3bba/watchfiles-1.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:48aa25e5992b61debc908a61ab4d3f216b64f44fdaa71eb082d8b2de846b7d12", size = 292607, upload-time = "2025-06-15T19:05:21.937Z" }, - { url = "https://files.pythonhosted.org/packages/61/9f/2973b7539f2bdb6ea86d2c87f70f615a71a1fc2dba2911795cea25968aea/watchfiles-1.1.0-cp311-cp311-win_arm64.whl", hash = "sha256:00645eb79a3faa70d9cb15c8d4187bb72970b2470e938670240c7998dad9f13a", size = 285056, upload-time = "2025-06-15T19:05:23.12Z" }, - { url = "https://files.pythonhosted.org/packages/f6/b8/858957045a38a4079203a33aaa7d23ea9269ca7761c8a074af3524fbb240/watchfiles-1.1.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9dc001c3e10de4725c749d4c2f2bdc6ae24de5a88a339c4bce32300a31ede179", size = 402339, upload-time = "2025-06-15T19:05:24.516Z" }, - { url = "https://files.pythonhosted.org/packages/80/28/98b222cca751ba68e88521fabd79a4fab64005fc5976ea49b53fa205d1fa/watchfiles-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d9ba68ec283153dead62cbe81872d28e053745f12335d037de9cbd14bd1877f5", size = 394409, upload-time = "2025-06-15T19:05:25.469Z" }, - { url = "https://files.pythonhosted.org/packages/86/50/dee79968566c03190677c26f7f47960aff738d32087087bdf63a5473e7df/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:130fc497b8ee68dce163e4254d9b0356411d1490e868bd8790028bc46c5cc297", size = 450939, upload-time = "2025-06-15T19:05:26.494Z" }, - { url = "https://files.pythonhosted.org/packages/40/45/a7b56fb129700f3cfe2594a01aa38d033b92a33dddce86c8dfdfc1247b72/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:50a51a90610d0845a5931a780d8e51d7bd7f309ebc25132ba975aca016b576a0", size = 457270, upload-time = "2025-06-15T19:05:27.466Z" }, - { url = "https://files.pythonhosted.org/packages/b5/c8/fa5ef9476b1d02dc6b5e258f515fcaaecf559037edf8b6feffcbc097c4b8/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc44678a72ac0910bac46fa6a0de6af9ba1355669b3dfaf1ce5f05ca7a74364e", size = 483370, upload-time = "2025-06-15T19:05:28.548Z" }, - { url = "https://files.pythonhosted.org/packages/98/68/42cfcdd6533ec94f0a7aab83f759ec11280f70b11bfba0b0f885e298f9bd/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a543492513a93b001975ae283a51f4b67973662a375a403ae82f420d2c7205ee", size = 598654, upload-time = "2025-06-15T19:05:29.997Z" }, - { url = "https://files.pythonhosted.org/packages/d3/74/b2a1544224118cc28df7e59008a929e711f9c68ce7d554e171b2dc531352/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ac164e20d17cc285f2b94dc31c384bc3aa3dd5e7490473b3db043dd70fbccfd", size = 478667, upload-time = "2025-06-15T19:05:31.172Z" }, - { url = "https://files.pythonhosted.org/packages/8c/77/e3362fe308358dc9f8588102481e599c83e1b91c2ae843780a7ded939a35/watchfiles-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f7590d5a455321e53857892ab8879dce62d1f4b04748769f5adf2e707afb9d4f", size = 452213, upload-time = "2025-06-15T19:05:32.299Z" }, - { url = "https://files.pythonhosted.org/packages/6e/17/c8f1a36540c9a1558d4faf08e909399e8133599fa359bf52ec8fcee5be6f/watchfiles-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:37d3d3f7defb13f62ece99e9be912afe9dd8a0077b7c45ee5a57c74811d581a4", size = 626718, upload-time = "2025-06-15T19:05:33.415Z" }, - { url = "https://files.pythonhosted.org/packages/26/45/fb599be38b4bd38032643783d7496a26a6f9ae05dea1a42e58229a20ac13/watchfiles-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7080c4bb3efd70a07b1cc2df99a7aa51d98685be56be6038c3169199d0a1c69f", size = 623098, upload-time = "2025-06-15T19:05:34.534Z" }, - { url = "https://files.pythonhosted.org/packages/a1/e7/fdf40e038475498e160cd167333c946e45d8563ae4dd65caf757e9ffe6b4/watchfiles-1.1.0-cp312-cp312-win32.whl", hash = "sha256:cbcf8630ef4afb05dc30107bfa17f16c0896bb30ee48fc24bf64c1f970f3b1fd", size = 279209, upload-time = "2025-06-15T19:05:35.577Z" }, - { url = "https://files.pythonhosted.org/packages/3f/d3/3ae9d5124ec75143bdf088d436cba39812122edc47709cd2caafeac3266f/watchfiles-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:cbd949bdd87567b0ad183d7676feb98136cde5bb9025403794a4c0db28ed3a47", size = 292786, upload-time = "2025-06-15T19:05:36.559Z" }, - { url = "https://files.pythonhosted.org/packages/26/2f/7dd4fc8b5f2b34b545e19629b4a018bfb1de23b3a496766a2c1165ca890d/watchfiles-1.1.0-cp312-cp312-win_arm64.whl", hash = "sha256:0a7d40b77f07be87c6faa93d0951a0fcd8cbca1ddff60a1b65d741bac6f3a9f6", size = 284343, upload-time = "2025-06-15T19:05:37.5Z" }, - { url = "https://files.pythonhosted.org/packages/d3/42/fae874df96595556a9089ade83be34a2e04f0f11eb53a8dbf8a8a5e562b4/watchfiles-1.1.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:5007f860c7f1f8df471e4e04aaa8c43673429047d63205d1630880f7637bca30", size = 402004, upload-time = "2025-06-15T19:05:38.499Z" }, - { url = "https://files.pythonhosted.org/packages/fa/55/a77e533e59c3003d9803c09c44c3651224067cbe7fb5d574ddbaa31e11ca/watchfiles-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:20ecc8abbd957046f1fe9562757903f5eaf57c3bce70929fda6c7711bb58074a", size = 393671, upload-time = "2025-06-15T19:05:39.52Z" }, - { url = "https://files.pythonhosted.org/packages/05/68/b0afb3f79c8e832e6571022611adbdc36e35a44e14f129ba09709aa4bb7a/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2f0498b7d2a3c072766dba3274fe22a183dbea1f99d188f1c6c72209a1063dc", size = 449772, upload-time = "2025-06-15T19:05:40.897Z" }, - { url = "https://files.pythonhosted.org/packages/ff/05/46dd1f6879bc40e1e74c6c39a1b9ab9e790bf1f5a2fe6c08b463d9a807f4/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:239736577e848678e13b201bba14e89718f5c2133dfd6b1f7846fa1b58a8532b", size = 456789, upload-time = "2025-06-15T19:05:42.045Z" }, - { url = "https://files.pythonhosted.org/packages/8b/ca/0eeb2c06227ca7f12e50a47a3679df0cd1ba487ea19cf844a905920f8e95/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eff4b8d89f444f7e49136dc695599a591ff769300734446c0a86cba2eb2f9895", size = 482551, upload-time = "2025-06-15T19:05:43.781Z" }, - { url = "https://files.pythonhosted.org/packages/31/47/2cecbd8694095647406645f822781008cc524320466ea393f55fe70eed3b/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12b0a02a91762c08f7264e2e79542f76870c3040bbc847fb67410ab81474932a", size = 597420, upload-time = "2025-06-15T19:05:45.244Z" }, - { url = "https://files.pythonhosted.org/packages/d9/7e/82abc4240e0806846548559d70f0b1a6dfdca75c1b4f9fa62b504ae9b083/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:29e7bc2eee15cbb339c68445959108803dc14ee0c7b4eea556400131a8de462b", size = 477950, upload-time = "2025-06-15T19:05:46.332Z" }, - { url = "https://files.pythonhosted.org/packages/25/0d/4d564798a49bf5482a4fa9416dea6b6c0733a3b5700cb8a5a503c4b15853/watchfiles-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9481174d3ed982e269c090f780122fb59cee6c3796f74efe74e70f7780ed94c", size = 451706, upload-time = "2025-06-15T19:05:47.459Z" }, - { url = "https://files.pythonhosted.org/packages/81/b5/5516cf46b033192d544102ea07c65b6f770f10ed1d0a6d388f5d3874f6e4/watchfiles-1.1.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:80f811146831c8c86ab17b640801c25dc0a88c630e855e2bef3568f30434d52b", size = 625814, upload-time = "2025-06-15T19:05:48.654Z" }, - { url = "https://files.pythonhosted.org/packages/0c/dd/7c1331f902f30669ac3e754680b6edb9a0dd06dea5438e61128111fadd2c/watchfiles-1.1.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:60022527e71d1d1fda67a33150ee42869042bce3d0fcc9cc49be009a9cded3fb", size = 622820, upload-time = "2025-06-15T19:05:50.088Z" }, - { url = "https://files.pythonhosted.org/packages/1b/14/36d7a8e27cd128d7b1009e7715a7c02f6c131be9d4ce1e5c3b73d0e342d8/watchfiles-1.1.0-cp313-cp313-win32.whl", hash = "sha256:32d6d4e583593cb8576e129879ea0991660b935177c0f93c6681359b3654bfa9", size = 279194, upload-time = "2025-06-15T19:05:51.186Z" }, - { url = "https://files.pythonhosted.org/packages/25/41/2dd88054b849aa546dbeef5696019c58f8e0774f4d1c42123273304cdb2e/watchfiles-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:f21af781a4a6fbad54f03c598ab620e3a77032c5878f3d780448421a6e1818c7", size = 292349, upload-time = "2025-06-15T19:05:52.201Z" }, - { url = "https://files.pythonhosted.org/packages/c8/cf/421d659de88285eb13941cf11a81f875c176f76a6d99342599be88e08d03/watchfiles-1.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:5366164391873ed76bfdf618818c82084c9db7fac82b64a20c44d335eec9ced5", size = 283836, upload-time = "2025-06-15T19:05:53.265Z" }, - { url = "https://files.pythonhosted.org/packages/45/10/6faf6858d527e3599cc50ec9fcae73590fbddc1420bd4fdccfebffeedbc6/watchfiles-1.1.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:17ab167cca6339c2b830b744eaf10803d2a5b6683be4d79d8475d88b4a8a4be1", size = 400343, upload-time = "2025-06-15T19:05:54.252Z" }, - { url = "https://files.pythonhosted.org/packages/03/20/5cb7d3966f5e8c718006d0e97dfe379a82f16fecd3caa7810f634412047a/watchfiles-1.1.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:328dbc9bff7205c215a7807da7c18dce37da7da718e798356212d22696404339", size = 392916, upload-time = "2025-06-15T19:05:55.264Z" }, - { url = "https://files.pythonhosted.org/packages/8c/07/d8f1176328fa9e9581b6f120b017e286d2a2d22ae3f554efd9515c8e1b49/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7208ab6e009c627b7557ce55c465c98967e8caa8b11833531fdf95799372633", size = 449582, upload-time = "2025-06-15T19:05:56.317Z" }, - { url = "https://files.pythonhosted.org/packages/66/e8/80a14a453cf6038e81d072a86c05276692a1826471fef91df7537dba8b46/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a8f6f72974a19efead54195bc9bed4d850fc047bb7aa971268fd9a8387c89011", size = 456752, upload-time = "2025-06-15T19:05:57.359Z" }, - { url = "https://files.pythonhosted.org/packages/5a/25/0853b3fe0e3c2f5af9ea60eb2e781eade939760239a72c2d38fc4cc335f6/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d181ef50923c29cf0450c3cd47e2f0557b62218c50b2ab8ce2ecaa02bd97e670", size = 481436, upload-time = "2025-06-15T19:05:58.447Z" }, - { url = "https://files.pythonhosted.org/packages/fe/9e/4af0056c258b861fbb29dcb36258de1e2b857be4a9509e6298abcf31e5c9/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:adb4167043d3a78280d5d05ce0ba22055c266cf8655ce942f2fb881262ff3cdf", size = 596016, upload-time = "2025-06-15T19:05:59.59Z" }, - { url = "https://files.pythonhosted.org/packages/c5/fa/95d604b58aa375e781daf350897aaaa089cff59d84147e9ccff2447c8294/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c5701dc474b041e2934a26d31d39f90fac8a3dee2322b39f7729867f932b1d4", size = 476727, upload-time = "2025-06-15T19:06:01.086Z" }, - { url = "https://files.pythonhosted.org/packages/65/95/fe479b2664f19be4cf5ceeb21be05afd491d95f142e72d26a42f41b7c4f8/watchfiles-1.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b067915e3c3936966a8607f6fe5487df0c9c4afb85226613b520890049deea20", size = 451864, upload-time = "2025-06-15T19:06:02.144Z" }, - { url = "https://files.pythonhosted.org/packages/d3/8a/3c4af14b93a15ce55901cd7a92e1a4701910f1768c78fb30f61d2b79785b/watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:9c733cda03b6d636b4219625a4acb5c6ffb10803338e437fb614fef9516825ef", size = 625626, upload-time = "2025-06-15T19:06:03.578Z" }, - { url = "https://files.pythonhosted.org/packages/da/f5/cf6aa047d4d9e128f4b7cde615236a915673775ef171ff85971d698f3c2c/watchfiles-1.1.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:cc08ef8b90d78bfac66f0def80240b0197008e4852c9f285907377b2947ffdcb", size = 622744, upload-time = "2025-06-15T19:06:05.066Z" }, - { url = "https://files.pythonhosted.org/packages/2c/00/70f75c47f05dea6fd30df90f047765f6fc2d6eb8b5a3921379b0b04defa2/watchfiles-1.1.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:9974d2f7dc561cce3bb88dfa8eb309dab64c729de85fba32e98d75cf24b66297", size = 402114, upload-time = "2025-06-15T19:06:06.186Z" }, - { url = "https://files.pythonhosted.org/packages/53/03/acd69c48db4a1ed1de26b349d94077cca2238ff98fd64393f3e97484cae6/watchfiles-1.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c68e9f1fcb4d43798ad8814c4c1b61547b014b667216cb754e606bfade587018", size = 393879, upload-time = "2025-06-15T19:06:07.369Z" }, - { url = "https://files.pythonhosted.org/packages/2f/c8/a9a2a6f9c8baa4eceae5887fecd421e1b7ce86802bcfc8b6a942e2add834/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95ab1594377effac17110e1352989bdd7bdfca9ff0e5eeccd8c69c5389b826d0", size = 450026, upload-time = "2025-06-15T19:06:08.476Z" }, - { url = "https://files.pythonhosted.org/packages/fe/51/d572260d98388e6e2b967425c985e07d47ee6f62e6455cefb46a6e06eda5/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fba9b62da882c1be1280a7584ec4515d0a6006a94d6e5819730ec2eab60ffe12", size = 457917, upload-time = "2025-06-15T19:06:09.988Z" }, - { url = "https://files.pythonhosted.org/packages/c6/2d/4258e52917bf9f12909b6ec314ff9636276f3542f9d3807d143f27309104/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3434e401f3ce0ed6b42569128b3d1e3af773d7ec18751b918b89cd49c14eaafb", size = 483602, upload-time = "2025-06-15T19:06:11.088Z" }, - { url = "https://files.pythonhosted.org/packages/84/99/bee17a5f341a4345fe7b7972a475809af9e528deba056f8963d61ea49f75/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa257a4d0d21fcbca5b5fcba9dca5a78011cb93c0323fb8855c6d2dfbc76eb77", size = 596758, upload-time = "2025-06-15T19:06:12.197Z" }, - { url = "https://files.pythonhosted.org/packages/40/76/e4bec1d59b25b89d2b0716b41b461ed655a9a53c60dc78ad5771fda5b3e6/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7fd1b3879a578a8ec2076c7961076df540b9af317123f84569f5a9ddee64ce92", size = 477601, upload-time = "2025-06-15T19:06:13.391Z" }, - { url = "https://files.pythonhosted.org/packages/1f/fa/a514292956f4a9ce3c567ec0c13cce427c158e9f272062685a8a727d08fc/watchfiles-1.1.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62cc7a30eeb0e20ecc5f4bd113cd69dcdb745a07c68c0370cea919f373f65d9e", size = 451936, upload-time = "2025-06-15T19:06:14.656Z" }, - { url = "https://files.pythonhosted.org/packages/32/5d/c3bf927ec3bbeb4566984eba8dd7a8eb69569400f5509904545576741f88/watchfiles-1.1.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:891c69e027748b4a73847335d208e374ce54ca3c335907d381fde4e41661b13b", size = 626243, upload-time = "2025-06-15T19:06:16.232Z" }, - { url = "https://files.pythonhosted.org/packages/e6/65/6e12c042f1a68c556802a84d54bb06d35577c81e29fba14019562479159c/watchfiles-1.1.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:12fe8eaffaf0faa7906895b4f8bb88264035b3f0243275e0bf24af0436b27259", size = 623073, upload-time = "2025-06-15T19:06:17.457Z" }, - { url = "https://files.pythonhosted.org/packages/89/ab/7f79d9bf57329e7cbb0a6fd4c7bd7d0cee1e4a8ef0041459f5409da3506c/watchfiles-1.1.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:bfe3c517c283e484843cb2e357dd57ba009cff351edf45fb455b5fbd1f45b15f", size = 400872, upload-time = "2025-06-15T19:06:18.57Z" }, - { url = "https://files.pythonhosted.org/packages/df/d5/3f7bf9912798e9e6c516094db6b8932df53b223660c781ee37607030b6d3/watchfiles-1.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a9ccbf1f129480ed3044f540c0fdbc4ee556f7175e5ab40fe077ff6baf286d4e", size = 392877, upload-time = "2025-06-15T19:06:19.55Z" }, - { url = "https://files.pythonhosted.org/packages/0d/c5/54ec7601a2798604e01c75294770dbee8150e81c6e471445d7601610b495/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba0e3255b0396cac3cc7bbace76404dd72b5438bf0d8e7cefa2f79a7f3649caa", size = 449645, upload-time = "2025-06-15T19:06:20.66Z" }, - { url = "https://files.pythonhosted.org/packages/0a/04/c2f44afc3b2fce21ca0b7802cbd37ed90a29874f96069ed30a36dfe57c2b/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4281cd9fce9fc0a9dbf0fc1217f39bf9cf2b4d315d9626ef1d4e87b84699e7e8", size = 457424, upload-time = "2025-06-15T19:06:21.712Z" }, - { url = "https://files.pythonhosted.org/packages/9f/b0/eec32cb6c14d248095261a04f290636da3df3119d4040ef91a4a50b29fa5/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d2404af8db1329f9a3c9b79ff63e0ae7131986446901582067d9304ae8aaf7f", size = 481584, upload-time = "2025-06-15T19:06:22.777Z" }, - { url = "https://files.pythonhosted.org/packages/d1/e2/ca4bb71c68a937d7145aa25709e4f5d68eb7698a25ce266e84b55d591bbd/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e78b6ed8165996013165eeabd875c5dfc19d41b54f94b40e9fff0eb3193e5e8e", size = 596675, upload-time = "2025-06-15T19:06:24.226Z" }, - { url = "https://files.pythonhosted.org/packages/a1/dd/b0e4b7fb5acf783816bc950180a6cd7c6c1d2cf7e9372c0ea634e722712b/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:249590eb75ccc117f488e2fabd1bfa33c580e24b96f00658ad88e38844a040bb", size = 477363, upload-time = "2025-06-15T19:06:25.42Z" }, - { url = "https://files.pythonhosted.org/packages/69/c4/088825b75489cb5b6a761a4542645718893d395d8c530b38734f19da44d2/watchfiles-1.1.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d05686b5487cfa2e2c28ff1aa370ea3e6c5accfe6435944ddea1e10d93872147", size = 452240, upload-time = "2025-06-15T19:06:26.552Z" }, - { url = "https://files.pythonhosted.org/packages/10/8c/22b074814970eeef43b7c44df98c3e9667c1f7bf5b83e0ff0201b0bd43f9/watchfiles-1.1.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:d0e10e6f8f6dc5762adee7dece33b722282e1f59aa6a55da5d493a97282fedd8", size = 625607, upload-time = "2025-06-15T19:06:27.606Z" }, - { url = "https://files.pythonhosted.org/packages/32/fa/a4f5c2046385492b2273213ef815bf71a0d4c1943b784fb904e184e30201/watchfiles-1.1.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:af06c863f152005c7592df1d6a7009c836a247c9d8adb78fef8575a5a98699db", size = 623315, upload-time = "2025-06-15T19:06:29.076Z" }, - { url = "https://files.pythonhosted.org/packages/8c/6b/686dcf5d3525ad17b384fd94708e95193529b460a1b7bf40851f1328ec6e/watchfiles-1.1.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0ece16b563b17ab26eaa2d52230c9a7ae46cf01759621f4fbbca280e438267b3", size = 406910, upload-time = "2025-06-15T19:06:49.335Z" }, - { url = "https://files.pythonhosted.org/packages/f3/d3/71c2dcf81dc1edcf8af9f4d8d63b1316fb0a2dd90cbfd427e8d9dd584a90/watchfiles-1.1.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:51b81e55d40c4b4aa8658427a3ee7ea847c591ae9e8b81ef94a90b668999353c", size = 398816, upload-time = "2025-06-15T19:06:50.433Z" }, - { url = "https://files.pythonhosted.org/packages/b8/fa/12269467b2fc006f8fce4cd6c3acfa77491dd0777d2a747415f28ccc8c60/watchfiles-1.1.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2bcdc54ea267fe72bfc7d83c041e4eb58d7d8dc6f578dfddb52f037ce62f432", size = 451584, upload-time = "2025-06-15T19:06:51.834Z" }, - { url = "https://files.pythonhosted.org/packages/bd/d3/254cea30f918f489db09d6a8435a7de7047f8cb68584477a515f160541d6/watchfiles-1.1.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:923fec6e5461c42bd7e3fd5ec37492c6f3468be0499bc0707b4bbbc16ac21792", size = 454009, upload-time = "2025-06-15T19:06:52.896Z" }, -] - -[[package]] -name = "websocket-client" -version = "1.8.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e6/30/fba0d96b4b5fbf5948ed3f4681f7da2f9f64512e1d303f94b4cc174c24a5/websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da", size = 54648, upload-time = "2024-04-23T22:16:16.976Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/84/44687a29792a70e111c5c477230a72c4b957d88d16141199bf9acb7537a3/websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526", size = 58826, upload-time = "2024-04-23T22:16:14.422Z" }, + { url = "https://files.pythonhosted.org/packages/76/06/04c8e804f813cf972e3262f3f8584c232de64f0cde9f703b46cf53a45090/virtualenv-20.34.0-py3-none-any.whl", hash = "sha256:341f5afa7eee943e4984a9207c025feedd768baff6753cd660c857ceb3e36026", size = 5983279, upload-time = "2025-08-13T14:24:05.111Z" }, ] [[package]] @@ -4346,6 +3539,65 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, ] +[[package]] +name = "wrapt" +version = "1.17.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/8f/aeb76c5b46e273670962298c23e7ddde79916cb74db802131d49a85e4b7d/wrapt-1.17.3.tar.gz", hash = "sha256:f66eb08feaa410fe4eebd17f2a2c8e2e46d3476e9f8c783daa8e09e0faa666d0", size = 55547, upload-time = "2025-08-12T05:53:21.714Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/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]] name = "wsproto" version = "1.2.0" @@ -4504,59 +3756,74 @@ wheels = [ [[package]] name = "zstandard" -version = "0.23.0" +version = "0.24.0" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cffi", marker = "platform_python_implementation == 'PyPy'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/ed/f6/2ac0287b442160a89d726b17a9184a4c615bb5237db763791a7fd16d9df1/zstandard-0.23.0.tar.gz", hash = "sha256:b2d8c62d08e7255f68f7a740bae85b3c9b8e5466baa9cbf7f57f1cde0ac6bc09", size = 681701, upload-time = "2024-07-15T00:18:06.141Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/40/f67e7d2c25a0e2dc1744dd781110b0b60306657f8696cafb7ad7579469bd/zstandard-0.23.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:34895a41273ad33347b2fc70e1bff4240556de3c46c6ea430a7ed91f9042aa4e", size = 788699, upload-time = "2024-07-15T00:14:04.909Z" }, - { url = "https://files.pythonhosted.org/packages/e8/46/66d5b55f4d737dd6ab75851b224abf0afe5774976fe511a54d2eb9063a41/zstandard-0.23.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:77ea385f7dd5b5676d7fd943292ffa18fbf5c72ba98f7d09fc1fb9e819b34c23", size = 633681, upload-time = "2024-07-15T00:14:13.99Z" }, - { url = "https://files.pythonhosted.org/packages/63/b6/677e65c095d8e12b66b8f862b069bcf1f1d781b9c9c6f12eb55000d57583/zstandard-0.23.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:983b6efd649723474f29ed42e1467f90a35a74793437d0bc64a5bf482bedfa0a", size = 4944328, upload-time = "2024-07-15T00:14:16.588Z" }, - { url = "https://files.pythonhosted.org/packages/59/cc/e76acb4c42afa05a9d20827116d1f9287e9c32b7ad58cc3af0721ce2b481/zstandard-0.23.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80a539906390591dd39ebb8d773771dc4db82ace6372c4d41e2d293f8e32b8db", size = 5311955, upload-time = "2024-07-15T00:14:19.389Z" }, - { url = "https://files.pythonhosted.org/packages/78/e4/644b8075f18fc7f632130c32e8f36f6dc1b93065bf2dd87f03223b187f26/zstandard-0.23.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:445e4cb5048b04e90ce96a79b4b63140e3f4ab5f662321975679b5f6360b90e2", size = 5344944, upload-time = "2024-07-15T00:14:22.173Z" }, - { url = "https://files.pythonhosted.org/packages/76/3f/dbafccf19cfeca25bbabf6f2dd81796b7218f768ec400f043edc767015a6/zstandard-0.23.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd30d9c67d13d891f2360b2a120186729c111238ac63b43dbd37a5a40670b8ca", size = 5442927, upload-time = "2024-07-15T00:14:24.825Z" }, - { url = "https://files.pythonhosted.org/packages/0c/c3/d24a01a19b6733b9f218e94d1a87c477d523237e07f94899e1c10f6fd06c/zstandard-0.23.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d20fd853fbb5807c8e84c136c278827b6167ded66c72ec6f9a14b863d809211c", size = 4864910, upload-time = "2024-07-15T00:14:26.982Z" }, - { url = "https://files.pythonhosted.org/packages/1c/a9/cf8f78ead4597264f7618d0875be01f9bc23c9d1d11afb6d225b867cb423/zstandard-0.23.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ed1708dbf4d2e3a1c5c69110ba2b4eb6678262028afd6c6fbcc5a8dac9cda68e", size = 4935544, upload-time = "2024-07-15T00:14:29.582Z" }, - { url = "https://files.pythonhosted.org/packages/2c/96/8af1e3731b67965fb995a940c04a2c20997a7b3b14826b9d1301cf160879/zstandard-0.23.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:be9b5b8659dff1f913039c2feee1aca499cfbc19e98fa12bc85e037c17ec6ca5", size = 5467094, upload-time = "2024-07-15T00:14:40.126Z" }, - { url = "https://files.pythonhosted.org/packages/ff/57/43ea9df642c636cb79f88a13ab07d92d88d3bfe3e550b55a25a07a26d878/zstandard-0.23.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:65308f4b4890aa12d9b6ad9f2844b7ee42c7f7a4fd3390425b242ffc57498f48", size = 4860440, upload-time = "2024-07-15T00:14:42.786Z" }, - { url = "https://files.pythonhosted.org/packages/46/37/edb78f33c7f44f806525f27baa300341918fd4c4af9472fbc2c3094be2e8/zstandard-0.23.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:98da17ce9cbf3bfe4617e836d561e433f871129e3a7ac16d6ef4c680f13a839c", size = 4700091, upload-time = "2024-07-15T00:14:45.184Z" }, - { url = "https://files.pythonhosted.org/packages/c1/f1/454ac3962671a754f3cb49242472df5c2cced4eb959ae203a377b45b1a3c/zstandard-0.23.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:8ed7d27cb56b3e058d3cf684d7200703bcae623e1dcc06ed1e18ecda39fee003", size = 5208682, upload-time = "2024-07-15T00:14:47.407Z" }, - { url = "https://files.pythonhosted.org/packages/85/b2/1734b0fff1634390b1b887202d557d2dd542de84a4c155c258cf75da4773/zstandard-0.23.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:b69bb4f51daf461b15e7b3db033160937d3ff88303a7bc808c67bbc1eaf98c78", size = 5669707, upload-time = "2024-07-15T00:15:03.529Z" }, - { url = "https://files.pythonhosted.org/packages/52/5a/87d6971f0997c4b9b09c495bf92189fb63de86a83cadc4977dc19735f652/zstandard-0.23.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:034b88913ecc1b097f528e42b539453fa82c3557e414b3de9d5632c80439a473", size = 5201792, upload-time = "2024-07-15T00:15:28.372Z" }, - { url = "https://files.pythonhosted.org/packages/79/02/6f6a42cc84459d399bd1a4e1adfc78d4dfe45e56d05b072008d10040e13b/zstandard-0.23.0-cp311-cp311-win32.whl", hash = "sha256:f2d4380bf5f62daabd7b751ea2339c1a21d1c9463f1feb7fc2bdcea2c29c3160", size = 430586, upload-time = "2024-07-15T00:15:32.26Z" }, - { url = "https://files.pythonhosted.org/packages/be/a2/4272175d47c623ff78196f3c10e9dc7045c1b9caf3735bf041e65271eca4/zstandard-0.23.0-cp311-cp311-win_amd64.whl", hash = "sha256:62136da96a973bd2557f06ddd4e8e807f9e13cbb0bfb9cc06cfe6d98ea90dfe0", size = 495420, upload-time = "2024-07-15T00:15:34.004Z" }, - { url = "https://files.pythonhosted.org/packages/7b/83/f23338c963bd9de687d47bf32efe9fd30164e722ba27fb59df33e6b1719b/zstandard-0.23.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b4567955a6bc1b20e9c31612e615af6b53733491aeaa19a6b3b37f3b65477094", size = 788713, upload-time = "2024-07-15T00:15:35.815Z" }, - { url = "https://files.pythonhosted.org/packages/5b/b3/1a028f6750fd9227ee0b937a278a434ab7f7fdc3066c3173f64366fe2466/zstandard-0.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e172f57cd78c20f13a3415cc8dfe24bf388614324d25539146594c16d78fcc8", size = 633459, upload-time = "2024-07-15T00:15:37.995Z" }, - { url = "https://files.pythonhosted.org/packages/26/af/36d89aae0c1f95a0a98e50711bc5d92c144939efc1f81a2fcd3e78d7f4c1/zstandard-0.23.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0e166f698c5a3e914947388c162be2583e0c638a4703fc6a543e23a88dea3c1", size = 4945707, upload-time = "2024-07-15T00:15:39.872Z" }, - { url = "https://files.pythonhosted.org/packages/cd/2e/2051f5c772f4dfc0aae3741d5fc72c3dcfe3aaeb461cc231668a4db1ce14/zstandard-0.23.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12a289832e520c6bd4dcaad68e944b86da3bad0d339ef7989fb7e88f92e96072", size = 5306545, upload-time = "2024-07-15T00:15:41.75Z" }, - { url = "https://files.pythonhosted.org/packages/0a/9e/a11c97b087f89cab030fa71206963090d2fecd8eb83e67bb8f3ffb84c024/zstandard-0.23.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d50d31bfedd53a928fed6707b15a8dbeef011bb6366297cc435accc888b27c20", size = 5337533, upload-time = "2024-07-15T00:15:44.114Z" }, - { url = "https://files.pythonhosted.org/packages/fc/79/edeb217c57fe1bf16d890aa91a1c2c96b28c07b46afed54a5dcf310c3f6f/zstandard-0.23.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72c68dda124a1a138340fb62fa21b9bf4848437d9ca60bd35db36f2d3345f373", size = 5436510, upload-time = "2024-07-15T00:15:46.509Z" }, - { url = "https://files.pythonhosted.org/packages/81/4f/c21383d97cb7a422ddf1ae824b53ce4b51063d0eeb2afa757eb40804a8ef/zstandard-0.23.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53dd9d5e3d29f95acd5de6802e909ada8d8d8cfa37a3ac64836f3bc4bc5512db", size = 4859973, upload-time = "2024-07-15T00:15:49.939Z" }, - { url = "https://files.pythonhosted.org/packages/ab/15/08d22e87753304405ccac8be2493a495f529edd81d39a0870621462276ef/zstandard-0.23.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6a41c120c3dbc0d81a8e8adc73312d668cd34acd7725f036992b1b72d22c1772", size = 4936968, upload-time = "2024-07-15T00:15:52.025Z" }, - { url = "https://files.pythonhosted.org/packages/eb/fa/f3670a597949fe7dcf38119a39f7da49a8a84a6f0b1a2e46b2f71a0ab83f/zstandard-0.23.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:40b33d93c6eddf02d2c19f5773196068d875c41ca25730e8288e9b672897c105", size = 5467179, upload-time = "2024-07-15T00:15:54.971Z" }, - { url = "https://files.pythonhosted.org/packages/4e/a9/dad2ab22020211e380adc477a1dbf9f109b1f8d94c614944843e20dc2a99/zstandard-0.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9206649ec587e6b02bd124fb7799b86cddec350f6f6c14bc82a2b70183e708ba", size = 4848577, upload-time = "2024-07-15T00:15:57.634Z" }, - { url = "https://files.pythonhosted.org/packages/08/03/dd28b4484b0770f1e23478413e01bee476ae8227bbc81561f9c329e12564/zstandard-0.23.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76e79bc28a65f467e0409098fa2c4376931fd3207fbeb6b956c7c476d53746dd", size = 4693899, upload-time = "2024-07-15T00:16:00.811Z" }, - { url = "https://files.pythonhosted.org/packages/2b/64/3da7497eb635d025841e958bcd66a86117ae320c3b14b0ae86e9e8627518/zstandard-0.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:66b689c107857eceabf2cf3d3fc699c3c0fe8ccd18df2219d978c0283e4c508a", size = 5199964, upload-time = "2024-07-15T00:16:03.669Z" }, - { url = "https://files.pythonhosted.org/packages/43/a4/d82decbab158a0e8a6ebb7fc98bc4d903266bce85b6e9aaedea1d288338c/zstandard-0.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9c236e635582742fee16603042553d276cca506e824fa2e6489db04039521e90", size = 5655398, upload-time = "2024-07-15T00:16:06.694Z" }, - { url = "https://files.pythonhosted.org/packages/f2/61/ac78a1263bc83a5cf29e7458b77a568eda5a8f81980691bbc6eb6a0d45cc/zstandard-0.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a8fffdbd9d1408006baaf02f1068d7dd1f016c6bcb7538682622c556e7b68e35", size = 5191313, upload-time = "2024-07-15T00:16:09.758Z" }, - { url = "https://files.pythonhosted.org/packages/e7/54/967c478314e16af5baf849b6ee9d6ea724ae5b100eb506011f045d3d4e16/zstandard-0.23.0-cp312-cp312-win32.whl", hash = "sha256:dc1d33abb8a0d754ea4763bad944fd965d3d95b5baef6b121c0c9013eaf1907d", size = 430877, upload-time = "2024-07-15T00:16:11.758Z" }, - { url = "https://files.pythonhosted.org/packages/75/37/872d74bd7739639c4553bf94c84af7d54d8211b626b352bc57f0fd8d1e3f/zstandard-0.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:64585e1dba664dc67c7cdabd56c1e5685233fbb1fc1966cfba2a340ec0dfff7b", size = 495595, upload-time = "2024-07-15T00:16:13.731Z" }, - { url = "https://files.pythonhosted.org/packages/80/f1/8386f3f7c10261fe85fbc2c012fdb3d4db793b921c9abcc995d8da1b7a80/zstandard-0.23.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:576856e8594e6649aee06ddbfc738fec6a834f7c85bf7cadd1c53d4a58186ef9", size = 788975, upload-time = "2024-07-15T00:16:16.005Z" }, - { url = "https://files.pythonhosted.org/packages/16/e8/cbf01077550b3e5dc86089035ff8f6fbbb312bc0983757c2d1117ebba242/zstandard-0.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:38302b78a850ff82656beaddeb0bb989a0322a8bbb1bf1ab10c17506681d772a", size = 633448, upload-time = "2024-07-15T00:16:17.897Z" }, - { url = "https://files.pythonhosted.org/packages/06/27/4a1b4c267c29a464a161aeb2589aff212b4db653a1d96bffe3598f3f0d22/zstandard-0.23.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2240ddc86b74966c34554c49d00eaafa8200a18d3a5b6ffbf7da63b11d74ee2", size = 4945269, upload-time = "2024-07-15T00:16:20.136Z" }, - { url = "https://files.pythonhosted.org/packages/7c/64/d99261cc57afd9ae65b707e38045ed8269fbdae73544fd2e4a4d50d0ed83/zstandard-0.23.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ef230a8fd217a2015bc91b74f6b3b7d6522ba48be29ad4ea0ca3a3775bf7dd5", size = 5306228, upload-time = "2024-07-15T00:16:23.398Z" }, - { url = "https://files.pythonhosted.org/packages/7a/cf/27b74c6f22541f0263016a0fd6369b1b7818941de639215c84e4e94b2a1c/zstandard-0.23.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:774d45b1fac1461f48698a9d4b5fa19a69d47ece02fa469825b442263f04021f", size = 5336891, upload-time = "2024-07-15T00:16:26.391Z" }, - { url = "https://files.pythonhosted.org/packages/fa/18/89ac62eac46b69948bf35fcd90d37103f38722968e2981f752d69081ec4d/zstandard-0.23.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f77fa49079891a4aab203d0b1744acc85577ed16d767b52fc089d83faf8d8ed", size = 5436310, upload-time = "2024-07-15T00:16:29.018Z" }, - { url = "https://files.pythonhosted.org/packages/a8/a8/5ca5328ee568a873f5118d5b5f70d1f36c6387716efe2e369010289a5738/zstandard-0.23.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac184f87ff521f4840e6ea0b10c0ec90c6b1dcd0bad2f1e4a9a1b4fa177982ea", size = 4859912, upload-time = "2024-07-15T00:16:31.871Z" }, - { url = "https://files.pythonhosted.org/packages/ea/ca/3781059c95fd0868658b1cf0440edd832b942f84ae60685d0cfdb808bca1/zstandard-0.23.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c363b53e257246a954ebc7c488304b5592b9c53fbe74d03bc1c64dda153fb847", size = 4936946, upload-time = "2024-07-15T00:16:34.593Z" }, - { url = "https://files.pythonhosted.org/packages/ce/11/41a58986f809532742c2b832c53b74ba0e0a5dae7e8ab4642bf5876f35de/zstandard-0.23.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e7792606d606c8df5277c32ccb58f29b9b8603bf83b48639b7aedf6df4fe8171", size = 5466994, upload-time = "2024-07-15T00:16:36.887Z" }, - { url = "https://files.pythonhosted.org/packages/83/e3/97d84fe95edd38d7053af05159465d298c8b20cebe9ccb3d26783faa9094/zstandard-0.23.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a0817825b900fcd43ac5d05b8b3079937073d2b1ff9cf89427590718b70dd840", size = 4848681, upload-time = "2024-07-15T00:16:39.709Z" }, - { url = "https://files.pythonhosted.org/packages/6e/99/cb1e63e931de15c88af26085e3f2d9af9ce53ccafac73b6e48418fd5a6e6/zstandard-0.23.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9da6bc32faac9a293ddfdcb9108d4b20416219461e4ec64dfea8383cac186690", size = 4694239, upload-time = "2024-07-15T00:16:41.83Z" }, - { url = "https://files.pythonhosted.org/packages/ab/50/b1e703016eebbc6501fc92f34db7b1c68e54e567ef39e6e59cf5fb6f2ec0/zstandard-0.23.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fd7699e8fd9969f455ef2926221e0233f81a2542921471382e77a9e2f2b57f4b", size = 5200149, upload-time = "2024-07-15T00:16:44.287Z" }, - { url = "https://files.pythonhosted.org/packages/aa/e0/932388630aaba70197c78bdb10cce2c91fae01a7e553b76ce85471aec690/zstandard-0.23.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d477ed829077cd945b01fc3115edd132c47e6540ddcd96ca169facff28173057", size = 5655392, upload-time = "2024-07-15T00:16:46.423Z" }, - { url = "https://files.pythonhosted.org/packages/02/90/2633473864f67a15526324b007a9f96c96f56d5f32ef2a56cc12f9548723/zstandard-0.23.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ce8b52c5987b3e34d5674b0ab529a4602b632ebab0a93b07bfb4dfc8f8a33", size = 5191299, upload-time = "2024-07-15T00:16:49.053Z" }, - { url = "https://files.pythonhosted.org/packages/b0/4c/315ca5c32da7e2dc3455f3b2caee5c8c2246074a61aac6ec3378a97b7136/zstandard-0.23.0-cp313-cp313-win32.whl", hash = "sha256:a9b07268d0c3ca5c170a385a0ab9fb7fdd9f5fd866be004c4ea39e44edce47dd", size = 430862, upload-time = "2024-07-15T00:16:51.003Z" }, - { url = "https://files.pythonhosted.org/packages/a2/bf/c6aaba098e2d04781e8f4f7c0ba3c7aa73d00e4c436bcc0cf059a66691d1/zstandard-0.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:f3513916e8c645d0610815c257cbfd3242adfd5c4cfa78be514e5a3ebb42a41b", size = 495578, upload-time = "2024-07-15T00:16:53.135Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/09/1b/c20b2ef1d987627765dcd5bf1dadb8ef6564f00a87972635099bb76b7a05/zstandard-0.24.0.tar.gz", hash = "sha256:fe3198b81c00032326342d973e526803f183f97aa9e9a98e3f897ebafe21178f", size = 905681, upload-time = "2025-08-17T18:36:36.352Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/01/1f/5c72806f76043c0ef9191a2b65281dacdf3b65b0828eb13bb2c987c4fb90/zstandard-0.24.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:addfc23e3bd5f4b6787b9ca95b2d09a1a67ad5a3c318daaa783ff90b2d3a366e", size = 795228, upload-time = "2025-08-17T18:21:46.978Z" }, + { url = "https://files.pythonhosted.org/packages/0b/ba/3059bd5cd834666a789251d14417621b5c61233bd46e7d9023ea8bc1043a/zstandard-0.24.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6b005bcee4be9c3984b355336283afe77b2defa76ed6b89332eced7b6fa68b68", size = 640520, upload-time = "2025-08-17T18:21:48.162Z" }, + { url = "https://files.pythonhosted.org/packages/57/07/f0e632bf783f915c1fdd0bf68614c4764cae9dd46ba32cbae4dd659592c3/zstandard-0.24.0-cp311-cp311-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:3f96a9130171e01dbb6c3d4d9925d604e2131a97f540e223b88ba45daf56d6fb", size = 5347682, upload-time = "2025-08-17T18:21:50.266Z" }, + { url = "https://files.pythonhosted.org/packages/a6/4c/63523169fe84773a7462cd090b0989cb7c7a7f2a8b0a5fbf00009ba7d74d/zstandard-0.24.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd0d3d16e63873253bad22b413ec679cf6586e51b5772eb10733899832efec42", size = 5057650, upload-time = "2025-08-17T18:21:52.634Z" }, + { url = "https://files.pythonhosted.org/packages/c6/16/49013f7ef80293f5cebf4c4229535a9f4c9416bbfd238560edc579815dbe/zstandard-0.24.0-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:b7a8c30d9bf4bd5e4dcfe26900bef0fcd9749acde45cdf0b3c89e2052fda9a13", size = 5404893, upload-time = "2025-08-17T18:21:54.54Z" }, + { url = "https://files.pythonhosted.org/packages/4d/38/78e8bcb5fc32a63b055f2b99e0be49b506f2351d0180173674f516cf8a7a/zstandard-0.24.0-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:52cd7d9fa0a115c9446abb79b06a47171b7d916c35c10e0c3aa6f01d57561382", size = 5452389, upload-time = "2025-08-17T18:21:56.822Z" }, + { url = "https://files.pythonhosted.org/packages/55/8a/81671f05619edbacd49bd84ce6899a09fc8299be20c09ae92f6618ccb92d/zstandard-0.24.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a0f6fc2ea6e07e20df48752e7700e02e1892c61f9a6bfbacaf2c5b24d5ad504b", size = 5558888, upload-time = "2025-08-17T18:21:58.68Z" }, + { url = "https://files.pythonhosted.org/packages/49/cc/e83feb2d7d22d1f88434defbaeb6e5e91f42a4f607b5d4d2d58912b69d67/zstandard-0.24.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e46eb6702691b24ddb3e31e88b4a499e31506991db3d3724a85bd1c5fc3cfe4e", size = 5048038, upload-time = "2025-08-17T18:22:00.642Z" }, + { url = "https://files.pythonhosted.org/packages/08/c3/7a5c57ff49ef8943877f85c23368c104c2aea510abb339a2dc31ad0a27c3/zstandard-0.24.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5e3b9310fd7f0d12edc75532cd9a56da6293840c84da90070d692e0bb15f186", size = 5573833, upload-time = "2025-08-17T18:22:02.402Z" }, + { url = "https://files.pythonhosted.org/packages/f9/00/64519983cd92535ba4bdd4ac26ac52db00040a52d6c4efb8d1764abcc343/zstandard-0.24.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:76cdfe7f920738ea871f035568f82bad3328cbc8d98f1f6988264096b5264efd", size = 4961072, upload-time = "2025-08-17T18:22:04.384Z" }, + { url = "https://files.pythonhosted.org/packages/72/ab/3a08a43067387d22994fc87c3113636aa34ccd2914a4d2d188ce365c5d85/zstandard-0.24.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:3f2fe35ec84908dddf0fbf66b35d7c2878dbe349552dd52e005c755d3493d61c", size = 5268462, upload-time = "2025-08-17T18:22:06.095Z" }, + { url = "https://files.pythonhosted.org/packages/49/cf/2abb3a1ad85aebe18c53e7eca73223f1546ddfa3bf4d2fb83fc5a064c5ca/zstandard-0.24.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:aa705beb74ab116563f4ce784fa94771f230c05d09ab5de9c397793e725bb1db", size = 5443319, upload-time = "2025-08-17T18:22:08.572Z" }, + { url = "https://files.pythonhosted.org/packages/40/42/0dd59fc2f68f1664cda11c3b26abdf987f4e57cb6b6b0f329520cd074552/zstandard-0.24.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:aadf32c389bb7f02b8ec5c243c38302b92c006da565e120dfcb7bf0378f4f848", size = 5822355, upload-time = "2025-08-17T18:22:10.537Z" }, + { url = "https://files.pythonhosted.org/packages/99/c0/ea4e640fd4f7d58d6f87a1e7aca11fb886ac24db277fbbb879336c912f63/zstandard-0.24.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e40cd0fc734aa1d4bd0e7ad102fd2a1aefa50ce9ef570005ffc2273c5442ddc3", size = 5365257, upload-time = "2025-08-17T18:22:13.159Z" }, + { url = "https://files.pythonhosted.org/packages/27/a9/92da42a5c4e7e4003271f2e1f0efd1f37cfd565d763ad3604e9597980a1c/zstandard-0.24.0-cp311-cp311-win32.whl", hash = "sha256:cda61c46343809ecda43dc620d1333dd7433a25d0a252f2dcc7667f6331c7b61", size = 435559, upload-time = "2025-08-17T18:22:17.29Z" }, + { url = "https://files.pythonhosted.org/packages/e2/8e/2c8e5c681ae4937c007938f954a060fa7c74f36273b289cabdb5ef0e9a7e/zstandard-0.24.0-cp311-cp311-win_amd64.whl", hash = "sha256:3b95fc06489aa9388400d1aab01a83652bc040c9c087bd732eb214909d7fb0dd", size = 505070, upload-time = "2025-08-17T18:22:14.808Z" }, + { url = "https://files.pythonhosted.org/packages/52/10/a2f27a66bec75e236b575c9f7b0d7d37004a03aa2dcde8e2decbe9ed7b4d/zstandard-0.24.0-cp311-cp311-win_arm64.whl", hash = "sha256:ad9fd176ff6800a0cf52bcf59c71e5de4fa25bf3ba62b58800e0f84885344d34", size = 461507, upload-time = "2025-08-17T18:22:15.964Z" }, + { url = "https://files.pythonhosted.org/packages/26/e9/0bd281d9154bba7fc421a291e263911e1d69d6951aa80955b992a48289f6/zstandard-0.24.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a2bda8f2790add22773ee7a4e43c90ea05598bffc94c21c40ae0a9000b0133c3", size = 795710, upload-time = "2025-08-17T18:22:19.189Z" }, + { url = "https://files.pythonhosted.org/packages/36/26/b250a2eef515caf492e2d86732e75240cdac9d92b04383722b9753590c36/zstandard-0.24.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:cc76de75300f65b8eb574d855c12518dc25a075dadb41dd18f6322bda3fe15d5", size = 640336, upload-time = "2025-08-17T18:22:20.466Z" }, + { url = "https://files.pythonhosted.org/packages/79/bf/3ba6b522306d9bf097aac8547556b98a4f753dc807a170becaf30dcd6f01/zstandard-0.24.0-cp312-cp312-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:d2b3b4bda1a025b10fe0269369475f420177f2cb06e0f9d32c95b4873c9f80b8", size = 5342533, upload-time = "2025-08-17T18:22:22.326Z" }, + { url = "https://files.pythonhosted.org/packages/ea/ec/22bc75bf054e25accdf8e928bc68ab36b4466809729c554ff3a1c1c8bce6/zstandard-0.24.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9b84c6c210684286e504022d11ec294d2b7922d66c823e87575d8b23eba7c81f", size = 5062837, upload-time = "2025-08-17T18:22:24.416Z" }, + { url = "https://files.pythonhosted.org/packages/48/cc/33edfc9d286e517fb5b51d9c3210e5bcfce578d02a675f994308ca587ae1/zstandard-0.24.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c59740682a686bf835a1a4d8d0ed1eefe31ac07f1c5a7ed5f2e72cf577692b00", size = 5393855, upload-time = "2025-08-17T18:22:26.786Z" }, + { url = "https://files.pythonhosted.org/packages/73/36/59254e9b29da6215fb3a717812bf87192d89f190f23817d88cb8868c47ac/zstandard-0.24.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6324fde5cf5120fbf6541d5ff3c86011ec056e8d0f915d8e7822926a5377193a", size = 5451058, upload-time = "2025-08-17T18:22:28.885Z" }, + { url = "https://files.pythonhosted.org/packages/9a/c7/31674cb2168b741bbbe71ce37dd397c9c671e73349d88ad3bca9e9fae25b/zstandard-0.24.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:51a86bd963de3f36688553926a84e550d45d7f9745bd1947d79472eca27fcc75", size = 5546619, upload-time = "2025-08-17T18:22:31.115Z" }, + { url = "https://files.pythonhosted.org/packages/e6/01/1a9f22239f08c00c156f2266db857545ece66a6fc0303d45c298564bc20b/zstandard-0.24.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d82ac87017b734f2fb70ff93818c66f0ad2c3810f61040f077ed38d924e19980", size = 5046676, upload-time = "2025-08-17T18:22:33.077Z" }, + { url = "https://files.pythonhosted.org/packages/a7/91/6c0cf8fa143a4988a0361380ac2ef0d7cb98a374704b389fbc38b5891712/zstandard-0.24.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:92ea7855d5bcfb386c34557516c73753435fb2d4a014e2c9343b5f5ba148b5d8", size = 5576381, upload-time = "2025-08-17T18:22:35.391Z" }, + { url = "https://files.pythonhosted.org/packages/e2/77/1526080e22e78871e786ccf3c84bf5cec9ed25110a9585507d3c551da3d6/zstandard-0.24.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3adb4b5414febf074800d264ddf69ecade8c658837a83a19e8ab820e924c9933", size = 4953403, upload-time = "2025-08-17T18:22:37.266Z" }, + { url = "https://files.pythonhosted.org/packages/6e/d0/a3a833930bff01eab697eb8abeafb0ab068438771fa066558d96d7dafbf9/zstandard-0.24.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:6374feaf347e6b83ec13cc5dcfa70076f06d8f7ecd46cc71d58fac798ff08b76", size = 5267396, upload-time = "2025-08-17T18:22:39.757Z" }, + { url = "https://files.pythonhosted.org/packages/f3/5e/90a0db9a61cd4769c06374297ecfcbbf66654f74cec89392519deba64d76/zstandard-0.24.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:13fc548e214df08d896ee5f29e1f91ee35db14f733fef8eabea8dca6e451d1e2", size = 5433269, upload-time = "2025-08-17T18:22:42.131Z" }, + { url = "https://files.pythonhosted.org/packages/ce/58/fc6a71060dd67c26a9c5566e0d7c99248cbe5abfda6b3b65b8f1a28d59f7/zstandard-0.24.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0a416814608610abf5488889c74e43ffa0343ca6cf43957c6b6ec526212422da", size = 5814203, upload-time = "2025-08-17T18:22:44.017Z" }, + { url = "https://files.pythonhosted.org/packages/5c/6a/89573d4393e3ecbfa425d9a4e391027f58d7810dec5cdb13a26e4cdeef5c/zstandard-0.24.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0d66da2649bb0af4471699aeb7a83d6f59ae30236fb9f6b5d20fb618ef6c6777", size = 5359622, upload-time = "2025-08-17T18:22:45.802Z" }, + { url = "https://files.pythonhosted.org/packages/60/ff/2cbab815d6f02a53a9d8d8703bc727d8408a2e508143ca9af6c3cca2054b/zstandard-0.24.0-cp312-cp312-win32.whl", hash = "sha256:ff19efaa33e7f136fe95f9bbcc90ab7fb60648453b03f95d1de3ab6997de0f32", size = 435968, upload-time = "2025-08-17T18:22:49.493Z" }, + { url = "https://files.pythonhosted.org/packages/ce/a3/8f96b8ddb7ad12344218fbd0fd2805702dafd126ae9f8a1fb91eef7b33da/zstandard-0.24.0-cp312-cp312-win_amd64.whl", hash = "sha256:bc05f8a875eb651d1cc62e12a4a0e6afa5cd0cc231381adb830d2e9c196ea895", size = 505195, upload-time = "2025-08-17T18:22:47.193Z" }, + { url = "https://files.pythonhosted.org/packages/a3/4a/bfca20679da63bfc236634ef2e4b1b4254203098b0170e3511fee781351f/zstandard-0.24.0-cp312-cp312-win_arm64.whl", hash = "sha256:b04c94718f7a8ed7cdd01b162b6caa1954b3c9d486f00ecbbd300f149d2b2606", size = 461605, upload-time = "2025-08-17T18:22:48.317Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ef/db949de3bf81ed122b8ee4db6a8d147a136fe070e1015f5a60d8a3966748/zstandard-0.24.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e4ebb000c0fe24a6d0f3534b6256844d9dbf042fdf003efe5cf40690cf4e0f3e", size = 795700, upload-time = "2025-08-17T18:22:50.851Z" }, + { url = "https://files.pythonhosted.org/packages/99/56/fc04395d6f5eabd2fe6d86c0800d198969f3038385cb918bfbe94f2b0c62/zstandard-0.24.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:498f88f5109666c19531f0243a90d2fdd2252839cd6c8cc6e9213a3446670fa8", size = 640343, upload-time = "2025-08-17T18:22:51.999Z" }, + { url = "https://files.pythonhosted.org/packages/9b/0f/0b0e0d55f2f051d5117a0d62f4f9a8741b3647440c0ee1806b7bd47ed5ae/zstandard-0.24.0-cp313-cp313-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:0a9e95ceb180ccd12a8b3437bac7e8a8a089c9094e39522900a8917745542184", size = 5342571, upload-time = "2025-08-17T18:22:53.734Z" }, + { url = "https://files.pythonhosted.org/packages/5d/43/d74e49f04fbd62d4b5d89aeb7a29d693fc637c60238f820cd5afe6ca8180/zstandard-0.24.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bcf69e0bcddbf2adcfafc1a7e864edcc204dd8171756d3a8f3340f6f6cc87b7b", size = 5062723, upload-time = "2025-08-17T18:22:55.624Z" }, + { url = "https://files.pythonhosted.org/packages/8e/97/df14384d4d6a004388e6ed07ded02933b5c7e0833a9150c57d0abc9545b7/zstandard-0.24.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:10e284748a7e7fbe2815ca62a9d6e84497d34cfdd0143fa9e8e208efa808d7c4", size = 5393282, upload-time = "2025-08-17T18:22:57.655Z" }, + { url = "https://files.pythonhosted.org/packages/7e/09/8f5c520e59a4d41591b30b7568595eda6fd71c08701bb316d15b7ed0613a/zstandard-0.24.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:1bda8a85e5b9d5e73af2e61b23609a8cc1598c1b3b2473969912979205a1ff25", size = 5450895, upload-time = "2025-08-17T18:22:59.749Z" }, + { url = "https://files.pythonhosted.org/packages/d9/3d/02aba892327a67ead8cba160ee835cfa1fc292a9dcb763639e30c07da58b/zstandard-0.24.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1b14bc92af065d0534856bf1b30fc48753163ea673da98857ea4932be62079b1", size = 5546353, upload-time = "2025-08-17T18:23:01.457Z" }, + { url = "https://files.pythonhosted.org/packages/6a/6e/96c52afcde44da6a5313a1f6c356349792079808f12d8b69a7d1d98ef353/zstandard-0.24.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:b4f20417a4f511c656762b001ec827500cbee54d1810253c6ca2df2c0a307a5f", size = 5046404, upload-time = "2025-08-17T18:23:03.418Z" }, + { url = "https://files.pythonhosted.org/packages/da/b6/eefee6b92d341a7db7cd1b3885d42d30476a093720fb5c181e35b236d695/zstandard-0.24.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:337572a7340e1d92fd7fb5248c8300d0e91071002d92e0b8cabe8d9ae7b58159", size = 5576095, upload-time = "2025-08-17T18:23:05.331Z" }, + { url = "https://files.pythonhosted.org/packages/a3/29/743de3131f6239ba6611e17199581e6b5e0f03f268924d42468e29468ca0/zstandard-0.24.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:df4be1cf6e8f0f2bbe2a3eabfff163ef592c84a40e1a20a8d7db7f27cfe08fc2", size = 4953448, upload-time = "2025-08-17T18:23:07.225Z" }, + { url = "https://files.pythonhosted.org/packages/c9/11/bd36ef49fba82e307d69d93b5abbdcdc47d6a0bcbc7ffbbfe0ef74c2fec5/zstandard-0.24.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6885ae4b33aee8835dbdb4249d3dfec09af55e705d74d9b660bfb9da51baaa8b", size = 5267388, upload-time = "2025-08-17T18:23:09.127Z" }, + { url = "https://files.pythonhosted.org/packages/c0/23/a4cfe1b871d3f1ce1f88f5c68d7e922e94be0043f3ae5ed58c11578d1e21/zstandard-0.24.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:663848a8bac4fdbba27feea2926049fdf7b55ec545d5b9aea096ef21e7f0b079", size = 5433383, upload-time = "2025-08-17T18:23:11.343Z" }, + { url = "https://files.pythonhosted.org/packages/77/26/f3fb85f00e732cca617d4b9cd1ffa6484f613ea07fad872a8bdc3a0ce753/zstandard-0.24.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:05d27c953f2e0a3ecc8edbe91d6827736acc4c04d0479672e0400ccdb23d818c", size = 5813988, upload-time = "2025-08-17T18:23:13.194Z" }, + { url = "https://files.pythonhosted.org/packages/3d/8c/d7e3b424b73f3ce66e754595cbcb6d94ff49790c9ac37d50e40e8145cd44/zstandard-0.24.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:77b8b7b98893eaf47da03d262816f01f251c2aa059c063ed8a45c50eada123a5", size = 5359756, upload-time = "2025-08-17T18:23:15.021Z" }, + { url = "https://files.pythonhosted.org/packages/90/6c/f1f0e11f1b295138f9da7e7ae22dcd9a1bb96a9544fa3b31507e431288f5/zstandard-0.24.0-cp313-cp313-win32.whl", hash = "sha256:cf7fbb4e54136e9a03c7ed7691843c4df6d2ecc854a2541f840665f4f2bb2edd", size = 435957, upload-time = "2025-08-17T18:23:18.835Z" }, + { url = "https://files.pythonhosted.org/packages/9f/03/ab8b82ae5eb49eca4d3662705399c44442666cc1ce45f44f2d263bb1ae31/zstandard-0.24.0-cp313-cp313-win_amd64.whl", hash = "sha256:d64899cc0f33a8f446f1e60bffc21fa88b99f0e8208750d9144ea717610a80ce", size = 505171, upload-time = "2025-08-17T18:23:16.44Z" }, + { url = "https://files.pythonhosted.org/packages/db/12/89a2ecdea4bc73a934a30b66a7cfac5af352beac94d46cf289e103b65c34/zstandard-0.24.0-cp313-cp313-win_arm64.whl", hash = "sha256:57be3abb4313e0dd625596376bbb607f40059d801d51c1a1da94d7477e63b255", size = 461596, upload-time = "2025-08-17T18:23:17.603Z" }, + { url = "https://files.pythonhosted.org/packages/c9/56/f3d2c4d64aacee4aab89e788783636884786b6f8334c819f09bff1aa207b/zstandard-0.24.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:b7fa260dd2731afd0dfa47881c30239f422d00faee4b8b341d3e597cface1483", size = 795747, upload-time = "2025-08-17T18:23:19.968Z" }, + { url = "https://files.pythonhosted.org/packages/32/2d/9d3e5f6627e4cb5e511803788be1feee2f0c3b94594591e92b81db324253/zstandard-0.24.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e05d66239d14a04b4717998b736a25494372b1b2409339b04bf42aa4663bf251", size = 640475, upload-time = "2025-08-17T18:23:21.5Z" }, + { url = "https://files.pythonhosted.org/packages/be/5d/48e66abf8c146d95330e5385633a8cfdd556fa8bd14856fe721590cbab2b/zstandard-0.24.0-cp314-cp314-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:622e1e04bd8a085994e02313ba06fbcf4f9ed9a488c6a77a8dbc0692abab6a38", size = 5343866, upload-time = "2025-08-17T18:23:23.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/6c/65fe7ba71220a551e082e4a52790487f1d6bb8dfc2156883e088f975ad6d/zstandard-0.24.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:55872e818598319f065e8192ebefecd6ac05f62a43f055ed71884b0a26218f41", size = 5062719, upload-time = "2025-08-17T18:23:25.192Z" }, + { url = "https://files.pythonhosted.org/packages/cb/68/15ed0a813ff91be80cc2a610ac42e0fc8d29daa737de247bbf4bab9429a1/zstandard-0.24.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:bb2446a55b3a0fd8aa02aa7194bd64740015464a2daaf160d2025204e1d7c282", size = 5393090, upload-time = "2025-08-17T18:23:27.145Z" }, + { url = "https://files.pythonhosted.org/packages/d4/89/e560427b74fa2da6a12b8f3af8ee29104fe2bb069a25e7d314c35eec7732/zstandard-0.24.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:2825a3951f945fb2613ded0f517d402b1e5a68e87e0ee65f5bd224a8333a9a46", size = 5450383, upload-time = "2025-08-17T18:23:29.044Z" }, + { url = "https://files.pythonhosted.org/packages/a3/95/0498328cbb1693885509f2fc145402b108b750a87a3af65b7250b10bd896/zstandard-0.24.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:09887301001e7a81a3618156bc1759e48588de24bddfdd5b7a4364da9a8fbc20", size = 5546142, upload-time = "2025-08-17T18:23:31.281Z" }, + { url = "https://files.pythonhosted.org/packages/8a/8a/64aa15a726594df3bf5d8decfec14fe20cd788c60890f44fcfc74d98c2cc/zstandard-0.24.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:98ca91dc9602cf351497d5600aa66e6d011a38c085a8237b370433fcb53e3409", size = 4953456, upload-time = "2025-08-17T18:23:33.234Z" }, + { url = "https://files.pythonhosted.org/packages/b0/b6/e94879c5cd6017af57bcba08519ed1228b1ebb15681efd949f4a00199449/zstandard-0.24.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:e69f8e534b4e254f523e2f9d4732cf9c169c327ca1ce0922682aac9a5ee01155", size = 5268287, upload-time = "2025-08-17T18:23:35.145Z" }, + { url = "https://files.pythonhosted.org/packages/fd/e5/1a3b3a93f953dbe9e77e2a19be146e9cd2af31b67b1419d6cc8e8898d409/zstandard-0.24.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:444633b487a711e34f4bccc46a0c5dfbe1aee82c1a511e58cdc16f6bd66f187c", size = 5433197, upload-time = "2025-08-17T18:23:36.969Z" }, + { url = "https://files.pythonhosted.org/packages/39/83/b6eb1e1181de994b29804e1e0d2dc677bece4177f588c71653093cb4f6d5/zstandard-0.24.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f7d3fe9e1483171e9183ffdb1fab07c5fef80a9c3840374a38ec2ab869ebae20", size = 5813161, upload-time = "2025-08-17T18:23:38.812Z" }, + { url = "https://files.pythonhosted.org/packages/f6/d3/2fb4166561591e9d75e8e35c79182aa9456644e2f4536f29e51216d1c513/zstandard-0.24.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:27b6fa72b57824a3f7901fc9cc4ce1c1c834b28f3a43d1d4254c64c8f11149d4", size = 5359831, upload-time = "2025-08-17T18:23:41.162Z" }, + { url = "https://files.pythonhosted.org/packages/11/94/6a9227315b774f64a67445f62152c69b4e5e49a52a3c7c4dad8520a55e20/zstandard-0.24.0-cp314-cp314-win32.whl", hash = "sha256:fdc7a52a4cdaf7293e10813fd6a3abc0c7753660db12a3b864ab1fb5a0c60c16", size = 444448, upload-time = "2025-08-17T18:23:45.151Z" }, + { url = "https://files.pythonhosted.org/packages/fc/de/67acaba311013e0798cb96d1a2685cb6edcdfc1cae378b297ea7b02c319f/zstandard-0.24.0-cp314-cp314-win_amd64.whl", hash = "sha256:656ed895b28c7e42dd5b40dfcea3217cfc166b6b7eef88c3da2f5fc62484035b", size = 516075, upload-time = "2025-08-17T18:23:42.8Z" }, + { url = "https://files.pythonhosted.org/packages/10/ae/45fd8921263cea0228b20aa31bce47cc66016b2aba1afae1c6adcc3dbb1f/zstandard-0.24.0-cp314-cp314-win_arm64.whl", hash = "sha256:0101f835da7de08375f380192ff75135527e46e3f79bef224e3c49cb640fef6a", size = 476847, upload-time = "2025-08-17T18:23:43.892Z" }, ]