diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index d9600942..f2db8342 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -12,7 +12,6 @@ We actively welcome your pull requests. 3. If you've changed APIs, update the documentation. 4. Ensure the test suite passes. 5. Make sure your code lints. -6. If you haven't already, complete the Contributor License Agreement ("CLA"). ### PR Description Format @@ -54,13 +53,6 @@ Example: - Deprecated setup scripts ``` -## Contributor License Agreement ("CLA") - -In order to accept your pull request, we need you to submit a CLA. You only need -to do this once to work on any of Facebook's open source projects. - -Complete your CLA here: - ## Issues We use GitHub issues to track public bugs. Please ensure your description is diff --git a/.github/labeler.yaml b/.github/labeler.yaml index a7643190..3a96c2d6 100644 --- a/.github/labeler.yaml +++ b/.github/labeler.yaml @@ -67,6 +67,7 @@ type/docs: - changed-files: - any-glob-to-any-file: "docs/**/*" - any-glob-to-any-file: "*.md" + - any-glob-to-any-file: "*.mdx" # Core Files Labels type/core: diff --git a/.gitignore b/.gitignore index 9c06aa2c..41fb2481 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Testing code notebooks/ +examples/.logfire # Logfire temp .logfire/ diff --git a/.hooks/check_pinned_hash_dependencies.py b/.hooks/check_pinned_hash_dependencies.py index ba9a7008..728f82a7 100755 --- a/.hooks/check_pinned_hash_dependencies.py +++ b/.hooks/check_pinned_hash_dependencies.py @@ -10,7 +10,9 @@ def __init__(self) -> None: self.pinned_pattern = re.compile(r"uses:\s+([^@\s]+)@([a-f0-9]{40})") # Pattern for actions with version tags (unpinned) - self.unpinned_pattern = re.compile(r"uses:\s+([^@\s]+)@(v\d+(?:\.\d+)*(?:-[a-zA-Z0-9]+(?:\.\d+)*)?)") + self.unpinned_pattern = re.compile( + r"uses:\s+([^@\s]+)@(v\d+(?:\.\d+)*(?:-[a-zA-Z0-9]+(?:\.\d+)*)?)", + ) # Pattern for all uses statements self.all_uses_pattern = re.compile(r"uses:\s+([^@\s]+)@([^\s\n]+)") @@ -30,16 +32,18 @@ def format_terminal_link(self, file_path: str, line_number: int) -> str: def get_line_numbers(self, content: str, pattern: re.Pattern[str]) -> list[tuple[str, int]]: """Find matches with their line numbers.""" matches = [] - for i, line in enumerate(content.splitlines(), 1): - for match in pattern.finditer(line): - matches.append((match.group(0), i)) + matches.extend( + (match.group(0), i) + for i, line in enumerate(content.splitlines(), 1) + for match in pattern.finditer(line) + ) return matches def check_file(self, file_path: str) -> bool: """Check a single file for unpinned dependencies.""" try: content = Path(file_path).read_text() - except Exception as e: + except (FileNotFoundError, PermissionError, IsADirectoryError, OSError) as e: print(f"\033[91mError reading file {file_path}: {e}\033[0m") return False @@ -84,7 +88,9 @@ def check_file(self, file_path: str) -> bool: has_errors = True print("\033[91m[!] Completely unpinned (no SHA or version):\033[0m") for match, line_num in unpinned_without_hash: - print(f" |- {match} \033[90m({self.format_terminal_link(file_path, line_num)})\033[0m") + print( + f" |- {match} \033[90m({self.format_terminal_link(file_path, line_num)})\033[0m", + ) # Print summary total_actions = len(pinned_matches) + len(unpinned_matches) + len(unpinned_without_hash) diff --git a/.hooks/generate_pr_description.py b/.hooks/generate_pr_description.py index 8da350f7..ba7c5959 100755 --- a/.hooks/generate_pr_description.py +++ b/.hooks/generate_pr_description.py @@ -9,14 +9,17 @@ import asyncio import os import typing as t +from pathlib import Path import rigging as rg import typer -TRUNCATION_WARNING = "\n---\n**Note**: Due to the large size of this diff, some content has been truncated." +TRUNCATION_WARNING = ( + "\n---\n**Note**: Due to the large size of this diff, some content has been truncated." +) -@rg.prompt # type: ignore +@rg.prompt def generate_pr_description(diff: str) -> t.Annotated[str, rg.Ctx("markdown")]: # type: ignore[empty-body] """ Analyze the provided git diff and create a PR description in markdown format. @@ -40,13 +43,19 @@ async def _run_git_command(args: list[str]) -> str: """ # Validate git exists in PATH git_path = "git" # Could use shutil.which("git") for more security - if not any(os.path.isfile(os.path.join(path, "git")) for path in os.environ["PATH"].split(os.pathsep)): + if not any( + Path(path).joinpath("git").is_file() for path in os.environ["PATH"].split(os.pathsep) + ): raise ValueError("Git executable not found in PATH") # Validate input parameters if not all(isinstance(arg, str) for arg in args): raise ValueError("All command arguments must be strings") + def check_return_code(return_code: int): + if return_code != 0: + raise RuntimeError(f"Git command failed: {stderr.decode()}") + # Use os.execv for more secure command execution try: # nosec B603 - Input is validated @@ -58,12 +67,15 @@ async def _run_git_command(args: list[str]) -> str: ) stdout, stderr = await proc.communicate() - if proc.returncode != 0: - raise RuntimeError(f"Git command failed: {stderr.decode()}") + check_return_code(proc.returncode) return stdout.decode().strip() - except Exception as e: - raise RuntimeError(f"Failed to execute git command: {e}") from e + except FileNotFoundError as e: + raise RuntimeError("Git executable not found or invalid command") from e + except asyncio.SubprocessError as e: + raise RuntimeError("Error occurred while running the subprocess") from e + except UnicodeDecodeError as e: + raise RuntimeError("Failed to decode the output of the git command") from e async def get_diff(base_ref: str, source_ref: str, *, exclude: list[str] | None = None) -> str: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d5b710c0..5635eb64 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -87,15 +87,3 @@ repos: entry: .hooks/prettier.sh language: script types: [json, yaml] - - - id: generate-readme - name: Generate README - description: Updates auto-generated sections in README.md - entry: python scripts/generate_readme.py - language: python - files: ^(pyproject\.toml|templates/README\.md\.j2)$ - pass_filenames: false - require_serial: true - additional_dependencies: - - jinja2 - - tomli>=2.0.1 diff --git a/README.md b/README.md index 8886fe8a..a23e355d 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,99 @@ -# sdk +

+ Logo +

- -
+

+Dreadnode Strikes SDK +

-[![Pre-Commit](https://github.com/dreadnode/python-template/actions/workflows/pre-commit.yaml/badge.svg)](https://github.com/dreadnode/python-template/actions/workflows/pre-commit.yaml) -[![Renovate](https://github.com/dreadnode/python-template/actions/workflows/renovate.yaml/badge.svg)](https://github.com/dreadnode/python-template/actions/workflows/renovate.yaml) +

+ PyPI - Python Version + PyPI - Version + GitHub License + Tests + Pre-Commit + Renovate +

-
- -Dreadnode SDK +
+ +Strikes is an platform for building, experimenting with, and evaluating AI security agent code. + +- **Experiment + Tasking + Observability** in a single place that's lightweight and scales. +- **Track your data** with parameters, inputs, and outputs all connected to your tasks. +- **Measure everything** with metrics throughout your code and anywhere you need them. +- **Scale your code** from a single run to thousands. + +```python +import dreadnode as dn +import rigging as rg + +from .tools import reversing_tools + +dn.configure() + +@dataclass +class Finding: + name: str + severity: str + description: str + exploit_code: str + +@dn.scorer(name="Score Finding") +async def score_finding(finding: Finding) -> float: + if finding.severity == "critical": + return 1.0 + elif finding.severity == "high": + return 0.8 + else: + return 0.2 + +@dn.task(scorers=[score_finding]) +@rg.prompt(tools=[reversing_tools]) +async def analyze_binary(binary: str) -> list[Finding]: + """ + Analyze the binary for vulnerabilities. + """ + ... + +with dn.run(tags=["reverse-engineering"]): + binary = "c2/downloads/service.exe" + + dn.log_params( + model="gpt-4", + temperature=0.5, + binary=binary + ) + + findings = await analyze_binary(binary) + + dn.log_metric("findings", len(findings)) +``` + +## Installation + +We publish every version to PyPi: +```bash +pip install -U dreadnode +``` + +If you want to build from source: +```bash +poetry install +``` + +See our **[installation guide](https://docs.dreadnode.io/strikes/install)** for more options. + +## Getting Started + +Read through our **[introduction guide](https://docs.dreadnode.io/strikes/intro)** in the docs. + +## Examples + +Check out **[dreadnode/example-agents](https://github.com/dreadnode/example-agents)** to find your favorite use case. \ No newline at end of file diff --git a/dreadnode/__init__.py b/dreadnode/__init__.py index abb8e224..a7bbd9cd 100644 --- a/dreadnode/__init__.py +++ b/dreadnode/__init__.py @@ -1,8 +1,9 @@ -from .main import DEFAULT_INSTANCE -from .score import Scorer -from .task import Task -from .tracing import RunSpan, Score, Span, TaskSpan -from .version import VERSION +from dreadnode.main import DEFAULT_INSTANCE, Dreadnode +from dreadnode.metric import Metric, MetricDict, Scorer +from dreadnode.object import Object +from dreadnode.task import Task +from dreadnode.tracing.span import RunSpan, Span, TaskSpan +from dreadnode.version import VERSION configure = DEFAULT_INSTANCE.configure shutdown = DEFAULT_INSTANCE.shutdown @@ -12,29 +13,39 @@ task = DEFAULT_INSTANCE.task task_span = DEFAULT_INSTANCE.task_span run = DEFAULT_INSTANCE.run +scorer = DEFAULT_INSTANCE.scorer +task_span = DEFAULT_INSTANCE.task_span push_update = DEFAULT_INSTANCE.push_update log_metric = DEFAULT_INSTANCE.log_metric log_param = DEFAULT_INSTANCE.log_param log_params = DEFAULT_INSTANCE.log_params -log_score = DEFAULT_INSTANCE.log_score +log_input = DEFAULT_INSTANCE.log_input +log_inputs = DEFAULT_INSTANCE.log_inputs +log_output = DEFAULT_INSTANCE.log_output +link_objects = DEFAULT_INSTANCE.link_objects +log_artifact = DEFAULT_INSTANCE.log_artifact __version__ = VERSION __all__ = [ - "configure", - "shutdown", - "span", - "task", - "run", - "log_metric", - "log_param", + "Dreadnode", + "Metric", + "MetricDict", + "Object", "Run", - "Task", - "Scorer", + "RunSpan", "Score", - "TaskSpan", + "Scorer", "Span", - "RunSpan", + "Task", + "TaskSpan", "__version__", + "configure", + "log_metric", + "log_param", + "run", + "shutdown", + "span", + "task", ] diff --git a/dreadnode/api/client.py b/dreadnode/api/client.py index 4594c9be..674f8c1f 100644 --- a/dreadnode/api/client.py +++ b/dreadnode/api/client.py @@ -1,12 +1,26 @@ +import io import json import typing as t import httpx +import pandas as pd from pydantic import BaseModel -from rich import print as rich_print - -from ..version import VERSION -from .strikes import StrikesClient +from ulid import ULID + +from dreadnode.util import logger +from dreadnode.version import VERSION + +from .models import ( + MetricAggregationType, + Project, + Run, + StatusFilter, + Task, + TimeAggregationType, + TimeAxisType, + TraceSpan, + UserDataCredentials, +) ModelT = t.TypeVar("ModelT", bound=BaseModel) @@ -14,8 +28,6 @@ class ApiClient: """Client for the Dreadnode API.""" - strikes: StrikesClient - def __init__( self, base_url: str, @@ -41,25 +53,23 @@ def __init__( self._client.event_hooks["request"].append(self._log_request) self._client.event_hooks["response"].append(self._log_response) - self.strikes = StrikesClient(self) - def _log_request(self, request: httpx.Request) -> None: """Log every request to the console if debug is enabled.""" - rich_print("-------------------------------------------") - rich_print(f"[bold]{request.method}[/] {request.url}") - rich_print("Headers:", request.headers) - rich_print("Content:", request.content) - rich_print("-------------------------------------------") + logger.debug("-------------------------------------------") + logger.debug("%s %s", request.method, request.url) + logger.debug("Headers: %s", request.headers) + logger.debug("Content: %s", request.content) + logger.debug("-------------------------------------------") def _log_response(self, response: httpx.Response) -> None: """Log every response to the console if debug is enabled.""" - rich_print("-------------------------------------------") - rich_print(f"Response: {response.status_code}") - rich_print("Headers:", response.headers) - rich_print("Content:", response.read()) - rich_print("--------------------------------------------") + logger.debug("-------------------------------------------") + logger.debug("Response: %s", response.status_code) + logger.debug("Headers: %s", response.headers) + logger.debug("Content: %s", response.read()) + logger.debug("--------------------------------------------") def _get_error_message(self, response: httpx.Response) -> str: """Get the error message from the response.""" @@ -67,35 +77,173 @@ def _get_error_message(self, response: httpx.Response) -> str: try: obj = response.json() return f"{response.status_code}: {obj.get('detail', json.dumps(obj))}" - except Exception: + except Exception: # noqa: BLE001 return str(response.content) def _request( self, method: str, path: str, - query_params: dict[str, t.Any] | None = None, + params: dict[str, t.Any] | None = None, json_data: dict[str, t.Any] | None = None, ) -> httpx.Response: """Make a raw request to the API.""" - return self._client.request(method, path, json=json_data, params=query_params) + return self._client.request(method, path, json=json_data, params=params) def request( self, method: str, path: str, - query_params: dict[str, t.Any] | None = None, + params: dict[str, t.Any] | None = None, json_data: dict[str, t.Any] | None = None, ) -> httpx.Response: """Make a request to the API. Raise an exception for non-200 status codes.""" - response = self._request(method, path, query_params, json_data) - if response.status_code == 401: - raise Exception("Authentication failed, please check your API token.") + response = self._request(method, path, params, json_data) + if response.status_code == 401: # noqa: PLR2004 + raise RuntimeError("Authentication failed, please check your API token.") try: response.raise_for_status() - return response except httpx.HTTPStatusError as e: - raise Exception(self._get_error_message(response)) from e + raise RuntimeError(self._get_error_message(response)) from e + + return response + + # This currently won't work with API keys + # def get_user(self) -> UserResponse: + # response = self.request("GET", "/user") + # return UserResponse(**response.json()) + + def list_projects(self) -> list[Project]: + response = self.request("GET", "/strikes/projects") + return [Project(**project) for project in response.json()] + + def get_project(self, project: str) -> Project: + response = self.request("GET", f"/strikes/projects/{project!s}") + return Project(**response.json()) + + def list_runs(self, project: str) -> list[Run]: + response = self.request("GET", f"/strikes/projects/{project!s}/runs") + return [Run(**run) for run in response.json()] + + def get_run(self, run: str | ULID) -> Run: + response = self.request("GET", f"/strikes/projects/runs/{run!s}") + return Run(**response.json()) + + def get_run_tasks(self, run: str | ULID) -> list[Task]: + response = self.request("GET", f"/strikes/projects/runs/{run!s}/tasks") + return [Task(**task) for task in response.json()] + + def get_run_trace(self, run: str | ULID) -> list[Task | TraceSpan]: + response = self.request("GET", f"/strikes/projects/runs/{run!s}/spans") + spans: list[Task | TraceSpan] = [] + for item in response.json(): + if "parent_task_span_id" in item: + spans.append(Task(**item)) + else: + spans.append(TraceSpan(**item)) + return spans + + # Data exports + + def export_runs( + self, + project: str, + *, + filter: str | None = None, + # format: ExportFormat = "parquet", + status: StatusFilter = "completed", + aggregations: list[MetricAggregationType] | None = None, + ) -> pd.DataFrame: + response = self.request( + "GET", + f"/strikes/projects/{project!s}/export", + params={ + "format": "parquet", + "status": status, + **({"filter": filter} if filter else {}), + **({"aggregations": aggregations} if aggregations else {}), + }, + ) + return pd.read_parquet(io.BytesIO(response.content)) + + def export_metrics( + self, + project: str, + *, + filter: str | None = None, + # format: ExportFormat = "parquet", + status: StatusFilter = "completed", + metrics: list[str] | None = None, + aggregations: list[MetricAggregationType] | None = None, + ) -> pd.DataFrame: + response = self.request( + "GET", + f"/strikes/projects/{project!s}/export/metrics", + params={ + "format": "parquet", + "status": status, + "filter": filter, + **({"metrics": metrics} if metrics else {}), + **({"aggregations": aggregations} if aggregations else {}), + }, + ) + return pd.read_parquet(io.BytesIO(response.content)) + + def export_parameters( + self, + project: str, + *, + filter: str | None = None, + # format: ExportFormat = "parquet", + status: StatusFilter = "completed", + parameters: list[str] | None = None, + metrics: list[str] | None = None, + aggregations: list[MetricAggregationType] | None = None, + ) -> pd.DataFrame: + response = self.request( + "GET", + f"/strikes/projects/{project!s}/export/parameters", + params={ + "format": "parquet", + "status": status, + "filter": filter, + **({"parameters": parameters} if parameters else {}), + **({"metrics": metrics} if metrics else {}), + **({"aggregations": aggregations} if aggregations else {}), + }, + ) + return pd.read_parquet(io.BytesIO(response.content)) + + def export_timeseries( + self, + project: str, + *, + filter: str | None = None, + # format: ExportFormat = "parquet", + status: StatusFilter = "completed", + metrics: list[str] | None = None, + time_axis: TimeAxisType = "relative", + aggregations: list[TimeAggregationType] | None = None, + ) -> pd.DataFrame: + response = self.request( + "GET", + f"/strikes/projects/{project!s}/export/timeseries", + params={ + "format": "parquet", + "status": status, + "filter": filter, + "time_axis": time_axis, + **({"metrics": metrics} if metrics else {}), + **({"aggregation": aggregations} if aggregations else {}), + }, + ) + return pd.read_parquet(io.BytesIO(response.content)) + + # User data access + + def get_user_data_credentials(self) -> UserDataCredentials: + response = self.request("GET", "/user-data/credentials") + return UserDataCredentials(**response.json()) diff --git a/dreadnode/api/models.py b/dreadnode/api/models.py new file mode 100644 index 00000000..0c11913a --- /dev/null +++ b/dreadnode/api/models.py @@ -0,0 +1,210 @@ +import typing as t +from datetime import datetime +from uuid import UUID + +from pydantic import BaseModel, Field +from ulid import ULID + +AnyDict = dict[str, t.Any] + +# User + + +class UserAPIKey(BaseModel): + key: str + + +class UserResponse(BaseModel): + id: UUID + email_address: str + username: str + api_key: UserAPIKey + + +# Strikes + +SpanStatus = t.Literal[ + "pending", # A pending span has been created + "completed", # The span has been finished + "failed", # The raised an exception +] + +ExportFormat = t.Literal["csv", "json", "jsonl", "parquet"] +StatusFilter = t.Literal["all", "completed", "failed"] +TimeAxisType = t.Literal["wall", "relative", "step"] +TimeAggregationType = t.Literal["max", "min", "sum", "count"] +MetricAggregationType = t.Literal[ + "avg", + "median", + "min", + "max", + "sum", + "first", + "last", + "count", + "std", + "var", +] + + +class SpanException(BaseModel): + type: str + message: str + stacktrace: str + + +class SpanEvent(BaseModel): + timestamp: datetime + name: str + attributes: AnyDict + + +class SpanLink(BaseModel): + trace_id: str + span_id: str + attributes: AnyDict + + +class TraceLog(BaseModel): + timestamp: datetime + body: str + severity: str + service: str | None + trace_id: str | None + span_id: str | None + attributes: AnyDict + container: str | None + + +class TraceSpan(BaseModel): + timestamp: datetime + duration: int + trace_id: str + span_id: str + parent_span_id: str | None + service_name: str | None + status: SpanStatus + exception: SpanException | None + name: str + attributes: AnyDict + resource_attributes: AnyDict + events: list[SpanEvent] + links: list[SpanLink] + + +class Metric(BaseModel): + value: float + step: int + timestamp: datetime + attributes: AnyDict + + +class ObjectRef(BaseModel): + name: str + label: str + hash: str + + +class ObjectUri(BaseModel): + hash: str + schema_hash: str + uri: str + size: int + type: t.Literal["uri"] + + +class ObjectVal(BaseModel): + hash: str + schema_hash: str + value: t.Any + type: t.Literal["val"] + + +Object = ObjectUri | ObjectVal + + +class V0Object(BaseModel): + name: str + label: str + value: t.Any + + +class Run(BaseModel): + id: ULID + name: str + span_id: str + trace_id: str + timestamp: datetime + duration: int + status: SpanStatus + exception: SpanException | None + tags: set[str] + params: AnyDict + metrics: dict[str, list[Metric]] + inputs: list[ObjectRef] + outputs: list[ObjectRef] + objects: dict[str, Object] + object_schemas: AnyDict + schema_: AnyDict = Field(alias="schema") + + +class Task(BaseModel): + name: str + span_id: str + trace_id: str + parent_span_id: str | None + parent_task_span_id: str | None + timestamp: datetime + duration: int + status: SpanStatus + exception: SpanException | None + tags: set[str] + params: AnyDict + metrics: dict[str, list[Metric]] + inputs: list[ObjectRef] | list[V0Object] # v0 compat + outputs: list[ObjectRef] | list[V0Object] # v0 compat + schema_: AnyDict = Field(alias="schema") + attributes: AnyDict + resource_attributes: AnyDict + events: list[SpanEvent] + links: list[SpanLink] + + +class Project(BaseModel): + id: UUID + key: str + name: str + description: str | None + created_at: datetime + updated_at: datetime + run_count: int + last_run: Run | None + + +# Derived types + + +class TaskTree(BaseModel): + task: Task + children: list["TaskTree"] = [] + + +class SpanTree(BaseModel): + """Tree representation of a trace span with its children""" + + span: Task | TraceSpan + children: list["SpanTree"] = [] + + +# User data credentials + + +class UserDataCredentials(BaseModel): + access_key_id: str + secret_access_key: str + session_token: str + expiration: datetime + region: str + bucket: str + prefix: str + endpoint: str | None diff --git a/dreadnode/api/strikes.py b/dreadnode/api/strikes.py deleted file mode 100644 index ef3f26bc..00000000 --- a/dreadnode/api/strikes.py +++ /dev/null @@ -1,275 +0,0 @@ -import io -import typing as t -from datetime import datetime -from uuid import UUID - -import pandas as pd # type: ignore -from pydantic import BaseModel, Field -from ulid import ULID - -if t.TYPE_CHECKING: - from .client import ApiClient - -# Models - -StrikeSpanStatus = t.Literal[ - "pending", # A pending span has been created - "completed", # The span has been finished - "failed", # The raised an exception -] - -ExportFormat = t.Literal["csv", "json", "jsonl", "parquet"] -StatusFilter = t.Literal["all", "completed", "failed"] -TimeAxisType = t.Literal["wall", "relative", "step"] -TimeAggregationType = t.Literal["max", "min", "sum", "count"] -MetricAggregationType = t.Literal["avg", "median", "min", "max", "sum", "first", "last", "count", "std", "var"] - - -class StrikeSpanException(BaseModel): - type: str - message: str - stacktrace: str - - -class StrikeSpanEvent(BaseModel): - timestamp: datetime - name: str - attributes: dict[str, str] - - -class StrikeSpanLink(BaseModel): - trace_id: str - span_id: str - attributes: dict[str, str] - - -class StrikeTraceLog(BaseModel): - timestamp: datetime - body: str - severity: str - service: str | None - trace_id: str | None - span_id: str | None - attributes: dict[str, str] - container: str | None - - -class StrikeTraceSpan(BaseModel): - timestamp: datetime - duration: int - trace_id: str = Field(repr=False) - span_id: str - parent_span_id: str | None = Field(repr=False) - service_name: str | None = Field(repr=False) - status: StrikeSpanStatus - exception: StrikeSpanException | None - name: str - attributes: dict[str, str] = Field(repr=False) - resource_attributes: dict[str, str] = Field(repr=False) - events: list[StrikeSpanEvent] = Field(repr=False) - links: list[StrikeSpanLink] = Field(repr=False) - - -class ProjectMetric(BaseModel): - timestamp: datetime - value: float - step: int - - -ProjectParams = dict[str, t.Any] - - -class ProjectTaskScore(BaseModel): - name: str - value: float - attributes: dict[str, t.Any] - timestamp: datetime - - -class StrikeProjectRunResponse(BaseModel): - id: ULID - name: str - span_id: str - trace_id: str = Field(repr=False) - timestamp: datetime - duration: int - status: StrikeSpanStatus - exception: StrikeSpanException | None - tags: set[str] - params: dict[str, t.Any] = Field(repr=False) - metrics: dict[str, list[ProjectMetric]] = Field(repr=False) - schema_: dict[str, t.Any] = Field(alias="schema", repr=False) - - -class StrikeProjectTaskResponse(BaseModel): - name: str - span_id: str - trace_id: str = Field(repr=False) - parent_span_id: str | None = Field(repr=False) - parent_task_span_id: str | None = Field(repr=False) - timestamp: datetime - duration: int - status: StrikeSpanStatus - exception: StrikeSpanException | None - tags: set[str] - args: dict[str, t.Any] - output: t.Any - scores: list[ProjectTaskScore] - schema_: dict[str, t.Any] = Field(alias="schema", repr=False) - attributes: dict[str, str] = Field(repr=False) - resource_attributes: dict[str, str] = Field(repr=False) - events: list[StrikeSpanEvent] = Field(repr=False) - links: list[StrikeSpanLink] = Field(repr=False) - - -class CreateStrikeProjectRequest(BaseModel): - name: str - description: str | None = None - - -class UpdateStrikeProjectRequest(BaseModel): - name: str - description: str | None = None - - -class StrikeProjectResponse(BaseModel): - id: UUID - key: str - name: str - description: str | None - created_at: datetime - updated_at: datetime - run_count: int - last_run: StrikeProjectRunResponse | None = Field(repr=False) - - -# Client - - -class StrikesClient: - def __init__(self, client: "ApiClient") -> None: - self._client = client - - def list_projects(self) -> list[StrikeProjectResponse]: - response = self._client.request("GET", "/strikes/projects") - return [StrikeProjectResponse(**project) for project in response.json()] - - def get_project(self, project: str) -> StrikeProjectResponse: - response = self._client.request("GET", f"/strikes/projects/{str(project)}") - return StrikeProjectResponse(**response.json()) - - def list_runs(self, project: str) -> list[StrikeProjectRunResponse]: - response = self._client.request("GET", f"/strikes/projects/{str(project)}/runs") - return [StrikeProjectRunResponse(**run) for run in response.json()] - - def get_run(self, run: str | ULID) -> StrikeProjectRunResponse: - response = self._client.request("GET", f"/strikes/projects/runs/{str(run)}") - return StrikeProjectRunResponse(**response.json()) - - def get_run_tasks(self, run: str | ULID) -> list[StrikeProjectTaskResponse]: - response = self._client.request("GET", f"/strikes/projects/runs/{str(run)}/tasks") - return [StrikeProjectTaskResponse(**task) for task in response.json()] - - def get_run_trace(self, run: str | ULID) -> list[StrikeProjectTaskResponse | StrikeTraceSpan]: - response = self._client.request("GET", f"/strikes/projects/runs/{str(run)}/spans") - spans: list[StrikeProjectTaskResponse | StrikeTraceSpan] = [] - for item in response.json(): - if "parent_task_span_id" in item: - spans.append(StrikeProjectTaskResponse(**item)) - else: - spans.append(StrikeTraceSpan(**item)) - return spans - - def export_runs( - self, - project: str, - *, - filter: str | None = None, - # format: ExportFormat = "parquet", - status: StatusFilter = "all", - aggregations: list[MetricAggregationType] | None = None, - ) -> pd.DataFrame: - response = self._client.request( - "GET", - f"/strikes/projects/{str(project)}/export", - query_params={ - "format": "parquet", - "status": status, - **({"filter": filter} if filter else {}), - **({"aggregations": aggregations} if aggregations else {}), - }, - ) - return pd.read_parquet(io.BytesIO(response.content)) - - def export_metrics( - self, - project: str, - *, - filter: str | None = None, - # format: ExportFormat = "parquet", - status: StatusFilter = "all", - metrics: list[str] | None = None, - aggregations: list[MetricAggregationType] | None = None, - ) -> pd.DataFrame: - response = self._client.request( - "GET", - f"/strikes/projects/{str(project)}/export/metrics", - query_params={ - "format": "parquet", - "status": status, - "filter": filter, - **({"metrics": metrics} if metrics else {}), - **({"aggregations": aggregations} if aggregations else {}), - }, - ) - return pd.read_parquet(io.BytesIO(response.content)) - - def export_parameters( - self, - project: str, - *, - filter: str | None = None, - # format: ExportFormat = "parquet", - status: StatusFilter = "all", - parameters: list[str] | None = None, - metrics: list[str] | None = None, - aggregations: list[MetricAggregationType] | None = None, - ) -> pd.DataFrame: - response = self._client.request( - "GET", - f"/strikes/projects/{str(project)}/export/parameters", - query_params={ - "format": "parquet", - "status": status, - "filter": filter, - **({"parameters": parameters} if parameters else {}), - **({"metrics": metrics} if metrics else {}), - **({"aggregations": aggregations} if aggregations else {}), - }, - ) - return pd.read_parquet(io.BytesIO(response.content)) - - def export_timeseries( - self, - project: str, - *, - filter: str | None = None, - # format: ExportFormat = "parquet", - status: StatusFilter = "all", - metrics: list[str] | None = None, - time_axis: TimeAxisType = "relative", - aggregations: list[TimeAggregationType] | None = None, - ) -> pd.DataFrame: - response = self._client.request( - "GET", - f"/strikes/projects/{str(project)}/export/timeseries", - query_params={ - "format": "parquet", - "status": status, - "filter": filter, - "time_axis": time_axis, - **({"metrics": metrics} if metrics else {}), - **({"aggregation": aggregations} if aggregations else {}), - }, - ) - return pd.read_parquet(io.BytesIO(response.content)) diff --git a/dreadnode/artifact/__init__.py b/dreadnode/artifact/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/dreadnode/artifact/merger.py b/dreadnode/artifact/merger.py new file mode 100644 index 00000000..f7ddef98 --- /dev/null +++ b/dreadnode/artifact/merger.py @@ -0,0 +1,575 @@ +""" +Utility for merging artifact tree structures while preserving directory hierarchy. +""" + +import hashlib +from pathlib import Path +from typing import cast + +from dreadnode.artifact.tree_builder import DirectoryNode, FileNode + + +class ArtifactMerger: + """ + Class responsible for merging artifact tree structures. + Handles overlapping directory structures and efficiently combines artifacts. + + Example: + ```python + # Create a merger instance + merger = ArtifactMerger() + + # Add multiple artifact trees + merger.add_tree(tree1) # First tree gets added directly + merger.add_tree(tree2) # Second tree gets merged if it overlaps + + # Get the merged result + merged_trees = merger.get_merged_trees() + ``` + """ + + def __init__(self) -> None: + self._path_map: dict[str, DirectoryNode | FileNode] = {} + # Maps file hashes to all matching files + self._hash_map: dict[str, list[FileNode]] = {} + self._merged_trees: list[DirectoryNode] = [] + + def add_tree(self, new_tree: DirectoryNode) -> None: + """ + Add a new artifact tree, merging with existing trees if needed. + + This method analyzes the new tree and determines how to integrate it + with existing trees, handling parent/child relationships and overlaps. + + Args: + new_tree: New directory tree to add + + Example: + ```python + # Add first tree (e.g., /data/audio/sub1) + merger.add_tree({ + "type": "dir", + "dir_path": "/data/audio/sub1", + "hash": "abc123", + "children": [...] + }) + + # Add parent directory later (e.g., /data/audio) + # The merger will recognize the relationship and restructure + merger.add_tree({ + "type": "dir", + "dir_path": "/data/audio", + "hash": "def456", + "children": [...] + }) + ``` + """ + # First artifact - just add it + if not self._merged_trees: + self._merged_trees = [new_tree] + self._build_maps(new_tree) + return + + # Get new tree's path + new_dir_path = new_tree["dir_path"] + + # Check for direct match with existing trees + for existing_tree in self._merged_trees: + if existing_tree["dir_path"] == new_dir_path: + # Same directory - merge them + self._merge_directory_nodes(existing_tree, new_tree) + self._build_maps() # Rebuild maps + return + + # Check if new tree is parent of any existing trees + children_to_remove = [] + for existing_tree in self._merged_trees: + existing_dir_path = existing_tree["dir_path"] + + # New tree is parent of existing tree + if existing_dir_path.startswith(new_dir_path + "/"): + rel_path = existing_dir_path[len(new_dir_path) + 1 :].split("/") + self._place_tree_at_path(new_tree, existing_tree, rel_path) + children_to_remove.append(existing_tree) + + # Remove trees that are now incorporated into new tree + if children_to_remove: + for child in children_to_remove: + if child in self._merged_trees: + self._merged_trees.remove(child) + self._merged_trees.append(new_tree) + self._build_maps() # Rebuild maps + return + + # Check if new tree is child of an existing tree + for existing_tree in self._merged_trees: + existing_dir_path = existing_tree["dir_path"] + + if new_dir_path.startswith(existing_dir_path + "/"): + rel_path = new_dir_path[len(existing_dir_path) + 1 :].split("/") + self._place_tree_at_path(existing_tree, new_tree, rel_path) + self._build_maps() # Rebuild maps + return + + # Try to find and handle overlaps + new_path_map: dict[str, DirectoryNode | FileNode] = {} + new_hash_map: dict[str, list[FileNode]] = {} + self._build_path_and_hash_maps(new_tree, new_path_map, new_hash_map) + + # Find common paths between existing and new tree + path_overlaps = set(self._path_map.keys()) & set(new_path_map.keys()) + + if path_overlaps and self._handle_overlaps(path_overlaps, new_path_map): + # Successfully merged via overlaps + self._build_maps() # Rebuild maps + return + + # If we get here, add new tree as a separate root + self._merged_trees.append(new_tree) + self._build_maps() # Rebuild maps + + def get_merged_trees(self) -> list[DirectoryNode]: + """ + Get the current merged trees. + + Returns: + List of merged directory trees + + Example: + ```python + # Get the merged trees after adding multiple trees + trees = merger.get_merged_trees() + + # Typically there will be a single root tree if all added trees are related + if len(trees) == 1: + root_tree = trees[0] + print(f"Root directory: {root_tree['dir_path']}") + ``` + """ + return self._merged_trees + + def _place_tree_at_path( + self, + parent_tree: DirectoryNode, + child_tree: DirectoryNode, + path_parts: list[str], + ) -> None: + """ + Place child_tree at the specified path under parent_tree. + + This creates any necessary intermediate directories and then merges + the child tree at the correct location in the parent tree. + + Args: + parent_tree: The parent tree to place under + child_tree: The child tree to place + path_parts: Path components from parent to child + + Example: + ```python + # Internal use to place /data/audio/sub1 under /data + # path_parts would be ['audio', 'sub1'] + self._place_tree_at_path( + parent_tree=data_tree, # /data + child_tree=sub1_tree, # /data/audio/sub1 + path_parts=['audio', 'sub1'] + ) + ``` + """ + current = parent_tree + + # Navigate to the correct location, creating directories as needed + for part in path_parts: + if not part: # Skip empty path parts + continue + + # Look for existing directory + next_node = None + for child in current["children"]: + if child["type"] == "dir" and Path(child["dir_path"]).name == part: + next_node = child + break + + # Create directory if it doesn't exist + if next_node is None: + next_dir_path = f"{current['dir_path']}/{part}" + next_node = { + "type": "dir", + "dir_path": next_dir_path, + "hash": "", + "children": [], + } + current["children"].append(next_node) + + current = next_node + + # Merge the trees at the final location + self._merge_directory_nodes(current, child_tree) + + def _handle_overlaps( + self, + overlaps: set[str], + new_path_map: dict[str, DirectoryNode | FileNode], + ) -> bool: + """ + Handle overlapping paths between trees. + + This method processes paths that exist in both the existing trees + and the new tree, merging directories and handling file conflicts. + + Args: + overlaps: Set of overlapping paths + new_path_map: Path map for the new tree + + Returns: + True if the tree was merged, False otherwise + + Example: + ```python + # Internal use when two directories have some paths in common + # but neither is a parent of the other + overlapping_paths = {'/data/shared/file1.txt', '/data/shared/configs'} + result = self._handle_overlaps( + overlaps=overlapping_paths, + new_path_map={'/data/shared/file1.txt': file_node, ...} + ) + # If result is True, the trees were successfully merged + ``` + """ + merged = False + + for path in sorted(overlaps, key=len): + existing_node = self._path_map.get(path) + new_node = new_path_map.get(path) + + if not existing_node or not new_node: + continue + + if existing_node["type"] == "dir" and new_node["type"] == "dir": + # Both are directories - merge them + self._merge_directory_nodes( + cast("DirectoryNode", existing_node), + cast("DirectoryNode", new_node), + ) + merged = True + elif existing_node["type"] == "file" and new_node["type"] == "file": + # Both are files - propagate URIs and update if hash differs + existing_file = cast("FileNode", existing_node) + new_file = cast("FileNode", new_node) + + if existing_file["hash"] != new_file["hash"]: + # Find the parent directory and update the file + for tree in self._merged_trees: + if self._update_file_in_tree(tree, existing_file, new_file): + merged = True + break + else: + # Same hash - ensure URI is propagated + self._propagate_uri(existing_file, new_file) + merged = True + + return merged + + def _propagate_uri(self, file1: FileNode, file2: FileNode) -> None: + """ + Ensure URIs are propagated between files with the same hash. + + If one file has a URI and the other doesn't, the URI will be copied. + + Args: + file1: First file node + file2: Second file node + + Example: + ```python + # Internal use to ensure URIs are shared between identical files + # If file1 has a URI but file2 doesn't, file2 will get file1's URI + self._propagate_uri( + file1={"type": "file", "uri": "s3://bucket/file.txt", ...}, + file2={"type": "file", "uri": "", ...} + ) + # After: file2["uri"] == "s3://bucket/file.txt" + ``` + """ + if not file1["uri"] and file2["uri"]: + file1["uri"] = file2["uri"] + elif not file2["uri"] and file1["uri"]: + file2["uri"] = file1["uri"] + + def _update_file_in_tree( + self, + tree: DirectoryNode, + old_file: FileNode, + new_file: FileNode, + ) -> bool: + """ + Update a file in a directory tree. + + This replaces old_file with new_file in the tree, recursively searching + if necessary. + + Args: + tree: The directory tree to search + old_file: The file to replace + new_file: The new file + + Returns: + True if the file was found and updated + + Example: + ```python + # Internal use to replace an outdated file with a newer version + success = self._update_file_in_tree( + tree=root_tree, + old_file={"type": "file", "hash": "abc123", ...}, + new_file={"type": "file", "hash": "def456", ...} + ) + # If success is True, the file was found and replaced + ``` + """ + for i, child in enumerate(tree["children"]): + if child is old_file: + tree["children"][i] = new_file + return True + + if child["type"] == "dir" and self._update_file_in_tree( + cast("DirectoryNode", child), + old_file, + new_file, + ): + return True + return False + + def _build_maps(self, new_tree: DirectoryNode | None = None) -> None: + """ + Build or rebuild the path and hash maps. + + This method populates the internal path and hash maps that enable + efficient lookups during tree merging. + + Args: + new_tree: Optional new tree to add directly to the maps + + Example: + ```python + # Internal use to initialize maps with a new tree + self._build_maps(new_tree=first_tree) + + # Or to rebuild all maps after changes + self._build_maps() + ``` + """ + self._path_map.clear() + self._hash_map.clear() + + if new_tree: + self._build_path_and_hash_maps(new_tree, self._path_map, self._hash_map) + else: + for tree in self._merged_trees: + self._build_path_and_hash_maps(tree, self._path_map, self._hash_map) + + def _build_path_and_hash_maps( + self, + node: DirectoryNode | FileNode, + path_map: dict[str, DirectoryNode | FileNode], + hash_map: dict[str, list[FileNode]], + ) -> None: + """ + Build both path and hash maps simultaneously. + + This method recursively processes a node (file or directory) and adds + it to the appropriate maps. + + Args: + node: The node to process + path_map: Map of paths to nodes + hash_map: Map of file hashes to file nodes + + Example: + ```python + # Internal use to build maps for a tree + path_map = {} + hash_map = {} + self._build_path_and_hash_maps( + node=root_tree, + path_map=path_map, + hash_map=hash_map + ) + # After: path_map contains all paths, hash_map contains all file hashes + ``` + """ + if node["type"] == "dir": + # Add directory to path map + dir_node = cast("DirectoryNode", node) + dir_path = dir_node["dir_path"] + path_map[dir_path] = dir_node + + # Process children + for child in dir_node["children"]: + self._build_path_and_hash_maps(child, path_map, hash_map) + else: # File node + # Add file to path map + file_node = cast("FileNode", node) + file_path = file_node["final_real_path"] + path_map[file_path] = file_node + + # Add file to hash map + file_hash = file_node["hash"] + if file_hash not in hash_map: + hash_map[file_hash] = [] + hash_map[file_hash].append(file_node) + + def _merge_directory_nodes(self, target_dir: DirectoryNode, source_dir: DirectoryNode) -> None: + """ + Merge contents from source directory into target directory. + + This combines children from both directories, handling duplicates + and updating files as needed. + + Args: + target_dir: Directory to merge into + source_dir: Directory to merge from + + Example: + ```python + # Internal use to merge two directory nodes + self._merge_directory_nodes( + target_dir={"type": "dir", "dir_path": "/data", "children": [...]}, + source_dir={"type": "dir", "dir_path": "/data", "children": [...]} + ) + # After: target_dir contains all children from both directories + ``` + """ + # Delegate file and directory processing to separate methods to reduce branches + path_to_index, hash_to_index = self._build_indices(target_dir) + + # Process each child from source + for source_child in source_dir["children"]: + if source_child["type"] == "dir": + self._merge_directory_child( + target_dir, + cast("DirectoryNode", source_child), + path_to_index, + ) + else: # file + self._merge_file_child( + target_dir, + cast("FileNode", source_child), + path_to_index, + hash_to_index, + ) + + # Update hash + self._update_directory_hash(target_dir) + + def _build_indices(self, dir_node: DirectoryNode) -> tuple[dict[str, int], dict[str, int]]: + """ + Build indices for efficient child lookups. + + Returns: + A tuple of (path_to_index, hash_to_index) dictionaries + """ + path_to_index: dict[str, int] = {} + hash_to_index: dict[str, int] = {} + + for i, child in enumerate(dir_node["children"]): + if child["type"] == "dir": + path_to_index[cast("DirectoryNode", child)["dir_path"]] = i + else: # file + file_child = cast("FileNode", child) + path_to_index[file_child["final_real_path"]] = i + hash_to_index[file_child["hash"]] = i + + return path_to_index, hash_to_index + + def _merge_directory_child( + self, + target_dir: DirectoryNode, + source_dir: DirectoryNode, + path_to_index: dict[str, int], + ) -> None: + """Merge a directory child from source into target directory.""" + dir_path = source_dir["dir_path"] + if dir_path in path_to_index: + # Directory exists in both - merge recursively + index = path_to_index[dir_path] + existing_child = target_dir["children"][index] + if existing_child["type"] == "dir": + self._merge_directory_nodes( + cast("DirectoryNode", existing_child), + source_dir, + ) + else: + # Directory only in source - add to target + target_dir["children"].append(source_dir) + + def _merge_file_child( + self, + target_dir: DirectoryNode, + source_file: FileNode, + path_to_index: dict[str, int], + hash_to_index: dict[str, int], + ) -> None: + """Merge a file child from source into target directory.""" + file_path = source_file["final_real_path"] + file_hash = source_file["hash"] + + if file_path in path_to_index: + # File exists at same path - update if hash differs + index = path_to_index[file_path] + existing_child = target_dir["children"][index] + if existing_child["hash"] != file_hash: + target_dir["children"][index] = source_file + elif existing_child["type"] == "file": + # Same file - propagate URI if needed + self._propagate_uri(cast("FileNode", existing_child), source_file) + elif file_hash in hash_to_index: + # Same file content exists but at different path + index = hash_to_index[file_hash] + existing_child = target_dir["children"][index] + if existing_child["type"] == "file": + # Propagate URI if needed + self._propagate_uri(cast("FileNode", existing_child), source_file) + # Keep both files since they're at different paths + target_dir["children"].append(source_file) + else: + # File only in source - add to target + target_dir["children"].append(source_file) + + def _update_directory_hash(self, dir_node: DirectoryNode) -> str: + """ + Update the hash of a directory based on its children. + + This computes a content-based hash for a directory by combining + the hashes of all its children. + + Args: + dir_node: The directory to update + + Returns: + The updated hash + + Example: + ```python + # Internal use to compute directory hash after changes + new_hash = self._update_directory_hash( + dir_node={"type": "dir", "children": [...]} + ) + # After: dir_node["hash"] is updated and returned + ``` + """ + child_hashes = [] + + for child in dir_node["children"]: + if child["type"] == "file": + child_hashes.append(cast(FileNode, child)["hash"]) # noqa: TC006 + else: + child_hash = self._update_directory_hash(cast(DirectoryNode, child)) # noqa: TC006 + child_hashes.append(child_hash) + + child_hashes.sort() # Ensure consistent hash regardless of order + hash_input = "|".join(child_hashes) + dir_hash = hashlib.sha1(hash_input.encode()).hexdigest()[:16] # noqa: S324 + + dir_node["hash"] = dir_hash + return dir_hash diff --git a/dreadnode/artifact/storage.py b/dreadnode/artifact/storage.py new file mode 100644 index 00000000..43f538c3 --- /dev/null +++ b/dreadnode/artifact/storage.py @@ -0,0 +1,126 @@ +""" +Artifact storage implementation for fsspec-compatible file systems. +Provides efficient uploading of files and directories with deduplication. +""" + +import hashlib +from pathlib import Path + +import fsspec # type: ignore[import-untyped] + +from dreadnode.util import logger + +CHUNK_SIZE = 8 * 1024 * 1024 # 8MB + + +class ArtifactStorage: + """ + Storage for artifacts with efficient handling of large files and directories. + + Supports: + - Content-based deduplication using SHA1 hashing + - Batch uploads for directories handled by fsspec + """ + + def __init__(self, file_system: fsspec.AbstractFileSystem): + """ + Initialize artifact storage with a file system and prefix path. + + Args: + file_system: FSSpec-compatible file system + """ + self._file_system = file_system + + def store_file(self, file_path: Path, target_key: str) -> str: + """ + Store a file in the storage system, using multipart upload for large files. + + Args: + file_path: Path to the local file + target_key: Key/path where the file should be stored + + Returns: + Full URI with protocol to the stored file + """ + if not self._file_system.exists(target_key): + self._file_system.put(str(file_path), target_key) + logger.debug("Artifact successfully stored at %s", target_key) + else: + logger.debug("Artifact already exists at %s, skipping upload.", target_key) + + return str(self._file_system.unstrip_protocol(target_key)) + + def batch_upload_files(self, source_paths: list[str], target_paths: list[str]) -> list[str]: + """ + Upload multiple files in a single batch operation. + + Args: + source_paths: List of local file paths + target_paths: List of target keys/paths + + Returns: + List of URIs for the uploaded files + """ + if not source_paths: + return [] + + logger.debug("Batch uploading %d files", len(source_paths)) + + srcs = [] + dsts = [] + + for src, dst in zip(source_paths, target_paths, strict=False): + if not self._file_system.exists(dst): + srcs.append(src) + dsts.append(dst) + + if srcs: + self._file_system.put(srcs, dsts) + logger.debug("Batch upload completed for %d files", len(srcs)) + else: + logger.debug("All files already exist, skipping upload") + + return [str(self._file_system.unstrip_protocol(target)) for target in target_paths] + + def compute_file_hash(self, file_path: Path, stream_threshold_mb: int = 10) -> str: + """ + Compute SHA1 hash of a file, using streaming only for larger files. + + Args: + file_path: Path to the file + stream_threshold_mb: Size threshold in MB for streaming vs. loading whole file + + Returns: + First 16 chars of SHA1 hash + """ + file_size = file_path.stat().st_size + stream_threshold = stream_threshold_mb * 1024 * 1024 # Convert MB to bytes + + sha1 = hashlib.sha1() # noqa: S324 + + if file_size < stream_threshold: + with file_path.open("rb") as f: + data = f.read() + sha1.update(data) + else: + with file_path.open("rb") as f: + for chunk in iter(lambda: f.read(CHUNK_SIZE), b""): + sha1.update(chunk) + + return sha1.hexdigest()[:16] + + def compute_file_hashes(self, file_paths: list[Path]) -> dict[str, str]: + """ + Compute SHA1 hashes for multiple files. + + Args: + file_paths: List of file paths to hash + + Returns: + Dictionary mapping file paths to their hash values + """ + result = {} + for file_path in file_paths: + file_path_str = file_path.resolve().as_posix() + result[file_path_str] = self.compute_file_hash(file_path) + return result diff --git a/dreadnode/artifact/tree_builder.py b/dreadnode/artifact/tree_builder.py new file mode 100644 index 00000000..af37bafa --- /dev/null +++ b/dreadnode/artifact/tree_builder.py @@ -0,0 +1,455 @@ +""" +Tree structure builder for artifacts with directory hierarchy preservation. +Provides efficient uploads and tree construction for frontend to consume. +""" + +import hashlib +import os +from dataclasses import dataclass +from pathlib import Path +from typing import Literal, TypedDict, Union + +from dreadnode.artifact.storage import ArtifactStorage +from dreadnode.util import logger + + +class FileNode(TypedDict): + """ + Represents a file node in the artifact tree. + Contains metadata about the file, including its name, uri, size_bytes, and final_real_path. + """ + + type: Literal["file"] + hash: str + uri: str + size_bytes: int + final_real_path: str + + +class DirectoryNode(TypedDict): + """ + Represents a directory node in the artifact tree. + Contains metadata about the directory, including its dir_path, hash, and children nodes. + """ + + type: Literal["dir"] + dir_path: str + hash: str + children: list[Union["DirectoryNode", FileNode]] + + +@dataclass +class ArtifactTreeBuilder: + """ + Builds a hierarchical tree structure for artifacts while uploading them to storage. + Preserves directory structure and handles efficient uploads. + """ + + storage: ArtifactStorage + prefix_path: str | None = None + + def process_artifact(self, local_uri: str | Path) -> DirectoryNode: + """ + Process an artifact (file or directory) and build its tree representation. + + Args: + local_uri: Path to the local file or directory + + Returns: + Directory tree structure representing the artifact + + Raises: + FileNotFoundError: If the path doesn't exist + """ + local_path = Path(local_uri).expanduser().resolve() + if not local_path.exists(): + raise FileNotFoundError(f"{local_path} does not exist") + + if local_path.is_dir(): + return self._process_directory(local_path) + + return self._process_single_file(local_path) + + def _process_single_file(self, file_path: Path) -> DirectoryNode: + """ + Process a single file and create a directory structure for it. + + Args: + file_path: Path to the file to be processed + + Returns: + DirectoryNode containing the single file + """ + file_node = self._process_file(file_path) + + file_node["final_real_path"] = file_path.resolve().as_posix() + + dir_path = file_path.parent.resolve().as_posix() + return { + "type": "dir", + "dir_path": dir_path, + "hash": file_node["hash"], + "children": [file_node], + } + + def _process_directory(self, dir_path: Path) -> DirectoryNode: + """ + Process a directory and all its contents efficiently. + + Args: + dir_path: Path to the directory to be processed. + + Returns: + DirectoryNode: A hierarchical tree structure representing the directory and its contents. + """ + logger.debug("Processing directory: %s", dir_path) + + all_files: list[Path] = [] + for root, _, files in os.walk(dir_path): + root_path = Path(root) + for file in files: + file_path = root_path / file + all_files.append(file_path) + + file_hashes = self.storage.compute_file_hashes(all_files) + + source_paths = [] + target_paths = [] + file_nodes_by_path: dict[Path, FileNode] = {} + file_hash_cache: dict[str, FileNode] = {} + + for file_path in all_files: + file_path_str = file_path.resolve().as_posix() + file_hash = file_hashes.get(file_path_str) + if not file_hash: + raise ValueError(f"File {file_path} not found in hash computation") + + # Check local cache for duplicates within this directory + if file_hash in file_hash_cache: + cached_node = file_hash_cache[file_hash].copy() + cached_node["final_real_path"] = file_path_str + file_nodes_by_path[file_path] = cached_node + continue + + file_extension = file_path.suffix + file_size = file_path.stat().st_size + + if self.prefix_path: + prefix = self.prefix_path.rstrip("/") + target_key = f"{prefix}/artifacts/{file_hash}{file_extension}" + else: + raise ValueError("Prefix path is invalid or empty") + + source_paths.append(file_path_str) + target_paths.append(target_key) + + # Create the file node without URI (will be set after upload) + file_node: FileNode = { + "type": "file", + "uri": "", + "hash": file_hash, + "size_bytes": file_size, + "final_real_path": file_path.resolve().as_posix(), + } + + file_nodes_by_path[file_path] = file_node + file_hash_cache[file_hash] = file_node + + if source_paths: + logger.debug("Uploading %d files in batch", len(source_paths)) + uris = self.storage.batch_upload_files(source_paths, target_paths) + + # Update file nodes with URIs + for i, file_path_str in enumerate(source_paths): + file_path = Path(file_path_str) + if file_path in file_nodes_by_path: + file_nodes_by_path[file_path]["uri"] = uris[i] + + return self._build_tree_structure(dir_path, file_nodes_by_path) + + def _build_tree_structure( + self, + base_dir: Path, + file_nodes_by_path: dict[Path, FileNode], + ) -> DirectoryNode: + """ + Build a hierarchical tree structure from processed files and directories. + + This method constructs a directory tree representation from a dictionary of + file paths and their corresponding `FileNode` objects, while preserving empty directories. + + Args: + base_dir (Path): The root directory for the tree structure. + file_nodes_by_path (dict[Path, FileNode]): A dictionary mapping file paths + to their corresponding `FileNode` objects. + + Returns: + DirectoryNode: A hierarchical tree structure representing the directory + and its contents. + + Example: + Given the following directory structure: + ``` + base_dir/ + ├── file1.txt + ├── subdir1/ + │ ├── file2.txt + │ └── file3.txt + └── subdir2/ + └── file4.txt + ``` + + And the [file_nodes_by_path] dictionary: + { + Path("base_dir/file1.txt"): FileNode(...), + Path("base_dir/subdir1/file2.txt"): FileNode(...), + Path("base_dir/subdir1/file3.txt"): FileNode(...), + Path("base_dir/subdir2/file4.txt"): FileNode(...), + } + + The returned tree structure will look like: + { + "type": "dir", + "name": "base_dir", + "hash": "", + "children": [ + { + "type": "file", + "name": "file1.txt", + ... + }, + { + "type": "dir", + "name": "subdir1", + "hash": "", + "children": [ + { + "type": "file", + "name": "file2.txt", + ... + }, + { + "type": "file", + "name": "file3.txt", + ... + } + ] + }, + { + "type": "dir", + "name": "subdir2", + "hash": "", + "children": [ + { + "type": "file", + "name": "file4.txt", + ... + } + ] + } + ] + } + """ + dir_structure: dict[str, DirectoryNode] = {} + + # Create root node + root_dir_path = base_dir.resolve().as_posix() + root_node: DirectoryNode = { + "type": "dir", + "dir_path": root_dir_path, + "hash": "", # Will be computed later + "children": [], + } + dir_structure[root_dir_path] = root_node + + for file_path in file_nodes_by_path: # noqa: PLC0206 + try: + rel_path = file_path.relative_to(base_dir) + parts = rel_path.parts + except ValueError: + logger.debug("File %s is not relative to base directory %s", file_path, base_dir) + continue + + # File in the root directory + if len(parts) == 1: + root_node["children"].append(file_nodes_by_path[file_path]) + continue + + # Create parent directories + current_dir = base_dir + current_dir_str = current_dir.resolve().as_posix() + for part in parts[:-1]: + next_dir = current_dir / part + next_dir_str = next_dir.resolve().as_posix() + if next_dir_str not in dir_structure: + dir_node: DirectoryNode = { + "type": "dir", + "dir_path": next_dir_str, + "hash": "", # Will be computed later + "children": [], + } + dir_structure[next_dir_str] = dir_node + dir_structure[current_dir_str]["children"].append(dir_node) + current_dir = next_dir + current_dir_str = next_dir_str + # Now add the file to its parent directory + parent_dir_str = file_path.parent.resolve().as_posix() + if parent_dir_str in dir_structure: + dir_structure[parent_dir_str]["children"].append(file_nodes_by_path[file_path]) + self._compute_directory_hashes(dir_structure) + + return root_node + + def _compute_directory_hashes(self, dir_structure: dict[str, DirectoryNode]) -> None: + """ + Compute hashes for all directories in the structure. + + Args: + dir_structure: Dictionary mapping directory paths to DirectoryNode objects + """ + parents = self._map_parent_child_relationships(dir_structure) + leaf_dirs = self._find_leaf_directories(dir_structure, parents) + self._process_directories_bottom_up(dir_structure, parents, leaf_dirs) + + def _map_parent_child_relationships( + self, + dir_structure: dict[str, DirectoryNode], + ) -> dict[str, str]: + """ + Create a mapping of parent-child relationships for directories. + + Args: + dir_structure: Dictionary mapping directory paths to DirectoryNode objects + + Returns: + A dictionary mapping child directory paths to their parent directory paths. + """ + parents = {} + for dir_path, dir_node in dir_structure.items(): + for child in dir_node["children"]: + if child["type"] == "dir": + child_path = child["dir_path"] + parents[child_path] = dir_path + return parents + + def _find_leaf_directories( + self, + dir_structure: dict[str, DirectoryNode], + parents: dict[str, str], + ) -> set[str]: + """ + Find leaf directories (those with no directory children). + + Args: + dir_structure: Dictionary mapping directory paths to DirectoryNode objects + parents: Dictionary mapping child directory paths to parent directory paths + + Returns: + A set of leaf directory paths. + """ + leaf_dirs = set() + for dir_path in dir_structure: + if dir_path not in parents.values(): + leaf_dirs.add(dir_path) + return leaf_dirs + + def _process_directories_bottom_up( + self, + dir_structure: dict[str, DirectoryNode], + parents: dict[str, str], + leaf_dirs: set[str], + ) -> None: + """ + Process directories bottom-up starting from leaf directories. + + Args: + dir_structure: Dictionary mapping directory paths to DirectoryNode objects + parents: Dictionary mapping child directory paths to parent directory paths + leaf_dirs: Set of leaf directory paths + """ + processed = set() + while leaf_dirs: + dir_path = leaf_dirs.pop() + dir_node = dir_structure[dir_path] + + # Compute hash based on children + dir_node["hash"] = self._compute_directory_hash(dir_node) + + processed.add(dir_path) + + # Add parent to leaf_dirs if all its children are processed + if dir_path in parents: + parent_path = parents[dir_path] + if self._are_all_children_processed(dir_structure[parent_path], processed): + leaf_dirs.add(parent_path) + + def _compute_directory_hash(self, dir_node: DirectoryNode) -> str: + """ + Compute the hash for a directory based on its children. + + Args: + dir_node: The DirectoryNode object + + Returns: + The computed hash as a string. + """ + child_hashes = [child["hash"] for child in dir_node["children"]] + child_hashes.sort() # Ensure consistent hash + hash_input = "|".join(child_hashes) + return hashlib.sha1(hash_input.encode()).hexdigest()[:16] # noqa: S324 + + def _are_all_children_processed(self, parent_node: DirectoryNode, processed: set[str]) -> bool: + """ + Check if all children of a parent directory have been processed. + + Args: + parent_node: The parent DirectoryNode object + processed: Set of processed directory paths + + Returns: + True if all children are processed, False otherwise. + """ + for child in parent_node["children"]: + if child["type"] == "dir" and child["dir_path"] not in processed: + return False + return True + + def _process_file(self, file_path: Path) -> FileNode: + """ + Process a single file by hashing and uploading it to storage. + + This method computes a SHA1 hash of the file's contents to uniquely identify it. + If the file has already been processed (based on the hash), the cached result is + returned. Otherwise, the file is uploaded to the storage system, and a `FileNode` + is created to represent the file. + + The method also extracts metadata such as the file's size, MIME type, and extension, + and determines the target storage path based on the user ID and file hash. + + Args: + file_path (Path): Path to the file to be processed. + + Returns: + FileNode: A dictionary representing the processed file, including its metadata + and storage URI. + """ + file_hash = self.storage.compute_file_hash(file_path) + + file_extension = file_path.suffix + file_size = file_path.stat().st_size + + if self.prefix_path: + prefix = self.prefix_path.rstrip("/") + target_key = f"{prefix}/artifacts/{file_hash}{file_extension}" + else: + raise ValueError("Prefix path is invalid or empty") + + uri = self.storage.store_file(file_path, target_key) + + return { + "type": "file", + "uri": uri, + "hash": file_hash, + "size_bytes": file_size, + "final_real_path": file_path.resolve().as_posix(), + } diff --git a/dreadnode/constants.py b/dreadnode/constants.py index 28e8e297..70c70642 100644 --- a/dreadnode/constants.py +++ b/dreadnode/constants.py @@ -1,29 +1,16 @@ -from __future__ import annotations - -import typing as t - -SPAN_NAMESPACE = "dreadnode" - -SpanType = t.Literal["run", "task", "span", "run_update"] - -SPAN_ATTRIBUTE_TYPE_KEY = f"{SPAN_NAMESPACE}.type" -SPAN_ATTRIBUTE_SCHEMA_KEY = f"{SPAN_NAMESPACE}.schema" -SPAN_ATTRIBUTE_TAGS_KEY = f"{SPAN_NAMESPACE}.tags" -SPAN_ATTRIBUTE_KIND_KEY = f"{SPAN_NAMESPACE}.kind" -SPAN_ATTRIBUTE_PROJECT_KEY = f"{SPAN_NAMESPACE}.project" - -SPAN_ATTRIBUTE_RUN_ID_KEY = f"{SPAN_NAMESPACE}.run.id" -SPAN_ATTRIBUTE_RUN_METRICS_KEY = f"{SPAN_NAMESPACE}.run.metrics" -SPAN_ATTRIBUTE_RUN_PARAMS_KEY = f"{SPAN_NAMESPACE}.run.params" -SPAN_ATTRIBUTE_RUN_ARTIFACTS_KEY = f"{SPAN_NAMESPACE}.run.artifacts" - -SPAN_ATTRIBUTE_PARENT_TASK_ID_KEY = f"{SPAN_NAMESPACE}.task.parent_id" -SPAN_ATTRIBUTE_TASK_ARGS_KEY = f"{SPAN_NAMESPACE}.task.args" -SPAN_ATTRIBUTE_TASK_OUTPUT_KEY = f"{SPAN_NAMESPACE}.task.output" -SPAN_ATTRIBUTE_TASK_SCORES_KEY = f"{SPAN_NAMESPACE}.task.scores" - # Environment variable names + ENV_SERVER_URL = "DREADNODE_SERVER_URL" -ENV_API_TOKEN = "DREADNODE_API_TOKEN" +ENV_SERVER = "DREADNODE_SERVER" # alternative to SERVER_URL +ENV_API_TOKEN = "DREADNODE_API_TOKEN" # noqa: S105 +ENV_API_KEY = "DREADNODE_API_KEY" # alternative to API_TOKEN ENV_LOCAL_DIR = "DREADNODE_LOCAL_DIR" ENV_PROJECT = "DREADNODE_PROJECT" + +# Default values + +DEFAULT_SERVER_URL = "https://platform.dreadnode.io" +DEFAULT_LOCAL_OBJECT_DIR = ".dreadnode/objects" + +# Default values for the S3 storage +MAX_INLINE_OBJECT_BYTES = 10 * 1024 # 10KB diff --git a/dreadnode/context.py b/dreadnode/context.py deleted file mode 100644 index 9971a9f1..00000000 --- a/dreadnode/context.py +++ /dev/null @@ -1,181 +0,0 @@ -# type: ignore -from __future__ import annotations - -import typing as t -from contextvars import ContextVar -from dataclasses import dataclass, field -from typing import Annotated, Any, Generic, TypeVar, get_args, get_origin - -from fast_depends.library import CustomField - -T = TypeVar("T") - - -class ContextField(CustomField, Generic[T]): - """A custom field that resolves values from the current run context.""" - - def __init__( - self, - id_: str | None = None, - strict: bool = True, - default: Any = None, - ): - self.id_ = id_ - self.default = default - super().__init__(cast=True, required=strict and default is None) - - def use(self, **kwargs: Any) -> dict[str, Any]: - """Resolve the value from current context when parameter is needed.""" - if self.param_name in kwargs: - return kwargs - - if (ctx := current_run_context.get()) is None: - if not self.required: - kwargs[self.param_name] = self.default - return kwargs - raise RuntimeError(f"No active run context for parameter {self.param_name}") - - # Extract the expected type from the parameter annotation - param_type = self._get_parameter_type() - if param_type is None: - raise RuntimeError(f"Could not determine type for parameter {self.param_name}") - - try: - value = ctx.get(param_type, self.id_, strict=self.required) - if value is None and self.default is not None: - value = self.default - kwargs[self.param_name] = value - except (KeyError, ValueError) as e: - if not self.required: - kwargs[self.param_name] = self.default - else: - raise RuntimeError(f"Context resolution failed for {self.param_name}: {str(e)}") from e - - return kwargs - - def _get_parameter_type(self) -> type | None: - """Extract the actual type from the parameter's type annotation.""" - from fast_depends.core import CallModel - - if not hasattr(self, "_call_model"): - return None - - model: CallModel = self._call_model - param_info = model.params.get(self.param_name) - if param_info is None: - return None - - annotation = param_info[0] - if get_origin(annotation) is Annotated: - # Get the real type from the Annotated type - return get_args(annotation)[0] - return annotation - - -@dataclass -class RunContext: - """Context manager for a specific run instance.""" - - run_id: str - _global_context: dict[tuple[type, str | None], t.Any] = field(default_factory=dict) - _scoped_context: dict[tuple[type, str | None], t.Any] = field(default_factory=dict) - - def set_global(self, value: t.Any, id_: str | None = None) -> None: - """Set a global context value for this run.""" - self._global_context[type(value), id_] = value - - def set_scoped(self, value: t.Any, id_: str | None = None) -> None: - """Set a scoped context value.""" - self._scoped_context[type(value), id_] = value - - @t.overload - def get(self, type_: type[T], id_: str | None = None, strict: t.Literal[True] = True) -> T: ... - - @t.overload - def get(self, type_: type[T], id_: str | None = None, strict: t.Literal[False] = ...) -> T | None: ... - - def get(self, type_: type[T], id_: str | list[str] | None = None, strict: bool = True) -> T | None: - return self._get(type_, id_, strict) - - def _get(self, type_: type[T], id_: str | None = None, strict: bool = True) -> T | None: - # First check scoped context - key = (type_, id_) - if id_ is None: - # If no ID specified, look for single instance of type - matching = [(k, v) for k, v in self._scoped_context.items() if issubclass(k[0], type_)] - if len(matching) == 1: - return matching[0][1] - elif len(matching) > 1 and strict: - raise ValueError( - f"Multiple {type_.__name__} objects exist. Use a specific id to get one of the values." - ) - else: - if key in self._scoped_context: - return self._scoped_context[key] - - # Then check global context - if id_ is None: - matching = [(k, v) for k, v in self._global_context.items() if issubclass(k[0], type_)] - if len(matching) == 1: - return matching[0][1] - elif len(matching) > 1 and strict: - raise ValueError( - f"Multiple {type_.__name__} objects exist. Use a specific id to get one of the values." - ) - else: - if key in self._global_context: - return self._global_context[key] - - if strict: - raise KeyError(f"{type_.__name__}{' with id ' + repr(id_) if id_ else ''}") - return None - - def scope(self, **values: t.Any): - """Create a scoped context manager.""" - return ScopedContext(self, values) - - -class ScopedContext: - """Context manager for temporary scoped values.""" - - def __init__(self, context: RunContext, values: dict[str, Any]): - self.context = context - self.values = values - self.previous: dict[tuple[type, str | None], Any] = {} - - def __enter__(self) -> None: - # Save current values and set new ones - for id_, value in self.values.items(): - key = (type(value), id_) - self.previous[key] = self.context._scoped_context.get(key) - self.context.set_scoped(value, id_) - - def __exit__(self, *exc: Any) -> None: - # Restore previous values - for key, value in self.previous.items(): - if value is None: - self.context._scoped_context.pop(key, None) - else: - self.context._scoped_context[key] = value - - -# Global context access -current_run_context: ContextVar[RunContext | None] = ContextVar("current_run_context", default=None) - - -def get_context() -> RunContext: - """Get the currently active run context.""" - if (ctx := current_run_context.get()) is None: - raise RuntimeError("No active run context") - return ctx - - -# Context field creation helpers -def ctx(*, id_: str | None = None) -> Any: - """Create a context field that resolves using the parameter's type annotation.""" - return ContextField(id_=id_) - - -def optional_ctx(*, id_: str | None = None, default: Any = None) -> Any: - """Create an optional context field.""" - return ContextField(id_=id_, strict=False, default=default) diff --git a/dreadnode/integrations/transformers.py b/dreadnode/integrations/transformers.py index 42d630c3..c21059b4 100644 --- a/dreadnode/integrations/transformers.py +++ b/dreadnode/integrations/transformers.py @@ -5,7 +5,7 @@ import typing as t -from transformers.trainer_callback import ( # type: ignore +from transformers.trainer_callback import ( # type: ignore [import-untyped] TrainerCallback, TrainerControl, TrainerState, @@ -15,18 +15,27 @@ import dreadnode as dn if t.TYPE_CHECKING: - from dreadnode.tracing import RunSpan, Span + from dreadnode.tracing.span import RunSpan, Span + +# ruff: noqa: ARG002 def _clean_keys(data: dict[str, t.Any]) -> dict[str, t.Any]: cleaned: dict[str, t.Any] = {} for key, val in data.items(): - key = key.replace("eval_", "eval/").replace("test_", "test/").replace("train_", "train/") - cleaned[key] = val + _key = key.replace("eval_", "eval/").replace("test_", "test/").replace("train_", "train/") + cleaned[_key] = val return cleaned class DreadnodeCallback(TrainerCallback): # type: ignore [misc] + """ + An implementation of the `TrainerCallback` interface for Dreadnode. + + This callback is used to log metrics and parameters to Dreadnode during training inside + the `transformers` library or derivations (`trl`, etc.). + """ + def __init__( self, project: str | None = None, @@ -67,7 +76,9 @@ def _setup(self, args: TrainingArguments, state: TrainerState, model: t.Any) -> combined_dict = {**args.to_sanitized_dict()} if hasattr(model, "config") and model.config is not None: - model_config = model.config if isinstance(model.config, dict) else model.config.to_dict() + model_config = ( + model.config if isinstance(model.config, dict) else model.config.to_dict() + ) for key, value in model_config.items(): combined_dict[f"model/{key}"] = value if hasattr(model, "peft_config") and model.peft_config is not None: @@ -98,7 +109,11 @@ def on_train_begin( self._setup(args, state, model) def on_train_end( - self, args: TrainingArguments, state: TrainerState, control: TrainerControl, **kwargs: t.Any + self, + args: TrainingArguments, + state: TrainerState, + control: TrainerControl, + **kwargs: t.Any, ) -> None: self._shutdown() diff --git a/dreadnode/main.py b/dreadnode/main.py index f7866c95..e6d93a9a 100644 --- a/dreadnode/main.py +++ b/dreadnode/main.py @@ -1,50 +1,86 @@ -from __future__ import annotations - import contextlib import inspect import os import random +import re import typing as t from dataclasses import dataclass -from datetime import datetime +from datetime import datetime, timezone from pathlib import Path from urllib.parse import urljoin -import coolname # type: ignore +import coolname # type: ignore [import-untyped] import logfire +from fsspec.implementations.local import ( # type: ignore [import-untyped] + LocalFileSystem, +) from logfire._internal.exporters.remove_pending import RemovePendingSpansExporter from logfire._internal.stack_info import get_filepath_attribute, warn_at_user_stacklevel from logfire._internal.utils import safe_repr from opentelemetry.exporter.otlp.proto.http import Compression from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter -from opentelemetry.sdk.metrics.export import MetricReader -from opentelemetry.sdk.trace import SpanProcessor from opentelemetry.sdk.trace.export import BatchSpanProcessor -from opentelemetry.trace import Tracer - -from .api.client import ApiClient -from .constants import ENV_API_TOKEN, ENV_LOCAL_DIR, ENV_PROJECT, ENV_SERVER_URL -from .exporters import FileExportConfig, FileMetricReader, FileSpanExporter -from .score import Scorer, ScorerCallable, T -from .task import P, R, Task -from .tracing import ( - JsonValue, +from s3fs import S3FileSystem # type: ignore [import-untyped] + +from dreadnode.api.client import ApiClient +from dreadnode.constants import ( + DEFAULT_SERVER_URL, + ENV_API_KEY, + ENV_API_TOKEN, + ENV_LOCAL_DIR, + ENV_PROJECT, + ENV_SERVER, + ENV_SERVER_URL, +) +from dreadnode.metric import Metric, Scorer, ScorerCallable, T +from dreadnode.task import P, R, Task +from dreadnode.tracing.exporters import ( + FileExportConfig, + FileMetricReader, + FileSpanExporter, +) +from dreadnode.tracing.span import ( RunSpan, - Score, Span, TaskSpan, current_run_span, current_task_span, ) -from .version import VERSION +from dreadnode.types import ( + AnyDict, + JsonValue, +) +from dreadnode.util import handle_internal_errors +from dreadnode.version import VERSION + +if t.TYPE_CHECKING: + from fsspec import AbstractFileSystem # type: ignore [import-untyped] + from opentelemetry.sdk.metrics.export import MetricReader + from opentelemetry.sdk.trace import SpanProcessor + from opentelemetry.trace import Tracer + + +ToObject = t.Literal["task-or-run", "run"] class DreadnodeConfigWarning(UserWarning): pass +class DreadnodeUsageWarning(UserWarning): + pass + + @dataclass class Dreadnode: + """ + The core Dreadnode SDK class. + + A default instance of this class is created and can be used directly with `dreadnode.*`. + + Otherwise, you can create your own instance and configure it with `configure()`. + """ + server: str | None token: str | None local_dir: str | Path | t.Literal[False] @@ -83,10 +119,14 @@ def __init__( self._logfire = logfire.DEFAULT_LOGFIRE_INSTANCE self._logfire.config.ignore_no_config = True + self._fs: AbstractFileSystem = LocalFileSystem(auto_mkdir=True) + self._fs_prefix: str = ".dreadnode/storage/" + self._initialized = False def configure( self, + *, server: str | None = None, token: str | None = None, local_dir: str | Path | t.Literal[False] = False, @@ -97,10 +137,33 @@ def configure( send_to_logfire: bool | t.Literal["if-token-present"] = "if-token-present", otel_scope: str = "dreadnode", ) -> None: + """ + Configure the Dreadnode SDK and call `initialize()`. + + This method should always be called before using the SDK. + + If `server` and `token` are not provided, the SDK will look in + the associated environment variables: + + - `DREADNODE_SERVER_URL` or `DREADNODE_SERVER` + - `DREADNODE_API_TOKEN` or `DREADNODE_API_KEY` + + Args: + server: The Dreadnode server URL. + token: The Dreadnode API token. + local_dir: The local directory to store data in. + project: The defautlt project name to associate all runs with. + service_name: The service name to use for OpenTelemetry. + service_version: The service version to use for OpenTelemetry. + console: Whether to log span information to the console. + send_to_logfire: Whether to send data to Logfire. + otel_scope: The OpenTelemetry scope name. + """ + self._initialized = False - self.server = server or os.environ.get(ENV_SERVER_URL) - self.token = token or os.environ.get(ENV_API_TOKEN) + self.server = server or os.environ.get(ENV_SERVER_URL) or os.environ.get(ENV_SERVER) + self.token = token or os.environ.get(ENV_API_TOKEN) or os.environ.get(ENV_API_KEY) if local_dir is False and ENV_LOCAL_DIR in os.environ: env_local_dir = os.environ.get(ENV_LOCAL_DIR) @@ -121,16 +184,23 @@ def configure( self.initialize() def initialize(self) -> None: + """ + Initialize the Dreadnode SDK. + + This method is called automatically when you call `configure()`. + """ if self._initialized: return span_processors: list[SpanProcessor] = [] metric_readers: list[MetricReader] = [] - if self.server is None and self.local_dir is False and self.send_to_logfire is not True: + self.server = self.server or DEFAULT_SERVER_URL + if self.server is None and self.local_dir is False: warn_at_user_stacklevel( "Your current configuration won't persist run data anywhere. " - f"Use `dreadnode.init(server=..., token=...)`, `dreadnode.init(local_dir=...)`, or use environment variables ({ENV_SERVER_URL}, {ENV_API_TOKEN}, {ENV_LOCAL_DIR}).", + "Use `dreadnode.init(server=..., token=...)`, `dreadnode.init(local_dir=...)`, " + f"or use environment variables ({ENV_SERVER_URL}, {ENV_API_TOKEN}, {ENV_LOCAL_DIR}).", category=DreadnodeConfigWarning, ) @@ -142,12 +212,16 @@ def initialize(self) -> None: span_processors.append(BatchSpanProcessor(FileSpanExporter(config))) metric_readers.append(FileMetricReader(config)) - if self.server is not None: - if self.token is None: - raise ValueError(f"Token ({ENV_API_TOKEN}) must be provided when server is set") - + if self.token is not None: self._api = ApiClient(self.server, self.token) + try: + self._api.list_projects() + except Exception as e: + raise RuntimeError( + "Failed to authenticate with the provided server and token", + ) from e + headers = {"User-Agent": f"dreadnode/{VERSION}", "X-Api-Key": self.token} span_processors.append( BatchSpanProcessor( @@ -156,22 +230,35 @@ def initialize(self) -> None: endpoint=urljoin(self.server, "/api/otel/traces"), headers=headers, compression=Compression.Gzip, - ) - ) - ) + ), + ), + ), ) - # TODO: Metrics + # TODO(nick): Metrics + # https://linear.app/dreadnode/issue/ENG-1310/sdk-add-metrics-exports # metric_readers.append( # PeriodicExportingMetricReader( # OTLPMetricExporter( # endpoint=urljoin(self.server, "/v1/metrics"), # headers=headers, # compression=Compression.Gzip, - # # TODO: preferred_temporality + # # preferred_temporality # ) # ) # ) + credentials = self._api.get_user_data_credentials() + self._fs = S3FileSystem( + key=credentials.access_key_id, + secret=credentials.secret_access_key, + token=credentials.session_token, + client_kwargs={ + "endpoint_url": credentials.endpoint, + "region_name": credentials.region, + }, + ) + self._fs_prefix = f"{credentials.bucket}/{credentials.prefix}/" + self._logfire = logfire.configure( local=not self.is_default, send_to_logfire=self.send_to_logfire, @@ -190,11 +277,22 @@ def initialize(self) -> None: def is_default(self) -> bool: return self is DEFAULT_INSTANCE - # I'd like to feel like a property as well, - # but it won't work well for our lazy initialization - def api(self, *, base_url: str | None = None, token: str | None = None) -> ApiClient: - if base_url is not None and token is not None: - return ApiClient(base_url, token) + def api(self, *, server: str | None = None, token: str | None = None) -> ApiClient: + """ + Get an API client based on the current configuration or the provided server and token. + + If the server and token are not provided, the method will use the current configuration + and `configure()` needs to be called first. + + Args: + server: The server URL to use for the API client. + token: The API token to use for authentication. + + Returns: + An ApiClient instance. + """ + if server is not None and token is not None: + return ApiClient(server, token) if not self._initialized: raise RuntimeError("Call .configure() before accessing the API") @@ -204,14 +302,24 @@ def api(self, *, base_url: str | None = None, token: str | None = None) -> ApiCl return self._api - def _get_tracer(self, *, is_span_tracer: bool = True) -> Tracer: - return self._logfire._tracer_provider.get_tracer( + def _get_tracer(self, *, is_span_tracer: bool = True) -> "Tracer": + return self._logfire._tracer_provider.get_tracer( # noqa: SLF001 self.otel_scope, VERSION, is_span_tracer=is_span_tracer, ) + @handle_internal_errors() def shutdown(self) -> None: + """ + Shutdown any associate OpenTelemetry components and flush any pending spans. + + It is not required to call this method, as the SDK will automatically + flush and shutdown when the process exits. + + However, if you want to ensure that all spans are flushed before + exiting, you can call this method manually. + """ if not self._initialized: return @@ -224,6 +332,28 @@ def span( tags: t.Sequence[str] | None = None, **attributes: t.Any, ) -> Span: + """ + Create a new OpenTelemety span. + + Spans are more lightweight than tasks, but still let you track + work being performed and view it in the UI. You cannot + log parameters, inputs, or outputs to spans. + + Example: + ``` + with dreadnode.span("my_span") as span: + # do some work here + pass + ``` + + Args: + name: The name of the span. + tags: A list of tags to attach to the span. + **attributes: A dictionary of attributes to attach to the span. + + Returns: + A Span object. + """ return Span( name=name, attributes=attributes, @@ -231,16 +361,60 @@ def span( tags=tags, ) + # Some excessive typing here to ensure we can properly + # overload our decorator for sync/async and cases + # where we need the return type of the task to align + # with the scorer inputs + + class TaskDecorator(t.Protocol): + @t.overload + def __call__( + self, + func: t.Callable[P, t.Awaitable[R]], + ) -> Task[P, R]: ... + + @t.overload + def __call__( + self, + func: t.Callable[P, R], + ) -> Task[P, R]: ... + + def __call__( + self, + func: t.Callable[P, t.Awaitable[R]] | t.Callable[P, R], + ) -> Task[P, R]: ... + + class ScoredTaskDecorator(t.Protocol, t.Generic[R]): + @t.overload + def __call__( + self, + func: t.Callable[P, t.Awaitable[R]], + ) -> Task[P, R]: ... + + @t.overload + def __call__( + self, + func: t.Callable[P, R], + ) -> Task[P, R]: ... + + def __call__( + self, + func: t.Callable[P, t.Awaitable[R]] | t.Callable[P, R], + ) -> Task[P, R]: ... + @t.overload def task( self, *, scorers: None = None, name: str | None = None, + label: str | None = None, + log_params: t.Sequence[str] | bool = False, + log_inputs: t.Sequence[str] | bool = True, + log_output: bool = True, tags: t.Sequence[str] | None = None, **attributes: t.Any, - ) -> t.Callable[[t.Callable[P, t.Awaitable[R]]], Task[P, R]]: - ... + ) -> TaskDecorator: ... @t.overload def task( @@ -248,31 +422,74 @@ def task( *, scorers: t.Sequence[Scorer[R] | ScorerCallable[R]], name: str | None = None, + label: str | None = None, + log_params: t.Sequence[str] | bool = False, + log_inputs: t.Sequence[str] | bool = True, + log_output: bool = True, tags: t.Sequence[str] | None = None, **attributes: t.Any, - ) -> t.Callable[[t.Callable[P, t.Awaitable[R]]], Task[P, R]]: - ... + ) -> ScoredTaskDecorator[R]: ... def task( self, *, - scorers: t.Sequence[Scorer[t.Any] | ScorerCallable[R]] | None = None, + scorers: t.Sequence[Scorer[t.Any] | ScorerCallable[t.Any]] | None = None, name: str | None = None, + label: str | None = None, + log_params: t.Sequence[str] | bool = False, + log_inputs: t.Sequence[str] | bool = True, + log_output: bool = True, tags: t.Sequence[str] | None = None, **attributes: t.Any, - ) -> t.Callable[[t.Callable[P, t.Awaitable[R]]], Task[P, R]]: - def make_task(func: t.Callable[P, t.Awaitable[R]]) -> Task[P, R]: + ) -> TaskDecorator: + """ + Create a new task from a function. + + Example: + ``` + @dreadnode.task(name="my_task") + async def my_task(x: int) -> int: + return x * 2 + + await my_task(2) + ``` + + Args: + scorers: A list of scorers to attach to the task. These will be called after every execution + of the task and will be passed the task's output. + name: The name of the task. + label: The label of the task - useful for filtering in the UI. + log_params: Whether to log all, or specific, incoming arguments to the function as parameters. + log_inputs: Whether to log all, or specific, incoming arguments to the function as inputs. + log_output: Whether to log the result of the function as an output. + tags: A list of tags to attach to the task span. + **attributes: A dictionary of attributes to attach to the task span. + + Returns: + A new Task object. + """ + + def make_task( + func: t.Callable[P, t.Awaitable[R]] | t.Callable[P, R], + ) -> Task[P, R]: unwrapped = inspect.unwrap(func) - qualified_func_name = func_name = getattr( + + if inspect.isgeneratorfunction(unwrapped) or inspect.isasyncgenfunction( + unwrapped, + ): + raise TypeError("@task cannot be applied to generators") + + func_name = getattr( unwrapped, "__qualname__", - getattr(unwrapped, "__name__", safe_repr(unwrapped)), + getattr(func, "__name__", safe_repr(func)), ) - with contextlib.suppress(Exception): - qualified_func_name = f"{inspect.getmodule(unwrapped).__name__}.{func_name}" # type: ignore + _name = name or func_name + _label = label or func_name - _name = name or qualified_func_name + # conform our label for sanity + _label = re.sub(r"[\W_]+", "_", _label.lower()) _attributes = attributes or {} _attributes["code.function"] = func_name @@ -280,19 +497,27 @@ def make_task(func: t.Callable[P, t.Awaitable[R]]) -> Task[P, R]: _attributes["code.lineno"] = unwrapped.__code__.co_firstlineno with contextlib.suppress(Exception): _attributes.update( - get_filepath_attribute(inspect.getsourcefile(unwrapped)) # type: ignore + get_filepath_attribute( + inspect.getsourcefile(unwrapped), # type: ignore [arg-type] + ), ) return Task( tracer=self._get_tracer(), name=_name, attributes=_attributes, - func=func, + func=t.cast("t.Callable[P, R]", func), scorers=[ - scorer if isinstance(scorer, Scorer) else Scorer.from_callable(self._get_tracer(), scorer) + scorer + if isinstance(scorer, Scorer) + else Scorer.from_callable(self._get_tracer(), scorer) for scorer in scorers or [] ], tags=list(tags or []), + log_params=log_params, + log_inputs=log_inputs, + log_output=log_output, + label=_label, ) return make_task @@ -301,30 +526,90 @@ def task_span( self, name: str, *, + label: str | None = None, + params: AnyDict | None = None, tags: t.Sequence[str] | None = None, - args: dict[str, t.Any] | None = None, **attributes: t.Any, ) -> TaskSpan[t.Any]: + """ + Create a task span without an explicit associated function. + + This is useful for creating tasks on the fly without having to + define a function. + + Example: + ``` + async with dreadnode.task_span("my_task") as task: + # do some work here + pass + ``` + Args: + name: The name of the task. + label: The label of the task - useful for filtering in the UI. + params: A dictionary of parameters to attach to the task span. + tags: A list of tags to attach to the task span. + **attributes: A dictionary of attributes to attach to the task span. + + Returns: + A TaskSpan object. + """ if (run := current_run_span.get()) is None: raise RuntimeError("Task spans must be created within a run") + label = label or re.sub(r"[\W_]+", "_", name.lower()) return TaskSpan( name=name, - args=args or {}, + label=label, attributes=attributes, - tracer=self._get_tracer(), - run_id=run.run_id, + params=params, tags=tags, + run_id=run.run_id, + tracer=self._get_tracer(), ) def scorer( self, *, name: str | None = None, + tags: t.Sequence[str] | None = None, **attributes: t.Any, ) -> t.Callable[[ScorerCallable[T]], Scorer[T]]: + """ + Make a scorer from a callable function. + + This is useful when you want to change the name of the scorer + or add additional attributes to it. + + Example: + ``` + @dreadnode.scorer(name="my_scorer") + async def my_scorer(x: int) -> float: + return x * 2 + + @dreadnode.task(scorers=[my_scorer]) + async def my_task(x: int) -> int: + return x * 2 + + await my_task(2) + ``` + + Args: + name: The name of the scorer. + tags: A list of tags to attach to the scorer. + **attributes: A dictionary of attributes to attach to the scorer. + + Returns: + A new Scorer object. + """ + def make_scorer(func: ScorerCallable[T]) -> Scorer[T]: - return Scorer.from_callable(self._get_tracer(), func, name=name, attributes=attributes) + return Scorer.from_callable( + self._get_tracer(), + func, + name=name, + tags=tags, + attributes=attributes, + ) return make_scorer @@ -333,61 +618,407 @@ def run( name: str | None = None, *, tags: t.Sequence[str] | None = None, + params: AnyDict | None = None, project: str | None = None, **attributes: t.Any, ) -> RunSpan: + """ + Create a new run. + + Runs are the main way to track work in Dreadnode. They are + associated with a specific project and can have parameters, + inputs, and outputs logged to them. + + You cannot create runs inside other runs. + + Example: + ``` + with dreadnode.run("my_run"): + # do some work here + pass + ``` + + Args: + name: The name of the run. If not provided, a random name will be generated. + tags: A list of tags to attach to the run. + params: A dictionary of parameters to attach to the run. + project: The project name to associate the run with. If not provided, + the project passed to `configure()` will be used, or the + run will be associated with a default project. + **attributes: Additional attributes to attach to the run span. + """ if not self._initialized: self.initialize() if name is None: - name = f"{coolname.generate_slug(2)}-{random.randint(100, 999)}" + name = f"{coolname.generate_slug(2)}-{random.randint(100, 999)}" # noqa: S311 return RunSpan( name=name, project=project or self.project or "default", attributes=attributes, tracer=self._get_tracer(), + params=params, tags=tags, + file_system=self._fs, + prefix_path=self._fs_prefix, ) + @handle_internal_errors() def push_update(self) -> None: + """ + Push any pending metric or parameter data to the server. + + This is useful for ensuring that the UI is up to date with the + latest data. Otherwise, all data for the run will be pushed + automatically when the run is closed. + + Example: + ``` + with dreadnode.run("my_run"): + dreadnode.log_params(...) + dreadnode.log_metric(...) + dreadnode.push_update() + """ if (run := current_run_span.get()) is None: raise RuntimeError("Run updates must be pushed within a run") run.push_update() - def log_param(self, key: str, value: JsonValue) -> None: - if (run := current_run_span.get()) is None: - raise RuntimeError("Params must be set within a run") - run.log_param(key, value) + @handle_internal_errors() + def log_param( + self, + key: str, + value: JsonValue, + *, + to: ToObject = "task-or-run", + ) -> None: + """ + Log a single parameter to the current task or run. - def log_params(self, **params: JsonValue) -> None: - if (run := current_run_span.get()) is None: - raise RuntimeError("Params must be set within a run") - run.log_params(**params) + Parameters are key-value pairs that are associated with the task or run + and can be used to track configuration values, hyperparameters, or other + metadata. + + Example: + ``` + with dreadnode.run("my_run") as run: + run.log_param("param_name", "param_value") + ``` + + Args: + key: The name of the parameter. + value: The value of the parameter. + to: The target object to log the parameter to. Can be "task-or-run" or "run". + Defaults to "task-or-run". If "task-or-run", the parameter will be logged + to the current task or run, whichever is the nearest ancestor. + """ + self.log_params(to=to, **{key: value}) + @handle_internal_errors() + def log_params(self, to: ToObject = "run", **params: JsonValue) -> None: + """ + Log multiple parameters to the current task or run. + + Parameters are key-value pairs that are associated with the task or run + and can be used to track configuration values, hyperparameters, or other + metadata. + + Example: + ``` + with dreadnode.run("my_run") as run: + run.log_params( + param1="value1", + param2="value2" + ) + ``` + + Args: + to: The target object to log the parameters to. Can be "task-or-run" or "run". + Defaults to "task-or-run". If "task-or-run", the parameters will be logged + to the current task or run, whichever is the nearest ancestor. + **params: The parameters to log. Each parameter is a key-value pair. + """ + task = current_task_span.get() + run = current_run_span.get() + + target = (task or run) if to == "task-or-run" else run + if target is None: + raise RuntimeError("log_params() must be called within a run") + + target.log_params(**params) + + @t.overload def log_metric( self, key: str, - value: float, + value: float | bool, + *, step: int = 0, + origin: t.Any | None = None, + timestamp: datetime | None = None, + to: ToObject = "task-or-run", + ) -> None: + """ + Log a single metric to the current task or run. + + Metrics are some measurement or recorded value related to the task or run. + They can be used to track performance, resource usage, or other quantitative data. + + Example: + ``` + with dreadnode.run("my_run") as run: + run.log_metric("metric_name", 42.0) + ``` + + Args: + key: The name of the metric. + value: The value of the metric. + step: The step of the metric. + origin: The origin of the metric - can be provided any object which was logged + as an input or output anywhere in the run. + timestamp: The timestamp of the metric - defaults to the current time. + to: The target object to log the metric to. Can be "task-or-run" or "run". + Defaults to "task-or-run". If "task-or-run", the metric will be logged + to the current task or run, whichever is the nearest ancestor. + """ + + @t.overload + def log_metric( + self, + key: str, + value: Metric, + *, + origin: t.Any | None = None, + to: ToObject = "task-or-run", + ) -> None: + """ + Log a single metric to the current task or run. + + Metrics are some measurement or recorded value related to the task or run. + They can be used to track performance, resource usage, or other quantitative data. + + Example: + ``` + with dreadnode.run("my_run") as run: + run.log_metric("metric_name", 42.0) + ``` + + Args: + key: The name of the metric. + value: The metric object. + origin: The origin of the metric - can be provided any object which was logged + as an input or output anywhere in the run. + to: The target object to log the metric to. Can be "task-or-run" or "run". + Defaults to "task-or-run". If "task-or-run", the metric will be logged + to the current task or run, whichever is the nearest ancestor. + """ + ... # noqa: PIE790 + + @handle_internal_errors() + def log_metric( + self, + key: str, + value: float | bool | Metric, *, + step: int = 0, + origin: t.Any | None = None, timestamp: datetime | None = None, + to: ToObject = "task-or-run", ) -> None: - if (run := current_run_span.get()) is None: - raise RuntimeError("Metrics must be logged within a run") - run.log_metric(key, value, step=step, timestamp=timestamp) + task = current_task_span.get() + run = current_run_span.get() + + target = (task or run) if to == "task-or-run" else run + if target is None: + raise RuntimeError("log_metric() must be called within a run") + + metric = ( + value + if isinstance(value, Metric) + else Metric(float(value), step, timestamp or datetime.now(timezone.utc)) + ) + target.log_metric(key, metric, origin=origin) + + @handle_internal_errors() + def log_artifact( + self, + local_uri: str | Path, + ) -> None: + """ + Log a file or directory artifact to the current run. + + This method uploads a local file or directory to the artifact storage associated with the run. + + Examples: + Log a single file: + ``` + with dreadnode.run("my_run") as run: + # Save a file + with open("results.json", "w") as f: + json.dump(results, f) - def log_score(self, score: Score) -> None: + # Log it as an artifact + run.log_artifact("results.json") + ``` + + Log a directory: + ``` + with dreadnode.run("my_run") as run: + # Create a directory with model files + os.makedirs("model_output", exist_ok=True) + save_model("model_output/model.pkl") + save_config("model_output/config.yaml") + + # Log the entire directory as an artifact + run.log_artifact("model_output") + ``` + + Args: + local_uri: The local path to the file to upload. + to: The target object to log the artifact to. Only "run" is supported. + """ if (run := current_run_span.get()) is None: - raise RuntimeError("Scores must be logged within a run") + raise RuntimeError("log_artifact() must be called within a run") - run.scores.append(score) + run.log_artifact(local_uri=local_uri) - if (task := current_task_span.get()) is None: - return + @handle_internal_errors() + def log_input( + self, + name: str, + value: JsonValue, + *, + label: str | None = None, + to: ToObject = "task-or-run", + **attributes: t.Any, + ) -> None: + """ + Log a single input to the current task or run. + + Inputs can be any runtime object, which are serialized, stored, and tracked + in the Dreadnode UI. + + Example: + ``` + @dreadnode.task + async def my_task(x: int) -> int: + dreadnode.log_input("input_name", x) + return x * 2 + + with dreadnode.run("my_run"): + dreadnode.log_input("input_name", some_dataframe) + + await my_task(2) + ``` + """ + task = current_task_span.get() + run = current_run_span.get() + + target = (task or run) if to == "task-or-run" else run + if target is None: + raise RuntimeError("log_inputs() must be called within a run") + + target.log_input(name, value, label=label, **attributes) + + @handle_internal_errors() + def log_inputs( + self, + to: ToObject = "task-or-run", + **inputs: JsonValue, + ) -> None: + """ + Log multiple inputs to the current task or run. + + See `log_input()` for more details. + """ + for name, value in inputs.items(): + self.log_input(name, value, to=to) + + @handle_internal_errors() + def log_output( + self, + name: str, + value: t.Any, + *, + label: str | None = None, + to: ToObject = "task-or-run", + **attributes: JsonValue, + ) -> None: + """ + Log a single output to the current task or run. + + Outputs can be any runtime object, which are serialized, stored, and tracked + in the Dreadnode UI. + + Example: + ``` + @dreadnode.task + async def my_task(x: int) -> int: + result = x * 2 + dreadnode.log_output("result", x * 2) + return result + + with dreadnode.run("my_run"): + await my_task(2) + + dreadnode.log_output("other", 123) + ``` + """ + task = current_task_span.get() + run = current_run_span.get() + + target = (task or run) if to == "task-or-run" else run + if target is None: + raise RuntimeError( + "log_output() must be called within a run or a task", + ) + + target.log_output(name, value, label=label, **attributes) + + @handle_internal_errors() + def log_outputs( + self, + to: ToObject = "task-or-run", + **outputs: JsonValue, + ) -> None: + """ + Log multiple outputs to the current task or run. + + See `log_output()` for more details. + """ + for name, value in outputs.items(): + self.log_output(name, value, to=to) + + @handle_internal_errors() + def link_objects(self, origin: t.Any, link: t.Any, **attributes: JsonValue) -> None: + """ + Associate two runtime objects with each other. + + This is useful for linking any two objects which are related to + each other, such as a model and its training data, or an input + prompt and the resulting output. + + Example: + ``` + with dreadnode.run("my_run") as run: + model = SomeModel() + data = SomeData() + + run.link_objects(model, data) + ``` + + Args: + origin: The origin object to link from. + link: The linked object to link to. + **attributes: Additional attributes to attach to the link. + """ + if (run := current_run_span.get()) is None: + raise RuntimeError("link() must be called within a run") - task.scores.append(score) + origin_hash = run.log_object(origin) + link_hash = run.log_object(link) + run.link_objects(origin_hash, link_hash, **attributes) DEFAULT_INSTANCE = Dreadnode() diff --git a/dreadnode/metric.py b/dreadnode/metric.py new file mode 100644 index 00000000..bce03cbc --- /dev/null +++ b/dreadnode/metric.py @@ -0,0 +1,183 @@ +import inspect +import typing as t +from dataclasses import dataclass, field +from datetime import datetime, timezone + +from logfire._internal.utils import safe_repr +from opentelemetry.trace import Tracer + +from dreadnode.types import JsonDict, JsonValue + +T = t.TypeVar("T") + + +@dataclass +class Metric: + """ + Any reported value regarding the state of a run, task, and optionally object (input/output). + """ + + value: float + "The value of the metric, e.g. 0.5, 1.0, 2.0, etc." + step: int = 0 + "An step value to indicate when this metric was reported." + timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc)) + "The timestamp when the metric was reported." + attributes: JsonDict = field(default_factory=dict) + "A dictionary of attributes to attach to the metric." + + @classmethod + def from_many( + cls, + values: t.Sequence[tuple[str, float, float]], + step: int = 0, + **attributes: JsonValue, + ) -> "Metric": + """ + Create a composite metric from individual values and weights. + + This is useful for creating a metric that is the weighted average of multiple values. + The values should be a sequence of tuples, where each tuple contains the name of the metric, + the value of the metric, and the weight of the metric. + + The individual values will be reported in the attributes of the metric. + + Args: + values: A sequence of tuples containing the name, value, and weight of each metric. + step: The step value to attach to the metric. + **attributes: Additional attributes to attach to the metric. + + Returns: + A composite Metric + """ + total = sum(value * weight for _, value, weight in values) + weight = sum(weight for _, _, weight in values) + score_attributes = {name: value for name, value, _ in values} + return cls(value=total / weight, step=step, attributes={**attributes, **score_attributes}) + + +MetricDict = dict[str, list[Metric]] + +ScorerResult = float | int | bool | Metric +ScorerCallable = t.Callable[[T], t.Awaitable[ScorerResult]] | t.Callable[[T], ScorerResult] + + +@dataclass +class Scorer(t.Generic[T]): + tracer: Tracer + + name: str + "The name of the scorer, used for reporting metrics." + tags: t.Sequence[str] + "A list of tags to attach to the metric." + attributes: dict[str, t.Any] + "A dictionary of attributes to attach to the metric." + func: ScorerCallable[T] + "The function to call to get the metric." + step: int = 0 + "The step value to attach to metrics produced by this Scorer." + auto_increment_step: bool = False + "Whether to automatically increment the step for each time this scorer is called." + + @classmethod + def from_callable( + cls, + tracer: Tracer, + func: ScorerCallable[T] | "Scorer[T]", # noqa: TC010 + *, + name: str | None = None, + tags: t.Sequence[str] | None = None, + **attributes: t.Any, + ) -> "Scorer[T]": + """ + Create a scorer from a callable function. + + Args: + tracer: The tracer to use for reporting metrics. + func: The function to call to get the metric. + name: The name of the scorer, used for reporting metrics. + tags: A list of tags to attach to the metric. + **attributes: A dictionary of attributes to attach to the metric. + + Returns: + A Scorer object. + """ + if isinstance(func, Scorer): + if name is not None or attributes is not None: + func = func.clone() + func.name = name or func.name + func.attributes.update(attributes or {}) + return func + + func = inspect.unwrap(func) + func_name = getattr( + func, + "__qualname__", + getattr(func, "__name__", safe_repr(func)), + ) + name = name or func_name + return cls( + tracer=tracer, + name=name, + tags=tags or [], + attributes=attributes or {}, + func=func, + ) + + def __post_init__(self) -> None: + self.__signature__ = inspect.signature(self.func) + self.__name__ = self.name + + def clone(self) -> "Scorer[T]": + """ + Clone the scorer. + + Returns: + A new Scorer. + """ + return Scorer( + tracer=self.tracer, + name=self.name, + tags=self.tags, + attributes=self.attributes, + func=self.func, + step=self.step, + auto_increment_step=self.auto_increment_step, + ) + + async def __call__(self, object: T) -> Metric: + """ + Execute the scorer and return the metric. + + Any output value will be converted to a Metric object. + + Args: + object: The object to score. + + Returns: + A Metric object. + """ + from dreadnode.tracing.span import Span + + with Span( + name=self.name, + tags=self.tags, + attributes=self.attributes, + tracer=self.tracer, + ): + metric = self.func(object) + if inspect.isawaitable(metric): + metric = await metric + + if not isinstance(metric, Metric): + metric = Metric( + float(metric), + step=self.step, + timestamp=datetime.now(timezone.utc), + attributes=self.attributes, + ) + + if self.auto_increment_step: + self.step += 1 + + return metric diff --git a/dreadnode/object.py b/dreadnode/object.py new file mode 100644 index 00000000..708beecd --- /dev/null +++ b/dreadnode/object.py @@ -0,0 +1,29 @@ +import typing as t +from dataclasses import dataclass + + +@dataclass +class ObjectRef: + name: str + label: str + hash: str + + +@dataclass +class ObjectUri: + hash: str + schema_hash: str + uri: str + size: int + type: t.Literal["uri"] = "uri" + + +@dataclass +class ObjectVal: + hash: str + schema_hash: str + value: t.Any + type: t.Literal["val"] = "val" + + +Object = ObjectUri | ObjectVal diff --git a/dreadnode/score.py b/dreadnode/score.py deleted file mode 100644 index 66cb2535..00000000 --- a/dreadnode/score.py +++ /dev/null @@ -1,109 +0,0 @@ -import contextlib -import inspect -import typing as t -from collections import defaultdict -from dataclasses import dataclass -from datetime import datetime, timezone -from itertools import groupby - -from logfire._internal.utils import safe_repr -from opentelemetry.trace import Tracer - -from .tracing import Metric, MetricDict, Score, Span - -T = t.TypeVar("T") - -ScorerCallable = t.Callable[[T], float | Score | t.Awaitable[float | Score]] - - -@dataclass -class Scorer(t.Generic[T]): - tracer: Tracer - - name: str - attributes: dict[str, t.Any] - func: ScorerCallable[T] - - @classmethod - def from_callable( - cls, - tracer: Tracer, - func: ScorerCallable[T] | "Scorer[T]", - *, - name: str | None = None, - attributes: dict[str, t.Any] | None = None, - ) -> "Scorer[T]": - if isinstance(func, Scorer): - if name is not None or attributes is not None: - func = func.clone() - func.name = name or func.name - func.attributes.update(attributes or {}) - return func - - func = inspect.unwrap(func) - qualified_func_name = func_name = getattr(func, "__qualname__", getattr(func, "__name__", safe_repr(func))) - with contextlib.suppress(Exception): - qualified_func_name = f"{inspect.getmodule(func).__name__}.{func_name}" # type: ignore - name = name or str(getattr(func, "__name__", qualified_func_name)) - return cls(tracer=tracer, name=name, attributes=attributes or {}, func=func) - - def __post_init__(self) -> None: - self.__signature__ = inspect.signature(self.func) - self.__name__ = self.name - - def clone(self) -> "Scorer[T]": - return Scorer( - tracer=self.tracer, - name=self.name, - attributes=self.attributes, - func=self.func, - ) - - async def __call__(self, object: T) -> Score: - with Span( - name=self.name, - attributes=self.attributes, - tracer=self.tracer, - ): - score = self.func(object) - if inspect.isawaitable(score): - score = await score - - if not isinstance(score, Score): - score = Score(timestamp=datetime.now(timezone.utc), name=self.name, value=float(score)) - - return score - - -def scores_to_metrics(scores: t.Sequence[Score]) -> MetricDict: - if not scores: - return {} - - sorted_by_name = sorted(scores, key=lambda score: score.name) - grouped_by_name = {name: list(group) for name, group in groupby(sorted_by_name, key=lambda score: score.name)} - - metrics: MetricDict = defaultdict(list) - for name, group in grouped_by_name.items(): - score_group = sorted(group, key=lambda score: score.timestamp) - - # Write the flat scores inside a single metric + cumulative - cumulative: float = 0 - for score in score_group: - metric = Metric(timestamp=score.timestamp, value=score.value, step=0) - metrics[name].append(metric) - cumulative += score.value - metrics[f"{name}.cum"].append(Metric(timestamp=score.timestamp, value=cumulative, step=0)) - - if not score_group: - continue - - # Include an average - avg_value = sum(score.value for score in score_group) / len(score_group) - avg_metric = Metric(timestamp=datetime.now(timezone.utc), value=avg_value, step=0) - metrics[f"{name}.avg"].append(avg_metric) - - # Include a count - count_metric = Metric(timestamp=datetime.now(timezone.utc), value=float(len(score_group)), step=0) - metrics[f"{name}.count"].append(count_metric) - - return dict(metrics) diff --git a/dreadnode/serialization.py b/dreadnode/serialization.py new file mode 100644 index 00000000..8964ac8c --- /dev/null +++ b/dreadnode/serialization.py @@ -0,0 +1,731 @@ +import base64 +import contextlib +import dataclasses +import datetime +import hashlib +import io +import json +import typing as t +from collections import deque +from collections.abc import Callable, Iterable, Mapping, Sequence +from decimal import Decimal +from enum import Enum +from functools import lru_cache +from ipaddress import ( + IPv4Address, + IPv4Interface, + IPv4Network, + IPv6Address, + IPv6Interface, + IPv6Network, +) +from pathlib import PosixPath +from re import Pattern +from uuid import UUID + +from dreadnode.types import JsonDict, JsonValue +from dreadnode.util import safe_repr + +# Types + +HandlerFunc = Callable[[t.Any, set[int]], tuple[JsonValue, JsonDict]] + +# Constants + +EMPTY_SCHEMA: JsonDict = {} +UNKNOWN_OBJECT_SCHEMA: JsonDict = {"type": "object", "x-python-datatype": "unknown"} + + +# Helpers + +try: + import attrs + + def _is_attrs_instance(_cls: type) -> bool: + return attrs.has(_cls) + +except ModuleNotFoundError: + + def _is_attrs_instance(_cls: type) -> bool: + return False + + +# Specific handlers + + +def _handle_sequence( + obj: Sequence[t.Any] | set[t.Any] | frozenset[t.Any], + seen: set[int], +) -> tuple[JsonValue, JsonDict]: + obj_type = type(obj) + items_list = list(obj) + + with contextlib.suppress(TypeError): + items_list.sort() # sort if possible (e.g., for sets) + + serialized: list[JsonValue] = [] + item_schemas: list[JsonDict] = [] + + non_empty_schemas_found = False + + for item in items_list: + s_item, schema_item = _serialize(item, seen) + serialized.append(s_item) + item_schemas.append(schema_item) + if schema_item != EMPTY_SCHEMA: + non_empty_schemas_found = True + + schema: JsonDict = {"type": "array"} + if obj_type != list: # noqa: E721 + schema["title"] = obj_type.__name__ + type_name_map = {tuple: "tuple", set: "set", frozenset: "set", deque: "deque"} + schema["x-python-datatype"] = type_name_map.get(obj_type, obj_type.__name__) + + if not items_list: # if empty, basic array schema is sufficient + return serialized, schema + + if not non_empty_schemas_found: + first_item_type = type(items_list[0]) + if first_item_type in {str, int, float, bool, type(None)} and all( + type(item) is first_item_type for item in items_list + ): + type_map = { + str: "string", + int: "integer", + float: "number", + bool: "boolean", + type(None): "null", + } + schema["items"] = {"type": type_map[first_item_type]} + + else: + # Check if all non-empty schemas are the same + first_real_schema = next((s for s in item_schemas if s != EMPTY_SCHEMA), None) + if first_real_schema and all(s in (first_real_schema, EMPTY_SCHEMA) for s in item_schemas): + # All items conform to the same schema (or are primitives implicitly covered) + schema["items"] = first_real_schema + else: + # Mixed schemas, use prefixItems (best for tuples, compromise for lists/sets) + schema["prefixItems"] = item_schemas # type: ignore [assignment] + + return serialized, schema + + +def _handle_mapping( + obj: Mapping[t.Any, t.Any], + seen: set[int], +) -> tuple[JsonValue, JsonDict]: + serialized_dict: JsonDict = {} + schema_properties: JsonDict = {} + + for key, value in obj.items(): + str_key = key if isinstance(key, str) else safe_repr(key) + val_serialized, val_schema = _serialize(value, seen) + serialized_dict[str_key] = val_serialized + if val_schema != EMPTY_SCHEMA: + schema_properties[str_key] = val_schema + + schema: JsonDict = {"type": "object"} + if not isinstance(obj, dict): + schema["title"] = obj.__class__.__name__ + schema["x-python-datatype"] = "Mapping" + + if schema_properties: + schema["properties"] = schema_properties + + return serialized_dict, schema + + +def _handle_bytes( + obj: bytes, + _seen: set[int], + schema_extras: JsonDict | None = None, +) -> tuple[JsonValue, JsonDict]: + schema = { + "type": "string", + "x-python-datatype": "bytes", + } + + if obj.__class__.__name__ != "bytes": + schema["title"] = obj.__class__.__name__ + + try: + serialized = obj.decode() + if not serialized.isprintable(): + raise ValueError("Non-printable characters found") # noqa: TRY301 + except (UnicodeDecodeError, ValueError): + serialized = base64.b64encode(obj).decode() + schema["format"] = "base64" + + return serialized, {**schema, **(schema_extras or {})} + + +def _handle_bytearray( + obj: bytearray, + seen: set[int], +) -> tuple[JsonValue, JsonDict]: + serialized, schema = _handle_bytes(bytes(obj), seen) + schema["x-python-datatype"] = "bytearray" + if obj.__class__.__name__ != "bytearray": + schema["title"] = obj.__class__.__name__ + return serialized, schema + + +def _handle_enum( + obj: Enum, + seen: set[int], +) -> tuple[JsonValue, JsonDict]: + enum_cls = obj.__class__ + serialized, _ = _serialize(obj.value, seen) # Process the underlying value + + # Determine schema type based on enum values + value_type = "object" + if enum_values := [e.value for e in enum_cls]: + first_val_type = type(enum_values[0]) + if all(isinstance(v, first_val_type) for v in enum_values): + type_map = {str: "string", int: "integer", float: "number", bool: "boolean"} + value_type = type_map.get(first_val_type, "object") + + # Get serialized representations of all possible enum values + serialized_enum_values = [] + for e in enum_cls: + s_enum_val, _ = _serialize(e.value, seen.copy()) + serialized_enum_values.append(s_enum_val) + + schema: JsonDict = { + "type": value_type, + "title": enum_cls.__name__, + "x-python-datatype": "Enum", + "enum": serialized_enum_values, + } + + return serialized, schema + + +def _handle_datetime_iso( + obj: datetime.date | datetime.datetime | datetime.time, + _seen: set[int], +) -> tuple[JsonValue, JsonDict]: + format_map = { + datetime.datetime: "date-time", + datetime.date: "date", + datetime.time: "time", + } + return obj.isoformat(), { + "type": "string", + "format": format_map.get(type(obj), "unknown-datetime"), + } + + +def _handle_timedelta( + obj: datetime.timedelta, + _seen: set[int], +) -> tuple[JsonValue, JsonDict]: + return obj.total_seconds(), { + "type": "number", + "format": "time-delta-seconds", + "x-python-datatype": "timedelta", + } + + +def _handle_decimal( + obj: Decimal, + _seen: set[int], +) -> tuple[JsonValue, JsonDict]: + return str(obj), {"type": "string", "format": "decimal"} + + +def _handle_str_based( + obj: t.Any, + _seen: set[int], + schema_extras: JsonDict | None = None, +) -> tuple[JsonValue, JsonDict]: + return str(obj), {"type": "string", **(schema_extras or {})} + + +def _handle_uuid( + obj: UUID, + seen: set[int], +) -> tuple[JsonValue, JsonDict]: + return _handle_str_based(obj, seen, {"format": "uuid"}) + + +def _handle_path( + obj: PosixPath, + seen: set[int], +) -> tuple[JsonValue, JsonDict]: + return _handle_str_based( + obj, + seen, + {"format": "path", "x-python-datatype": "PosixPath"}, + ) + + +def _handle_pattern( + obj: Pattern[t.Any], + seen: set[int], +) -> tuple[JsonValue, JsonDict]: + return _handle_str_based(obj.pattern, seen, {"format": "regex"}) + + +def _handle_exception(obj: Exception, _seen: set[int]) -> tuple[JsonValue, JsonDict]: + return _handle_str_based( + obj, + _seen, + {"title": obj.__class__.__name__, "x-python-datatype": "Exception"}, + ) + + +def _handle_range(obj: range, _seen: set[int]) -> tuple[JsonValue, JsonDict]: + return list(obj), {"type": "array", "items": {"type": "integer"}, "x-python-datatype": "range"} + + +def _handle_custom_object( + obj: t.Any, + keys: Iterable[str], + seen: set[int], + datatype_name: str, +) -> tuple[JsonValue, JsonDict]: + obj_type = type(obj) + serialized_props: JsonDict = {} + schema_properties: JsonDict = {} + + for key in keys: + with contextlib.suppress(AttributeError): + value = getattr(obj, key) + s_value, schema_value = _serialize(value, seen) + serialized_props[key] = s_value + if schema_value != EMPTY_SCHEMA: + schema_properties[key] = schema_value + + schema: JsonDict = { + "type": "object", + "title": obj_type.__name__, + "x-python-datatype": datatype_name, + } + + if schema_properties: + schema["properties"] = schema_properties + + return serialized_props, schema + + +def _handle_dataclass(obj: t.Any, seen: set[int]) -> tuple[JsonValue, JsonDict]: + keys = [f.name for f in dataclasses.fields(obj) if f.repr] + return _handle_custom_object(obj, keys, seen, "dataclass") + + +def _handle_attrs(obj: t.Any, seen: set[int]) -> tuple[JsonValue, JsonDict]: + import attrs + + keys = [f.name for f in attrs.fields(obj.__class__)] + return _handle_custom_object(obj, keys, seen, "attrs") + + +def _handle_pydantic_model(obj: t.Any, _seen: set[int]) -> tuple[JsonValue, JsonDict]: + import pydantic + + if not isinstance(obj, pydantic.BaseModel): + return safe_repr(obj), UNKNOWN_OBJECT_SCHEMA + + schema: JsonDict = { + "type": "object", + "title": type(obj).__name__, + "x-python-datatype": "pydantic.BaseModel", + } + + with contextlib.suppress(Exception): + schema = obj.model_json_schema() + + return obj.model_dump(mode="json"), schema + + +def _handle_numpy_array( + obj: t.Any, + seen: set[int], +) -> tuple[JsonValue, JsonDict]: + import numpy # noqa: ICN001 + + if not isinstance(obj, numpy.ndarray): + return safe_repr(obj), UNKNOWN_OBJECT_SCHEMA + + serialized, schema = _handle_bytes(obj.tobytes(), seen) + + schema["x-python-datatype"] = "numpy.ndarray" + schema["x-numpy-dtype"] = str(obj.dtype) + schema["x-numpy-shape"] = list(obj.shape) + + return serialized, schema + + +def _handle_pandas_dataframe( + obj: t.Any, + seen: set[int], +) -> tuple[JsonValue, JsonDict]: + import pandas as pd + + if not isinstance(obj, pd.DataFrame): + return safe_repr(obj), UNKNOWN_OBJECT_SCHEMA + + serialized, schema = _serialize(obj.to_dict(), seen) + schema["x-python-datatype"] = "pandas.DataFrame" + + return serialized, schema + + +def _handle_pandas_series( + obj: t.Any, + seen: set[int], +) -> tuple[JsonValue, JsonDict]: + import pandas as pd + + if not isinstance(obj, pd.Series): + return safe_repr(obj), UNKNOWN_OBJECT_SCHEMA + + serialized, schema = _serialize(obj.tolist(), seen) + schema["x-python-datatype"] = "pandas.Series" + + return serialized, schema + + +def _handle_pil_image( + obj: t.Any, + _seen: set[int], +) -> tuple[JsonValue, JsonDict]: + import PIL.Image + + if not isinstance(obj, PIL.Image.Image): + return safe_repr(obj), UNKNOWN_OBJECT_SCHEMA + + buffer = io.BytesIO() + export_format = "PNG" + + if hasattr(obj, "format") and isinstance(obj.format, str): + export_format = obj.format.lower() + + obj.save(buffer, format=export_format) + + return _handle_bytes( + buffer.getvalue(), + _seen, + { + "x-python-datatype": "PIL.Image", + "format": export_format.lower(), + }, + ) + + +def _handle_pydub_audio_segment( + obj: t.Any, + _seen: set[int], +) -> tuple[JsonValue, JsonDict]: + from pydub import AudioSegment # type: ignore[import-untyped, unused-ignore, import-not-found] + + if not isinstance(obj, AudioSegment): + return safe_repr(obj), UNKNOWN_OBJECT_SCHEMA + + # AudioSegment can be in different formats, but we will use WAV as a default + # Since there is no way to get the format from the AudioSegment object, we will use WAV + # as a default format for export. TODO: Add a way to get the format from the user via tags may be. + export_format = "wav" + # Raw audio data from AudioSegment class is in bytes format. + raw_bytes_data = obj.raw_data + schema = { + "x-python-datatype": "pydub.AudioSegment", + "format": export_format, + "x-audio-sample-rate": obj.frame_rate, + "x-audio-channels": obj.channels, + "x-audio-sample-width": obj.sample_width, + } + + return _handle_bytes(raw_bytes_data, _seen, schema) + + +def _handle_moviepy_video_clip( + obj: t.Any, + _seen: set[int], +) -> tuple[JsonValue, JsonDict]: + import tempfile + from pathlib import Path + + from moviepy import ( # type: ignore[import-untyped, unused-ignore, import-not-found] + VideoFileClip, + ) + + if not isinstance(obj, VideoFileClip): + return safe_repr(obj), UNKNOWN_OBJECT_SCHEMA + + # Infer format from filename if available + export_format = "mp4" + if getattr(obj, "filename", None): + ext = Path(obj.filename).suffix.lstrip(".").lower() + if ext: + export_format = ext + + # Export video to temp file + with tempfile.NamedTemporaryFile(suffix=f".{export_format}") as temp_file: + obj.write_videofile( + temp_file.name, + ) + raw_bytes_data = Path(temp_file.name).read_bytes() + + schema = { + "x-python-datatype": "moviepy.VideoFileClip", + "format": export_format, + "start": obj.start, + "end": obj.end, + "duration": obj.duration, + "fps": obj.fps, + "size": obj.size, + "rotation": obj.rotation, + "aspect_ratio": obj.aspect_ratio, + "w": obj.w, + "h": obj.h, + "n_frames": obj.n_frames, + } + + return _handle_bytes(raw_bytes_data, _seen, schema) + + +def _handle_dataset(obj: t.Any, _seen: set[int]) -> tuple[JsonValue, JsonDict]: + import datasets # type: ignore[import-untyped] + + if not isinstance(obj, datasets.Dataset): + return safe_repr(obj), UNKNOWN_OBJECT_SCHEMA + + buffer = io.BytesIO() + obj.to_parquet(buffer) + + return _handle_bytes( + buffer.getvalue(), + _seen, + { + "x-python-datatype": "datasets.Dataset", + "format": "parquet", + }, + ) + + +@lru_cache(maxsize=1) +def _get_handlers() -> dict[type, HandlerFunc]: + handlers: dict[type, HandlerFunc] = { + list: _handle_sequence, + tuple: _handle_sequence, + set: _handle_sequence, + frozenset: _handle_sequence, + deque: _handle_sequence, + dict: _handle_mapping, + bytes: _handle_bytes, + bytearray: _handle_bytearray, + Enum: _handle_enum, + Decimal: _handle_decimal, + datetime.datetime: _handle_datetime_iso, + datetime.date: _handle_datetime_iso, + datetime.time: _handle_datetime_iso, + datetime.timedelta: _handle_timedelta, + UUID: _handle_uuid, + PosixPath: _handle_path, + Pattern: _handle_pattern, + range: _handle_range, + Exception: _handle_exception, + IPv4Address: lambda o, s: _handle_str_based(o, s, {"format": "ipv4"}), + IPv6Address: lambda o, s: _handle_str_based(o, s, {"format": "ipv6"}), + IPv4Interface: lambda o, s: _handle_str_based(o, s, {"x-python-datatype": "IPv4Interface"}), + IPv6Interface: lambda o, s: _handle_str_based(o, s, {"x-python-datatype": "IPv6Interface"}), + IPv4Network: lambda o, s: _handle_str_based(o, s, {"x-python-datatype": "IPv4Network"}), + IPv6Network: lambda o, s: _handle_str_based(o, s, {"x-python-datatype": "IPv6Network"}), + } + + # Pydantic + + with contextlib.suppress(Exception): + import pydantic + + handlers[pydantic.NameEmail] = lambda o, s: _handle_str_based( + o, + s, + {"format": "email", "x-python-datatype": "pydantic.NameEmail"}, + ) + handlers[pydantic.SecretStr] = lambda _o, s: _handle_str_based( + "***", + s, + {"x-python-datatype": "pydantic.SecretStr"}, + ) + handlers[pydantic.SecretBytes] = lambda _o, s: _handle_bytes( + b"***", + s, + {"x-python-datatype": "pydantic.SecretBytes"}, + ) + handlers[pydantic.AnyUrl] = lambda o, s: _handle_str_based( + o, + s, + {"format": "url", "x-python-datatype": "pydantic.AnyUrl"}, + ) + handlers[pydantic.BaseModel] = _handle_pydantic_model + + with contextlib.suppress(Exception): + import numpy as np + + handlers[np.ndarray] = _handle_numpy_array + handlers[np.floating] = lambda o, s: _serialize(float(o), s) + handlers[np.integer] = lambda o, s: _serialize(int(o), s) + handlers[np.bool_] = lambda o, s: _serialize(bool(o), s) + handlers[np.str_] = lambda o, s: _handle_str_based( + o, + s, + {"x-python-datatype": "numpy.str_"}, + ) + handlers[np.bytes_] = lambda o, s: _handle_bytes( + o, + s, + {"x-python-datatype": "numpy.bytes_"}, + ) + + with contextlib.suppress(Exception): + import pandas as pd + + handlers[pd.DataFrame] = _handle_pandas_dataframe + handlers[pd.Series] = _handle_pandas_series + + with contextlib.suppress(Exception): + import PIL.Image + + handlers[PIL.Image.Image] = _handle_pil_image + + with contextlib.suppress(Exception): + import datasets + + handlers[datasets.Dataset] = _handle_dataset + + with contextlib.suppress(Exception): + from pydub import AudioSegment + + handlers[AudioSegment] = _handle_pydub_audio_segment + + with contextlib.suppress(Exception): + from moviepy import VideoFileClip + + handlers[VideoFileClip] = _handle_moviepy_video_clip + + return handlers + + +# Core functions + + +def _serialize(obj: t.Any, seen: set[int] | None = None) -> tuple[JsonValue, JsonDict]: # noqa: PLR0911 + # Primitives early + + if isinstance(obj, str | int | float | bool) or obj is None: + return obj, EMPTY_SCHEMA + + # Cycle tracking + + seen = seen or set() + + obj_id = id(obj) + if obj_id in seen: + return "", {} + + seen = seen.copy() + seen.add(obj_id) + + obj_type = type(obj) + handlers = _get_handlers() + + with contextlib.suppress(Exception): + # MRO-based lookup first + + for base in obj_type.__mro__: + if base in handlers: + handler = handlers[base] + return handler(obj, seen) + + # Common collections + + if isinstance(obj, list | tuple | set | frozenset | deque): + return _handle_sequence(obj, seen) + + if isinstance(obj, Mapping): + return _handle_mapping(obj, seen) + + # Common struct types + + if dataclasses.is_dataclass(obj) and not isinstance(obj, type): + return _handle_dataclass(obj, seen) + + if _is_attrs_instance(obj_type): + return _handle_attrs(obj, seen) + + # Generic sequences (if not list/tuple/set/deque and no other handler matched) + + if isinstance(obj, Sequence): + return _handle_sequence(obj, seen) + + # Common fallbacks + + if hasattr(obj, "to_dict"): + return _serialize(obj.to_dict(), seen) + + if hasattr(obj, "asdict"): # e.g., namedtuple + return _serialize(obj.asdict(), seen) + + # Fallback to repr + + return safe_repr(obj), { + "type": "string", + "title": obj_type.__name__, + "x-python-datatype": "unknown", + } + + +@dataclasses.dataclass +class Serialized: + data: JsonValue | None + data_bytes: bytes | None + data_len: int + data_hash: str + schema: JsonDict + schema_hash: str + + +EMPTY_HASH = "0" * 16 + + +def serialize(obj: t.Any) -> Serialized: + """ + Serializes a Python object into a JSON-compatible structure and + generates a corresponding JSON Schema, ensuring consistency between + the serialization format and the schema. + + Args: + obj: The Python object to process. + + Returns: + An object containing the serialized data, schema, and their hashes. + """ + serialized, schema = _serialize(obj) + + if isinstance(serialized, str | int | bool | float): + serialized_bytes = str(serialized).encode() + else: + serialized_bytes = json.dumps(serialized, separators=(",", ":")).encode() + + schema_str = json.dumps(schema, separators=(",", ":")) + + data_hash = EMPTY_HASH + if serialized is not None: + data_hash = hashlib.sha1(serialized_bytes).hexdigest()[:16] # noqa: S324 (using sha1 for speed) + + schema_hash = EMPTY_HASH + if schema and schema != EMPTY_SCHEMA: + schema_hash = hashlib.sha1(schema_str.encode()).hexdigest()[:16] # noqa: S324 + + return Serialized( + data=serialized, + data_bytes=serialized_bytes if serialized is not None else None, + data_len=len(serialized_bytes) if serialized is not None else 0, + data_hash=data_hash, + schema=schema, + schema_hash=schema_hash, + ) diff --git a/dreadnode/task.py b/dreadnode/task.py index 65a0cbff..f58f9746 100644 --- a/dreadnode/task.py +++ b/dreadnode/task.py @@ -1,44 +1,130 @@ import asyncio import inspect +import traceback import typing as t from dataclasses import dataclass +from logfire._internal.stack_info import warn_at_user_stacklevel from opentelemetry.trace import Tracer -from .score import Scorer, ScorerCallable -from .tracing import TaskSpan, current_run_span +from dreadnode.metric import Scorer, ScorerCallable +from dreadnode.tracing.span import TaskSpan, current_run_span P = t.ParamSpec("P") R = t.TypeVar("R") +class TaskFailedWarning(UserWarning): + pass + + +class TaskGeneratorWarning(UserWarning): + pass + + class TaskSpanList(list[TaskSpan[R]]): + """ + Lightweight wrapper around a list of TaskSpans to provide some convenience methods. + """ + def sorted(self, *, reverse: bool = True) -> "TaskSpanList[R]": - return TaskSpanList(sorted(self, key=lambda span: span.average_score, reverse=reverse)) + """ + Sorts the spans in this list by their average metric value. + + Args: + reverse: If True, sorts in descending order. Defaults to True. + + Returns: + A new TaskSpanList sorted by average metric value. + """ + return TaskSpanList( + sorted( + self, + key=lambda span: span.get_average_metric_value(), + reverse=reverse, + ), + ) @t.overload - def top_n(self, n: int, *, as_outputs: t.Literal[False] = False, reverse: bool = True) -> "TaskSpanList[R]": ... + def top_n( + self, + n: int, + *, + as_outputs: t.Literal[False] = False, + reverse: bool = True, + ) -> "TaskSpanList[R]": ... @t.overload - def top_n(self, n: int, *, as_outputs: t.Literal[True], reverse: bool = True) -> list[R]: ... + def top_n( + self, + n: int, + *, + as_outputs: t.Literal[True], + reverse: bool = True, + ) -> list[R]: ... - def top_n(self, n: int, *, as_outputs: bool = False, reverse: bool = True) -> "TaskSpanList[R] | list[R]": - sorted = self.sorted(reverse=reverse)[:n] - return t.cast(list[R], [span.output for span in sorted]) if as_outputs else TaskSpanList(sorted) + def top_n( + self, + n: int, + *, + as_outputs: bool = False, + reverse: bool = True, + ) -> "TaskSpanList[R] | list[R]": + """ + Take the top n spans from this list, sorted by their average metric value. + + Args: + n: The number of spans to take. + as_outputs: If True, returns a list of outputs instead of spans. Defaults to False. + reverse: If True, sorts in descending order. Defaults to True. + + Returns: + A new TaskSpanList or list of outputs sorted by average metric value. + """ + sorted_ = self.sorted(reverse=reverse)[:n] + return ( + t.cast(list[R], [span.output for span in sorted_]) # noqa: TC006 + if as_outputs + else TaskSpanList(sorted_) + ) @dataclass class Task(t.Generic[P, R]): + """ + Structured task wrapper for a function that can be executed within a run. + + Tasks allow you to associate metadata, inputs, outputs, and metrics for a unit of work. + """ + tracer: Tracer name: str + "The name of the task. This is used for logging and tracing." + label: str + "The label of the task - used to group associated metrics and data together." attributes: dict[str, t.Any] - func: t.Callable[P, t.Awaitable[R]] + "A dictionary of attributes to attach to the task span." + func: t.Callable[P, R] + "The function to execute as the task." scorers: list[Scorer[R]] + "A list of scorers to evaluate the task's output." tags: list[str] + "A list of tags to attach to the task span." + + log_params: t.Sequence[str] | bool = False + "Whether to log all, or specific, incoming arguments to the function as parameters." + log_inputs: t.Sequence[str] | bool = True + "Whether to log all, or specific, incoming arguments to the function as inputs." + log_output: bool = True + "Whether to automatically log the result of the function as an output." def __post_init__(self) -> None: - self.__signature__ = getattr(self.func, "__signature__", inspect.signature(self.func)) + self.__signature__ = getattr( + self.func, + "__signature__", + inspect.signature(self.func), + ) self.__name__ = getattr(self.func, "__name__", self.name) self.__doc__ = getattr(self.func, "__doc__", None) @@ -49,13 +135,23 @@ def _bind_args(self, *args: P.args, **kwargs: P.kwargs) -> dict[str, t.Any]: return dict(bound_args.arguments) def clone(self) -> "Task[P, R]": + """ + Clone a task. + + Returns: + A new Task instance with the same attributes as this one. + """ return Task( tracer=self.tracer, name=self.name, + label=self.label, attributes=self.attributes.copy(), func=self.func, - scorers=self.scorers.copy(), + scorers=[scorer.clone() for scorer in self.scorers], tags=self.tags.copy(), + log_params=self.log_params, + log_inputs=self.log_inputs, + log_output=self.log_output, ) def with_( @@ -64,11 +160,36 @@ def with_( scorers: t.Sequence[Scorer[R] | ScorerCallable[R]] | None = None, name: str | None = None, tags: t.Sequence[str] | None = None, + label: str | None = None, + log_params: t.Sequence[str] | bool | None = None, + log_inputs: t.Sequence[str] | bool | None = None, + log_output: bool | None = None, append: bool = False, **attributes: t.Any, ) -> "Task[P, R]": + """ + Clone a task and modify its attributes. + + Args: + scorers: A list of new scorers to set or append to the task. + name: The new name for the task. + tags: A list of new tags to set or append to the task. + label: The new label for the task. + log_params: Whether to log all, or specific, incoming arguments to the function as parameters. + log_inputs: Whether to log all, or specific, incoming arguments to the function as inputs. + log_output: Whether to automatically log the result of the function as an output. + append: If True, appends the new scorers and tags to the existing ones. If False, replaces them. + **attributes: Additional attributes to set or update in the task. + + Returns: + A new Task instance with the modified attributes. + """ task = self.clone() task.name = name or task.name + task.label = label or task.label + task.log_params = log_params if log_params is not None else task.log_params + task.log_inputs = log_inputs if log_inputs is not None else task.log_inputs + task.log_output = log_output if log_output is not None else task.log_output new_scorers = [Scorer.from_callable(self.tracer, scorer) for scorer in (scorers or [])] new_tags = list(tags or []) @@ -85,68 +206,235 @@ def with_( return task async def run(self, *args: P.args, **kwargs: P.kwargs) -> TaskSpan[R]: + """ + Execute the task and return the result as a TaskSpan. + + Args: + args: The arguments to pass to the task. + kwargs: The keyword arguments to pass to the task. + + Returns: + The span associated with task execution. + """ run = current_run_span.get() if run is None or not run.is_recording: raise RuntimeError("Tasks must be executed within a run") + bound_args = self._bind_args(*args, **kwargs) + + params_to_log = ( + bound_args + if self.log_params is True + else {k: v for k, v in bound_args.items() if k in self.log_params} + if self.log_params is not False + else {} + ) + inputs_to_log = ( + bound_args + if self.log_inputs is True + else {k: v for k, v in bound_args.items() if k in self.log_inputs} + if self.log_inputs is not False + else {} + ) + with TaskSpan[R]( name=self.name, + label=self.label, attributes=self.attributes, - args=self._bind_args(*args, **kwargs), + params=params_to_log, + tags=self.tags, run_id=run.run_id, tracer=self.tracer, ) as span: - output = t.cast(R | t.Awaitable[R], self.func(*args, **kwargs)) + for name, value in params_to_log.items(): + span.log_param(name, value) + + input_object_hashes: list[str] = [ + span.log_input(name, value, label=f"{self.label}.input.{name}") + for name, value in inputs_to_log.items() + ] + + output = t.cast(R | t.Awaitable[R], self.func(*args, **kwargs)) # noqa: TC006 if inspect.isawaitable(output): output = await output span.output = output + if self.log_output: + output_object_hash = span.log_output( + "output", + output, + label=f"{self.label}.output", + ) + + # Link the output to the inputs + for input_object_hash in input_object_hashes: + span.run.link_objects(output_object_hash, input_object_hash) + for scorer in self.scorers: - score = await scorer(span.output) - span.scores.append(score) - run.scores.append(score) + metric = await scorer(output) + span.log_metric(scorer.name, metric, origin=output) return span async def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R: span = await self.run(*args, **kwargs) - return span.output # type: ignore + return span.output - # TODO: Not sure I'm in love with these being instance methods here. + # NOTE(nick): Not sure I'm in love with these being instance methods here. # We could move them to the top level class maybe. - async def map_run(self, count: int, *args: P.args, **kwargs: P.kwargs) -> TaskSpanList[R]: + async def map_run( + self, + count: int, + *args: P.args, + **kwargs: P.kwargs, + ) -> TaskSpanList[R]: + """ + Run the task multiple times and return a list of spans. + + Args: + count: The number of times to run the task. + args: The arguments to pass to the task. + kwargs: The keyword arguments to pass to the task. + + Returns: + A TaskSpanList associated with each task execution. + """ spans = await asyncio.gather(*[self.run(*args, **kwargs) for _ in range(count)]) return TaskSpanList(spans) async def map(self, count: int, *args: P.args, **kwargs: P.kwargs) -> list[R]: + """ + Run the task multiple times and return a list of outputs. + + Args: + count: The number of times to run the task. + args: The arguments to pass to the task. + kwargs: The keyword arguments to pass to the task. + + Returns: + A list of outputs from each task execution. + """ spans = await self.map_run(count, *args, **kwargs) - return [span.output for span in spans] # type: ignore + return [span.output for span in spans] - async def top_n(self, count: int, n: int, *args: P.args, **kwargs: P.kwargs) -> list[R]: + async def top_n( + self, + count: int, + n: int, + *args: P.args, + **kwargs: P.kwargs, + ) -> list[R]: + """ + Run the task multiple times and return the top n outputs. + + Args: + count: The number of times to run the task. + n: The number of top outputs to return. + args: The arguments to pass to the task. + kwargs: The keyword arguments to pass to the task. + + Returns: + A list of the top n outputs from the task executions. + """ spans = await self.map_run(count, *args, **kwargs) return spans.top_n(n, as_outputs=True) async def try_run(self, *args: P.args, **kwargs: P.kwargs) -> TaskSpan[R] | None: + """ + Attempt to run the task and return the result as a TaskSpan. + If the task fails, a warning is logged and None is returned. + + Args: + args: The arguments to pass to the task. + kwargs: The keyword arguments to pass to the task. + + Returns: + The span associated with task execution, or None if the task failed. + """ try: return await self.run(*args, **kwargs) - except Exception as e: # TODO: Pretty this up - print(f"Task {self.name} failed with exception: {e}") + except Exception: # noqa: BLE001 + warn_at_user_stacklevel( + f"Task '{self.name}' ({self.label}) failed:\n{traceback.format_exc()}", + TaskFailedWarning, + ) return None async def try_(self, *args: P.args, **kwargs: P.kwargs) -> R | None: + """ + Attempt to run the task and return the result. + If the task fails, a warning is logged and None is returned. + + Args: + args: The arguments to pass to the task. + kwargs: The keyword arguments to pass to the task. + + Returns: + The output of the task, or None if the task failed. + """ span = await self.try_run(*args, **kwargs) return span.output if span else None - async def try_map_run(self, count: int, *args: P.args, **kwargs: P.kwargs) -> TaskSpanList[R]: - spans = await asyncio.gather(*[self.try_run(*args, **kwargs) for _ in range(count)]) + async def try_map_run( + self, + count: int, + *args: P.args, + **kwargs: P.kwargs, + ) -> TaskSpanList[R]: + """ + Attempt to run the task multiple times and return a list of spans. + If any task fails, a warning is logged and None is returned for that task. + + Args: + count: The number of times to run the task. + args: The arguments to pass to the task. + kwargs: The keyword arguments to pass to the task. + + Returns: + A TaskSpanList associated with each task execution. + """ + spans = await asyncio.gather( + *[self.try_run(*args, **kwargs) for _ in range(count)], + ) return TaskSpanList([span for span in spans if span]) - async def try_top_n(self, count: int, n: int, *args: P.args, **kwargs: P.kwargs) -> list[R]: + async def try_top_n( + self, + count: int, + n: int, + *args: P.args, + **kwargs: P.kwargs, + ) -> list[R]: + """ + Attempt to run the task multiple times and return the top n outputs. + If any task fails, a warning is logged and None is returned for that task. + + Args: + count: The number of times to run the task. + n: The number of top outputs to return. + args: The arguments to pass to the task. + kwargs: The keyword arguments to pass to the task. + + Returns: + A list of the top n outputs from the task executions. + """ spans = await self.try_map_run(count, *args, **kwargs) return spans.top_n(n, as_outputs=True) async def try_map(self, count: int, *args: P.args, **kwargs: P.kwargs) -> list[R]: + """ + Attempt to run the task multiple times and return a list of outputs. + If any task fails, a warning is logged and None is returned for that task. + + Args: + count: The number of times to run the task. + args: The arguments to pass to the task. + kwargs: The keyword arguments to pass to the task. + + Returns: + A list of outputs from each task execution. + """ spans = await self.try_map_run(count, *args, **kwargs) - return [span.output for span in spans if span] # type: ignore + return [span.output for span in spans if span] diff --git a/dreadnode/tracing.py b/dreadnode/tracing.py deleted file mode 100644 index 3849c3db..00000000 --- a/dreadnode/tracing.py +++ /dev/null @@ -1,381 +0,0 @@ -import typing as t -from contextvars import ContextVar, Token -from copy import deepcopy -from dataclasses import dataclass, field -from datetime import datetime, timezone - -import typing_extensions as te -from logfire._internal.json_encoder import logfire_json_dumps as json_dumps -from logfire._internal.json_schema import JsonSchemaProperties, attributes_json_schema, create_json_schema -from logfire._internal.tracer import OPEN_SPANS -from logfire._internal.utils import uniquify_sequence -from opentelemetry import context as context_api -from opentelemetry import trace as trace_api -from opentelemetry.sdk.trace import ReadableSpan -from opentelemetry.trace import Tracer -from opentelemetry.util import types as otel_types -from ulid import ULID - -from .constants import ( - SPAN_ATTRIBUTE_PARENT_TASK_ID_KEY, - SPAN_ATTRIBUTE_PROJECT_KEY, - SPAN_ATTRIBUTE_RUN_ID_KEY, - SPAN_ATTRIBUTE_RUN_METRICS_KEY, - SPAN_ATTRIBUTE_RUN_PARAMS_KEY, - SPAN_ATTRIBUTE_SCHEMA_KEY, - SPAN_ATTRIBUTE_TAGS_KEY, - SPAN_ATTRIBUTE_TASK_ARGS_KEY, - SPAN_ATTRIBUTE_TASK_OUTPUT_KEY, - SPAN_ATTRIBUTE_TASK_SCORES_KEY, - SPAN_ATTRIBUTE_TYPE_KEY, - SpanType, -) - -R = t.TypeVar("R") - -JsonValue = t.Union[int, float, str, bool, None, list["JsonValue"], tuple["JsonValue", ...], "JsonDict"] -JsonDict = dict[str, JsonValue] - -current_task_span: ContextVar["TaskSpan[t.Any] | None"] = ContextVar("current_task_span", default=None) -current_run_span: ContextVar["RunSpan | None"] = ContextVar("current_run_span", default=None) - - -@dataclass -class Score: - name: str - value: float - timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc)) - attributes: JsonDict = field(default_factory=dict) - - @classmethod - def from_many(cls, name: str, values: t.Sequence[tuple[str, float, float]]) -> "Score": - "Create a composite score from individual values and weights." - total = sum(value * weight for _, value, weight in values) - weight = sum(weight for _, _, weight in values) - return cls(name, total / weight, attributes={name: value for name, value, _ in values}) - - -@dataclass -class Metric: - value: float - step: int = 0 - timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc)) - - -MetricDict = dict[str, list[Metric]] - - -class Span(ReadableSpan): - def __init__( - self, - name: str, - attributes: dict[str, t.Any], - tracer: Tracer, - type: SpanType = "span", - tags: t.Sequence[str] | None = None, - ) -> None: - self._span_name = name - self._pre_attributes = { - SPAN_ATTRIBUTE_TYPE_KEY: type, - SPAN_ATTRIBUTE_TAGS_KEY: tuple(uniquify_sequence(tags or ())), - **attributes, - } - self._tracer = tracer - - self._schema: JsonSchemaProperties = JsonSchemaProperties({}) - self._token: object | None = None # trace sdk context - self._span: trace_api.Span | None = None - - if not t.TYPE_CHECKING: - - def __getattr__(self, name: str) -> t.Any: - return getattr(self._span, name) - - def __enter__(self) -> te.Self: - if self._span is None: - self._span = self._tracer.start_span( - name=self._span_name, - attributes=prepare_otlp_attributes(self._pre_attributes), - ) - - self._span.__enter__() - - OPEN_SPANS.add(self._span) # type: ignore [arg-type] - - if self._token is None: - self._token = context_api.attach(trace_api.set_span_in_context(self._span)) - - return self - - def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: t.Any) -> None: - if self._token is None or self._span is None: - return - - context_api.detach(self._token) - self._token = None - - if not self._span.is_recording(): - return - - self._span.set_attribute(SPAN_ATTRIBUTE_SCHEMA_KEY, attributes_json_schema(self._schema)) - self._span.__exit__(exc_type, exc_value, traceback) - - OPEN_SPANS.discard(self._span) # type: ignore [arg-type] - - @property - def span_id(self) -> str: - if self._span is None: - raise ValueError("Span is not active") - return trace_api.format_span_id(self._span.get_span_context().span_id) - - @property - def trace_id(self) -> str: - if self._span is None: - raise ValueError("Span is not active") - return trace_api.format_trace_id(self._span.get_span_context().trace_id) - - @property - def is_recording(self) -> bool: - if self._span is None: - return False - return self._span.is_recording() - - @property - def tags(self) -> tuple[str, ...]: - return tuple(self.get_attribute(SPAN_ATTRIBUTE_TAGS_KEY, ())) - - @tags.setter - def tags(self, new_tags: t.Sequence[str]) -> None: - self.set_attribute(SPAN_ATTRIBUTE_TAGS_KEY, tuple(uniquify_sequence(new_tags))) - - def set_attribute(self, key: str, value: t.Any) -> None: - self._added_attributes = True - self._schema[key] = create_json_schema(value, set()) - otel_value = self._pre_attributes[key] = prepare_otlp_attribute(value) - if self._span is not None: - self._span.set_attribute(key, otel_value) - self._pre_attributes[key] = otel_value - - def set_attributes(self, attributes: dict[str, t.Any]) -> None: - for key, value in attributes.items(): - self.set_attribute(key, value) - - def get_attributes(self) -> dict[str, t.Any]: - if self._span is not None: - return getattr(self._span, "attributes", {}) - return self._pre_attributes - - def get_attribute(self, key: str, default: t.Any) -> t.Any: - return self.get_attributes().get(key, default) - - -class RunUpdateSpan(Span): - def __init__( - self, - run_id: str, - tracer: Tracer, - project: str, - *, - metrics: MetricDict | None = None, - params: JsonDict | None = None, - ) -> None: - attributes: dict[str, t.Any] = { - SPAN_ATTRIBUTE_RUN_ID_KEY: run_id, - SPAN_ATTRIBUTE_PROJECT_KEY: project, - } - - if metrics: - attributes[SPAN_ATTRIBUTE_RUN_METRICS_KEY] = metrics - if params: - attributes[SPAN_ATTRIBUTE_RUN_PARAMS_KEY] = params - - super().__init__(f"run.{run_id}.update", attributes, tracer, "run_update") - - -class RunSpan(Span): - def __init__( - self, - name: str, - project: str, - attributes: dict[str, t.Any], - tracer: Tracer, - run_id: str | None = None, - tags: t.Sequence[str] | None = None, - ) -> None: - self._params: JsonDict = {} - self._metrics: dict[str, list[Metric]] = {} - self.scores: list[Score] = [] - self.project = project - - self._last_pushed_params = deepcopy(self._params) - self._last_pushed_metrics = deepcopy(self._metrics) - - self._context_token: Token[RunSpan | None] | None = None # contextvars context - - attributes = { - SPAN_ATTRIBUTE_RUN_ID_KEY: str(run_id or ULID()), - SPAN_ATTRIBUTE_PROJECT_KEY: project, - **attributes, - } - super().__init__(name, attributes, tracer, "run", tags) - - def __enter__(self) -> te.Self: - self._context_token = current_run_span.set(self) - return super().__enter__() - - def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: t.Any) -> None: - from .score import scores_to_metrics - - score_metrics = scores_to_metrics(self.scores) - self._metrics.update(score_metrics) - - self.set_attribute(SPAN_ATTRIBUTE_RUN_PARAMS_KEY, self._params) - self.set_attribute(SPAN_ATTRIBUTE_RUN_METRICS_KEY, self._metrics) - super().__exit__(exc_type, exc_value, traceback) - if self._context_token is not None: - current_run_span.reset(self._context_token) - - def push_update(self) -> None: - if self._span is None: - return - - metrics: MetricDict | None = None - if self._last_pushed_metrics != self._metrics: - metrics = self._metrics - self._last_pushed_metrics = deepcopy(self._metrics) - - params: JsonDict | None = None - if self._last_pushed_params != self._params: - params = self._params - self._last_pushed_params = deepcopy(self._params) - - if metrics is None and params is None: - return - - with RunUpdateSpan( - run_id=self.run_id, project=self.project, tracer=self._tracer, params=params, metrics=metrics - ): - pass - - @property - def run_id(self) -> str: - return str(self.get_attribute(SPAN_ATTRIBUTE_RUN_ID_KEY, "")) - - @property - def params(self) -> JsonDict: - return self._params - - def log_param(self, key: str, value: JsonValue) -> None: - self.log_params(**{key: value}) - - def log_params(self, **params: JsonValue) -> None: - for key, value in params.items(): - self.params[key] = value - - if self._span is None: - return - - @property - def metrics(self) -> dict[str, list[Metric]]: - return self._metrics - - def log_metric(self, key: str, value: float, step: int = 0, *, timestamp: datetime | None = None) -> None: - metric = Metric(value, step, timestamp or datetime.now(timezone.utc)) - self._metrics.setdefault(key, []).append(metric) - if self._span is None: - return - - @property - def scores(self) -> list[Score]: - return self._scores - - @scores.setter - def scores(self, value: list[Score]) -> None: - self._scores = value - - -class TaskSpan(Span, t.Generic[R]): - def __init__( - self, - name: str, - attributes: dict[str, t.Any], - args: dict[str, t.Any], - run_id: str, - tracer: Tracer, - tags: t.Sequence[str] | None = None, - ) -> None: - self._args = args - self._output: R | None = None - self._scores: list[Score] = [] - - self._context_token: Token[TaskSpan[t.Any] | None] | None = None # contextvars context - - attributes = { - SPAN_ATTRIBUTE_RUN_ID_KEY: str(run_id), - SPAN_ATTRIBUTE_TASK_ARGS_KEY: args, - **attributes, - } - super().__init__(name, attributes, tracer, "task", tags) - - def __enter__(self) -> te.Self: - self._parent_task = current_task_span.get() - if self._parent_task is not None: - self.set_attribute(SPAN_ATTRIBUTE_PARENT_TASK_ID_KEY, self._parent_task.span_id) - self._context_token = current_task_span.set(self) - return super().__enter__() - - def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: t.Any) -> None: - self.set_attribute(SPAN_ATTRIBUTE_TASK_OUTPUT_KEY, self._output) - self.set_attribute(SPAN_ATTRIBUTE_TASK_ARGS_KEY, self._args) - self.set_attribute(SPAN_ATTRIBUTE_TASK_SCORES_KEY, self._scores) - super().__exit__(exc_type, exc_value, traceback) - if self._context_token is not None: - current_task_span.reset(self._context_token) - - @property - def run_id(self) -> str: - return str(self.get_attribute(SPAN_ATTRIBUTE_RUN_ID_KEY, "")) - - @property - def parent_task_id(self) -> str: - return str(self.get_attribute(SPAN_ATTRIBUTE_PARENT_TASK_ID_KEY, "")) - - @property - def output(self) -> R | None: - return self._output - - @output.setter - def output(self, value: R | None) -> None: - self._output = value - - @property - def args(self) -> dict[str, t.Any]: - return self._args - - @args.setter - def args(self, value: dict[str, t.Any]) -> None: - self._args = value - - @property - def scores(self) -> list[Score]: - return self._scores - - @scores.setter - def scores(self, value: list[Score]) -> None: - self._scores = value - - @property - def average_score(self) -> float: - if not self._scores: - return 0.0 - return sum(score.value for score in self._scores) / len(self._scores) - - -def prepare_otlp_attributes(attributes: dict[str, t.Any]) -> dict[str, otel_types.AttributeValue]: - return {key: prepare_otlp_attribute(value) for key, value in attributes.items()} - - -def prepare_otlp_attribute(value: t.Any) -> otel_types.AttributeValue: - if isinstance(value, str | int | bool | float): - return value - return json_dumps(value) diff --git a/dreadnode/tracing/__init__.py b/dreadnode/tracing/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/dreadnode/tracing/constants.py b/dreadnode/tracing/constants.py new file mode 100644 index 00000000..8f5dc281 --- /dev/null +++ b/dreadnode/tracing/constants.py @@ -0,0 +1,35 @@ +import typing as t + +SPAN_NAMESPACE = "dreadnode" + +SpanType = t.Literal["run", "task", "span", "run_update"] + +SPAN_ATTRIBUTE_VERSION = f"{SPAN_NAMESPACE}.version" +SPAN_ATTRIBUTE_TYPE = f"{SPAN_NAMESPACE}.type" +SPAN_ATTRIBUTE_SCHEMA = f"{SPAN_NAMESPACE}.schema" +SPAN_ATTRIBUTE_LABEL = f"{SPAN_NAMESPACE}.label" +SPAN_ATTRIBUTE_TAGS_ = f"{SPAN_NAMESPACE}.tags" +SPAN_ATTRIBUTE_PROJECT = f"{SPAN_NAMESPACE}.project" +SPAN_ATTRIBUTE_PARAMS = f"{SPAN_NAMESPACE}.params" +SPAN_ATTRIBUTE_INPUTS = f"{SPAN_NAMESPACE}.inputs" +SPAN_ATTRIBUTE_METRICS = f"{SPAN_NAMESPACE}.metrics" +SPAN_ATTRIBUTE_OUTPUTS = f"{SPAN_NAMESPACE}.outputs" +SPAN_ATTRIBUTE_OBJECTS = f"{SPAN_NAMESPACE}.objects" +SPAN_ATTRIBUTE_OBJECT_SCHEMAS = f"{SPAN_NAMESPACE}.object_schemas" +SPAN_ATTRIBUTE_ARTIFACTS = f"{SPAN_NAMESPACE}.artifacts" +SPAN_ATTRIBUTE_RUN_ID = f"{SPAN_NAMESPACE}.run.id" +SPAN_ATTRIBUTE_PARENT_TASK_ID = f"{SPAN_NAMESPACE}.task.parent_id" +SPAN_ATTRIBUTE_LARGE_ATTRIBUTES = f"{SPAN_NAMESPACE}.large_attributes" + +EVENT_NAME_OBJECT = f"{SPAN_NAMESPACE}.object" +EVENT_NAME_OBJECT_INPUT = f"{SPAN_NAMESPACE}.object.input" +EVENT_NAME_OBJECT_OUTPUT = f"{SPAN_NAMESPACE}.object.output" +EVENT_NAME_OBJECT_METRIC = f"{SPAN_NAMESPACE}.object.metric" +EVENT_NAME_OBJECT_LINK = f"{SPAN_NAMESPACE}.object.link" + +EVENT_ATTRIBUTE_OBJECT_LABEL = f"{SPAN_NAMESPACE}.object.label" +EVENT_ATTRIBUTE_OBJECT_HASH = f"{SPAN_NAMESPACE}.object.hash" +EVENT_ATTRIBUTE_LINK_HASH = f"{SPAN_NAMESPACE}.link.hash" +EVENT_ATTRIBUTE_ORIGIN_SPAN_ID = f"{SPAN_NAMESPACE}.origin.span_id" + +METRIC_ATTRIBUTE_SOURCE_HASH = f"{SPAN_NAMESPACE}.origin.hash" diff --git a/dreadnode/exporters.py b/dreadnode/tracing/exporters.py similarity index 82% rename from dreadnode/exporters.py rename to dreadnode/tracing/exporters.py index 853d6307..007188a6 100644 --- a/dreadnode/exporters.py +++ b/dreadnode/tracing/exporters.py @@ -18,6 +18,8 @@ from opentelemetry.sdk.trace import ReadableSpan from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult +from dreadnode.util import logger + @dataclass class FileExportConfig: @@ -51,8 +53,8 @@ def file(self) -> IO[str]: def _receive_metrics( self, metrics_data: MetricsData, - timeout_millis: float = 10_000, - **kwargs: t.Any, + timeout_millis: float = 10_000, # noqa: ARG002 + **kwargs: t.Any, # noqa: ARG002 ) -> None: if metrics_data is None: return @@ -63,10 +65,14 @@ def _receive_metrics( with self._lock: self.file.write(json_str + "\n") self.file.flush() - except Exception as e: - print(f"Failed to export metrics: {e}") + except Exception as e: # noqa: BLE001 + logger.error(f"Failed to export metrics: {e}") - def shutdown(self, timeout_millis: float = 30_000, **kwargs: t.Any) -> None: + def shutdown( + self, + timeout_millis: float = 30_000, # noqa: ARG002 + **kwargs: t.Any, # noqa: ARG002 + ) -> None: with self._lock: if self._file: self._file.close() @@ -94,12 +100,15 @@ def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult: with self._lock: self.file.write(json_str + "\n") self.file.flush() - return SpanExportResult.SUCCESS - except Exception as e: - print(f"Failed to export spans: {e}") + except Exception as e: # noqa: BLE001 + logger.error(f"Failed to export spans: {e}") return SpanExportResult.FAILURE + return SpanExportResult.SUCCESS - def force_flush(self, timeout_millis: float = 30_000) -> bool: + def force_flush( + self, + timeout_millis: float = 30_000, # noqa: ARG002 + ) -> bool: return True # We flush above def shutdown(self) -> None: @@ -130,12 +139,15 @@ def export(self, batch: Sequence[LogData]) -> LogExportResult: with self._lock: self.file.write(json_str + "\n") self.file.flush() - return LogExportResult.SUCCESS - except Exception as e: - print(f"Failed to export logs: {e}") + except Exception as e: # noqa: BLE001 + logger.error(f"Failed to export logs: {e}") return LogExportResult.FAILURE + return LogExportResult.SUCCESS - def force_flush(self, timeout_millis: float = 30_000) -> bool: + def force_flush( + self, + timeout_millis: float = 30_000, # noqa: ARG002 + ) -> bool: return True def shutdown(self) -> None: diff --git a/dreadnode/tracing/span.py b/dreadnode/tracing/span.py new file mode 100644 index 00000000..7e590fe1 --- /dev/null +++ b/dreadnode/tracing/span.py @@ -0,0 +1,801 @@ +import logging +import re +import types +import typing as t +from contextvars import ContextVar, Token +from copy import deepcopy +from datetime import datetime, timezone +from pathlib import Path + +import typing_extensions as te +from fsspec import AbstractFileSystem # type: ignore [import-untyped] +from logfire._internal.json_encoder import logfire_json_dumps as json_dumps +from logfire._internal.json_schema import ( + JsonSchemaProperties, + attributes_json_schema, + create_json_schema, +) +from logfire._internal.tracer import OPEN_SPANS +from logfire._internal.utils import uniquify_sequence +from opentelemetry import context as context_api +from opentelemetry import trace as trace_api +from opentelemetry.sdk.trace import ReadableSpan +from opentelemetry.trace import Tracer +from opentelemetry.util import types as otel_types +from ulid import ULID + +from dreadnode.artifact.merger import ArtifactMerger +from dreadnode.artifact.storage import ArtifactStorage +from dreadnode.artifact.tree_builder import ArtifactTreeBuilder, DirectoryNode +from dreadnode.constants import MAX_INLINE_OBJECT_BYTES +from dreadnode.metric import Metric, MetricDict +from dreadnode.object import Object, ObjectRef, ObjectUri, ObjectVal +from dreadnode.serialization import Serialized, serialize +from dreadnode.types import UNSET, AnyDict, JsonDict, JsonValue, Unset +from dreadnode.version import VERSION + +from .constants import ( + EVENT_ATTRIBUTE_LINK_HASH, + EVENT_ATTRIBUTE_OBJECT_HASH, + EVENT_ATTRIBUTE_OBJECT_LABEL, + EVENT_ATTRIBUTE_ORIGIN_SPAN_ID, + EVENT_NAME_OBJECT, + EVENT_NAME_OBJECT_INPUT, + EVENT_NAME_OBJECT_LINK, + EVENT_NAME_OBJECT_METRIC, + EVENT_NAME_OBJECT_OUTPUT, + METRIC_ATTRIBUTE_SOURCE_HASH, + SPAN_ATTRIBUTE_ARTIFACTS, + SPAN_ATTRIBUTE_INPUTS, + SPAN_ATTRIBUTE_LABEL, + SPAN_ATTRIBUTE_LARGE_ATTRIBUTES, + SPAN_ATTRIBUTE_METRICS, + SPAN_ATTRIBUTE_OBJECT_SCHEMAS, + SPAN_ATTRIBUTE_OBJECTS, + SPAN_ATTRIBUTE_OUTPUTS, + SPAN_ATTRIBUTE_PARAMS, + SPAN_ATTRIBUTE_PARENT_TASK_ID, + SPAN_ATTRIBUTE_PROJECT, + SPAN_ATTRIBUTE_RUN_ID, + SPAN_ATTRIBUTE_SCHEMA, + SPAN_ATTRIBUTE_TAGS_, + SPAN_ATTRIBUTE_TYPE, + SPAN_ATTRIBUTE_VERSION, + SpanType, +) + +logger = logging.getLogger(__name__) + +R = t.TypeVar("R") + + +current_task_span: ContextVar["TaskSpan[t.Any] | None"] = ContextVar( + "current_task_span", + default=None, +) +current_run_span: ContextVar["RunSpan | None"] = ContextVar( + "current_run_span", + default=None, +) + + +class Span(ReadableSpan): + def __init__( + self, + name: str, + attributes: AnyDict, + tracer: Tracer, + *, + label: str | None = None, + type: SpanType = "span", + tags: t.Sequence[str] | None = None, + ) -> None: + self._label = label or "" + self._span_name = name + self._pre_attributes = { + SPAN_ATTRIBUTE_VERSION: VERSION, + SPAN_ATTRIBUTE_TYPE: type, + SPAN_ATTRIBUTE_LABEL: self._label, + SPAN_ATTRIBUTE_TAGS_: uniquify_sequence(tags or ()), + **attributes, + } + self._tracer = tracer + + self._schema: JsonSchemaProperties = JsonSchemaProperties({}) + self._token: object | None = None # trace sdk context + self._span: trace_api.Span | None = None + + if not t.TYPE_CHECKING: + + def __getattr__(self, name: str) -> t.Any: + return getattr(self._span, name) + + def __enter__(self) -> te.Self: + if self._span is None: + self._span = self._tracer.start_span( + name=self._span_name, + attributes=prepare_otlp_attributes(self._pre_attributes), + ) + + self._span.__enter__() + + OPEN_SPANS.add(self._span) # type: ignore [arg-type] + + if self._token is None: + self._token = context_api.attach(trace_api.set_span_in_context(self._span)) + + return self + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: types.TracebackType | None, + ) -> None: + if self._token is None or self._span is None: + return + + context_api.detach(self._token) + self._token = None + + if not self._span.is_recording(): + return + + self._span.set_attribute( + SPAN_ATTRIBUTE_SCHEMA, + attributes_json_schema(self._schema) if self._schema else r"{}", + ) + self._span.__exit__(exc_type, exc_value, traceback) + + OPEN_SPANS.discard(self._span) # type: ignore [arg-type] + + @property + def span_id(self) -> str: + if self._span is None: + raise ValueError("Span is not active") + return trace_api.format_span_id(self._span.get_span_context().span_id) + + @property + def trace_id(self) -> str: + if self._span is None: + raise ValueError("Span is not active") + return trace_api.format_trace_id(self._span.get_span_context().trace_id) + + @property + def is_recording(self) -> bool: + if self._span is None: + return False + return self._span.is_recording() + + @property + def tags(self) -> tuple[str, ...]: + return tuple(self.get_attribute(SPAN_ATTRIBUTE_TAGS_, ())) + + @tags.setter + def tags(self, new_tags: t.Sequence[str]) -> None: + self.set_attribute(SPAN_ATTRIBUTE_TAGS_, uniquify_sequence(new_tags)) + + def set_attribute( + self, + key: str, + value: t.Any, + *, + schema: bool = True, + raw: bool = False, + ) -> None: + self._added_attributes = True + if schema and raw is False: + self._schema[key] = create_json_schema(value, set()) + otel_value = self._pre_attributes[key] = value if raw else prepare_otlp_attribute(value) + if self._span is not None: + self._span.set_attribute(key, otel_value) + self._pre_attributes[key] = otel_value + + def set_attributes(self, attributes: AnyDict) -> None: + for key, value in attributes.items(): + self.set_attribute(key, value) + + def get_attributes(self) -> AnyDict: + if self._span is not None: + return getattr(self._span, "attributes", {}) + return self._pre_attributes + + def get_attribute(self, key: str, default: t.Any) -> t.Any: + return self.get_attributes().get(key, default) + + def log_event( + self, + name: str, + attributes: AnyDict | None = None, + ) -> None: + if self._span is not None: + self._span.add_event( + name, + attributes=prepare_otlp_attributes(attributes or {}), + ) + + +class RunUpdateSpan(Span): + def __init__( + self, + run_id: str, + tracer: Tracer, + project: str, + *, + metrics: MetricDict | None = None, + params: JsonDict | None = None, + inputs: JsonDict | None = None, + ) -> None: + attributes: AnyDict = { + SPAN_ATTRIBUTE_RUN_ID: run_id, + SPAN_ATTRIBUTE_PROJECT: project, + } + + if metrics: + attributes[SPAN_ATTRIBUTE_METRICS] = metrics + if params: + attributes[SPAN_ATTRIBUTE_PARAMS] = params + if inputs: + attributes[SPAN_ATTRIBUTE_INPUTS] = inputs + + super().__init__(f"run.{run_id}.update", attributes, tracer, type="run_update") + + +class RunSpan(Span): + def __init__( + self, + name: str, + project: str, + attributes: AnyDict, + tracer: Tracer, + file_system: AbstractFileSystem, + prefix_path: str, + params: AnyDict | None = None, + metrics: MetricDict | None = None, + run_id: str | None = None, + tags: t.Sequence[str] | None = None, + ) -> None: + self._params = params or {} + self._metrics = metrics or {} + self._objects: dict[str, Object] = {} + self._object_schemas: dict[str, JsonDict] = {} + self._inputs: list[ObjectRef] = [] + self._outputs: list[ObjectRef] = [] + self._artifact_storage = ArtifactStorage(file_system=file_system) + self._artifacts: list[DirectoryNode] = [] + self._artifact_merger = ArtifactMerger() + self._artifact_tree_builder = ArtifactTreeBuilder( + storage=self._artifact_storage, + prefix_path=prefix_path, + ) + self.project = project + + self._last_pushed_params = deepcopy(self._params) + self._last_pushed_metrics = deepcopy(self._metrics) + + self._context_token: Token[RunSpan | None] | None = None # contextvars context + self._file_system = file_system + self._prefix_path = prefix_path + + attributes = { + SPAN_ATTRIBUTE_RUN_ID: str(run_id or ULID()), + SPAN_ATTRIBUTE_PROJECT: project, + SPAN_ATTRIBUTE_PARAMS: self._params, + SPAN_ATTRIBUTE_METRICS: self._metrics, + **attributes, + } + super().__init__(name, attributes, tracer, type="run", tags=tags) + + def __enter__(self) -> te.Self: + if current_run_span.get() is not None: + raise RuntimeError("You cannot start a run span within another run") + + self._context_token = current_run_span.set(self) + return super().__enter__() + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: types.TracebackType | None, + ) -> None: + self.set_attribute(SPAN_ATTRIBUTE_PARAMS, self._params) + self.set_attribute(SPAN_ATTRIBUTE_INPUTS, self._inputs, schema=False) + self.set_attribute(SPAN_ATTRIBUTE_METRICS, self._metrics, schema=False) + self.set_attribute(SPAN_ATTRIBUTE_OBJECTS, self._objects, schema=False) + self.set_attribute( + SPAN_ATTRIBUTE_OBJECT_SCHEMAS, + self._object_schemas, + schema=False, + ) + self.set_attribute(SPAN_ATTRIBUTE_ARTIFACTS, self._artifacts, schema=False) + + # Mark our objects attribute as large so it's stored separately + self.set_attribute( + SPAN_ATTRIBUTE_LARGE_ATTRIBUTES, + [SPAN_ATTRIBUTE_OBJECTS, SPAN_ATTRIBUTE_OBJECT_SCHEMAS], + raw=True, + ) + + super().__exit__(exc_type, exc_value, traceback) + if self._context_token is not None: + current_run_span.reset(self._context_token) + + def push_update(self) -> None: + if self._span is None: + return + + metrics: MetricDict | None = None + if self._last_pushed_metrics != self._metrics: + metrics = self._metrics + self._last_pushed_metrics = deepcopy(self._metrics) + + params: JsonDict | None = None + if self._last_pushed_params != self._params: + params = self._params + self._last_pushed_params = deepcopy(self._params) + + if metrics is None and params is None: + return + + with RunUpdateSpan( + run_id=self.run_id, + project=self.project, + tracer=self._tracer, + params=params, + metrics=metrics, + ): + pass + + @property + def run_id(self) -> str: + return str(self.get_attribute(SPAN_ATTRIBUTE_RUN_ID, "")) + + def log_object( + self, + value: t.Any, + *, + label: str | None = None, + event_name: str = EVENT_NAME_OBJECT, + **attributes: JsonValue, + ) -> str: + serialized = serialize(value) + data_hash = serialized.data_hash + schema_hash = serialized.schema_hash + + # Store object if we haven't already + if data_hash not in self._objects: + self._objects[data_hash] = self._create_object(serialized) + + object_ = self._objects[data_hash] + + # Store schema if new + if schema_hash not in self._object_schemas: + self._object_schemas[schema_hash] = serialized.schema + + # Build event attributes + event_attributes = { + **attributes, + EVENT_ATTRIBUTE_OBJECT_HASH: object_.hash, + EVENT_ATTRIBUTE_ORIGIN_SPAN_ID: trace_api.format_span_id( + trace_api.get_current_span().get_span_context().span_id, + ), + } + if label is not None: + event_attributes[EVENT_ATTRIBUTE_OBJECT_LABEL] = label + + self.log_event(name=event_name, attributes=event_attributes) + return object_.hash + + def _store_file_by_hash(self, data: bytes, full_path: str) -> str: + """ + Writes data to the given full_path in the object store if it doesn't already exist. + + Args: + data: Content to write. + full_path: The path in the object store (e.g., S3 key or local path). + + Returns: + The unstrip_protocol version of the full path (for object store URI). + """ + if not self._file_system.exists(full_path): + logger.debug("Storing new object at: %s", full_path) + with self._file_system.open(full_path, "wb") as f: + f.write(data) + + return str(self._file_system.unstrip_protocol(full_path)) + + def _create_object(self, serialized: Serialized) -> Object: + """Create an ObjectVal or ObjectUri depending on size.""" + data = serialized.data + data_bytes = serialized.data_bytes + data_len = serialized.data_len + data_hash = serialized.data_hash + schema_hash = serialized.schema_hash + + if data is None or data_bytes is None or data_len <= MAX_INLINE_OBJECT_BYTES: + return ObjectVal( + hash=data_hash, + value=data, + schema_hash=schema_hash, + ) + + # Offload to file system (e.g., S3) + full_path = f"{self._prefix_path.rstrip('/')}/{data_hash}" + object_uri = self._store_file_by_hash(data_bytes, full_path) + + return ObjectUri( + hash=data_hash, + uri=object_uri, + schema_hash=schema_hash, + size=data_len, + ) + + def get_object(self, hash_: str) -> t.Any: + return self._objects[hash_] + + def link_objects( + self, + object_hash: str, + link_hash: str, + **attributes: JsonValue, + ) -> None: + self.log_event( + name=EVENT_NAME_OBJECT_LINK, + attributes={ + **attributes, + EVENT_ATTRIBUTE_OBJECT_HASH: object_hash, + EVENT_ATTRIBUTE_LINK_HASH: link_hash, + EVENT_ATTRIBUTE_ORIGIN_SPAN_ID: ( + trace_api.format_span_id( + trace_api.get_current_span().get_span_context().span_id, + ) + ), + }, + ) + + @property + def params(self) -> AnyDict: + return self._params + + def log_param(self, key: str, value: t.Any) -> None: + self.log_params(**{key: value}) + + def log_params(self, **params: t.Any) -> None: + for key, value in params.items(): + self._params[key] = value + + # Always push updates for run params + self.push_update() + + @property + def inputs(self) -> AnyDict: + return {ref.name: self.get_object(ref.hash) for ref in self._inputs} + + def log_input( + self, + name: str, + value: t.Any, + *, + label: str | None = None, + **attributes: JsonValue, + ) -> None: + label = label or re.sub(r"\W+", "_", name.lower()) + hash_ = self.log_object( + value, + label=label, + event_name=EVENT_NAME_OBJECT_INPUT, + **attributes, + ) + self._inputs.append(ObjectRef(name, label=label, hash=hash_)) + + def log_artifact( + self, + local_uri: str | Path, + ) -> None: + """ + Logs a local file or directory as an artifact to the object store. + Preserves directory structure and uses content hashing for deduplication. + + Args: + local_uri: Path to the local file or directory + + Returns: + DirectoryNode representing the artifact's tree structure + + Raises: + FileNotFoundError: If the path doesn't exist + """ + + artifact_tree = self._artifact_tree_builder.process_artifact(local_uri) + + self._artifact_merger.add_tree(artifact_tree) + + self._artifacts = self._artifact_merger.get_merged_trees() + + @property + def metrics(self) -> MetricDict: + return self._metrics + + @t.overload + def log_metric( + self, + key: str, + value: float | bool, + *, + step: int = 0, + origin: t.Any | None = None, + timestamp: datetime | None = None, + ) -> None: ... + + @t.overload + def log_metric( + self, + key: str, + value: Metric, + *, + origin: t.Any | None = None, + ) -> None: ... + + def log_metric( + self, + key: str, + value: float | bool | Metric, + *, + step: int = 0, + origin: t.Any | None = None, + timestamp: datetime | None = None, + ) -> None: + metric = ( + value + if isinstance(value, Metric) + else Metric(float(value), step, timestamp or datetime.now(timezone.utc)) + ) + + if origin is not None: + origin_hash = self.log_object( + origin, + label=key, + event_name=EVENT_NAME_OBJECT_METRIC, + ) + metric.attributes[METRIC_ATTRIBUTE_SOURCE_HASH] = origin_hash + + self._metrics.setdefault(key, []).append(metric) + if self._span is None: + return + + @property + def outputs(self) -> AnyDict: + return {ref.name: self.get_object(ref.hash) for ref in self._outputs} + + def log_output( + self, + name: str, + value: t.Any, + *, + label: str | None = None, + **attributes: JsonValue, + ) -> None: + label = label or re.sub(r"\W+", "_", name.lower()) + hash_ = self.log_object( + value, + label=label, + event_name=EVENT_NAME_OBJECT_OUTPUT, + **attributes, + ) + self._outputs.append(ObjectRef(name, label=label, hash=hash_)) + + +class TaskSpan(Span, t.Generic[R]): + def __init__( + self, + name: str, + attributes: AnyDict, + run_id: str, + tracer: Tracer, + *, + label: str | None = None, + params: AnyDict | None = None, + metrics: MetricDict | None = None, + tags: t.Sequence[str] | None = None, + ) -> None: + self._params = params or {} + self._metrics = metrics or {} + self._inputs: list[ObjectRef] = [] + self._outputs: list[ObjectRef] = [] + + self._output: R | Unset = UNSET # For the python output + + self._context_token: Token[TaskSpan[t.Any] | None] | None = None # contextvars context + + attributes = { + SPAN_ATTRIBUTE_RUN_ID: str(run_id), + SPAN_ATTRIBUTE_PARAMS: self._params, + SPAN_ATTRIBUTE_INPUTS: self._inputs, + SPAN_ATTRIBUTE_METRICS: self._metrics, + SPAN_ATTRIBUTE_OUTPUTS: self._outputs, + **attributes, + } + super().__init__(name, attributes, tracer, type="task", label=label, tags=tags) + + def __enter__(self) -> te.Self: + self._parent_task = current_task_span.get() + if self._parent_task is not None: + self.set_attribute(SPAN_ATTRIBUTE_PARENT_TASK_ID, self._parent_task.span_id) + + self._run = current_run_span.get() + if self._run is None: + raise RuntimeError("You cannot start a task span without a run") + + self._context_token = current_task_span.set(self) + return super().__enter__() + + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: types.TracebackType | None, + ) -> None: + self.set_attribute(SPAN_ATTRIBUTE_PARAMS, self._params) + self.set_attribute(SPAN_ATTRIBUTE_INPUTS, self._inputs, schema=False) + self.set_attribute(SPAN_ATTRIBUTE_METRICS, self._metrics, schema=False) + self.set_attribute(SPAN_ATTRIBUTE_OUTPUTS, self._outputs, schema=False) + super().__exit__(exc_type, exc_value, traceback) + if self._context_token is not None: + current_task_span.reset(self._context_token) + + @property + def run_id(self) -> str: + return str(self.get_attribute(SPAN_ATTRIBUTE_RUN_ID, "")) + + @property + def parent_task_id(self) -> str: + return str(self.get_attribute(SPAN_ATTRIBUTE_PARENT_TASK_ID, "")) + + @property + def run(self) -> RunSpan: + if self._run is None: + raise ValueError("Task span is not in an active run") + return self._run + + @property + def outputs(self) -> AnyDict: + return {ref.name: self.run.get_object(ref.hash) for ref in self._outputs} + + @property + def output(self) -> R: + if isinstance(self._output, Unset): + raise TypeError("Task output is not set") + return self._output + + @output.setter + def output(self, value: R) -> None: + self._output = value + + def log_output( + self, + name: str, + value: t.Any, + *, + label: str | None = None, + **attributes: JsonValue, + ) -> str: + label = label or re.sub(r"\W+", "_", name.lower()) + hash_ = self.run.log_object( + value, + label=label, + event_name=EVENT_NAME_OBJECT_OUTPUT, + **attributes, + ) + self._outputs.append(ObjectRef(name, label=label, hash=hash_)) + return hash_ + + @property + def params(self) -> AnyDict: + return self._params + + def log_param(self, key: str, value: t.Any) -> None: + self.log_params(**{key: value}) + + def log_params(self, **params: t.Any) -> None: + self._params.update(params) + + @property + def inputs(self) -> AnyDict: + return {ref.name: self.run.get_object(ref.hash) for ref in self._inputs} + + def log_input( + self, + name: str, + value: t.Any, + *, + label: str | None = None, + **attributes: JsonValue, + ) -> str: + label = label or re.sub(r"\W+", "_", name.lower()) + hash_ = self.run.log_object( + value, + label=label, + event_name=EVENT_NAME_OBJECT_INPUT, + **attributes, + ) + self._inputs.append(ObjectRef(name, label=label, hash=hash_)) + return hash_ + + @property + def metrics(self) -> dict[str, list[Metric]]: + return self._metrics + + @t.overload + def log_metric( + self, + key: str, + value: float | bool, + *, + step: int = 0, + origin: t.Any | None = None, + timestamp: datetime | None = None, + ) -> None: ... + + @t.overload + def log_metric( + self, + key: str, + value: Metric, + *, + origin: t.Any | None = None, + ) -> None: ... + + def log_metric( + self, + key: str, + value: float | bool | Metric, + *, + step: int = 0, + origin: t.Any | None = None, + timestamp: datetime | None = None, + ) -> None: + metric = ( + value + if isinstance(value, Metric) + else Metric(float(value), step, timestamp or datetime.now(timezone.utc)) + ) + + if origin is not None: + origin_hash = self.run.log_object( + origin, + label=key, + event_name=EVENT_NAME_OBJECT_METRIC, + ) + metric.attributes[METRIC_ATTRIBUTE_SOURCE_HASH] = origin_hash + + self._metrics.setdefault(key, []).append(metric) + + # For every metric we log, also log it to the run + # with our `label` as a prefix. + # + # Don't include `source` as we handled it here. + if (run := current_run_span.get()) is not None: + run.log_metric(f"{self._label}.{key}", metric) + + def get_average_metric_value(self, key: str | None = None) -> float: + metrics = ( + self._metrics.get(key, []) + if key is not None + else [m for ms in self._metrics.values() for m in ms] + ) + return sum(metric.value for metric in metrics) / len( + metrics, + ) + + +def prepare_otlp_attributes( + attributes: AnyDict, +) -> dict[str, otel_types.AttributeValue]: + return {key: prepare_otlp_attribute(value) for key, value in attributes.items()} + + +def prepare_otlp_attribute(value: t.Any) -> otel_types.AttributeValue: + if isinstance(value, str | int | bool | float): + return value + return json_dumps(value) diff --git a/dreadnode/types.py b/dreadnode/types.py new file mode 100644 index 00000000..030b3d35 --- /dev/null +++ b/dreadnode/types.py @@ -0,0 +1,25 @@ +import typing as t + +# Common types + +JsonValue = t.Union[ + int, + float, + str, + bool, + None, + list["JsonValue"], + tuple["JsonValue", ...], + "JsonDict", +] +JsonDict = dict[str, JsonValue] + +AnyDict = dict[str, t.Any] + + +class Unset: + def __bool__(self) -> t.Literal[False]: + return False + + +UNSET: Unset = Unset() diff --git a/dreadnode/util.py b/dreadnode/util.py new file mode 100644 index 00000000..30fdd233 --- /dev/null +++ b/dreadnode/util.py @@ -0,0 +1,150 @@ +# Lots of utilities shamelessly copied from the `logfire` package. +# https://github.com/pydantic/logfire + +import inspect +import logging +import os +import sys +import typing as t +from contextlib import contextmanager +from pathlib import Path +from types import TracebackType + +from logfire import suppress_instrumentation +from logfire._internal.stack_info import add_non_user_code_prefix, is_user_code + +import dreadnode + +SysExcInfo = ( + tuple[type[BaseException], BaseException, TracebackType | None] | tuple[None, None, None] +) +""" +The return type of sys.exc_info(): exc_type, exc_val, exc_tb. +""" + +logger = logging.getLogger("dreadnode") + +add_non_user_code_prefix(Path(dreadnode.__file__).parent) + + +def safe_repr(obj: t.Any) -> str: + """ + Return some kind of non-empty string representation of an object, catching exceptions. + """ + + try: + result = repr(obj) + except Exception: # noqa: BLE001 + result = "" + + if result: + return result + + try: + return f"<{type(obj).__name__} object>" + except Exception: # noqa: BLE001 + return "" + + +def log_internal_error() -> None: + try: + current_test = os.environ.get("PYTEST_CURRENT_TEST", "") + reraise = bool(current_test and "test_internal_exception" not in current_test) + except Exception: # noqa: BLE001 + reraise = False + + if reraise: + raise # noqa: PLE0704 + + with suppress_instrumentation(): # prevent infinite recursion from the logging integration + logger.exception( + "Caught an error in Dreadnode. This will not prevent code from running, but you may lose data.", + exc_info=_internal_error_exc_info(), + ) + + +def _internal_error_exc_info() -> SysExcInfo: + """Returns an exc_info tuple with a nicely tweaked traceback.""" + original_exc_info: SysExcInfo = sys.exc_info() + exc_type, exc_val, original_tb = original_exc_info + try: + # First remove redundant frames already in the traceback about where the error was raised. + tb = original_tb + if tb and tb.tb_frame and tb.tb_frame.f_code is _HANDLE_INTERNAL_ERRORS_CODE: + # Skip the 'yield' line in _handle_internal_errors + tb = tb.tb_next + + if ( + tb + and tb.tb_frame + and tb.tb_frame.f_code.co_filename == contextmanager.__code__.co_filename + and tb.tb_frame.f_code.co_name == "inner" + ): + # Skip the 'inner' function frame when handle_internal_errors is used as a decorator. + # It looks like `return func(*args, **kwds)` + tb = tb.tb_next + + # Now add useful outer frames that give context, but skipping frames that are just about handling the error. + frame = inspect.currentframe() + # Skip this frame right here. + assert frame # noqa: S101 + frame = frame.f_back + + if frame and frame.f_code is log_internal_error.__code__: # pragma: no branch + # This function is always called from log_internal_error, so skip that frame. + frame = frame.f_back + assert frame # noqa: S101 + + if frame.f_code is _HANDLE_INTERNAL_ERRORS_CODE: + # Skip the line in _handle_internal_errors that calls log_internal_error + frame = frame.f_back + # Skip the frame defining the _handle_internal_errors context manager + assert frame # noqa: S101 + assert frame.f_code.co_name == "__exit__" # noqa: S101 + frame = frame.f_back + assert frame # noqa: S101 + # Skip the frame calling the context manager, on the `with` line. + frame = frame.f_back + else: + # `log_internal_error()` was called directly, so just skip that frame. No context manager stuff. + frame = frame.f_back + + # Now add all remaining frames from internal logfire code. + while frame and not is_user_code(frame.f_code): + tb = TracebackType( + tb_next=tb, + tb_frame=frame, + tb_lasti=frame.f_lasti, + tb_lineno=frame.f_lineno, + ) + frame = frame.f_back + + # Add up to 3 frames from user code. + for _ in range(3): + if not frame: # pragma: no cover + break + tb = TracebackType( + tb_next=tb, + tb_frame=frame, + tb_lasti=frame.f_lasti, + tb_lineno=frame.f_lineno, + ) + frame = frame.f_back + + assert exc_type # noqa: S101 + assert exc_val # noqa: S101 + exc_val = exc_val.with_traceback(tb) + return exc_type, exc_val, tb # noqa: TRY300 + except Exception: # noqa: BLE001 + return original_exc_info + + +@contextmanager +def handle_internal_errors() -> t.Iterator[None]: + try: + yield + except Exception: # noqa: BLE001 + log_internal_error() + + +_HANDLE_INTERNAL_ERRORS_CODE = inspect.unwrap(handle_internal_errors).__code__ diff --git a/examples/log_artifact.ipynb b/examples/log_artifact.ipynb new file mode 100644 index 00000000..e1827178 --- /dev/null +++ b/examples/log_artifact.ipynb @@ -0,0 +1,237 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Dreadnode Artifact Logging\n", + "\n", + "This notebook demonstrates how to log artifacts (files and directories) to your Dreadnode platform projects. Artifacts are preserved with their original structure and can be used to track assets.\n", + "\n", + "### Benefits of Artifact Logging\n", + "Artifact logging provides several key benefits for your workflow:\n", + "\n", + "- **Reproducibility**: Ensure your runs can reference the exact files used.\n", + "- **Organization**: Maintain the structure of complex file hierarchies.\n", + "- **Efficiency**: Content-based deduplication saves storage space.\n", + "- **Traceability**: Connect inputs, outputs, and code in one place.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[1mLogfire\u001b[0m project URL: \u001b]8;id=725195;https://logfire-us.pydantic.dev/raja/starter-project\u001b\\\u001b[4;36mhttps://logfire-us.pydantic.dev/raja/starter-project\u001b[0m\u001b]8;;\u001b\\\n" + ] + } + ], + "source": [ + "from dreadnode import (\n", + " configure, run, log_artifact\n", + ")\n", + "\n", + "configure(\n", + " server=\"\", # Replace with your server\n", + " token=\"\", # Replace with your token\n", + " send_to_logfire=True,\n", + " project=\"log-artifact\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Logging Entire Directory as an Artifact\n", + "When you log a directory, Dreadnode SDK will preserve the entire directory structure and all files within it. This could be useful for keeping track of datasets, model checkpoints, or collections of related files in a project." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "05:11:24.228 wise-buzzard-624\n" + ] + } + ], + "source": [ + "# Path to the directory we want to log\n", + "dir_path = \"../../../data\" # Replace with your directory path\n", + "\n", + "# Log the directory as an artifact\n", + "with run() as r:\n", + " log_artifact(dir_path)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Logging a Single File as an Artifact\n", + "For individual files like pkl, image, or standalone models, you can log them directly:" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "05:21:07.327 versatile-moth-829\n" + ] + } + ], + "source": [ + "# Path to an individual file\n", + "file_path = \"../../../data/model.pkl\" # Replace with your file path\n", + "\n", + "# Log the file as an artifact\n", + "with run() as r:\n", + " artifact = log_artifact(file_path)\n", + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Example of logging multiple artifacts in the same run\n", + "\n", + "Dreadnode's artifact logging intelligently handles overlapping directories. This is particularly useful when logging multiple related directories in the same run.\n", + "\n", + "Let's consider a file structure like this:\n", + "```bash\n", + "data/\n", + "└── audio/\n", + " ├── subaudio/\n", + " │ ├── file_example_MP3_2MG.mp3\n", + " │ └── file_example_WAV_2MG.wav\n", + " ├── subaudio2/\n", + " │ └── file_example_OOG_2MG.ogg\n", + " └── copied/\n", + " ├── subaudio/\n", + " │ ├── file_example_MP3_2MG.mp3\n", + " │ └── file_example_WAV_2MG.wav\n", + " └── subaudio2/\n", + " └── file_example_OOG_2MG.ogg\n", + "```\n", + "When logging these directories, Dreadnode intelligently merges and deduplicates:" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "05:32:48.543 Smart Directory Merging\n", + "Number of root artifacts: 1\n", + "Root directory: /Users/raja/Desktop/dreadnode/data/audio\n", + "Children: .DS_Store, subaudio, copied, subaudio2\n" + ] + } + ], + "source": [ + "import json\n", + "\n", + "# Define our paths\n", + "file_path_subaudio2 = \"../../../data/audio/subaudio2\"\n", + "file_path_subaudio = \"../../../data/audio/subaudio\"\n", + "file_path_audio = \"../../../data/audio\"\n", + "file_path_copied = \"../../../data/audio/copied\"\n", + "\n", + "# Log in different orders to see the intelligent merging\n", + "with run(\"Smart Directory Merging\") as r:\n", + " # First log sub-directories, then parent directory\n", + " artifact1 = log_artifact(file_path_subaudio2)\n", + " artifact2 = log_artifact(file_path_subaudio)\n", + " artifact3 = log_artifact(file_path_audio) # Will merge previous two artifacts\n", + " artifact4 = log_artifact(file_path_copied) # Will be merged as a subdirectory\n", + " \n", + " # Get the final artifact trees\n", + " print(f\"Number of root artifacts: {len(r._artifacts)}\")\n", + " \n", + " # Print first level of directories\n", + " for artifact in r._artifacts:\n", + " children = [child[\"dir_path\"].split(\"/\")[-1] if child[\"type\"] == \"dir\" \n", + " else child[\"final_real_path\"].split(\"/\")[-1] \n", + " for child in artifact[\"children\"]]\n", + " print(f\"Root directory: {artifact['dir_path']}\")\n", + " print(f\"Children: {', '.join(children)}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Examining the Order-Dependent Scenario\n", + "Let's look more closely at our example of logging subdirectories before parent directories:" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "05:23:28.303 Mixed Directories\n" + ] + } + ], + "source": [ + "# Example 3: Mixed independent directories\n", + "with run(\"Mixed Directories\") as r:\n", + " log_artifact(file_path_subaudio) # Independent subdirectory\n", + " log_artifact(file_path_copied) # Independent different subdirectory\n", + " \n", + " # Result: Two separate trees, no merging\n", + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.7" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/poetry.lock b/poetry.lock index bb8e0432..2f86c5df 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,12 +1,143 @@ # This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +description = "Happy Eyeballs for asyncio" +optional = false +python-versions = ">=3.9" +groups = ["main", "dev"] +files = [ + {file = "aiohappyeyeballs-2.6.1-py3-none-any.whl", hash = "sha256:f349ba8f4b75cb25c99c5c2d84e997e485204d2902a9597802b0371f09331fb8"}, + {file = "aiohappyeyeballs-2.6.1.tar.gz", hash = "sha256:c3f9d0113123803ccadfdf3f0faa505bc78e6a72d1cc4806cbd719826e943558"}, +] + +[[package]] +name = "aiohttp" +version = "3.11.18" +description = "Async http client/server framework (asyncio)" +optional = false +python-versions = ">=3.9" +groups = ["main", "dev"] +files = [ + {file = "aiohttp-3.11.18-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:96264854fedbea933a9ca4b7e0c745728f01380691687b7365d18d9e977179c4"}, + {file = "aiohttp-3.11.18-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9602044ff047043430452bc3a2089743fa85da829e6fc9ee0025351d66c332b6"}, + {file = "aiohttp-3.11.18-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5691dc38750fcb96a33ceef89642f139aa315c8a193bbd42a0c33476fd4a1609"}, + {file = "aiohttp-3.11.18-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:554c918ec43f8480b47a5ca758e10e793bd7410b83701676a4782672d670da55"}, + {file = "aiohttp-3.11.18-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a4076a2b3ba5b004b8cffca6afe18a3b2c5c9ef679b4d1e9859cf76295f8d4f"}, + {file = "aiohttp-3.11.18-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:767a97e6900edd11c762be96d82d13a1d7c4fc4b329f054e88b57cdc21fded94"}, + {file = "aiohttp-3.11.18-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0ddc9337a0fb0e727785ad4f41163cc314376e82b31846d3835673786420ef1"}, + {file = "aiohttp-3.11.18-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f414f37b244f2a97e79b98d48c5ff0789a0b4b4609b17d64fa81771ad780e415"}, + {file = "aiohttp-3.11.18-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fdb239f47328581e2ec7744ab5911f97afb10752332a6dd3d98e14e429e1a9e7"}, + {file = "aiohttp-3.11.18-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:f2c50bad73ed629cc326cc0f75aed8ecfb013f88c5af116f33df556ed47143eb"}, + {file = "aiohttp-3.11.18-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0a8d8f20c39d3fa84d1c28cdb97f3111387e48209e224408e75f29c6f8e0861d"}, + {file = "aiohttp-3.11.18-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:106032eaf9e62fd6bc6578c8b9e6dc4f5ed9a5c1c7fb2231010a1b4304393421"}, + {file = "aiohttp-3.11.18-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:b491e42183e8fcc9901d8dcd8ae644ff785590f1727f76ca86e731c61bfe6643"}, + {file = "aiohttp-3.11.18-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ad8c745ff9460a16b710e58e06a9dec11ebc0d8f4dd82091cefb579844d69868"}, + {file = "aiohttp-3.11.18-cp310-cp310-win32.whl", hash = "sha256:8e57da93e24303a883146510a434f0faf2f1e7e659f3041abc4e3fb3f6702a9f"}, + {file = "aiohttp-3.11.18-cp310-cp310-win_amd64.whl", hash = "sha256:cc93a4121d87d9f12739fc8fab0a95f78444e571ed63e40bfc78cd5abe700ac9"}, + {file = "aiohttp-3.11.18-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:427fdc56ccb6901ff8088544bde47084845ea81591deb16f957897f0f0ba1be9"}, + {file = "aiohttp-3.11.18-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c828b6d23b984255b85b9b04a5b963a74278b7356a7de84fda5e3b76866597b"}, + {file = "aiohttp-3.11.18-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5c2eaa145bb36b33af1ff2860820ba0589e165be4ab63a49aebfd0981c173b66"}, + {file = "aiohttp-3.11.18-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d518ce32179f7e2096bf4e3e8438cf445f05fedd597f252de9f54c728574756"}, + {file = "aiohttp-3.11.18-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0700055a6e05c2f4711011a44364020d7a10fbbcd02fbf3e30e8f7e7fddc8717"}, + {file = "aiohttp-3.11.18-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8bd1cde83e4684324e6ee19adfc25fd649d04078179890be7b29f76b501de8e4"}, + {file = "aiohttp-3.11.18-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73b8870fe1c9a201b8c0d12c94fe781b918664766728783241a79e0468427e4f"}, + {file = "aiohttp-3.11.18-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25557982dd36b9e32c0a3357f30804e80790ec2c4d20ac6bcc598533e04c6361"}, + {file = "aiohttp-3.11.18-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:7e889c9df381a2433802991288a61e5a19ceb4f61bd14f5c9fa165655dcb1fd1"}, + {file = "aiohttp-3.11.18-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:9ea345fda05bae217b6cce2acf3682ce3b13d0d16dd47d0de7080e5e21362421"}, + {file = "aiohttp-3.11.18-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9f26545b9940c4b46f0a9388fd04ee3ad7064c4017b5a334dd450f616396590e"}, + {file = "aiohttp-3.11.18-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:3a621d85e85dccabd700294494d7179ed1590b6d07a35709bb9bd608c7f5dd1d"}, + {file = "aiohttp-3.11.18-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9c23fd8d08eb9c2af3faeedc8c56e134acdaf36e2117ee059d7defa655130e5f"}, + {file = "aiohttp-3.11.18-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d9e6b0e519067caa4fd7fb72e3e8002d16a68e84e62e7291092a5433763dc0dd"}, + {file = "aiohttp-3.11.18-cp311-cp311-win32.whl", hash = "sha256:122f3e739f6607e5e4c6a2f8562a6f476192a682a52bda8b4c6d4254e1138f4d"}, + {file = "aiohttp-3.11.18-cp311-cp311-win_amd64.whl", hash = "sha256:e6f3c0a3a1e73e88af384b2e8a0b9f4fb73245afd47589df2afcab6b638fa0e6"}, + {file = "aiohttp-3.11.18-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:63d71eceb9cad35d47d71f78edac41fcd01ff10cacaa64e473d1aec13fa02df2"}, + {file = "aiohttp-3.11.18-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d1929da615840969929e8878d7951b31afe0bac883d84418f92e5755d7b49508"}, + {file = "aiohttp-3.11.18-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d0aebeb2392f19b184e3fdd9e651b0e39cd0f195cdb93328bd124a1d455cd0e"}, + {file = "aiohttp-3.11.18-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3849ead845e8444f7331c284132ab314b4dac43bfae1e3cf350906d4fff4620f"}, + {file = "aiohttp-3.11.18-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5e8452ad6b2863709f8b3d615955aa0807bc093c34b8e25b3b52097fe421cb7f"}, + {file = "aiohttp-3.11.18-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b8d2b42073611c860a37f718b3d61ae8b4c2b124b2e776e2c10619d920350ec"}, + {file = "aiohttp-3.11.18-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40fbf91f6a0ac317c0a07eb328a1384941872f6761f2e6f7208b63c4cc0a7ff6"}, + {file = "aiohttp-3.11.18-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ff5625413fec55216da5eaa011cf6b0a2ed67a565914a212a51aa3755b0009"}, + {file = "aiohttp-3.11.18-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7f33a92a2fde08e8c6b0c61815521324fc1612f397abf96eed86b8e31618fdb4"}, + {file = "aiohttp-3.11.18-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:11d5391946605f445ddafda5eab11caf310f90cdda1fd99865564e3164f5cff9"}, + {file = "aiohttp-3.11.18-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3cc314245deb311364884e44242e00c18b5896e4fe6d5f942e7ad7e4cb640adb"}, + {file = "aiohttp-3.11.18-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0f421843b0f70740772228b9e8093289924359d306530bcd3926f39acbe1adda"}, + {file = "aiohttp-3.11.18-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e220e7562467dc8d589e31c1acd13438d82c03d7f385c9cd41a3f6d1d15807c1"}, + {file = "aiohttp-3.11.18-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ab2ef72f8605046115bc9aa8e9d14fd49086d405855f40b79ed9e5c1f9f4faea"}, + {file = "aiohttp-3.11.18-cp312-cp312-win32.whl", hash = "sha256:12a62691eb5aac58d65200c7ae94d73e8a65c331c3a86a2e9670927e94339ee8"}, + {file = "aiohttp-3.11.18-cp312-cp312-win_amd64.whl", hash = "sha256:364329f319c499128fd5cd2d1c31c44f234c58f9b96cc57f743d16ec4f3238c8"}, + {file = "aiohttp-3.11.18-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:474215ec618974054cf5dc465497ae9708543cbfc312c65212325d4212525811"}, + {file = "aiohttp-3.11.18-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6ced70adf03920d4e67c373fd692123e34d3ac81dfa1c27e45904a628567d804"}, + {file = "aiohttp-3.11.18-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2d9f6c0152f8d71361905aaf9ed979259537981f47ad099c8b3d81e0319814bd"}, + {file = "aiohttp-3.11.18-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a35197013ed929c0aed5c9096de1fc5a9d336914d73ab3f9df14741668c0616c"}, + {file = "aiohttp-3.11.18-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:540b8a1f3a424f1af63e0af2d2853a759242a1769f9f1ab053996a392bd70118"}, + {file = "aiohttp-3.11.18-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9e6710ebebfce2ba21cee6d91e7452d1125100f41b906fb5af3da8c78b764c1"}, + {file = "aiohttp-3.11.18-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8af2ef3b4b652ff109f98087242e2ab974b2b2b496304063585e3d78de0b000"}, + {file = "aiohttp-3.11.18-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:28c3f975e5ae3dbcbe95b7e3dcd30e51da561a0a0f2cfbcdea30fc1308d72137"}, + {file = "aiohttp-3.11.18-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c28875e316c7b4c3e745172d882d8a5c835b11018e33432d281211af35794a93"}, + {file = "aiohttp-3.11.18-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:13cd38515568ae230e1ef6919e2e33da5d0f46862943fcda74e7e915096815f3"}, + {file = "aiohttp-3.11.18-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0e2a92101efb9f4c2942252c69c63ddb26d20f46f540c239ccfa5af865197bb8"}, + {file = "aiohttp-3.11.18-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e6d3e32b8753c8d45ac550b11a1090dd66d110d4ef805ffe60fa61495360b3b2"}, + {file = "aiohttp-3.11.18-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ea4cf2488156e0f281f93cc2fd365025efcba3e2d217cbe3df2840f8c73db261"}, + {file = "aiohttp-3.11.18-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d4df95ad522c53f2b9ebc07f12ccd2cb15550941e11a5bbc5ddca2ca56316d7"}, + {file = "aiohttp-3.11.18-cp313-cp313-win32.whl", hash = "sha256:cdd1bbaf1e61f0d94aced116d6e95fe25942f7a5f42382195fd9501089db5d78"}, + {file = "aiohttp-3.11.18-cp313-cp313-win_amd64.whl", hash = "sha256:bdd619c27e44382cf642223f11cfd4d795161362a5a1fc1fa3940397bc89db01"}, + {file = "aiohttp-3.11.18-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:469ac32375d9a716da49817cd26f1916ec787fc82b151c1c832f58420e6d3533"}, + {file = "aiohttp-3.11.18-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3cec21dd68924179258ae14af9f5418c1ebdbba60b98c667815891293902e5e0"}, + {file = "aiohttp-3.11.18-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b426495fb9140e75719b3ae70a5e8dd3a79def0ae3c6c27e012fc59f16544a4a"}, + {file = "aiohttp-3.11.18-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad2f41203e2808616292db5d7170cccf0c9f9c982d02544443c7eb0296e8b0c7"}, + {file = "aiohttp-3.11.18-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5bc0ae0a5e9939e423e065a3e5b00b24b8379f1db46046d7ab71753dfc7dd0e1"}, + {file = "aiohttp-3.11.18-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe7cdd3f7d1df43200e1c80f1aed86bb36033bf65e3c7cf46a2b97a253ef8798"}, + {file = "aiohttp-3.11.18-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5199be2a2f01ffdfa8c3a6f5981205242986b9e63eb8ae03fd18f736e4840721"}, + {file = "aiohttp-3.11.18-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ccec9e72660b10f8e283e91aa0295975c7bd85c204011d9f5eb69310555cf30"}, + {file = "aiohttp-3.11.18-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1596ebf17e42e293cbacc7a24c3e0dc0f8f755b40aff0402cb74c1ff6baec1d3"}, + {file = "aiohttp-3.11.18-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:eab7b040a8a873020113ba814b7db7fa935235e4cbaf8f3da17671baa1024863"}, + {file = "aiohttp-3.11.18-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:5d61df4a05476ff891cff0030329fee4088d40e4dc9b013fac01bc3c745542c2"}, + {file = "aiohttp-3.11.18-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:46533e6792e1410f9801d09fd40cbbff3f3518d1b501d6c3c5b218f427f6ff08"}, + {file = "aiohttp-3.11.18-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c1b90407ced992331dd6d4f1355819ea1c274cc1ee4d5b7046c6761f9ec11829"}, + {file = "aiohttp-3.11.18-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a2fd04ae4971b914e54fe459dd7edbbd3f2ba875d69e057d5e3c8e8cac094935"}, + {file = "aiohttp-3.11.18-cp39-cp39-win32.whl", hash = "sha256:b2f317d1678002eee6fe85670039fb34a757972284614638f82b903a03feacdc"}, + {file = "aiohttp-3.11.18-cp39-cp39-win_amd64.whl", hash = "sha256:5e7007b8d1d09bce37b54111f593d173691c530b80f27c6493b928dabed9e6ef"}, + {file = "aiohttp-3.11.18.tar.gz", hash = "sha256:ae856e1138612b7e412db63b7708735cff4d38d0399f6a5435d3dac2669f558a"}, +] + +[package.dependencies] +aiohappyeyeballs = ">=2.3.0" +aiosignal = ">=1.1.2" +async-timeout = {version = ">=4.0,<6.0", markers = "python_version < \"3.11\""} +attrs = ">=17.3.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +propcache = ">=0.2.0" +yarl = ">=1.17.0,<2.0" + +[package.extras] +speedups = ["Brotli ; platform_python_implementation == \"CPython\"", "aiodns (>=3.2.0) ; sys_platform == \"linux\" or sys_platform == \"darwin\"", "brotlicffi ; platform_python_implementation != \"CPython\""] + +[[package]] +name = "aiosignal" +version = "1.3.2" +description = "aiosignal: a list of registered asynchronous callbacks" +optional = false +python-versions = ">=3.9" +groups = ["main", "dev"] +files = [ + {file = "aiosignal-1.3.2-py2.py3-none-any.whl", hash = "sha256:45cde58e409a301715980c2b01d0c28bdde3770d8290b5eb2173759d9acb31a5"}, + {file = "aiosignal-1.3.2.tar.gz", hash = "sha256:a8c255c66fafb1e499c9351d0bf32ff2d8a0321595ebac3b93713656d2436f54"}, +] + +[package.dependencies] +frozenlist = ">=1.1.0" + [[package]] name = "annotated-types" version = "0.7.0" description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53"}, {file = "annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89"}, @@ -18,7 +149,7 @@ version = "4.9.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c"}, {file = "anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028"}, @@ -35,6 +166,525 @@ doc = ["Sphinx (>=8.2,<9.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", test = ["anyio[trio]", "blockbuster (>=1.5.23)", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1) ; python_version >= \"3.10\"", "uvloop (>=0.21) ; platform_python_implementation == \"CPython\" and platform_system != \"Windows\" and python_version < \"3.14\""] trio = ["trio (>=0.26.1)"] +[[package]] +name = "async-timeout" +version = "5.0.1" +description = "Timeout context manager for asyncio programs" +optional = false +python-versions = ">=3.8" +groups = ["main", "dev"] +markers = "python_version < \"3.11\"" +files = [ + {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"}, + {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"}, +] + +[[package]] +name = "attrs" +version = "25.3.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.8" +groups = ["main", "dev"] +files = [ + {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"}, + {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"}, +] + +[package.extras] +benchmark = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"] +tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""] + +[[package]] +name = "boto3" +version = "1.38.1" +description = "The AWS SDK for Python" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "boto3-1.38.1-py3-none-any.whl", hash = "sha256:f192a4a34885a9e3e970b5ce5e6bec947be0f3fe6c4693b2a737c14407b12a5a"}, + {file = "boto3-1.38.1.tar.gz", hash = "sha256:988e7fae7fd4d59798f84604d73a3a019c07b048f746c7c40258c0e656473887"}, +] + +[package.dependencies] +botocore = ">=1.38.1,<1.39.0" +jmespath = ">=0.7.1,<2.0.0" +s3transfer = ">=0.12.0,<0.13.0" + +[package.extras] +crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] + +[[package]] +name = "boto3-stubs" +version = "1.38.1" +description = "Type annotations for boto3 1.38.1 generated with mypy-boto3-builder 8.10.1" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "boto3_stubs-1.38.1-py3-none-any.whl", hash = "sha256:3501f98c39b8c2d613b1138a4e8881ceef2ac9497ac030be47cf4336f1aa0573"}, + {file = "boto3_stubs-1.38.1.tar.gz", hash = "sha256:25b03fdbda288c1576fbe002ecf40088e9f5d6cdf0518de8a84a7467aa898092"}, +] + +[package.dependencies] +botocore-stubs = "*" +mypy-boto3-s3 = {version = ">=1.38.0,<1.39.0", optional = true, markers = "extra == \"s3\""} +types-s3transfer = "*" +typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.12\""} + +[package.extras] +accessanalyzer = ["mypy-boto3-accessanalyzer (>=1.38.0,<1.39.0)"] +account = ["mypy-boto3-account (>=1.38.0,<1.39.0)"] +acm = ["mypy-boto3-acm (>=1.38.0,<1.39.0)"] +acm-pca = ["mypy-boto3-acm-pca (>=1.38.0,<1.39.0)"] +all = ["mypy-boto3-accessanalyzer (>=1.38.0,<1.39.0)", "mypy-boto3-account (>=1.38.0,<1.39.0)", "mypy-boto3-acm (>=1.38.0,<1.39.0)", "mypy-boto3-acm-pca (>=1.38.0,<1.39.0)", "mypy-boto3-amp (>=1.38.0,<1.39.0)", "mypy-boto3-amplify (>=1.38.0,<1.39.0)", "mypy-boto3-amplifybackend (>=1.38.0,<1.39.0)", "mypy-boto3-amplifyuibuilder (>=1.38.0,<1.39.0)", "mypy-boto3-apigateway (>=1.38.0,<1.39.0)", "mypy-boto3-apigatewaymanagementapi (>=1.38.0,<1.39.0)", "mypy-boto3-apigatewayv2 (>=1.38.0,<1.39.0)", "mypy-boto3-appconfig (>=1.38.0,<1.39.0)", "mypy-boto3-appconfigdata (>=1.38.0,<1.39.0)", "mypy-boto3-appfabric (>=1.38.0,<1.39.0)", "mypy-boto3-appflow (>=1.38.0,<1.39.0)", "mypy-boto3-appintegrations (>=1.38.0,<1.39.0)", "mypy-boto3-application-autoscaling (>=1.38.0,<1.39.0)", "mypy-boto3-application-insights (>=1.38.0,<1.39.0)", "mypy-boto3-application-signals (>=1.38.0,<1.39.0)", "mypy-boto3-applicationcostprofiler (>=1.38.0,<1.39.0)", "mypy-boto3-appmesh (>=1.38.0,<1.39.0)", "mypy-boto3-apprunner (>=1.38.0,<1.39.0)", "mypy-boto3-appstream (>=1.38.0,<1.39.0)", "mypy-boto3-appsync (>=1.38.0,<1.39.0)", "mypy-boto3-apptest (>=1.38.0,<1.39.0)", "mypy-boto3-arc-zonal-shift (>=1.38.0,<1.39.0)", "mypy-boto3-artifact (>=1.38.0,<1.39.0)", "mypy-boto3-athena (>=1.38.0,<1.39.0)", "mypy-boto3-auditmanager (>=1.38.0,<1.39.0)", "mypy-boto3-autoscaling (>=1.38.0,<1.39.0)", "mypy-boto3-autoscaling-plans (>=1.38.0,<1.39.0)", "mypy-boto3-b2bi (>=1.38.0,<1.39.0)", "mypy-boto3-backup (>=1.38.0,<1.39.0)", "mypy-boto3-backup-gateway (>=1.38.0,<1.39.0)", "mypy-boto3-backupsearch (>=1.38.0,<1.39.0)", "mypy-boto3-batch (>=1.38.0,<1.39.0)", "mypy-boto3-bcm-data-exports (>=1.38.0,<1.39.0)", "mypy-boto3-bcm-pricing-calculator (>=1.38.0,<1.39.0)", "mypy-boto3-bedrock (>=1.38.0,<1.39.0)", "mypy-boto3-bedrock-agent (>=1.38.0,<1.39.0)", "mypy-boto3-bedrock-agent-runtime (>=1.38.0,<1.39.0)", "mypy-boto3-bedrock-data-automation (>=1.38.0,<1.39.0)", "mypy-boto3-bedrock-data-automation-runtime (>=1.38.0,<1.39.0)", "mypy-boto3-bedrock-runtime (>=1.38.0,<1.39.0)", "mypy-boto3-billing (>=1.38.0,<1.39.0)", "mypy-boto3-billingconductor (>=1.38.0,<1.39.0)", "mypy-boto3-braket (>=1.38.0,<1.39.0)", "mypy-boto3-budgets (>=1.38.0,<1.39.0)", "mypy-boto3-ce (>=1.38.0,<1.39.0)", "mypy-boto3-chatbot (>=1.38.0,<1.39.0)", "mypy-boto3-chime (>=1.38.0,<1.39.0)", "mypy-boto3-chime-sdk-identity (>=1.38.0,<1.39.0)", "mypy-boto3-chime-sdk-media-pipelines (>=1.38.0,<1.39.0)", "mypy-boto3-chime-sdk-meetings (>=1.38.0,<1.39.0)", "mypy-boto3-chime-sdk-messaging (>=1.38.0,<1.39.0)", "mypy-boto3-chime-sdk-voice (>=1.38.0,<1.39.0)", "mypy-boto3-cleanrooms (>=1.38.0,<1.39.0)", "mypy-boto3-cleanroomsml (>=1.38.0,<1.39.0)", "mypy-boto3-cloud9 (>=1.38.0,<1.39.0)", "mypy-boto3-cloudcontrol (>=1.38.0,<1.39.0)", "mypy-boto3-clouddirectory (>=1.38.0,<1.39.0)", "mypy-boto3-cloudformation (>=1.38.0,<1.39.0)", "mypy-boto3-cloudfront (>=1.38.0,<1.39.0)", "mypy-boto3-cloudfront-keyvaluestore (>=1.38.0,<1.39.0)", "mypy-boto3-cloudhsm (>=1.38.0,<1.39.0)", "mypy-boto3-cloudhsmv2 (>=1.38.0,<1.39.0)", "mypy-boto3-cloudsearch (>=1.38.0,<1.39.0)", "mypy-boto3-cloudsearchdomain (>=1.38.0,<1.39.0)", "mypy-boto3-cloudtrail (>=1.38.0,<1.39.0)", "mypy-boto3-cloudtrail-data (>=1.38.0,<1.39.0)", "mypy-boto3-cloudwatch (>=1.38.0,<1.39.0)", "mypy-boto3-codeartifact (>=1.38.0,<1.39.0)", "mypy-boto3-codebuild (>=1.38.0,<1.39.0)", "mypy-boto3-codecatalyst (>=1.38.0,<1.39.0)", "mypy-boto3-codecommit (>=1.38.0,<1.39.0)", "mypy-boto3-codeconnections (>=1.38.0,<1.39.0)", "mypy-boto3-codedeploy (>=1.38.0,<1.39.0)", "mypy-boto3-codeguru-reviewer (>=1.38.0,<1.39.0)", "mypy-boto3-codeguru-security (>=1.38.0,<1.39.0)", "mypy-boto3-codeguruprofiler (>=1.38.0,<1.39.0)", "mypy-boto3-codepipeline (>=1.38.0,<1.39.0)", "mypy-boto3-codestar-connections (>=1.38.0,<1.39.0)", "mypy-boto3-codestar-notifications (>=1.38.0,<1.39.0)", "mypy-boto3-cognito-identity (>=1.38.0,<1.39.0)", "mypy-boto3-cognito-idp (>=1.38.0,<1.39.0)", "mypy-boto3-cognito-sync (>=1.38.0,<1.39.0)", "mypy-boto3-comprehend (>=1.38.0,<1.39.0)", "mypy-boto3-comprehendmedical (>=1.38.0,<1.39.0)", "mypy-boto3-compute-optimizer (>=1.38.0,<1.39.0)", "mypy-boto3-config (>=1.38.0,<1.39.0)", "mypy-boto3-connect (>=1.38.0,<1.39.0)", "mypy-boto3-connect-contact-lens (>=1.38.0,<1.39.0)", "mypy-boto3-connectcampaigns (>=1.38.0,<1.39.0)", "mypy-boto3-connectcampaignsv2 (>=1.38.0,<1.39.0)", "mypy-boto3-connectcases (>=1.38.0,<1.39.0)", "mypy-boto3-connectparticipant (>=1.38.0,<1.39.0)", "mypy-boto3-controlcatalog (>=1.38.0,<1.39.0)", "mypy-boto3-controltower (>=1.38.0,<1.39.0)", "mypy-boto3-cost-optimization-hub (>=1.38.0,<1.39.0)", "mypy-boto3-cur (>=1.38.0,<1.39.0)", "mypy-boto3-customer-profiles (>=1.38.0,<1.39.0)", "mypy-boto3-databrew (>=1.38.0,<1.39.0)", "mypy-boto3-dataexchange (>=1.38.0,<1.39.0)", "mypy-boto3-datapipeline (>=1.38.0,<1.39.0)", "mypy-boto3-datasync (>=1.38.0,<1.39.0)", "mypy-boto3-datazone (>=1.38.0,<1.39.0)", "mypy-boto3-dax (>=1.38.0,<1.39.0)", "mypy-boto3-deadline (>=1.38.0,<1.39.0)", "mypy-boto3-detective (>=1.38.0,<1.39.0)", "mypy-boto3-devicefarm (>=1.38.0,<1.39.0)", "mypy-boto3-devops-guru (>=1.38.0,<1.39.0)", "mypy-boto3-directconnect (>=1.38.0,<1.39.0)", "mypy-boto3-discovery (>=1.38.0,<1.39.0)", "mypy-boto3-dlm (>=1.38.0,<1.39.0)", "mypy-boto3-dms (>=1.38.0,<1.39.0)", "mypy-boto3-docdb (>=1.38.0,<1.39.0)", "mypy-boto3-docdb-elastic (>=1.38.0,<1.39.0)", "mypy-boto3-drs (>=1.38.0,<1.39.0)", "mypy-boto3-ds (>=1.38.0,<1.39.0)", "mypy-boto3-ds-data (>=1.38.0,<1.39.0)", "mypy-boto3-dsql (>=1.38.0,<1.39.0)", "mypy-boto3-dynamodb (>=1.38.0,<1.39.0)", "mypy-boto3-dynamodbstreams (>=1.38.0,<1.39.0)", "mypy-boto3-ebs (>=1.38.0,<1.39.0)", "mypy-boto3-ec2 (>=1.38.0,<1.39.0)", "mypy-boto3-ec2-instance-connect (>=1.38.0,<1.39.0)", "mypy-boto3-ecr (>=1.38.0,<1.39.0)", "mypy-boto3-ecr-public (>=1.38.0,<1.39.0)", "mypy-boto3-ecs (>=1.38.0,<1.39.0)", "mypy-boto3-efs (>=1.38.0,<1.39.0)", "mypy-boto3-eks (>=1.38.0,<1.39.0)", "mypy-boto3-eks-auth (>=1.38.0,<1.39.0)", "mypy-boto3-elasticache (>=1.38.0,<1.39.0)", "mypy-boto3-elasticbeanstalk (>=1.38.0,<1.39.0)", "mypy-boto3-elastictranscoder (>=1.38.0,<1.39.0)", "mypy-boto3-elb (>=1.38.0,<1.39.0)", "mypy-boto3-elbv2 (>=1.38.0,<1.39.0)", "mypy-boto3-emr (>=1.38.0,<1.39.0)", "mypy-boto3-emr-containers (>=1.38.0,<1.39.0)", "mypy-boto3-emr-serverless (>=1.38.0,<1.39.0)", "mypy-boto3-entityresolution (>=1.38.0,<1.39.0)", "mypy-boto3-es (>=1.38.0,<1.39.0)", "mypy-boto3-events (>=1.38.0,<1.39.0)", "mypy-boto3-evidently (>=1.38.0,<1.39.0)", "mypy-boto3-finspace (>=1.38.0,<1.39.0)", "mypy-boto3-finspace-data (>=1.38.0,<1.39.0)", "mypy-boto3-firehose (>=1.38.0,<1.39.0)", "mypy-boto3-fis (>=1.38.0,<1.39.0)", "mypy-boto3-fms (>=1.38.0,<1.39.0)", "mypy-boto3-forecast (>=1.38.0,<1.39.0)", "mypy-boto3-forecastquery (>=1.38.0,<1.39.0)", "mypy-boto3-frauddetector (>=1.38.0,<1.39.0)", "mypy-boto3-freetier (>=1.38.0,<1.39.0)", "mypy-boto3-fsx (>=1.38.0,<1.39.0)", "mypy-boto3-gamelift (>=1.38.0,<1.39.0)", "mypy-boto3-gameliftstreams (>=1.38.0,<1.39.0)", "mypy-boto3-geo-maps (>=1.38.0,<1.39.0)", "mypy-boto3-geo-places (>=1.38.0,<1.39.0)", "mypy-boto3-geo-routes (>=1.38.0,<1.39.0)", "mypy-boto3-glacier (>=1.38.0,<1.39.0)", "mypy-boto3-globalaccelerator (>=1.38.0,<1.39.0)", "mypy-boto3-glue (>=1.38.0,<1.39.0)", "mypy-boto3-grafana (>=1.38.0,<1.39.0)", "mypy-boto3-greengrass (>=1.38.0,<1.39.0)", "mypy-boto3-greengrassv2 (>=1.38.0,<1.39.0)", "mypy-boto3-groundstation (>=1.38.0,<1.39.0)", "mypy-boto3-guardduty (>=1.38.0,<1.39.0)", "mypy-boto3-health (>=1.38.0,<1.39.0)", "mypy-boto3-healthlake (>=1.38.0,<1.39.0)", "mypy-boto3-iam (>=1.38.0,<1.39.0)", "mypy-boto3-identitystore (>=1.38.0,<1.39.0)", "mypy-boto3-imagebuilder (>=1.38.0,<1.39.0)", "mypy-boto3-importexport (>=1.38.0,<1.39.0)", "mypy-boto3-inspector (>=1.38.0,<1.39.0)", "mypy-boto3-inspector-scan (>=1.38.0,<1.39.0)", "mypy-boto3-inspector2 (>=1.38.0,<1.39.0)", "mypy-boto3-internetmonitor (>=1.38.0,<1.39.0)", "mypy-boto3-invoicing (>=1.38.0,<1.39.0)", "mypy-boto3-iot (>=1.38.0,<1.39.0)", "mypy-boto3-iot-data (>=1.38.0,<1.39.0)", "mypy-boto3-iot-jobs-data (>=1.38.0,<1.39.0)", "mypy-boto3-iot-managed-integrations (>=1.38.0,<1.39.0)", "mypy-boto3-iotanalytics (>=1.38.0,<1.39.0)", "mypy-boto3-iotdeviceadvisor (>=1.38.0,<1.39.0)", "mypy-boto3-iotevents (>=1.38.0,<1.39.0)", "mypy-boto3-iotevents-data (>=1.38.0,<1.39.0)", "mypy-boto3-iotfleethub (>=1.38.0,<1.39.0)", "mypy-boto3-iotfleetwise (>=1.38.0,<1.39.0)", "mypy-boto3-iotsecuretunneling (>=1.38.0,<1.39.0)", "mypy-boto3-iotsitewise (>=1.38.0,<1.39.0)", "mypy-boto3-iotthingsgraph (>=1.38.0,<1.39.0)", "mypy-boto3-iottwinmaker (>=1.38.0,<1.39.0)", "mypy-boto3-iotwireless (>=1.38.0,<1.39.0)", "mypy-boto3-ivs (>=1.38.0,<1.39.0)", "mypy-boto3-ivs-realtime (>=1.38.0,<1.39.0)", "mypy-boto3-ivschat (>=1.38.0,<1.39.0)", "mypy-boto3-kafka (>=1.38.0,<1.39.0)", "mypy-boto3-kafkaconnect (>=1.38.0,<1.39.0)", "mypy-boto3-kendra (>=1.38.0,<1.39.0)", "mypy-boto3-kendra-ranking (>=1.38.0,<1.39.0)", "mypy-boto3-keyspaces (>=1.38.0,<1.39.0)", "mypy-boto3-kinesis (>=1.38.0,<1.39.0)", "mypy-boto3-kinesis-video-archived-media (>=1.38.0,<1.39.0)", "mypy-boto3-kinesis-video-media (>=1.38.0,<1.39.0)", "mypy-boto3-kinesis-video-signaling (>=1.38.0,<1.39.0)", "mypy-boto3-kinesis-video-webrtc-storage (>=1.38.0,<1.39.0)", "mypy-boto3-kinesisanalytics (>=1.38.0,<1.39.0)", "mypy-boto3-kinesisanalyticsv2 (>=1.38.0,<1.39.0)", "mypy-boto3-kinesisvideo (>=1.38.0,<1.39.0)", "mypy-boto3-kms (>=1.38.0,<1.39.0)", "mypy-boto3-lakeformation (>=1.38.0,<1.39.0)", "mypy-boto3-lambda (>=1.38.0,<1.39.0)", "mypy-boto3-launch-wizard (>=1.38.0,<1.39.0)", "mypy-boto3-lex-models (>=1.38.0,<1.39.0)", "mypy-boto3-lex-runtime (>=1.38.0,<1.39.0)", "mypy-boto3-lexv2-models (>=1.38.0,<1.39.0)", "mypy-boto3-lexv2-runtime (>=1.38.0,<1.39.0)", "mypy-boto3-license-manager (>=1.38.0,<1.39.0)", "mypy-boto3-license-manager-linux-subscriptions (>=1.38.0,<1.39.0)", "mypy-boto3-license-manager-user-subscriptions (>=1.38.0,<1.39.0)", "mypy-boto3-lightsail (>=1.38.0,<1.39.0)", "mypy-boto3-location (>=1.38.0,<1.39.0)", "mypy-boto3-logs (>=1.38.0,<1.39.0)", "mypy-boto3-lookoutequipment (>=1.38.0,<1.39.0)", "mypy-boto3-lookoutmetrics (>=1.38.0,<1.39.0)", "mypy-boto3-lookoutvision (>=1.38.0,<1.39.0)", "mypy-boto3-m2 (>=1.38.0,<1.39.0)", "mypy-boto3-machinelearning (>=1.38.0,<1.39.0)", "mypy-boto3-macie2 (>=1.38.0,<1.39.0)", "mypy-boto3-mailmanager (>=1.38.0,<1.39.0)", "mypy-boto3-managedblockchain (>=1.38.0,<1.39.0)", "mypy-boto3-managedblockchain-query (>=1.38.0,<1.39.0)", "mypy-boto3-marketplace-agreement (>=1.38.0,<1.39.0)", "mypy-boto3-marketplace-catalog (>=1.38.0,<1.39.0)", "mypy-boto3-marketplace-deployment (>=1.38.0,<1.39.0)", "mypy-boto3-marketplace-entitlement (>=1.38.0,<1.39.0)", "mypy-boto3-marketplace-reporting (>=1.38.0,<1.39.0)", "mypy-boto3-marketplacecommerceanalytics (>=1.38.0,<1.39.0)", "mypy-boto3-mediaconnect (>=1.38.0,<1.39.0)", "mypy-boto3-mediaconvert (>=1.38.0,<1.39.0)", "mypy-boto3-medialive (>=1.38.0,<1.39.0)", "mypy-boto3-mediapackage (>=1.38.0,<1.39.0)", "mypy-boto3-mediapackage-vod (>=1.38.0,<1.39.0)", "mypy-boto3-mediapackagev2 (>=1.38.0,<1.39.0)", "mypy-boto3-mediastore (>=1.38.0,<1.39.0)", "mypy-boto3-mediastore-data (>=1.38.0,<1.39.0)", "mypy-boto3-mediatailor (>=1.38.0,<1.39.0)", "mypy-boto3-medical-imaging (>=1.38.0,<1.39.0)", "mypy-boto3-memorydb (>=1.38.0,<1.39.0)", "mypy-boto3-meteringmarketplace (>=1.38.0,<1.39.0)", "mypy-boto3-mgh (>=1.38.0,<1.39.0)", "mypy-boto3-mgn (>=1.38.0,<1.39.0)", "mypy-boto3-migration-hub-refactor-spaces (>=1.38.0,<1.39.0)", "mypy-boto3-migrationhub-config (>=1.38.0,<1.39.0)", "mypy-boto3-migrationhuborchestrator (>=1.38.0,<1.39.0)", "mypy-boto3-migrationhubstrategy (>=1.38.0,<1.39.0)", "mypy-boto3-mq (>=1.38.0,<1.39.0)", "mypy-boto3-mturk (>=1.38.0,<1.39.0)", "mypy-boto3-mwaa (>=1.38.0,<1.39.0)", "mypy-boto3-neptune (>=1.38.0,<1.39.0)", "mypy-boto3-neptune-graph (>=1.38.0,<1.39.0)", "mypy-boto3-neptunedata (>=1.38.0,<1.39.0)", "mypy-boto3-network-firewall (>=1.38.0,<1.39.0)", "mypy-boto3-networkflowmonitor (>=1.38.0,<1.39.0)", "mypy-boto3-networkmanager (>=1.38.0,<1.39.0)", "mypy-boto3-networkmonitor (>=1.38.0,<1.39.0)", "mypy-boto3-notifications (>=1.38.0,<1.39.0)", "mypy-boto3-notificationscontacts (>=1.38.0,<1.39.0)", "mypy-boto3-oam (>=1.38.0,<1.39.0)", "mypy-boto3-observabilityadmin (>=1.38.0,<1.39.0)", "mypy-boto3-omics (>=1.38.0,<1.39.0)", "mypy-boto3-opensearch (>=1.38.0,<1.39.0)", "mypy-boto3-opensearchserverless (>=1.38.0,<1.39.0)", "mypy-boto3-opsworks (>=1.38.0,<1.39.0)", "mypy-boto3-opsworkscm (>=1.38.0,<1.39.0)", "mypy-boto3-organizations (>=1.38.0,<1.39.0)", "mypy-boto3-osis (>=1.38.0,<1.39.0)", "mypy-boto3-outposts (>=1.38.0,<1.39.0)", "mypy-boto3-panorama (>=1.38.0,<1.39.0)", "mypy-boto3-partnercentral-selling (>=1.38.0,<1.39.0)", "mypy-boto3-payment-cryptography (>=1.38.0,<1.39.0)", "mypy-boto3-payment-cryptography-data (>=1.38.0,<1.39.0)", "mypy-boto3-pca-connector-ad (>=1.38.0,<1.39.0)", "mypy-boto3-pca-connector-scep (>=1.38.0,<1.39.0)", "mypy-boto3-pcs (>=1.38.0,<1.39.0)", "mypy-boto3-personalize (>=1.38.0,<1.39.0)", "mypy-boto3-personalize-events (>=1.38.0,<1.39.0)", "mypy-boto3-personalize-runtime (>=1.38.0,<1.39.0)", "mypy-boto3-pi (>=1.38.0,<1.39.0)", "mypy-boto3-pinpoint (>=1.38.0,<1.39.0)", "mypy-boto3-pinpoint-email (>=1.38.0,<1.39.0)", "mypy-boto3-pinpoint-sms-voice (>=1.38.0,<1.39.0)", "mypy-boto3-pinpoint-sms-voice-v2 (>=1.38.0,<1.39.0)", "mypy-boto3-pipes (>=1.38.0,<1.39.0)", "mypy-boto3-polly (>=1.38.0,<1.39.0)", "mypy-boto3-pricing (>=1.38.0,<1.39.0)", "mypy-boto3-privatenetworks (>=1.38.0,<1.39.0)", "mypy-boto3-proton (>=1.38.0,<1.39.0)", "mypy-boto3-qapps (>=1.38.0,<1.39.0)", "mypy-boto3-qbusiness (>=1.38.0,<1.39.0)", "mypy-boto3-qconnect (>=1.38.0,<1.39.0)", "mypy-boto3-qldb (>=1.38.0,<1.39.0)", "mypy-boto3-qldb-session (>=1.38.0,<1.39.0)", "mypy-boto3-quicksight (>=1.38.0,<1.39.0)", "mypy-boto3-ram (>=1.38.0,<1.39.0)", "mypy-boto3-rbin (>=1.38.0,<1.39.0)", "mypy-boto3-rds (>=1.38.0,<1.39.0)", "mypy-boto3-rds-data (>=1.38.0,<1.39.0)", "mypy-boto3-redshift (>=1.38.0,<1.39.0)", "mypy-boto3-redshift-data (>=1.38.0,<1.39.0)", "mypy-boto3-redshift-serverless (>=1.38.0,<1.39.0)", "mypy-boto3-rekognition (>=1.38.0,<1.39.0)", "mypy-boto3-repostspace (>=1.38.0,<1.39.0)", "mypy-boto3-resiliencehub (>=1.38.0,<1.39.0)", "mypy-boto3-resource-explorer-2 (>=1.38.0,<1.39.0)", "mypy-boto3-resource-groups (>=1.38.0,<1.39.0)", "mypy-boto3-resourcegroupstaggingapi (>=1.38.0,<1.39.0)", "mypy-boto3-robomaker (>=1.38.0,<1.39.0)", "mypy-boto3-rolesanywhere (>=1.38.0,<1.39.0)", "mypy-boto3-route53 (>=1.38.0,<1.39.0)", "mypy-boto3-route53-recovery-cluster (>=1.38.0,<1.39.0)", "mypy-boto3-route53-recovery-control-config (>=1.38.0,<1.39.0)", "mypy-boto3-route53-recovery-readiness (>=1.38.0,<1.39.0)", "mypy-boto3-route53domains (>=1.38.0,<1.39.0)", "mypy-boto3-route53profiles (>=1.38.0,<1.39.0)", "mypy-boto3-route53resolver (>=1.38.0,<1.39.0)", "mypy-boto3-rum (>=1.38.0,<1.39.0)", "mypy-boto3-s3 (>=1.38.0,<1.39.0)", "mypy-boto3-s3control (>=1.38.0,<1.39.0)", "mypy-boto3-s3outposts (>=1.38.0,<1.39.0)", "mypy-boto3-s3tables (>=1.38.0,<1.39.0)", "mypy-boto3-sagemaker (>=1.38.0,<1.39.0)", "mypy-boto3-sagemaker-a2i-runtime (>=1.38.0,<1.39.0)", "mypy-boto3-sagemaker-edge (>=1.38.0,<1.39.0)", "mypy-boto3-sagemaker-featurestore-runtime (>=1.38.0,<1.39.0)", "mypy-boto3-sagemaker-geospatial (>=1.38.0,<1.39.0)", "mypy-boto3-sagemaker-metrics (>=1.38.0,<1.39.0)", "mypy-boto3-sagemaker-runtime (>=1.38.0,<1.39.0)", "mypy-boto3-savingsplans (>=1.38.0,<1.39.0)", "mypy-boto3-scheduler (>=1.38.0,<1.39.0)", "mypy-boto3-schemas (>=1.38.0,<1.39.0)", "mypy-boto3-sdb (>=1.38.0,<1.39.0)", "mypy-boto3-secretsmanager (>=1.38.0,<1.39.0)", "mypy-boto3-security-ir (>=1.38.0,<1.39.0)", "mypy-boto3-securityhub (>=1.38.0,<1.39.0)", "mypy-boto3-securitylake (>=1.38.0,<1.39.0)", "mypy-boto3-serverlessrepo (>=1.38.0,<1.39.0)", "mypy-boto3-service-quotas (>=1.38.0,<1.39.0)", "mypy-boto3-servicecatalog (>=1.38.0,<1.39.0)", "mypy-boto3-servicecatalog-appregistry (>=1.38.0,<1.39.0)", "mypy-boto3-servicediscovery (>=1.38.0,<1.39.0)", "mypy-boto3-ses (>=1.38.0,<1.39.0)", "mypy-boto3-sesv2 (>=1.38.0,<1.39.0)", "mypy-boto3-shield (>=1.38.0,<1.39.0)", "mypy-boto3-signer (>=1.38.0,<1.39.0)", "mypy-boto3-simspaceweaver (>=1.38.0,<1.39.0)", "mypy-boto3-sms (>=1.38.0,<1.39.0)", "mypy-boto3-sms-voice (>=1.38.0,<1.39.0)", "mypy-boto3-snow-device-management (>=1.38.0,<1.39.0)", "mypy-boto3-snowball (>=1.38.0,<1.39.0)", "mypy-boto3-sns (>=1.38.0,<1.39.0)", "mypy-boto3-socialmessaging (>=1.38.0,<1.39.0)", "mypy-boto3-sqs (>=1.38.0,<1.39.0)", "mypy-boto3-ssm (>=1.38.0,<1.39.0)", "mypy-boto3-ssm-contacts (>=1.38.0,<1.39.0)", "mypy-boto3-ssm-incidents (>=1.38.0,<1.39.0)", "mypy-boto3-ssm-quicksetup (>=1.38.0,<1.39.0)", "mypy-boto3-ssm-sap (>=1.38.0,<1.39.0)", "mypy-boto3-sso (>=1.38.0,<1.39.0)", "mypy-boto3-sso-admin (>=1.38.0,<1.39.0)", "mypy-boto3-sso-oidc (>=1.38.0,<1.39.0)", "mypy-boto3-stepfunctions (>=1.38.0,<1.39.0)", "mypy-boto3-storagegateway (>=1.38.0,<1.39.0)", "mypy-boto3-sts (>=1.38.0,<1.39.0)", "mypy-boto3-supplychain (>=1.38.0,<1.39.0)", "mypy-boto3-support (>=1.38.0,<1.39.0)", "mypy-boto3-support-app (>=1.38.0,<1.39.0)", "mypy-boto3-swf (>=1.38.0,<1.39.0)", "mypy-boto3-synthetics (>=1.38.0,<1.39.0)", "mypy-boto3-taxsettings (>=1.38.0,<1.39.0)", "mypy-boto3-textract (>=1.38.0,<1.39.0)", "mypy-boto3-timestream-influxdb (>=1.38.0,<1.39.0)", "mypy-boto3-timestream-query (>=1.38.0,<1.39.0)", "mypy-boto3-timestream-write (>=1.38.0,<1.39.0)", "mypy-boto3-tnb (>=1.38.0,<1.39.0)", "mypy-boto3-transcribe (>=1.38.0,<1.39.0)", "mypy-boto3-transfer (>=1.38.0,<1.39.0)", "mypy-boto3-translate (>=1.38.0,<1.39.0)", "mypy-boto3-trustedadvisor (>=1.38.0,<1.39.0)", "mypy-boto3-verifiedpermissions (>=1.38.0,<1.39.0)", "mypy-boto3-voice-id (>=1.38.0,<1.39.0)", "mypy-boto3-vpc-lattice (>=1.38.0,<1.39.0)", "mypy-boto3-waf (>=1.38.0,<1.39.0)", "mypy-boto3-waf-regional (>=1.38.0,<1.39.0)", "mypy-boto3-wafv2 (>=1.38.0,<1.39.0)", "mypy-boto3-wellarchitected (>=1.38.0,<1.39.0)", "mypy-boto3-wisdom (>=1.38.0,<1.39.0)", "mypy-boto3-workdocs (>=1.38.0,<1.39.0)", "mypy-boto3-workmail (>=1.38.0,<1.39.0)", "mypy-boto3-workmailmessageflow (>=1.38.0,<1.39.0)", "mypy-boto3-workspaces (>=1.38.0,<1.39.0)", "mypy-boto3-workspaces-thin-client (>=1.38.0,<1.39.0)", "mypy-boto3-workspaces-web (>=1.38.0,<1.39.0)", "mypy-boto3-xray (>=1.38.0,<1.39.0)"] +amp = ["mypy-boto3-amp (>=1.38.0,<1.39.0)"] +amplify = ["mypy-boto3-amplify (>=1.38.0,<1.39.0)"] +amplifybackend = ["mypy-boto3-amplifybackend (>=1.38.0,<1.39.0)"] +amplifyuibuilder = ["mypy-boto3-amplifyuibuilder (>=1.38.0,<1.39.0)"] +apigateway = ["mypy-boto3-apigateway (>=1.38.0,<1.39.0)"] +apigatewaymanagementapi = ["mypy-boto3-apigatewaymanagementapi (>=1.38.0,<1.39.0)"] +apigatewayv2 = ["mypy-boto3-apigatewayv2 (>=1.38.0,<1.39.0)"] +appconfig = ["mypy-boto3-appconfig (>=1.38.0,<1.39.0)"] +appconfigdata = ["mypy-boto3-appconfigdata (>=1.38.0,<1.39.0)"] +appfabric = ["mypy-boto3-appfabric (>=1.38.0,<1.39.0)"] +appflow = ["mypy-boto3-appflow (>=1.38.0,<1.39.0)"] +appintegrations = ["mypy-boto3-appintegrations (>=1.38.0,<1.39.0)"] +application-autoscaling = ["mypy-boto3-application-autoscaling (>=1.38.0,<1.39.0)"] +application-insights = ["mypy-boto3-application-insights (>=1.38.0,<1.39.0)"] +application-signals = ["mypy-boto3-application-signals (>=1.38.0,<1.39.0)"] +applicationcostprofiler = ["mypy-boto3-applicationcostprofiler (>=1.38.0,<1.39.0)"] +appmesh = ["mypy-boto3-appmesh (>=1.38.0,<1.39.0)"] +apprunner = ["mypy-boto3-apprunner (>=1.38.0,<1.39.0)"] +appstream = ["mypy-boto3-appstream (>=1.38.0,<1.39.0)"] +appsync = ["mypy-boto3-appsync (>=1.38.0,<1.39.0)"] +apptest = ["mypy-boto3-apptest (>=1.38.0,<1.39.0)"] +arc-zonal-shift = ["mypy-boto3-arc-zonal-shift (>=1.38.0,<1.39.0)"] +artifact = ["mypy-boto3-artifact (>=1.38.0,<1.39.0)"] +athena = ["mypy-boto3-athena (>=1.38.0,<1.39.0)"] +auditmanager = ["mypy-boto3-auditmanager (>=1.38.0,<1.39.0)"] +autoscaling = ["mypy-boto3-autoscaling (>=1.38.0,<1.39.0)"] +autoscaling-plans = ["mypy-boto3-autoscaling-plans (>=1.38.0,<1.39.0)"] +b2bi = ["mypy-boto3-b2bi (>=1.38.0,<1.39.0)"] +backup = ["mypy-boto3-backup (>=1.38.0,<1.39.0)"] +backup-gateway = ["mypy-boto3-backup-gateway (>=1.38.0,<1.39.0)"] +backupsearch = ["mypy-boto3-backupsearch (>=1.38.0,<1.39.0)"] +batch = ["mypy-boto3-batch (>=1.38.0,<1.39.0)"] +bcm-data-exports = ["mypy-boto3-bcm-data-exports (>=1.38.0,<1.39.0)"] +bcm-pricing-calculator = ["mypy-boto3-bcm-pricing-calculator (>=1.38.0,<1.39.0)"] +bedrock = ["mypy-boto3-bedrock (>=1.38.0,<1.39.0)"] +bedrock-agent = ["mypy-boto3-bedrock-agent (>=1.38.0,<1.39.0)"] +bedrock-agent-runtime = ["mypy-boto3-bedrock-agent-runtime (>=1.38.0,<1.39.0)"] +bedrock-data-automation = ["mypy-boto3-bedrock-data-automation (>=1.38.0,<1.39.0)"] +bedrock-data-automation-runtime = ["mypy-boto3-bedrock-data-automation-runtime (>=1.38.0,<1.39.0)"] +bedrock-runtime = ["mypy-boto3-bedrock-runtime (>=1.38.0,<1.39.0)"] +billing = ["mypy-boto3-billing (>=1.38.0,<1.39.0)"] +billingconductor = ["mypy-boto3-billingconductor (>=1.38.0,<1.39.0)"] +boto3 = ["boto3 (==1.38.1)"] +braket = ["mypy-boto3-braket (>=1.38.0,<1.39.0)"] +budgets = ["mypy-boto3-budgets (>=1.38.0,<1.39.0)"] +ce = ["mypy-boto3-ce (>=1.38.0,<1.39.0)"] +chatbot = ["mypy-boto3-chatbot (>=1.38.0,<1.39.0)"] +chime = ["mypy-boto3-chime (>=1.38.0,<1.39.0)"] +chime-sdk-identity = ["mypy-boto3-chime-sdk-identity (>=1.38.0,<1.39.0)"] +chime-sdk-media-pipelines = ["mypy-boto3-chime-sdk-media-pipelines (>=1.38.0,<1.39.0)"] +chime-sdk-meetings = ["mypy-boto3-chime-sdk-meetings (>=1.38.0,<1.39.0)"] +chime-sdk-messaging = ["mypy-boto3-chime-sdk-messaging (>=1.38.0,<1.39.0)"] +chime-sdk-voice = ["mypy-boto3-chime-sdk-voice (>=1.38.0,<1.39.0)"] +cleanrooms = ["mypy-boto3-cleanrooms (>=1.38.0,<1.39.0)"] +cleanroomsml = ["mypy-boto3-cleanroomsml (>=1.38.0,<1.39.0)"] +cloud9 = ["mypy-boto3-cloud9 (>=1.38.0,<1.39.0)"] +cloudcontrol = ["mypy-boto3-cloudcontrol (>=1.38.0,<1.39.0)"] +clouddirectory = ["mypy-boto3-clouddirectory (>=1.38.0,<1.39.0)"] +cloudformation = ["mypy-boto3-cloudformation (>=1.38.0,<1.39.0)"] +cloudfront = ["mypy-boto3-cloudfront (>=1.38.0,<1.39.0)"] +cloudfront-keyvaluestore = ["mypy-boto3-cloudfront-keyvaluestore (>=1.38.0,<1.39.0)"] +cloudhsm = ["mypy-boto3-cloudhsm (>=1.38.0,<1.39.0)"] +cloudhsmv2 = ["mypy-boto3-cloudhsmv2 (>=1.38.0,<1.39.0)"] +cloudsearch = ["mypy-boto3-cloudsearch (>=1.38.0,<1.39.0)"] +cloudsearchdomain = ["mypy-boto3-cloudsearchdomain (>=1.38.0,<1.39.0)"] +cloudtrail = ["mypy-boto3-cloudtrail (>=1.38.0,<1.39.0)"] +cloudtrail-data = ["mypy-boto3-cloudtrail-data (>=1.38.0,<1.39.0)"] +cloudwatch = ["mypy-boto3-cloudwatch (>=1.38.0,<1.39.0)"] +codeartifact = ["mypy-boto3-codeartifact (>=1.38.0,<1.39.0)"] +codebuild = ["mypy-boto3-codebuild (>=1.38.0,<1.39.0)"] +codecatalyst = ["mypy-boto3-codecatalyst (>=1.38.0,<1.39.0)"] +codecommit = ["mypy-boto3-codecommit (>=1.38.0,<1.39.0)"] +codeconnections = ["mypy-boto3-codeconnections (>=1.38.0,<1.39.0)"] +codedeploy = ["mypy-boto3-codedeploy (>=1.38.0,<1.39.0)"] +codeguru-reviewer = ["mypy-boto3-codeguru-reviewer (>=1.38.0,<1.39.0)"] +codeguru-security = ["mypy-boto3-codeguru-security (>=1.38.0,<1.39.0)"] +codeguruprofiler = ["mypy-boto3-codeguruprofiler (>=1.38.0,<1.39.0)"] +codepipeline = ["mypy-boto3-codepipeline (>=1.38.0,<1.39.0)"] +codestar-connections = ["mypy-boto3-codestar-connections (>=1.38.0,<1.39.0)"] +codestar-notifications = ["mypy-boto3-codestar-notifications (>=1.38.0,<1.39.0)"] +cognito-identity = ["mypy-boto3-cognito-identity (>=1.38.0,<1.39.0)"] +cognito-idp = ["mypy-boto3-cognito-idp (>=1.38.0,<1.39.0)"] +cognito-sync = ["mypy-boto3-cognito-sync (>=1.38.0,<1.39.0)"] +comprehend = ["mypy-boto3-comprehend (>=1.38.0,<1.39.0)"] +comprehendmedical = ["mypy-boto3-comprehendmedical (>=1.38.0,<1.39.0)"] +compute-optimizer = ["mypy-boto3-compute-optimizer (>=1.38.0,<1.39.0)"] +config = ["mypy-boto3-config (>=1.38.0,<1.39.0)"] +connect = ["mypy-boto3-connect (>=1.38.0,<1.39.0)"] +connect-contact-lens = ["mypy-boto3-connect-contact-lens (>=1.38.0,<1.39.0)"] +connectcampaigns = ["mypy-boto3-connectcampaigns (>=1.38.0,<1.39.0)"] +connectcampaignsv2 = ["mypy-boto3-connectcampaignsv2 (>=1.38.0,<1.39.0)"] +connectcases = ["mypy-boto3-connectcases (>=1.38.0,<1.39.0)"] +connectparticipant = ["mypy-boto3-connectparticipant (>=1.38.0,<1.39.0)"] +controlcatalog = ["mypy-boto3-controlcatalog (>=1.38.0,<1.39.0)"] +controltower = ["mypy-boto3-controltower (>=1.38.0,<1.39.0)"] +cost-optimization-hub = ["mypy-boto3-cost-optimization-hub (>=1.38.0,<1.39.0)"] +cur = ["mypy-boto3-cur (>=1.38.0,<1.39.0)"] +customer-profiles = ["mypy-boto3-customer-profiles (>=1.38.0,<1.39.0)"] +databrew = ["mypy-boto3-databrew (>=1.38.0,<1.39.0)"] +dataexchange = ["mypy-boto3-dataexchange (>=1.38.0,<1.39.0)"] +datapipeline = ["mypy-boto3-datapipeline (>=1.38.0,<1.39.0)"] +datasync = ["mypy-boto3-datasync (>=1.38.0,<1.39.0)"] +datazone = ["mypy-boto3-datazone (>=1.38.0,<1.39.0)"] +dax = ["mypy-boto3-dax (>=1.38.0,<1.39.0)"] +deadline = ["mypy-boto3-deadline (>=1.38.0,<1.39.0)"] +detective = ["mypy-boto3-detective (>=1.38.0,<1.39.0)"] +devicefarm = ["mypy-boto3-devicefarm (>=1.38.0,<1.39.0)"] +devops-guru = ["mypy-boto3-devops-guru (>=1.38.0,<1.39.0)"] +directconnect = ["mypy-boto3-directconnect (>=1.38.0,<1.39.0)"] +discovery = ["mypy-boto3-discovery (>=1.38.0,<1.39.0)"] +dlm = ["mypy-boto3-dlm (>=1.38.0,<1.39.0)"] +dms = ["mypy-boto3-dms (>=1.38.0,<1.39.0)"] +docdb = ["mypy-boto3-docdb (>=1.38.0,<1.39.0)"] +docdb-elastic = ["mypy-boto3-docdb-elastic (>=1.38.0,<1.39.0)"] +drs = ["mypy-boto3-drs (>=1.38.0,<1.39.0)"] +ds = ["mypy-boto3-ds (>=1.38.0,<1.39.0)"] +ds-data = ["mypy-boto3-ds-data (>=1.38.0,<1.39.0)"] +dsql = ["mypy-boto3-dsql (>=1.38.0,<1.39.0)"] +dynamodb = ["mypy-boto3-dynamodb (>=1.38.0,<1.39.0)"] +dynamodbstreams = ["mypy-boto3-dynamodbstreams (>=1.38.0,<1.39.0)"] +ebs = ["mypy-boto3-ebs (>=1.38.0,<1.39.0)"] +ec2 = ["mypy-boto3-ec2 (>=1.38.0,<1.39.0)"] +ec2-instance-connect = ["mypy-boto3-ec2-instance-connect (>=1.38.0,<1.39.0)"] +ecr = ["mypy-boto3-ecr (>=1.38.0,<1.39.0)"] +ecr-public = ["mypy-boto3-ecr-public (>=1.38.0,<1.39.0)"] +ecs = ["mypy-boto3-ecs (>=1.38.0,<1.39.0)"] +efs = ["mypy-boto3-efs (>=1.38.0,<1.39.0)"] +eks = ["mypy-boto3-eks (>=1.38.0,<1.39.0)"] +eks-auth = ["mypy-boto3-eks-auth (>=1.38.0,<1.39.0)"] +elasticache = ["mypy-boto3-elasticache (>=1.38.0,<1.39.0)"] +elasticbeanstalk = ["mypy-boto3-elasticbeanstalk (>=1.38.0,<1.39.0)"] +elastictranscoder = ["mypy-boto3-elastictranscoder (>=1.38.0,<1.39.0)"] +elb = ["mypy-boto3-elb (>=1.38.0,<1.39.0)"] +elbv2 = ["mypy-boto3-elbv2 (>=1.38.0,<1.39.0)"] +emr = ["mypy-boto3-emr (>=1.38.0,<1.39.0)"] +emr-containers = ["mypy-boto3-emr-containers (>=1.38.0,<1.39.0)"] +emr-serverless = ["mypy-boto3-emr-serverless (>=1.38.0,<1.39.0)"] +entityresolution = ["mypy-boto3-entityresolution (>=1.38.0,<1.39.0)"] +es = ["mypy-boto3-es (>=1.38.0,<1.39.0)"] +essential = ["mypy-boto3-cloudformation (>=1.38.0,<1.39.0)", "mypy-boto3-dynamodb (>=1.38.0,<1.39.0)", "mypy-boto3-ec2 (>=1.38.0,<1.39.0)", "mypy-boto3-lambda (>=1.38.0,<1.39.0)", "mypy-boto3-rds (>=1.38.0,<1.39.0)", "mypy-boto3-s3 (>=1.38.0,<1.39.0)", "mypy-boto3-sqs (>=1.38.0,<1.39.0)"] +events = ["mypy-boto3-events (>=1.38.0,<1.39.0)"] +evidently = ["mypy-boto3-evidently (>=1.38.0,<1.39.0)"] +finspace = ["mypy-boto3-finspace (>=1.38.0,<1.39.0)"] +finspace-data = ["mypy-boto3-finspace-data (>=1.38.0,<1.39.0)"] +firehose = ["mypy-boto3-firehose (>=1.38.0,<1.39.0)"] +fis = ["mypy-boto3-fis (>=1.38.0,<1.39.0)"] +fms = ["mypy-boto3-fms (>=1.38.0,<1.39.0)"] +forecast = ["mypy-boto3-forecast (>=1.38.0,<1.39.0)"] +forecastquery = ["mypy-boto3-forecastquery (>=1.38.0,<1.39.0)"] +frauddetector = ["mypy-boto3-frauddetector (>=1.38.0,<1.39.0)"] +freetier = ["mypy-boto3-freetier (>=1.38.0,<1.39.0)"] +fsx = ["mypy-boto3-fsx (>=1.38.0,<1.39.0)"] +full = ["boto3-stubs-full (>=1.38.0,<1.39.0)"] +gamelift = ["mypy-boto3-gamelift (>=1.38.0,<1.39.0)"] +gameliftstreams = ["mypy-boto3-gameliftstreams (>=1.38.0,<1.39.0)"] +geo-maps = ["mypy-boto3-geo-maps (>=1.38.0,<1.39.0)"] +geo-places = ["mypy-boto3-geo-places (>=1.38.0,<1.39.0)"] +geo-routes = ["mypy-boto3-geo-routes (>=1.38.0,<1.39.0)"] +glacier = ["mypy-boto3-glacier (>=1.38.0,<1.39.0)"] +globalaccelerator = ["mypy-boto3-globalaccelerator (>=1.38.0,<1.39.0)"] +glue = ["mypy-boto3-glue (>=1.38.0,<1.39.0)"] +grafana = ["mypy-boto3-grafana (>=1.38.0,<1.39.0)"] +greengrass = ["mypy-boto3-greengrass (>=1.38.0,<1.39.0)"] +greengrassv2 = ["mypy-boto3-greengrassv2 (>=1.38.0,<1.39.0)"] +groundstation = ["mypy-boto3-groundstation (>=1.38.0,<1.39.0)"] +guardduty = ["mypy-boto3-guardduty (>=1.38.0,<1.39.0)"] +health = ["mypy-boto3-health (>=1.38.0,<1.39.0)"] +healthlake = ["mypy-boto3-healthlake (>=1.38.0,<1.39.0)"] +iam = ["mypy-boto3-iam (>=1.38.0,<1.39.0)"] +identitystore = ["mypy-boto3-identitystore (>=1.38.0,<1.39.0)"] +imagebuilder = ["mypy-boto3-imagebuilder (>=1.38.0,<1.39.0)"] +importexport = ["mypy-boto3-importexport (>=1.38.0,<1.39.0)"] +inspector = ["mypy-boto3-inspector (>=1.38.0,<1.39.0)"] +inspector-scan = ["mypy-boto3-inspector-scan (>=1.38.0,<1.39.0)"] +inspector2 = ["mypy-boto3-inspector2 (>=1.38.0,<1.39.0)"] +internetmonitor = ["mypy-boto3-internetmonitor (>=1.38.0,<1.39.0)"] +invoicing = ["mypy-boto3-invoicing (>=1.38.0,<1.39.0)"] +iot = ["mypy-boto3-iot (>=1.38.0,<1.39.0)"] +iot-data = ["mypy-boto3-iot-data (>=1.38.0,<1.39.0)"] +iot-jobs-data = ["mypy-boto3-iot-jobs-data (>=1.38.0,<1.39.0)"] +iot-managed-integrations = ["mypy-boto3-iot-managed-integrations (>=1.38.0,<1.39.0)"] +iotanalytics = ["mypy-boto3-iotanalytics (>=1.38.0,<1.39.0)"] +iotdeviceadvisor = ["mypy-boto3-iotdeviceadvisor (>=1.38.0,<1.39.0)"] +iotevents = ["mypy-boto3-iotevents (>=1.38.0,<1.39.0)"] +iotevents-data = ["mypy-boto3-iotevents-data (>=1.38.0,<1.39.0)"] +iotfleethub = ["mypy-boto3-iotfleethub (>=1.38.0,<1.39.0)"] +iotfleetwise = ["mypy-boto3-iotfleetwise (>=1.38.0,<1.39.0)"] +iotsecuretunneling = ["mypy-boto3-iotsecuretunneling (>=1.38.0,<1.39.0)"] +iotsitewise = ["mypy-boto3-iotsitewise (>=1.38.0,<1.39.0)"] +iotthingsgraph = ["mypy-boto3-iotthingsgraph (>=1.38.0,<1.39.0)"] +iottwinmaker = ["mypy-boto3-iottwinmaker (>=1.38.0,<1.39.0)"] +iotwireless = ["mypy-boto3-iotwireless (>=1.38.0,<1.39.0)"] +ivs = ["mypy-boto3-ivs (>=1.38.0,<1.39.0)"] +ivs-realtime = ["mypy-boto3-ivs-realtime (>=1.38.0,<1.39.0)"] +ivschat = ["mypy-boto3-ivschat (>=1.38.0,<1.39.0)"] +kafka = ["mypy-boto3-kafka (>=1.38.0,<1.39.0)"] +kafkaconnect = ["mypy-boto3-kafkaconnect (>=1.38.0,<1.39.0)"] +kendra = ["mypy-boto3-kendra (>=1.38.0,<1.39.0)"] +kendra-ranking = ["mypy-boto3-kendra-ranking (>=1.38.0,<1.39.0)"] +keyspaces = ["mypy-boto3-keyspaces (>=1.38.0,<1.39.0)"] +kinesis = ["mypy-boto3-kinesis (>=1.38.0,<1.39.0)"] +kinesis-video-archived-media = ["mypy-boto3-kinesis-video-archived-media (>=1.38.0,<1.39.0)"] +kinesis-video-media = ["mypy-boto3-kinesis-video-media (>=1.38.0,<1.39.0)"] +kinesis-video-signaling = ["mypy-boto3-kinesis-video-signaling (>=1.38.0,<1.39.0)"] +kinesis-video-webrtc-storage = ["mypy-boto3-kinesis-video-webrtc-storage (>=1.38.0,<1.39.0)"] +kinesisanalytics = ["mypy-boto3-kinesisanalytics (>=1.38.0,<1.39.0)"] +kinesisanalyticsv2 = ["mypy-boto3-kinesisanalyticsv2 (>=1.38.0,<1.39.0)"] +kinesisvideo = ["mypy-boto3-kinesisvideo (>=1.38.0,<1.39.0)"] +kms = ["mypy-boto3-kms (>=1.38.0,<1.39.0)"] +lakeformation = ["mypy-boto3-lakeformation (>=1.38.0,<1.39.0)"] +lambda = ["mypy-boto3-lambda (>=1.38.0,<1.39.0)"] +launch-wizard = ["mypy-boto3-launch-wizard (>=1.38.0,<1.39.0)"] +lex-models = ["mypy-boto3-lex-models (>=1.38.0,<1.39.0)"] +lex-runtime = ["mypy-boto3-lex-runtime (>=1.38.0,<1.39.0)"] +lexv2-models = ["mypy-boto3-lexv2-models (>=1.38.0,<1.39.0)"] +lexv2-runtime = ["mypy-boto3-lexv2-runtime (>=1.38.0,<1.39.0)"] +license-manager = ["mypy-boto3-license-manager (>=1.38.0,<1.39.0)"] +license-manager-linux-subscriptions = ["mypy-boto3-license-manager-linux-subscriptions (>=1.38.0,<1.39.0)"] +license-manager-user-subscriptions = ["mypy-boto3-license-manager-user-subscriptions (>=1.38.0,<1.39.0)"] +lightsail = ["mypy-boto3-lightsail (>=1.38.0,<1.39.0)"] +location = ["mypy-boto3-location (>=1.38.0,<1.39.0)"] +logs = ["mypy-boto3-logs (>=1.38.0,<1.39.0)"] +lookoutequipment = ["mypy-boto3-lookoutequipment (>=1.38.0,<1.39.0)"] +lookoutmetrics = ["mypy-boto3-lookoutmetrics (>=1.38.0,<1.39.0)"] +lookoutvision = ["mypy-boto3-lookoutvision (>=1.38.0,<1.39.0)"] +m2 = ["mypy-boto3-m2 (>=1.38.0,<1.39.0)"] +machinelearning = ["mypy-boto3-machinelearning (>=1.38.0,<1.39.0)"] +macie2 = ["mypy-boto3-macie2 (>=1.38.0,<1.39.0)"] +mailmanager = ["mypy-boto3-mailmanager (>=1.38.0,<1.39.0)"] +managedblockchain = ["mypy-boto3-managedblockchain (>=1.38.0,<1.39.0)"] +managedblockchain-query = ["mypy-boto3-managedblockchain-query (>=1.38.0,<1.39.0)"] +marketplace-agreement = ["mypy-boto3-marketplace-agreement (>=1.38.0,<1.39.0)"] +marketplace-catalog = ["mypy-boto3-marketplace-catalog (>=1.38.0,<1.39.0)"] +marketplace-deployment = ["mypy-boto3-marketplace-deployment (>=1.38.0,<1.39.0)"] +marketplace-entitlement = ["mypy-boto3-marketplace-entitlement (>=1.38.0,<1.39.0)"] +marketplace-reporting = ["mypy-boto3-marketplace-reporting (>=1.38.0,<1.39.0)"] +marketplacecommerceanalytics = ["mypy-boto3-marketplacecommerceanalytics (>=1.38.0,<1.39.0)"] +mediaconnect = ["mypy-boto3-mediaconnect (>=1.38.0,<1.39.0)"] +mediaconvert = ["mypy-boto3-mediaconvert (>=1.38.0,<1.39.0)"] +medialive = ["mypy-boto3-medialive (>=1.38.0,<1.39.0)"] +mediapackage = ["mypy-boto3-mediapackage (>=1.38.0,<1.39.0)"] +mediapackage-vod = ["mypy-boto3-mediapackage-vod (>=1.38.0,<1.39.0)"] +mediapackagev2 = ["mypy-boto3-mediapackagev2 (>=1.38.0,<1.39.0)"] +mediastore = ["mypy-boto3-mediastore (>=1.38.0,<1.39.0)"] +mediastore-data = ["mypy-boto3-mediastore-data (>=1.38.0,<1.39.0)"] +mediatailor = ["mypy-boto3-mediatailor (>=1.38.0,<1.39.0)"] +medical-imaging = ["mypy-boto3-medical-imaging (>=1.38.0,<1.39.0)"] +memorydb = ["mypy-boto3-memorydb (>=1.38.0,<1.39.0)"] +meteringmarketplace = ["mypy-boto3-meteringmarketplace (>=1.38.0,<1.39.0)"] +mgh = ["mypy-boto3-mgh (>=1.38.0,<1.39.0)"] +mgn = ["mypy-boto3-mgn (>=1.38.0,<1.39.0)"] +migration-hub-refactor-spaces = ["mypy-boto3-migration-hub-refactor-spaces (>=1.38.0,<1.39.0)"] +migrationhub-config = ["mypy-boto3-migrationhub-config (>=1.38.0,<1.39.0)"] +migrationhuborchestrator = ["mypy-boto3-migrationhuborchestrator (>=1.38.0,<1.39.0)"] +migrationhubstrategy = ["mypy-boto3-migrationhubstrategy (>=1.38.0,<1.39.0)"] +mq = ["mypy-boto3-mq (>=1.38.0,<1.39.0)"] +mturk = ["mypy-boto3-mturk (>=1.38.0,<1.39.0)"] +mwaa = ["mypy-boto3-mwaa (>=1.38.0,<1.39.0)"] +neptune = ["mypy-boto3-neptune (>=1.38.0,<1.39.0)"] +neptune-graph = ["mypy-boto3-neptune-graph (>=1.38.0,<1.39.0)"] +neptunedata = ["mypy-boto3-neptunedata (>=1.38.0,<1.39.0)"] +network-firewall = ["mypy-boto3-network-firewall (>=1.38.0,<1.39.0)"] +networkflowmonitor = ["mypy-boto3-networkflowmonitor (>=1.38.0,<1.39.0)"] +networkmanager = ["mypy-boto3-networkmanager (>=1.38.0,<1.39.0)"] +networkmonitor = ["mypy-boto3-networkmonitor (>=1.38.0,<1.39.0)"] +notifications = ["mypy-boto3-notifications (>=1.38.0,<1.39.0)"] +notificationscontacts = ["mypy-boto3-notificationscontacts (>=1.38.0,<1.39.0)"] +oam = ["mypy-boto3-oam (>=1.38.0,<1.39.0)"] +observabilityadmin = ["mypy-boto3-observabilityadmin (>=1.38.0,<1.39.0)"] +omics = ["mypy-boto3-omics (>=1.38.0,<1.39.0)"] +opensearch = ["mypy-boto3-opensearch (>=1.38.0,<1.39.0)"] +opensearchserverless = ["mypy-boto3-opensearchserverless (>=1.38.0,<1.39.0)"] +opsworks = ["mypy-boto3-opsworks (>=1.38.0,<1.39.0)"] +opsworkscm = ["mypy-boto3-opsworkscm (>=1.38.0,<1.39.0)"] +organizations = ["mypy-boto3-organizations (>=1.38.0,<1.39.0)"] +osis = ["mypy-boto3-osis (>=1.38.0,<1.39.0)"] +outposts = ["mypy-boto3-outposts (>=1.38.0,<1.39.0)"] +panorama = ["mypy-boto3-panorama (>=1.38.0,<1.39.0)"] +partnercentral-selling = ["mypy-boto3-partnercentral-selling (>=1.38.0,<1.39.0)"] +payment-cryptography = ["mypy-boto3-payment-cryptography (>=1.38.0,<1.39.0)"] +payment-cryptography-data = ["mypy-boto3-payment-cryptography-data (>=1.38.0,<1.39.0)"] +pca-connector-ad = ["mypy-boto3-pca-connector-ad (>=1.38.0,<1.39.0)"] +pca-connector-scep = ["mypy-boto3-pca-connector-scep (>=1.38.0,<1.39.0)"] +pcs = ["mypy-boto3-pcs (>=1.38.0,<1.39.0)"] +personalize = ["mypy-boto3-personalize (>=1.38.0,<1.39.0)"] +personalize-events = ["mypy-boto3-personalize-events (>=1.38.0,<1.39.0)"] +personalize-runtime = ["mypy-boto3-personalize-runtime (>=1.38.0,<1.39.0)"] +pi = ["mypy-boto3-pi (>=1.38.0,<1.39.0)"] +pinpoint = ["mypy-boto3-pinpoint (>=1.38.0,<1.39.0)"] +pinpoint-email = ["mypy-boto3-pinpoint-email (>=1.38.0,<1.39.0)"] +pinpoint-sms-voice = ["mypy-boto3-pinpoint-sms-voice (>=1.38.0,<1.39.0)"] +pinpoint-sms-voice-v2 = ["mypy-boto3-pinpoint-sms-voice-v2 (>=1.38.0,<1.39.0)"] +pipes = ["mypy-boto3-pipes (>=1.38.0,<1.39.0)"] +polly = ["mypy-boto3-polly (>=1.38.0,<1.39.0)"] +pricing = ["mypy-boto3-pricing (>=1.38.0,<1.39.0)"] +privatenetworks = ["mypy-boto3-privatenetworks (>=1.38.0,<1.39.0)"] +proton = ["mypy-boto3-proton (>=1.38.0,<1.39.0)"] +qapps = ["mypy-boto3-qapps (>=1.38.0,<1.39.0)"] +qbusiness = ["mypy-boto3-qbusiness (>=1.38.0,<1.39.0)"] +qconnect = ["mypy-boto3-qconnect (>=1.38.0,<1.39.0)"] +qldb = ["mypy-boto3-qldb (>=1.38.0,<1.39.0)"] +qldb-session = ["mypy-boto3-qldb-session (>=1.38.0,<1.39.0)"] +quicksight = ["mypy-boto3-quicksight (>=1.38.0,<1.39.0)"] +ram = ["mypy-boto3-ram (>=1.38.0,<1.39.0)"] +rbin = ["mypy-boto3-rbin (>=1.38.0,<1.39.0)"] +rds = ["mypy-boto3-rds (>=1.38.0,<1.39.0)"] +rds-data = ["mypy-boto3-rds-data (>=1.38.0,<1.39.0)"] +redshift = ["mypy-boto3-redshift (>=1.38.0,<1.39.0)"] +redshift-data = ["mypy-boto3-redshift-data (>=1.38.0,<1.39.0)"] +redshift-serverless = ["mypy-boto3-redshift-serverless (>=1.38.0,<1.39.0)"] +rekognition = ["mypy-boto3-rekognition (>=1.38.0,<1.39.0)"] +repostspace = ["mypy-boto3-repostspace (>=1.38.0,<1.39.0)"] +resiliencehub = ["mypy-boto3-resiliencehub (>=1.38.0,<1.39.0)"] +resource-explorer-2 = ["mypy-boto3-resource-explorer-2 (>=1.38.0,<1.39.0)"] +resource-groups = ["mypy-boto3-resource-groups (>=1.38.0,<1.39.0)"] +resourcegroupstaggingapi = ["mypy-boto3-resourcegroupstaggingapi (>=1.38.0,<1.39.0)"] +robomaker = ["mypy-boto3-robomaker (>=1.38.0,<1.39.0)"] +rolesanywhere = ["mypy-boto3-rolesanywhere (>=1.38.0,<1.39.0)"] +route53 = ["mypy-boto3-route53 (>=1.38.0,<1.39.0)"] +route53-recovery-cluster = ["mypy-boto3-route53-recovery-cluster (>=1.38.0,<1.39.0)"] +route53-recovery-control-config = ["mypy-boto3-route53-recovery-control-config (>=1.38.0,<1.39.0)"] +route53-recovery-readiness = ["mypy-boto3-route53-recovery-readiness (>=1.38.0,<1.39.0)"] +route53domains = ["mypy-boto3-route53domains (>=1.38.0,<1.39.0)"] +route53profiles = ["mypy-boto3-route53profiles (>=1.38.0,<1.39.0)"] +route53resolver = ["mypy-boto3-route53resolver (>=1.38.0,<1.39.0)"] +rum = ["mypy-boto3-rum (>=1.38.0,<1.39.0)"] +s3 = ["mypy-boto3-s3 (>=1.38.0,<1.39.0)"] +s3control = ["mypy-boto3-s3control (>=1.38.0,<1.39.0)"] +s3outposts = ["mypy-boto3-s3outposts (>=1.38.0,<1.39.0)"] +s3tables = ["mypy-boto3-s3tables (>=1.38.0,<1.39.0)"] +sagemaker = ["mypy-boto3-sagemaker (>=1.38.0,<1.39.0)"] +sagemaker-a2i-runtime = ["mypy-boto3-sagemaker-a2i-runtime (>=1.38.0,<1.39.0)"] +sagemaker-edge = ["mypy-boto3-sagemaker-edge (>=1.38.0,<1.39.0)"] +sagemaker-featurestore-runtime = ["mypy-boto3-sagemaker-featurestore-runtime (>=1.38.0,<1.39.0)"] +sagemaker-geospatial = ["mypy-boto3-sagemaker-geospatial (>=1.38.0,<1.39.0)"] +sagemaker-metrics = ["mypy-boto3-sagemaker-metrics (>=1.38.0,<1.39.0)"] +sagemaker-runtime = ["mypy-boto3-sagemaker-runtime (>=1.38.0,<1.39.0)"] +savingsplans = ["mypy-boto3-savingsplans (>=1.38.0,<1.39.0)"] +scheduler = ["mypy-boto3-scheduler (>=1.38.0,<1.39.0)"] +schemas = ["mypy-boto3-schemas (>=1.38.0,<1.39.0)"] +sdb = ["mypy-boto3-sdb (>=1.38.0,<1.39.0)"] +secretsmanager = ["mypy-boto3-secretsmanager (>=1.38.0,<1.39.0)"] +security-ir = ["mypy-boto3-security-ir (>=1.38.0,<1.39.0)"] +securityhub = ["mypy-boto3-securityhub (>=1.38.0,<1.39.0)"] +securitylake = ["mypy-boto3-securitylake (>=1.38.0,<1.39.0)"] +serverlessrepo = ["mypy-boto3-serverlessrepo (>=1.38.0,<1.39.0)"] +service-quotas = ["mypy-boto3-service-quotas (>=1.38.0,<1.39.0)"] +servicecatalog = ["mypy-boto3-servicecatalog (>=1.38.0,<1.39.0)"] +servicecatalog-appregistry = ["mypy-boto3-servicecatalog-appregistry (>=1.38.0,<1.39.0)"] +servicediscovery = ["mypy-boto3-servicediscovery (>=1.38.0,<1.39.0)"] +ses = ["mypy-boto3-ses (>=1.38.0,<1.39.0)"] +sesv2 = ["mypy-boto3-sesv2 (>=1.38.0,<1.39.0)"] +shield = ["mypy-boto3-shield (>=1.38.0,<1.39.0)"] +signer = ["mypy-boto3-signer (>=1.38.0,<1.39.0)"] +simspaceweaver = ["mypy-boto3-simspaceweaver (>=1.38.0,<1.39.0)"] +sms = ["mypy-boto3-sms (>=1.38.0,<1.39.0)"] +sms-voice = ["mypy-boto3-sms-voice (>=1.38.0,<1.39.0)"] +snow-device-management = ["mypy-boto3-snow-device-management (>=1.38.0,<1.39.0)"] +snowball = ["mypy-boto3-snowball (>=1.38.0,<1.39.0)"] +sns = ["mypy-boto3-sns (>=1.38.0,<1.39.0)"] +socialmessaging = ["mypy-boto3-socialmessaging (>=1.38.0,<1.39.0)"] +sqs = ["mypy-boto3-sqs (>=1.38.0,<1.39.0)"] +ssm = ["mypy-boto3-ssm (>=1.38.0,<1.39.0)"] +ssm-contacts = ["mypy-boto3-ssm-contacts (>=1.38.0,<1.39.0)"] +ssm-incidents = ["mypy-boto3-ssm-incidents (>=1.38.0,<1.39.0)"] +ssm-quicksetup = ["mypy-boto3-ssm-quicksetup (>=1.38.0,<1.39.0)"] +ssm-sap = ["mypy-boto3-ssm-sap (>=1.38.0,<1.39.0)"] +sso = ["mypy-boto3-sso (>=1.38.0,<1.39.0)"] +sso-admin = ["mypy-boto3-sso-admin (>=1.38.0,<1.39.0)"] +sso-oidc = ["mypy-boto3-sso-oidc (>=1.38.0,<1.39.0)"] +stepfunctions = ["mypy-boto3-stepfunctions (>=1.38.0,<1.39.0)"] +storagegateway = ["mypy-boto3-storagegateway (>=1.38.0,<1.39.0)"] +sts = ["mypy-boto3-sts (>=1.38.0,<1.39.0)"] +supplychain = ["mypy-boto3-supplychain (>=1.38.0,<1.39.0)"] +support = ["mypy-boto3-support (>=1.38.0,<1.39.0)"] +support-app = ["mypy-boto3-support-app (>=1.38.0,<1.39.0)"] +swf = ["mypy-boto3-swf (>=1.38.0,<1.39.0)"] +synthetics = ["mypy-boto3-synthetics (>=1.38.0,<1.39.0)"] +taxsettings = ["mypy-boto3-taxsettings (>=1.38.0,<1.39.0)"] +textract = ["mypy-boto3-textract (>=1.38.0,<1.39.0)"] +timestream-influxdb = ["mypy-boto3-timestream-influxdb (>=1.38.0,<1.39.0)"] +timestream-query = ["mypy-boto3-timestream-query (>=1.38.0,<1.39.0)"] +timestream-write = ["mypy-boto3-timestream-write (>=1.38.0,<1.39.0)"] +tnb = ["mypy-boto3-tnb (>=1.38.0,<1.39.0)"] +transcribe = ["mypy-boto3-transcribe (>=1.38.0,<1.39.0)"] +transfer = ["mypy-boto3-transfer (>=1.38.0,<1.39.0)"] +translate = ["mypy-boto3-translate (>=1.38.0,<1.39.0)"] +trustedadvisor = ["mypy-boto3-trustedadvisor (>=1.38.0,<1.39.0)"] +verifiedpermissions = ["mypy-boto3-verifiedpermissions (>=1.38.0,<1.39.0)"] +voice-id = ["mypy-boto3-voice-id (>=1.38.0,<1.39.0)"] +vpc-lattice = ["mypy-boto3-vpc-lattice (>=1.38.0,<1.39.0)"] +waf = ["mypy-boto3-waf (>=1.38.0,<1.39.0)"] +waf-regional = ["mypy-boto3-waf-regional (>=1.38.0,<1.39.0)"] +wafv2 = ["mypy-boto3-wafv2 (>=1.38.0,<1.39.0)"] +wellarchitected = ["mypy-boto3-wellarchitected (>=1.38.0,<1.39.0)"] +wisdom = ["mypy-boto3-wisdom (>=1.38.0,<1.39.0)"] +workdocs = ["mypy-boto3-workdocs (>=1.38.0,<1.39.0)"] +workmail = ["mypy-boto3-workmail (>=1.38.0,<1.39.0)"] +workmailmessageflow = ["mypy-boto3-workmailmessageflow (>=1.38.0,<1.39.0)"] +workspaces = ["mypy-boto3-workspaces (>=1.38.0,<1.39.0)"] +workspaces-thin-client = ["mypy-boto3-workspaces-thin-client (>=1.38.0,<1.39.0)"] +workspaces-web = ["mypy-boto3-workspaces-web (>=1.38.0,<1.39.0)"] +xray = ["mypy-boto3-xray (>=1.38.0,<1.39.0)"] + +[[package]] +name = "botocore" +version = "1.38.1" +description = "Low-level, data-driven core of boto 3." +optional = false +python-versions = ">=3.9" +groups = ["main", "dev"] +files = [ + {file = "botocore-1.38.1-py3-none-any.whl", hash = "sha256:b1673975e3c42d0e2d1804f9f73e88961e95eac371c8f8c0a0d7e661ec3c90c3"}, + {file = "botocore-1.38.1.tar.gz", hash = "sha256:c2eb42eeaa502f236ba894a65ea7f7241711150cc450b9d59fbbad41e741adc0"}, +] + +[package.dependencies] +jmespath = ">=0.7.1,<2.0.0" +python-dateutil = ">=2.1,<3.0.0" +urllib3 = {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""} + +[package.extras] +crt = ["awscrt (==0.23.8)"] + +[[package]] +name = "botocore-stubs" +version = "1.38.1" +description = "Type annotations and code completion for botocore" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "botocore_stubs-1.38.1-py3-none-any.whl", hash = "sha256:9a5bc74d0cd21f3a3a025d97780d740cba2618b4ce7bb37b9cc93064039490a5"}, + {file = "botocore_stubs-1.38.1.tar.gz", hash = "sha256:e2fc463b5981eb66b6a809b08682383df10dfaeb703ccbbde1f10424659ac219"}, +] + +[package.dependencies] +types-awscrt = "*" + +[package.extras] +botocore = ["botocore"] + [[package]] name = "certifi" version = "2025.1.31" @@ -161,18 +811,33 @@ files = [ {file = "charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3"}, ] +[[package]] +name = "click" +version = "8.1.8" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, + {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + [[package]] name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" -groups = ["dev"] -markers = "sys_platform == \"win32\"" +groups = ["main", "dev"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +markers = {main = "sys_platform == \"win32\" or platform_system == \"Windows\""} [[package]] name = "coolname" @@ -186,6 +851,62 @@ files = [ {file = "coolname-2.2.0.tar.gz", hash = "sha256:6c5d5731759104479e7ca195a9b64f7900ac5bead40183c09323c7d0be9e75c7"}, ] +[[package]] +name = "datasets" +version = "3.5.0" +description = "HuggingFace community-driven open-source library of datasets" +optional = false +python-versions = ">=3.9.0" +groups = ["main"] +files = [ + {file = "datasets-3.5.0-py3-none-any.whl", hash = "sha256:b3b7f163acc6ac4e01a1b00eef26d48bd4039288ceea3601d169272bd5581006"}, + {file = "datasets-3.5.0.tar.gz", hash = "sha256:9e39560e34f83a64e48ceca7adeb645ede3c3055c5cf48ed2b454f8ed2b89754"}, +] + +[package.dependencies] +aiohttp = "*" +dill = ">=0.3.0,<0.3.9" +filelock = "*" +fsspec = {version = ">=2023.1.0,<=2024.12.0", extras = ["http"]} +huggingface-hub = ">=0.24.0" +multiprocess = "<0.70.17" +numpy = ">=1.17" +packaging = "*" +pandas = "*" +pyarrow = ">=15.0.0" +pyyaml = ">=5.1" +requests = ">=2.32.2" +tqdm = ">=4.66.3" +xxhash = "*" + +[package.extras] +audio = ["librosa", "soundfile (>=0.12.1)", "soxr (>=0.4.0) ; python_version >= \"3.9\""] +benchmarks = ["tensorflow (==2.12.0)", "torch (==2.0.1)", "transformers (==4.30.1)"] +dev = ["Pillow (>=9.4.0)", "absl-py", "decorator", "elasticsearch (>=7.17.12,<8.0.0)", "faiss-cpu (>=1.8.0.post1)", "jax (>=0.3.14) ; sys_platform != \"win32\"", "jaxlib (>=0.3.14) ; sys_platform != \"win32\"", "joblib (<1.3.0)", "joblibspark", "librosa", "lz4", "moto[server]", "polars[timezone] (>=0.20.0)", "protobuf (<4.0.0)", "py7zr", "pyav", "pyspark (>=3.4)", "pytest", "pytest-datadir", "pytest-xdist", "rarfile (>=4.0)", "ruff (>=0.3.0)", "s3fs", "s3fs (>=2021.11.1)", "soundfile (>=0.12.1)", "soundfile (>=0.12.1)", "soxr (>=0.4.0) ; python_version >= \"3.9\"", "sqlalchemy", "tensorflow (>=2.16.0) ; python_version >= \"3.10\"", "tensorflow (>=2.6.0)", "tensorflow (>=2.6.0) ; python_version < \"3.10\"", "tiktoken", "torch", "torch (>=2.0.0)", "torchdata", "torchvision", "transformers", "transformers (>=4.42.0)", "zstandard"] +docs = ["s3fs", "tensorflow (>=2.6.0)", "torch", "transformers"] +jax = ["jax (>=0.3.14)", "jaxlib (>=0.3.14)"] +pdfs = ["pdfplumber (>=0.11.4)"] +quality = ["ruff (>=0.3.0)"] +s3 = ["s3fs"] +tensorflow = ["tensorflow (>=2.6.0)"] +tensorflow-gpu = ["tensorflow (>=2.6.0)"] +tests = ["Pillow (>=9.4.0)", "absl-py", "decorator", "elasticsearch (>=7.17.12,<8.0.0)", "faiss-cpu (>=1.8.0.post1)", "jax (>=0.3.14) ; sys_platform != \"win32\"", "jaxlib (>=0.3.14) ; sys_platform != \"win32\"", "joblib (<1.3.0)", "joblibspark", "librosa", "lz4", "moto[server]", "polars[timezone] (>=0.20.0)", "protobuf (<4.0.0)", "py7zr", "pyav", "pyspark (>=3.4)", "pytest", "pytest-datadir", "pytest-xdist", "rarfile (>=4.0)", "s3fs (>=2021.11.1)", "soundfile (>=0.12.1)", "soundfile (>=0.12.1)", "soxr (>=0.4.0) ; python_version >= \"3.9\"", "sqlalchemy", "tensorflow (>=2.16.0) ; python_version >= \"3.10\"", "tensorflow (>=2.6.0) ; python_version < \"3.10\"", "tiktoken", "torch (>=2.0.0)", "torchdata", "torchvision", "transformers (>=4.42.0)", "zstandard"] +tests-numpy2 = ["Pillow (>=9.4.0)", "absl-py", "decorator", "elasticsearch (>=7.17.12,<8.0.0)", "jax (>=0.3.14) ; sys_platform != \"win32\"", "jaxlib (>=0.3.14) ; sys_platform != \"win32\"", "joblib (<1.3.0)", "joblibspark", "lz4", "moto[server]", "polars[timezone] (>=0.20.0)", "protobuf (<4.0.0)", "py7zr", "pyav", "pyspark (>=3.4)", "pytest", "pytest-datadir", "pytest-xdist", "rarfile (>=4.0)", "s3fs (>=2021.11.1)", "soundfile (>=0.12.1)", "soundfile (>=0.12.1)", "soxr (>=0.4.0) ; python_version >= \"3.9\"", "sqlalchemy", "tiktoken", "torch (>=2.0.0)", "torchdata", "torchvision", "transformers (>=4.42.0)", "zstandard"] +torch = ["torch"] +vision = ["Pillow (>=9.4.0)"] + +[[package]] +name = "decorator" +version = "5.2.1" +description = "Decorators for Humans" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a"}, + {file = "decorator-5.2.1.tar.gz", hash = "sha256:65f266143752f734b0a7cc83c46f4618af75b8c5911b00ccb61d0ac9b6da0360"}, +] + [[package]] name = "deprecated" version = "1.2.18" @@ -205,24 +926,20 @@ wrapt = ">=1.10,<2" dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "setuptools ; python_version >= \"3.12\"", "tox"] [[package]] -name = "detect-secrets" -version = "1.5.0" -description = "Tool for detecting secrets in the codebase" +name = "dill" +version = "0.3.8" +description = "serialize all of Python" optional = false -python-versions = "*" -groups = ["dev"] +python-versions = ">=3.8" +groups = ["main"] files = [ - {file = "detect_secrets-1.5.0-py3-none-any.whl", hash = "sha256:e24e7b9b5a35048c313e983f76c4bd09dad89f045ff059e354f9943bf45aa060"}, - {file = "detect_secrets-1.5.0.tar.gz", hash = "sha256:6bb46dcc553c10df51475641bb30fd69d25645cc12339e46c824c1e0c388898a"}, + {file = "dill-0.3.8-py3-none-any.whl", hash = "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7"}, + {file = "dill-0.3.8.tar.gz", hash = "sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca"}, ] -[package.dependencies] -pyyaml = "*" -requests = "*" - [package.extras] -gibberish = ["gibberish-detector"] -word-list = ["pyahocorasick"] +graph = ["objgraph (>=1.7.2)"] +profile = ["gprof2dot (>=2022.7.29)"] [[package]] name = "distlib" @@ -236,6 +953,78 @@ files = [ {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, ] +[[package]] +name = "distro" +version = "1.9.0" +description = "Distro - an OS platform information API" +optional = false +python-versions = ">=3.6" +groups = ["dev"] +files = [ + {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, + {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, +] + +[[package]] +name = "elastic-transport" +version = "8.17.1" +description = "Transport classes and utilities shared among Python Elastic client libraries" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "elastic_transport-8.17.1-py3-none-any.whl", hash = "sha256:192718f498f1d10c5e9aa8b9cf32aed405e469a7f0e9d6a8923431dbb2c59fb8"}, + {file = "elastic_transport-8.17.1.tar.gz", hash = "sha256:5edef32ac864dca8e2f0a613ef63491ee8d6b8cfb52881fa7313ba9290cac6d2"}, +] + +[package.dependencies] +certifi = "*" +urllib3 = ">=1.26.2,<3" + +[package.extras] +develop = ["aiohttp", "furo", "httpx", "opentelemetry-api", "opentelemetry-sdk", "orjson", "pytest", "pytest-asyncio", "pytest-cov", "pytest-httpserver", "pytest-mock", "requests", "respx", "sphinx (>2)", "sphinx-autodoc-typehints", "trustme"] + +[[package]] +name = "elasticsearch" +version = "8.18.0" +description = "Python client for Elasticsearch" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "elasticsearch-8.18.0-py3-none-any.whl", hash = "sha256:b26a6e67958b7bd04711a31f3400197aabf4a6f03873eadce1a0aeb446653886"}, + {file = "elasticsearch-8.18.0.tar.gz", hash = "sha256:4fb28cfe82d480c72fc049e659afe4cad450b318c210e43493ca402e900793eb"}, +] + +[package.dependencies] +elastic-transport = ">=8.15.1,<9" +python-dateutil = "*" +typing-extensions = "*" + +[package.extras] +async = ["aiohttp (>=3,<4)"] +dev = ["aiohttp", "black", "build", "coverage", "isort", "jinja2", "mapbox-vector-tile", "mypy", "nltk", "nox", "numpy", "orjson", "pandas", "pyarrow", "pyright", "pytest", "pytest-asyncio", "pytest-cov", "pytest-mock", "python-dateutil", "pyyaml (>=5.4)", "requests (>=2,<3)", "sentence-transformers", "simsimd", "tqdm", "twine", "types-python-dateutil", "types-tqdm", "unasync"] +docs = ["sphinx", "sphinx-autodoc-typehints", "sphinx-rtd-theme (>=2.0)"] +orjson = ["orjson (>=3)"] +pyarrow = ["pyarrow (>=1)"] +requests = ["requests (>=2.4.0,!=2.32.2,<3.0.0)"] +vectorstore-mmr = ["numpy (>=1)", "simsimd (>=3)"] + +[[package]] +name = "eval-type-backport" +version = "0.2.2" +description = "Like `typing._eval_type`, but lets older Python versions use newer typing features." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "eval_type_backport-0.2.2-py3-none-any.whl", hash = "sha256:cb6ad7c393517f476f96d456d0412ea80f0a8cf96f6892834cd9340149111b0a"}, + {file = "eval_type_backport-0.2.2.tar.gz", hash = "sha256:f0576b4cf01ebb5bd358d02314d31846af5e07678387486e2c798af0e7d849c1"}, +] + +[package.extras] +tests = ["pytest"] + [[package]] name = "exceptiongroup" version = "1.2.2" @@ -285,14 +1074,14 @@ pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<3.0.0" [[package]] name = "filelock" -version = "3.17.0" +version = "3.18.0" description = "A platform independent file lock." optional = false python-versions = ">=3.9" -groups = ["dev"] +groups = ["main", "dev"] files = [ - {file = "filelock-3.17.0-py3-none-any.whl", hash = "sha256:533dc2f7ba78dc2f0f531fc6c4940addf7b70a481e269a5a3b93be94ffbe8338"}, - {file = "filelock-3.17.0.tar.gz", hash = "sha256:ee4e77401ef576ebb38cd7f13b9b28893194acc20a8e68e18730ba9c0e54660e"}, + {file = "filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de"}, + {file = "filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2"}, ] [package.extras] @@ -300,16 +1089,174 @@ docs = ["furo (>=2024.8.6)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3) testing = ["covdefaults (>=2.3)", "coverage (>=7.6.10)", "diff-cover (>=9.2.1)", "pytest (>=8.3.4)", "pytest-asyncio (>=0.25.2)", "pytest-cov (>=6)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.28.1)"] typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""] +[[package]] +name = "frozenlist" +version = "1.6.0" +description = "A list-like structure which implements collections.abc.MutableSequence" +optional = false +python-versions = ">=3.9" +groups = ["main", "dev"] +files = [ + {file = "frozenlist-1.6.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e6e558ea1e47fd6fa8ac9ccdad403e5dd5ecc6ed8dda94343056fa4277d5c65e"}, + {file = "frozenlist-1.6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f4b3cd7334a4bbc0c472164f3744562cb72d05002cc6fcf58adb104630bbc352"}, + {file = "frozenlist-1.6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9799257237d0479736e2b4c01ff26b5c7f7694ac9692a426cb717f3dc02fff9b"}, + {file = "frozenlist-1.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a7bb0fe1f7a70fb5c6f497dc32619db7d2cdd53164af30ade2f34673f8b1fc"}, + {file = "frozenlist-1.6.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:36d2fc099229f1e4237f563b2a3e0ff7ccebc3999f729067ce4e64a97a7f2869"}, + {file = "frozenlist-1.6.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f27a9f9a86dcf00708be82359db8de86b80d029814e6693259befe82bb58a106"}, + {file = "frozenlist-1.6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75ecee69073312951244f11b8627e3700ec2bfe07ed24e3a685a5979f0412d24"}, + {file = "frozenlist-1.6.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2c7d5aa19714b1b01a0f515d078a629e445e667b9da869a3cd0e6fe7dec78bd"}, + {file = "frozenlist-1.6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69bbd454f0fb23b51cadc9bdba616c9678e4114b6f9fa372d462ff2ed9323ec8"}, + {file = "frozenlist-1.6.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7daa508e75613809c7a57136dec4871a21bca3080b3a8fc347c50b187df4f00c"}, + {file = "frozenlist-1.6.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:89ffdb799154fd4d7b85c56d5fa9d9ad48946619e0eb95755723fffa11022d75"}, + {file = "frozenlist-1.6.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:920b6bd77d209931e4c263223381d63f76828bec574440f29eb497cf3394c249"}, + {file = "frozenlist-1.6.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:d3ceb265249fb401702fce3792e6b44c1166b9319737d21495d3611028d95769"}, + {file = "frozenlist-1.6.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:52021b528f1571f98a7d4258c58aa8d4b1a96d4f01d00d51f1089f2e0323cb02"}, + {file = "frozenlist-1.6.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0f2ca7810b809ed0f1917293050163c7654cefc57a49f337d5cd9de717b8fad3"}, + {file = "frozenlist-1.6.0-cp310-cp310-win32.whl", hash = "sha256:0e6f8653acb82e15e5443dba415fb62a8732b68fe09936bb6d388c725b57f812"}, + {file = "frozenlist-1.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:f1a39819a5a3e84304cd286e3dc62a549fe60985415851b3337b6f5cc91907f1"}, + {file = "frozenlist-1.6.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ae8337990e7a45683548ffb2fee1af2f1ed08169284cd829cdd9a7fa7470530d"}, + {file = "frozenlist-1.6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8c952f69dd524558694818a461855f35d36cc7f5c0adddce37e962c85d06eac0"}, + {file = "frozenlist-1.6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8f5fef13136c4e2dee91bfb9a44e236fff78fc2cd9f838eddfc470c3d7d90afe"}, + {file = "frozenlist-1.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:716bbba09611b4663ecbb7cd022f640759af8259e12a6ca939c0a6acd49eedba"}, + {file = "frozenlist-1.6.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7b8c4dc422c1a3ffc550b465090e53b0bf4839047f3e436a34172ac67c45d595"}, + {file = "frozenlist-1.6.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b11534872256e1666116f6587a1592ef395a98b54476addb5e8d352925cb5d4a"}, + {file = "frozenlist-1.6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c6eceb88aaf7221f75be6ab498dc622a151f5f88d536661af3ffc486245a626"}, + {file = "frozenlist-1.6.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62c828a5b195570eb4b37369fcbbd58e96c905768d53a44d13044355647838ff"}, + {file = "frozenlist-1.6.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1c6bd2c6399920c9622362ce95a7d74e7f9af9bfec05fff91b8ce4b9647845a"}, + {file = "frozenlist-1.6.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:49ba23817781e22fcbd45fd9ff2b9b8cdb7b16a42a4851ab8025cae7b22e96d0"}, + {file = "frozenlist-1.6.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:431ef6937ae0f853143e2ca67d6da76c083e8b1fe3df0e96f3802fd37626e606"}, + {file = "frozenlist-1.6.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:9d124b38b3c299ca68433597ee26b7819209cb8a3a9ea761dfe9db3a04bba584"}, + {file = "frozenlist-1.6.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:118e97556306402e2b010da1ef21ea70cb6d6122e580da64c056b96f524fbd6a"}, + {file = "frozenlist-1.6.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fb3b309f1d4086b5533cf7bbcf3f956f0ae6469664522f1bde4feed26fba60f1"}, + {file = "frozenlist-1.6.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54dece0d21dce4fdb188a1ffc555926adf1d1c516e493c2914d7c370e454bc9e"}, + {file = "frozenlist-1.6.0-cp311-cp311-win32.whl", hash = "sha256:654e4ba1d0b2154ca2f096bed27461cf6160bc7f504a7f9a9ef447c293caf860"}, + {file = "frozenlist-1.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:3e911391bffdb806001002c1f860787542f45916c3baf764264a52765d5a5603"}, + {file = "frozenlist-1.6.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c5b9e42ace7d95bf41e19b87cec8f262c41d3510d8ad7514ab3862ea2197bfb1"}, + {file = "frozenlist-1.6.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ca9973735ce9f770d24d5484dcb42f68f135351c2fc81a7a9369e48cf2998a29"}, + {file = "frozenlist-1.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6ac40ec76041c67b928ca8aaffba15c2b2ee3f5ae8d0cb0617b5e63ec119ca25"}, + {file = "frozenlist-1.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95b7a8a3180dfb280eb044fdec562f9b461614c0ef21669aea6f1d3dac6ee576"}, + {file = "frozenlist-1.6.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c444d824e22da6c9291886d80c7d00c444981a72686e2b59d38b285617cb52c8"}, + {file = "frozenlist-1.6.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb52c8166499a8150bfd38478248572c924c003cbb45fe3bcd348e5ac7c000f9"}, + {file = "frozenlist-1.6.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b35298b2db9c2468106278537ee529719228950a5fdda686582f68f247d1dc6e"}, + {file = "frozenlist-1.6.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d108e2d070034f9d57210f22fefd22ea0d04609fc97c5f7f5a686b3471028590"}, + {file = "frozenlist-1.6.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e1be9111cb6756868ac242b3c2bd1f09d9aea09846e4f5c23715e7afb647103"}, + {file = "frozenlist-1.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:94bb451c664415f02f07eef4ece976a2c65dcbab9c2f1705b7031a3a75349d8c"}, + {file = "frozenlist-1.6.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:d1a686d0b0949182b8faddea596f3fc11f44768d1f74d4cad70213b2e139d821"}, + {file = "frozenlist-1.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ea8e59105d802c5a38bdbe7362822c522230b3faba2aa35c0fa1765239b7dd70"}, + {file = "frozenlist-1.6.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:abc4e880a9b920bc5020bf6a431a6bb40589d9bca3975c980495f63632e8382f"}, + {file = "frozenlist-1.6.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9a79713adfe28830f27a3c62f6b5406c37376c892b05ae070906f07ae4487046"}, + {file = "frozenlist-1.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9a0318c2068e217a8f5e3b85e35899f5a19e97141a45bb925bb357cfe1daf770"}, + {file = "frozenlist-1.6.0-cp312-cp312-win32.whl", hash = "sha256:853ac025092a24bb3bf09ae87f9127de9fe6e0c345614ac92536577cf956dfcc"}, + {file = "frozenlist-1.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:2bdfe2d7e6c9281c6e55523acd6c2bf77963cb422fdc7d142fb0cb6621b66878"}, + {file = "frozenlist-1.6.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1d7fb014fe0fbfee3efd6a94fc635aeaa68e5e1720fe9e57357f2e2c6e1a647e"}, + {file = "frozenlist-1.6.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:01bcaa305a0fdad12745502bfd16a1c75b14558dabae226852f9159364573117"}, + {file = "frozenlist-1.6.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8b314faa3051a6d45da196a2c495e922f987dc848e967d8cfeaee8a0328b1cd4"}, + {file = "frozenlist-1.6.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da62fecac21a3ee10463d153549d8db87549a5e77eefb8c91ac84bb42bb1e4e3"}, + {file = "frozenlist-1.6.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d1eb89bf3454e2132e046f9599fbcf0a4483ed43b40f545551a39316d0201cd1"}, + {file = "frozenlist-1.6.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18689b40cb3936acd971f663ccb8e2589c45db5e2c5f07e0ec6207664029a9c"}, + {file = "frozenlist-1.6.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e67ddb0749ed066b1a03fba812e2dcae791dd50e5da03be50b6a14d0c1a9ee45"}, + {file = "frozenlist-1.6.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fc5e64626e6682638d6e44398c9baf1d6ce6bc236d40b4b57255c9d3f9761f1f"}, + {file = "frozenlist-1.6.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:437cfd39564744ae32ad5929e55b18ebd88817f9180e4cc05e7d53b75f79ce85"}, + {file = "frozenlist-1.6.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:62dd7df78e74d924952e2feb7357d826af8d2f307557a779d14ddf94d7311be8"}, + {file = "frozenlist-1.6.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:a66781d7e4cddcbbcfd64de3d41a61d6bdde370fc2e38623f30b2bd539e84a9f"}, + {file = "frozenlist-1.6.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:482fe06e9a3fffbcd41950f9d890034b4a54395c60b5e61fae875d37a699813f"}, + {file = "frozenlist-1.6.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:e4f9373c500dfc02feea39f7a56e4f543e670212102cc2eeb51d3a99c7ffbde6"}, + {file = "frozenlist-1.6.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e69bb81de06827147b7bfbaeb284d85219fa92d9f097e32cc73675f279d70188"}, + {file = "frozenlist-1.6.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7613d9977d2ab4a9141dde4a149f4357e4065949674c5649f920fec86ecb393e"}, + {file = "frozenlist-1.6.0-cp313-cp313-win32.whl", hash = "sha256:4def87ef6d90429f777c9d9de3961679abf938cb6b7b63d4a7eb8a268babfce4"}, + {file = "frozenlist-1.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:37a8a52c3dfff01515e9bbbee0e6063181362f9de3db2ccf9bc96189b557cbfd"}, + {file = "frozenlist-1.6.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:46138f5a0773d064ff663d273b309b696293d7a7c00a0994c5c13a5078134b64"}, + {file = "frozenlist-1.6.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:f88bc0a2b9c2a835cb888b32246c27cdab5740059fb3688852bf91e915399b91"}, + {file = "frozenlist-1.6.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:777704c1d7655b802c7850255639672e90e81ad6fa42b99ce5ed3fbf45e338dd"}, + {file = "frozenlist-1.6.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85ef8d41764c7de0dcdaf64f733a27352248493a85a80661f3c678acd27e31f2"}, + {file = "frozenlist-1.6.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:da5cb36623f2b846fb25009d9d9215322318ff1c63403075f812b3b2876c8506"}, + {file = "frozenlist-1.6.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cbb56587a16cf0fb8acd19e90ff9924979ac1431baea8681712716a8337577b0"}, + {file = "frozenlist-1.6.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c6154c3ba59cda3f954c6333025369e42c3acd0c6e8b6ce31eb5c5b8116c07e0"}, + {file = "frozenlist-1.6.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e8246877afa3f1ae5c979fe85f567d220f86a50dc6c493b9b7d8191181ae01e"}, + {file = "frozenlist-1.6.0-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b0f6cce16306d2e117cf9db71ab3a9e8878a28176aeaf0dbe35248d97b28d0c"}, + {file = "frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:1b8e8cd8032ba266f91136d7105706ad57770f3522eac4a111d77ac126a25a9b"}, + {file = "frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:e2ada1d8515d3ea5378c018a5f6d14b4994d4036591a52ceaf1a1549dec8e1ad"}, + {file = "frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:cdb2c7f071e4026c19a3e32b93a09e59b12000751fc9b0b7758da899e657d215"}, + {file = "frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:03572933a1969a6d6ab509d509e5af82ef80d4a5d4e1e9f2e1cdd22c77a3f4d2"}, + {file = "frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:77effc978947548b676c54bbd6a08992759ea6f410d4987d69feea9cd0919911"}, + {file = "frozenlist-1.6.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:a2bda8be77660ad4089caf2223fdbd6db1858462c4b85b67fbfa22102021e497"}, + {file = "frozenlist-1.6.0-cp313-cp313t-win32.whl", hash = "sha256:a4d96dc5bcdbd834ec6b0f91027817214216b5b30316494d2b1aebffb87c534f"}, + {file = "frozenlist-1.6.0-cp313-cp313t-win_amd64.whl", hash = "sha256:e18036cb4caa17ea151fd5f3d70be9d354c99eb8cf817a3ccde8a7873b074348"}, + {file = "frozenlist-1.6.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:536a1236065c29980c15c7229fbb830dedf809708c10e159b8136534233545f0"}, + {file = "frozenlist-1.6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ed5e3a4462ff25ca84fb09e0fada8ea267df98a450340ead4c91b44857267d70"}, + {file = "frozenlist-1.6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e19c0fc9f4f030fcae43b4cdec9e8ab83ffe30ec10c79a4a43a04d1af6c5e1ad"}, + {file = "frozenlist-1.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7c608f833897501dac548585312d73a7dca028bf3b8688f0d712b7acfaf7fb3"}, + {file = "frozenlist-1.6.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:0dbae96c225d584f834b8d3cc688825911960f003a85cb0fd20b6e5512468c42"}, + {file = "frozenlist-1.6.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:625170a91dd7261a1d1c2a0c1a353c9e55d21cd67d0852185a5fef86587e6f5f"}, + {file = "frozenlist-1.6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1db8b2fc7ee8a940b547a14c10e56560ad3ea6499dc6875c354e2335812f739d"}, + {file = "frozenlist-1.6.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4da6fc43048b648275a220e3a61c33b7fff65d11bdd6dcb9d9c145ff708b804c"}, + {file = "frozenlist-1.6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ef8e7e8f2f3820c5f175d70fdd199b79e417acf6c72c5d0aa8f63c9f721646f"}, + {file = "frozenlist-1.6.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:aa733d123cc78245e9bb15f29b44ed9e5780dc6867cfc4e544717b91f980af3b"}, + {file = "frozenlist-1.6.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:ba7f8d97152b61f22d7f59491a781ba9b177dd9f318486c5fbc52cde2db12189"}, + {file = "frozenlist-1.6.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:56a0b8dd6d0d3d971c91f1df75e824986667ccce91e20dca2023683814344791"}, + {file = "frozenlist-1.6.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:5c9e89bf19ca148efcc9e3c44fd4c09d5af85c8a7dd3dbd0da1cb83425ef4983"}, + {file = "frozenlist-1.6.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:1330f0a4376587face7637dfd245380a57fe21ae8f9d360c1c2ef8746c4195fa"}, + {file = "frozenlist-1.6.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:2187248203b59625566cac53572ec8c2647a140ee2738b4e36772930377a533c"}, + {file = "frozenlist-1.6.0-cp39-cp39-win32.whl", hash = "sha256:2b8cf4cfea847d6c12af06091561a89740f1f67f331c3fa8623391905e878530"}, + {file = "frozenlist-1.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:1255d5d64328c5a0d066ecb0f02034d086537925f1f04b50b1ae60d37afbf572"}, + {file = "frozenlist-1.6.0-py3-none-any.whl", hash = "sha256:535eec9987adb04701266b92745d6cdcef2e77669299359c3009c3404dd5d191"}, + {file = "frozenlist-1.6.0.tar.gz", hash = "sha256:b99655c32c1c8e06d111e7f41c06c29a5318cb1835df23a45518e02a47c63b68"}, +] + +[[package]] +name = "fsspec" +version = "2024.12.0" +description = "File-system specification" +optional = false +python-versions = ">=3.8" +groups = ["main", "dev"] +files = [ + {file = "fsspec-2024.12.0-py3-none-any.whl", hash = "sha256:b520aed47ad9804237ff878b504267a3b0b441e97508bd6d2d8774e3db85cee2"}, + {file = "fsspec-2024.12.0.tar.gz", hash = "sha256:670700c977ed2fb51e0d9f9253177ed20cbde4a3e5c0283cc5385b5870c8533f"}, +] + +[package.dependencies] +aiohttp = {version = "<4.0.0a0 || >4.0.0a0,<4.0.0a1 || >4.0.0a1", optional = true, markers = "extra == \"http\""} +s3fs = {version = "*", optional = true, markers = "extra == \"s3\""} + +[package.extras] +abfs = ["adlfs"] +adl = ["adlfs"] +arrow = ["pyarrow (>=1)"] +dask = ["dask", "distributed"] +dev = ["pre-commit", "ruff"] +doc = ["numpydoc", "sphinx", "sphinx-design", "sphinx-rtd-theme", "yarl"] +dropbox = ["dropbox", "dropboxdrivefs", "requests"] +full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "dask", "distributed", "dropbox", "dropboxdrivefs", "fusepy", "gcsfs", "libarchive-c", "ocifs", "panel", "paramiko", "pyarrow (>=1)", "pygit2", "requests", "s3fs", "smbprotocol", "tqdm"] +fuse = ["fusepy"] +gcs = ["gcsfs"] +git = ["pygit2"] +github = ["requests"] +gs = ["gcsfs"] +gui = ["panel"] +hdfs = ["pyarrow (>=1)"] +http = ["aiohttp (!=4.0.0a0,!=4.0.0a1)"] +libarchive = ["libarchive-c"] +oci = ["ocifs"] +s3 = ["s3fs"] +sftp = ["paramiko"] +smb = ["smbprotocol"] +ssh = ["paramiko"] +test = ["aiohttp (!=4.0.0a0,!=4.0.0a1)", "numpy", "pytest", "pytest-asyncio (!=0.22.0)", "pytest-benchmark", "pytest-cov", "pytest-mock", "pytest-recording", "pytest-rerunfailures", "requests"] +test-downstream = ["aiobotocore (>=2.5.4,<3.0.0)", "dask-expr", "dask[dataframe,test]", "moto[server] (>4,<5)", "pytest-timeout", "xarray"] +test-full = ["adlfs", "aiohttp (!=4.0.0a0,!=4.0.0a1)", "cloudpickle", "dask", "distributed", "dropbox", "dropboxdrivefs", "fastparquet", "fusepy", "gcsfs", "jinja2", "kerchunk", "libarchive-c", "lz4", "notebook", "numpy", "ocifs", "pandas", "panel", "paramiko", "pyarrow", "pyarrow (>=1)", "pyftpdlib", "pygit2", "pytest", "pytest-asyncio (!=0.22.0)", "pytest-benchmark", "pytest-cov", "pytest-mock", "pytest-recording", "pytest-rerunfailures", "python-snappy", "requests", "smbprotocol", "tqdm", "urllib3", "zarr", "zstandard"] +tqdm = ["tqdm"] + [[package]] name = "googleapis-common-protos" -version = "1.69.2" +version = "1.70.0" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "googleapis_common_protos-1.69.2-py3-none-any.whl", hash = "sha256:0b30452ff9c7a27d80bfc5718954063e8ab53dd3697093d3bc99581f5fd24212"}, - {file = "googleapis_common_protos-1.69.2.tar.gz", hash = "sha256:3e1b904a27a33c821b4b749fd31d334c0c9c30e6113023d495e48979a3dc9c5f"}, + {file = "googleapis_common_protos-1.70.0-py3-none-any.whl", hash = "sha256:b8bfcca8c25a2bb253e0e0b0adaf8c00773e5e6af6fd92397576680b807e0fd8"}, + {file = "googleapis_common_protos-1.70.0.tar.gz", hash = "sha256:0e1b44e0ea153e6594f9f394fef15193a68aaaea2d843f83e2742717ca753257"}, ] [package.dependencies] @@ -324,7 +1271,7 @@ version = "0.14.0" description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.7" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, @@ -332,14 +1279,14 @@ files = [ [[package]] name = "httpcore" -version = "1.0.7" +version = "1.0.8" description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "dev"] files = [ - {file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"}, - {file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"}, + {file = "httpcore-1.0.8-py3-none-any.whl", hash = "sha256:5254cf149bcb5f75e9d1b2b9f729ea4a4b883d1ad7379fc632b727cec23674be"}, + {file = "httpcore-1.0.8.tar.gz", hash = "sha256:86e94505ed24ea06514883fd44d2bc02d90e77e7979c8eb71b90f41d364a1bad"}, ] [package.dependencies] @@ -358,7 +1305,7 @@ version = "0.28.1" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad"}, {file = "httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc"}, @@ -377,16 +1324,52 @@ http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] zstd = ["zstandard (>=0.18.0)"] +[[package]] +name = "huggingface-hub" +version = "0.30.2" +description = "Client library to download and publish models, datasets and other repos on the huggingface.co hub" +optional = false +python-versions = ">=3.8.0" +groups = ["main", "dev"] +files = [ + {file = "huggingface_hub-0.30.2-py3-none-any.whl", hash = "sha256:68ff05969927058cfa41df4f2155d4bb48f5f54f719dd0390103eefa9b191e28"}, + {file = "huggingface_hub-0.30.2.tar.gz", hash = "sha256:9a7897c5b6fd9dad3168a794a8998d6378210f5b9688d0dfc180b1a228dc2466"}, +] + +[package.dependencies] +filelock = "*" +fsspec = ">=2023.5.0" +packaging = ">=20.9" +pyyaml = ">=5.1" +requests = "*" +tqdm = ">=4.42.1" +typing-extensions = ">=3.7.4.3" + +[package.extras] +all = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio (>=4.0.0)", "jedi", "libcst (==1.4.0)", "mypy (==1.5.1)", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "ruff (>=0.9.0)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] +cli = ["InquirerPy (==0.3.4)"] +dev = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio (>=4.0.0)", "jedi", "libcst (==1.4.0)", "mypy (==1.5.1)", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "ruff (>=0.9.0)", "soundfile", "types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)", "urllib3 (<2.0)"] +fastai = ["fastai (>=2.4)", "fastcore (>=1.3.27)", "toml"] +hf-transfer = ["hf-transfer (>=0.1.4)"] +hf-xet = ["hf-xet (>=0.1.4)"] +inference = ["aiohttp"] +quality = ["libcst (==1.4.0)", "mypy (==1.5.1)", "ruff (>=0.9.0)"] +tensorflow = ["graphviz", "pydot", "tensorflow"] +tensorflow-testing = ["keras (<3.0)", "tensorflow"] +testing = ["InquirerPy (==0.3.4)", "Jinja2", "Pillow", "aiohttp", "fastapi", "gradio (>=4.0.0)", "jedi", "numpy", "pytest (>=8.1.1,<8.2.2)", "pytest-asyncio", "pytest-cov", "pytest-env", "pytest-mock", "pytest-rerunfailures", "pytest-vcr", "pytest-xdist", "soundfile", "urllib3 (<2.0)"] +torch = ["safetensors[torch]", "torch"] +typing = ["types-PyYAML", "types-requests", "types-simplejson", "types-toml", "types-tqdm", "types-urllib3", "typing-extensions (>=4.8.0)"] + [[package]] name = "identify" -version = "2.6.7" +version = "2.6.10" description = "File identification library for Python" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "identify-2.6.7-py2.py3-none-any.whl", hash = "sha256:155931cb617a401807b09ecec6635d6c692d180090a1cedca8ef7d58ba5b6aa0"}, - {file = "identify-2.6.7.tar.gz", hash = "sha256:3fa266b42eba321ee0b2bb0936a6a6b9e36a1351cbb69055b3082f4193035684"}, + {file = "identify-2.6.10-py2.py3-none-any.whl", hash = "sha256:5f34248f54136beed1a7ba6a6b5c4b6cf21ff495aac7c359e1ef831ae3b8ab25"}, + {file = "identify-2.6.10.tar.gz", hash = "sha256:45e92fd704f3da71cc3880036633f48b4b7265fd4de2b57627cb157216eb7eb8"}, ] [package.extras] @@ -407,13 +1390,64 @@ files = [ [package.extras] all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] +[[package]] +name = "imageio" +version = "2.37.0" +description = "Library for reading and writing a wide range of image, video, scientific, and volumetric data formats." +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "imageio-2.37.0-py3-none-any.whl", hash = "sha256:11efa15b87bc7871b61590326b2d635439acc321cf7f8ce996f812543ce10eed"}, + {file = "imageio-2.37.0.tar.gz", hash = "sha256:71b57b3669666272c818497aebba2b4c5f20d5b37c81720e5e1a56d59c492996"}, +] + +[package.dependencies] +numpy = "*" +pillow = ">=8.3.2" + +[package.extras] +all-plugins = ["astropy", "av", "imageio-ffmpeg", "numpy (>2)", "pillow-heif", "psutil", "rawpy", "tifffile"] +all-plugins-pypy = ["av", "imageio-ffmpeg", "pillow-heif", "psutil", "tifffile"] +build = ["wheel"] +dev = ["black", "flake8", "fsspec[github]", "pytest", "pytest-cov"] +docs = ["numpydoc", "pydata-sphinx-theme", "sphinx (<6)"] +ffmpeg = ["imageio-ffmpeg", "psutil"] +fits = ["astropy"] +full = ["astropy", "av", "black", "flake8", "fsspec[github]", "gdal", "imageio-ffmpeg", "itk", "numpy (>2)", "numpydoc", "pillow-heif", "psutil", "pydata-sphinx-theme", "pytest", "pytest-cov", "rawpy", "sphinx (<6)", "tifffile", "wheel"] +gdal = ["gdal"] +itk = ["itk"] +linting = ["black", "flake8"] +pillow-heif = ["pillow-heif"] +pyav = ["av"] +rawpy = ["numpy (>2)", "rawpy"] +test = ["fsspec[github]", "pytest", "pytest-cov"] +tifffile = ["tifffile"] + +[[package]] +name = "imageio-ffmpeg" +version = "0.6.0" +description = "FFMPEG wrapper for Python" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "imageio_ffmpeg-0.6.0-py3-none-macosx_10_9_intel.macosx_10_9_x86_64.whl", hash = "sha256:9d2baaf867088508d4a3458e61eeb30e945c4ad8016025545f66c4b5aaef0a61"}, + {file = "imageio_ffmpeg-0.6.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:b1ae3173414b5fc5f538a726c4e48ea97edc0d2cdc11f103afee655c463fa742"}, + {file = "imageio_ffmpeg-0.6.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:1d47bebd83d2c5fc770720d211855f208af8a596c82d17730aa51e815cdee6dc"}, + {file = "imageio_ffmpeg-0.6.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:c7e46fcec401dd990405049d2e2f475e2b397779df2519b544b8aab515195282"}, + {file = "imageio_ffmpeg-0.6.0-py3-none-win32.whl", hash = "sha256:196faa79366b4a82f95c0f4053191d2013f4714a715780f0ad2a68ff37483cc2"}, + {file = "imageio_ffmpeg-0.6.0-py3-none-win_amd64.whl", hash = "sha256:02fa47c83703c37df6bfe4896aab339013f62bf02c5ebf2dce6da56af04ffc0a"}, + {file = "imageio_ffmpeg-0.6.0.tar.gz", hash = "sha256:e2556bed8e005564a9f925bb7afa4002d82770d6b08825078b7697ab88ba1755"}, +] + [[package]] name = "importlib-metadata" version = "8.6.1" description = "Read metadata from Python packages" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "importlib_metadata-8.6.1-py3-none-any.whl", hash = "sha256:02a89390c1e15fdfdc0d7c6b25cb3e62650d0494005c97d6f148bf5b9787525e"}, {file = "importlib_metadata-8.6.1.tar.gz", hash = "sha256:310b41d755445d74569f993ccfc22838295d9fe005425094fad953d7f15c8580"}, @@ -433,26 +1467,236 @@ type = ["pytest-mypy"] [[package]] name = "iniconfig" -version = "2.0.0" +version = "2.1.0" description = "brain-dead simple config-ini parsing" optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, + {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, + {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "jiter" +version = "0.9.0" +description = "Fast iterable JSON parser." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "jiter-0.9.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:816ec9b60fdfd1fec87da1d7ed46c66c44ffec37ab2ef7de5b147b2fce3fd5ad"}, + {file = "jiter-0.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9b1d3086f8a3ee0194ecf2008cf81286a5c3e540d977fa038ff23576c023c0ea"}, + {file = "jiter-0.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1339f839b91ae30b37c409bf16ccd3dc453e8b8c3ed4bd1d6a567193651a4a51"}, + {file = "jiter-0.9.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ffba79584b3b670fefae66ceb3a28822365d25b7bf811e030609a3d5b876f538"}, + {file = "jiter-0.9.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cfc7d0a8e899089d11f065e289cb5b2daf3d82fbe028f49b20d7b809193958d"}, + {file = "jiter-0.9.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e00a1a2bbfaaf237e13c3d1592356eab3e9015d7efd59359ac8b51eb56390a12"}, + {file = "jiter-0.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1d9870561eb26b11448854dce0ff27a9a27cb616b632468cafc938de25e9e51"}, + {file = "jiter-0.9.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9872aeff3f21e437651df378cb75aeb7043e5297261222b6441a620218b58708"}, + {file = "jiter-0.9.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1fd19112d1049bdd47f17bfbb44a2c0001061312dcf0e72765bfa8abd4aa30e5"}, + {file = "jiter-0.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6ef5da104664e526836070e4a23b5f68dec1cc673b60bf1edb1bfbe8a55d0678"}, + {file = "jiter-0.9.0-cp310-cp310-win32.whl", hash = "sha256:cb12e6d65ebbefe5518de819f3eda53b73187b7089040b2d17f5b39001ff31c4"}, + {file = "jiter-0.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:c43ca669493626d8672be3b645dbb406ef25af3f4b6384cfd306da7eb2e70322"}, + {file = "jiter-0.9.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:6c4d99c71508912a7e556d631768dcdef43648a93660670986916b297f1c54af"}, + {file = "jiter-0.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8f60fb8ce7df529812bf6c625635a19d27f30806885139e367af93f6e734ef58"}, + {file = "jiter-0.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51c4e1a4f8ea84d98b7b98912aa4290ac3d1eabfde8e3c34541fae30e9d1f08b"}, + {file = "jiter-0.9.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f4c677c424dc76684fea3e7285a7a2a7493424bea89ac441045e6a1fb1d7b3b"}, + {file = "jiter-0.9.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2221176dfec87f3470b21e6abca056e6b04ce9bff72315cb0b243ca9e835a4b5"}, + {file = "jiter-0.9.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3c7adb66f899ffa25e3c92bfcb593391ee1947dbdd6a9a970e0d7e713237d572"}, + {file = "jiter-0.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c98d27330fdfb77913c1097a7aab07f38ff2259048949f499c9901700789ac15"}, + {file = "jiter-0.9.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eda3f8cc74df66892b1d06b5d41a71670c22d95a1ca2cbab73654745ce9d0419"}, + {file = "jiter-0.9.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dd5ab5ddc11418dce28343123644a100f487eaccf1de27a459ab36d6cca31043"}, + {file = "jiter-0.9.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:42f8a68a69f047b310319ef8e2f52fdb2e7976fb3313ef27df495cf77bcad965"}, + {file = "jiter-0.9.0-cp311-cp311-win32.whl", hash = "sha256:a25519efb78a42254d59326ee417d6f5161b06f5da827d94cf521fed961b1ff2"}, + {file = "jiter-0.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:923b54afdd697dfd00d368b7ccad008cccfeb1efb4e621f32860c75e9f25edbd"}, + {file = "jiter-0.9.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:7b46249cfd6c48da28f89eb0be3f52d6fdb40ab88e2c66804f546674e539ec11"}, + {file = "jiter-0.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:609cf3c78852f1189894383cf0b0b977665f54cb38788e3e6b941fa6d982c00e"}, + {file = "jiter-0.9.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d726a3890a54561e55a9c5faea1f7655eda7f105bd165067575ace6e65f80bb2"}, + {file = "jiter-0.9.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2e89dc075c1fef8fa9be219e249f14040270dbc507df4215c324a1839522ea75"}, + {file = "jiter-0.9.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04e8ffa3c353b1bc4134f96f167a2082494351e42888dfcf06e944f2729cbe1d"}, + {file = "jiter-0.9.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:203f28a72a05ae0e129b3ed1f75f56bc419d5f91dfacd057519a8bd137b00c42"}, + {file = "jiter-0.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fca1a02ad60ec30bb230f65bc01f611c8608b02d269f998bc29cca8619a919dc"}, + {file = "jiter-0.9.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:237e5cee4d5d2659aaf91bbf8ec45052cc217d9446070699441a91b386ae27dc"}, + {file = "jiter-0.9.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:528b6b71745e7326eed73c53d4aa57e2a522242320b6f7d65b9c5af83cf49b6e"}, + {file = "jiter-0.9.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9f48e86b57bc711eb5acdfd12b6cb580a59cc9a993f6e7dcb6d8b50522dcd50d"}, + {file = "jiter-0.9.0-cp312-cp312-win32.whl", hash = "sha256:699edfde481e191d81f9cf6d2211debbfe4bd92f06410e7637dffb8dd5dfde06"}, + {file = "jiter-0.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:099500d07b43f61d8bd780466d429c45a7b25411b334c60ca875fa775f68ccb0"}, + {file = "jiter-0.9.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:2764891d3f3e8b18dce2cff24949153ee30c9239da7c00f032511091ba688ff7"}, + {file = "jiter-0.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:387b22fbfd7a62418d5212b4638026d01723761c75c1c8232a8b8c37c2f1003b"}, + {file = "jiter-0.9.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d8da8629ccae3606c61d9184970423655fb4e33d03330bcdfe52d234d32f69"}, + {file = "jiter-0.9.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1be73d8982bdc278b7b9377426a4b44ceb5c7952073dd7488e4ae96b88e1103"}, + {file = "jiter-0.9.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2228eaaaa111ec54b9e89f7481bffb3972e9059301a878d085b2b449fbbde635"}, + {file = "jiter-0.9.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:11509bfecbc319459647d4ac3fd391d26fdf530dad00c13c4dadabf5b81f01a4"}, + {file = "jiter-0.9.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f22238da568be8bbd8e0650e12feeb2cfea15eda4f9fc271d3b362a4fa0604d"}, + {file = "jiter-0.9.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:17f5d55eb856597607562257c8e36c42bc87f16bef52ef7129b7da11afc779f3"}, + {file = "jiter-0.9.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:6a99bed9fbb02f5bed416d137944419a69aa4c423e44189bc49718859ea83bc5"}, + {file = "jiter-0.9.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e057adb0cd1bd39606100be0eafe742de2de88c79df632955b9ab53a086b3c8d"}, + {file = "jiter-0.9.0-cp313-cp313-win32.whl", hash = "sha256:f7e6850991f3940f62d387ccfa54d1a92bd4bb9f89690b53aea36b4364bcab53"}, + {file = "jiter-0.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:c8ae3bf27cd1ac5e6e8b7a27487bf3ab5f82318211ec2e1346a5b058756361f7"}, + {file = "jiter-0.9.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f0b2827fb88dda2cbecbbc3e596ef08d69bda06c6f57930aec8e79505dc17001"}, + {file = "jiter-0.9.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:062b756ceb1d40b0b28f326cba26cfd575a4918415b036464a52f08632731e5a"}, + {file = "jiter-0.9.0-cp313-cp313t-win_amd64.whl", hash = "sha256:6f7838bc467ab7e8ef9f387bd6de195c43bad82a569c1699cb822f6609dd4cdf"}, + {file = "jiter-0.9.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:4a2d16360d0642cd68236f931b85fe50288834c383492e4279d9f1792e309571"}, + {file = "jiter-0.9.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e84ed1c9c9ec10bbb8c37f450077cbe3c0d4e8c2b19f0a49a60ac7ace73c7452"}, + {file = "jiter-0.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f3c848209ccd1bfa344a1240763975ca917de753c7875c77ec3034f4151d06c"}, + {file = "jiter-0.9.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7825f46e50646bee937e0f849d14ef3a417910966136f59cd1eb848b8b5bb3e4"}, + {file = "jiter-0.9.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d82a811928b26d1a6311a886b2566f68ccf2b23cf3bfed042e18686f1f22c2d7"}, + {file = "jiter-0.9.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0c058ecb51763a67f019ae423b1cbe3fa90f7ee6280c31a1baa6ccc0c0e2d06e"}, + {file = "jiter-0.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9897115ad716c48f0120c1f0c4efae348ec47037319a6c63b2d7838bb53aaef4"}, + {file = "jiter-0.9.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:351f4c90a24c4fb8c87c6a73af2944c440494ed2bea2094feecacb75c50398ae"}, + {file = "jiter-0.9.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d45807b0f236c485e1e525e2ce3a854807dfe28ccf0d013dd4a563395e28008a"}, + {file = "jiter-0.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1537a890724ba00fdba21787010ac6f24dad47f763410e9e1093277913592784"}, + {file = "jiter-0.9.0-cp38-cp38-win32.whl", hash = "sha256:e3630ec20cbeaddd4b65513fa3857e1b7c4190d4481ef07fb63d0fad59033321"}, + {file = "jiter-0.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:2685f44bf80e95f8910553bf2d33b9c87bf25fceae6e9f0c1355f75d2922b0ee"}, + {file = "jiter-0.9.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:9ef340fae98065071ccd5805fe81c99c8f80484e820e40043689cf97fb66b3e2"}, + {file = "jiter-0.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:efb767d92c63b2cd9ec9f24feeb48f49574a713870ec87e9ba0c2c6e9329c3e2"}, + {file = "jiter-0.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:113f30f87fb1f412510c6d7ed13e91422cfd329436364a690c34c8b8bd880c42"}, + {file = "jiter-0.9.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8793b6df019b988526f5a633fdc7456ea75e4a79bd8396a3373c371fc59f5c9b"}, + {file = "jiter-0.9.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7a9aaa5102dba4e079bb728076fadd5a2dca94c05c04ce68004cfd96f128ea34"}, + {file = "jiter-0.9.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d838650f6ebaf4ccadfb04522463e74a4c378d7e667e0eb1865cfe3990bfac49"}, + {file = "jiter-0.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0194f813efdf4b8865ad5f5c5f50f8566df7d770a82c51ef593d09e0b347020"}, + {file = "jiter-0.9.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a7954a401d0a8a0b8bc669199db78af435aae1e3569187c2939c477c53cb6a0a"}, + {file = "jiter-0.9.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4feafe787eb8a8d98168ab15637ca2577f6ddf77ac6c8c66242c2d028aa5420e"}, + {file = "jiter-0.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:27cd1f2e8bb377f31d3190b34e4328d280325ad7ef55c6ac9abde72f79e84d2e"}, + {file = "jiter-0.9.0-cp39-cp39-win32.whl", hash = "sha256:161d461dcbe658cf0bd0aa375b30a968b087cdddc624fc585f3867c63c6eca95"}, + {file = "jiter-0.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:e8b36d8a16a61993be33e75126ad3d8aa29cf450b09576f3c427d27647fcb4aa"}, + {file = "jiter-0.9.0.tar.gz", hash = "sha256:aadba0964deb424daa24492abc3d229c60c4a31bfee205aedbf1acc7639d7893"}, +] + +[[package]] +name = "jmespath" +version = "1.0.1" +description = "JSON Matching Expressions" +optional = false python-versions = ">=3.7" +groups = ["main", "dev"] +files = [ + {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, + {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, +] + +[[package]] +name = "jsonpath-ng" +version = "1.7.0" +description = "A final implementation of JSONPath for Python that aims to be standard compliant, including arithmetic and binary comparison operators and providing clear AST for metaprogramming." +optional = false +python-versions = "*" +groups = ["dev"] +files = [ + {file = "jsonpath-ng-1.7.0.tar.gz", hash = "sha256:f6f5f7fd4e5ff79c785f1573b394043b39849fb2bb47bcead935d12b00beab3c"}, + {file = "jsonpath_ng-1.7.0-py2-none-any.whl", hash = "sha256:898c93fc173f0c336784a3fa63d7434297544b7198124a68f9a3ef9597b0ae6e"}, + {file = "jsonpath_ng-1.7.0-py3-none-any.whl", hash = "sha256:f3d7f9e848cba1b6da28c55b1c26ff915dc9e0b1ba7e752a53d6da8d5cbd00b6"}, +] + +[package.dependencies] +ply = "*" + +[[package]] +name = "jsonref" +version = "1.1.0" +description = "jsonref is a library for automatic dereferencing of JSON Reference objects for Python." +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "jsonref-1.1.0-py3-none-any.whl", hash = "sha256:590dc7773df6c21cbf948b5dac07a72a251db28b0238ceecce0a2abfa8ec30a9"}, + {file = "jsonref-1.1.0.tar.gz", hash = "sha256:32fe8e1d85af0fdefbebce950af85590b22b60f9e95443176adbde4e1ecea552"}, +] + +[[package]] +name = "jsonschema" +version = "4.23.0" +description = "An implementation of JSON Schema validation for Python" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566"}, + {file = "jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +jsonschema-specifications = ">=2023.03.6" +referencing = ">=0.28.4" +rpds-py = ">=0.7.1" + +[package.extras] +format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] +format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=24.6.0)"] + +[[package]] +name = "jsonschema-specifications" +version = "2025.4.1" +description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "jsonschema_specifications-2025.4.1-py3-none-any.whl", hash = "sha256:4653bffbd6584f7de83a67e0d620ef16900b390ddc7939d56684d6c81e33f1af"}, + {file = "jsonschema_specifications-2025.4.1.tar.gz", hash = "sha256:630159c9f4dbea161a6a2205c3011cc4f18ff381b189fff48bb39b9bf26ae608"}, +] + +[package.dependencies] +referencing = ">=0.31.0" + +[[package]] +name = "litellm" +version = "1.67.1" +description = "Library to easily interface with LLM API providers" +optional = false +python-versions = "!=2.7.*,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,!=3.7.*,>=3.8" groups = ["dev"] files = [ - {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, - {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, + {file = "litellm-1.67.1-py3-none-any.whl", hash = "sha256:8fff5b2a16b63bb594b94d6c071ad0f27d3d8cd4348bd5acea2fd40c8e0c11e8"}, + {file = "litellm-1.67.1.tar.gz", hash = "sha256:78eab1bd3d759ec13aa4a05864356a4a4725634e78501db609d451bf72150ee7"}, ] +[package.dependencies] +aiohttp = "*" +click = "*" +httpx = ">=0.23.0" +importlib-metadata = ">=6.8.0" +jinja2 = ">=3.1.2,<4.0.0" +jsonschema = ">=4.22.0,<5.0.0" +openai = ">=1.68.2" +pydantic = ">=2.0.0,<3.0.0" +python-dotenv = ">=0.2.0" +tiktoken = ">=0.7.0" +tokenizers = "*" + +[package.extras] +extra-proxy = ["azure-identity (>=1.15.0,<2.0.0)", "azure-keyvault-secrets (>=4.8.0,<5.0.0)", "google-cloud-kms (>=2.21.3,<3.0.0)", "prisma (==0.11.0)", "redisvl (>=0.4.1,<0.5.0) ; python_version >= \"3.9\" and python_version < \"3.14\"", "resend (>=0.8.0,<0.9.0)"] +proxy = ["PyJWT (>=2.8.0,<3.0.0)", "apscheduler (>=3.10.4,<4.0.0)", "backoff", "boto3 (==1.34.34)", "cryptography (>=43.0.1,<44.0.0)", "fastapi (>=0.115.5,<0.116.0)", "fastapi-sso (>=0.16.0,<0.17.0)", "gunicorn (>=23.0.0,<24.0.0)", "litellm-proxy-extras (==0.1.11)", "mcp (==1.5.0) ; python_version >= \"3.10\"", "orjson (>=3.9.7,<4.0.0)", "pynacl (>=1.5.0,<2.0.0)", "python-multipart (>=0.0.18,<0.0.19)", "pyyaml (>=6.0.1,<7.0.0)", "rq", "uvicorn (>=0.29.0,<0.30.0)", "uvloop (>=0.21.0,<0.22.0)", "websockets (>=13.1.0,<14.0.0)"] + [[package]] name = "logfire" -version = "3.13.1" +version = "3.14.0" description = "The best Python observability tool! 🪵🔥" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "logfire-3.13.1-py3-none-any.whl", hash = "sha256:b1c662dcb88fbdcfa1b6561f20ed0e758778e5542b21618cca71fee3c45c27eb"}, - {file = "logfire-3.13.1.tar.gz", hash = "sha256:56cd1133bf93d25d2cd44c004df131183997d479a163290e9aab81d58118889a"}, + {file = "logfire-3.14.0-py3-none-any.whl", hash = "sha256:4f95cf98a7c29cd7cd00e093ba75ce1e4e19e5069acda8b1577a4b7790e0237a"}, + {file = "logfire-3.14.0.tar.gz", hash = "sha256:afdd23386a8a57da7ab97938cc5eec17928ce9195907b85860d906f04c5d33e3"}, ] [package.dependencies] @@ -487,13 +1731,44 @@ starlette = ["opentelemetry-instrumentation-starlette (>=0.42b0)"] system-metrics = ["opentelemetry-instrumentation-system-metrics (>=0.42b0)"] wsgi = ["opentelemetry-instrumentation-wsgi (>=0.42b0)"] +[[package]] +name = "logfire-api" +version = "3.14.0" +description = "Shim for the Logfire SDK which does nothing unless Logfire is installed" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "logfire_api-3.14.0-py3-none-any.whl", hash = "sha256:e01f9049bca809cc102eb7550c4263fe560fa26abd68688e6dc2b8666e506a57"}, + {file = "logfire_api-3.14.0.tar.gz", hash = "sha256:70d5bcf075a50e89ecf8cdabe6220e3b00978fa5c0bb56cb9c75d8619107df49"}, +] + +[[package]] +name = "loguru" +version = "0.7.3" +description = "Python logging made (stupidly) simple" +optional = false +python-versions = "<4.0,>=3.5" +groups = ["main", "dev"] +files = [ + {file = "loguru-0.7.3-py3-none-any.whl", hash = "sha256:31a33c10c8e1e10422bfd431aeb5d351c7cf7fa671e3c4df004162264b28220c"}, + {file = "loguru-0.7.3.tar.gz", hash = "sha256:19480589e77d47b8d85b2c827ad95d49bf31b0dcde16593892eb51dd18706eb6"}, +] + +[package.dependencies] +colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} +win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} + +[package.extras] +dev = ["Sphinx (==8.1.3) ; python_version >= \"3.11\"", "build (==1.2.2) ; python_version >= \"3.11\"", "colorama (==0.4.5) ; python_version < \"3.8\"", "colorama (==0.4.6) ; python_version >= \"3.8\"", "exceptiongroup (==1.1.3) ; python_version >= \"3.7\" and python_version < \"3.11\"", "freezegun (==1.1.0) ; python_version < \"3.8\"", "freezegun (==1.5.0) ; python_version >= \"3.8\"", "mypy (==v0.910) ; python_version < \"3.6\"", "mypy (==v0.971) ; python_version == \"3.6\"", "mypy (==v1.13.0) ; python_version >= \"3.8\"", "mypy (==v1.4.1) ; python_version == \"3.7\"", "myst-parser (==4.0.0) ; python_version >= \"3.11\"", "pre-commit (==4.0.1) ; python_version >= \"3.9\"", "pytest (==6.1.2) ; python_version < \"3.8\"", "pytest (==8.3.2) ; python_version >= \"3.8\"", "pytest-cov (==2.12.1) ; python_version < \"3.8\"", "pytest-cov (==5.0.0) ; python_version == \"3.8\"", "pytest-cov (==6.0.0) ; python_version >= \"3.9\"", "pytest-mypy-plugins (==1.9.3) ; python_version >= \"3.6\" and python_version < \"3.8\"", "pytest-mypy-plugins (==3.1.0) ; python_version >= \"3.8\"", "sphinx-rtd-theme (==3.0.2) ; python_version >= \"3.11\"", "tox (==3.27.1) ; python_version < \"3.8\"", "tox (==4.23.2) ; python_version >= \"3.8\"", "twine (==6.0.1) ; python_version >= \"3.11\""] + [[package]] name = "markdown-it-py" version = "3.0.0" description = "Python port of markdown-it. Markdown parsing, done right!" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, @@ -512,18 +1787,257 @@ profiling = ["gprof2dot"] rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] +[[package]] +name = "markupsafe" +version = "3.0.2" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38a9ef736c01fccdd6600705b09dc574584b89bea478200c5fbf112a6b0d5579"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbcb445fa71794da8f178f0f6d66789a28d7319071af7a496d4d507ed566270d"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57cb5a3cf367aeb1d316576250f65edec5bb3be939e9247ae594b4bcbc317dfb"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3809ede931876f5b2ec92eef964286840ed3540dadf803dd570c3b7e13141a3b"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e07c3764494e3776c602c1e78e298937c3315ccc9043ead7e685b7f2b8d47b3c"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b424c77b206d63d500bcb69fa55ed8d0e6a3774056bdc4839fc9298a7edca171"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win32.whl", hash = "sha256:fcabf5ff6eea076f859677f5f0b6b5c1a51e70a376b0579e0eadef8db48c6b50"}, + {file = "MarkupSafe-3.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:6af100e168aa82a50e186c82875a5893c5597a0c1ccdb0d8b40240b1f28b969a"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9025b4018f3a1314059769c7bf15441064b2207cb3f065e6ea1e7359cb46db9d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93335ca3812df2f366e80509ae119189886b0f3c2b81325d39efdb84a1e2ae93"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cb8438c3cbb25e220c2ab33bb226559e7afb3baec11c4f218ffa7308603c832"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a123e330ef0853c6e822384873bef7507557d8e4a082961e1defa947aa59ba84"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e084f686b92e5b83186b07e8a17fc09e38fff551f3602b249881fec658d3eca"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d8213e09c917a951de9d09ecee036d5c7d36cb6cb7dbaece4c71a60d79fb9798"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5b02fb34468b6aaa40dfc198d813a641e3a63b98c2b05a16b9f80b7ec314185e"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0bff5e0ae4ef2e1ae4fdf2dfd5b76c75e5c2fa4132d05fc1b0dabcd20c7e28c4"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win32.whl", hash = "sha256:6c89876f41da747c8d3677a2b540fb32ef5715f97b66eeb0c6b66f5e3ef6f59d"}, + {file = "MarkupSafe-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:70a87b411535ccad5ef2f1df5136506a10775d267e197e4cf531ced10537bd6b"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30"}, + {file = "MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1"}, + {file = "MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6"}, + {file = "MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:eaa0a10b7f72326f1372a713e73c3f739b524b3af41feb43e4921cb529f5929a"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:48032821bbdf20f5799ff537c7ac3d1fba0ba032cfc06194faffa8cda8b560ff"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a9d3f5f0901fdec14d8d2f66ef7d035f2157240a433441719ac9a3fba440b13"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88b49a3b9ff31e19998750c38e030fc7bb937398b1f78cfa599aaef92d693144"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cfad01eed2c2e0c01fd0ecd2ef42c492f7f93902e39a42fc9ee1692961443a29"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1225beacc926f536dc82e45f8a4d68502949dc67eea90eab715dea3a21c1b5f0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3169b1eefae027567d1ce6ee7cae382c57fe26e82775f460f0b2778beaad66c0"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:eb7972a85c54febfb25b5c4b4f3af4dcc731994c7da0d8a0b4a6eb0640e1d178"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win32.whl", hash = "sha256:8c4e8c3ce11e1f92f6536ff07154f9d49677ebaaafc32db9db4620bc11ed480f"}, + {file = "MarkupSafe-3.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:6e296a513ca3d94054c2c881cc913116e90fd030ad1c656b3869762b754f5f8a"}, + {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, +] + [[package]] name = "mdurl" version = "0.1.2" description = "Markdown URL utilities" optional = false python-versions = ">=3.7" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, ] +[[package]] +name = "moviepy" +version = "2.1.2" +description = "Video editing with Python" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "moviepy-2.1.2-py3-none-any.whl", hash = "sha256:6cdc0d739110c8f347a224d72bd59eebaec010720d01eff290d37111bf545a73"}, + {file = "moviepy-2.1.2.tar.gz", hash = "sha256:22c57a7472f607eaad9fe80791df67c05082e1060fb74817c4eaac68e138ee77"}, +] + +[package.dependencies] +decorator = ">=4.0.2,<6.0" +imageio = ">=2.5,<3.0" +imageio_ffmpeg = ">=0.2.0" +numpy = ">=1.25.0" +pillow = ">=9.2.0,<11.0" +proglog = "<=1.0.0" +python-dotenv = ">=0.10" + +[package.extras] +doc = ["Sphinx (==6.*)", "numpydoc (<2.0)", "pydata-sphinx-theme (==0.13)", "sphinx_design"] +lint = ["black (>=23.7.0)", "flake8 (>=6.0.0)", "flake8-absolute-import (>=1.0)", "flake8-docstrings (>=1.7.0)", "flake8-implicit-str-concat (==0.4.0)", "flake8-rst-docstrings (>=0.3)", "isort (>=5.12)", "pre-commit (>=3.3)"] +test = ["coveralls (>=3.0,<4.0)", "pytest (>=3.0.0,<7.0.0)", "pytest-cov (>=2.5.1,<3.0)"] + +[[package]] +name = "multidict" +version = "6.4.3" +description = "multidict implementation" +optional = false +python-versions = ">=3.9" +groups = ["main", "dev"] +files = [ + {file = "multidict-6.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:32a998bd8a64ca48616eac5a8c1cc4fa38fb244a3facf2eeb14abe186e0f6cc5"}, + {file = "multidict-6.4.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a54ec568f1fc7f3c313c2f3b16e5db346bf3660e1309746e7fccbbfded856188"}, + {file = "multidict-6.4.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a7be07e5df178430621c716a63151165684d3e9958f2bbfcb644246162007ab7"}, + {file = "multidict-6.4.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b128dbf1c939674a50dd0b28f12c244d90e5015e751a4f339a96c54f7275e291"}, + {file = "multidict-6.4.3-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:b9cb19dfd83d35b6ff24a4022376ea6e45a2beba8ef3f0836b8a4b288b6ad685"}, + {file = "multidict-6.4.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3cf62f8e447ea2c1395afa289b332e49e13d07435369b6f4e41f887db65b40bf"}, + {file = "multidict-6.4.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:909f7d43ff8f13d1adccb6a397094adc369d4da794407f8dd592c51cf0eae4b1"}, + {file = "multidict-6.4.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0bb8f8302fbc7122033df959e25777b0b7659b1fd6bcb9cb6bed76b5de67afef"}, + {file = "multidict-6.4.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:224b79471b4f21169ea25ebc37ed6f058040c578e50ade532e2066562597b8a9"}, + {file = "multidict-6.4.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a7bd27f7ab3204f16967a6f899b3e8e9eb3362c0ab91f2ee659e0345445e0078"}, + {file = "multidict-6.4.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:99592bd3162e9c664671fd14e578a33bfdba487ea64bcb41d281286d3c870ad7"}, + {file = "multidict-6.4.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:a62d78a1c9072949018cdb05d3c533924ef8ac9bcb06cbf96f6d14772c5cd451"}, + {file = "multidict-6.4.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:3ccdde001578347e877ca4f629450973c510e88e8865d5aefbcb89b852ccc666"}, + {file = "multidict-6.4.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:eccb67b0e78aa2e38a04c5ecc13bab325a43e5159a181a9d1a6723db913cbb3c"}, + {file = "multidict-6.4.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8b6fcf6054fc4114a27aa865f8840ef3d675f9316e81868e0ad5866184a6cba5"}, + {file = "multidict-6.4.3-cp310-cp310-win32.whl", hash = "sha256:f92c7f62d59373cd93bc9969d2da9b4b21f78283b1379ba012f7ee8127b3152e"}, + {file = "multidict-6.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:b57e28dbc031d13916b946719f213c494a517b442d7b48b29443e79610acd887"}, + {file = "multidict-6.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f6f19170197cc29baccd33ccc5b5d6a331058796485857cf34f7635aa25fb0cd"}, + {file = "multidict-6.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f2882bf27037eb687e49591690e5d491e677272964f9ec7bc2abbe09108bdfb8"}, + {file = "multidict-6.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fbf226ac85f7d6b6b9ba77db4ec0704fde88463dc17717aec78ec3c8546c70ad"}, + {file = "multidict-6.4.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e329114f82ad4b9dd291bef614ea8971ec119ecd0f54795109976de75c9a852"}, + {file = "multidict-6.4.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:1f4e0334d7a555c63f5c8952c57ab6f1c7b4f8c7f3442df689fc9f03df315c08"}, + {file = "multidict-6.4.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:740915eb776617b57142ce0bb13b7596933496e2f798d3d15a20614adf30d229"}, + {file = "multidict-6.4.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255dac25134d2b141c944b59a0d2f7211ca12a6d4779f7586a98b4b03ea80508"}, + {file = "multidict-6.4.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4e8535bd4d741039b5aad4285ecd9b902ef9e224711f0b6afda6e38d7ac02c7"}, + {file = "multidict-6.4.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c433a33be000dd968f5750722eaa0991037be0be4a9d453eba121774985bc8"}, + {file = "multidict-6.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4eb33b0bdc50acd538f45041f5f19945a1f32b909b76d7b117c0c25d8063df56"}, + {file = "multidict-6.4.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:75482f43465edefd8a5d72724887ccdcd0c83778ded8f0cb1e0594bf71736cc0"}, + {file = "multidict-6.4.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ce5b3082e86aee80b3925ab4928198450d8e5b6466e11501fe03ad2191c6d777"}, + {file = "multidict-6.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e413152e3212c4d39f82cf83c6f91be44bec9ddea950ce17af87fbf4e32ca6b2"}, + {file = "multidict-6.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:8aac2eeff69b71f229a405c0a4b61b54bade8e10163bc7b44fcd257949620618"}, + {file = "multidict-6.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ab583ac203af1d09034be41458feeab7863c0635c650a16f15771e1386abf2d7"}, + {file = "multidict-6.4.3-cp311-cp311-win32.whl", hash = "sha256:1b2019317726f41e81154df636a897de1bfe9228c3724a433894e44cd2512378"}, + {file = "multidict-6.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:43173924fa93c7486402217fab99b60baf78d33806af299c56133a3755f69589"}, + {file = "multidict-6.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1f1c2f58f08b36f8475f3ec6f5aeb95270921d418bf18f90dffd6be5c7b0e676"}, + {file = "multidict-6.4.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:26ae9ad364fc61b936fb7bf4c9d8bd53f3a5b4417142cd0be5c509d6f767e2f1"}, + {file = "multidict-6.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:659318c6c8a85f6ecfc06b4e57529e5a78dfdd697260cc81f683492ad7e9435a"}, + {file = "multidict-6.4.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e1eb72c741fd24d5a28242ce72bb61bc91f8451877131fa3fe930edb195f7054"}, + {file = "multidict-6.4.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3cd06d88cb7398252284ee75c8db8e680aa0d321451132d0dba12bc995f0adcc"}, + {file = "multidict-6.4.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4543d8dc6470a82fde92b035a92529317191ce993533c3c0c68f56811164ed07"}, + {file = "multidict-6.4.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:30a3ebdc068c27e9d6081fca0e2c33fdf132ecea703a72ea216b81a66860adde"}, + {file = "multidict-6.4.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b038f10e23f277153f86f95c777ba1958bcd5993194fda26a1d06fae98b2f00c"}, + {file = "multidict-6.4.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c605a2b2dc14282b580454b9b5d14ebe0668381a3a26d0ac39daa0ca115eb2ae"}, + {file = "multidict-6.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8bd2b875f4ca2bb527fe23e318ddd509b7df163407b0fb717df229041c6df5d3"}, + {file = "multidict-6.4.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c2e98c840c9c8e65c0e04b40c6c5066c8632678cd50c8721fdbcd2e09f21a507"}, + {file = "multidict-6.4.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:66eb80dd0ab36dbd559635e62fba3083a48a252633164857a1d1684f14326427"}, + {file = "multidict-6.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c23831bdee0a2a3cf21be057b5e5326292f60472fb6c6f86392bbf0de70ba731"}, + {file = "multidict-6.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:1535cec6443bfd80d028052e9d17ba6ff8a5a3534c51d285ba56c18af97e9713"}, + {file = "multidict-6.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3b73e7227681f85d19dec46e5b881827cd354aabe46049e1a61d2f9aaa4e285a"}, + {file = "multidict-6.4.3-cp312-cp312-win32.whl", hash = "sha256:8eac0c49df91b88bf91f818e0a24c1c46f3622978e2c27035bfdca98e0e18124"}, + {file = "multidict-6.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:11990b5c757d956cd1db7cb140be50a63216af32cd6506329c2c59d732d802db"}, + {file = "multidict-6.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a76534263d03ae0cfa721fea40fd2b5b9d17a6f85e98025931d41dc49504474"}, + {file = "multidict-6.4.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:805031c2f599eee62ac579843555ed1ce389ae00c7e9f74c2a1b45e0564a88dd"}, + {file = "multidict-6.4.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:c56c179839d5dcf51d565132185409d1d5dd8e614ba501eb79023a6cab25576b"}, + {file = "multidict-6.4.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c64f4ddb3886dd8ab71b68a7431ad4aa01a8fa5be5b11543b29674f29ca0ba3"}, + {file = "multidict-6.4.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3002a856367c0b41cad6784f5b8d3ab008eda194ed7864aaa58f65312e2abcac"}, + {file = "multidict-6.4.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3d75e621e7d887d539d6e1d789f0c64271c250276c333480a9e1de089611f790"}, + {file = "multidict-6.4.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:995015cf4a3c0d72cbf453b10a999b92c5629eaf3a0c3e1efb4b5c1f602253bb"}, + {file = "multidict-6.4.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2b0fabae7939d09d7d16a711468c385272fa1b9b7fb0d37e51143585d8e72e0"}, + {file = "multidict-6.4.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:61ed4d82f8a1e67eb9eb04f8587970d78fe7cddb4e4d6230b77eda23d27938f9"}, + {file = "multidict-6.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:062428944a8dc69df9fdc5d5fc6279421e5f9c75a9ee3f586f274ba7b05ab3c8"}, + {file = "multidict-6.4.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:b90e27b4674e6c405ad6c64e515a505c6d113b832df52fdacb6b1ffd1fa9a1d1"}, + {file = "multidict-6.4.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7d50d4abf6729921e9613d98344b74241572b751c6b37feed75fb0c37bd5a817"}, + {file = "multidict-6.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:43fe10524fb0a0514be3954be53258e61d87341008ce4914f8e8b92bee6f875d"}, + {file = "multidict-6.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:236966ca6c472ea4e2d3f02f6673ebfd36ba3f23159c323f5a496869bc8e47c9"}, + {file = "multidict-6.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:422a5ec315018e606473ba1f5431e064cf8b2a7468019233dcf8082fabad64c8"}, + {file = "multidict-6.4.3-cp313-cp313-win32.whl", hash = "sha256:f901a5aace8e8c25d78960dcc24c870c8d356660d3b49b93a78bf38eb682aac3"}, + {file = "multidict-6.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:1c152c49e42277bc9a2f7b78bd5fa10b13e88d1b0328221e7aef89d5c60a99a5"}, + {file = "multidict-6.4.3-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:be8751869e28b9c0d368d94f5afcb4234db66fe8496144547b4b6d6a0645cfc6"}, + {file = "multidict-6.4.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0d4b31f8a68dccbcd2c0ea04f0e014f1defc6b78f0eb8b35f2265e8716a6df0c"}, + {file = "multidict-6.4.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:032efeab3049e37eef2ff91271884303becc9e54d740b492a93b7e7266e23756"}, + {file = "multidict-6.4.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e78006af1a7c8a8007e4f56629d7252668344442f66982368ac06522445e375"}, + {file = "multidict-6.4.3-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:daeac9dd30cda8703c417e4fddccd7c4dc0c73421a0b54a7da2713be125846be"}, + {file = "multidict-6.4.3-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f6f90700881438953eae443a9c6f8a509808bc3b185246992c4233ccee37fea"}, + {file = "multidict-6.4.3-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f84627997008390dd15762128dcf73c3365f4ec0106739cde6c20a07ed198ec8"}, + {file = "multidict-6.4.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3307b48cd156153b117c0ea54890a3bdbf858a5b296ddd40dc3852e5f16e9b02"}, + {file = "multidict-6.4.3-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ead46b0fa1dcf5af503a46e9f1c2e80b5d95c6011526352fa5f42ea201526124"}, + {file = "multidict-6.4.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:1748cb2743bedc339d63eb1bca314061568793acd603a6e37b09a326334c9f44"}, + {file = "multidict-6.4.3-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:acc9fa606f76fc111b4569348cc23a771cb52c61516dcc6bcef46d612edb483b"}, + {file = "multidict-6.4.3-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:31469d5832b5885adeb70982e531ce86f8c992334edd2f2254a10fa3182ac504"}, + {file = "multidict-6.4.3-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ba46b51b6e51b4ef7bfb84b82f5db0dc5e300fb222a8a13b8cd4111898a869cf"}, + {file = "multidict-6.4.3-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:389cfefb599edf3fcfd5f64c0410da686f90f5f5e2c4d84e14f6797a5a337af4"}, + {file = "multidict-6.4.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:64bc2bbc5fba7b9db5c2c8d750824f41c6994e3882e6d73c903c2afa78d091e4"}, + {file = "multidict-6.4.3-cp313-cp313t-win32.whl", hash = "sha256:0ecdc12ea44bab2807d6b4a7e5eef25109ab1c82a8240d86d3c1fc9f3b72efd5"}, + {file = "multidict-6.4.3-cp313-cp313t-win_amd64.whl", hash = "sha256:7146a8742ea71b5d7d955bffcef58a9e6e04efba704b52a460134fefd10a8208"}, + {file = "multidict-6.4.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5427a2679e95a642b7f8b0f761e660c845c8e6fe3141cddd6b62005bd133fc21"}, + {file = "multidict-6.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:24a8caa26521b9ad09732972927d7b45b66453e6ebd91a3c6a46d811eeb7349b"}, + {file = "multidict-6.4.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6b5a272bc7c36a2cd1b56ddc6bff02e9ce499f9f14ee4a45c45434ef083f2459"}, + {file = "multidict-6.4.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edf74dc5e212b8c75165b435c43eb0d5e81b6b300a938a4eb82827119115e840"}, + {file = "multidict-6.4.3-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9f35de41aec4b323c71f54b0ca461ebf694fb48bec62f65221f52e0017955b39"}, + {file = "multidict-6.4.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae93e0ff43b6f6892999af64097b18561691ffd835e21a8348a441e256592e1f"}, + {file = "multidict-6.4.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e3929269e9d7eff905d6971d8b8c85e7dbc72c18fb99c8eae6fe0a152f2e343"}, + {file = "multidict-6.4.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb6214fe1750adc2a1b801a199d64b5a67671bf76ebf24c730b157846d0e90d2"}, + {file = "multidict-6.4.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6d79cf5c0c6284e90f72123f4a3e4add52d6c6ebb4a9054e88df15b8d08444c6"}, + {file = "multidict-6.4.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2427370f4a255262928cd14533a70d9738dfacadb7563bc3b7f704cc2360fc4e"}, + {file = "multidict-6.4.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:fbd8d737867912b6c5f99f56782b8cb81f978a97b4437a1c476de90a3e41c9a1"}, + {file = "multidict-6.4.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:0ee1bf613c448997f73fc4efb4ecebebb1c02268028dd4f11f011f02300cf1e8"}, + {file = "multidict-6.4.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:578568c4ba5f2b8abd956baf8b23790dbfdc953e87d5b110bce343b4a54fc9e7"}, + {file = "multidict-6.4.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:a059ad6b80de5b84b9fa02a39400319e62edd39d210b4e4f8c4f1243bdac4752"}, + {file = "multidict-6.4.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:dd53893675b729a965088aaadd6a1f326a72b83742b056c1065bdd2e2a42b4df"}, + {file = "multidict-6.4.3-cp39-cp39-win32.whl", hash = "sha256:abcfed2c4c139f25c2355e180bcc077a7cae91eefbb8b3927bb3f836c9586f1f"}, + {file = "multidict-6.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:b1b389ae17296dd739015d5ddb222ee99fd66adeae910de21ac950e00979d897"}, + {file = "multidict-6.4.3-py3-none-any.whl", hash = "sha256:59fe01ee8e2a1e8ceb3f6dbb216b09c8d9f4ef1c22c4fc825d045a147fa2ebc9"}, + {file = "multidict-6.4.3.tar.gz", hash = "sha256:3ada0b058c9f213c5f95ba301f922d402ac234f1111a7d8fd70f1b99f3c281ec"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""} + +[[package]] +name = "multiprocess" +version = "0.70.16" +description = "better multiprocessing and multithreading in Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "multiprocess-0.70.16-pp310-pypy310_pp73-macosx_10_13_x86_64.whl", hash = "sha256:476887be10e2f59ff183c006af746cb6f1fd0eadcfd4ef49e605cbe2659920ee"}, + {file = "multiprocess-0.70.16-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d951bed82c8f73929ac82c61f01a7b5ce8f3e5ef40f5b52553b4f547ce2b08ec"}, + {file = "multiprocess-0.70.16-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:37b55f71c07e2d741374998c043b9520b626a8dddc8b3129222ca4f1a06ef67a"}, + {file = "multiprocess-0.70.16-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ba8c31889abf4511c7308a8c52bb4a30b9d590e7f58523302ba00237702ca054"}, + {file = "multiprocess-0.70.16-pp39-pypy39_pp73-macosx_10_13_x86_64.whl", hash = "sha256:0dfd078c306e08d46d7a8d06fb120313d87aa43af60d66da43ffff40b44d2f41"}, + {file = "multiprocess-0.70.16-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e7b9d0f307cd9bd50851afaac0dba2cb6c44449efff697df7c7645f7d3f2be3a"}, + {file = "multiprocess-0.70.16-py310-none-any.whl", hash = "sha256:c4a9944c67bd49f823687463660a2d6daae94c289adff97e0f9d696ba6371d02"}, + {file = "multiprocess-0.70.16-py311-none-any.whl", hash = "sha256:af4cabb0dac72abfb1e794fa7855c325fd2b55a10a44628a3c1ad3311c04127a"}, + {file = "multiprocess-0.70.16-py312-none-any.whl", hash = "sha256:fc0544c531920dde3b00c29863377f87e1632601092ea2daca74e4beb40faa2e"}, + {file = "multiprocess-0.70.16-py38-none-any.whl", hash = "sha256:a71d82033454891091a226dfc319d0cfa8019a4e888ef9ca910372a446de4435"}, + {file = "multiprocess-0.70.16-py39-none-any.whl", hash = "sha256:a0bafd3ae1b732eac64be2e72038231c1ba97724b60b09400d68f229fcc2fbf3"}, + {file = "multiprocess-0.70.16.tar.gz", hash = "sha256:161af703d4652a0e1410be6abccecde4a7ddffd19341be0a7011b94aeb171ac1"}, +] + +[package.dependencies] +dill = ">=0.3.8" + [[package]] name = "mypy" version = "1.15.0" @@ -578,16 +2092,31 @@ install-types = ["pip"] mypyc = ["setuptools (>=50)"] reports = ["lxml"] +[[package]] +name = "mypy-boto3-s3" +version = "1.38.0" +description = "Type annotations for boto3 S3 1.38.0 service generated with mypy-boto3-builder 8.10.1" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "mypy_boto3_s3-1.38.0-py3-none-any.whl", hash = "sha256:5cd9449df0ef6cf89e00e6fc9130a0ab641f703a23ab1d2146c394da058e8282"}, + {file = "mypy_boto3_s3-1.38.0.tar.gz", hash = "sha256:f8fe586e45123ffcd305a0c30847128f3931d888649e2b4c5a52f412183c840a"}, +] + +[package.dependencies] +typing-extensions = {version = "*", markers = "python_version < \"3.12\""} + [[package]] name = "mypy-extensions" -version = "1.0.0" +version = "1.1.0" description = "Type system extensions for programs checked with the mypy type checker." optional = false -python-versions = ">=3.5" +python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, - {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, + {file = "mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505"}, + {file = "mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558"}, ] [[package]] @@ -604,79 +2133,106 @@ files = [ [[package]] name = "numpy" -version = "2.2.4" +version = "2.2.5" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.10" -groups = ["main"] +groups = ["main", "dev"] +files = [ + {file = "numpy-2.2.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1f4a922da1729f4c40932b2af4fe84909c7a6e167e6e99f71838ce3a29f3fe26"}, + {file = "numpy-2.2.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b6f91524d31b34f4a5fee24f5bc16dcd1491b668798b6d85585d836c1e633a6a"}, + {file = "numpy-2.2.5-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:19f4718c9012e3baea91a7dba661dcab2451cda2550678dc30d53acb91a7290f"}, + {file = "numpy-2.2.5-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:eb7fd5b184e5d277afa9ec0ad5e4eb562ecff541e7f60e69ee69c8d59e9aeaba"}, + {file = "numpy-2.2.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6413d48a9be53e183eb06495d8e3b006ef8f87c324af68241bbe7a39e8ff54c3"}, + {file = "numpy-2.2.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7451f92eddf8503c9b8aa4fe6aa7e87fd51a29c2cfc5f7dbd72efde6c65acf57"}, + {file = "numpy-2.2.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0bcb1d057b7571334139129b7f941588f69ce7c4ed15a9d6162b2ea54ded700c"}, + {file = "numpy-2.2.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:36ab5b23915887543441efd0417e6a3baa08634308894316f446027611b53bf1"}, + {file = "numpy-2.2.5-cp310-cp310-win32.whl", hash = "sha256:422cc684f17bc963da5f59a31530b3936f57c95a29743056ef7a7903a5dbdf88"}, + {file = "numpy-2.2.5-cp310-cp310-win_amd64.whl", hash = "sha256:e4f0b035d9d0ed519c813ee23e0a733db81ec37d2e9503afbb6e54ccfdee0fa7"}, + {file = "numpy-2.2.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c42365005c7a6c42436a54d28c43fe0e01ca11eb2ac3cefe796c25a5f98e5e9b"}, + {file = "numpy-2.2.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:498815b96f67dc347e03b719ef49c772589fb74b8ee9ea2c37feae915ad6ebda"}, + {file = "numpy-2.2.5-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:6411f744f7f20081b1b4e7112e0f4c9c5b08f94b9f086e6f0adf3645f85d3a4d"}, + {file = "numpy-2.2.5-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:9de6832228f617c9ef45d948ec1cd8949c482238d68b2477e6f642c33a7b0a54"}, + {file = "numpy-2.2.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:369e0d4647c17c9363244f3468f2227d557a74b6781cb62ce57cf3ef5cc7c610"}, + {file = "numpy-2.2.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:262d23f383170f99cd9191a7c85b9a50970fe9069b2f8ab5d786eca8a675d60b"}, + {file = "numpy-2.2.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:aa70fdbdc3b169d69e8c59e65c07a1c9351ceb438e627f0fdcd471015cd956be"}, + {file = "numpy-2.2.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37e32e985f03c06206582a7323ef926b4e78bdaa6915095ef08070471865b906"}, + {file = "numpy-2.2.5-cp311-cp311-win32.whl", hash = "sha256:f5045039100ed58fa817a6227a356240ea1b9a1bc141018864c306c1a16d4175"}, + {file = "numpy-2.2.5-cp311-cp311-win_amd64.whl", hash = "sha256:b13f04968b46ad705f7c8a80122a42ae8f620536ea38cf4bdd374302926424dd"}, + {file = "numpy-2.2.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ee461a4eaab4f165b68780a6a1af95fb23a29932be7569b9fab666c407969051"}, + {file = "numpy-2.2.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ec31367fd6a255dc8de4772bd1658c3e926d8e860a0b6e922b615e532d320ddc"}, + {file = "numpy-2.2.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:47834cde750d3c9f4e52c6ca28a7361859fcaf52695c7dc3cc1a720b8922683e"}, + {file = "numpy-2.2.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:2c1a1c6ccce4022383583a6ded7bbcda22fc635eb4eb1e0a053336425ed36dfa"}, + {file = "numpy-2.2.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d75f338f5f79ee23548b03d801d28a505198297534f62416391857ea0479571"}, + {file = "numpy-2.2.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a801fef99668f309b88640e28d261991bfad9617c27beda4a3aec4f217ea073"}, + {file = "numpy-2.2.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:abe38cd8381245a7f49967a6010e77dbf3680bd3627c0fe4362dd693b404c7f8"}, + {file = "numpy-2.2.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5a0ac90e46fdb5649ab6369d1ab6104bfe5854ab19b645bf5cda0127a13034ae"}, + {file = "numpy-2.2.5-cp312-cp312-win32.whl", hash = "sha256:0cd48122a6b7eab8f06404805b1bd5856200e3ed6f8a1b9a194f9d9054631beb"}, + {file = "numpy-2.2.5-cp312-cp312-win_amd64.whl", hash = "sha256:ced69262a8278547e63409b2653b372bf4baff0870c57efa76c5703fd6543282"}, + {file = "numpy-2.2.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:059b51b658f4414fff78c6d7b1b4e18283ab5fa56d270ff212d5ba0c561846f4"}, + {file = "numpy-2.2.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:47f9ed103af0bc63182609044b0490747e03bd20a67e391192dde119bf43d52f"}, + {file = "numpy-2.2.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:261a1ef047751bb02f29dfe337230b5882b54521ca121fc7f62668133cb119c9"}, + {file = "numpy-2.2.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4520caa3807c1ceb005d125a75e715567806fed67e315cea619d5ec6e75a4191"}, + {file = "numpy-2.2.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d14b17b9be5f9c9301f43d2e2a4886a33b53f4e6fdf9ca2f4cc60aeeee76372"}, + {file = "numpy-2.2.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ba321813a00e508d5421104464510cc962a6f791aa2fca1c97b1e65027da80d"}, + {file = "numpy-2.2.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4cbdef3ddf777423060c6f81b5694bad2dc9675f110c4b2a60dc0181543fac7"}, + {file = "numpy-2.2.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:54088a5a147ab71a8e7fdfd8c3601972751ded0739c6b696ad9cb0343e21ab73"}, + {file = "numpy-2.2.5-cp313-cp313-win32.whl", hash = "sha256:c8b82a55ef86a2d8e81b63da85e55f5537d2157165be1cb2ce7cfa57b6aef38b"}, + {file = "numpy-2.2.5-cp313-cp313-win_amd64.whl", hash = "sha256:d8882a829fd779f0f43998e931c466802a77ca1ee0fe25a3abe50278616b1471"}, + {file = "numpy-2.2.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:e8b025c351b9f0e8b5436cf28a07fa4ac0204d67b38f01433ac7f9b870fa38c6"}, + {file = "numpy-2.2.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8dfa94b6a4374e7851bbb6f35e6ded2120b752b063e6acdd3157e4d2bb922eba"}, + {file = "numpy-2.2.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:97c8425d4e26437e65e1d189d22dff4a079b747ff9c2788057bfb8114ce1e133"}, + {file = "numpy-2.2.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:352d330048c055ea6db701130abc48a21bec690a8d38f8284e00fab256dc1376"}, + {file = "numpy-2.2.5-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b4c0773b6ada798f51f0f8e30c054d32304ccc6e9c5d93d46cb26f3d385ab19"}, + {file = "numpy-2.2.5-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55f09e00d4dccd76b179c0f18a44f041e5332fd0e022886ba1c0bbf3ea4a18d0"}, + {file = "numpy-2.2.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:02f226baeefa68f7d579e213d0f3493496397d8f1cff5e2b222af274c86a552a"}, + {file = "numpy-2.2.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c26843fd58f65da9491165072da2cccc372530681de481ef670dcc8e27cfb066"}, + {file = "numpy-2.2.5-cp313-cp313t-win32.whl", hash = "sha256:1a161c2c79ab30fe4501d5a2bbfe8b162490757cf90b7f05be8b80bc02f7bb8e"}, + {file = "numpy-2.2.5-cp313-cp313t-win_amd64.whl", hash = "sha256:d403c84991b5ad291d3809bace5e85f4bbf44a04bdc9a88ed2bb1807b3360bb8"}, + {file = "numpy-2.2.5-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b4ea7e1cff6784e58fe281ce7e7f05036b3e1c89c6f922a6bfbc0a7e8768adbe"}, + {file = "numpy-2.2.5-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:d7543263084a85fbc09c704b515395398d31d6395518446237eac219eab9e55e"}, + {file = "numpy-2.2.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0255732338c4fdd00996c0421884ea8a3651eea555c3a56b84892b66f696eb70"}, + {file = "numpy-2.2.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d2e3bdadaba0e040d1e7ab39db73e0afe2c74ae277f5614dad53eadbecbbb169"}, + {file = "numpy-2.2.5.tar.gz", hash = "sha256:a9c0d994680cd991b1cb772e8b297340085466a6fe964bc9d4e80f5e2f43c291"}, +] + +[[package]] +name = "openai" +version = "1.76.0" +description = "The official Python library for the openai API" +optional = false +python-versions = ">=3.8" +groups = ["dev"] files = [ - {file = "numpy-2.2.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8146f3550d627252269ac42ae660281d673eb6f8b32f113538e0cc2a9aed42b9"}, - {file = "numpy-2.2.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e642d86b8f956098b564a45e6f6ce68a22c2c97a04f5acd3f221f57b8cb850ae"}, - {file = "numpy-2.2.4-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:a84eda42bd12edc36eb5b53bbcc9b406820d3353f1994b6cfe453a33ff101775"}, - {file = "numpy-2.2.4-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:4ba5054787e89c59c593a4169830ab362ac2bee8a969249dc56e5d7d20ff8df9"}, - {file = "numpy-2.2.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7716e4a9b7af82c06a2543c53ca476fa0b57e4d760481273e09da04b74ee6ee2"}, - {file = "numpy-2.2.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:adf8c1d66f432ce577d0197dceaac2ac00c0759f573f28516246351c58a85020"}, - {file = "numpy-2.2.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:218f061d2faa73621fa23d6359442b0fc658d5b9a70801373625d958259eaca3"}, - {file = "numpy-2.2.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:df2f57871a96bbc1b69733cd4c51dc33bea66146b8c63cacbfed73eec0883017"}, - {file = "numpy-2.2.4-cp310-cp310-win32.whl", hash = "sha256:a0258ad1f44f138b791327961caedffbf9612bfa504ab9597157806faa95194a"}, - {file = "numpy-2.2.4-cp310-cp310-win_amd64.whl", hash = "sha256:0d54974f9cf14acf49c60f0f7f4084b6579d24d439453d5fc5805d46a165b542"}, - {file = "numpy-2.2.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e9e0a277bb2eb5d8a7407e14688b85fd8ad628ee4e0c7930415687b6564207a4"}, - {file = "numpy-2.2.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9eeea959168ea555e556b8188da5fa7831e21d91ce031e95ce23747b7609f8a4"}, - {file = "numpy-2.2.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:bd3ad3b0a40e713fc68f99ecfd07124195333f1e689387c180813f0e94309d6f"}, - {file = "numpy-2.2.4-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cf28633d64294969c019c6df4ff37f5698e8326db68cc2b66576a51fad634880"}, - {file = "numpy-2.2.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fa8fa7697ad1646b5c93de1719965844e004fcad23c91228aca1cf0800044a1"}, - {file = "numpy-2.2.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4162988a360a29af158aeb4a2f4f09ffed6a969c9776f8f3bdee9b06a8ab7e5"}, - {file = "numpy-2.2.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:892c10d6a73e0f14935c31229e03325a7b3093fafd6ce0af704be7f894d95687"}, - {file = "numpy-2.2.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db1f1c22173ac1c58db249ae48aa7ead29f534b9a948bc56828337aa84a32ed6"}, - {file = "numpy-2.2.4-cp311-cp311-win32.whl", hash = "sha256:ea2bb7e2ae9e37d96835b3576a4fa4b3a97592fbea8ef7c3587078b0068b8f09"}, - {file = "numpy-2.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:f7de08cbe5551911886d1ab60de58448c6df0f67d9feb7d1fb21e9875ef95e91"}, - {file = "numpy-2.2.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a7b9084668aa0f64e64bd00d27ba5146ef1c3a8835f3bd912e7a9e01326804c4"}, - {file = "numpy-2.2.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dbe512c511956b893d2dacd007d955a3f03d555ae05cfa3ff1c1ff6df8851854"}, - {file = "numpy-2.2.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:bb649f8b207ab07caebba230d851b579a3c8711a851d29efe15008e31bb4de24"}, - {file = "numpy-2.2.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:f34dc300df798742b3d06515aa2a0aee20941c13579d7a2f2e10af01ae4901ee"}, - {file = "numpy-2.2.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3f7ac96b16955634e223b579a3e5798df59007ca43e8d451a0e6a50f6bfdfba"}, - {file = "numpy-2.2.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f92084defa704deadd4e0a5ab1dc52d8ac9e8a8ef617f3fbb853e79b0ea3592"}, - {file = "numpy-2.2.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4e84a6283b36632e2a5b56e121961f6542ab886bc9e12f8f9818b3c266bfbb"}, - {file = "numpy-2.2.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:11c43995255eb4127115956495f43e9343736edb7fcdb0d973defd9de14cd84f"}, - {file = "numpy-2.2.4-cp312-cp312-win32.whl", hash = "sha256:65ef3468b53269eb5fdb3a5c09508c032b793da03251d5f8722b1194f1790c00"}, - {file = "numpy-2.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:2aad3c17ed2ff455b8eaafe06bcdae0062a1db77cb99f4b9cbb5f4ecb13c5146"}, - {file = "numpy-2.2.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cf4e5c6a278d620dee9ddeb487dc6a860f9b199eadeecc567f777daace1e9e7"}, - {file = "numpy-2.2.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1974afec0b479e50438fc3648974268f972e2d908ddb6d7fb634598cdb8260a0"}, - {file = "numpy-2.2.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:79bd5f0a02aa16808fcbc79a9a376a147cc1045f7dfe44c6e7d53fa8b8a79392"}, - {file = "numpy-2.2.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:3387dd7232804b341165cedcb90694565a6015433ee076c6754775e85d86f1fc"}, - {file = "numpy-2.2.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f527d8fdb0286fd2fd97a2a96c6be17ba4232da346931d967a0630050dfd298"}, - {file = "numpy-2.2.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bce43e386c16898b91e162e5baaad90c4b06f9dcbe36282490032cec98dc8ae7"}, - {file = "numpy-2.2.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:31504f970f563d99f71a3512d0c01a645b692b12a63630d6aafa0939e52361e6"}, - {file = "numpy-2.2.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:81413336ef121a6ba746892fad881a83351ee3e1e4011f52e97fba79233611fd"}, - {file = "numpy-2.2.4-cp313-cp313-win32.whl", hash = "sha256:f486038e44caa08dbd97275a9a35a283a8f1d2f0ee60ac260a1790e76660833c"}, - {file = "numpy-2.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:207a2b8441cc8b6a2a78c9ddc64d00d20c303d79fba08c577752f080c4007ee3"}, - {file = "numpy-2.2.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8120575cb4882318c791f839a4fd66161a6fa46f3f0a5e613071aae35b5dd8f8"}, - {file = "numpy-2.2.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a761ba0fa886a7bb33c6c8f6f20213735cb19642c580a931c625ee377ee8bd39"}, - {file = "numpy-2.2.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:ac0280f1ba4a4bfff363a99a6aceed4f8e123f8a9b234c89140f5e894e452ecd"}, - {file = "numpy-2.2.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:879cf3a9a2b53a4672a168c21375166171bc3932b7e21f622201811c43cdd3b0"}, - {file = "numpy-2.2.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f05d4198c1bacc9124018109c5fba2f3201dbe7ab6e92ff100494f236209c960"}, - {file = "numpy-2.2.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f085ce2e813a50dfd0e01fbfc0c12bbe5d2063d99f8b29da30e544fb6483b8"}, - {file = "numpy-2.2.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:92bda934a791c01d6d9d8e038363c50918ef7c40601552a58ac84c9613a665bc"}, - {file = "numpy-2.2.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ee4d528022f4c5ff67332469e10efe06a267e32f4067dc76bb7e2cddf3cd25ff"}, - {file = "numpy-2.2.4-cp313-cp313t-win32.whl", hash = "sha256:05c076d531e9998e7e694c36e8b349969c56eadd2cdcd07242958489d79a7286"}, - {file = "numpy-2.2.4-cp313-cp313t-win_amd64.whl", hash = "sha256:188dcbca89834cc2e14eb2f106c96d6d46f200fe0200310fc29089657379c58d"}, - {file = "numpy-2.2.4-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7051ee569db5fbac144335e0f3b9c2337e0c8d5c9fee015f259a5bd70772b7e8"}, - {file = "numpy-2.2.4-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:ab2939cd5bec30a7430cbdb2287b63151b77cf9624de0532d629c9a1c59b1d5c"}, - {file = "numpy-2.2.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0f35b19894a9e08639fd60a1ec1978cb7f5f7f1eace62f38dd36be8aecdef4d"}, - {file = "numpy-2.2.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b4adfbbc64014976d2f91084915ca4e626fbf2057fb81af209c1a6d776d23e3d"}, - {file = "numpy-2.2.4.tar.gz", hash = "sha256:9ba03692a45d3eef66559efe1d1096c4b9b75c0986b5dff5530c378fb8331d4f"}, + {file = "openai-1.76.0-py3-none-any.whl", hash = "sha256:a712b50e78cf78e6d7b2a8f69c4978243517c2c36999756673e07a14ce37dc0a"}, + {file = "openai-1.76.0.tar.gz", hash = "sha256:fd2bfaf4608f48102d6b74f9e11c5ecaa058b60dad9c36e409c12477dfd91fb2"}, ] +[package.dependencies] +anyio = ">=3.5.0,<5" +distro = ">=1.7.0,<2" +httpx = ">=0.23.0,<1" +jiter = ">=0.4.0,<1" +pydantic = ">=1.9.0,<3" +sniffio = "*" +tqdm = ">4" +typing-extensions = ">=4.11,<5" + +[package.extras] +datalib = ["numpy (>=1)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"] +realtime = ["websockets (>=13,<16)"] +voice-helpers = ["numpy (>=2.0.2)", "sounddevice (>=0.5.1)"] + [[package]] name = "opentelemetry-api" -version = "1.31.1" +version = "1.32.1" description = "OpenTelemetry Python API" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "opentelemetry_api-1.31.1-py3-none-any.whl", hash = "sha256:1511a3f470c9c8a32eeea68d4ea37835880c0eed09dd1a0187acc8b1301da0a1"}, - {file = "opentelemetry_api-1.31.1.tar.gz", hash = "sha256:137ad4b64215f02b3000a0292e077641c8611aab636414632a9b9068593b7e91"}, + {file = "opentelemetry_api-1.32.1-py3-none-any.whl", hash = "sha256:bbd19f14ab9f15f0e85e43e6a958aa4cb1f36870ee62b7fd205783a112012724"}, + {file = "opentelemetry_api-1.32.1.tar.gz", hash = "sha256:a5be71591694a4d9195caf6776b055aa702e964d961051a0715d05f8632c32fb"}, ] [package.dependencies] @@ -685,68 +2241,68 @@ importlib-metadata = ">=6.0,<8.7.0" [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.31.1" +version = "1.32.1" description = "OpenTelemetry Protobuf encoding" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "opentelemetry_exporter_otlp_proto_common-1.31.1-py3-none-any.whl", hash = "sha256:7cadf89dbab12e217a33c5d757e67c76dd20ce173f8203e7370c4996f2e9efd8"}, - {file = "opentelemetry_exporter_otlp_proto_common-1.31.1.tar.gz", hash = "sha256:c748e224c01f13073a2205397ba0e415dcd3be9a0f95101ba4aace5fc730e0da"}, + {file = "opentelemetry_exporter_otlp_proto_common-1.32.1-py3-none-any.whl", hash = "sha256:a1e9ad3d0d9a9405c7ff8cdb54ba9b265da16da9844fe36b8c9661114b56c5d9"}, + {file = "opentelemetry_exporter_otlp_proto_common-1.32.1.tar.gz", hash = "sha256:da4edee4f24aaef109bfe924efad3a98a2e27c91278115505b298ee61da5d68e"}, ] [package.dependencies] -opentelemetry-proto = "1.31.1" +opentelemetry-proto = "1.32.1" [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.31.1" +version = "1.32.1" description = "OpenTelemetry Collector Protobuf over HTTP Exporter" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "opentelemetry_exporter_otlp_proto_http-1.31.1-py3-none-any.whl", hash = "sha256:5dee1f051f096b13d99706a050c39b08e3f395905f29088bfe59e54218bd1cf4"}, - {file = "opentelemetry_exporter_otlp_proto_http-1.31.1.tar.gz", hash = "sha256:723bd90eb12cfb9ae24598641cb0c92ca5ba9f1762103902f6ffee3341ba048e"}, + {file = "opentelemetry_exporter_otlp_proto_http-1.32.1-py3-none-any.whl", hash = "sha256:3cc048b0c295aa2cbafb883feaf217c7525b396567eeeabb5459affb08b7fefe"}, + {file = "opentelemetry_exporter_otlp_proto_http-1.32.1.tar.gz", hash = "sha256:f854a6e7128858213850dbf1929478a802faf50e799ffd2eb4d7424390023828"}, ] [package.dependencies] deprecated = ">=1.2.6" googleapis-common-protos = ">=1.52,<2.0" opentelemetry-api = ">=1.15,<2.0" -opentelemetry-exporter-otlp-proto-common = "1.31.1" -opentelemetry-proto = "1.31.1" -opentelemetry-sdk = ">=1.31.1,<1.32.0" +opentelemetry-exporter-otlp-proto-common = "1.32.1" +opentelemetry-proto = "1.32.1" +opentelemetry-sdk = ">=1.32.1,<1.33.0" requests = ">=2.7,<3.0" [[package]] name = "opentelemetry-instrumentation" -version = "0.52b1" +version = "0.53b1" description = "Instrumentation Tools & Auto Instrumentation for OpenTelemetry Python" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation-0.52b1-py3-none-any.whl", hash = "sha256:8c0059c4379d77bbd8015c8d8476020efe873c123047ec069bb335e4b8717477"}, - {file = "opentelemetry_instrumentation-0.52b1.tar.gz", hash = "sha256:739f3bfadbbeec04dd59297479e15660a53df93c131d907bb61052e3d3c1406f"}, + {file = "opentelemetry_instrumentation-0.53b1-py3-none-any.whl", hash = "sha256:c07850cecfbc51e8b357f56d5886ae5ccaa828635b220d0f5e78f941ea9a83ca"}, + {file = "opentelemetry_instrumentation-0.53b1.tar.gz", hash = "sha256:0e69ca2c75727e8a300de671c4a2ec0e86e63a8e906beaa5d6c9f5228e8687e5"}, ] [package.dependencies] opentelemetry-api = ">=1.4,<2.0" -opentelemetry-semantic-conventions = "0.52b1" +opentelemetry-semantic-conventions = "0.53b1" packaging = ">=18.0" wrapt = ">=1.0.0,<2.0.0" [[package]] name = "opentelemetry-proto" -version = "1.31.1" +version = "1.32.1" description = "OpenTelemetry Python Proto" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "opentelemetry_proto-1.31.1-py3-none-any.whl", hash = "sha256:1398ffc6d850c2f1549ce355744e574c8cd7c1dba3eea900d630d52c41d07178"}, - {file = "opentelemetry_proto-1.31.1.tar.gz", hash = "sha256:d93e9c2b444e63d1064fb50ae035bcb09e5822274f1683886970d2734208e790"}, + {file = "opentelemetry_proto-1.32.1-py3-none-any.whl", hash = "sha256:fe56df31033ab0c40af7525f8bf4c487313377bbcfdf94184b701a8ccebc800e"}, + {file = "opentelemetry_proto-1.32.1.tar.gz", hash = "sha256:bc6385ccf87768f029371535312071a2d09e6c9ebf119ac17dbc825a6a56ba53"}, ] [package.dependencies] @@ -754,47 +2310,47 @@ protobuf = ">=5.0,<6.0" [[package]] name = "opentelemetry-sdk" -version = "1.31.1" +version = "1.32.1" description = "OpenTelemetry Python SDK" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "opentelemetry_sdk-1.31.1-py3-none-any.whl", hash = "sha256:882d021321f223e37afaca7b4e06c1d8bbc013f9e17ff48a7aa017460a8e7dae"}, - {file = "opentelemetry_sdk-1.31.1.tar.gz", hash = "sha256:c95f61e74b60769f8ff01ec6ffd3d29684743404603df34b20aa16a49dc8d903"}, + {file = "opentelemetry_sdk-1.32.1-py3-none-any.whl", hash = "sha256:bba37b70a08038613247bc42beee5a81b0ddca422c7d7f1b097b32bf1c7e2f17"}, + {file = "opentelemetry_sdk-1.32.1.tar.gz", hash = "sha256:8ef373d490961848f525255a42b193430a0637e064dd132fd2a014d94792a092"}, ] [package.dependencies] -opentelemetry-api = "1.31.1" -opentelemetry-semantic-conventions = "0.52b1" +opentelemetry-api = "1.32.1" +opentelemetry-semantic-conventions = "0.53b1" typing-extensions = ">=3.7.4" [[package]] name = "opentelemetry-semantic-conventions" -version = "0.52b1" +version = "0.53b1" description = "OpenTelemetry Semantic Conventions" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "opentelemetry_semantic_conventions-0.52b1-py3-none-any.whl", hash = "sha256:72b42db327e29ca8bb1b91e8082514ddf3bbf33f32ec088feb09526ade4bc77e"}, - {file = "opentelemetry_semantic_conventions-0.52b1.tar.gz", hash = "sha256:7b3d226ecf7523c27499758a58b542b48a0ac8d12be03c0488ff8ec60c5bae5d"}, + {file = "opentelemetry_semantic_conventions-0.53b1-py3-none-any.whl", hash = "sha256:21df3ed13f035f8f3ea42d07cbebae37020367a53b47f1ebee3b10a381a00208"}, + {file = "opentelemetry_semantic_conventions-0.53b1.tar.gz", hash = "sha256:4c5a6fede9de61211b2e9fc1e02e8acacce882204cd770177342b6a3be682992"}, ] [package.dependencies] deprecated = ">=1.2.6" -opentelemetry-api = "1.31.1" +opentelemetry-api = "1.32.1" [[package]] name = "packaging" -version = "24.2" +version = "25.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" groups = ["main", "dev"] files = [ - {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, - {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, + {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, + {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, ] [[package]] @@ -803,7 +2359,7 @@ version = "2.2.3" description = "Powerful data structures for data analysis, time series, and statistics" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "pandas-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1948ddde24197a0f7add2bdc4ca83bf2b1ef84a1bc8ccffd95eda17fd836ecb5"}, {file = "pandas-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:381175499d3802cde0eabbaf6324cce0c4f5d52ca6f8c377c29ad442f50f6348"}, @@ -884,22 +2440,136 @@ sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-d test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] xml = ["lxml (>=4.9.2)"] +[[package]] +name = "pandas-stubs" +version = "2.2.3.250308" +description = "Type annotations for pandas" +optional = false +python-versions = ">=3.10" +groups = ["dev"] +files = [ + {file = "pandas_stubs-2.2.3.250308-py3-none-any.whl", hash = "sha256:a377edff3b61f8b268c82499fdbe7c00fdeed13235b8b71d6a1dc347aeddc74d"}, + {file = "pandas_stubs-2.2.3.250308.tar.gz", hash = "sha256:3a6e9daf161f00b85c83772ed3d5cff9522028f07a94817472c07b91f46710fd"}, +] + +[package.dependencies] +numpy = ">=1.23.5" +types-pytz = ">=2022.1.1" + +[[package]] +name = "pillow" +version = "10.4.0" +description = "Python Imaging Library (Fork)" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e"}, + {file = "pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc"}, + {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e"}, + {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46"}, + {file = "pillow-10.4.0-cp310-cp310-win32.whl", hash = "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984"}, + {file = "pillow-10.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141"}, + {file = "pillow-10.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1"}, + {file = "pillow-10.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c"}, + {file = "pillow-10.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319"}, + {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d"}, + {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696"}, + {file = "pillow-10.4.0-cp311-cp311-win32.whl", hash = "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496"}, + {file = "pillow-10.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91"}, + {file = "pillow-10.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22"}, + {file = "pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94"}, + {file = "pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a"}, + {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b"}, + {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9"}, + {file = "pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42"}, + {file = "pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a"}, + {file = "pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9"}, + {file = "pillow-10.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3"}, + {file = "pillow-10.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc"}, + {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a"}, + {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309"}, + {file = "pillow-10.4.0-cp313-cp313-win32.whl", hash = "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060"}, + {file = "pillow-10.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea"}, + {file = "pillow-10.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d"}, + {file = "pillow-10.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8d4d5063501b6dd4024b8ac2f04962d661222d120381272deea52e3fc52d3736"}, + {file = "pillow-10.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c1ee6f42250df403c5f103cbd2768a28fe1a0ea1f0f03fe151c8741e1469c8b"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15e02e9bb4c21e39876698abf233c8c579127986f8207200bc8a8f6bb27acf2"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a8d4bade9952ea9a77d0c3e49cbd8b2890a399422258a77f357b9cc9be8d680"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:43efea75eb06b95d1631cb784aa40156177bf9dd5b4b03ff38979e048258bc6b"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:950be4d8ba92aca4b2bb0741285a46bfae3ca699ef913ec8416c1b78eadd64cd"}, + {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d7480af14364494365e89d6fddc510a13e5a2c3584cb19ef65415ca57252fb84"}, + {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:73664fe514b34c8f02452ffb73b7a92c6774e39a647087f83d67f010eb9a0cf0"}, + {file = "pillow-10.4.0-cp38-cp38-win32.whl", hash = "sha256:e88d5e6ad0d026fba7bdab8c3f225a69f063f116462c49892b0149e21b6c0a0e"}, + {file = "pillow-10.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:5161eef006d335e46895297f642341111945e2c1c899eb406882a6c61a4357ab"}, + {file = "pillow-10.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d"}, + {file = "pillow-10.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c"}, + {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1"}, + {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df"}, + {file = "pillow-10.4.0-cp39-cp39-win32.whl", hash = "sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef"}, + {file = "pillow-10.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5"}, + {file = "pillow-10.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3"}, + {file = "pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06"}, +] + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=7.3)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] +fpx = ["olefile"] +mic = ["olefile"] +tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] +typing = ["typing-extensions ; python_version < \"3.10\""] +xmp = ["defusedxml"] + [[package]] name = "platformdirs" -version = "4.3.6" +version = "4.3.7" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, - {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, + {file = "platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94"}, + {file = "platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351"}, ] [package.extras] -docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] -type = ["mypy (>=1.11.2)"] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.1.3)", "sphinx-autodoc-typehints (>=3)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.4)", "pytest-cov (>=6)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.14.1)"] [[package]] name = "pluggy" @@ -917,16 +2587,28 @@ files = [ dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] +[[package]] +name = "ply" +version = "3.11" +description = "Python Lex & Yacc" +optional = false +python-versions = "*" +groups = ["dev"] +files = [ + {file = "ply-3.11-py2.py3-none-any.whl", hash = "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce"}, + {file = "ply-3.11.tar.gz", hash = "sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3"}, +] + [[package]] name = "pre-commit" -version = "4.2.0" +version = "3.8.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "pre_commit-4.2.0-py2.py3-none-any.whl", hash = "sha256:a009ca7205f1eb497d10b845e52c838a98b6cdd2102a6c8e4540e94ee75c58bd"}, - {file = "pre_commit-4.2.0.tar.gz", hash = "sha256:601283b9757afd87d40c4c4a9b2b5de9637a8ea02eaff7adc2d0fb4e04841146"}, + {file = "pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f"}, + {file = "pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af"}, ] [package.dependencies] @@ -936,6 +2618,129 @@ nodeenv = ">=0.11.1" pyyaml = ">=5.1" virtualenv = ">=20.10.0" +[[package]] +name = "proglog" +version = "0.1.11" +description = "Log and progress bar manager for console, notebooks, web..." +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "proglog-0.1.11-py3-none-any.whl", hash = "sha256:1729b829e1e609a3f340d6659fbde401cace9e2feab65647ceaf52ecfccf362d"}, + {file = "proglog-0.1.11.tar.gz", hash = "sha256:ce35a0f9d1153e69d0063cdae6e6f2d8708fa0a588fc4e089501b77005e72884"}, +] + +[package.dependencies] +tqdm = "*" + +[[package]] +name = "propcache" +version = "0.3.1" +description = "Accelerated property cache" +optional = false +python-versions = ">=3.9" +groups = ["main", "dev"] +files = [ + {file = "propcache-0.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f27785888d2fdd918bc36de8b8739f2d6c791399552333721b58193f68ea3e98"}, + {file = "propcache-0.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4e89cde74154c7b5957f87a355bb9c8ec929c167b59c83d90654ea36aeb6180"}, + {file = "propcache-0.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:730178f476ef03d3d4d255f0c9fa186cb1d13fd33ffe89d39f2cda4da90ceb71"}, + {file = "propcache-0.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:967a8eec513dbe08330f10137eacb427b2ca52118769e82ebcfcab0fba92a649"}, + {file = "propcache-0.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b9145c35cc87313b5fd480144f8078716007656093d23059e8993d3a8fa730f"}, + {file = "propcache-0.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e64e948ab41411958670f1093c0a57acfdc3bee5cf5b935671bbd5313bcf229"}, + {file = "propcache-0.3.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:319fa8765bfd6a265e5fa661547556da381e53274bc05094fc9ea50da51bfd46"}, + {file = "propcache-0.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c66d8ccbc902ad548312b96ed8d5d266d0d2c6d006fd0f66323e9d8f2dd49be7"}, + {file = "propcache-0.3.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2d219b0dbabe75e15e581fc1ae796109b07c8ba7d25b9ae8d650da582bed01b0"}, + {file = "propcache-0.3.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:cd6a55f65241c551eb53f8cf4d2f4af33512c39da5d9777694e9d9c60872f519"}, + {file = "propcache-0.3.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9979643ffc69b799d50d3a7b72b5164a2e97e117009d7af6dfdd2ab906cb72cd"}, + {file = "propcache-0.3.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4cf9e93a81979f1424f1a3d155213dc928f1069d697e4353edb8a5eba67c6259"}, + {file = "propcache-0.3.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2fce1df66915909ff6c824bbb5eb403d2d15f98f1518e583074671a30fe0c21e"}, + {file = "propcache-0.3.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:4d0dfdd9a2ebc77b869a0b04423591ea8823f791293b527dc1bb896c1d6f1136"}, + {file = "propcache-0.3.1-cp310-cp310-win32.whl", hash = "sha256:1f6cc0ad7b4560e5637eb2c994e97b4fa41ba8226069c9277eb5ea7101845b42"}, + {file = "propcache-0.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:47ef24aa6511e388e9894ec16f0fbf3313a53ee68402bc428744a367ec55b833"}, + {file = "propcache-0.3.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7f30241577d2fef2602113b70ef7231bf4c69a97e04693bde08ddab913ba0ce5"}, + {file = "propcache-0.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:43593c6772aa12abc3af7784bff4a41ffa921608dd38b77cf1dfd7f5c4e71371"}, + {file = "propcache-0.3.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a75801768bbe65499495660b777e018cbe90c7980f07f8aa57d6be79ea6f71da"}, + {file = "propcache-0.3.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6f1324db48f001c2ca26a25fa25af60711e09b9aaf4b28488602776f4f9a744"}, + {file = "propcache-0.3.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cdb0f3e1eb6dfc9965d19734d8f9c481b294b5274337a8cb5cb01b462dcb7e0"}, + {file = "propcache-0.3.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1eb34d90aac9bfbced9a58b266f8946cb5935869ff01b164573a7634d39fbcb5"}, + {file = "propcache-0.3.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f35c7070eeec2cdaac6fd3fe245226ed2a6292d3ee8c938e5bb645b434c5f256"}, + {file = "propcache-0.3.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b23c11c2c9e6d4e7300c92e022046ad09b91fd00e36e83c44483df4afa990073"}, + {file = "propcache-0.3.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3e19ea4ea0bf46179f8a3652ac1426e6dcbaf577ce4b4f65be581e237340420d"}, + {file = "propcache-0.3.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:bd39c92e4c8f6cbf5f08257d6360123af72af9f4da75a690bef50da77362d25f"}, + {file = "propcache-0.3.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b0313e8b923b3814d1c4a524c93dfecea5f39fa95601f6a9b1ac96cd66f89ea0"}, + {file = "propcache-0.3.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e861ad82892408487be144906a368ddbe2dc6297074ade2d892341b35c59844a"}, + {file = "propcache-0.3.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:61014615c1274df8da5991a1e5da85a3ccb00c2d4701ac6f3383afd3ca47ab0a"}, + {file = "propcache-0.3.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:71ebe3fe42656a2328ab08933d420df5f3ab121772eef78f2dc63624157f0ed9"}, + {file = "propcache-0.3.1-cp311-cp311-win32.whl", hash = "sha256:58aa11f4ca8b60113d4b8e32d37e7e78bd8af4d1a5b5cb4979ed856a45e62005"}, + {file = "propcache-0.3.1-cp311-cp311-win_amd64.whl", hash = "sha256:9532ea0b26a401264b1365146c440a6d78269ed41f83f23818d4b79497aeabe7"}, + {file = "propcache-0.3.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f78eb8422acc93d7b69964012ad7048764bb45a54ba7a39bb9e146c72ea29723"}, + {file = "propcache-0.3.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:89498dd49c2f9a026ee057965cdf8192e5ae070ce7d7a7bd4b66a8e257d0c976"}, + {file = "propcache-0.3.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:09400e98545c998d57d10035ff623266927cb784d13dd2b31fd33b8a5316b85b"}, + {file = "propcache-0.3.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa8efd8c5adc5a2c9d3b952815ff8f7710cefdcaf5f2c36d26aff51aeca2f12f"}, + {file = "propcache-0.3.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2fe5c910f6007e716a06d269608d307b4f36e7babee5f36533722660e8c4a70"}, + {file = "propcache-0.3.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a0ab8cf8cdd2194f8ff979a43ab43049b1df0b37aa64ab7eca04ac14429baeb7"}, + {file = "propcache-0.3.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:563f9d8c03ad645597b8d010ef4e9eab359faeb11a0a2ac9f7b4bc8c28ebef25"}, + {file = "propcache-0.3.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb6e0faf8cb6b4beea5d6ed7b5a578254c6d7df54c36ccd3d8b3eb00d6770277"}, + {file = "propcache-0.3.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1c5c7ab7f2bb3f573d1cb921993006ba2d39e8621019dffb1c5bc94cdbae81e8"}, + {file = "propcache-0.3.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:050b571b2e96ec942898f8eb46ea4bfbb19bd5502424747e83badc2d4a99a44e"}, + {file = "propcache-0.3.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e1c4d24b804b3a87e9350f79e2371a705a188d292fd310e663483af6ee6718ee"}, + {file = "propcache-0.3.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e4fe2a6d5ce975c117a6bb1e8ccda772d1e7029c1cca1acd209f91d30fa72815"}, + {file = "propcache-0.3.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:feccd282de1f6322f56f6845bf1207a537227812f0a9bf5571df52bb418d79d5"}, + {file = "propcache-0.3.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ec314cde7314d2dd0510c6787326bbffcbdc317ecee6b7401ce218b3099075a7"}, + {file = "propcache-0.3.1-cp312-cp312-win32.whl", hash = "sha256:7d2d5a0028d920738372630870e7d9644ce437142197f8c827194fca404bf03b"}, + {file = "propcache-0.3.1-cp312-cp312-win_amd64.whl", hash = "sha256:88c423efef9d7a59dae0614eaed718449c09a5ac79a5f224a8b9664d603f04a3"}, + {file = "propcache-0.3.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:f1528ec4374617a7a753f90f20e2f551121bb558fcb35926f99e3c42367164b8"}, + {file = "propcache-0.3.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dc1915ec523b3b494933b5424980831b636fe483d7d543f7afb7b3bf00f0c10f"}, + {file = "propcache-0.3.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a110205022d077da24e60b3df8bcee73971be9575dec5573dd17ae5d81751111"}, + {file = "propcache-0.3.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d249609e547c04d190e820d0d4c8ca03ed4582bcf8e4e160a6969ddfb57b62e5"}, + {file = "propcache-0.3.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ced33d827625d0a589e831126ccb4f5c29dfdf6766cac441d23995a65825dcb"}, + {file = "propcache-0.3.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4114c4ada8f3181af20808bedb250da6bae56660e4b8dfd9cd95d4549c0962f7"}, + {file = "propcache-0.3.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:975af16f406ce48f1333ec5e912fe11064605d5c5b3f6746969077cc3adeb120"}, + {file = "propcache-0.3.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a34aa3a1abc50740be6ac0ab9d594e274f59960d3ad253cd318af76b996dd654"}, + {file = "propcache-0.3.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9cec3239c85ed15bfaded997773fdad9fb5662b0a7cbc854a43f291eb183179e"}, + {file = "propcache-0.3.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:05543250deac8e61084234d5fc54f8ebd254e8f2b39a16b1dce48904f45b744b"}, + {file = "propcache-0.3.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5cb5918253912e088edbf023788de539219718d3b10aef334476b62d2b53de53"}, + {file = "propcache-0.3.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f3bbecd2f34d0e6d3c543fdb3b15d6b60dd69970c2b4c822379e5ec8f6f621d5"}, + {file = "propcache-0.3.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aca63103895c7d960a5b9b044a83f544b233c95e0dcff114389d64d762017af7"}, + {file = "propcache-0.3.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a0a9898fdb99bf11786265468571e628ba60af80dc3f6eb89a3545540c6b0ef"}, + {file = "propcache-0.3.1-cp313-cp313-win32.whl", hash = "sha256:3a02a28095b5e63128bcae98eb59025924f121f048a62393db682f049bf4ac24"}, + {file = "propcache-0.3.1-cp313-cp313-win_amd64.whl", hash = "sha256:813fbb8b6aea2fc9659815e585e548fe706d6f663fa73dff59a1677d4595a037"}, + {file = "propcache-0.3.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:a444192f20f5ce8a5e52761a031b90f5ea6288b1eef42ad4c7e64fef33540b8f"}, + {file = "propcache-0.3.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0fbe94666e62ebe36cd652f5fc012abfbc2342de99b523f8267a678e4dfdee3c"}, + {file = "propcache-0.3.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f011f104db880f4e2166bcdcf7f58250f7a465bc6b068dc84c824a3d4a5c94dc"}, + {file = "propcache-0.3.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e584b6d388aeb0001d6d5c2bd86b26304adde6d9bb9bfa9c4889805021b96de"}, + {file = "propcache-0.3.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a17583515a04358b034e241f952f1715243482fc2c2945fd99a1b03a0bd77d6"}, + {file = "propcache-0.3.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5aed8d8308215089c0734a2af4f2e95eeb360660184ad3912686c181e500b2e7"}, + {file = "propcache-0.3.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d8e309ff9a0503ef70dc9a0ebd3e69cf7b3894c9ae2ae81fc10943c37762458"}, + {file = "propcache-0.3.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b655032b202028a582d27aeedc2e813299f82cb232f969f87a4fde491a233f11"}, + {file = "propcache-0.3.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f64d91b751df77931336b5ff7bafbe8845c5770b06630e27acd5dbb71e1931c"}, + {file = "propcache-0.3.1-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:19a06db789a4bd896ee91ebc50d059e23b3639c25d58eb35be3ca1cbe967c3bf"}, + {file = "propcache-0.3.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:bef100c88d8692864651b5f98e871fb090bd65c8a41a1cb0ff2322db39c96c27"}, + {file = "propcache-0.3.1-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:87380fb1f3089d2a0b8b00f006ed12bd41bd858fabfa7330c954c70f50ed8757"}, + {file = "propcache-0.3.1-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:e474fc718e73ba5ec5180358aa07f6aded0ff5f2abe700e3115c37d75c947e18"}, + {file = "propcache-0.3.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:17d1c688a443355234f3c031349da69444be052613483f3e4158eef751abcd8a"}, + {file = "propcache-0.3.1-cp313-cp313t-win32.whl", hash = "sha256:359e81a949a7619802eb601d66d37072b79b79c2505e6d3fd8b945538411400d"}, + {file = "propcache-0.3.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e7fb9a84c9abbf2b2683fa3e7b0d7da4d8ecf139a1c635732a8bda29c5214b0e"}, + {file = "propcache-0.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ed5f6d2edbf349bd8d630e81f474d33d6ae5d07760c44d33cd808e2f5c8f4ae6"}, + {file = "propcache-0.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:668ddddc9f3075af019f784456267eb504cb77c2c4bd46cc8402d723b4d200bf"}, + {file = "propcache-0.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0c86e7ceea56376216eba345aa1fc6a8a6b27ac236181f840d1d7e6a1ea9ba5c"}, + {file = "propcache-0.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83be47aa4e35b87c106fc0c84c0fc069d3f9b9b06d3c494cd404ec6747544894"}, + {file = "propcache-0.3.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:27c6ac6aa9fc7bc662f594ef380707494cb42c22786a558d95fcdedb9aa5d035"}, + {file = "propcache-0.3.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64a956dff37080b352c1c40b2966b09defb014347043e740d420ca1eb7c9b908"}, + {file = "propcache-0.3.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82de5da8c8893056603ac2d6a89eb8b4df49abf1a7c19d536984c8dd63f481d5"}, + {file = "propcache-0.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c3c3a203c375b08fd06a20da3cf7aac293b834b6f4f4db71190e8422750cca5"}, + {file = "propcache-0.3.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b303b194c2e6f171cfddf8b8ba30baefccf03d36a4d9cab7fd0bb68ba476a3d7"}, + {file = "propcache-0.3.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:916cd229b0150129d645ec51614d38129ee74c03293a9f3f17537be0029a9641"}, + {file = "propcache-0.3.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:a461959ead5b38e2581998700b26346b78cd98540b5524796c175722f18b0294"}, + {file = "propcache-0.3.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:069e7212890b0bcf9b2be0a03afb0c2d5161d91e1bf51569a64f629acc7defbf"}, + {file = "propcache-0.3.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ef2e4e91fb3945769e14ce82ed53007195e616a63aa43b40fb7ebaaf907c8d4c"}, + {file = "propcache-0.3.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:8638f99dca15b9dff328fb6273e09f03d1c50d9b6512f3b65a4154588a7595fe"}, + {file = "propcache-0.3.1-cp39-cp39-win32.whl", hash = "sha256:6f173bbfe976105aaa890b712d1759de339d8a7cef2fc0a1714cc1a1e1c47f64"}, + {file = "propcache-0.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:603f1fe4144420374f1a69b907494c3acbc867a581c2d49d4175b0de7cc64566"}, + {file = "propcache-0.3.1-py3-none-any.whl", hash = "sha256:9a8ecf38de50a7f518c21568c80f985e776397b902f1ce0b01f799aba1608b40"}, + {file = "propcache-0.3.1.tar.gz", hash = "sha256:40d980c33765359098837527e18eddefc9a24cea5b45e078a7f3bb5b032c6ecf"}, +] + [[package]] name = "protobuf" version = "5.29.4" @@ -1018,7 +2823,7 @@ version = "2.11.3" description = "Data validation using Python type hints" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "pydantic-2.11.3-py3-none-any.whl", hash = "sha256:a082753436a07f9ba1289c6ffa01cd93db3548776088aa917cc43b63f68fa60f"}, {file = "pydantic-2.11.3.tar.gz", hash = "sha256:7471657138c16adad9322fe3070c0116dd6c3ad8d649300e3cbdfe91f4db4ec3"}, @@ -1040,7 +2845,7 @@ version = "2.33.1" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "pydantic_core-2.33.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3077cfdb6125cc8dab61b155fdd714663e401f0e6883f9632118ec12cf42df26"}, {file = "pydantic_core-2.33.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8ffab8b2908d152e74862d276cf5017c81a2f3719f14e8e3e8d6b83fda863927"}, @@ -1146,13 +2951,45 @@ files = [ [package.dependencies] typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" +[[package]] +name = "pydantic-xml" +version = "2.16.0" +description = "pydantic xml extension" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "pydantic_xml-2.16.0-py3-none-any.whl", hash = "sha256:e1ecd513287e30070ce0a9f8c0e461187ebf5b18da79ca62f5dd4219fb93b68e"}, + {file = "pydantic_xml-2.16.0.tar.gz", hash = "sha256:64ae5d8538a23706471f0b2007c9252ef290dff40c216dbc3051c79030aaf03f"}, +] + +[package.dependencies] +pydantic = ">=2.6.0,<2.10.0b1 || >2.10.0b1" +pydantic-core = ">=2.15.0" + +[package.extras] +docs = ["Sphinx (>=5.3.0,<6.0.0)", "furo (>=2022.12.7,<2023.0.0)", "sphinx-copybutton (>=0.5.1,<0.6.0)", "sphinx_design (>=0.3.0,<0.4.0)", "toml (>=0.10.2,<0.11.0)"] +lxml = ["lxml (>=4.9.0)"] + +[[package]] +name = "pydub" +version = "0.25.1" +description = "Manipulate audio with an simple and easy high level interface" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "pydub-0.25.1-py2.py3-none-any.whl", hash = "sha256:65617e33033874b59d87db603aa1ed450633288aefead953b30bded59cb599a6"}, + {file = "pydub-0.25.1.tar.gz", hash = "sha256:980a33ce9949cab2a569606b65674d748ecbca4f0796887fd6f46173a7b0d30f"}, +] + [[package]] name = "pygments" version = "2.19.1" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, @@ -1186,21 +3023,21 @@ dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments [[package]] name = "pytest-asyncio" -version = "0.26.0" +version = "0.24.0" description = "Pytest support for asyncio" optional = false -python-versions = ">=3.9" +python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "pytest_asyncio-0.26.0-py3-none-any.whl", hash = "sha256:7b51ed894f4fbea1340262bdae5135797ebbe21d8638978e35d31c6d19f72fb0"}, - {file = "pytest_asyncio-0.26.0.tar.gz", hash = "sha256:c4df2a697648241ff39e7f0e4a73050b03f123f760673956cf0d72a4990e312f"}, + {file = "pytest_asyncio-0.24.0-py3-none-any.whl", hash = "sha256:a811296ed596b69bf0b6f3dc40f83bcaf341b155a269052d82efa2b25ac7037b"}, + {file = "pytest_asyncio-0.24.0.tar.gz", hash = "sha256:d081d828e576d85f875399194281e92bf8a68d60d72d1a2faf2feddb6c46b276"}, ] [package.dependencies] pytest = ">=8.2,<9" [package.extras] -docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1)"] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] [[package]] @@ -1209,7 +3046,7 @@ version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -1218,6 +3055,21 @@ files = [ [package.dependencies] six = ">=1.5" +[[package]] +name = "python-dotenv" +version = "1.1.0" +description = "Read key-value pairs from a .env file and set them as environment variables" +optional = false +python-versions = ">=3.9" +groups = ["main", "dev"] +files = [ + {file = "python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d"}, + {file = "python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + [[package]] name = "python-ulid" version = "3.0.0" @@ -1239,7 +3091,7 @@ version = "2025.2" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00"}, {file = "pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3"}, @@ -1251,7 +3103,7 @@ version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" -groups = ["dev"] +groups = ["main", "dev"] files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, @@ -1308,6 +3160,127 @@ files = [ {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, ] +[[package]] +name = "referencing" +version = "0.36.2" +description = "JSON Referencing + Python" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0"}, + {file = "referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +rpds-py = ">=0.7.0" +typing-extensions = {version = ">=4.4.0", markers = "python_version < \"3.13\""} + +[[package]] +name = "regex" +version = "2024.11.6" +description = "Alternative regular expression module, to replace re." +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ff590880083d60acc0433f9c3f713c51f7ac6ebb9adf889c79a261ecf541aa91"}, + {file = "regex-2024.11.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:658f90550f38270639e83ce492f27d2c8d2cd63805c65a13a14d36ca126753f0"}, + {file = "regex-2024.11.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:164d8b7b3b4bcb2068b97428060b2a53be050085ef94eca7f240e7947f1b080e"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3660c82f209655a06b587d55e723f0b813d3a7db2e32e5e7dc64ac2a9e86fde"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d22326fcdef5e08c154280b71163ced384b428343ae16a5ab2b3354aed12436e"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ac758ef6aebfc8943560194e9fd0fa18bcb34d89fd8bd2af18183afd8da3a2"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:997d6a487ff00807ba810e0f8332c18b4eb8d29463cfb7c820dc4b6e7562d0cf"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:02a02d2bb04fec86ad61f3ea7f49c015a0681bf76abb9857f945d26159d2968c"}, + {file = "regex-2024.11.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f02f93b92358ee3f78660e43b4b0091229260c5d5c408d17d60bf26b6c900e86"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06eb1be98df10e81ebaded73fcd51989dcf534e3c753466e4b60c4697a003b67"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:040df6fe1a5504eb0f04f048e6d09cd7c7110fef851d7c567a6b6e09942feb7d"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabbfc59f2c6edba2a6622c647b716e34e8e3867e0ab975412c5c2f79b82da2"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8447d2d39b5abe381419319f942de20b7ecd60ce86f16a23b0698f22e1b70008"}, + {file = "regex-2024.11.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:da8f5fc57d1933de22a9e23eec290a0d8a5927a5370d24bda9a6abe50683fe62"}, + {file = "regex-2024.11.6-cp310-cp310-win32.whl", hash = "sha256:b489578720afb782f6ccf2840920f3a32e31ba28a4b162e13900c3e6bd3f930e"}, + {file = "regex-2024.11.6-cp310-cp310-win_amd64.whl", hash = "sha256:5071b2093e793357c9d8b2929dfc13ac5f0a6c650559503bb81189d0a3814519"}, + {file = "regex-2024.11.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5478c6962ad548b54a591778e93cd7c456a7a29f8eca9c49e4f9a806dcc5d638"}, + {file = "regex-2024.11.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c89a8cc122b25ce6945f0423dc1352cb9593c68abd19223eebbd4e56612c5b7"}, + {file = "regex-2024.11.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94d87b689cdd831934fa3ce16cc15cd65748e6d689f5d2b8f4f4df2065c9fa20"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1062b39a0a2b75a9c694f7a08e7183a80c63c0d62b301418ffd9c35f55aaa114"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:167ed4852351d8a750da48712c3930b031f6efdaa0f22fa1933716bfcd6bf4a3"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d548dafee61f06ebdb584080621f3e0c23fff312f0de1afc776e2a2ba99a74f"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2a19f302cd1ce5dd01a9099aaa19cae6173306d1302a43b627f62e21cf18ac0"}, + {file = "regex-2024.11.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bec9931dfb61ddd8ef2ebc05646293812cb6b16b60cf7c9511a832b6f1854b55"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9714398225f299aa85267fd222f7142fcb5c769e73d7733344efc46f2ef5cf89"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:202eb32e89f60fc147a41e55cb086db2a3f8cb82f9a9a88440dcfc5d37faae8d"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:4181b814e56078e9b00427ca358ec44333765f5ca1b45597ec7446d3a1ef6e34"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:068376da5a7e4da51968ce4c122a7cd31afaaec4fccc7856c92f63876e57b51d"}, + {file = "regex-2024.11.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f2c4184420d881a3475fb2c6f4d95d53a8d50209a2500723d831036f7c45"}, + {file = "regex-2024.11.6-cp311-cp311-win32.whl", hash = "sha256:c36f9b6f5f8649bb251a5f3f66564438977b7ef8386a52460ae77e6070d309d9"}, + {file = "regex-2024.11.6-cp311-cp311-win_amd64.whl", hash = "sha256:02e28184be537f0e75c1f9b2f8847dc51e08e6e171c6bde130b2687e0c33cf60"}, + {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:52fb28f528778f184f870b7cf8f225f5eef0a8f6e3778529bdd40c7b3920796a"}, + {file = "regex-2024.11.6-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fdd6028445d2460f33136c55eeb1f601ab06d74cb3347132e1c24250187500d9"}, + {file = "regex-2024.11.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:805e6b60c54bf766b251e94526ebad60b7de0c70f70a4e6210ee2891acb70bf2"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b85c2530be953a890eaffde05485238f07029600e8f098cdf1848d414a8b45e4"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bb26437975da7dc36b7efad18aa9dd4ea569d2357ae6b783bf1118dabd9ea577"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abfa5080c374a76a251ba60683242bc17eeb2c9818d0d30117b4486be10c59d3"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b7fa6606c2881c1db9479b0eaa11ed5dfa11c8d60a474ff0e095099f39d98e"}, + {file = "regex-2024.11.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c32f75920cf99fe6b6c539c399a4a128452eaf1af27f39bce8909c9a3fd8cbe"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:982e6d21414e78e1f51cf595d7f321dcd14de1f2881c5dc6a6e23bbbbd68435e"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:a7c2155f790e2fb448faed6dd241386719802296ec588a8b9051c1f5c481bc29"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149f5008d286636e48cd0b1dd65018548944e495b0265b45e1bffecce1ef7f39"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:e5364a4502efca094731680e80009632ad6624084aff9a23ce8c8c6820de3e51"}, + {file = "regex-2024.11.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0a86e7eeca091c09e021db8eb72d54751e527fa47b8d5787caf96d9831bd02ad"}, + {file = "regex-2024.11.6-cp312-cp312-win32.whl", hash = "sha256:32f9a4c643baad4efa81d549c2aadefaeba12249b2adc5af541759237eee1c54"}, + {file = "regex-2024.11.6-cp312-cp312-win_amd64.whl", hash = "sha256:a93c194e2df18f7d264092dc8539b8ffb86b45b899ab976aa15d48214138e81b"}, + {file = "regex-2024.11.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a6ba92c0bcdf96cbf43a12c717eae4bc98325ca3730f6b130ffa2e3c3c723d84"}, + {file = "regex-2024.11.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:525eab0b789891ac3be914d36893bdf972d483fe66551f79d3e27146191a37d4"}, + {file = "regex-2024.11.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:086a27a0b4ca227941700e0b31425e7a28ef1ae8e5e05a33826e17e47fbfdba0"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bde01f35767c4a7899b7eb6e823b125a64de314a8ee9791367c9a34d56af18d0"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b583904576650166b3d920d2bcce13971f6f9e9a396c673187f49811b2769dc7"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1c4de13f06a0d54fa0d5ab1b7138bfa0d883220965a29616e3ea61b35d5f5fc7"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cde6e9f2580eb1665965ce9bf17ff4952f34f5b126beb509fee8f4e994f143c"}, + {file = "regex-2024.11.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d7f453dca13f40a02b79636a339c5b62b670141e63efd511d3f8f73fba162b3"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:59dfe1ed21aea057a65c6b586afd2a945de04fc7db3de0a6e3ed5397ad491b07"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b97c1e0bd37c5cd7902e65f410779d39eeda155800b65fc4d04cc432efa9bc6e"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d1e379028e0fc2ae3654bac3cbbef81bf3fd571272a42d56c24007979bafb6"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:13291b39131e2d002a7940fb176e120bec5145f3aeb7621be6534e46251912c4"}, + {file = "regex-2024.11.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f51f88c126370dcec4908576c5a627220da6c09d0bff31cfa89f2523843316d"}, + {file = "regex-2024.11.6-cp313-cp313-win32.whl", hash = "sha256:63b13cfd72e9601125027202cad74995ab26921d8cd935c25f09c630436348ff"}, + {file = "regex-2024.11.6-cp313-cp313-win_amd64.whl", hash = "sha256:2b3361af3198667e99927da8b84c1b010752fa4b1115ee30beaa332cabc3ef1a"}, + {file = "regex-2024.11.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3a51ccc315653ba012774efca4f23d1d2a8a8f278a6072e29c7147eee7da446b"}, + {file = "regex-2024.11.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ad182d02e40de7459b73155deb8996bbd8e96852267879396fb274e8700190e3"}, + {file = "regex-2024.11.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ba9b72e5643641b7d41fa1f6d5abda2c9a263ae835b917348fc3c928182ad467"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40291b1b89ca6ad8d3f2b82782cc33807f1406cf68c8d440861da6304d8ffbbd"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cdf58d0e516ee426a48f7b2c03a332a4114420716d55769ff7108c37a09951bf"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a36fdf2af13c2b14738f6e973aba563623cb77d753bbbd8d414d18bfaa3105dd"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1cee317bfc014c2419a76bcc87f071405e3966da434e03e13beb45f8aced1a6"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50153825ee016b91549962f970d6a4442fa106832e14c918acd1c8e479916c4f"}, + {file = "regex-2024.11.6-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ea1bfda2f7162605f6e8178223576856b3d791109f15ea99a9f95c16a7636fb5"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:df951c5f4a1b1910f1a99ff42c473ff60f8225baa1cdd3539fe2819d9543e9df"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:072623554418a9911446278f16ecb398fb3b540147a7828c06e2011fa531e773"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f654882311409afb1d780b940234208a252322c24a93b442ca714d119e68086c"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:89d75e7293d2b3e674db7d4d9b1bee7f8f3d1609428e293771d1a962617150cc"}, + {file = "regex-2024.11.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:f65557897fc977a44ab205ea871b690adaef6b9da6afda4790a2484b04293a5f"}, + {file = "regex-2024.11.6-cp38-cp38-win32.whl", hash = "sha256:6f44ec28b1f858c98d3036ad5d7d0bfc568bdd7a74f9c24e25f41ef1ebfd81a4"}, + {file = "regex-2024.11.6-cp38-cp38-win_amd64.whl", hash = "sha256:bb8f74f2f10dbf13a0be8de623ba4f9491faf58c24064f32b65679b021ed0001"}, + {file = "regex-2024.11.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5704e174f8ccab2026bd2f1ab6c510345ae8eac818b613d7d73e785f1310f839"}, + {file = "regex-2024.11.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:220902c3c5cc6af55d4fe19ead504de80eb91f786dc102fbd74894b1551f095e"}, + {file = "regex-2024.11.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5e7e351589da0850c125f1600a4c4ba3c722efefe16b297de54300f08d734fbf"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5056b185ca113c88e18223183aa1a50e66507769c9640a6ff75859619d73957b"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e34b51b650b23ed3354b5a07aab37034d9f923db2a40519139af34f485f77d0"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5670bce7b200273eee1840ef307bfa07cda90b38ae56e9a6ebcc9f50da9c469b"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08986dce1339bc932923e7d1232ce9881499a0e02925f7402fb7c982515419ef"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93c0b12d3d3bc25af4ebbf38f9ee780a487e8bf6954c115b9f015822d3bb8e48"}, + {file = "regex-2024.11.6-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:764e71f22ab3b305e7f4c21f1a97e1526a25ebdd22513e251cf376760213da13"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f056bf21105c2515c32372bbc057f43eb02aae2fda61052e2f7622c801f0b4e2"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:69ab78f848845569401469da20df3e081e6b5a11cb086de3eed1d48f5ed57c95"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:86fddba590aad9208e2fa8b43b4c098bb0ec74f15718bb6a704e3c63e2cef3e9"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:684d7a212682996d21ca12ef3c17353c021fe9de6049e19ac8481ec35574a70f"}, + {file = "regex-2024.11.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a03e02f48cd1abbd9f3b7e3586d97c8f7a9721c436f51a5245b3b9483044480b"}, + {file = "regex-2024.11.6-cp39-cp39-win32.whl", hash = "sha256:41758407fc32d5c3c5de163888068cfee69cb4c2be844e7ac517a52770f9af57"}, + {file = "regex-2024.11.6-cp39-cp39-win_amd64.whl", hash = "sha256:b2837718570f95dd41675328e111345f9b7095d821bac435aac173ac80b19983"}, + {file = "regex-2024.11.6.tar.gz", hash = "sha256:7ab159b063c52a0333c884e4679f8d7a85112ee3078fe3d9004b2dd875585519"}, +] + [[package]] name = "requests" version = "2.32.3" @@ -1336,7 +3309,7 @@ version = "14.0.0" description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" optional = false python-versions = ">=3.8.0" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "rich-14.0.0-py3-none-any.whl", hash = "sha256:1c9491e1951aac09caffd42f448ee3d04e58923ffe14993f6e83068dc395d7e0"}, {file = "rich-14.0.0.tar.gz", hash = "sha256:82f1bc23a6a21ebca4ae0c45af9bdbc492ed20231dcb63f297d6d1021a9d5725"}, @@ -1350,13 +3323,170 @@ typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.1 [package.extras] jupyter = ["ipywidgets (>=7.5.1,<9)"] +[[package]] +name = "rigging" +version = "2.3.0" +description = "LLM Interaction Framework" +optional = false +python-versions = "<4.0,>=3.9" +groups = ["dev"] +files = [ + {file = "rigging-2.3.0-py3-none-any.whl", hash = "sha256:e17a78acb4c36651fc30eb55c8166858402d2f51b359bcbf717833883a6dad8f"}, + {file = "rigging-2.3.0.tar.gz", hash = "sha256:2c021cbfeaf6c6cd80762ba6bba310ef77443bf792eaadadef54795a877a8883"}, +] + +[package.dependencies] +boto3 = ">=1.35.0,<2.0.0" +boto3-stubs = {version = ">=1.35.0,<2.0.0", extras = ["s3"]} +colorama = ">=0.4.6,<0.5.0" +elasticsearch = ">=8.13.2,<9.0.0" +eval-type-backport = ">=0.2.0,<0.3.0" +jsonpath-ng = ">=1.7.0,<2.0.0" +jsonref = ">=1.1.0,<2.0.0" +litellm = ">=1.60.0,<2.0.0" +logfire-api = ">=3.1.1,<4.0.0" +loguru = ">=0.7.2,<0.8.0" +pandas = ">=2.2.2,<3.0.0" +pydantic = ">=2.7.3,<3.0.0" +pydantic-xml = ">=2.11.0,<3.0.0" +ruamel-yaml = ">=0.18.10,<0.19.0" +xmltodict = ">=0.13.0,<0.14.0" + +[package.extras] +all = ["accelerate (>=0.30.1,<0.31.0)", "aiodocker (>=0.22.2,<0.23.0)", "asyncssh (>=2.14.2,<3.0.0)", "click (>=8.1.7,<9.0.0)", "httpx (>=0.27.0,<0.28.0)", "transformers (>=4.41.0,<5.0.0)", "vllm (>=0.5.0,<0.6.0)", "websockets (>=13.0,<14.0)"] +examples = ["aiodocker (>=0.22.2,<0.23.0)", "asyncssh (>=2.14.2,<3.0.0)", "click (>=8.1.7,<9.0.0)", "httpx (>=0.27.0,<0.28.0)", "websockets (>=13.0,<14.0)"] + +[[package]] +name = "rpds-py" +version = "0.24.0" +description = "Python bindings to Rust's persistent data structures (rpds)" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "rpds_py-0.24.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:006f4342fe729a368c6df36578d7a348c7c716be1da0a1a0f86e3021f8e98724"}, + {file = "rpds_py-0.24.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2d53747da70a4e4b17f559569d5f9506420966083a31c5fbd84e764461c4444b"}, + {file = "rpds_py-0.24.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8acd55bd5b071156bae57b555f5d33697998752673b9de554dd82f5b5352727"}, + {file = "rpds_py-0.24.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7e80d375134ddb04231a53800503752093dbb65dad8dabacce2c84cccc78e964"}, + {file = "rpds_py-0.24.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60748789e028d2a46fc1c70750454f83c6bdd0d05db50f5ae83e2db500b34da5"}, + {file = "rpds_py-0.24.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6e1daf5bf6c2be39654beae83ee6b9a12347cb5aced9a29eecf12a2d25fff664"}, + {file = "rpds_py-0.24.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1b221c2457d92a1fb3c97bee9095c874144d196f47c038462ae6e4a14436f7bc"}, + {file = "rpds_py-0.24.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:66420986c9afff67ef0c5d1e4cdc2d0e5262f53ad11e4f90e5e22448df485bf0"}, + {file = "rpds_py-0.24.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:43dba99f00f1d37b2a0265a259592d05fcc8e7c19d140fe51c6e6f16faabeb1f"}, + {file = "rpds_py-0.24.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:a88c0d17d039333a41d9bf4616bd062f0bd7aa0edeb6cafe00a2fc2a804e944f"}, + {file = "rpds_py-0.24.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:cc31e13ce212e14a539d430428cd365e74f8b2d534f8bc22dd4c9c55b277b875"}, + {file = "rpds_py-0.24.0-cp310-cp310-win32.whl", hash = "sha256:fc2c1e1b00f88317d9de6b2c2b39b012ebbfe35fe5e7bef980fd2a91f6100a07"}, + {file = "rpds_py-0.24.0-cp310-cp310-win_amd64.whl", hash = "sha256:c0145295ca415668420ad142ee42189f78d27af806fcf1f32a18e51d47dd2052"}, + {file = "rpds_py-0.24.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:2d3ee4615df36ab8eb16c2507b11e764dcc11fd350bbf4da16d09cda11fcedef"}, + {file = "rpds_py-0.24.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e13ae74a8a3a0c2f22f450f773e35f893484fcfacb00bb4344a7e0f4f48e1f97"}, + {file = "rpds_py-0.24.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf86f72d705fc2ef776bb7dd9e5fbba79d7e1f3e258bf9377f8204ad0fc1c51e"}, + {file = "rpds_py-0.24.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c43583ea8517ed2e780a345dd9960896afc1327e8cf3ac8239c167530397440d"}, + {file = "rpds_py-0.24.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4cd031e63bc5f05bdcda120646a0d32f6d729486d0067f09d79c8db5368f4586"}, + {file = "rpds_py-0.24.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:34d90ad8c045df9a4259c47d2e16a3f21fdb396665c94520dbfe8766e62187a4"}, + {file = "rpds_py-0.24.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e838bf2bb0b91ee67bf2b889a1a841e5ecac06dd7a2b1ef4e6151e2ce155c7ae"}, + {file = "rpds_py-0.24.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04ecf5c1ff4d589987b4d9882872f80ba13da7d42427234fce8f22efb43133bc"}, + {file = "rpds_py-0.24.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:630d3d8ea77eabd6cbcd2ea712e1c5cecb5b558d39547ac988351195db433f6c"}, + {file = "rpds_py-0.24.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ebcb786b9ff30b994d5969213a8430cbb984cdd7ea9fd6df06663194bd3c450c"}, + {file = "rpds_py-0.24.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:174e46569968ddbbeb8a806d9922f17cd2b524aa753b468f35b97ff9c19cb718"}, + {file = "rpds_py-0.24.0-cp311-cp311-win32.whl", hash = "sha256:5ef877fa3bbfb40b388a5ae1cb00636a624690dcb9a29a65267054c9ea86d88a"}, + {file = "rpds_py-0.24.0-cp311-cp311-win_amd64.whl", hash = "sha256:e274f62cbd274359eff63e5c7e7274c913e8e09620f6a57aae66744b3df046d6"}, + {file = "rpds_py-0.24.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:d8551e733626afec514b5d15befabea0dd70a343a9f23322860c4f16a9430205"}, + {file = "rpds_py-0.24.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e374c0ce0ca82e5b67cd61fb964077d40ec177dd2c4eda67dba130de09085c7"}, + {file = "rpds_py-0.24.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d69d003296df4840bd445a5d15fa5b6ff6ac40496f956a221c4d1f6f7b4bc4d9"}, + {file = "rpds_py-0.24.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8212ff58ac6dfde49946bea57474a386cca3f7706fc72c25b772b9ca4af6b79e"}, + {file = "rpds_py-0.24.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:528927e63a70b4d5f3f5ccc1fa988a35456eb5d15f804d276709c33fc2f19bda"}, + {file = "rpds_py-0.24.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a824d2c7a703ba6daaca848f9c3d5cb93af0505be505de70e7e66829affd676e"}, + {file = "rpds_py-0.24.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44d51febb7a114293ffd56c6cf4736cb31cd68c0fddd6aa303ed09ea5a48e029"}, + {file = "rpds_py-0.24.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3fab5f4a2c64a8fb64fc13b3d139848817a64d467dd6ed60dcdd6b479e7febc9"}, + {file = "rpds_py-0.24.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9be4f99bee42ac107870c61dfdb294d912bf81c3c6d45538aad7aecab468b6b7"}, + {file = "rpds_py-0.24.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:564c96b6076a98215af52f55efa90d8419cc2ef45d99e314fddefe816bc24f91"}, + {file = "rpds_py-0.24.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:75a810b7664c17f24bf2ffd7f92416c00ec84b49bb68e6a0d93e542406336b56"}, + {file = "rpds_py-0.24.0-cp312-cp312-win32.whl", hash = "sha256:f6016bd950be4dcd047b7475fdf55fb1e1f59fc7403f387be0e8123e4a576d30"}, + {file = "rpds_py-0.24.0-cp312-cp312-win_amd64.whl", hash = "sha256:998c01b8e71cf051c28f5d6f1187abbdf5cf45fc0efce5da6c06447cba997034"}, + {file = "rpds_py-0.24.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:3d2d8e4508e15fc05b31285c4b00ddf2e0eb94259c2dc896771966a163122a0c"}, + {file = "rpds_py-0.24.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0f00c16e089282ad68a3820fd0c831c35d3194b7cdc31d6e469511d9bffc535c"}, + {file = "rpds_py-0.24.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:951cc481c0c395c4a08639a469d53b7d4afa252529a085418b82a6b43c45c240"}, + {file = "rpds_py-0.24.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c9ca89938dff18828a328af41ffdf3902405a19f4131c88e22e776a8e228c5a8"}, + {file = "rpds_py-0.24.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed0ef550042a8dbcd657dfb284a8ee00f0ba269d3f2286b0493b15a5694f9fe8"}, + {file = "rpds_py-0.24.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b2356688e5d958c4d5cb964af865bea84db29971d3e563fb78e46e20fe1848b"}, + {file = "rpds_py-0.24.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78884d155fd15d9f64f5d6124b486f3d3f7fd7cd71a78e9670a0f6f6ca06fb2d"}, + {file = "rpds_py-0.24.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6a4a535013aeeef13c5532f802708cecae8d66c282babb5cd916379b72110cf7"}, + {file = "rpds_py-0.24.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:84e0566f15cf4d769dade9b366b7b87c959be472c92dffb70462dd0844d7cbad"}, + {file = "rpds_py-0.24.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:823e74ab6fbaa028ec89615ff6acb409e90ff45580c45920d4dfdddb069f2120"}, + {file = "rpds_py-0.24.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c61a2cb0085c8783906b2f8b1f16a7e65777823c7f4d0a6aaffe26dc0d358dd9"}, + {file = "rpds_py-0.24.0-cp313-cp313-win32.whl", hash = "sha256:60d9b630c8025b9458a9d114e3af579a2c54bd32df601c4581bd054e85258143"}, + {file = "rpds_py-0.24.0-cp313-cp313-win_amd64.whl", hash = "sha256:6eea559077d29486c68218178ea946263b87f1c41ae7f996b1f30a983c476a5a"}, + {file = "rpds_py-0.24.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:d09dc82af2d3c17e7dd17120b202a79b578d79f2b5424bda209d9966efeed114"}, + {file = "rpds_py-0.24.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5fc13b44de6419d1e7a7e592a4885b323fbc2f46e1f22151e3a8ed3b8b920405"}, + {file = "rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c347a20d79cedc0a7bd51c4d4b7dbc613ca4e65a756b5c3e57ec84bd43505b47"}, + {file = "rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:20f2712bd1cc26a3cc16c5a1bfee9ed1abc33d4cdf1aabd297fe0eb724df4272"}, + {file = "rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aad911555286884be1e427ef0dc0ba3929e6821cbeca2194b13dc415a462c7fd"}, + {file = "rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0aeb3329c1721c43c58cae274d7d2ca85c1690d89485d9c63a006cb79a85771a"}, + {file = "rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a0f156e9509cee987283abd2296ec816225145a13ed0391df8f71bf1d789e2d"}, + {file = "rpds_py-0.24.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:aa6800adc8204ce898c8a424303969b7aa6a5e4ad2789c13f8648739830323b7"}, + {file = "rpds_py-0.24.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a18fc371e900a21d7392517c6f60fe859e802547309e94313cd8181ad9db004d"}, + {file = "rpds_py-0.24.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9168764133fd919f8dcca2ead66de0105f4ef5659cbb4fa044f7014bed9a1797"}, + {file = "rpds_py-0.24.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5f6e3cec44ba05ee5cbdebe92d052f69b63ae792e7d05f1020ac5e964394080c"}, + {file = "rpds_py-0.24.0-cp313-cp313t-win32.whl", hash = "sha256:8ebc7e65ca4b111d928b669713865f021b7773350eeac4a31d3e70144297baba"}, + {file = "rpds_py-0.24.0-cp313-cp313t-win_amd64.whl", hash = "sha256:675269d407a257b8c00a6b58205b72eec8231656506c56fd429d924ca00bb350"}, + {file = "rpds_py-0.24.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a36b452abbf29f68527cf52e181fced56685731c86b52e852053e38d8b60bc8d"}, + {file = "rpds_py-0.24.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8b3b397eefecec8e8e39fa65c630ef70a24b09141a6f9fc17b3c3a50bed6b50e"}, + {file = "rpds_py-0.24.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdabcd3beb2a6dca7027007473d8ef1c3b053347c76f685f5f060a00327b8b65"}, + {file = "rpds_py-0.24.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5db385bacd0c43f24be92b60c857cf760b7f10d8234f4bd4be67b5b20a7c0b6b"}, + {file = "rpds_py-0.24.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8097b3422d020ff1c44effc40ae58e67d93e60d540a65649d2cdaf9466030791"}, + {file = "rpds_py-0.24.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:493fe54318bed7d124ce272fc36adbf59d46729659b2c792e87c3b95649cdee9"}, + {file = "rpds_py-0.24.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8aa362811ccdc1f8dadcc916c6d47e554169ab79559319ae9fae7d7752d0d60c"}, + {file = "rpds_py-0.24.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d8f9a6e7fd5434817526815f09ea27f2746c4a51ee11bb3439065f5fc754db58"}, + {file = "rpds_py-0.24.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:8205ee14463248d3349131bb8099efe15cd3ce83b8ef3ace63c7e976998e7124"}, + {file = "rpds_py-0.24.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:921ae54f9ecba3b6325df425cf72c074cd469dea843fb5743a26ca7fb2ccb149"}, + {file = "rpds_py-0.24.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:32bab0a56eac685828e00cc2f5d1200c548f8bc11f2e44abf311d6b548ce2e45"}, + {file = "rpds_py-0.24.0-cp39-cp39-win32.whl", hash = "sha256:f5c0ed12926dec1dfe7d645333ea59cf93f4d07750986a586f511c0bc61fe103"}, + {file = "rpds_py-0.24.0-cp39-cp39-win_amd64.whl", hash = "sha256:afc6e35f344490faa8276b5f2f7cbf71f88bc2cda4328e00553bd451728c571f"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:619ca56a5468f933d940e1bf431c6f4e13bef8e688698b067ae68eb4f9b30e3a"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:4b28e5122829181de1898c2c97f81c0b3246d49f585f22743a1246420bb8d399"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8e5ab32cf9eb3647450bc74eb201b27c185d3857276162c101c0f8c6374e098"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:208b3a70a98cf3710e97cabdc308a51cd4f28aa6e7bb11de3d56cd8b74bab98d"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbc4362e06f950c62cad3d4abf1191021b2ffaf0b31ac230fbf0526453eee75e"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ebea2821cdb5f9fef44933617be76185b80150632736f3d76e54829ab4a3b4d1"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9a4df06c35465ef4d81799999bba810c68d29972bf1c31db61bfdb81dd9d5bb"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d3aa13bdf38630da298f2e0d77aca967b200b8cc1473ea05248f6c5e9c9bdb44"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:041f00419e1da7a03c46042453598479f45be3d787eb837af382bfc169c0db33"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:d8754d872a5dfc3c5bf9c0e059e8107451364a30d9fd50f1f1a85c4fb9481164"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:896c41007931217a343eff197c34513c154267636c8056fb409eafd494c3dcdc"}, + {file = "rpds_py-0.24.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:92558d37d872e808944c3c96d0423b8604879a3d1c86fdad508d7ed91ea547d5"}, + {file = "rpds_py-0.24.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f9e0057a509e096e47c87f753136c9b10d7a91842d8042c2ee6866899a717c0d"}, + {file = "rpds_py-0.24.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d6e109a454412ab82979c5b1b3aee0604eca4bbf9a02693bb9df027af2bfa91a"}, + {file = "rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc1c892b1ec1f8cbd5da8de287577b455e388d9c328ad592eabbdcb6fc93bee5"}, + {file = "rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9c39438c55983d48f4bb3487734d040e22dad200dab22c41e331cee145e7a50d"}, + {file = "rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d7e8ce990ae17dda686f7e82fd41a055c668e13ddcf058e7fb5e9da20b57793"}, + {file = "rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9ea7f4174d2e4194289cb0c4e172d83e79a6404297ff95f2875cf9ac9bced8ba"}, + {file = "rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb2954155bb8f63bb19d56d80e5e5320b61d71084617ed89efedb861a684baea"}, + {file = "rpds_py-0.24.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:04f2b712a2206e13800a8136b07aaedc23af3facab84918e7aa89e4be0260032"}, + {file = "rpds_py-0.24.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:eda5c1e2a715a4cbbca2d6d304988460942551e4e5e3b7457b50943cd741626d"}, + {file = "rpds_py-0.24.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:9abc80fe8c1f87218db116016de575a7998ab1629078c90840e8d11ab423ee25"}, + {file = "rpds_py-0.24.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:6a727fd083009bc83eb83d6950f0c32b3c94c8b80a9b667c87f4bd1274ca30ba"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e0f3ef95795efcd3b2ec3fe0a5bcfb5dadf5e3996ea2117427e524d4fbf309c6"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:2c13777ecdbbba2077670285dd1fe50828c8742f6a4119dbef6f83ea13ad10fb"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79e8d804c2ccd618417e96720ad5cd076a86fa3f8cb310ea386a3e6229bae7d1"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fd822f019ccccd75c832deb7aa040bb02d70a92eb15a2f16c7987b7ad4ee8d83"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0047638c3aa0dbcd0ab99ed1e549bbf0e142c9ecc173b6492868432d8989a046"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a5b66d1b201cc71bc3081bc2f1fc36b0c1f268b773e03bbc39066651b9e18391"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dbcbb6db5582ea33ce46a5d20a5793134b5365110d84df4e30b9d37c6fd40ad3"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:63981feca3f110ed132fd217bf7768ee8ed738a55549883628ee3da75bb9cb78"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:3a55fc10fdcbf1a4bd3c018eea422c52cf08700cf99c28b5cb10fe97ab77a0d3"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:c30ff468163a48535ee7e9bf21bd14c7a81147c0e58a36c1078289a8ca7af0bd"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:369d9c6d4c714e36d4a03957b4783217a3ccd1e222cdd67d464a3a479fc17796"}, + {file = "rpds_py-0.24.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:24795c099453e3721fda5d8ddd45f5dfcc8e5a547ce7b8e9da06fecc3832e26f"}, + {file = "rpds_py-0.24.0.tar.gz", hash = "sha256:772cc1b2cd963e7e17e6cc55fe0371fb9c704d63e44cacec7b9b7f523b78919e"}, +] + [[package]] name = "ruamel-yaml" version = "0.18.10" description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip preservation of comments, seq/map flow style, and map key order" optional = false python-versions = ">=3.7" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "ruamel.yaml-0.18.10-py3-none-any.whl", hash = "sha256:30f22513ab2301b3d2b577adc121c6471f28734d3d9728581245f1e76468b4f1"}, {file = "ruamel.yaml-0.18.10.tar.gz", hash = "sha256:20c86ab29ac2153f80a428e1254a8adf686d3383df04490514ca3b79a362db58"}, @@ -1375,7 +3505,7 @@ version = "0.2.12" description = "C version of reader, parser and emitter for ruamel.yaml derived from libyaml" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "dev"] markers = "platform_python_implementation == \"CPython\"" files = [ {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:11f891336688faf5156a36293a9c362bdc7c88f03a8a027c2c1d8e0bcde998e5"}, @@ -1454,13 +3584,59 @@ files = [ {file = "ruff-0.11.6.tar.gz", hash = "sha256:bec8bcc3ac228a45ccc811e45f7eb61b950dbf4cf31a67fa89352574b01c7d79"}, ] +[[package]] +name = "s3fs" +version = "0.4.2" +description = "Convenient Filesystem interface over S3" +optional = false +python-versions = ">= 3.5" +groups = ["main"] +files = [ + {file = "s3fs-0.4.2-py3-none-any.whl", hash = "sha256:91c1dfb45e5217bd441a7a560946fe865ced6225ff7eb0fb459fe6e601a95ed3"}, + {file = "s3fs-0.4.2.tar.gz", hash = "sha256:2ca5de8dc18ad7ad350c0bd01aef0406aa5d0fff78a561f0f710f9d9858abdd0"}, +] + +[package.dependencies] +botocore = ">=1.12.91" +fsspec = ">=0.6.0" + +[[package]] +name = "s3transfer" +version = "0.12.0" +description = "An Amazon S3 Transfer Manager" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "s3transfer-0.12.0-py3-none-any.whl", hash = "sha256:35b314d7d82865756edab59f7baebc6b477189e6ab4c53050e28c1de4d9cce18"}, + {file = "s3transfer-0.12.0.tar.gz", hash = "sha256:8ac58bc1989a3fdb7c7f3ee0918a66b160d038a147c7b5db1500930a607e9a1c"}, +] + +[package.dependencies] +botocore = ">=1.37.4,<2.0a.0" + +[package.extras] +crt = ["botocore[crt] (>=1.37.4,<2.0a.0)"] + +[[package]] +name = "shellingham" +version = "1.5.4" +description = "Tool to Detect Surrounding Shell" +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, + {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, +] + [[package]] name = "six" version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -1472,12 +3648,93 @@ version = "1.3.1" description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, ] +[[package]] +name = "tiktoken" +version = "0.9.0" +description = "tiktoken is a fast BPE tokeniser for use with OpenAI's models" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "tiktoken-0.9.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:586c16358138b96ea804c034b8acf3f5d3f0258bd2bc3b0227af4af5d622e382"}, + {file = "tiktoken-0.9.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d9c59ccc528c6c5dd51820b3474402f69d9a9e1d656226848ad68a8d5b2e5108"}, + {file = "tiktoken-0.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0968d5beeafbca2a72c595e8385a1a1f8af58feaebb02b227229b69ca5357fd"}, + {file = "tiktoken-0.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:92a5fb085a6a3b7350b8fc838baf493317ca0e17bd95e8642f95fc69ecfed1de"}, + {file = "tiktoken-0.9.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:15a2752dea63d93b0332fb0ddb05dd909371ededa145fe6a3242f46724fa7990"}, + {file = "tiktoken-0.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:26113fec3bd7a352e4b33dbaf1bd8948de2507e30bd95a44e2b1156647bc01b4"}, + {file = "tiktoken-0.9.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:f32cc56168eac4851109e9b5d327637f15fd662aa30dd79f964b7c39fbadd26e"}, + {file = "tiktoken-0.9.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:45556bc41241e5294063508caf901bf92ba52d8ef9222023f83d2483a3055348"}, + {file = "tiktoken-0.9.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03935988a91d6d3216e2ec7c645afbb3d870b37bcb67ada1943ec48678e7ee33"}, + {file = "tiktoken-0.9.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b3d80aad8d2c6b9238fc1a5524542087c52b860b10cbf952429ffb714bc1136"}, + {file = "tiktoken-0.9.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b2a21133be05dc116b1d0372af051cd2c6aa1d2188250c9b553f9fa49301b336"}, + {file = "tiktoken-0.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:11a20e67fdf58b0e2dea7b8654a288e481bb4fc0289d3ad21291f8d0849915fb"}, + {file = "tiktoken-0.9.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e88f121c1c22b726649ce67c089b90ddda8b9662545a8aeb03cfef15967ddd03"}, + {file = "tiktoken-0.9.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a6600660f2f72369acb13a57fb3e212434ed38b045fd8cc6cdd74947b4b5d210"}, + {file = "tiktoken-0.9.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95e811743b5dfa74f4b227927ed86cbc57cad4df859cb3b643be797914e41794"}, + {file = "tiktoken-0.9.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99376e1370d59bcf6935c933cb9ba64adc29033b7e73f5f7569f3aad86552b22"}, + {file = "tiktoken-0.9.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:badb947c32739fb6ddde173e14885fb3de4d32ab9d8c591cbd013c22b4c31dd2"}, + {file = "tiktoken-0.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:5a62d7a25225bafed786a524c1b9f0910a1128f4232615bf3f8257a73aaa3b16"}, + {file = "tiktoken-0.9.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:2b0e8e05a26eda1249e824156d537015480af7ae222ccb798e5234ae0285dbdb"}, + {file = "tiktoken-0.9.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:27d457f096f87685195eea0165a1807fae87b97b2161fe8c9b1df5bd74ca6f63"}, + {file = "tiktoken-0.9.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2cf8ded49cddf825390e36dd1ad35cd49589e8161fdcb52aa25f0583e90a3e01"}, + {file = "tiktoken-0.9.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc156cb314119a8bb9748257a2eaebd5cc0753b6cb491d26694ed42fc7cb3139"}, + {file = "tiktoken-0.9.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cd69372e8c9dd761f0ab873112aba55a0e3e506332dd9f7522ca466e817b1b7a"}, + {file = "tiktoken-0.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:5ea0edb6f83dc56d794723286215918c1cde03712cbbafa0348b33448faf5b95"}, + {file = "tiktoken-0.9.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c6386ca815e7d96ef5b4ac61e0048cd32ca5a92d5781255e13b31381d28667dc"}, + {file = "tiktoken-0.9.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:75f6d5db5bc2c6274b674ceab1615c1778e6416b14705827d19b40e6355f03e0"}, + {file = "tiktoken-0.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e15b16f61e6f4625a57a36496d28dd182a8a60ec20a534c5343ba3cafa156ac7"}, + {file = "tiktoken-0.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ebcec91babf21297022882344c3f7d9eed855931466c3311b1ad6b64befb3df"}, + {file = "tiktoken-0.9.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e5fd49e7799579240f03913447c0cdfa1129625ebd5ac440787afc4345990427"}, + {file = "tiktoken-0.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:26242ca9dc8b58e875ff4ca078b9a94d2f0813e6a535dcd2205df5d49d927cc7"}, + {file = "tiktoken-0.9.0.tar.gz", hash = "sha256:d02a5ca6a938e0490e1ff957bc48c8b078c88cb83977be1625b1fd8aac792c5d"}, +] + +[package.dependencies] +regex = ">=2022.1.18" +requests = ">=2.26.0" + +[package.extras] +blobfile = ["blobfile (>=2)"] + +[[package]] +name = "tokenizers" +version = "0.21.1" +description = "" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "tokenizers-0.21.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:e78e413e9e668ad790a29456e677d9d3aa50a9ad311a40905d6861ba7692cf41"}, + {file = "tokenizers-0.21.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:cd51cd0a91ecc801633829fcd1fda9cf8682ed3477c6243b9a095539de4aecf3"}, + {file = "tokenizers-0.21.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28da6b72d4fb14ee200a1bd386ff74ade8992d7f725f2bde2c495a9a98cf4d9f"}, + {file = "tokenizers-0.21.1-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:34d8cfde551c9916cb92014e040806122295a6800914bab5865deb85623931cf"}, + {file = "tokenizers-0.21.1-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aaa852d23e125b73d283c98f007e06d4595732104b65402f46e8ef24b588d9f8"}, + {file = "tokenizers-0.21.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a21a15d5c8e603331b8a59548bbe113564136dc0f5ad8306dd5033459a226da0"}, + {file = "tokenizers-0.21.1-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2fdbd4c067c60a0ac7eca14b6bd18a5bebace54eb757c706b47ea93204f7a37c"}, + {file = "tokenizers-0.21.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dd9a0061e403546f7377df940e866c3e678d7d4e9643d0461ea442b4f89e61a"}, + {file = "tokenizers-0.21.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:db9484aeb2e200c43b915a1a0150ea885e35f357a5a8fabf7373af333dcc8dbf"}, + {file = "tokenizers-0.21.1-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:ed248ab5279e601a30a4d67bdb897ecbe955a50f1e7bb62bd99f07dd11c2f5b6"}, + {file = "tokenizers-0.21.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:9ac78b12e541d4ce67b4dfd970e44c060a2147b9b2a21f509566d556a509c67d"}, + {file = "tokenizers-0.21.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e5a69c1a4496b81a5ee5d2c1f3f7fbdf95e90a0196101b0ee89ed9956b8a168f"}, + {file = "tokenizers-0.21.1-cp39-abi3-win32.whl", hash = "sha256:1039a3a5734944e09de1d48761ade94e00d0fa760c0e0551151d4dd851ba63e3"}, + {file = "tokenizers-0.21.1-cp39-abi3-win_amd64.whl", hash = "sha256:0f0dcbcc9f6e13e675a66d7a5f2f225a736745ce484c1a4e07476a89ccdad382"}, + {file = "tokenizers-0.21.1.tar.gz", hash = "sha256:a1bb04dc5b448985f86ecd4b05407f5a8d97cb2c0532199b2a302a604a0165ab"}, +] + +[package.dependencies] +huggingface-hub = ">=0.16.4,<1.0" + +[package.extras] +dev = ["tokenizers[testing]"] +docs = ["setuptools-rust", "sphinx", "sphinx-rtd-theme"] +testing = ["black (==22.3)", "datasets", "numpy", "pytest", "requests", "ruff"] + [[package]] name = "tomli" version = "2.2.1" @@ -1521,28 +3778,119 @@ files = [ {file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"}, ] +[[package]] +name = "tqdm" +version = "4.67.1" +description = "Fast, Extensible Progress Meter" +optional = false +python-versions = ">=3.7" +groups = ["main", "dev"] +files = [ + {file = "tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2"}, + {file = "tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +dev = ["nbval", "pytest (>=6)", "pytest-asyncio (>=0.24)", "pytest-cov", "pytest-timeout"] +discord = ["requests"] +notebook = ["ipywidgets (>=6)"] +slack = ["slack-sdk"] +telegram = ["requests"] + +[[package]] +name = "typer" +version = "0.15.2" +description = "Typer, build great CLIs. Easy to code. Based on Python type hints." +optional = false +python-versions = ">=3.7" +groups = ["dev"] +files = [ + {file = "typer-0.15.2-py3-none-any.whl", hash = "sha256:46a499c6107d645a9c13f7ee46c5d5096cae6f5fc57dd11eccbbb9ae3e44ddfc"}, + {file = "typer-0.15.2.tar.gz", hash = "sha256:ab2fab47533a813c49fe1f16b1a370fd5819099c00b119e0633df65f22144ba5"}, +] + +[package.dependencies] +click = ">=8.0.0" +rich = ">=10.11.0" +shellingham = ">=1.3.0" +typing-extensions = ">=3.7.4.3" + +[[package]] +name = "types-awscrt" +version = "0.26.1" +description = "Type annotations and code completion for awscrt" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "types_awscrt-0.26.1-py3-none-any.whl", hash = "sha256:176d320a26990efc057d4bf71396e05be027c142252ac48cc0d87aaea0704280"}, + {file = "types_awscrt-0.26.1.tar.gz", hash = "sha256:aca96f889b3745c0e74f42f08f277fed3bf6e9baa2cf9b06a36f78d77720e504"}, +] + [[package]] name = "types-protobuf" -version = "5.29.1.20250208" +version = "5.29.1.20250403" description = "Typing stubs for protobuf" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "types_protobuf-5.29.1.20250208-py3-none-any.whl", hash = "sha256:c5f8bfb4afdc1b5cbca1848f2c8b361a2090add7401f410b22b599ef647bf483"}, - {file = "types_protobuf-5.29.1.20250208.tar.gz", hash = "sha256:c1acd6a59ab554dbe09b5d1fa7dd701e2fcfb2212937a3af1c03b736060b792a"}, + {file = "types_protobuf-5.29.1.20250403-py3-none-any.whl", hash = "sha256:c71de04106a2d54e5b2173d0a422058fae0ef2d058d70cf369fb797bf61ffa59"}, + {file = "types_protobuf-5.29.1.20250403.tar.gz", hash = "sha256:7ff44f15022119c9d7558ce16e78b2d485bf7040b4fadced4dd069bb5faf77a2"}, +] + +[[package]] +name = "types-pytz" +version = "2025.2.0.20250326" +description = "Typing stubs for pytz" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "types_pytz-2025.2.0.20250326-py3-none-any.whl", hash = "sha256:3c397fd1b845cd2b3adc9398607764ced9e578a98a5d1fbb4a9bc9253edfb162"}, + {file = "types_pytz-2025.2.0.20250326.tar.gz", hash = "sha256:deda02de24f527066fc8d6a19e284ab3f3ae716a42b4adb6b40e75e408c08d36"}, +] + +[[package]] +name = "types-requests" +version = "2.32.0.20250328" +description = "Typing stubs for requests" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "types_requests-2.32.0.20250328-py3-none-any.whl", hash = "sha256:72ff80f84b15eb3aa7a8e2625fffb6a93f2ad5a0c20215fc1dcfa61117bcb2a2"}, + {file = "types_requests-2.32.0.20250328.tar.gz", hash = "sha256:c9e67228ea103bd811c96984fac36ed2ae8da87a36a633964a21f199d60baf32"}, +] + +[package.dependencies] +urllib3 = ">=2" + +[[package]] +name = "types-s3transfer" +version = "0.12.0" +description = "Type annotations and code completion for s3transfer" +optional = false +python-versions = ">=3.8" +groups = ["dev"] +files = [ + {file = "types_s3transfer-0.12.0-py3-none-any.whl", hash = "sha256:101bbc5b7f00b71512374df881f480fc6bf63c948b5098ab024bf3370fbfb0e8"}, + {file = "types_s3transfer-0.12.0.tar.gz", hash = "sha256:f8f59201481e904362873bf0be3267f259d60ad946ebdfcb847d092a1fa26f98"}, ] [[package]] name = "typing-extensions" -version = "4.12.2" +version = "4.13.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" groups = ["main", "dev"] files = [ - {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, - {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, + {file = "typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c"}, + {file = "typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef"}, ] [[package]] @@ -1551,7 +3899,7 @@ version = "0.4.0" description = "Runtime typing introspection tools" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f"}, {file = "typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122"}, @@ -1566,7 +3914,7 @@ version = "2025.2" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8"}, {file = "tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9"}, @@ -1574,14 +3922,14 @@ files = [ [[package]] name = "urllib3" -version = "2.3.0" +version = "2.4.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df"}, - {file = "urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d"}, + {file = "urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813"}, + {file = "urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466"}, ] [package.extras] @@ -1592,14 +3940,14 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.29.2" +version = "20.30.0" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "virtualenv-20.29.2-py3-none-any.whl", hash = "sha256:febddfc3d1ea571bdb1dc0f98d7b45d24def7428214d4fb73cc486c9568cce6a"}, - {file = "virtualenv-20.29.2.tar.gz", hash = "sha256:fdaabebf6d03b5ba83ae0a02cfe96f48a716f4fae556461d180825866f75b728"}, + {file = "virtualenv-20.30.0-py3-none-any.whl", hash = "sha256:e34302959180fca3af42d1800df014b35019490b119eba981af27f2fa486e5d6"}, + {file = "virtualenv-20.30.0.tar.gz", hash = "sha256:800863162bcaa5450a6e4d721049730e7f2dae07720e0902b0e4040bd6f9ada8"}, ] [package.dependencies] @@ -1609,7 +3957,23 @@ platformdirs = ">=3.9.1,<5" [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] -test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8) ; platform_python_implementation == \"PyPy\" or platform_python_implementation == \"GraalVM\" or platform_python_implementation == \"CPython\" and sys_platform == \"win32\" and python_version >= \"3.13\"", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10) ; platform_python_implementation == \"CPython\""] + +[[package]] +name = "win32-setctime" +version = "1.2.0" +description = "A small Python utility to set file creation time on Windows" +optional = false +python-versions = ">=3.5" +groups = ["main", "dev"] +markers = "sys_platform == \"win32\"" +files = [ + {file = "win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390"}, + {file = "win32_setctime-1.2.0.tar.gz", hash = "sha256:ae1fdf948f5640aae05c511ade119313fb6a30d7eabe25fef9764dca5873c4c0"}, +] + +[package.extras] +dev = ["black (>=19.3b0) ; python_version >= \"3.6\"", "pytest (>=4.6.2)"] [[package]] name = "wrapt" @@ -1700,13 +4064,277 @@ files = [ {file = "wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3"}, ] +[[package]] +name = "xmltodict" +version = "0.13.0" +description = "Makes working with XML feel like you are working with JSON" +optional = false +python-versions = ">=3.4" +groups = ["dev"] +files = [ + {file = "xmltodict-0.13.0-py2.py3-none-any.whl", hash = "sha256:aa89e8fd76320154a40d19a0df04a4695fb9dc5ba977cbb68ab3e4eb225e7852"}, + {file = "xmltodict-0.13.0.tar.gz", hash = "sha256:341595a488e3e01a85a9d8911d8912fd922ede5fecc4dce437eb4b6c8d037e56"}, +] + +[[package]] +name = "xxhash" +version = "3.5.0" +description = "Python binding for xxHash" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "xxhash-3.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ece616532c499ee9afbb83078b1b952beffef121d989841f7f4b3dc5ac0fd212"}, + {file = "xxhash-3.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3171f693dbc2cef6477054a665dc255d996646b4023fe56cb4db80e26f4cc520"}, + {file = "xxhash-3.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c5d3e570ef46adaf93fc81b44aca6002b5a4d8ca11bd0580c07eac537f36680"}, + {file = "xxhash-3.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7cb29a034301e2982df8b1fe6328a84f4b676106a13e9135a0d7e0c3e9f806da"}, + {file = "xxhash-3.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5d0d307d27099bb0cbeea7260eb39ed4fdb99c5542e21e94bb6fd29e49c57a23"}, + {file = "xxhash-3.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0342aafd421795d740e514bc9858ebddfc705a75a8c5046ac56d85fe97bf196"}, + {file = "xxhash-3.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3dbbd9892c5ebffeca1ed620cf0ade13eb55a0d8c84e0751a6653adc6ac40d0c"}, + {file = "xxhash-3.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4cc2d67fdb4d057730c75a64c5923abfa17775ae234a71b0200346bfb0a7f482"}, + {file = "xxhash-3.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ec28adb204b759306a3d64358a5e5c07d7b1dd0ccbce04aa76cb9377b7b70296"}, + {file = "xxhash-3.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:1328f6d8cca2b86acb14104e381225a3d7b42c92c4b86ceae814e5c400dbb415"}, + {file = "xxhash-3.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8d47ebd9f5d9607fd039c1fbf4994e3b071ea23eff42f4ecef246ab2b7334198"}, + {file = "xxhash-3.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b96d559e0fcddd3343c510a0fe2b127fbff16bf346dd76280b82292567523442"}, + {file = "xxhash-3.5.0-cp310-cp310-win32.whl", hash = "sha256:61c722ed8d49ac9bc26c7071eeaa1f6ff24053d553146d5df031802deffd03da"}, + {file = "xxhash-3.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:9bed5144c6923cc902cd14bb8963f2d5e034def4486ab0bbe1f58f03f042f9a9"}, + {file = "xxhash-3.5.0-cp310-cp310-win_arm64.whl", hash = "sha256:893074d651cf25c1cc14e3bea4fceefd67f2921b1bb8e40fcfeba56820de80c6"}, + {file = "xxhash-3.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02c2e816896dc6f85922ced60097bcf6f008dedfc5073dcba32f9c8dd786f3c1"}, + {file = "xxhash-3.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6027dcd885e21581e46d3c7f682cfb2b870942feeed58a21c29583512c3f09f8"}, + {file = "xxhash-3.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1308fa542bbdbf2fa85e9e66b1077eea3a88bef38ee8a06270b4298a7a62a166"}, + {file = "xxhash-3.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c28b2fdcee797e1c1961cd3bcd3d545cab22ad202c846235197935e1df2f8ef7"}, + {file = "xxhash-3.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:924361811732ddad75ff23e90efd9ccfda4f664132feecb90895bade6a1b4623"}, + {file = "xxhash-3.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89997aa1c4b6a5b1e5b588979d1da048a3c6f15e55c11d117a56b75c84531f5a"}, + {file = "xxhash-3.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:685c4f4e8c59837de103344eb1c8a3851f670309eb5c361f746805c5471b8c88"}, + {file = "xxhash-3.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dbd2ecfbfee70bc1a4acb7461fa6af7748ec2ab08ac0fa298f281c51518f982c"}, + {file = "xxhash-3.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:25b5a51dc3dfb20a10833c8eee25903fd2e14059e9afcd329c9da20609a307b2"}, + {file = "xxhash-3.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a8fb786fb754ef6ff8c120cb96629fb518f8eb5a61a16aac3a979a9dbd40a084"}, + {file = "xxhash-3.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a905ad00ad1e1c34fe4e9d7c1d949ab09c6fa90c919860c1534ff479f40fd12d"}, + {file = "xxhash-3.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:963be41bcd49f53af6d795f65c0da9b4cc518c0dd9c47145c98f61cb464f4839"}, + {file = "xxhash-3.5.0-cp311-cp311-win32.whl", hash = "sha256:109b436096d0a2dd039c355fa3414160ec4d843dfecc64a14077332a00aeb7da"}, + {file = "xxhash-3.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:b702f806693201ad6c0a05ddbbe4c8f359626d0b3305f766077d51388a6bac58"}, + {file = "xxhash-3.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:c4dcb4120d0cc3cc448624147dba64e9021b278c63e34a38789b688fd0da9bf3"}, + {file = "xxhash-3.5.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:14470ace8bd3b5d51318782cd94e6f94431974f16cb3b8dc15d52f3b69df8e00"}, + {file = "xxhash-3.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:59aa1203de1cb96dbeab595ded0ad0c0056bb2245ae11fac11c0ceea861382b9"}, + {file = "xxhash-3.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08424f6648526076e28fae6ea2806c0a7d504b9ef05ae61d196d571e5c879c84"}, + {file = "xxhash-3.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:61a1ff00674879725b194695e17f23d3248998b843eb5e933007ca743310f793"}, + {file = "xxhash-3.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f2f2c61bee5844d41c3eb015ac652a0229e901074951ae48581d58bfb2ba01be"}, + {file = "xxhash-3.5.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d32a592cac88d18cc09a89172e1c32d7f2a6e516c3dfde1b9adb90ab5df54a6"}, + {file = "xxhash-3.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70dabf941dede727cca579e8c205e61121afc9b28516752fd65724be1355cc90"}, + {file = "xxhash-3.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e5d0ddaca65ecca9c10dcf01730165fd858533d0be84c75c327487c37a906a27"}, + {file = "xxhash-3.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e5b5e16c5a480fe5f59f56c30abdeba09ffd75da8d13f6b9b6fd224d0b4d0a2"}, + {file = "xxhash-3.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:149b7914451eb154b3dfaa721315117ea1dac2cc55a01bfbd4df7c68c5dd683d"}, + {file = "xxhash-3.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:eade977f5c96c677035ff39c56ac74d851b1cca7d607ab3d8f23c6b859379cab"}, + {file = "xxhash-3.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fa9f547bd98f5553d03160967866a71056a60960be00356a15ecc44efb40ba8e"}, + {file = "xxhash-3.5.0-cp312-cp312-win32.whl", hash = "sha256:f7b58d1fd3551b8c80a971199543379be1cee3d0d409e1f6d8b01c1a2eebf1f8"}, + {file = "xxhash-3.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:fa0cafd3a2af231b4e113fba24a65d7922af91aeb23774a8b78228e6cd785e3e"}, + {file = "xxhash-3.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:586886c7e89cb9828bcd8a5686b12e161368e0064d040e225e72607b43858ba2"}, + {file = "xxhash-3.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:37889a0d13b0b7d739cfc128b1c902f04e32de17b33d74b637ad42f1c55101f6"}, + {file = "xxhash-3.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:97a662338797c660178e682f3bc180277b9569a59abfb5925e8620fba00b9fc5"}, + {file = "xxhash-3.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f85e0108d51092bdda90672476c7d909c04ada6923c14ff9d913c4f7dc8a3bc"}, + {file = "xxhash-3.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd2fd827b0ba763ac919440042302315c564fdb797294d86e8cdd4578e3bc7f3"}, + {file = "xxhash-3.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:82085c2abec437abebf457c1d12fccb30cc8b3774a0814872511f0f0562c768c"}, + {file = "xxhash-3.5.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07fda5de378626e502b42b311b049848c2ef38784d0d67b6f30bb5008642f8eb"}, + {file = "xxhash-3.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c279f0d2b34ef15f922b77966640ade58b4ccdfef1c4d94b20f2a364617a493f"}, + {file = "xxhash-3.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:89e66ceed67b213dec5a773e2f7a9e8c58f64daeb38c7859d8815d2c89f39ad7"}, + {file = "xxhash-3.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bcd51708a633410737111e998ceb3b45d3dbc98c0931f743d9bb0a209033a326"}, + {file = "xxhash-3.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3ff2c0a34eae7df88c868be53a8dd56fbdf592109e21d4bfa092a27b0bf4a7bf"}, + {file = "xxhash-3.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:4e28503dccc7d32e0b9817aa0cbfc1f45f563b2c995b7a66c4c8a0d232e840c7"}, + {file = "xxhash-3.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a6c50017518329ed65a9e4829154626f008916d36295b6a3ba336e2458824c8c"}, + {file = "xxhash-3.5.0-cp313-cp313-win32.whl", hash = "sha256:53a068fe70301ec30d868ece566ac90d873e3bb059cf83c32e76012c889b8637"}, + {file = "xxhash-3.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:80babcc30e7a1a484eab952d76a4f4673ff601f54d5142c26826502740e70b43"}, + {file = "xxhash-3.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:4811336f1ce11cac89dcbd18f3a25c527c16311709a89313c3acaf771def2d4b"}, + {file = "xxhash-3.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6e5f70f6dca1d3b09bccb7daf4e087075ff776e3da9ac870f86ca316736bb4aa"}, + {file = "xxhash-3.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e76e83efc7b443052dd1e585a76201e40b3411fe3da7af4fe434ec51b2f163b"}, + {file = "xxhash-3.5.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:33eac61d0796ca0591f94548dcfe37bb193671e0c9bcf065789b5792f2eda644"}, + {file = "xxhash-3.5.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ec70a89be933ea49222fafc3999987d7899fc676f688dd12252509434636622"}, + {file = "xxhash-3.5.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86b8e7f703ec6ff4f351cfdb9f428955859537125904aa8c963604f2e9d3e7"}, + {file = "xxhash-3.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0adfbd36003d9f86c8c97110039f7539b379f28656a04097e7434d3eaf9aa131"}, + {file = "xxhash-3.5.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:63107013578c8a730419adc05608756c3fa640bdc6abe806c3123a49fb829f43"}, + {file = "xxhash-3.5.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:683b94dbd1ca67557850b86423318a2e323511648f9f3f7b1840408a02b9a48c"}, + {file = "xxhash-3.5.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:5d2a01dcce81789cf4b12d478b5464632204f4c834dc2d064902ee27d2d1f0ee"}, + {file = "xxhash-3.5.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:a9d360a792cbcce2fe7b66b8d51274ec297c53cbc423401480e53b26161a290d"}, + {file = "xxhash-3.5.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:f0b48edbebea1b7421a9c687c304f7b44d0677c46498a046079d445454504737"}, + {file = "xxhash-3.5.0-cp37-cp37m-win32.whl", hash = "sha256:7ccb800c9418e438b44b060a32adeb8393764da7441eb52aa2aa195448935306"}, + {file = "xxhash-3.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c3bc7bf8cb8806f8d1c9bf149c18708cb1c406520097d6b0a73977460ea03602"}, + {file = "xxhash-3.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:74752ecaa544657d88b1d1c94ae68031e364a4d47005a90288f3bab3da3c970f"}, + {file = "xxhash-3.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:dee1316133c9b463aa81aca676bc506d3f80d8f65aeb0bba2b78d0b30c51d7bd"}, + {file = "xxhash-3.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:602d339548d35a8579c6b013339fb34aee2df9b4e105f985443d2860e4d7ffaa"}, + {file = "xxhash-3.5.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:695735deeddfb35da1677dbc16a083445360e37ff46d8ac5c6fcd64917ff9ade"}, + {file = "xxhash-3.5.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1030a39ba01b0c519b1a82f80e8802630d16ab95dc3f2b2386a0b5c8ed5cbb10"}, + {file = "xxhash-3.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5bc08f33c4966f4eb6590d6ff3ceae76151ad744576b5fc6c4ba8edd459fdec"}, + {file = "xxhash-3.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:160e0c19ee500482ddfb5d5570a0415f565d8ae2b3fd69c5dcfce8a58107b1c3"}, + {file = "xxhash-3.5.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:f1abffa122452481a61c3551ab3c89d72238e279e517705b8b03847b1d93d738"}, + {file = "xxhash-3.5.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:d5e9db7ef3ecbfc0b4733579cea45713a76852b002cf605420b12ef3ef1ec148"}, + {file = "xxhash-3.5.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:23241ff6423378a731d84864bf923a41649dc67b144debd1077f02e6249a0d54"}, + {file = "xxhash-3.5.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:82b833d5563fefd6fceafb1aed2f3f3ebe19f84760fdd289f8b926731c2e6e91"}, + {file = "xxhash-3.5.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0a80ad0ffd78bef9509eee27b4a29e56f5414b87fb01a888353e3d5bda7038bd"}, + {file = "xxhash-3.5.0-cp38-cp38-win32.whl", hash = "sha256:50ac2184ffb1b999e11e27c7e3e70cc1139047e7ebc1aa95ed12f4269abe98d4"}, + {file = "xxhash-3.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:392f52ebbb932db566973693de48f15ce787cabd15cf6334e855ed22ea0be5b3"}, + {file = "xxhash-3.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bfc8cdd7f33d57f0468b0614ae634cc38ab9202c6957a60e31d285a71ebe0301"}, + {file = "xxhash-3.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e0c48b6300cd0b0106bf49169c3e0536408dfbeb1ccb53180068a18b03c662ab"}, + {file = "xxhash-3.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe1a92cfbaa0a1253e339ccec42dbe6db262615e52df591b68726ab10338003f"}, + {file = "xxhash-3.5.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:33513d6cc3ed3b559134fb307aae9bdd94d7e7c02907b37896a6c45ff9ce51bd"}, + {file = "xxhash-3.5.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eefc37f6138f522e771ac6db71a6d4838ec7933939676f3753eafd7d3f4c40bc"}, + {file = "xxhash-3.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a606c8070ada8aa2a88e181773fa1ef17ba65ce5dd168b9d08038e2a61b33754"}, + {file = "xxhash-3.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:42eca420c8fa072cc1dd62597635d140e78e384a79bb4944f825fbef8bfeeef6"}, + {file = "xxhash-3.5.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:604253b2143e13218ff1ef0b59ce67f18b8bd1c4205d2ffda22b09b426386898"}, + {file = "xxhash-3.5.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:6e93a5ad22f434d7876665444a97e713a8f60b5b1a3521e8df11b98309bff833"}, + {file = "xxhash-3.5.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:7a46e1d6d2817ba8024de44c4fd79913a90e5f7265434cef97026215b7d30df6"}, + {file = "xxhash-3.5.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:30eb2efe6503c379b7ab99c81ba4a779748e3830241f032ab46bd182bf5873af"}, + {file = "xxhash-3.5.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c8aa771ff2c13dd9cda8166d685d7333d389fae30a4d2bb39d63ab5775de8606"}, + {file = "xxhash-3.5.0-cp39-cp39-win32.whl", hash = "sha256:5ed9ebc46f24cf91034544b26b131241b699edbfc99ec5e7f8f3d02d6eb7fba4"}, + {file = "xxhash-3.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:220f3f896c6b8d0316f63f16c077d52c412619e475f9372333474ee15133a558"}, + {file = "xxhash-3.5.0-cp39-cp39-win_arm64.whl", hash = "sha256:a7b1d8315d9b5e9f89eb2933b73afae6ec9597a258d52190944437158b49d38e"}, + {file = "xxhash-3.5.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:2014c5b3ff15e64feecb6b713af12093f75b7926049e26a580e94dcad3c73d8c"}, + {file = "xxhash-3.5.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fab81ef75003eda96239a23eda4e4543cedc22e34c373edcaf744e721a163986"}, + {file = "xxhash-3.5.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e2febf914ace002132aa09169cc572e0d8959d0f305f93d5828c4836f9bc5a6"}, + {file = "xxhash-3.5.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5d3a10609c51da2a1c0ea0293fc3968ca0a18bd73838455b5bca3069d7f8e32b"}, + {file = "xxhash-3.5.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:5a74f23335b9689b66eb6dbe2a931a88fcd7a4c2cc4b1cb0edba8ce381c7a1da"}, + {file = "xxhash-3.5.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2b4154c00eb22e4d543f472cfca430e7962a0f1d0f3778334f2e08a7ba59363c"}, + {file = "xxhash-3.5.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d30bbc1644f726b825b3278764240f449d75f1a8bdda892e641d4a688b1494ae"}, + {file = "xxhash-3.5.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fa0b72f2423e2aa53077e54a61c28e181d23effeaafd73fcb9c494e60930c8e"}, + {file = "xxhash-3.5.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:13de2b76c1835399b2e419a296d5b38dc4855385d9e96916299170085ef72f57"}, + {file = "xxhash-3.5.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:0691bfcc4f9c656bcb96cc5db94b4d75980b9d5589f2e59de790091028580837"}, + {file = "xxhash-3.5.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:297595fe6138d4da2c8ce9e72a04d73e58725bb60f3a19048bc96ab2ff31c692"}, + {file = "xxhash-3.5.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc1276d369452040cbb943300dc8abeedab14245ea44056a2943183822513a18"}, + {file = "xxhash-3.5.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2061188a1ba352fc699c82bff722f4baacb4b4b8b2f0c745d2001e56d0dfb514"}, + {file = "xxhash-3.5.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:38c384c434021e4f62b8d9ba0bc9467e14d394893077e2c66d826243025e1f81"}, + {file = "xxhash-3.5.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e6a4dd644d72ab316b580a1c120b375890e4c52ec392d4aef3c63361ec4d77d1"}, + {file = "xxhash-3.5.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:531af8845aaadcadf951b7e0c1345c6b9c68a990eeb74ff9acd8501a0ad6a1c9"}, + {file = "xxhash-3.5.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ce379bcaa9fcc00f19affa7773084dd09f5b59947b3fb47a1ceb0179f91aaa1"}, + {file = "xxhash-3.5.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd1b2281d01723f076df3c8188f43f2472248a6b63118b036e641243656b1b0f"}, + {file = "xxhash-3.5.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9c770750cc80e8694492244bca7251385188bc5597b6a39d98a9f30e8da984e0"}, + {file = "xxhash-3.5.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b150b8467852e1bd844387459aa6fbe11d7f38b56e901f9f3b3e6aba0d660240"}, + {file = "xxhash-3.5.0.tar.gz", hash = "sha256:84f2caddf951c9cbf8dc2e22a89d4ccf5d86391ac6418fe81e3c67d0cf60b45f"}, +] + +[[package]] +name = "yarl" +version = "1.20.0" +description = "Yet another URL library" +optional = false +python-versions = ">=3.9" +groups = ["main", "dev"] +files = [ + {file = "yarl-1.20.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f1f6670b9ae3daedb325fa55fbe31c22c8228f6e0b513772c2e1c623caa6ab22"}, + {file = "yarl-1.20.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:85a231fa250dfa3308f3c7896cc007a47bc76e9e8e8595c20b7426cac4884c62"}, + {file = "yarl-1.20.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1a06701b647c9939d7019acdfa7ebbfbb78ba6aa05985bb195ad716ea759a569"}, + {file = "yarl-1.20.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7595498d085becc8fb9203aa314b136ab0516c7abd97e7d74f7bb4eb95042abe"}, + {file = "yarl-1.20.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:af5607159085dcdb055d5678fc2d34949bd75ae6ea6b4381e784bbab1c3aa195"}, + {file = "yarl-1.20.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:95b50910e496567434cb77a577493c26bce0f31c8a305135f3bda6a2483b8e10"}, + {file = "yarl-1.20.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b594113a301ad537766b4e16a5a6750fcbb1497dcc1bc8a4daae889e6402a634"}, + {file = "yarl-1.20.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:083ce0393ea173cd37834eb84df15b6853b555d20c52703e21fbababa8c129d2"}, + {file = "yarl-1.20.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f1a350a652bbbe12f666109fbddfdf049b3ff43696d18c9ab1531fbba1c977a"}, + {file = "yarl-1.20.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fb0caeac4a164aadce342f1597297ec0ce261ec4532bbc5a9ca8da5622f53867"}, + {file = "yarl-1.20.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:d88cc43e923f324203f6ec14434fa33b85c06d18d59c167a0637164863b8e995"}, + {file = "yarl-1.20.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e52d6ed9ea8fd3abf4031325dc714aed5afcbfa19ee4a89898d663c9976eb487"}, + {file = "yarl-1.20.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ce360ae48a5e9961d0c730cf891d40698a82804e85f6e74658fb175207a77cb2"}, + {file = "yarl-1.20.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:06d06c9d5b5bc3eb56542ceeba6658d31f54cf401e8468512447834856fb0e61"}, + {file = "yarl-1.20.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c27d98f4e5c4060582f44e58309c1e55134880558f1add7a87c1bc36ecfade19"}, + {file = "yarl-1.20.0-cp310-cp310-win32.whl", hash = "sha256:f4d3fa9b9f013f7050326e165c3279e22850d02ae544ace285674cb6174b5d6d"}, + {file = "yarl-1.20.0-cp310-cp310-win_amd64.whl", hash = "sha256:bc906b636239631d42eb8a07df8359905da02704a868983265603887ed68c076"}, + {file = "yarl-1.20.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fdb5204d17cb32b2de2d1e21c7461cabfacf17f3645e4b9039f210c5d3378bf3"}, + {file = "yarl-1.20.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:eaddd7804d8e77d67c28d154ae5fab203163bd0998769569861258e525039d2a"}, + {file = "yarl-1.20.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:634b7ba6b4a85cf67e9df7c13a7fb2e44fa37b5d34501038d174a63eaac25ee2"}, + {file = "yarl-1.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d409e321e4addf7d97ee84162538c7258e53792eb7c6defd0c33647d754172e"}, + {file = "yarl-1.20.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:ea52f7328a36960ba3231c6677380fa67811b414798a6e071c7085c57b6d20a9"}, + {file = "yarl-1.20.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c8703517b924463994c344dcdf99a2d5ce9eca2b6882bb640aa555fb5efc706a"}, + {file = "yarl-1.20.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:077989b09ffd2f48fb2d8f6a86c5fef02f63ffe6b1dd4824c76de7bb01e4f2e2"}, + {file = "yarl-1.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0acfaf1da020253f3533526e8b7dd212838fdc4109959a2c53cafc6db611bff2"}, + {file = "yarl-1.20.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b4230ac0b97ec5eeb91d96b324d66060a43fd0d2a9b603e3327ed65f084e41f8"}, + {file = "yarl-1.20.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a6a1e6ae21cdd84011c24c78d7a126425148b24d437b5702328e4ba640a8902"}, + {file = "yarl-1.20.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:86de313371ec04dd2531f30bc41a5a1a96f25a02823558ee0f2af0beaa7ca791"}, + {file = "yarl-1.20.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:dd59c9dd58ae16eaa0f48c3d0cbe6be8ab4dc7247c3ff7db678edecbaf59327f"}, + {file = "yarl-1.20.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a0bc5e05f457b7c1994cc29e83b58f540b76234ba6b9648a4971ddc7f6aa52da"}, + {file = "yarl-1.20.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c9471ca18e6aeb0e03276b5e9b27b14a54c052d370a9c0c04a68cefbd1455eb4"}, + {file = "yarl-1.20.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:40ed574b4df723583a26c04b298b283ff171bcc387bc34c2683235e2487a65a5"}, + {file = "yarl-1.20.0-cp311-cp311-win32.whl", hash = "sha256:db243357c6c2bf3cd7e17080034ade668d54ce304d820c2a58514a4e51d0cfd6"}, + {file = "yarl-1.20.0-cp311-cp311-win_amd64.whl", hash = "sha256:8c12cd754d9dbd14204c328915e23b0c361b88f3cffd124129955e60a4fbfcfb"}, + {file = "yarl-1.20.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e06b9f6cdd772f9b665e5ba8161968e11e403774114420737f7884b5bd7bdf6f"}, + {file = "yarl-1.20.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b9ae2fbe54d859b3ade40290f60fe40e7f969d83d482e84d2c31b9bff03e359e"}, + {file = "yarl-1.20.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6d12b8945250d80c67688602c891237994d203d42427cb14e36d1a732eda480e"}, + {file = "yarl-1.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:087e9731884621b162a3e06dc0d2d626e1542a617f65ba7cc7aeab279d55ad33"}, + {file = "yarl-1.20.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:69df35468b66c1a6e6556248e6443ef0ec5f11a7a4428cf1f6281f1879220f58"}, + {file = "yarl-1.20.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b2992fe29002fd0d4cbaea9428b09af9b8686a9024c840b8a2b8f4ea4abc16f"}, + {file = "yarl-1.20.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4c903e0b42aab48abfbac668b5a9d7b6938e721a6341751331bcd7553de2dcae"}, + {file = "yarl-1.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf099e2432131093cc611623e0b0bcc399b8cddd9a91eded8bfb50402ec35018"}, + {file = "yarl-1.20.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8a7f62f5dc70a6c763bec9ebf922be52aa22863d9496a9a30124d65b489ea672"}, + {file = "yarl-1.20.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:54ac15a8b60382b2bcefd9a289ee26dc0920cf59b05368c9b2b72450751c6eb8"}, + {file = "yarl-1.20.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:25b3bc0763a7aca16a0f1b5e8ef0f23829df11fb539a1b70476dcab28bd83da7"}, + {file = "yarl-1.20.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b2586e36dc070fc8fad6270f93242124df68b379c3a251af534030a4a33ef594"}, + {file = "yarl-1.20.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:866349da9d8c5290cfefb7fcc47721e94de3f315433613e01b435473be63daa6"}, + {file = "yarl-1.20.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:33bb660b390a0554d41f8ebec5cd4475502d84104b27e9b42f5321c5192bfcd1"}, + {file = "yarl-1.20.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:737e9f171e5a07031cbee5e9180f6ce21a6c599b9d4b2c24d35df20a52fabf4b"}, + {file = "yarl-1.20.0-cp312-cp312-win32.whl", hash = "sha256:839de4c574169b6598d47ad61534e6981979ca2c820ccb77bf70f4311dd2cc64"}, + {file = "yarl-1.20.0-cp312-cp312-win_amd64.whl", hash = "sha256:3d7dbbe44b443b0c4aa0971cb07dcb2c2060e4a9bf8d1301140a33a93c98e18c"}, + {file = "yarl-1.20.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:2137810a20b933b1b1b7e5cf06a64c3ed3b4747b0e5d79c9447c00db0e2f752f"}, + {file = "yarl-1.20.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:447c5eadd750db8389804030d15f43d30435ed47af1313303ed82a62388176d3"}, + {file = "yarl-1.20.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:42fbe577272c203528d402eec8bf4b2d14fd49ecfec92272334270b850e9cd7d"}, + {file = "yarl-1.20.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18e321617de4ab170226cd15006a565d0fa0d908f11f724a2c9142d6b2812ab0"}, + {file = "yarl-1.20.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:4345f58719825bba29895011e8e3b545e6e00257abb984f9f27fe923afca2501"}, + {file = "yarl-1.20.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5d9b980d7234614bc4674468ab173ed77d678349c860c3af83b1fffb6a837ddc"}, + {file = "yarl-1.20.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af4baa8a445977831cbaa91a9a84cc09debb10bc8391f128da2f7bd070fc351d"}, + {file = "yarl-1.20.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:123393db7420e71d6ce40d24885a9e65eb1edefc7a5228db2d62bcab3386a5c0"}, + {file = "yarl-1.20.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab47acc9332f3de1b39e9b702d9c916af7f02656b2a86a474d9db4e53ef8fd7a"}, + {file = "yarl-1.20.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4a34c52ed158f89876cba9c600b2c964dfc1ca52ba7b3ab6deb722d1d8be6df2"}, + {file = "yarl-1.20.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:04d8cfb12714158abf2618f792c77bc5c3d8c5f37353e79509608be4f18705c9"}, + {file = "yarl-1.20.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7dc63ad0d541c38b6ae2255aaa794434293964677d5c1ec5d0116b0e308031f5"}, + {file = "yarl-1.20.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f9d02b591a64e4e6ca18c5e3d925f11b559c763b950184a64cf47d74d7e41877"}, + {file = "yarl-1.20.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:95fc9876f917cac7f757df80a5dda9de59d423568460fe75d128c813b9af558e"}, + {file = "yarl-1.20.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bb769ae5760cd1c6a712135ee7915f9d43f11d9ef769cb3f75a23e398a92d384"}, + {file = "yarl-1.20.0-cp313-cp313-win32.whl", hash = "sha256:70e0c580a0292c7414a1cead1e076c9786f685c1fc4757573d2967689b370e62"}, + {file = "yarl-1.20.0-cp313-cp313-win_amd64.whl", hash = "sha256:4c43030e4b0af775a85be1fa0433119b1565673266a70bf87ef68a9d5ba3174c"}, + {file = "yarl-1.20.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b6c4c3d0d6a0ae9b281e492b1465c72de433b782e6b5001c8e7249e085b69051"}, + {file = "yarl-1.20.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8681700f4e4df891eafa4f69a439a6e7d480d64e52bf460918f58e443bd3da7d"}, + {file = "yarl-1.20.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:84aeb556cb06c00652dbf87c17838eb6d92cfd317799a8092cee0e570ee11229"}, + {file = "yarl-1.20.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f166eafa78810ddb383e930d62e623d288fb04ec566d1b4790099ae0f31485f1"}, + {file = "yarl-1.20.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:5d3d6d14754aefc7a458261027a562f024d4f6b8a798adb472277f675857b1eb"}, + {file = "yarl-1.20.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2a8f64df8ed5d04c51260dbae3cc82e5649834eebea9eadfd829837b8093eb00"}, + {file = "yarl-1.20.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4d9949eaf05b4d30e93e4034a7790634bbb41b8be2d07edd26754f2e38e491de"}, + {file = "yarl-1.20.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c366b254082d21cc4f08f522ac201d0d83a8b8447ab562732931d31d80eb2a5"}, + {file = "yarl-1.20.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:91bc450c80a2e9685b10e34e41aef3d44ddf99b3a498717938926d05ca493f6a"}, + {file = "yarl-1.20.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9c2aa4387de4bc3a5fe158080757748d16567119bef215bec643716b4fbf53f9"}, + {file = "yarl-1.20.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:d2cbca6760a541189cf87ee54ff891e1d9ea6406079c66341008f7ef6ab61145"}, + {file = "yarl-1.20.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:798a5074e656f06b9fad1a162be5a32da45237ce19d07884d0b67a0aa9d5fdda"}, + {file = "yarl-1.20.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:f106e75c454288472dbe615accef8248c686958c2e7dd3b8d8ee2669770d020f"}, + {file = "yarl-1.20.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:3b60a86551669c23dc5445010534d2c5d8a4e012163218fc9114e857c0586fdd"}, + {file = "yarl-1.20.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3e429857e341d5e8e15806118e0294f8073ba9c4580637e59ab7b238afca836f"}, + {file = "yarl-1.20.0-cp313-cp313t-win32.whl", hash = "sha256:65a4053580fe88a63e8e4056b427224cd01edfb5f951498bfefca4052f0ce0ac"}, + {file = "yarl-1.20.0-cp313-cp313t-win_amd64.whl", hash = "sha256:53b2da3a6ca0a541c1ae799c349788d480e5144cac47dba0266c7cb6c76151fe"}, + {file = "yarl-1.20.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:119bca25e63a7725b0c9d20ac67ca6d98fa40e5a894bd5d4686010ff73397914"}, + {file = "yarl-1.20.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:35d20fb919546995f1d8c9e41f485febd266f60e55383090010f272aca93edcc"}, + {file = "yarl-1.20.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:484e7a08f72683c0f160270566b4395ea5412b4359772b98659921411d32ad26"}, + {file = "yarl-1.20.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d8a3d54a090e0fff5837cd3cc305dd8a07d3435a088ddb1f65e33b322f66a94"}, + {file = "yarl-1.20.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:f0cf05ae2d3d87a8c9022f3885ac6dea2b751aefd66a4f200e408a61ae9b7f0d"}, + {file = "yarl-1.20.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a884b8974729e3899d9287df46f015ce53f7282d8d3340fa0ed57536b440621c"}, + {file = "yarl-1.20.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f8d8aa8dd89ffb9a831fedbcb27d00ffd9f4842107d52dc9d57e64cb34073d5c"}, + {file = "yarl-1.20.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b4e88d6c3c8672f45a30867817e4537df1bbc6f882a91581faf1f6d9f0f1b5a"}, + {file = "yarl-1.20.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bdb77efde644d6f1ad27be8a5d67c10b7f769804fff7a966ccb1da5a4de4b656"}, + {file = "yarl-1.20.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4ba5e59f14bfe8d261a654278a0f6364feef64a794bd456a8c9e823071e5061c"}, + {file = "yarl-1.20.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:d0bf955b96ea44ad914bc792c26a0edcd71b4668b93cbcd60f5b0aeaaed06c64"}, + {file = "yarl-1.20.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:27359776bc359ee6eaefe40cb19060238f31228799e43ebd3884e9c589e63b20"}, + {file = "yarl-1.20.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:04d9c7a1dc0a26efb33e1acb56c8849bd57a693b85f44774356c92d610369efa"}, + {file = "yarl-1.20.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:faa709b66ae0e24c8e5134033187a972d849d87ed0a12a0366bedcc6b5dc14a5"}, + {file = "yarl-1.20.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:44869ee8538208fe5d9342ed62c11cc6a7a1af1b3d0bb79bb795101b6e77f6e0"}, + {file = "yarl-1.20.0-cp39-cp39-win32.whl", hash = "sha256:b7fa0cb9fd27ffb1211cde944b41f5c67ab1c13a13ebafe470b1e206b8459da8"}, + {file = "yarl-1.20.0-cp39-cp39-win_amd64.whl", hash = "sha256:d4fad6e5189c847820288286732075f213eabf81be4d08d6cc309912e62be5b7"}, + {file = "yarl-1.20.0-py3-none-any.whl", hash = "sha256:5d0fe6af927a47a230f31e6004621fd0959eaa915fc62acfafa67ff7229a3124"}, + {file = "yarl-1.20.0.tar.gz", hash = "sha256:686d51e51ee5dfe62dec86e4866ee0e9ed66df700d55c828a615640adc885307"}, +] + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" +propcache = ">=0.2.1" + [[package]] name = "zipp" version = "3.21.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"}, {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"}, @@ -1723,4 +4351,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = ">=3.10,<3.13" -content-hash = "cff385f0c49e113ea0857182fae6daa8bbd859f47149eb92fe4a3dc9d674ffa2" +content-hash = "4b25745b97ea325431c42046f891900e49ae922433a4b64733ee7a8c1bf2cb60" diff --git a/pyproject.toml b/pyproject.toml index 301f6c5d..074e17b8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "dreadnode" -version = "0.1.4" +version = "1.0.0-rc.0" description = "Dreadnode SDK" requires-python = ">=3.10,<3.13" @@ -53,15 +53,25 @@ fast-depends = "^2.4.12" coolname = "^2.2.0" pandas = "^2.2.3" pyarrow = "^19.0.1" +loguru = "^0.7.3" +fsspec = { extras = [ + "s3", +], version = "2024.12.0" } # pinned this version to be compatible with datasets +pydub = "^0.25.1" +moviepy = "^2.1.2" +datasets = "^3.5.0" [tool.poetry.group.dev.dependencies] mypy = "^1.8.0" -ruff = "^0.11.4" -pre-commit = "^4.1.0" +ruff = "^0.11.6" +pre-commit = "^3.8.0" pytest = "^8.3.3" -pytest-asyncio = "^0.26.0" +pytest-asyncio = "^0.24.0" types-protobuf = "^5.29.1.20250208" -detect-secrets = "^1.5.0" +pandas-stubs = "^2.2.3.250308" +types-requests = "^2.32.0.20250306" +rigging = "^2.3.0" +typer = "^0.15.2" [tool.pytest.ini_options] asyncio_mode = "auto" @@ -73,51 +83,39 @@ logo_url = "https://d1lppblt9t2x15.cloudfront.net/logos/5714928f3cdc09503751580c tagline = "with batteries included 🔋" emoji = "🐍" - [tool.ruff] -line-length = 120 -indent-width = 4 target-version = "py310" -exclude = [ - ".git", - ".git-rewrite", - ".mypy_cache", - ".ruff_cache", - "__pypackages__", - "build", - "dist", - ".venv", - "venv", +line-length = 100 +extend-exclude = [ + "*.ipynb", # jupyter notebooks ] [tool.ruff.lint] -dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" -fixable = ["ALL"] +select = ["ALL"] ignore = [ - "E501", # line too long, handled by ruff - "B008", # do not perform function calls in argument defaults - "C901", # too complex - "W191", # indentation contains tabs - "F722", # syntax error in forward annotation - "A001", # shadowing built-in - "A002", # shadowing built-in + "E501", # line too long (we make best effort) + "TRY003", # long messages in exception classes + "EM", # picky message construction for exceptions + "C90", # mccabe complexity + "A002", # shadowing built-in + "D", # docstrings + "ANN", # annotations (handled by mypy) + "PLR0913", # too many arguments + "ERA001", # commented out code + "FIX002", # contains todo, consider fixing + "COM812", # disabled for formatting + "ISC001", # disabled for formatting ] -select = [ - "E", # pycodestyle errors - "W", # pycodestyle warnings - "F", # pyflakes - "I", # isort - "C", # flake8-comprehensions - "B", # flake8-bugbear - "UP", # pyupgrade - "NPY", # numpydoc - "A", # flake8-annotations -] -unfixable = ["B"] - [tool.ruff.format] -quote-style = "double" -indent-style = "space" skip-magic-trailing-comma = false -line-ending = "auto" + +[tool.ruff.lint.extend-per-file-ignores] +".hooks/**/*.py" = [ + "ARG001", # temporary for rigging decorators + "T201", # printing is fine for hooks +] +"tests/**/*.py" = [ + "INP001", # namespace not required for pytest + "S101", # asserts allowed in tests... +] diff --git a/scripts/generate_readme.py b/scripts/generate_readme.py deleted file mode 100755 index 9d837318..00000000 --- a/scripts/generate_readme.py +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/python3 -import argparse - -import tomli -from jinja2 import Environment, FileSystemLoader - - -def load_project_metadata() -> dict[str, str]: - with open("pyproject.toml", "rb") as f: - pyproject = tomli.load(f) - return { - **pyproject["project"], - **pyproject.get("tool", {}).get("readme", {}), - } - - -def read_file(path: str) -> str: - with open(path) as f: - return f.read() - - -def write_file(path: str, content: str) -> None: - with open(path, "w") as f: - f.write(content) - - -def get_section_content(content: str, start_marker: str, end_marker: str) -> tuple[str, int, int]: - start_idx = content.find(start_marker) - if start_idx == -1: - return "", -1, -1 - end_idx = content.find(end_marker, start_idx) - if end_idx == -1: - return "", -1, -1 - return (content[start_idx : end_idx + len(end_marker)], start_idx, end_idx + len(end_marker)) - - -def main() -> None: - parser = argparse.ArgumentParser() - parser.add_argument("--dry-run", action="store_true", help="Print to stdout instead of writing file") - # Ignore unknown arguments (like filenames passed by pre-commit) - args, _ = parser.parse_known_args() - - # Setup Jinja environment - env = Environment(loader=FileSystemLoader("templates"), trim_blocks=True, lstrip_blocks=True, autoescape=True) - template = env.get_template("README.md.j2") - - # Load project metadata - metadata = load_project_metadata() - - # Default values - context = { - "project_name": metadata.get("name", "Project Name"), - "description": metadata.get("description", "A Python Project"), - "github_org": "organization", # Could be loaded from git config - "repo_name": metadata.get("name", "repository"), - "emoji": "🐍", # Default emoji - "logo_url": None, # Optional logo - "tagline": metadata.get("tagline", "with batteries included 🔋"), - } - - # Render template - new_content = template.render(**context) - - # Read existing README - try: - existing_content = read_file("README.md") - except FileNotFoundError: - existing_content = "" - - # Find auto-generated sections and replace - for marker_pair in [ - ("", ""), - # Add other marker pairs here as needed - ]: - start_marker, end_marker = marker_pair - new_section, start_idx, end_idx = get_section_content(new_content, start_marker, end_marker) - if not new_section: - continue - - existing_section, existing_start, existing_end = get_section_content(existing_content, start_marker, end_marker) - if existing_start >= 0: - # Replace existing section - existing_content = existing_content[:existing_start] + new_section + existing_content[existing_end:] - else: - # Add new section at the top after the first heading - first_heading_end = existing_content.find("\n", existing_content.find("#")) - if first_heading_end >= 0: - existing_content = ( - existing_content[: first_heading_end + 1] - + "\n" - + new_section - + "\n" - + existing_content[first_heading_end + 1 :] - ) - - if args.dry_run: - print(existing_content) - else: - write_file("README.md", existing_content) - - -if __name__ == "__main__": - main() diff --git a/templates/README.md.j2 b/templates/README.md.j2 deleted file mode 100644 index 9bb6324e..00000000 --- a/templates/README.md.j2 +++ /dev/null @@ -1,13 +0,0 @@ -# {{ project_name }} {% if emoji %}{{ emoji }}{% endif %} - - -
- -[![Pre-Commit](https://github.com/dreadnode/python-template/actions/workflows/pre-commit.yaml/badge.svg)](https://github.com/dreadnode/python-template/actions/workflows/pre-commit.yaml) -[![Renovate](https://github.com/dreadnode/python-template/actions/workflows/renovate.yaml/badge.svg)](https://github.com/dreadnode/python-template/actions/workflows/renovate.yaml) - -
- -{% raw %} -// ...rest of the existing README content... -{% endraw %}