From 55c5b3f38ae66828b10e9ee01856a257f8771880 Mon Sep 17 00:00:00 2001 From: monoxgas Date: Wed, 5 Mar 2025 17:38:33 +0000 Subject: [PATCH 01/24] In the middle of updates --- dreadnode/artifact.py | 166 +++++++++++++++++++++++++++++++++++++++++ dreadnode/constants.py | 18 ++++- dreadnode/context.py | 6 +- dreadnode/main.py | 65 +++++++++------- dreadnode/score.py | 70 ++++++----------- dreadnode/storage.py | 164 ++++++++++++++++++++++++++++++++++++++++ dreadnode/tracing.py | 129 +++++++++++++++----------------- pyproject.toml | 2 +- 8 files changed, 469 insertions(+), 151 deletions(-) create mode 100644 dreadnode/artifact.py create mode 100644 dreadnode/storage.py diff --git a/dreadnode/artifact.py b/dreadnode/artifact.py new file mode 100644 index 00000000..affa941a --- /dev/null +++ b/dreadnode/artifact.py @@ -0,0 +1,166 @@ +from __future__ import annotations + +import os +from abc import ABC, abstractmethod +from dataclasses import dataclass +from pathlib import Path + +# --- Artifact Core --- + + +@dataclass +class ArtifactInfo: + """Metadata about an artifact.""" + + path: str # Relative path within the run + size: int = 0 + is_dir: bool = False + file_format: str | None = None # Optional format if known + + +@dataclass +class Artifact: + """Represents an artifact associated with a run.""" + + path: str # Relative path within the run + description: str = "" + + # Source of the artifact (only one should be set) + local_path: Path | None = None # Path to local file/dir to upload + content: bytes | None = None # In-memory content + + # Flag for eager saving + eager: bool = False + + @property + def is_dir(self) -> bool: + """Check if this artifact represents a directory.""" + return self.local_path is not None and self.local_path.is_dir() + + +# --- Storage Abstraction --- + + +class ArtifactRepository(ABC): + """Interface for artifact storage systems.""" + + @abstractmethod + def log_artifact(self, run_id: str, artifact: Artifact) -> None: + """Save an artifact to the repository.""" + pass + + @abstractmethod + def log_artifacts(self, run_id: str, local_dir: Path, artifact_path: str | None = None) -> None: + """Save a directory of artifacts.""" + pass + + @abstractmethod + def list_artifacts(self, run_id: str, path: str | None = None) -> list[ArtifactInfo]: + """List artifacts for a run.""" + pass + + @abstractmethod + def download_artifact(self, run_id: str, path: str) -> Path: + """Download an artifact to a local path and return the path.""" + pass + + @abstractmethod + def get_artifact_uri(self, run_id: str, path: str | None = None) -> str: + """Get the URI for an artifact.""" + pass + + +class LocalArtifactRepository(ArtifactRepository): + """Store artifacts in the local filesystem.""" + + def __init__(self, base_path: Path | str): + self.base_path = Path(base_path) + self.base_path.mkdir(parents=True, exist_ok=True) + + def _get_run_artifact_dir(self, run_id: str) -> Path: + """Get the artifact directory for a run.""" + run_dir = self.base_path / run_id / "artifacts" + run_dir.mkdir(parents=True, exist_ok=True) + return run_dir + + def log_artifact(self, run_id: str, artifact: Artifact) -> None: + """Save an artifact to the repository.""" + artifact_dir = self._get_run_artifact_dir(run_id) + + # Handle artifact path with possible subdirectories + if artifact.path: + dest_dir = artifact_dir / os.path.dirname(artifact.path) + dest_dir.mkdir(parents=True, exist_ok=True) + dest_path = artifact_dir / artifact.path + else: + dest_path = artifact_dir / os.path.basename(str(artifact.local_path or "artifact")) + + # Save the artifact + if artifact.local_path: + if artifact.local_path.is_dir(): + self.log_artifacts(run_id, artifact.local_path, artifact.path) + else: + with open(artifact.local_path, "rb") as src, open(dest_path, "wb") as dst: + dst.write(src.read()) + elif artifact.content: + with open(dest_path, "wb") as f: + f.write(artifact.content) + else: + raise ValueError("Artifact has no content to save") + + def log_artifacts(self, run_id: str, local_dir: Path, artifact_path: str | None = None) -> None: + """Save a directory of artifacts.""" + artifact_dir = self._get_run_artifact_dir(run_id) + + if artifact_path: + dest_dir = artifact_dir / artifact_path + else: + dest_dir = artifact_dir + + dest_dir.mkdir(parents=True, exist_ok=True) + + for root, _, files in os.walk(local_dir): + for file in files: + src_path = Path(root) / file + rel_path = src_path.relative_to(local_dir) + dst_path = dest_dir / rel_path + + dst_path.parent.mkdir(parents=True, exist_ok=True) + with open(src_path, "rb") as src, open(dst_path, "wb") as dst: + dst.write(src.read()) + + def list_artifacts(self, run_id: str, path: str | None = None) -> list[ArtifactInfo]: + """List artifacts for a run.""" + artifact_dir = self._get_run_artifact_dir(run_id) + + if path: + list_dir = artifact_dir / path + else: + list_dir = artifact_dir + + if not list_dir.exists(): + return [] + + artifact_infos = [] + for item in list_dir.iterdir(): + rel_path = item.relative_to(artifact_dir) + info = ArtifactInfo( + path=str(rel_path), + size=item.stat().st_size if item.is_file() else 0, + is_dir=item.is_dir(), + ) + artifact_infos.append(info) + + return artifact_infos + + def download_artifact(self, run_id: str, path: str) -> Path: + """Download an artifact (local copy for consistency with remote repos).""" + artifact_path = self._get_run_artifact_dir(run_id) / path + return artifact_path + + def get_artifact_uri(self, run_id: str, path: str | None = None) -> str: + """Get the URI for an artifact.""" + if path: + return f"file://{self._get_run_artifact_dir(run_id) / path}" + else: + return f"file://{self._get_run_artifact_dir(run_id)}" diff --git a/dreadnode/constants.py b/dreadnode/constants.py index 28e8e297..ae545fd5 100644 --- a/dreadnode/constants.py +++ b/dreadnode/constants.py @@ -2,6 +2,8 @@ import typing as t +# Span attributes + SPAN_NAMESPACE = "dreadnode" SpanType = t.Literal["run", "task", "span", "run_update"] @@ -13,17 +15,27 @@ 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_ATTRIBUTES_RUN_INPUTS_KEY = f"{SPAN_NAMESPACE}.run.inputs" +SPAN_ATTRIBUTE_RUN_METRICS_KEY = f"{SPAN_NAMESPACE}.run.metrics" 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_PARAMS_KEY = f"{SPAN_NAMESPACE}.task.params" +SPAN_ATTRIBUTE_TASK_INPUTS_KEY = f"{SPAN_NAMESPACE}.task.inputs" +SPAN_ATTRIBUTE_TASK_METRICS_KEY = f"{SPAN_NAMESPACE}.task.metrics" SPAN_ATTRIBUTE_TASK_OUTPUT_KEY = f"{SPAN_NAMESPACE}.task.output" -SPAN_ATTRIBUTE_TASK_SCORES_KEY = f"{SPAN_NAMESPACE}.task.scores" + +SPAN_ATTRIBUTE_TASK_ARGS_KEY = f"{SPAN_NAMESPACE}.task.args" # deprecated +SPAN_ATTRIBUTE_TASK_SCORES_KEY = f"{SPAN_NAMESPACE}.task.scores" # deprecated # Environment variable names + ENV_SERVER_URL = "DREADNODE_SERVER_URL" +ENV_SERVER = "DREADNODE_SERVER" # alternative to SERVER_URL ENV_API_TOKEN = "DREADNODE_API_TOKEN" +ENV_API_KEY = "DREADNODE_API_KEY" # alternative to API_TOKEN ENV_LOCAL_DIR = "DREADNODE_LOCAL_DIR" ENV_PROJECT = "DREADNODE_PROJECT" diff --git a/dreadnode/context.py b/dreadnode/context.py index fd194d9a..884086af 100644 --- a/dreadnode/context.py +++ b/dreadnode/context.py @@ -18,10 +18,10 @@ def __init__( id_: str | None = None, strict: bool = True, default: Any = None, - ): + ) -> None: self.id_ = id_ self.default = default - super().__init__(cast=True, required=strict and default is None) + super().__init__(cast=False, required=strict and default is None) def use(self, **kwargs: Any) -> dict[str, Any]: """Resolve the value from current context when parameter is needed.""" @@ -99,7 +99,6 @@ def get(self, type_: type[T], id_: str | list[str] | None = None, strict: bool = 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 @@ -160,7 +159,6 @@ def __exit__(self, *exc: Any) -> None: self.context._scoped_context[key] = value -# Global context access current_run_context: ContextVar[RunContext | None] = ContextVar("current_run_context", default=None) diff --git a/dreadnode/main.py b/dreadnode/main.py index d156f502..c8acd509 100644 --- a/dreadnode/main.py +++ b/dreadnode/main.py @@ -27,9 +27,11 @@ from .exporters import FileExportConfig, FileMetricReader, FileSpanExporter from .score import Scorer, ScorerCallable, T from .task import P, R, Task -from .tracing import JsonValue, RunSpan, Score, Span, current_run_span, current_task_span +from .tracing import JsonValue, RunSpan, Span, current_run_span, current_task_span from .version import VERSION +ToObject = t.Literal["task-or-run", "run"] + class DreadnodeConfigWarning(UserWarning): pass @@ -131,9 +133,9 @@ 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: + if self.server is None: # TODO: Move this to constants + self.server = "https://platform.dreadnode.io" self._api = ApiClient(self.server, self.token) @@ -286,10 +288,11 @@ def scorer( self, *, name: str | None = None, + tags: t.Sequence[str] | None = None, **attributes: t.Any, ) -> t.Callable[[ScorerCallable[T]], Scorer[T]]: 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 @@ -316,15 +319,23 @@ def run( tags=tags, ) - 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) + def log_param(self, key: str, value: JsonValue, *, to: ToObject = "task-or-run") -> None: + self.log_params(to=to, **{key: value}) + + def log_params(self, to: ToObject = "task-or-run", **params: JsonValue) -> None: + task = current_task_span.get() + run = current_run_span.get() - 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) + if to == "task-or-run": + target = task or run + if target is None: + raise RuntimeError("log_params() with to='task-or-run' must be called within a run or a task") + target.log_params(**params) + + elif to == "run": + if run is None: + raise RuntimeError("log_params() with to='run' must be called within a run") + run.log_params(**params) def log_metric( self, @@ -333,21 +344,21 @@ def log_metric( step: int = 0, *, 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) - - def log_score(self, score: Score) -> None: - if (run := current_run_span.get()) is None: - raise RuntimeError("Scores must be logged within a run") - - run.scores.append(score) - - if (task := current_task_span.get()) is None: - return - - task.scores.append(score) + task = current_task_span.get() + run = current_run_span.get() + + if to == "task-or-run": + target = task or run + if target is None: + raise RuntimeError("log_metric() with to='task-or-run' must be called within a run or a task") + target.log_metric(key, value, step=step, timestamp=timestamp) + + elif to == "run": + if run is None: + raise RuntimeError("log_metric() with to='run' must be called within a run") + run.log_metric(key, value, step=step, timestamp=timestamp) DEFAULT_INSTANCE = Dreadnode() diff --git a/dreadnode/score.py b/dreadnode/score.py index 66cb2535..ec040961 100644 --- a/dreadnode/score.py +++ b/dreadnode/score.py @@ -1,19 +1,18 @@ 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 +from .tracing import Metric, Span T = t.TypeVar("T") -ScorerCallable = t.Callable[[T], float | Score | t.Awaitable[float | Score]] +ScorerResult = float | int | bool | Metric +ScorerCallable = t.Callable[[T], ScorerResult | t.Awaitable[ScorerResult]] @dataclass @@ -21,6 +20,7 @@ class Scorer(t.Generic[T]): tracer: Tracer name: str + tags: t.Sequence[str] attributes: dict[str, t.Any] func: ScorerCallable[T] @@ -31,6 +31,7 @@ def from_callable( func: ScorerCallable[T] | "Scorer[T]", *, name: str | None = None, + tags: t.Sequence[str] | None = None, attributes: dict[str, t.Any] | None = None, ) -> "Scorer[T]": if isinstance(func, Scorer): @@ -45,7 +46,7 @@ def from_callable( 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) + 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) @@ -55,55 +56,28 @@ def clone(self) -> "Scorer[T]": return Scorer( tracer=self.tracer, name=self.name, + tags=self.tags, attributes=self.attributes, func=self.func, ) - async def __call__(self, object: T) -> Score: + async def __call__(self, object: T) -> Metric: with Span( name=self.name, + tags=self.tags, 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) + metric = self.func(object) + if inspect.isawaitable(metric): + metric = await metric + + if not isinstance(metric, Metric): + metric = Metric( + float(metric), + step=0, # TODO: Do we integrate an increment/state system here? + timestamp=datetime.now(timezone.utc), + attributes=self.attributes, + ) + + return metric diff --git a/dreadnode/storage.py b/dreadnode/storage.py new file mode 100644 index 00000000..e995b870 --- /dev/null +++ b/dreadnode/storage.py @@ -0,0 +1,164 @@ +# New storage.py module +import io +import os +from abc import ABC, abstractmethod + +import fsspec + +from .artifact import Artifact, ArtifactMetadata + + +class ArtifactStorage(ABC): + """Base class for artifact storage.""" + + @abstractmethod + def save_artifact(self, run_id: str, artifact: Artifact) -> str: + """Save an artifact and return its URI.""" + pass + + @abstractmethod + def get_artifact(self, run_id: str, artifact_path: str) -> io.BytesIO: + """Get an artifact's content.""" + pass + + @abstractmethod + def list_artifacts(self, run_id: str, path: str | None = None) -> list[ArtifactMetadata]: + """List artifacts for a run.""" + pass + + def get_artifact_uri(self, run_id: str, artifact_path: str) -> str: + """Get a URI for referencing an artifact.""" + return f"artifact://{run_id}/{artifact_path}" + + +class FsspecArtifactStorage(ArtifactStorage): + """Artifact storage using fsspec.""" + + def __init__(self, base_uri: str): + """Initialize with a URI that fsspec can handle.""" + self.base_uri = base_uri + # Make sure base URI ends with a slash + if not self.base_uri.endswith("/"): + self.base_uri += "/" + + # Create content store for deduplication + self.content_store_uri = f"{self.base_uri}content_store/" + fs, _, _ = fsspec.get_fs_token_paths(self.content_store_uri) + fs.makedirs(self.content_store_uri, exist_ok=True) + + def _get_run_path(self, run_id: str, artifact_path: str | None = None) -> str: + """Get the full path for an artifact in a run.""" + if artifact_path: + return f"{self.base_uri}{run_id}/artifacts/{artifact_path}" + else: + return f"{self.base_uri}{run_id}/artifacts/" + + def _get_content_path(self, content_hash: str) -> str: + """Get the path for a content hash in the content store.""" + return f"{self.content_store_uri}{content_hash}" + + def save_artifact(self, run_id: str, artifact: Artifact) -> str: + """Save an artifact using content addressing for deduplication.""" + # Set run_id if not already set + if artifact.run_id is None: + artifact.run_id = run_id + + # Get content hash path + content_path = self._get_content_path(artifact.metadata.content_hash) + + # Check if content already exists + fs, _, _ = fsspec.get_fs_token_paths(content_path) + + # Save content if it doesn't exist + if not fs.exists(content_path): + if artifact.local_path: + with open(artifact.local_path, "rb") as f_src, fs.open(content_path, "wb") as f_dest: + f_dest.write(f_src.read()) + elif artifact.content: + with fs.open(content_path, "wb") as f: + f.write(artifact.content) + else: + raise ValueError("Artifact has no content to save") + + # Now create the artifact reference + artifact_path = self._get_run_path(run_id, artifact.metadata.path) + + # Ensure parent directories exist + parent_dir = os.path.dirname(artifact_path) + fs.makedirs(parent_dir, exist_ok=True) + + # Create the artifact reference (a JSON file with metadata) + metadata = { + "content_hash": artifact.metadata.content_hash, + "size": artifact.metadata.size, + "content_type": artifact.metadata.content_type, + "description": artifact.metadata.description, + "created_at": artifact.metadata.created_at.isoformat(), + "custom_metadata": artifact.metadata.custom_metadata, + } + + # Write metadata + with fs.open(f"{artifact_path}.meta", "w") as f: + import json + + json.dump(metadata, f) + + return artifact.metadata.path + + def get_artifact(self, run_id: str, artifact_path: str) -> io.BytesIO: + """Get an artifact's content.""" + # Get metadata to find content hash + meta_path = f"{self._get_run_path(run_id, artifact_path)}.meta" + fs, _, _ = fsspec.get_fs_token_paths(meta_path) + + if not fs.exists(meta_path): + raise FileNotFoundError(f"Artifact not found: {artifact_path}") + + # Read metadata to get content hash + with fs.open(meta_path, "r") as f: + import json + + metadata = json.load(f) + + # Get content from content store + content_path = self._get_content_path(metadata["content_hash"]) + + # Read content + with fs.open(content_path, "rb") as f: + return io.BytesIO(f.read()) + + def list_artifacts(self, run_id: str, path: str | None = None) -> list[ArtifactMetadata]: + """List artifacts for a run.""" + base_path = self._get_run_path(run_id, path) + fs, _, _ = fsspec.get_fs_token_paths(base_path) + + if not fs.exists(base_path): + return [] + + artifacts = [] + + # List all .meta files in the directory + for item in fs.glob(f"{base_path}/**/*.meta"): + # Read metadata + with fs.open(item, "r") as f: + import json + + metadata = json.load(f) + + # Get artifact path (strip .meta from the end) + rel_path = os.path.relpath(item[:-5], self._get_run_path(run_id, "")) + + # Create artifact metadata + artifact_meta = ArtifactMetadata( + path=rel_path, + content_hash=metadata["content_hash"], + size=metadata["size"], + content_type=metadata["content_type"], + description=metadata.get("description", ""), + created_at=datetime.fromisoformat(metadata["created_at"]), + custom_metadata=metadata.get("custom_metadata", {}), + ) + + artifacts.append(artifact_meta) + + return artifacts diff --git a/dreadnode/tracing.py b/dreadnode/tracing.py index 228abaa5..80e27a05 100644 --- a/dreadnode/tracing.py +++ b/dreadnode/tracing.py @@ -22,9 +22,9 @@ SPAN_ATTRIBUTE_RUN_PARAMS_KEY, SPAN_ATTRIBUTE_SCHEMA_KEY, SPAN_ATTRIBUTE_TAGS_KEY, - SPAN_ATTRIBUTE_TASK_ARGS_KEY, + SPAN_ATTRIBUTE_TASK_METRICS_KEY, SPAN_ATTRIBUTE_TASK_OUTPUT_KEY, - SPAN_ATTRIBUTE_TASK_SCORES_KEY, + SPAN_ATTRIBUTE_TASK_PARAMS_KEY, SPAN_ATTRIBUTE_TYPE_KEY, SpanType, ) @@ -34,30 +34,28 @@ JsonValue = t.Union[int, float, str, bool, None, list["JsonValue"], tuple["JsonValue", ...], "JsonDict"] JsonDict = dict[str, JsonValue] +AnyDict = dict[str, t.Any] + 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 +class Metric: value: float + step: int = 0 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." + 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." 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)) + 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]] @@ -67,7 +65,7 @@ class Span(ReadableSpan): def __init__( self, name: str, - attributes: dict[str, t.Any], + attributes: AnyDict, tracer: Tracer, type: SpanType = "span", tags: t.Sequence[str] | None = None, @@ -75,7 +73,7 @@ def __init__( self._span_name = name self._pre_attributes = { SPAN_ATTRIBUTE_TYPE_KEY: type, - SPAN_ATTRIBUTE_TAGS_KEY: tuple(uniquify_sequence(tags or ())), + SPAN_ATTRIBUTE_TAGS_KEY: uniquify_sequence(tags or ()), **attributes, } self._tracer = tracer @@ -140,7 +138,7 @@ def tags(self) -> tuple[str, ...]: @tags.setter def tags(self, new_tags: t.Sequence[str]) -> None: - self.set_attribute(SPAN_ATTRIBUTE_TAGS_KEY, tuple(uniquify_sequence(new_tags))) + self.set_attribute(SPAN_ATTRIBUTE_TAGS_KEY, uniquify_sequence(new_tags)) def set_attribute(self, key: str, value: t.Any) -> None: self._added_attributes = True @@ -150,11 +148,11 @@ def set_attribute(self, key: str, value: t.Any) -> None: self._span.set_attribute(key, otel_value) self._pre_attributes[key] = otel_value - def set_attributes(self, attributes: dict[str, t.Any]) -> None: + def set_attributes(self, attributes: AnyDict) -> None: for key, value in attributes.items(): self.set_attribute(key, value) - def get_attributes(self) -> dict[str, t.Any]: + def get_attributes(self) -> AnyDict: if self._span is not None: return getattr(self._span, "attributes", {}) return self._pre_attributes @@ -173,7 +171,7 @@ def __init__( metrics: MetricDict | None = None, params: JsonDict | None = None, ) -> None: - attributes: dict[str, t.Any] = { + attributes: AnyDict = { SPAN_ATTRIBUTE_RUN_ID_KEY: run_id, SPAN_ATTRIBUTE_PROJECT_KEY: project, } @@ -191,14 +189,17 @@ def __init__( self, name: str, project: str, - attributes: dict[str, t.Any], + attributes: AnyDict, tracer: Tracer, + params: AnyDict | None = None, + inputs: AnyDict | None = None, + metrics: MetricDict | None = None, 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._params = params or {} + self._inputs = inputs or {} + self._metrics = metrics or {} self.project = project self._context_token: Token[RunSpan | None] | None = None # contextvars context @@ -206,6 +207,8 @@ def __init__( attributes = { SPAN_ATTRIBUTE_RUN_ID_KEY: str(run_id or ULID()), SPAN_ATTRIBUTE_PROJECT_KEY: project, + SPAN_ATTRIBUTE_RUN_PARAMS_KEY: self._params, + SPAN_ATTRIBUTE_RUN_METRICS_KEY: self._metrics, **attributes, } super().__init__(name, attributes, tracer, "run", tags) @@ -215,11 +218,6 @@ def __enter__(self) -> te.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) @@ -231,15 +229,15 @@ def run_id(self) -> str: return str(self.get_attribute(SPAN_ATTRIBUTE_RUN_ID_KEY, "")) @property - def params(self) -> JsonDict: + def params(self) -> AnyDict: return self._params - def log_param(self, key: str, value: JsonValue) -> None: + def log_param(self, key: str, value: t.Any) -> None: self.log_params(**{key: value}) - def log_params(self, **params: JsonValue) -> None: + def log_params(self, **params: t.Any) -> None: for key, value in params.items(): - self.params[key] = value + self._params[key] = value if self._span is None: return @@ -248,11 +246,13 @@ def log_params(self, **params: JsonValue) -> None: pass @property - def metrics(self) -> dict[str, list[Metric]]: + def metrics(self) -> MetricDict: 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)) + def log_metric( + self, key: str, value: float | int | bool, step: int = 0, *, timestamp: datetime | None = None + ) -> None: + metric = Metric(float(value), step, timestamp or datetime.now(timezone.utc)) self._metrics.setdefault(key, []).append(metric) if self._span is None: return @@ -260,34 +260,28 @@ def log_metric(self, key: str, value: float, step: int = 0, *, timestamp: dateti with RunUpdateSpan(run_id=self.run_id, project=self.project, tracer=self._tracer, metrics=self._metrics): pass - @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], + attributes: AnyDict, run_id: str, tracer: Tracer, + params: AnyDict | None = None, + metrics: MetricDict | None = None, tags: t.Sequence[str] | None = None, ) -> None: - self._args = args + self._params = params or {} + self._metrics = metrics or {} 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, + SPAN_ATTRIBUTE_TASK_PARAMS_KEY: self._params, + SPAN_ATTRIBUTE_TASK_METRICS_KEY: self._metrics, **attributes, } super().__init__(name, attributes, tracer, "task", tags) @@ -301,8 +295,8 @@ def __enter__(self) -> te.Self: 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) + self.set_attribute(SPAN_ATTRIBUTE_TASK_PARAMS_KEY, self._params) + self.set_attribute(SPAN_ATTRIBUTE_TASK_METRICS_KEY, self._metrics) super().__exit__(exc_type, exc_value, traceback) if self._context_token is not None: current_task_span.reset(self._context_token) @@ -324,29 +318,28 @@ 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 + def params(self) -> AnyDict: + return self._params - @property - def scores(self) -> list[Score]: - return self._scores + def log_param(self, key: str, value: t.Any) -> None: + self.log_params(**{key: value}) - @scores.setter - def scores(self, value: list[Score]) -> None: - self._scores = value + def log_params(self, **params: t.Any) -> None: + for key, value in params.items(): + self._params[key] = 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 metrics(self) -> dict[str, list[Metric]]: + return self._metrics + + def log_metric( + self, key: str, value: float | int | bool, step: int = 0, *, timestamp: datetime | None = None + ) -> None: + metric = Metric(float(value), step, timestamp or datetime.now(timezone.utc)) + self._metrics.setdefault(key, []).append(metric) -def prepare_otlp_attributes(attributes: dict[str, t.Any]) -> dict[str, otel_types.AttributeValue]: +def prepare_otlp_attributes(attributes: AnyDict) -> dict[str, otel_types.AttributeValue]: return {key: prepare_otlp_attribute(value) for key, value in attributes.items()} diff --git a/pyproject.toml b/pyproject.toml index da9c05c5..b5a25186 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "dreadnode" -version = "0.1.0" +version = "0.1.2" description = "Dreadnode SDK" authors = [ "Nick Landers " From 80cd3b8dbce4b91ab601822a2d560d2556f1be78 Mon Sep 17 00:00:00 2001 From: monoxgas Date: Fri, 14 Mar 2025 13:05:18 -0600 Subject: [PATCH 02/24] Finalizing scoring->metrics and inputs/params for tasks --- dreadnode/__init__.py | 7 +- dreadnode/api/client.py | 14 ++- dreadnode/constants.py | 42 ++++--- dreadnode/main.py | 213 +++++++++++++++++++++++-------- dreadnode/score.py | 23 ++-- dreadnode/task.py | 92 +++++++++++--- dreadnode/tracing.py | 270 ++++++++++++++++++++++++++++++++-------- pyproject.toml | 56 +++------ 8 files changed, 523 insertions(+), 194 deletions(-) diff --git a/dreadnode/__init__.py b/dreadnode/__init__.py index 8eb61292..7912bd1a 100644 --- a/dreadnode/__init__.py +++ b/dreadnode/__init__.py @@ -1,7 +1,7 @@ from .main import DEFAULT_INSTANCE from .score import Scorer from .task import Task -from .tracing import RunSpan, Score, Span, TaskSpan +from .tracing import RunSpan, Span, TaskSpan from .version import VERSION configure = DEFAULT_INSTANCE.configure @@ -11,11 +11,14 @@ span = DEFAULT_INSTANCE.span task = DEFAULT_INSTANCE.task run = DEFAULT_INSTANCE.run +scorer = DEFAULT_INSTANCE.scorer 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 __version__ = VERSION diff --git a/dreadnode/api/client.py b/dreadnode/api/client.py index 5a3350af..6584a56e 100644 --- a/dreadnode/api/client.py +++ b/dreadnode/api/client.py @@ -5,7 +5,8 @@ from pydantic import BaseModel from rich import print -from ..version import VERSION +from dreadnode.version import VERSION + from .strikes import StrikesClient ModelT = t.TypeVar("ModelT", bound=BaseModel) @@ -67,7 +68,7 @@ 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( @@ -91,11 +92,12 @@ def request( """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.") + 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 diff --git a/dreadnode/constants.py b/dreadnode/constants.py index ae545fd5..8fd573a7 100644 --- a/dreadnode/constants.py +++ b/dreadnode/constants.py @@ -8,34 +8,40 @@ 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_TYPE = f"{SPAN_NAMESPACE}.type" +SPAN_ATTRIBUTE_SCHEMA = f"{SPAN_NAMESPACE}.schema" +SPAN_ATTRIBUTE_KIND = f"{SPAN_NAMESPACE}.kind" +SPAN_ATTRIBUTE_TAGS_ = f"{SPAN_NAMESPACE}.tags" +SPAN_ATTRIBUTE_KIND = f"{SPAN_NAMESPACE}.kind" +SPAN_ATTRIBUTE_PROJECT = f"{SPAN_NAMESPACE}.project" -SPAN_ATTRIBUTE_RUN_ID_KEY = f"{SPAN_NAMESPACE}.run.id" +SPAN_ATTRIBUTE_RUN_ID = f"{SPAN_NAMESPACE}.run.id" -SPAN_ATTRIBUTE_RUN_PARAMS_KEY = f"{SPAN_NAMESPACE}.run.params" -SPAN_ATTRIBUTES_RUN_INPUTS_KEY = f"{SPAN_NAMESPACE}.run.inputs" -SPAN_ATTRIBUTE_RUN_METRICS_KEY = f"{SPAN_NAMESPACE}.run.metrics" -SPAN_ATTRIBUTE_RUN_ARTIFACTS_KEY = f"{SPAN_NAMESPACE}.run.artifacts" +SPAN_ATTRIBUTE_RUN_PARAMS = f"{SPAN_NAMESPACE}.run.params" +SPAN_ATTRIBUTES_RUN_INPUTS = f"{SPAN_NAMESPACE}.run.inputs" +SPAN_ATTRIBUTE_RUN_METRICS = f"{SPAN_NAMESPACE}.run.metrics" +SPAN_ATTRIBUTE_RUN_ARTIFACTS = f"{SPAN_NAMESPACE}.run.artifacts" -SPAN_ATTRIBUTE_PARENT_TASK_ID_KEY = f"{SPAN_NAMESPACE}.task.parent_id" +SPAN_ATTRIBUTE_PARENT_TASK_ID = f"{SPAN_NAMESPACE}.task.parent_id" -SPAN_ATTRIBUTE_TASK_PARAMS_KEY = f"{SPAN_NAMESPACE}.task.params" -SPAN_ATTRIBUTE_TASK_INPUTS_KEY = f"{SPAN_NAMESPACE}.task.inputs" -SPAN_ATTRIBUTE_TASK_METRICS_KEY = f"{SPAN_NAMESPACE}.task.metrics" -SPAN_ATTRIBUTE_TASK_OUTPUT_KEY = f"{SPAN_NAMESPACE}.task.output" +SPAN_ATTRIBUTE_TASK_KEY = f"{SPAN_NAMESPACE}.task.key" +SPAN_ATTRIBUTE_TASK_PARAMS = f"{SPAN_NAMESPACE}.task.params" +SPAN_ATTRIBUTE_TASK_INPUTS = f"{SPAN_NAMESPACE}.task.inputs" +SPAN_ATTRIBUTE_TASK_METRICS = f"{SPAN_NAMESPACE}.task.metrics" +SPAN_ATTRIBUTE_TASK_OUTPUT = f"{SPAN_NAMESPACE}.task.output" -SPAN_ATTRIBUTE_TASK_ARGS_KEY = f"{SPAN_NAMESPACE}.task.args" # deprecated -SPAN_ATTRIBUTE_TASK_SCORES_KEY = f"{SPAN_NAMESPACE}.task.scores" # deprecated +SPAN_ATTRIBUTE_TASK_ARGS = f"{SPAN_NAMESPACE}.task.args" # deprecated +SPAN_ATTRIBUTE_TASK_SCORES = f"{SPAN_NAMESPACE}.task.scores" # deprecated # Environment variable names ENV_SERVER_URL = "DREADNODE_SERVER_URL" ENV_SERVER = "DREADNODE_SERVER" # alternative to SERVER_URL -ENV_API_TOKEN = "DREADNODE_API_TOKEN" +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" diff --git a/dreadnode/main.py b/dreadnode/main.py index c8acd509..9b769bed 100644 --- a/dreadnode/main.py +++ b/dreadnode/main.py @@ -6,30 +6,40 @@ import random 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 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 .constants import ( + DEFAULT_SERVER_URL, + ENV_API_KEY, + ENV_API_TOKEN, + ENV_LOCAL_DIR, + ENV_PROJECT, + ENV_SERVER, + 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, RunSpan, Span, current_run_span, current_task_span +from .tracing import AnyDict, JsonValue, Metric, RunSpan, Span, current_run_span, current_task_span from .version import VERSION +if t.TYPE_CHECKING: + 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"] @@ -83,18 +93,18 @@ def configure( self, server: str | None = None, token: str | None = None, - local_dir: str | Path | t.Literal[False] = False, + local_dir: str | Path | t.Literal[False] = False, # noqa: FBT002 project: str | None = None, service_name: str | None = None, service_version: str | None = None, - console: logfire.ConsoleOptions | t.Literal[False, True] = True, + console: logfire.ConsoleOptions | t.Literal[False, True] = True, # noqa: FBT002 send_to_logfire: bool | t.Literal["if-token-present"] = "if-token-present", otel_scope: str = "dreadnode", ) -> None: 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) @@ -129,14 +139,15 @@ def initialize(self) -> None: ) if self.local_dir is not False: - config = FileExportConfig(base_path=self.local_dir, prefix=self.project + "-" if self.project else "") + config = FileExportConfig( + base_path=self.local_dir, + prefix=self.project + "-" if self.project else "", + ) span_processors.append(BatchSpanProcessor(FileSpanExporter(config))) metric_readers.append(FileMetricReader(config)) if self.token is not None: - if self.server is None: # TODO: Move this to constants - self.server = "https://platform.dreadnode.io" - + self.server = self.server or DEFAULT_SERVER_URL self._api = ApiClient(self.server, self.token) headers = {"User-Agent": f"dreadnode/{VERSION}", "X-Api-Key": self.token} @@ -147,18 +158,19 @@ 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 # ) # ) # ) @@ -183,9 +195,9 @@ def is_default(self) -> bool: # 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: + 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") @@ -196,7 +208,7 @@ 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( + return self._logfire._tracer_provider.get_tracer( # noqa: SLF001 self.otel_scope, VERSION, is_span_tracer=is_span_tracer, @@ -211,7 +223,6 @@ def shutdown(self) -> None: def span( self, name: str, - /, *, tags: t.Sequence[str] | None = None, **attributes: t.Any, @@ -227,11 +238,15 @@ def span( def task( self, *, - scorers: None = None, - name: str | None = None, - tags: t.Sequence[str] | None = None, + scorers: None = ..., + name: str | None = ..., + kind: str | None = ..., + log_params: t.Sequence[str] | t.Literal[True] | None = ..., + log_inputs: t.Sequence[str] | t.Literal[True] | None = ..., + log_output: bool = ..., + tags: t.Sequence[str] | None = ..., **attributes: t.Any, - ) -> t.Callable[[t.Callable[P, t.Awaitable[R]]], Task[P, R]]: + ) -> t.Callable[[t.Callable[P, t.Awaitable[R]] | t.Callable[P, R]], Task[P, R]]: ... @t.overload @@ -239,10 +254,14 @@ def task( self, *, scorers: t.Sequence[Scorer[R] | ScorerCallable[R]], - name: str | None = None, - tags: t.Sequence[str] | None = None, + name: str | None = ..., + kind: str | None = ..., + log_params: t.Sequence[str] | t.Literal[True] | None = ..., + log_inputs: t.Sequence[str] | t.Literal[True] | None = ..., + log_output: bool = ..., + tags: t.Sequence[str] | None = ..., **attributes: t.Any, - ) -> t.Callable[[t.Callable[P, t.Awaitable[R]]], Task[P, R]]: + ) -> t.Callable[[t.Callable[P, t.Awaitable[R]] | t.Callable[P, R]], Task[P, R]]: ... def task( @@ -250,36 +269,53 @@ def task( *, scorers: t.Sequence[Scorer[t.Any] | ScorerCallable[R]] | None = None, name: str | None = None, + kind: str | None = None, + log_params: t.Sequence[str] | t.Literal[True] | None = None, + log_inputs: t.Sequence[str] | t.Literal[True] | None = None, + 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]: + ) -> t.Callable[[t.Callable[P, t.Awaitable[R]] | t.Callable[P, R]], Task[P, R]]: + def make_task(func: t.Callable[P, t.Awaitable[R]] | t.Callable[P, R]) -> Task[P, R]: func = inspect.unwrap(func) - qualified_func_name = func_name = getattr(func, "__qualname__", getattr(func, "__name__", safe_repr(func))) + if inspect.isgeneratorfunction(func) or inspect.isasyncgenfunction(func): + raise TypeError("@task cannot be applied to generators") - with contextlib.suppress(Exception): - qualified_func_name = f"{inspect.getmodule(func).__name__}.{func_name}" # type: ignore + func_name = getattr( + func, + "__qualname__", + getattr(func, "__name__", safe_repr(func)), + ) - _name = name or qualified_func_name + _name = name or func_name + _kind = kind or func_name _attributes = attributes or {} _attributes["code.function"] = func_name with contextlib.suppress(Exception): _attributes["code.lineno"] = func.__code__.co_firstlineno with contextlib.suppress(Exception): - _attributes.update(get_filepath_attribute(inspect.getsourcefile(func))) # type: ignore + _attributes.update( + get_filepath_attribute(inspect.getsourcefile(func)), # 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, + kind=_kind, ) return make_task @@ -292,16 +328,23 @@ def scorer( **attributes: t.Any, ) -> t.Callable[[ScorerCallable[T]], Scorer[T]]: def make_scorer(func: ScorerCallable[T]) -> Scorer[T]: - return Scorer.from_callable(self._get_tracer(), func, name=name, tags=tags, attributes=attributes) + return Scorer.from_callable( + self._get_tracer(), + func, + name=name, + tags=tags, + attributes=attributes, + ) return make_scorer def run( self, name: str | None = None, - /, *, tags: t.Sequence[str] | None = None, + params: AnyDict | None = None, + inputs: AnyDict | None = None, project: str | None = None, **attributes: t.Any, ) -> RunSpan: @@ -309,13 +352,15 @@ def run( 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, + inputs=inputs, tags=tags, ) @@ -329,7 +374,9 @@ def log_params(self, to: ToObject = "task-or-run", **params: JsonValue) -> None: if to == "task-or-run": target = task or run if target is None: - raise RuntimeError("log_params() with to='task-or-run' must be called within a run or a task") + raise RuntimeError( + "log_params() with to='task-or-run' must be called within a run or a task", + ) target.log_params(**params) elif to == "run": @@ -337,10 +384,32 @@ def log_params(self, to: ToObject = "task-or-run", **params: JsonValue) -> None: raise RuntimeError("log_params() with to='run' must be called within a run") run.log_params(**params) + @t.overload + def log_metric( + self, + key: str, + value: float | bool, + step: int = ..., + *, + timestamp: datetime | None = ..., + to: ToObject = ..., + ) -> None: + ... + + @t.overload def log_metric( self, key: str, - value: float, + value: Metric, + *, + to: ToObject = ..., + ) -> None: + ... + + def log_metric( + self, + key: str, + value: float | bool | Metric, step: int = 0, *, timestamp: datetime | None = None, @@ -349,16 +418,62 @@ def log_metric( task = current_task_span.get() run = current_run_span.get() + metric = ( + value + if isinstance(value, Metric) + else Metric(float(value), step, timestamp or datetime.now(timezone.utc)) + ) + if to == "task-or-run": target = task or run if target is None: - raise RuntimeError("log_metric() with to='task-or-run' must be called within a run or a task") - target.log_metric(key, value, step=step, timestamp=timestamp) + raise RuntimeError( + "log_metric() with to='task-or-run' must be called within a run or a task", + ) + target.log_metric(key, metric) elif to == "run": if run is None: raise RuntimeError("log_metric() with to='run' must be called within a run") - run.log_metric(key, value, step=step, timestamp=timestamp) + run.log_metric(key, metric) + + def log_input( + self, + key: str, + value: JsonValue, + *, + to: ToObject = "task-or-run", + ) -> None: + self.log_inputs(to=to, **{key: value}) + + def log_inputs( + self, + to: ToObject = "task-or-run", + **inputs: JsonValue, + ) -> None: + task = current_task_span.get() + run = current_run_span.get() + + if to == "task-or-run": + target = task or run + if target is None: + raise RuntimeError( + "log_inputs() with to='task-or-run' must be called within a run or a task", + ) + target.log_inputs(**inputs) + + elif to == "run": + if run is None: + raise RuntimeError("log_inputs() with to='run' must be called within a run") + run.log_inputs(**inputs) + + def log_output( + self, + value: t.Any, + ) -> None: + if (task := current_task_span.get()) is None: + raise RuntimeError("log_output() must be called within a task") + task.output = value DEFAULT_INSTANCE = Dreadnode() diff --git a/dreadnode/score.py b/dreadnode/score.py index ec040961..d7d88611 100644 --- a/dreadnode/score.py +++ b/dreadnode/score.py @@ -1,4 +1,3 @@ -import contextlib import inspect import typing as t from dataclasses import dataclass @@ -12,7 +11,7 @@ T = t.TypeVar("T") ScorerResult = float | int | bool | Metric -ScorerCallable = t.Callable[[T], ScorerResult | t.Awaitable[ScorerResult]] +ScorerCallable = t.Callable[[T], t.Awaitable[ScorerResult]] | t.Callable[[T], ScorerResult] @dataclass @@ -42,11 +41,19 @@ def from_callable( 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, tags=tags or [], attributes=attributes or {}, func=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) @@ -75,7 +82,7 @@ async def __call__(self, object: T) -> Metric: if not isinstance(metric, Metric): metric = Metric( float(metric), - step=0, # TODO: Do we integrate an increment/state system here? + step=0, # Do we integrate an increment/state system here? timestamp=datetime.now(timezone.utc), attributes=self.attributes, ) diff --git a/dreadnode/task.py b/dreadnode/task.py index 743e6e42..ea20d1fb 100644 --- a/dreadnode/task.py +++ b/dreadnode/task.py @@ -1,8 +1,10 @@ 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 @@ -12,21 +14,47 @@ R = t.TypeVar("R") +class TaskFailedWarning(UserWarning): + pass + + +class TaskGeneratorWarning(UserWarning): + pass + + class TaskSpanList(list[TaskSpan[R]]): def sorted(self, *, reverse: bool = True) -> "TaskSpanList[R]": - return TaskSpanList(sorted(self, key=lambda span: span.average_score, reverse=reverse)) + 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: 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]": + sorted_ = self.sorted(reverse=reverse)[:n] + return ( + t.cast(list[R], [span.output for span in sorted_]) + if as_outputs + else TaskSpanList(sorted_) + ) @dataclass @@ -34,11 +62,16 @@ class Task(t.Generic[P, R]): tracer: Tracer name: str + kind: str attributes: dict[str, t.Any] - func: t.Callable[P, t.Awaitable[R]] + func: t.Callable[P, R] scorers: list[Scorer[R]] tags: list[str] + log_params: t.Sequence[str] | t.Literal[True] | None = None + log_inputs: t.Sequence[str] | t.Literal[True] | None = None + log_output: bool = True + def __post_init__(self) -> None: self.__signature__ = inspect.signature(self.func) self.__name__ = getattr(self.func, "__name__", self.name) @@ -53,6 +86,7 @@ def clone(self) -> "Task[P, R]": return Task( tracer=self.tracer, name=self.name, + kind=self.kind, attributes=self.attributes.copy(), func=self.func, scorers=self.scorers.copy(), @@ -65,11 +99,13 @@ def with_( scorers: t.Sequence[Scorer[R] | ScorerCallable[R]] | None = None, name: str | None = None, tags: t.Sequence[str] | None = None, + kind: str | None = None, append: bool = False, **attributes: t.Any, ) -> "Task[P, R]": task = self.clone() task.name = name or task.name + task.kind = kind or task.kind new_scorers = [Scorer.from_callable(self.tracer, scorer) for scorer in (scorers or [])] new_tags = list(tags or []) @@ -90,31 +126,50 @@ async def run(self, *args: P.args, **kwargs: P.kwargs) -> TaskSpan[R]: 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 = ( + bound_args + if self.log_params is True + else {k: v for k, v in bound_args.items() if k in (self.log_params or [])} + ) + inputs = ( + bound_args + if self.log_inputs is True + else {k: v for k, v in bound_args.items() if k in (self.log_inputs or [])} + ) + with TaskSpan[R]( name=self.name, + kind=self.kind, attributes=self.attributes, - args=self._bind_args(*args, **kwargs), + params=params, + inputs=inputs, + tags=self.tags, run_id=run.run_id, tracer=self.tracer, + log_output=self.log_output, ) as span: output = t.cast(R | t.Awaitable[R], self.func(*args, **kwargs)) if inspect.isawaitable(output): output = await output - span.output = output + # Only apply the output if the user hasn't + # already logged it themselves. + if span._output is None: # noqa: SLF001 + span.output = output for scorer in self.scorers: - score = await scorer(span.output) - span.scores.append(score) - run.scores.append(score) + metric = await scorer(span.output) + span.log_metric(scorer.name, metric) 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]: @@ -123,7 +178,7 @@ async def map_run(self, count: int, *args: P.args, **kwargs: P.kwargs) -> TaskSp async def map(self, count: int, *args: P.args, **kwargs: P.kwargs) -> list[R]: 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]: spans = await self.map_run(count, *args, **kwargs) @@ -132,8 +187,11 @@ async def top_n(self, count: int, n: int, *args: P.args, **kwargs: P.kwargs) -> async def try_run(self, *args: P.args, **kwargs: P.kwargs) -> TaskSpan[R] | None: 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.kind}) failed:\n{traceback.format_exc()}", + TaskFailedWarning, + ) return None async def try_(self, *args: P.args, **kwargs: P.kwargs) -> R | None: @@ -150,4 +208,4 @@ async def try_top_n(self, count: int, n: int, *args: P.args, **kwargs: P.kwargs) async def try_map(self, count: int, *args: P.args, **kwargs: P.kwargs) -> list[R]: 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 index 80e27a05..804f7b6b 100644 --- a/dreadnode/tracing.py +++ b/dreadnode/tracing.py @@ -1,3 +1,4 @@ +import types import typing as t from contextvars import ContextVar, Token from dataclasses import dataclass, field @@ -5,7 +6,11 @@ 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.json_schema import ( + JsonSchemaProperties, + attributes_json_schema, + create_json_schema, +) from logfire._internal.utils import uniquify_sequence from opentelemetry import context as context_api from opentelemetry import trace as trace_api @@ -15,28 +20,43 @@ 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_METRICS_KEY, - SPAN_ATTRIBUTE_TASK_OUTPUT_KEY, - SPAN_ATTRIBUTE_TASK_PARAMS_KEY, - SPAN_ATTRIBUTE_TYPE_KEY, + SPAN_ATTRIBUTE_KIND, + SPAN_ATTRIBUTE_PARENT_TASK_ID, + SPAN_ATTRIBUTE_PROJECT, + SPAN_ATTRIBUTE_RUN_ID, + SPAN_ATTRIBUTE_RUN_METRICS, + SPAN_ATTRIBUTE_RUN_PARAMS, + SPAN_ATTRIBUTE_SCHEMA, + SPAN_ATTRIBUTE_TAGS_, + SPAN_ATTRIBUTE_TASK_INPUTS, + SPAN_ATTRIBUTE_TASK_METRICS, + SPAN_ATTRIBUTE_TASK_OUTPUT, + SPAN_ATTRIBUTE_TASK_PARAMS, + SPAN_ATTRIBUTE_TYPE, + SPAN_ATTRIBUTES_RUN_INPUTS, SpanType, ) R = t.TypeVar("R") -JsonValue = t.Union[int, float, str, bool, None, list["JsonValue"], tuple["JsonValue", ...], "JsonDict"] +JsonValue = t.Union[ + int, + float, + str, + bool, + None, + list["JsonValue"], + tuple["JsonValue", ...], + "JsonDict", +] JsonDict = dict[str, JsonValue] AnyDict = dict[str, t.Any] -current_task_span: ContextVar["TaskSpan[t.Any] | None"] = ContextVar("current_task_span", default=None) +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) @@ -49,7 +69,10 @@ class Metric: @classmethod def from_many( - cls, values: t.Sequence[tuple[str, float, float]], step: int = 0, **attributes: JsonValue + cls, + values: t.Sequence[tuple[str, float, float]], + step: int = 0, + **attributes: JsonValue, ) -> "Metric": "Create a composite metric from individual values and weights." total = sum(value * weight for _, value, weight in values) @@ -67,13 +90,16 @@ def __init__( name: str, attributes: AnyDict, tracer: Tracer, + *, + kind: str | None = None, 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: uniquify_sequence(tags or ()), + SPAN_ATTRIBUTE_TYPE: type, + SPAN_ATTRIBUTE_KIND: kind or "", + SPAN_ATTRIBUTE_TAGS_: uniquify_sequence(tags or ()), **attributes, } self._tracer = tracer @@ -101,7 +127,12 @@ def __enter__(self) -> te.Self: return self - def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: t.Any) -> None: + 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 @@ -111,7 +142,7 @@ def __exit__(self, exc_type: type[BaseException] | None, exc_value: BaseExceptio if not self._span.is_recording(): return - self._span.set_attribute(SPAN_ATTRIBUTE_SCHEMA_KEY, attributes_json_schema(self._schema)) + self._span.set_attribute(SPAN_ATTRIBUTE_SCHEMA, attributes_json_schema(self._schema)) self._span.__exit__(exc_type, exc_value, traceback) @property @@ -134,11 +165,11 @@ def is_recording(self) -> bool: @property def tags(self) -> tuple[str, ...]: - return tuple(self.get_attribute(SPAN_ATTRIBUTE_TAGS_KEY, ())) + 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_KEY, uniquify_sequence(new_tags)) + self.set_attribute(SPAN_ATTRIBUTE_TAGS_, uniquify_sequence(new_tags)) def set_attribute(self, key: str, value: t.Any) -> None: self._added_attributes = True @@ -170,18 +201,21 @@ def __init__( *, metrics: MetricDict | None = None, params: JsonDict | None = None, + inputs: JsonDict | None = None, ) -> None: attributes: AnyDict = { - SPAN_ATTRIBUTE_RUN_ID_KEY: run_id, - SPAN_ATTRIBUTE_PROJECT_KEY: project, + SPAN_ATTRIBUTE_RUN_ID: run_id, + SPAN_ATTRIBUTE_PROJECT: project, } if metrics: - attributes[SPAN_ATTRIBUTE_RUN_METRICS_KEY] = metrics + attributes[SPAN_ATTRIBUTE_RUN_METRICS] = metrics if params: - attributes[SPAN_ATTRIBUTE_RUN_PARAMS_KEY] = params + attributes[SPAN_ATTRIBUTE_RUN_PARAMS] = params + if inputs: + attributes[SPAN_ATTRIBUTES_RUN_INPUTS] = inputs - super().__init__(f"run.{run_id}.update", attributes, tracer, "run_update") + super().__init__(f"run.{run_id}.update", attributes, tracer, type="run_update") class RunSpan(Span): @@ -205,28 +239,38 @@ def __init__( 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, - SPAN_ATTRIBUTE_RUN_PARAMS_KEY: self._params, - SPAN_ATTRIBUTE_RUN_METRICS_KEY: self._metrics, + SPAN_ATTRIBUTE_RUN_ID: str(run_id or ULID()), + SPAN_ATTRIBUTE_PROJECT: project, + SPAN_ATTRIBUTE_RUN_PARAMS: self._params, + SPAN_ATTRIBUTES_RUN_INPUTS: self._inputs, + SPAN_ATTRIBUTE_RUN_METRICS: self._metrics, **attributes, } - super().__init__(name, attributes, tracer, "run", tags) + 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: t.Any) -> None: - self.set_attribute(SPAN_ATTRIBUTE_RUN_PARAMS_KEY, self._params) - self.set_attribute(SPAN_ATTRIBUTE_RUN_METRICS_KEY, self._metrics) + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: types.TracebackType | None, + ) -> None: + self.set_attribute(SPAN_ATTRIBUTE_RUN_PARAMS, self._params) + self.set_attribute(SPAN_ATTRIBUTES_RUN_INPUTS, self._inputs) + self.set_attribute(SPAN_ATTRIBUTE_RUN_METRICS, self._metrics) super().__exit__(exc_type, exc_value, traceback) if self._context_token is not None: current_run_span.reset(self._context_token) @property def run_id(self) -> str: - return str(self.get_attribute(SPAN_ATTRIBUTE_RUN_ID_KEY, "")) + return str(self.get_attribute(SPAN_ATTRIBUTE_RUN_ID, "")) @property def params(self) -> AnyDict: @@ -242,22 +286,70 @@ def log_params(self, **params: t.Any) -> None: if self._span is None: return - with RunUpdateSpan(run_id=self.run_id, project=self.project, tracer=self._tracer, params=self._params): + with RunUpdateSpan( + run_id=self.run_id, + project=self.project, + tracer=self._tracer, + params=params, # Only the new params + ): pass + @property + def inputs(self) -> AnyDict: + return self._inputs + + def log_input(self, key: str, value: t.Any) -> None: + self.log_inputs(**{key: value}) + + def log_inputs(self, **inputs: t.Any) -> None: + self._inputs.update(inputs) + @property def metrics(self) -> MetricDict: return self._metrics + @t.overload def log_metric( - self, key: str, value: float | int | bool, step: int = 0, *, timestamp: datetime | None = None + self, + key: str, + value: float | bool, + step: int = ..., + *, + timestamp: datetime | None = ..., + ) -> None: + ... + + @t.overload + def log_metric( + self, + key: str, + value: Metric, + ) -> None: + ... + + def log_metric( + self, + key: str, + value: float | bool | Metric, + step: int = 0, + *, + timestamp: datetime | None = None, ) -> None: - metric = Metric(float(value), step, timestamp or datetime.now(timezone.utc)) + metric = ( + value + if isinstance(value, Metric) + else Metric(float(value), step, timestamp or datetime.now(timezone.utc)) + ) self._metrics.setdefault(key, []).append(metric) if self._span is None: return - with RunUpdateSpan(run_id=self.run_id, project=self.project, tracer=self._tracer, metrics=self._metrics): + with RunUpdateSpan( + run_id=self.run_id, + project=self.project, + tracer=self._tracer, + metrics={key: self._metrics.get(key, [])}, # Only the new metric + ): pass @@ -268,49 +360,65 @@ def __init__( attributes: AnyDict, run_id: str, tracer: Tracer, + *, + kind: str | None = None, params: AnyDict | None = None, + inputs: AnyDict | None = None, metrics: MetricDict | None = None, tags: t.Sequence[str] | None = None, + log_output: bool = True, ) -> None: self._params = params or {} + self._inputs = inputs or {} self._metrics = metrics or {} self._output: R | None = None + self._log_output = log_output self._context_token: Token[TaskSpan[t.Any] | None] | None = None # contextvars context attributes = { - SPAN_ATTRIBUTE_RUN_ID_KEY: str(run_id), - SPAN_ATTRIBUTE_TASK_PARAMS_KEY: self._params, - SPAN_ATTRIBUTE_TASK_METRICS_KEY: self._metrics, + SPAN_ATTRIBUTE_RUN_ID: str(run_id), + SPAN_ATTRIBUTE_TASK_PARAMS: self._params, + SPAN_ATTRIBUTE_TASK_INPUTS: self._inputs, + SPAN_ATTRIBUTE_TASK_METRICS: self._metrics, **attributes, } - super().__init__(name, attributes, tracer, "task", tags) + super().__init__(name, attributes, tracer, type="task", kind=kind, 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_KEY, self._parent_task.span_id) + self.set_attribute(SPAN_ATTRIBUTE_PARENT_TASK_ID, 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_PARAMS_KEY, self._params) - self.set_attribute(SPAN_ATTRIBUTE_TASK_METRICS_KEY, self._metrics) + def __exit__( + self, + exc_type: type[BaseException] | None, + exc_value: BaseException | None, + traceback: types.TracebackType | None, + ) -> None: + if self._log_output: + self.set_attribute(SPAN_ATTRIBUTE_TASK_OUTPUT, self._output) + self.set_attribute(SPAN_ATTRIBUTE_TASK_PARAMS, self._params) + self.set_attribute(SPAN_ATTRIBUTE_TASK_INPUTS, self._inputs) + self.set_attribute(SPAN_ATTRIBUTE_TASK_METRICS, self._metrics) 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, "")) + 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_KEY, "")) + return str(self.get_attribute(SPAN_ATTRIBUTE_PARENT_TASK_ID, "")) @property - def output(self) -> R | None: + def output(self) -> R: + if self._output is None: + raise ValueError("Task output is not set") return self._output @output.setter @@ -325,19 +433,71 @@ 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 + self._params.update(params) + + @property + def inputs(self) -> AnyDict: + return self._inputs + + def log_input(self, key: str, value: t.Any) -> None: + self.log_inputs(**{key: value}) + + def log_inputs(self, **inputs: t.Any) -> None: + self._inputs.update(inputs) @property def metrics(self) -> dict[str, list[Metric]]: return self._metrics + @t.overload def log_metric( - self, key: str, value: float | int | bool, step: int = 0, *, timestamp: datetime | None = None + self, + key: str, + value: float | bool, + step: int = ..., + *, + timestamp: datetime | None = ..., + ) -> None: + ... + + @t.overload + def log_metric( + self, + key: str, + value: Metric, ) -> None: - metric = Metric(float(value), step, timestamp or datetime.now(timezone.utc)) + ... + + def log_metric( + self, + key: str, + value: float | bool | Metric, + step: int = 0, + *, + timestamp: datetime | None = None, + ) -> None: + metric = ( + value + if isinstance(value, Metric) + else Metric(float(value), step, timestamp or datetime.now(timezone.utc)) + ) self._metrics.setdefault(key, []).append(metric) + # For every metric we log, also log it to the run + # with our `kind` as a prefix + if (run := current_run_span.get()) is not None: + run.log_metric(f"{self.kind}.{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()} diff --git a/pyproject.toml b/pyproject.toml index b5a25186..ab0fe48e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,48 +38,26 @@ build-backend = "poetry.core.masonry.api" strict = true [tool.ruff] -line-length = 120 -indent-width = 4 target-version = "py310" -select = [ - "E", # pycodestyle errors - "W", # pycodestyle warnings - "F", # pyflakes - "I", # isort - "C", # flake8-comprehensions - "B", # flake8-bugbear - "UP", # pyupgrade - "NPY", # numpydoc - "A", # flake8-annotations -] -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 -] -exclude = [ - ".git", - ".git-rewrite", - ".mypy_cache", - ".ruff_cache", - "__pypackages__", - "build", - "dist", - ".venv", - "venv", +line-length = 100 +extend-exclude = [ + "*.ipynb" # jupyter notebooks ] [tool.ruff.lint] -fixable = ["ALL"] -unfixable = ["B"] -dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" +select = [ "ALL" ] +ignore = [ + "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 +] [tool.ruff.format] -quote-style = "double" -indent-style = "space" -skip-magic-trailing-comma = false -line-ending = "auto" +skip-magic-trailing-comma = false \ No newline at end of file From ea184c1a3d3f9d998ae126df71d748ce4a159935 Mon Sep 17 00:00:00 2001 From: monoxgas Date: Mon, 17 Mar 2025 14:30:28 -0600 Subject: [PATCH 03/24] Reworking object storage --- dreadnode/__init__.py | 11 +- dreadnode/api/client.py | 37 +++- dreadnode/api/models.py | 133 +++++++++++++ dreadnode/api/strikes.py | 173 ----------------- dreadnode/artifact.py | 186 ++++-------------- dreadnode/constants.py | 49 +++-- dreadnode/exporters.py | 22 ++- dreadnode/main.py | 142 ++++++++++---- dreadnode/{score.py => metric.py} | 30 ++- dreadnode/object.py | 209 ++++++++++++++++++++ dreadnode/task.py | 17 +- dreadnode/tracing.py | 307 +++++++++++++++++++++--------- 12 files changed, 834 insertions(+), 482 deletions(-) create mode 100644 dreadnode/api/models.py delete mode 100644 dreadnode/api/strikes.py rename dreadnode/{score.py => metric.py} (72%) create mode 100644 dreadnode/object.py diff --git a/dreadnode/__init__.py b/dreadnode/__init__.py index 7912bd1a..b6dbddb1 100644 --- a/dreadnode/__init__.py +++ b/dreadnode/__init__.py @@ -1,5 +1,6 @@ -from .main import DEFAULT_INSTANCE -from .score import Scorer +from .main import DEFAULT_INSTANCE, Dreadnode +from .metric import Metric, MetricDict, Scorer +from .object import Object from .task import Task from .tracing import RunSpan, Span, TaskSpan from .version import VERSION @@ -12,6 +13,7 @@ task = DEFAULT_INSTANCE.task run = DEFAULT_INSTANCE.run scorer = DEFAULT_INSTANCE.scorer +task_span = DEFAULT_INSTANCE.task_span log_metric = DEFAULT_INSTANCE.log_metric log_param = DEFAULT_INSTANCE.log_param @@ -19,10 +21,12 @@ log_input = DEFAULT_INSTANCE.log_input log_inputs = DEFAULT_INSTANCE.log_inputs log_output = DEFAULT_INSTANCE.log_output +link_objects = DEFAULT_INSTANCE.link_objects __version__ = VERSION __all__ = [ + "Dreadnode", "configure", "shutdown", "span", @@ -37,5 +41,8 @@ "TaskSpan", "Span", "RunSpan", + "Metric", + "MetricDict", + "Object", "__version__", ] diff --git a/dreadnode/api/client.py b/dreadnode/api/client.py index 6584a56e..fc5f1398 100644 --- a/dreadnode/api/client.py +++ b/dreadnode/api/client.py @@ -4,10 +4,11 @@ import httpx from pydantic import BaseModel from rich import print +from ulid import ULID from dreadnode.version import VERSION -from .strikes import StrikesClient +from .models import Project, Run, Task, TraceSpan ModelT = t.TypeVar("ModelT", bound=BaseModel) @@ -15,8 +16,6 @@ class ApiClient: """Client for the Dreadnode API.""" - strikes: StrikesClient - def __init__( self, base_url: str, @@ -42,8 +41,6 @@ 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.""" @@ -101,3 +98,33 @@ def request( raise RuntimeError(self._get_error_message(response)) from e return response + + def list_projects(self) -> list[Project]: + response = self._client.request("GET", "/strikes/projects") + return [Project(**project) for project in response.json()] + + def get_project(self, project: str) -> Project: + response = self._client.request("GET", f"/strikes/projects/{project!s}") + return Project(**response.json()) + + def list_runs(self, project: str) -> list[Run]: + response = self._client.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._client.request("GET", f"/strikes/projects/runs/{run!s}") + return Run(**response.json()) + + def get_run_tasks(self, run: str | ULID) -> list[Task]: + response = self._client.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._client.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 diff --git a/dreadnode/api/models.py b/dreadnode/api/models.py new file mode 100644 index 00000000..556c6d34 --- /dev/null +++ b/dreadnode/api/models.py @@ -0,0 +1,133 @@ +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] + + +SpanStatus = t.Literal[ + "pending", # A pending span has been created + "completed", # The span has been finished + "failed", # The raised an exception +] + + +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 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: dict[str, t.Any] + metrics: dict[str, list[Metric]] + inputs: AnyDict + outputs: AnyDict + objects: AnyDict + schema_: dict[str, t.Any] = 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: dict[str, t.Any] + metrics: dict[str, list[Metric]] + inputs: AnyDict + outputs: AnyDict + schema_: dict[str, t.Any] = 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"] = [] diff --git a/dreadnode/api/strikes.py b/dreadnode/api/strikes.py deleted file mode 100644 index 181effb4..00000000 --- a/dreadnode/api/strikes.py +++ /dev/null @@ -1,173 +0,0 @@ -import typing as t -from datetime import datetime -from uuid import UUID - -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 -] - - -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 - span_id: str - parent_span_id: str | None - service_name: str | None - status: StrikeSpanStatus - exception: StrikeSpanException | None - name: str - attributes: dict[str, str] - resource_attributes: dict[str, str] - events: list[StrikeSpanEvent] - links: list[StrikeSpanLink] - - -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 - timestamp: datetime - duration: int - status: StrikeSpanStatus - exception: StrikeSpanException | None - tags: set[str] - params: dict[str, t.Any] - metrics: dict[str, list[ProjectMetric]] - schema_: dict[str, t.Any] = Field(alias="schema") - - -class StrikeProjectTaskResponse(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: 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") - attributes: dict[str, str] - resource_attributes: dict[str, str] - events: list[StrikeSpanEvent] - links: list[StrikeSpanLink] - - -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 - - -# 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 diff --git a/dreadnode/artifact.py b/dreadnode/artifact.py index affa941a..77b663cc 100644 --- a/dreadnode/artifact.py +++ b/dreadnode/artifact.py @@ -1,166 +1,56 @@ -from __future__ import annotations - -import os -from abc import ABC, abstractmethod +import mimetypes +import typing as t from dataclasses import dataclass -from pathlib import Path - -# --- Artifact Core --- +from dreadnode.object import ObjectRef -@dataclass -class ArtifactInfo: - """Metadata about an artifact.""" - - path: str # Relative path within the run - size: int = 0 - is_dir: bool = False - file_format: str | None = None # Optional format if known +ArtifactType = t.Literal["markdown", "table", "image", "file"] @dataclass -class Artifact: - """Represents an artifact associated with a run.""" - - path: str # Relative path within the run +class Artifact(ObjectRef): description: str = "" + mime_type: str + extension: str - # Source of the artifact (only one should be set) - local_path: Path | None = None # Path to local file/dir to upload - content: bytes | None = None # In-memory content - - # Flag for eager saving - eager: bool = False - - @property - def is_dir(self) -> bool: - """Check if this artifact represents a directory.""" - return self.local_path is not None and self.local_path.is_dir() - - -# --- Storage Abstraction --- - - -class ArtifactRepository(ABC): - """Interface for artifact storage systems.""" - - @abstractmethod - def log_artifact(self, run_id: str, artifact: Artifact) -> None: - """Save an artifact to the repository.""" - pass - - @abstractmethod - def log_artifacts(self, run_id: str, local_dir: Path, artifact_path: str | None = None) -> None: - """Save a directory of artifacts.""" - pass - - @abstractmethod - def list_artifacts(self, run_id: str, path: str | None = None) -> list[ArtifactInfo]: - """List artifacts for a run.""" - pass - - @abstractmethod - def download_artifact(self, run_id: str, path: str) -> Path: - """Download an artifact to a local path and return the path.""" - pass - - @abstractmethod - def get_artifact_uri(self, run_id: str, path: str | None = None) -> str: - """Get the URI for an artifact.""" - pass - - -class LocalArtifactRepository(ArtifactRepository): - """Store artifacts in the local filesystem.""" - - def __init__(self, base_path: Path | str): - self.base_path = Path(base_path) - self.base_path.mkdir(parents=True, exist_ok=True) - - def _get_run_artifact_dir(self, run_id: str) -> Path: - """Get the artifact directory for a run.""" - run_dir = self.base_path / run_id / "artifacts" - run_dir.mkdir(parents=True, exist_ok=True) - return run_dir - - def log_artifact(self, run_id: str, artifact: Artifact) -> None: - """Save an artifact to the repository.""" - artifact_dir = self._get_run_artifact_dir(run_id) - - # Handle artifact path with possible subdirectories - if artifact.path: - dest_dir = artifact_dir / os.path.dirname(artifact.path) - dest_dir.mkdir(parents=True, exist_ok=True) - dest_path = artifact_dir / artifact.path - else: - dest_path = artifact_dir / os.path.basename(str(artifact.local_path or "artifact")) - - # Save the artifact - if artifact.local_path: - if artifact.local_path.is_dir(): - self.log_artifacts(run_id, artifact.local_path, artifact.path) - else: - with open(artifact.local_path, "rb") as src, open(dest_path, "wb") as dst: - dst.write(src.read()) - elif artifact.content: - with open(dest_path, "wb") as f: - f.write(artifact.content) - else: - raise ValueError("Artifact has no content to save") - - def log_artifacts(self, run_id: str, local_dir: Path, artifact_path: str | None = None) -> None: - """Save a directory of artifacts.""" - artifact_dir = self._get_run_artifact_dir(run_id) - - if artifact_path: - dest_dir = artifact_dir / artifact_path - else: - dest_dir = artifact_dir - dest_dir.mkdir(parents=True, exist_ok=True) +def get_mime_type_and_extension(data: t.Any) -> tuple[str, str]: + """Get the file extension and MIME type for the given data. - for root, _, files in os.walk(local_dir): - for file in files: - src_path = Path(root) / file - rel_path = src_path.relative_to(local_dir) - dst_path = dest_dir / rel_path + This method determines the file extension and MIME type for the given data based on its content. - dst_path.parent.mkdir(parents=True, exist_ok=True) - with open(src_path, "rb") as src, open(dst_path, "wb") as dst: - dst.write(src.read()) + Args: + data: The data to determine the file extension and MIME type for. - def list_artifacts(self, run_id: str, path: str | None = None) -> list[ArtifactInfo]: - """List artifacts for a run.""" - artifact_dir = self._get_run_artifact_dir(run_id) + Returns: + The file extension and MIME type for the given data. + """ + mime_type: str + extension: str - if path: - list_dir = artifact_dir / path - else: - list_dir = artifact_dir + if isinstance(data, str): + mime_type = "text/plain" + extension = ".txt" - if not list_dir.exists(): - return [] + elif isinstance(data, dict): + mime_type = "application/json" + extension = ".json" - artifact_infos = [] - for item in list_dir.iterdir(): - rel_path = item.relative_to(artifact_dir) - info = ArtifactInfo( - path=str(rel_path), - size=item.stat().st_size if item.is_file() else 0, - is_dir=item.is_dir(), - ) - artifact_infos.append(info) + # Handle binary data + elif isinstance(data, bytes): + # Use python-magic to detect file type + mime = magic.Magic(mime=True) + mime_type = mime.from_buffer(data) - return artifact_infos + # Get extension from MIME type + extension = mimetypes.guess_extension(mime_type) or "" + if not extension and mime_type == "image/jpeg": + extension = ".jpg" # Common case - def download_artifact(self, run_id: str, path: str) -> Path: - """Download an artifact (local copy for consistency with remote repos).""" - artifact_path = self._get_run_artifact_dir(run_id) / path - return artifact_path + # Handle other types + else: + mime_type = "application/octet-stream" + extension = ".bin" - def get_artifact_uri(self, run_id: str, path: str | None = None) -> str: - """Get the URI for an artifact.""" - if path: - return f"file://{self._get_run_artifact_dir(run_id) / path}" - else: - return f"file://{self._get_run_artifact_dir(run_id)}" + # get the original + return ArtifactMetadata(mime_type=mime_type, extension=extension) diff --git a/dreadnode/constants.py b/dreadnode/constants.py index 8fd573a7..07bea3b5 100644 --- a/dreadnode/constants.py +++ b/dreadnode/constants.py @@ -2,36 +2,57 @@ 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] + # Span attributes 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_KIND = f"{SPAN_NAMESPACE}.kind" SPAN_ATTRIBUTE_TAGS_ = f"{SPAN_NAMESPACE}.tags" SPAN_ATTRIBUTE_KIND = f"{SPAN_NAMESPACE}.kind" 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_ARTIFACTS = f"{SPAN_NAMESPACE}.artifacts" SPAN_ATTRIBUTE_RUN_ID = f"{SPAN_NAMESPACE}.run.id" - -SPAN_ATTRIBUTE_RUN_PARAMS = f"{SPAN_NAMESPACE}.run.params" -SPAN_ATTRIBUTES_RUN_INPUTS = f"{SPAN_NAMESPACE}.run.inputs" -SPAN_ATTRIBUTE_RUN_METRICS = f"{SPAN_NAMESPACE}.run.metrics" -SPAN_ATTRIBUTE_RUN_ARTIFACTS = f"{SPAN_NAMESPACE}.run.artifacts" - 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" -SPAN_ATTRIBUTE_TASK_KEY = f"{SPAN_NAMESPACE}.task.key" -SPAN_ATTRIBUTE_TASK_PARAMS = f"{SPAN_NAMESPACE}.task.params" -SPAN_ATTRIBUTE_TASK_INPUTS = f"{SPAN_NAMESPACE}.task.inputs" -SPAN_ATTRIBUTE_TASK_METRICS = f"{SPAN_NAMESPACE}.task.metrics" -SPAN_ATTRIBUTE_TASK_OUTPUT = f"{SPAN_NAMESPACE}.task.output" +EVENT_ATTRIBUTE_OBJECT_KIND = f"{SPAN_NAMESPACE}.object.kind" +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" -SPAN_ATTRIBUTE_TASK_ARGS = f"{SPAN_NAMESPACE}.task.args" # deprecated -SPAN_ATTRIBUTE_TASK_SCORES = f"{SPAN_NAMESPACE}.task.scores" # deprecated +METRIC_ATTRIBUTE_SOURCE_HASH = f"{SPAN_NAMESPACE}.origin.hash" # Environment variable names diff --git a/dreadnode/exporters.py b/dreadnode/exporters.py index 853d6307..2f543f77 100644 --- a/dreadnode/exporters.py +++ b/dreadnode/exporters.py @@ -51,8 +51,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 @@ -66,7 +66,11 @@ def _receive_metrics( except Exception as e: print(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 +98,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}") 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: @@ -135,7 +142,10 @@ def export(self, batch: Sequence[LogData]) -> LogExportResult: print(f"Failed to export logs: {e}") return LogExportResult.FAILURE - 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/main.py b/dreadnode/main.py index 9b769bed..2d57cca4 100644 --- a/dreadnode/main.py +++ b/dreadnode/main.py @@ -4,6 +4,7 @@ import inspect import os import random +import re import typing as t from dataclasses import dataclass from datetime import datetime, timezone @@ -28,11 +29,19 @@ ENV_PROJECT, ENV_SERVER, ENV_SERVER_URL, + AnyDict, + JsonValue, ) from .exporters import FileExportConfig, FileMetricReader, FileSpanExporter -from .score import Scorer, ScorerCallable, T +from .metric import Metric, Scorer, ScorerCallable, T from .task import P, R, Task -from .tracing import AnyDict, JsonValue, Metric, RunSpan, Span, current_run_span, current_task_span +from .tracing import ( + RunSpan, + Span, + TaskSpan, + current_run_span, + current_task_span, +) from .version import VERSION if t.TYPE_CHECKING: @@ -238,13 +247,13 @@ def span( def task( self, *, - scorers: None = ..., - name: str | None = ..., - kind: str | None = ..., - log_params: t.Sequence[str] | t.Literal[True] | None = ..., - log_inputs: t.Sequence[str] | t.Literal[True] | None = ..., - log_output: bool = ..., - tags: t.Sequence[str] | None = ..., + scorers: None = None, + name: str | None = None, + kind: str | None = None, + log_params: t.Sequence[str] | t.Literal[True] | None = None, + log_inputs: t.Sequence[str] | t.Literal[True] | None = None, + log_output: bool = True, + tags: t.Sequence[str] | None = None, **attributes: t.Any, ) -> t.Callable[[t.Callable[P, t.Awaitable[R]] | t.Callable[P, R]], Task[P, R]]: ... @@ -254,12 +263,12 @@ def task( self, *, scorers: t.Sequence[Scorer[R] | ScorerCallable[R]], - name: str | None = ..., - kind: str | None = ..., - log_params: t.Sequence[str] | t.Literal[True] | None = ..., - log_inputs: t.Sequence[str] | t.Literal[True] | None = ..., - log_output: bool = ..., - tags: t.Sequence[str] | None = ..., + name: str | None = None, + kind: str | None = None, + log_params: t.Sequence[str] | t.Literal[True] | None = None, + log_inputs: t.Sequence[str] | t.Literal[True] | None = None, + log_output: bool = True, + tags: t.Sequence[str] | None = None, **attributes: t.Any, ) -> t.Callable[[t.Callable[P, t.Awaitable[R]] | t.Callable[P, R]], Task[P, R]]: ... @@ -291,6 +300,9 @@ def make_task(func: t.Callable[P, t.Awaitable[R]] | t.Callable[P, R]) -> Task[P, _name = name or func_name _kind = kind or func_name + # conform our kind for sanity + _kind = re.sub(r"[\W_]+", "_", _kind.lower()) + _attributes = attributes or {} _attributes["code.function"] = func_name with contextlib.suppress(Exception): @@ -320,6 +332,29 @@ def make_task(func: t.Callable[P, t.Awaitable[R]] | t.Callable[P, R]) -> Task[P, return make_task + def task_span( + self, + name: str, + *, + kind: str | None = None, + params: AnyDict | None = None, + tags: t.Sequence[str] | None = None, + **attributes: t.Any, + ) -> TaskSpan[t.Any]: + if (run := current_run_span.get()) is None: + raise RuntimeError("task_span() must be called within a run") + + kind = kind or re.sub(r"[\W_]+", "_", name.lower()) + return TaskSpan( + name=name, + kind=kind, + attributes=attributes, + params=params, + tags=tags, + run_id=run.run_id, + tracer=self._get_tracer(), + ) + def scorer( self, *, @@ -344,7 +379,6 @@ def run( *, tags: t.Sequence[str] | None = None, params: AnyDict | None = None, - inputs: AnyDict | None = None, project: str | None = None, **attributes: t.Any, ) -> RunSpan: @@ -360,7 +394,6 @@ def run( attributes=attributes, tracer=self._get_tracer(), params=params, - inputs=inputs, tags=tags, ) @@ -389,10 +422,11 @@ def log_metric( self, key: str, value: float | bool, - step: int = ..., *, - timestamp: datetime | None = ..., - to: ToObject = ..., + step: int = 0, + origin: t.Any | None = None, + timestamp: datetime | None = None, + to: ToObject = "task-or-run", ) -> None: ... @@ -402,7 +436,8 @@ def log_metric( key: str, value: Metric, *, - to: ToObject = ..., + origin: t.Any | None = None, + to: ToObject = "task-or-run", ) -> None: ... @@ -410,8 +445,9 @@ def log_metric( self, key: str, value: float | bool | Metric, - step: int = 0, *, + step: int = 0, + origin: t.Any | None = None, timestamp: datetime | None = None, to: ToObject = "task-or-run", ) -> None: @@ -430,26 +466,54 @@ def log_metric( raise RuntimeError( "log_metric() with to='task-or-run' must be called within a run or a task", ) - target.log_metric(key, metric) + target.log_metric(key, metric, origin=origin) elif to == "run": if run is None: raise RuntimeError("log_metric() with to='run' must be called within a run") - run.log_metric(key, metric) + run.log_metric(key, metric, origin=origin) def log_input( self, - key: str, + name: str, value: JsonValue, *, + kind: str | None = None, to: ToObject = "task-or-run", + **attributes: t.Any, ) -> None: - self.log_inputs(to=to, **{key: value}) + task = current_task_span.get() + run = current_run_span.get() + + if to == "task-or-run": + target = task or run + if target is None: + raise RuntimeError( + "log_inputs() with to='task-or-run' must be called within a run or a task", + ) + target.log_input(name, value, kind=kind, **attributes) + + elif to == "run": + if run is None: + raise RuntimeError("log_inputs() with to='run' must be called within a run") + run.log_input(name, value, kind=kind, **attributes) def log_inputs( self, to: ToObject = "task-or-run", **inputs: JsonValue, + ) -> None: + for name, value in inputs.items(): + self.log_input(name, value, to=to) + + def log_output( + self, + name: str, + value: t.Any, + *, + kind: str | None = None, + to: ToObject = "task-or-run", + **attributes: JsonValue, ) -> None: task = current_task_span.get() run = current_run_span.get() @@ -458,22 +522,30 @@ def log_inputs( target = task or run if target is None: raise RuntimeError( - "log_inputs() with to='task-or-run' must be called within a run or a task", + "log_output() with to='task-or-run' must be called within a run or a task", ) - target.log_inputs(**inputs) + target.log_output(name, value, kind=kind, **attributes) elif to == "run": if run is None: - raise RuntimeError("log_inputs() with to='run' must be called within a run") - run.log_inputs(**inputs) + raise RuntimeError("log_output() with to='run' must be called within a run") + run.log_output(name, value, kind=kind, **attributes) - def log_output( + def log_outputs( self, - value: t.Any, + to: ToObject = "task-or-run", + **outputs: JsonValue, ) -> None: - if (task := current_task_span.get()) is None: - raise RuntimeError("log_output() must be called within a task") - task.output = value + for name, value in outputs.items(): + self.log_output(name, value, to=to) + + def link_objects(self, origin: t.Any, link: t.Any, **attributes: JsonValue) -> None: + if (run := current_run_span.get()) is None: + raise RuntimeError("link() must be called within a run") + + 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/score.py b/dreadnode/metric.py similarity index 72% rename from dreadnode/score.py rename to dreadnode/metric.py index d7d88611..90639222 100644 --- a/dreadnode/score.py +++ b/dreadnode/metric.py @@ -1,15 +1,39 @@ import inspect import typing as t -from dataclasses import dataclass +from dataclasses import dataclass, field from datetime import datetime, timezone from logfire._internal.utils import safe_repr from opentelemetry.trace import Tracer -from .tracing import Metric, Span +from .constants import JsonDict, JsonValue T = t.TypeVar("T") + +@dataclass +class Metric: + value: float + step: int = 0 + timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc)) + attributes: JsonDict = field(default_factory=dict) + + @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." + 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] @@ -69,6 +93,8 @@ def clone(self) -> "Scorer[T]": ) async def __call__(self, object: T) -> Metric: + from .tracing import Span + with Span( name=self.name, tags=self.tags, diff --git a/dreadnode/object.py b/dreadnode/object.py new file mode 100644 index 00000000..23d69cd0 --- /dev/null +++ b/dreadnode/object.py @@ -0,0 +1,209 @@ +import contextlib +import hashlib +import typing as t +from dataclasses import dataclass + +from pydantic import TypeAdapter + + +@dataclass +class ObjectRef: + name: str + kind: str + hash: str + + +@dataclass +class ObjectUri: + type: t.Literal["uri"] + uri: str + size: int + + +@dataclass +class ObjectVal: + type: t.Literal["val"] + value: t.Any + + +Object = ObjectUri | ObjectVal + + +def _hash(data: bytes) -> str: + return hashlib.md5(data).hexdigest()[:16] # noqa: S324 + + +def universal_hash(obj: t.Any) -> str: + # Type adapter for most common objects + with contextlib.suppress(Exception): + json_str = TypeAdapter(t.Any).dump_json(obj) + return _hash(json_str) + + # Numpy + with contextlib.suppress(Exception): + import numpy as np + + if isinstance(obj, np.ndarray): + return _hash(obj.tobytes()) + + # Pandas + with contextlib.suppress(Exception): + import pandas as pd # type: ignore [import-untyped] + + if isinstance(obj, pd.DataFrame | pd.Series): + return _hash(pd.util.hash_pandas_object(obj)) + + # Hashable objects + with contextlib.suppress(Exception): + return _hash(hash(obj).to_bytes(16, "big")) + + # Try repr() - commenting for now as it's probably not stable + # with contextlib.suppress(Exception): + # return g_hash_func(repr(obj).encode()).hexdigest() + + # Fallback to id() + return _hash(id(obj).to_bytes(16, "big")) + + +# Storage + + +# class ObjectStorage(abc.ABC): +# async def setup(self) -> None: +# """Setup the artifact storage manager.""" + +# @staticmethod +# @abstractmethod +# async def _save_artifact( +# self, +# run_state: RunState, +# artifact_id: uuid.UUID, +# data: ArtifactDataTypes, +# original_type: ArtifactOriginalTypes, +# ) -> ArtifactSaved: +# """ +# Asynchronously saves an artifact. +# Args: +# run_state (RunState): The current state of the run. +# artifact_id (uuid.UUID): The unique identifier of the artifact. +# data (ArtifactDataTypes): The data of the artifact to be saved. +# original_type (ArtifactOriginalTypes): The original type of the artifact. +# Returns: +# ArtifactSaved: The result of the artifact save operation. +# """ + +# @abstractmethod +# async def _get_artifact(self, uri: str) -> ArtifactDataTypes: +# """ +# Asynchronously retrieves an artifact from the given path. +# Args: +# path (str | Path): The path to the artifact. +# Returns: +# ArtifactDataTypes: The data type of the retrieved artifact. +# """ + +# @abstractmethod +# async def _delete_artifact(self, uri: str | Path) -> bool: +# """ +# Asynchronously deletes an artifact at the given path. +# Args: +# path (str | Path): The path to the artifact to be deleted. +# Returns: +# bool: True if the artifact was successfully deleted, False otherwise. +# """ + + +# class LocalArtifactRepository(ArtifactRepository): +# """Store artifacts in the local filesystem.""" + +# def __init__(self, base_path: Path | str): +# self.base_path = Path(base_path) +# self.base_path.mkdir(parents=True, exist_ok=True) + +# def _get_run_artifact_dir(self, run_id: str) -> Path: +# """Get the artifact directory for a run.""" +# run_dir = self.base_path / run_id / "artifacts" +# run_dir.mkdir(parents=True, exist_ok=True) +# return run_dir + +# def log_artifact(self, run_id: str, artifact: Artifact) -> None: +# """Save an artifact to the repository.""" +# artifact_dir = self._get_run_artifact_dir(run_id) + +# # Handle artifact path with possible subdirectories +# if artifact.path: +# dest_dir = artifact_dir / os.path.dirname(artifact.path) +# dest_dir.mkdir(parents=True, exist_ok=True) +# dest_path = artifact_dir / artifact.path +# else: +# dest_path = artifact_dir / os.path.basename(str(artifact.local_path or "artifact")) + +# # Save the artifact +# if artifact.local_path: +# if artifact.local_path.is_dir(): +# self.log_artifacts(run_id, artifact.local_path, artifact.path) +# else: +# with open(artifact.local_path, "rb") as src, open(dest_path, "wb") as dst: +# dst.write(src.read()) +# elif artifact.content: +# with open(dest_path, "wb") as f: +# f.write(artifact.content) +# else: +# raise ValueError("Artifact has no content to save") + +# def log_artifacts(self, run_id: str, local_dir: Path, artifact_path: str | None = None) -> None: +# """Save a directory of artifacts.""" +# artifact_dir = self._get_run_artifact_dir(run_id) + +# if artifact_path: +# dest_dir = artifact_dir / artifact_path +# else: +# dest_dir = artifact_dir + +# dest_dir.mkdir(parents=True, exist_ok=True) + +# for root, _, files in os.walk(local_dir): +# for file in files: +# src_path = Path(root) / file +# rel_path = src_path.relative_to(local_dir) +# dst_path = dest_dir / rel_path + +# dst_path.parent.mkdir(parents=True, exist_ok=True) +# with open(src_path, "rb") as src, open(dst_path, "wb") as dst: +# dst.write(src.read()) + +# def list_artifacts(self, run_id: str, path: str | None = None) -> list[ArtifactInfo]: +# """List artifacts for a run.""" +# artifact_dir = self._get_run_artifact_dir(run_id) + +# if path: +# list_dir = artifact_dir / path +# else: +# list_dir = artifact_dir + +# if not list_dir.exists(): +# return [] + +# artifact_infos = [] +# for item in list_dir.iterdir(): +# rel_path = item.relative_to(artifact_dir) +# info = ArtifactInfo( +# path=str(rel_path), +# size=item.stat().st_size if item.is_file() else 0, +# is_dir=item.is_dir(), +# ) +# artifact_infos.append(info) + +# return artifact_infos + +# def download_artifact(self, run_id: str, path: str) -> Path: +# """Download an artifact (local copy for consistency with remote repos).""" +# artifact_path = self._get_run_artifact_dir(run_id) / path +# return artifact_path + +# def get_artifact_uri(self, run_id: str, path: str | None = None) -> str: +# """Get the URI for an artifact.""" +# if path: +# return f"file://{self._get_run_artifact_dir(run_id) / path}" +# else: +# return f"file://{self._get_run_artifact_dir(run_id)}" diff --git a/dreadnode/task.py b/dreadnode/task.py index ea20d1fb..69cc7f67 100644 --- a/dreadnode/task.py +++ b/dreadnode/task.py @@ -7,7 +7,7 @@ from logfire._internal.stack_info import warn_at_user_stacklevel from opentelemetry.trace import Tracer -from .score import Scorer, ScorerCallable +from .metric import Scorer, ScorerCallable from .tracing import TaskSpan, current_run_span P = t.ParamSpec("P") @@ -144,24 +144,25 @@ async def run(self, *args: P.args, **kwargs: P.kwargs) -> TaskSpan[R]: kind=self.kind, attributes=self.attributes, params=params, - inputs=inputs, tags=self.tags, run_id=run.run_id, tracer=self.tracer, - log_output=self.log_output, ) as span: + for name, value in inputs.items(): + span.log_input(name, value, kind=f"{self.kind}.input.{name}") + output = t.cast(R | t.Awaitable[R], self.func(*args, **kwargs)) if inspect.isawaitable(output): output = await output - # Only apply the output if the user hasn't - # already logged it themselves. - if span._output is None: # noqa: SLF001 - span.output = output + span.output = output + + if self.log_output: + span.log_output("output", output, kind=f"{self.kind}.output") for scorer in self.scorers: metric = await scorer(span.output) - span.log_metric(scorer.name, metric) + span.log_metric(scorer.name, metric, origin=span.output) return span diff --git a/dreadnode/tracing.py b/dreadnode/tracing.py index 804f7b6b..91452583 100644 --- a/dreadnode/tracing.py +++ b/dreadnode/tracing.py @@ -1,7 +1,7 @@ +import re import types import typing as t from contextvars import ContextVar, Token -from dataclasses import dataclass, field from datetime import datetime, timezone import typing_extensions as te @@ -20,38 +20,41 @@ from ulid import ULID from .constants import ( + EVENT_ATTRIBUTE_LINK_HASH, + EVENT_ATTRIBUTE_OBJECT_HASH, + EVENT_ATTRIBUTE_OBJECT_KIND, + 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_INPUTS, SPAN_ATTRIBUTE_KIND, + SPAN_ATTRIBUTE_LARGE_ATTRIBUTES, + SPAN_ATTRIBUTE_METRICS, + SPAN_ATTRIBUTE_OBJECTS, + SPAN_ATTRIBUTE_OUTPUTS, + SPAN_ATTRIBUTE_PARAMS, SPAN_ATTRIBUTE_PARENT_TASK_ID, SPAN_ATTRIBUTE_PROJECT, SPAN_ATTRIBUTE_RUN_ID, - SPAN_ATTRIBUTE_RUN_METRICS, - SPAN_ATTRIBUTE_RUN_PARAMS, SPAN_ATTRIBUTE_SCHEMA, SPAN_ATTRIBUTE_TAGS_, - SPAN_ATTRIBUTE_TASK_INPUTS, - SPAN_ATTRIBUTE_TASK_METRICS, - SPAN_ATTRIBUTE_TASK_OUTPUT, - SPAN_ATTRIBUTE_TASK_PARAMS, SPAN_ATTRIBUTE_TYPE, - SPAN_ATTRIBUTES_RUN_INPUTS, + SPAN_ATTRIBUTE_VERSION, + AnyDict, + JsonDict, + JsonValue, SpanType, ) +from .metric import Metric, MetricDict +from .object import ObjectRef, universal_hash +from .version import VERSION R = t.TypeVar("R") -JsonValue = t.Union[ - int, - float, - str, - bool, - None, - list["JsonValue"], - tuple["JsonValue", ...], - "JsonDict", -] -JsonDict = dict[str, JsonValue] - -AnyDict = dict[str, t.Any] current_task_span: ContextVar["TaskSpan[t.Any] | None"] = ContextVar( "current_task_span", @@ -60,30 +63,6 @@ current_run_span: ContextVar["RunSpan | None"] = ContextVar("current_run_span", default=None) -@dataclass -class Metric: - value: float - step: int = 0 - timestamp: datetime = field(default_factory=lambda: datetime.now(timezone.utc)) - attributes: JsonDict = field(default_factory=dict) - - @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." - 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]] - - class Span(ReadableSpan): def __init__( self, @@ -97,6 +76,7 @@ def __init__( ) -> None: self._span_name = name self._pre_attributes = { + SPAN_ATTRIBUTE_VERSION: VERSION, SPAN_ATTRIBUTE_TYPE: type, SPAN_ATTRIBUTE_KIND: kind or "", SPAN_ATTRIBUTE_TAGS_: uniquify_sequence(tags or ()), @@ -191,6 +171,17 @@ def get_attributes(self) -> AnyDict: 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__( @@ -209,11 +200,11 @@ def __init__( } if metrics: - attributes[SPAN_ATTRIBUTE_RUN_METRICS] = metrics + attributes[SPAN_ATTRIBUTE_METRICS] = metrics if params: - attributes[SPAN_ATTRIBUTE_RUN_PARAMS] = params + attributes[SPAN_ATTRIBUTE_PARAMS] = params if inputs: - attributes[SPAN_ATTRIBUTES_RUN_INPUTS] = inputs + attributes[SPAN_ATTRIBUTE_INPUTS] = inputs super().__init__(f"run.{run_id}.update", attributes, tracer, type="run_update") @@ -226,14 +217,15 @@ def __init__( attributes: AnyDict, tracer: Tracer, params: AnyDict | None = None, - inputs: AnyDict | None = None, metrics: MetricDict | None = None, run_id: str | None = None, tags: t.Sequence[str] | None = None, ) -> None: self._params = params or {} - self._inputs = inputs or {} self._metrics = metrics or {} + self._objects: AnyDict = {} + self._inputs: list[ObjectRef] = [] + self._outputs: list[ObjectRef] = [] self.project = project self._context_token: Token[RunSpan | None] | None = None # contextvars context @@ -241,9 +233,8 @@ def __init__( attributes = { SPAN_ATTRIBUTE_RUN_ID: str(run_id or ULID()), SPAN_ATTRIBUTE_PROJECT: project, - SPAN_ATTRIBUTE_RUN_PARAMS: self._params, - SPAN_ATTRIBUTES_RUN_INPUTS: self._inputs, - SPAN_ATTRIBUTE_RUN_METRICS: self._metrics, + SPAN_ATTRIBUTE_PARAMS: self._params, + SPAN_ATTRIBUTE_METRICS: self._metrics, **attributes, } super().__init__(name, attributes, tracer, type="run", tags=tags) @@ -261,9 +252,15 @@ def __exit__( exc_value: BaseException | None, traceback: types.TracebackType | None, ) -> None: - self.set_attribute(SPAN_ATTRIBUTE_RUN_PARAMS, self._params) - self.set_attribute(SPAN_ATTRIBUTES_RUN_INPUTS, self._inputs) - self.set_attribute(SPAN_ATTRIBUTE_RUN_METRICS, self._metrics) + self.set_attribute(SPAN_ATTRIBUTE_PARAMS, self._params) + self.set_attribute(SPAN_ATTRIBUTE_INPUTS, self._inputs) + self.set_attribute(SPAN_ATTRIBUTE_METRICS, self._metrics) + self.set_attribute(SPAN_ATTRIBUTE_OBJECTS, self._objects) + self.set_attribute(SPAN_ATTRIBUTE_OUTPUTS, self._outputs) + + # Mark our objects attribute as large so it's stored separately + self.set_attribute(SPAN_ATTRIBUTE_LARGE_ATTRIBUTES, [SPAN_ATTRIBUTE_OBJECTS]) + super().__exit__(exc_type, exc_value, traceback) if self._context_token is not None: current_run_span.reset(self._context_token) @@ -272,6 +269,46 @@ def __exit__( def run_id(self) -> str: return str(self.get_attribute(SPAN_ATTRIBUTE_RUN_ID, "")) + def log_object( + self, + value: t.Any, + *, + kind: str | None = None, + event_name: str = EVENT_NAME_OBJECT, + **attributes: JsonValue, + ) -> str: + hash_ = universal_hash(value) + self._objects[hash_] = value + attributes = { + **attributes, + EVENT_ATTRIBUTE_OBJECT_HASH: hash_, + EVENT_ATTRIBUTE_ORIGIN_SPAN_ID: trace_api.format_span_id( + trace_api.get_current_span().get_span_context().span_id, + ), + } + if kind is not None: + attributes[EVENT_ATTRIBUTE_OBJECT_KIND] = kind + self.log_event(name=event_name, attributes=attributes) + return hash_ + + 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 @@ -296,13 +333,19 @@ def log_params(self, **params: t.Any) -> None: @property def inputs(self) -> AnyDict: - return self._inputs - - def log_input(self, key: str, value: t.Any) -> None: - self.log_inputs(**{key: value}) + return {ref.name: self.get_object(ref.hash) for ref in self._inputs} - def log_inputs(self, **inputs: t.Any) -> None: - self._inputs.update(inputs) + def log_input( + self, + name: str, + value: t.Any, + *, + kind: str | None = None, + **attributes: JsonValue, + ) -> None: + kind = kind or re.sub(r"\W+", "_", name.lower()) + hash_ = self.log_object(value, kind=kind, event_name=EVENT_NAME_OBJECT_INPUT, **attributes) + self._inputs.append(ObjectRef(name, kind=kind, hash=hash_)) @property def metrics(self) -> MetricDict: @@ -313,9 +356,10 @@ def log_metric( self, key: str, value: float | bool, - step: int = ..., *, - timestamp: datetime | None = ..., + step: int = 0, + origin: t.Any | None = None, + timestamp: datetime | None = None, ) -> None: ... @@ -324,6 +368,8 @@ def log_metric( self, key: str, value: Metric, + *, + origin: t.Any | None = None, ) -> None: ... @@ -331,8 +377,9 @@ def log_metric( self, key: str, value: float | bool | Metric, - step: int = 0, *, + step: int = 0, + origin: t.Any | None = None, timestamp: datetime | None = None, ) -> None: metric = ( @@ -340,6 +387,15 @@ def log_metric( 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, + kind=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 @@ -352,6 +408,22 @@ def log_metric( ): pass + @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, + *, + kind: str | None = None, + **attributes: JsonValue, + ) -> None: + kind = kind or re.sub(r"\W+", "_", name.lower()) + hash_ = self.log_object(value, kind=kind, event_name=EVENT_NAME_OBJECT_OUTPUT, **attributes) + self._outputs.append(ObjectRef(name, kind=kind, hash=hash_)) + class TaskSpan(Span, t.Generic[R]): def __init__( @@ -363,24 +435,24 @@ def __init__( *, kind: str | None = None, params: AnyDict | None = None, - inputs: AnyDict | None = None, metrics: MetricDict | None = None, tags: t.Sequence[str] | None = None, - log_output: bool = True, ) -> None: self._params = params or {} - self._inputs = inputs or {} self._metrics = metrics or {} - self._output: R | None = None - self._log_output = log_output + self._inputs: list[ObjectRef] = [] + self._outputs: list[ObjectRef] = [] + + self._output: R | None = None # 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_TASK_PARAMS: self._params, - SPAN_ATTRIBUTE_TASK_INPUTS: self._inputs, - SPAN_ATTRIBUTE_TASK_METRICS: self._metrics, + 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", kind=kind, tags=tags) @@ -389,6 +461,11 @@ 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__() @@ -398,11 +475,10 @@ def __exit__( exc_value: BaseException | None, traceback: types.TracebackType | None, ) -> None: - if self._log_output: - self.set_attribute(SPAN_ATTRIBUTE_TASK_OUTPUT, self._output) - self.set_attribute(SPAN_ATTRIBUTE_TASK_PARAMS, self._params) - self.set_attribute(SPAN_ATTRIBUTE_TASK_INPUTS, self._inputs) - self.set_attribute(SPAN_ATTRIBUTE_TASK_METRICS, self._metrics) + self.set_attribute(SPAN_ATTRIBUTE_PARAMS, self._params) + self.set_attribute(SPAN_ATTRIBUTE_INPUTS, self._inputs) + self.set_attribute(SPAN_ATTRIBUTE_METRICS, self._metrics) + self.set_attribute(SPAN_ATTRIBUTE_OUTPUTS, self._outputs) super().__exit__(exc_type, exc_value, traceback) if self._context_token is not None: current_task_span.reset(self._context_token) @@ -415,6 +491,16 @@ def run_id(self) -> str: 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 self._output is None: @@ -422,9 +508,26 @@ def output(self) -> R: return self._output @output.setter - def output(self, value: R | None) -> None: + def output(self, value: R) -> None: self._output = value + def log_output( + self, + name: str, + value: t.Any, + *, + kind: str | None = None, + **attributes: JsonValue, + ) -> None: + kind = kind or re.sub(r"\W+", "_", name.lower()) + hash_ = self.run.log_object( + value, + kind=kind, + event_name=EVENT_NAME_OBJECT_OUTPUT, + **attributes, + ) + self._outputs.append(ObjectRef(name, kind=kind, hash=hash_)) + @property def params(self) -> AnyDict: return self._params @@ -437,13 +540,24 @@ def log_params(self, **params: t.Any) -> None: @property def inputs(self) -> AnyDict: - return self._inputs + return {ref.name: self.run.get_object(ref.hash) for ref in self._inputs} - def log_input(self, key: str, value: t.Any) -> None: - self.log_inputs(**{key: value}) - - def log_inputs(self, **inputs: t.Any) -> None: - self._inputs.update(inputs) + def log_input( + self, + name: str, + value: t.Any, + *, + kind: str | None = None, + **attributes: JsonValue, + ) -> None: + kind = kind or re.sub(r"\W+", "_", name.lower()) + hash_ = self.run.log_object( + value, + kind=kind, + event_name=EVENT_NAME_OBJECT_INPUT, + **attributes, + ) + self._inputs.append(ObjectRef(name, kind=kind, hash=hash_)) @property def metrics(self) -> dict[str, list[Metric]]: @@ -454,9 +568,10 @@ def log_metric( self, key: str, value: float | bool, - step: int = ..., *, - timestamp: datetime | None = ..., + step: int = 0, + origin: t.Any | None = None, + timestamp: datetime | None = None, ) -> None: ... @@ -465,6 +580,8 @@ def log_metric( self, key: str, value: Metric, + *, + origin: t.Any | None = None, ) -> None: ... @@ -472,8 +589,9 @@ def log_metric( self, key: str, value: float | bool | Metric, - step: int = 0, *, + step: int = 0, + origin: t.Any | None = None, timestamp: datetime | None = None, ) -> None: metric = ( @@ -481,10 +599,21 @@ def log_metric( 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, + kind=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 `kind` as a prefix + # with our `kind` 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.kind}.{key}", metric) From 9c1fd05d86d588f9500eebbfebb157a2541d3fc1 Mon Sep 17 00:00:00 2001 From: monoxgas Date: Thu, 20 Mar 2025 12:12:45 -0600 Subject: [PATCH 04/24] shuffling some things prior to merge --- dreadnode/api/client.py | 110 +++++++++++++++++++++++++++++- dreadnode/api/models.py | 17 +++++ dreadnode/logging.py | 58 ++++++++++++++++ dreadnode/storage/__init__.py | 0 dreadnode/{ => storage}/object.py | 4 +- 5 files changed, 185 insertions(+), 4 deletions(-) create mode 100644 dreadnode/logging.py create mode 100644 dreadnode/storage/__init__.py rename dreadnode/{ => storage}/object.py (98%) diff --git a/dreadnode/api/client.py b/dreadnode/api/client.py index 9840238d..26ffc937 100644 --- a/dreadnode/api/client.py +++ b/dreadnode/api/client.py @@ -1,14 +1,24 @@ +import io import json import typing as t import httpx +import pandas as pd from pydantic import BaseModel -from rich import print from ulid import ULID from dreadnode.version import VERSION -from .models import Project, Run, Task, TraceSpan +from .models import ( + MetricAggregationType, + Project, + Run, + StatusFilter, + Task, + TimeAggregationType, + TimeAxisType, + TraceSpan, +) ModelT = t.TypeVar("ModelT", bound=BaseModel) @@ -128,3 +138,99 @@ def get_run_trace(self, run: str | ULID) -> list[Task | TraceSpan]: 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._client.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._client.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._client.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._client.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)) diff --git a/dreadnode/api/models.py b/dreadnode/api/models.py index 556c6d34..b7776c6f 100644 --- a/dreadnode/api/models.py +++ b/dreadnode/api/models.py @@ -14,6 +14,23 @@ "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 diff --git a/dreadnode/logging.py b/dreadnode/logging.py new file mode 100644 index 00000000..2b5f0892 --- /dev/null +++ b/dreadnode/logging.py @@ -0,0 +1,58 @@ +""" +We use loguru for logging. This module provides a function to configure logging handlers. + +To just enable dreadnode logs to flow, call `logger.enable("dreadnode")` after importing the module. +""" + +import pathlib +import sys +import typing as t + +from loguru import logger + +g_configured: bool = False + +LogLevelList = ["trace", "debug", "info", "success", "warning", "error", "critical"] +LogLevelLiteral = t.Literal["trace", "debug", "info", "success", "warning", "error", "critical"] +"""Valid logging levels.""" + + +def configure_logging( + log_level: LogLevelLiteral, + log_file: pathlib.Path | None = None, + log_file_level: LogLevelLiteral = "debug", +) -> None: + """ + Configures common loguru handlers. + + Args: + log_level: The desired log level. + log_file: The path to the log file. If None, logging + will only be done to the console. + log_file_level: The log level for the log file. + """ + global g_configured # noqa: PLW0603 + + if g_configured: + return + + logger.enable("dreadnode") + + logger.level("TRACE", color="", icon="[T]") + logger.level("DEBUG", color="", icon="[_]") + logger.level("INFO", color="", icon="[=]") + logger.level("SUCCESS", color="", icon="[+]") + logger.level("WARNING", color="", icon="[-]") + logger.level("ERROR", color="", icon="[!]") + logger.level("CRITICAL", color="", icon="[x]") + + custom_format = "{time:HH:mm:ss.SSS} | {level.icon} {message}" + + logger.remove() + logger.add(sys.stderr, format=custom_format, level=log_level.upper()) + + if log_file is not None: + logger.add(log_file, format=custom_format, level=log_file_level.upper()) + logger.info(f"Logging to {log_file}") + + g_configured = True diff --git a/dreadnode/storage/__init__.py b/dreadnode/storage/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/dreadnode/object.py b/dreadnode/storage/object.py similarity index 98% rename from dreadnode/object.py rename to dreadnode/storage/object.py index 23d69cd0..e9050645 100644 --- a/dreadnode/object.py +++ b/dreadnode/storage/object.py @@ -48,10 +48,10 @@ def universal_hash(obj: t.Any) -> str: # Pandas with contextlib.suppress(Exception): - import pandas as pd # type: ignore [import-untyped] + import pandas as pd if isinstance(obj, pd.DataFrame | pd.Series): - return _hash(pd.util.hash_pandas_object(obj)) + return _hash(pd.util.hash_pandas_object(obj).to_numpy().tobytes()) # Hashable objects with contextlib.suppress(Exception): From 0d41142d97f5b3ef3eadff30fc8d8b05b148100f Mon Sep 17 00:00:00 2001 From: monoxgas Date: Thu, 27 Mar 2025 10:49:20 -0600 Subject: [PATCH 05/24] Pending work --- dreadnode/__init__.py | 2 +- dreadnode/api/client.py | 58 +- dreadnode/api/models.py | 14 + dreadnode/artifact.py | 16 +- dreadnode/constants.py | 57 +- dreadnode/context.py | 36 +- dreadnode/{storage => ext}/__init__.py | 0 dreadnode/ext/fsspec.py | 38 + dreadnode/logging.py | 58 -- dreadnode/main.py | 23 +- dreadnode/metric.py | 4 +- dreadnode/{storage => }/object.py | 18 +- dreadnode/storage/base.py | 40 + dreadnode/task.py | 4 +- dreadnode/tracing/__init__.py | 0 dreadnode/tracing/constants.py | 35 + dreadnode/{ => tracing}/exporters.py | 0 dreadnode/{tracing.py => tracing/span.py} | 11 +- dreadnode/types.py | 17 + poetry.lock | 891 ++++++++++++++++++++-- pyproject.toml | 4 + 21 files changed, 1063 insertions(+), 263 deletions(-) rename dreadnode/{storage => ext}/__init__.py (100%) create mode 100644 dreadnode/ext/fsspec.py delete mode 100644 dreadnode/logging.py rename dreadnode/{storage => }/object.py (95%) create mode 100644 dreadnode/storage/base.py create mode 100644 dreadnode/tracing/__init__.py create mode 100644 dreadnode/tracing/constants.py rename dreadnode/{ => tracing}/exporters.py (100%) rename dreadnode/{tracing.py => tracing/span.py} (99%) create mode 100644 dreadnode/types.py diff --git a/dreadnode/__init__.py b/dreadnode/__init__.py index 3d8b34f0..e7fa4c49 100644 --- a/dreadnode/__init__.py +++ b/dreadnode/__init__.py @@ -2,7 +2,7 @@ from dreadnode.metric import Metric, MetricDict, Scorer from dreadnode.object import Object from dreadnode.task import Task -from dreadnode.tracing import RunSpan, Span, TaskSpan +from dreadnode.tracing.span import RunSpan, Span, TaskSpan from dreadnode.version import VERSION configure = DEFAULT_INSTANCE.configure diff --git a/dreadnode/api/client.py b/dreadnode/api/client.py index 26ffc937..d827035e 100644 --- a/dreadnode/api/client.py +++ b/dreadnode/api/client.py @@ -1,6 +1,7 @@ import io import json import typing as t +from logging import getLogger import httpx import pandas as pd @@ -18,8 +19,11 @@ TimeAggregationType, TimeAxisType, TraceSpan, + UserDataCredentials, ) +logger = getLogger(__name__) + ModelT = t.TypeVar("ModelT", bound=BaseModel) @@ -54,20 +58,20 @@ def __init__( def _log_request(self, request: httpx.Request) -> None: """Log every request to the console if debug is enabled.""" - print("-------------------------------------------") - print(f"[bold]{request.method}[/] {request.url}") - print("Headers:", request.headers) - print("Content:", request.content) - print("-------------------------------------------") + logger.info("-------------------------------------------") + logger.info("%s %s", request.method, request.url) + logger.info("Headers: %s", request.headers) + logger.info("Content: %s", request.content) + logger.info("-------------------------------------------") def _log_response(self, response: httpx.Response) -> None: """Log every response to the console if debug is enabled.""" - print("-------------------------------------------") - print(f"Response: {response.status_code}") - print("Headers:", response.headers) - print("Content:", response.read()) - print("--------------------------------------------") + logger.info("-------------------------------------------") + logger.info("Status: %s", response.status_code) + logger.info("Headers: %s", response.headers) + logger.info("Content: %s", response.content) + logger.info("--------------------------------------------") def _get_error_message(self, response: httpx.Response) -> str: """Get the error message from the response.""" @@ -82,23 +86,23 @@ 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) + response = self._request(method, path, params, json_data) if response.status_code == 401: # noqa: PLR2004 raise RuntimeError("Authentication failed, please check your API token.") @@ -110,27 +114,27 @@ def request( return response def list_projects(self) -> list[Project]: - response = self._client.request("GET", "/strikes/projects") + response = self.request("GET", "/strikes/projects") return [Project(**project) for project in response.json()] def get_project(self, project: str) -> Project: - response = self._client.request("GET", f"/strikes/projects/{project!s}") + response = self.request("GET", f"/strikes/projects/{project!s}") return Project(**response.json()) def list_runs(self, project: str) -> list[Run]: - response = self._client.request("GET", f"/strikes/projects/{project!s}/runs") + 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._client.request("GET", f"/strikes/projects/runs/{run!s}") + 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._client.request("GET", f"/strikes/projects/runs/{run!s}/tasks") + 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._client.request("GET", f"/strikes/projects/runs/{run!s}/spans") + 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: @@ -150,7 +154,7 @@ def export_runs( status: StatusFilter = "completed", aggregations: list[MetricAggregationType] | None = None, ) -> pd.DataFrame: - response = self._client.request( + response = self.request( "GET", f"/strikes/projects/{project!s}/export", params={ @@ -172,7 +176,7 @@ def export_metrics( metrics: list[str] | None = None, aggregations: list[MetricAggregationType] | None = None, ) -> pd.DataFrame: - response = self._client.request( + response = self.request( "GET", f"/strikes/projects/{project!s}/export/metrics", params={ @@ -196,7 +200,7 @@ def export_parameters( metrics: list[str] | None = None, aggregations: list[MetricAggregationType] | None = None, ) -> pd.DataFrame: - response = self._client.request( + response = self.request( "GET", f"/strikes/projects/{project!s}/export/parameters", params={ @@ -221,7 +225,7 @@ def export_timeseries( time_axis: TimeAxisType = "relative", aggregations: list[TimeAggregationType] | None = None, ) -> pd.DataFrame: - response = self._client.request( + response = self.request( "GET", f"/strikes/projects/{project!s}/export/timeseries", params={ @@ -234,3 +238,9 @@ def export_timeseries( }, ) 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 index b7776c6f..3f411745 100644 --- a/dreadnode/api/models.py +++ b/dreadnode/api/models.py @@ -148,3 +148,17 @@ class SpanTree(BaseModel): 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/artifact.py b/dreadnode/artifact.py index 77b663cc..27269238 100644 --- a/dreadnode/artifact.py +++ b/dreadnode/artifact.py @@ -3,13 +3,25 @@ from dataclasses import dataclass from dreadnode.object import ObjectRef +from dreadnode.types import AnyDict -ArtifactType = t.Literal["markdown", "table", "image", "file"] +ArtifactHint = t.Literal[ + "markdown", + "table", + "code", + "image", + "csv", + "dataframe", + "notebook", + "file", +] @dataclass class Artifact(ObjectRef): - description: str = "" + hint: ArtifactHint + description: str + attributes: AnyDict mime_type: str extension: str diff --git a/dreadnode/constants.py b/dreadnode/constants.py index 07bea3b5..e8936aa5 100644 --- a/dreadnode/constants.py +++ b/dreadnode/constants.py @@ -1,59 +1,3 @@ -from __future__ import annotations - -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] - -# Span attributes - -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_KIND = f"{SPAN_NAMESPACE}.kind" -SPAN_ATTRIBUTE_TAGS_ = f"{SPAN_NAMESPACE}.tags" -SPAN_ATTRIBUTE_KIND = f"{SPAN_NAMESPACE}.kind" -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_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_KIND = f"{SPAN_NAMESPACE}.object.kind" -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" - # Environment variable names ENV_SERVER_URL = "DREADNODE_SERVER_URL" @@ -66,3 +10,4 @@ # Default values DEFAULT_SERVER_URL = "https://platform.dreadnode.io" +DEFAULT_LOCAL_OBJECT_DIR = ".dreadnode/objects" diff --git a/dreadnode/context.py b/dreadnode/context.py index 884086af..258daea3 100644 --- a/dreadnode/context.py +++ b/dreadnode/context.py @@ -1,5 +1,3 @@ -from __future__ import annotations - import typing as t from contextvars import ContextVar from dataclasses import dataclass, field @@ -48,7 +46,7 @@ def use(self, **kwargs: Any) -> dict[str, Any]: if not self.required: kwargs[self.param_name] = self.default else: - raise RuntimeError(f"Context resolution failed for {self.param_name}: {str(e)}") + raise RuntimeError(f"Context resolution failed for {self.param_name}: {e!s}") return kwargs @@ -88,14 +86,16 @@ def set_scoped(self, value: t.Any, id_: str | None = None) -> None: 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: - ... + 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 | 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: + 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: @@ -105,26 +105,24 @@ def _get(self, type_: type[T], id_: str | None = None, strict: bool = True) -> T 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: + if len(matching) > 1 and strict: raise ValueError( - f"Multiple {type_.__name__} objects exist. Use a specific id to get one of the values." + 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] + elif 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: + if len(matching) > 1 and strict: raise ValueError( - f"Multiple {type_.__name__} objects exist. Use a specific id to get one of the values." + 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] + elif key in self._global_context: + return self._global_context[key] if strict: raise KeyError(f"{type_.__name__}{' with id ' + repr(id_) if id_ else ''}") @@ -150,7 +148,7 @@ def __enter__(self) -> None: self.previous[key] = self.context._scoped_context.get(key) self.context.set_scoped(value, id_) - def __exit__(self, *exc: Any) -> None: + def __exit__(self, *exc: object) -> None: # Restore previous values for key, value in self.previous.items(): if value is None: diff --git a/dreadnode/storage/__init__.py b/dreadnode/ext/__init__.py similarity index 100% rename from dreadnode/storage/__init__.py rename to dreadnode/ext/__init__.py diff --git a/dreadnode/ext/fsspec.py b/dreadnode/ext/fsspec.py new file mode 100644 index 00000000..210ba2ea --- /dev/null +++ b/dreadnode/ext/fsspec.py @@ -0,0 +1,38 @@ +import typing as t + +from fsspec import register_implementation # type: ignore [import-untyped] +from fsspec.implementations.dirfs import DirFileSystem # type: ignore [import-untyped] +from s3fs import S3FileSystem # type: ignore [import-untyped] + +from dreadnode.api.client import ApiClient + + +class DreadnodeS3Filesystem(DirFileSystem): # type: ignore [misc] + def __init__( + self, + api_client: ApiClient | None = None, + dirfs_options: dict[str, t.Any] | None = None, + **s3fs_options: t.Any, + ) -> None: + from dreadnode.main import DEFAULT_INSTANCE + + self.api_client = api_client or DEFAULT_INSTANCE.api() + credentials = self.api_client.get_user_data_credentials() + + s3_fs = S3FileSystem( + key=credentials.access_key_id, + secret=credentials.secret_access_key, + token=credentials.session_token, + endpoint_url=credentials.endpoint, + client_kwargs={"region_name": credentials.region}, + **s3fs_options, + ) + + super().__init__( + path=f"{credentials.bucket}/{credentials.prefix}/", + fs=s3_fs, + **(dirfs_options or {}), + ) + + +register_implementation("dn", DreadnodeS3Filesystem) diff --git a/dreadnode/logging.py b/dreadnode/logging.py deleted file mode 100644 index 2b5f0892..00000000 --- a/dreadnode/logging.py +++ /dev/null @@ -1,58 +0,0 @@ -""" -We use loguru for logging. This module provides a function to configure logging handlers. - -To just enable dreadnode logs to flow, call `logger.enable("dreadnode")` after importing the module. -""" - -import pathlib -import sys -import typing as t - -from loguru import logger - -g_configured: bool = False - -LogLevelList = ["trace", "debug", "info", "success", "warning", "error", "critical"] -LogLevelLiteral = t.Literal["trace", "debug", "info", "success", "warning", "error", "critical"] -"""Valid logging levels.""" - - -def configure_logging( - log_level: LogLevelLiteral, - log_file: pathlib.Path | None = None, - log_file_level: LogLevelLiteral = "debug", -) -> None: - """ - Configures common loguru handlers. - - Args: - log_level: The desired log level. - log_file: The path to the log file. If None, logging - will only be done to the console. - log_file_level: The log level for the log file. - """ - global g_configured # noqa: PLW0603 - - if g_configured: - return - - logger.enable("dreadnode") - - logger.level("TRACE", color="", icon="[T]") - logger.level("DEBUG", color="", icon="[_]") - logger.level("INFO", color="", icon="[=]") - logger.level("SUCCESS", color="", icon="[+]") - logger.level("WARNING", color="", icon="[-]") - logger.level("ERROR", color="", icon="[!]") - logger.level("CRITICAL", color="", icon="[x]") - - custom_format = "{time:HH:mm:ss.SSS} | {level.icon} {message}" - - logger.remove() - logger.add(sys.stderr, format=custom_format, level=log_level.upper()) - - if log_file is not None: - logger.add(log_file, format=custom_format, level=log_file_level.upper()) - logger.info(f"Logging to {log_file}") - - g_configured = True diff --git a/dreadnode/main.py b/dreadnode/main.py index fa3897ec..53e3c391 100644 --- a/dreadnode/main.py +++ b/dreadnode/main.py @@ -29,19 +29,17 @@ ENV_PROJECT, ENV_SERVER, ENV_SERVER_URL, - AnyDict, - JsonValue, ) from dreadnode.metric import Metric, Scorer, ScorerCallable, T from dreadnode.task import P, R, Task -from dreadnode.tracing import ( +from dreadnode.tracing.exporters import FileExportConfig, FileMetricReader, FileSpanExporter +from dreadnode.tracing.span import ( RunSpan, Span, TaskSpan, current_run_span, current_task_span, ) -from dreadnode.tracing.exporters import FileExportConfig, FileMetricReader, FileSpanExporter from .version import VERSION @@ -50,6 +48,11 @@ from opentelemetry.sdk.trace import SpanProcessor from opentelemetry.trace import Tracer + from dreadnode.types import ( + AnyDict, + JsonValue, + ) + ToObject = t.Literal["task-or-run", "run"] @@ -256,7 +259,8 @@ def task( log_output: bool = True, tags: t.Sequence[str] | None = None, **attributes: t.Any, - ) -> t.Callable[[t.Callable[P, t.Awaitable[R]] | t.Callable[P, R]], Task[P, R]]: ... + ) -> t.Callable[[t.Callable[P, t.Awaitable[R]] | t.Callable[P, R]], Task[P, R]]: + ... @t.overload def task( @@ -270,7 +274,8 @@ def task( log_output: bool = True, tags: t.Sequence[str] | None = None, **attributes: t.Any, - ) -> t.Callable[[t.Callable[P, t.Awaitable[R]] | t.Callable[P, R]], Task[P, R]]: ... + ) -> t.Callable[[t.Callable[P, t.Awaitable[R]] | t.Callable[P, R]], Task[P, R]]: + ... def task( self, @@ -432,7 +437,8 @@ def log_metric( origin: t.Any | None = None, timestamp: datetime | None = None, to: ToObject = "task-or-run", - ) -> None: ... + ) -> None: + ... @t.overload def log_metric( @@ -442,7 +448,8 @@ def log_metric( *, origin: t.Any | None = None, to: ToObject = "task-or-run", - ) -> None: ... + ) -> None: + ... def log_metric( self, diff --git a/dreadnode/metric.py b/dreadnode/metric.py index 90639222..dcc20539 100644 --- a/dreadnode/metric.py +++ b/dreadnode/metric.py @@ -6,7 +6,7 @@ from logfire._internal.utils import safe_repr from opentelemetry.trace import Tracer -from .constants import JsonDict, JsonValue +from dreadnode.types import JsonDict, JsonValue T = t.TypeVar("T") @@ -93,7 +93,7 @@ def clone(self) -> "Scorer[T]": ) async def __call__(self, object: T) -> Metric: - from .tracing import Span + from dreadnode.tracing.span import Span with Span( name=self.name, diff --git a/dreadnode/storage/object.py b/dreadnode/object.py similarity index 95% rename from dreadnode/storage/object.py rename to dreadnode/object.py index e9050645..1606156b 100644 --- a/dreadnode/storage/object.py +++ b/dreadnode/object.py @@ -5,6 +5,8 @@ from pydantic import TypeAdapter +from dreadnode.types import JsonDict + @dataclass class ObjectRef: @@ -14,16 +16,24 @@ class ObjectRef: @dataclass -class ObjectUri: - type: t.Literal["uri"] +class _Object: + hash: str + + +@dataclass +class ObjectUri(_Object): uri: str size: int + type: t.Literal["uri"] = "uri" + attributes: JsonDict | None = None @dataclass -class ObjectVal: - type: t.Literal["val"] +class ObjectVal(_Object): value: t.Any + type: t.Literal["val"] = "val" + hint: str + attributes: JsonDict | None = None Object = ObjectUri | ObjectVal diff --git a/dreadnode/storage/base.py b/dreadnode/storage/base.py new file mode 100644 index 00000000..d55ab607 --- /dev/null +++ b/dreadnode/storage/base.py @@ -0,0 +1,40 @@ +import abc +from pathlib import Path + +from dreadnode.constants import DEFAULT_LOCAL_OBJECT_DIR + + +class ObjectStore(abc.ABC): + @abc.abstractmethod + async def store(self, data: bytes, hash: str) -> str: ... + + @abc.abstractmethod + def exists(self, uri: str) -> bool: ... + + @abc.abstractmethod + async def get(self, uri: str) -> bytes: ... + + +class LocalObjectStore(ObjectStore): + def __init__(self, base_path: Path | str = DEFAULT_LOCAL_OBJECT_DIR): + self.base_path = Path(base_path) + self.base_path.mkdir(parents=True, exist_ok=True) + + def _resolve_uri(self, uri: str) -> Path: + if not uri.startswith("file://"): + raise ValueError(f"Invalid uri for local storage: {uri}") + uri_without_scheme = uri[7:] + return Path(uri_without_scheme) + + async def store(self, data: bytes, hash: str) -> str: + path = self.base_path / hash + path.write_bytes(data) + return path.absolute().as_uri() + + def exists(self, uri: str) -> bool: + if not uri.startswith("file://"): + return False + return self._resolve_uri(uri).exists() + + async def get(self, uri: str) -> bytes: + return self._resolve_uri(uri).read_bytes() diff --git a/dreadnode/task.py b/dreadnode/task.py index 69cc7f67..7faea1a4 100644 --- a/dreadnode/task.py +++ b/dreadnode/task.py @@ -7,8 +7,8 @@ from logfire._internal.stack_info import warn_at_user_stacklevel from opentelemetry.trace import Tracer -from .metric 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") 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..b54cc5ec --- /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_KIND = f"{SPAN_NAMESPACE}.kind" +SPAN_ATTRIBUTE_TAGS_ = f"{SPAN_NAMESPACE}.tags" +SPAN_ATTRIBUTE_KIND = f"{SPAN_NAMESPACE}.kind" +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_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_KIND = f"{SPAN_NAMESPACE}.object.kind" +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 100% rename from dreadnode/exporters.py rename to dreadnode/tracing/exporters.py diff --git a/dreadnode/tracing.py b/dreadnode/tracing/span.py similarity index 99% rename from dreadnode/tracing.py rename to dreadnode/tracing/span.py index d4e10d1a..c4db8eae 100644 --- a/dreadnode/tracing.py +++ b/dreadnode/tracing/span.py @@ -21,6 +21,11 @@ from opentelemetry.util import types as otel_types from ulid import ULID +from dreadnode.metric import Metric, MetricDict +from dreadnode.object import ObjectRef, universal_hash +from dreadnode.types import AnyDict, JsonDict, JsonValue +from dreadnode.version import VERSION + from .constants import ( EVENT_ATTRIBUTE_LINK_HASH, EVENT_ATTRIBUTE_OBJECT_HASH, @@ -46,14 +51,8 @@ SPAN_ATTRIBUTE_TAGS_, SPAN_ATTRIBUTE_TYPE, SPAN_ATTRIBUTE_VERSION, - AnyDict, - JsonDict, - JsonValue, SpanType, ) -from .metric import Metric, MetricDict -from .object import ObjectRef, universal_hash -from .version import VERSION R = t.TypeVar("R") diff --git a/dreadnode/types.py b/dreadnode/types.py new file mode 100644 index 00000000..0a8377f0 --- /dev/null +++ b/dreadnode/types.py @@ -0,0 +1,17 @@ +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] diff --git a/poetry.lock b/poetry.lock index 479d25c7..42f6aa5b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,176 @@ -# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. + +[[package]] +name = "aiobotocore" +version = "2.21.1" +description = "Async client for aws services using botocore and aiohttp" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "aiobotocore-2.21.1-py3-none-any.whl", hash = "sha256:bd7c49a6d6f8a3d9444b0a94417c8da13813b5c7eec1c4f0ec2db7e8ce8f23e7"}, + {file = "aiobotocore-2.21.1.tar.gz", hash = "sha256:010357f43004413e92a9d066bb0db1f241aeb29ffed306e9197061ffc94e6577"}, +] + +[package.dependencies] +aiohttp = ">=3.9.2,<4.0.0" +aioitertools = ">=0.5.1,<1.0.0" +botocore = ">=1.37.0,<1.37.2" +jmespath = ">=0.7.1,<2.0.0" +multidict = ">=6.0.0,<7.0.0" +python-dateutil = ">=2.1,<3.0.0" +wrapt = ">=1.10.10,<2.0.0" + +[package.extras] +awscli = ["awscli (>=1.38.0,<1.38.2)"] +boto3 = ["boto3 (>=1.37.0,<1.37.2)"] + +[[package]] +name = "aiohappyeyeballs" +version = "2.6.1" +description = "Happy Eyeballs for asyncio" +optional = false +python-versions = ">=3.9" +groups = ["main"] +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.14" +description = "Async http client/server framework (asyncio)" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "aiohttp-3.11.14-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e2bc827c01f75803de77b134afdbf74fa74b62970eafdf190f3244931d7a5c0d"}, + {file = "aiohttp-3.11.14-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e365034c5cf6cf74f57420b57682ea79e19eb29033399dd3f40de4d0171998fa"}, + {file = "aiohttp-3.11.14-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c32593ead1a8c6aabd58f9d7ee706e48beac796bb0cb71d6b60f2c1056f0a65f"}, + {file = "aiohttp-3.11.14-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4e7c7ec4146a94a307ca4f112802a8e26d969018fabed526efc340d21d3e7d0"}, + {file = "aiohttp-3.11.14-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c8b2df9feac55043759aa89f722a967d977d80f8b5865a4153fc41c93b957efc"}, + {file = "aiohttp-3.11.14-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c7571f99525c76a6280f5fe8e194eeb8cb4da55586c3c61c59c33a33f10cfce7"}, + {file = "aiohttp-3.11.14-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b59d096b5537ec7c85954cb97d821aae35cfccce3357a2cafe85660cc6295628"}, + {file = "aiohttp-3.11.14-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b42dbd097abb44b3f1156b4bf978ec5853840802d6eee2784857be11ee82c6a0"}, + {file = "aiohttp-3.11.14-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b05774864c87210c531b48dfeb2f7659407c2dda8643104fb4ae5e2c311d12d9"}, + {file = "aiohttp-3.11.14-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4e2e8ef37d4bc110917d038807ee3af82700a93ab2ba5687afae5271b8bc50ff"}, + {file = "aiohttp-3.11.14-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e9faafa74dbb906b2b6f3eb9942352e9e9db8d583ffed4be618a89bd71a4e914"}, + {file = "aiohttp-3.11.14-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:7e7abe865504f41b10777ac162c727af14e9f4db9262e3ed8254179053f63e6d"}, + {file = "aiohttp-3.11.14-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:4848ae31ad44330b30f16c71e4f586cd5402a846b11264c412de99fa768f00f3"}, + {file = "aiohttp-3.11.14-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2d0b46abee5b5737cb479cc9139b29f010a37b1875ee56d142aefc10686a390b"}, + {file = "aiohttp-3.11.14-cp310-cp310-win32.whl", hash = "sha256:a0d2c04a623ab83963576548ce098baf711a18e2c32c542b62322a0b4584b990"}, + {file = "aiohttp-3.11.14-cp310-cp310-win_amd64.whl", hash = "sha256:5409a59d5057f2386bb8b8f8bbcfb6e15505cedd8b2445db510563b5d7ea1186"}, + {file = "aiohttp-3.11.14-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f296d637a50bb15fb6a229fbb0eb053080e703b53dbfe55b1e4bb1c5ed25d325"}, + {file = "aiohttp-3.11.14-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ec6cd1954ca2bbf0970f531a628da1b1338f594bf5da7e361e19ba163ecc4f3b"}, + {file = "aiohttp-3.11.14-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:572def4aad0a4775af66d5a2b5923c7de0820ecaeeb7987dcbccda2a735a993f"}, + {file = "aiohttp-3.11.14-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c68e41c4d576cd6aa6c6d2eddfb32b2acfb07ebfbb4f9da991da26633a3db1a"}, + {file = "aiohttp-3.11.14-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99b8bbfc8111826aa8363442c0fc1f5751456b008737ff053570f06a151650b3"}, + {file = "aiohttp-3.11.14-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b0a200e85da5c966277a402736a96457b882360aa15416bf104ca81e6f5807b"}, + {file = "aiohttp-3.11.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d173c0ac508a2175f7c9a115a50db5fd3e35190d96fdd1a17f9cb10a6ab09aa1"}, + {file = "aiohttp-3.11.14-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:413fe39fd929329f697f41ad67936f379cba06fcd4c462b62e5b0f8061ee4a77"}, + {file = "aiohttp-3.11.14-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:65c75b14ee74e8eeff2886321e76188cbe938d18c85cff349d948430179ad02c"}, + {file = "aiohttp-3.11.14-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:321238a42ed463848f06e291c4bbfb3d15ba5a79221a82c502da3e23d7525d06"}, + {file = "aiohttp-3.11.14-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:59a05cdc636431f7ce843c7c2f04772437dd816a5289f16440b19441be6511f1"}, + {file = "aiohttp-3.11.14-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:daf20d9c3b12ae0fdf15ed92235e190f8284945563c4b8ad95b2d7a31f331cd3"}, + {file = "aiohttp-3.11.14-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:05582cb2d156ac7506e68b5eac83179faedad74522ed88f88e5861b78740dc0e"}, + {file = "aiohttp-3.11.14-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:12c5869e7ddf6b4b1f2109702b3cd7515667b437da90a5a4a50ba1354fe41881"}, + {file = "aiohttp-3.11.14-cp311-cp311-win32.whl", hash = "sha256:92868f6512714efd4a6d6cb2bfc4903b997b36b97baea85f744229f18d12755e"}, + {file = "aiohttp-3.11.14-cp311-cp311-win_amd64.whl", hash = "sha256:bccd2cb7aa5a3bfada72681bdb91637094d81639e116eac368f8b3874620a654"}, + {file = "aiohttp-3.11.14-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:70ab0f61c1a73d3e0342cedd9a7321425c27a7067bebeeacd509f96695b875fc"}, + {file = "aiohttp-3.11.14-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:602d4db80daf4497de93cb1ce00b8fc79969c0a7cf5b67bec96fa939268d806a"}, + {file = "aiohttp-3.11.14-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3a8a0d127c10b8d89e69bbd3430da0f73946d839e65fec00ae48ca7916a31948"}, + {file = "aiohttp-3.11.14-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca9f835cdfedcb3f5947304e85b8ca3ace31eef6346d8027a97f4de5fb687534"}, + {file = "aiohttp-3.11.14-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8aa5c68e1e68fff7cd3142288101deb4316b51f03d50c92de6ea5ce646e6c71f"}, + {file = "aiohttp-3.11.14-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b512f1de1c688f88dbe1b8bb1283f7fbeb7a2b2b26e743bb2193cbadfa6f307"}, + {file = "aiohttp-3.11.14-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc9253069158d57e27d47a8453d8a2c5a370dc461374111b5184cf2f147a3cc3"}, + {file = "aiohttp-3.11.14-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b2501f1b981e70932b4a552fc9b3c942991c7ae429ea117e8fba57718cdeed0"}, + {file = "aiohttp-3.11.14-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:28a3d083819741592685762d51d789e6155411277050d08066537c5edc4066e6"}, + {file = "aiohttp-3.11.14-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0df3788187559c262922846087e36228b75987f3ae31dd0a1e5ee1034090d42f"}, + {file = "aiohttp-3.11.14-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9e73fa341d8b308bb799cf0ab6f55fc0461d27a9fa3e4582755a3d81a6af8c09"}, + {file = "aiohttp-3.11.14-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:51ba80d473eb780a329d73ac8afa44aa71dfb521693ccea1dea8b9b5c4df45ce"}, + {file = "aiohttp-3.11.14-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:8d1dd75aa4d855c7debaf1ef830ff2dfcc33f893c7db0af2423ee761ebffd22b"}, + {file = "aiohttp-3.11.14-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41cf0cefd9e7b5c646c2ef529c8335e7eafd326f444cc1cdb0c47b6bc836f9be"}, + {file = "aiohttp-3.11.14-cp312-cp312-win32.whl", hash = "sha256:948abc8952aff63de7b2c83bfe3f211c727da3a33c3a5866a0e2cf1ee1aa950f"}, + {file = "aiohttp-3.11.14-cp312-cp312-win_amd64.whl", hash = "sha256:3b420d076a46f41ea48e5fcccb996f517af0d406267e31e6716f480a3d50d65c"}, + {file = "aiohttp-3.11.14-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d14e274828561db91e4178f0057a915f3af1757b94c2ca283cb34cbb6e00b50"}, + {file = "aiohttp-3.11.14-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f30fc72daf85486cdcdfc3f5e0aea9255493ef499e31582b34abadbfaafb0965"}, + {file = "aiohttp-3.11.14-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4edcbe34e6dba0136e4cabf7568f5a434d89cc9de5d5155371acda275353d228"}, + {file = "aiohttp-3.11.14-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a7169ded15505f55a87f8f0812c94c9412623c744227b9e51083a72a48b68a5"}, + {file = "aiohttp-3.11.14-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad1f2fb9fe9b585ea4b436d6e998e71b50d2b087b694ab277b30e060c434e5db"}, + {file = "aiohttp-3.11.14-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:20412c7cc3720e47a47e63c0005f78c0c2370020f9f4770d7fc0075f397a9fb0"}, + {file = "aiohttp-3.11.14-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dd9766da617855f7e85f27d2bf9a565ace04ba7c387323cd3e651ac4329db91"}, + {file = "aiohttp-3.11.14-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:599b66582f7276ebefbaa38adf37585e636b6a7a73382eb412f7bc0fc55fb73d"}, + {file = "aiohttp-3.11.14-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b41693b7388324b80f9acfabd479bd1c84f0bc7e8f17bab4ecd9675e9ff9c734"}, + {file = "aiohttp-3.11.14-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:86135c32d06927339c8c5e64f96e4eee8825d928374b9b71a3c42379d7437058"}, + {file = "aiohttp-3.11.14-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:04eb541ce1e03edc1e3be1917a0f45ac703e913c21a940111df73a2c2db11d73"}, + {file = "aiohttp-3.11.14-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dc311634f6f28661a76cbc1c28ecf3b3a70a8edd67b69288ab7ca91058eb5a33"}, + {file = "aiohttp-3.11.14-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:69bb252bfdca385ccabfd55f4cd740d421dd8c8ad438ded9637d81c228d0da49"}, + {file = "aiohttp-3.11.14-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2b86efe23684b58a88e530c4ab5b20145f102916bbb2d82942cafec7bd36a647"}, + {file = "aiohttp-3.11.14-cp313-cp313-win32.whl", hash = "sha256:b9c60d1de973ca94af02053d9b5111c4fbf97158e139b14f1be68337be267be6"}, + {file = "aiohttp-3.11.14-cp313-cp313-win_amd64.whl", hash = "sha256:0a29be28e60e5610d2437b5b2fed61d6f3dcde898b57fb048aa5079271e7f6f3"}, + {file = "aiohttp-3.11.14-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:14fc03508359334edc76d35b2821832f092c8f092e4b356e74e38419dfe7b6de"}, + {file = "aiohttp-3.11.14-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:92007c89a8cb7be35befa2732b0b32bf3a394c1b22ef2dff0ef12537d98a7bda"}, + {file = "aiohttp-3.11.14-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6d3986112e34eaa36e280dc8286b9dd4cc1a5bcf328a7f147453e188f6fe148f"}, + {file = "aiohttp-3.11.14-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:749f1eb10e51dbbcdba9df2ef457ec060554842eea4d23874a3e26495f9e87b1"}, + {file = "aiohttp-3.11.14-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:781c8bd423dcc4641298c8c5a2a125c8b1c31e11f828e8d35c1d3a722af4c15a"}, + {file = "aiohttp-3.11.14-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:997b57e38aa7dc6caab843c5e042ab557bc83a2f91b7bd302e3c3aebbb9042a1"}, + {file = "aiohttp-3.11.14-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a8b0321e40a833e381d127be993b7349d1564b756910b28b5f6588a159afef3"}, + {file = "aiohttp-3.11.14-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8778620396e554b758b59773ab29c03b55047841d8894c5e335f12bfc45ebd28"}, + {file = "aiohttp-3.11.14-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e906da0f2bcbf9b26cc2b144929e88cb3bf943dd1942b4e5af066056875c7618"}, + {file = "aiohttp-3.11.14-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:87f0e003fb4dd5810c7fbf47a1239eaa34cd929ef160e0a54c570883125c4831"}, + {file = "aiohttp-3.11.14-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:7f2dadece8b85596ac3ab1ec04b00694bdd62abc31e5618f524648d18d9dd7fa"}, + {file = "aiohttp-3.11.14-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:fe846f0a98aa9913c2852b630cd39b4098f296e0907dd05f6c7b30d911afa4c3"}, + {file = "aiohttp-3.11.14-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ced66c5c6ad5bcaf9be54560398654779ec1c3695f1a9cf0ae5e3606694a000a"}, + {file = "aiohttp-3.11.14-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a40087b82f83bd671cbeb5f582c233d196e9653220404a798798bfc0ee189fff"}, + {file = "aiohttp-3.11.14-cp39-cp39-win32.whl", hash = "sha256:95d7787f2bcbf7cb46823036a8d64ccfbc2ffc7d52016b4044d901abceeba3db"}, + {file = "aiohttp-3.11.14-cp39-cp39-win_amd64.whl", hash = "sha256:22a8107896877212130c58f74e64b77f7007cb03cea8698be317272643602d45"}, + {file = "aiohttp-3.11.14.tar.gz", hash = "sha256:d6edc538c7480fa0a3b2bdd705f8010062d74700198da55d16498e1b49549b9c"}, +] + +[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 = "aioitertools" +version = "0.12.0" +description = "itertools and builtins for AsyncIO and mixed iterables" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "aioitertools-0.12.0-py3-none-any.whl", hash = "sha256:fc1f5fac3d737354de8831cbba3eb04f79dd649d8f3afb4c5b114925e662a796"}, + {file = "aioitertools-0.12.0.tar.gz", hash = "sha256:c2a9055b4fbb7705f561b9d86053e8af5d10cc845d22c32008c43490b2d8dd6b"}, +] + +[package.extras] +dev = ["attribution (==1.8.0)", "black (==24.8.0)", "build (>=1.2)", "coverage (==7.6.1)", "flake8 (==7.1.1)", "flit (==3.9.0)", "mypy (==1.11.2)", "ufmt (==2.7.1)", "usort (==1.0.8.post1)"] +docs = ["sphinx (==8.0.2)", "sphinx-mdinclude (==0.6.2)"] + +[[package]] +name = "aiosignal" +version = "1.3.2" +description = "aiosignal: a list of registered asynchronous callbacks" +optional = false +python-versions = ">=3.9" +groups = ["main"] +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" @@ -7,7 +179,6 @@ description = "Reusable constraint types to use with typing.Annotated" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" 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"}, @@ -20,7 +191,6 @@ description = "High level compatibility layer for multiple asynchronous event lo optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "anyio-4.8.0-py3-none-any.whl", hash = "sha256:b5011f270ab5eb0abf13385f851315585cc37ef330dd88e27ec3d34d651fd47a"}, {file = "anyio-4.8.0.tar.gz", hash = "sha256:1d9fe889df5212298c0c0723fa20479d1b94883a2df44bd3897aa91083316f7a"}, @@ -34,9 +204,62 @@ typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"] +test = ["anyio[trio]", "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"] +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"] +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 = "botocore" +version = "1.37.1" +description = "Low-level, data-driven core of boto 3." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "botocore-1.37.1-py3-none-any.whl", hash = "sha256:c1db1bfc5d8c6b3b6d1ca6794f605294b4264e82a7e727b88e0fef9c2b9fbb9c"}, + {file = "botocore-1.37.1.tar.gz", hash = "sha256:b194db8fb2a0ffba53568c364ae26166e7eec0445496b2ac86a6e142f3dd982f"}, +] + +[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 = "certifi" version = "2025.1.31" @@ -44,7 +267,6 @@ description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, @@ -57,7 +279,6 @@ description = "Validate configuration and produce human readable error messages. optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, @@ -70,7 +291,6 @@ description = "The Real First Universal Charset Detector. Open, modern and activ optional = false python-versions = ">=3.7" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, @@ -172,8 +392,8 @@ 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 = "python_version <= \"3.11\" and sys_platform == \"win32\" or python_version >= \"3.12\" and sys_platform == \"win32\"" +groups = ["main", "dev"] +markers = "sys_platform == \"win32\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -186,7 +406,6 @@ description = "Random name and slug generator" optional = false python-versions = "*" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "coolname-2.2.0-py2.py3-none-any.whl", hash = "sha256:4d1563186cfaf71b394d5df4c744f8c41303b6846413645e31d31915cdeb13e8"}, {file = "coolname-2.2.0.tar.gz", hash = "sha256:6c5d5731759104479e7ca195a9b64f7900ac5bead40183c09323c7d0be9e75c7"}, @@ -199,7 +418,6 @@ description = "Python @deprecated decorator to deprecate old python classes, fun optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "Deprecated-1.2.18-py2.py3-none-any.whl", hash = "sha256:bd5011788200372a32418f888e326a09ff80d0214bd961147cfed01b5c018eec"}, {file = "deprecated-1.2.18.tar.gz", hash = "sha256:422b6f6d859da6f2ef57857761bfb392480502a64c3028ca9bbe86085d72115d"}, @@ -209,7 +427,7 @@ files = [ wrapt = ">=1.10,<2" [package.extras] -dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "setuptools", "tox"] +dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "setuptools ; python_version >= \"3.12\"", "tox"] [[package]] name = "distlib" @@ -218,7 +436,6 @@ description = "Distribution utilities" optional = false python-versions = "*" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87"}, {file = "distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403"}, @@ -247,14 +464,13 @@ description = "Get the currently executing AST node of a frame, and other inform optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "executing-2.2.0-py2.py3-none-any.whl", hash = "sha256:11387150cad388d62750327a53d3339fad4888b39a6fe233c3afbb54ecffd3aa"}, {file = "executing-2.2.0.tar.gz", hash = "sha256:5d108c028108fe2551d1a7b2e8b713341e2cb4fc0aa7dcf966fa4327a5226755"}, ] [package.extras] -tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"] +tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich ; python_version >= \"3.11\""] [[package]] name = "fast-depends" @@ -263,7 +479,6 @@ description = "FastDepends - extracted and cleared from HTTP domain logic FastAP optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "fast_depends-2.4.12-py3-none-any.whl", hash = "sha256:9e5d110ddc962329e46c9b35e5fe65655984247a13ee3ca5a33186db7d2d75c2"}, {file = "fast_depends-2.4.12.tar.gz", hash = "sha256:9393e6de827f7afa0141e54fa9553b737396aaf06bd0040e159d1f790487b16d"}, @@ -280,7 +495,6 @@ description = "A platform independent file lock." optional = false python-versions = ">=3.9" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "filelock-3.17.0-py3-none-any.whl", hash = "sha256:533dc2f7ba78dc2f0f531fc6c4940addf7b70a481e269a5a3b93be94ffbe8338"}, {file = "filelock-3.17.0.tar.gz", hash = "sha256:ee4e77401ef576ebb38cd7f13b9b28893194acc20a8e68e18730ba9c0e54660e"}, @@ -289,7 +503,152 @@ files = [ [package.extras] 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)"] +typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""] + +[[package]] +name = "frozenlist" +version = "1.5.0" +description = "A list-like structure which implements collections.abc.MutableSequence" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5b6a66c18b5b9dd261ca98dffcb826a525334b2f29e7caa54e182255c5f6a65a"}, + {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d1b3eb7b05ea246510b43a7e53ed1653e55c2121019a97e60cad7efb881a97bb"}, + {file = "frozenlist-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:15538c0cbf0e4fa11d1e3a71f823524b0c46299aed6e10ebb4c2089abd8c3bec"}, + {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e79225373c317ff1e35f210dd5f1344ff31066ba8067c307ab60254cd3a78ad5"}, + {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9272fa73ca71266702c4c3e2d4a28553ea03418e591e377a03b8e3659d94fa76"}, + {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:498524025a5b8ba81695761d78c8dd7382ac0b052f34e66939c42df860b8ff17"}, + {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92b5278ed9d50fe610185ecd23c55d8b307d75ca18e94c0e7de328089ac5dcba"}, + {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f3c8c1dacd037df16e85227bac13cca58c30da836c6f936ba1df0c05d046d8d"}, + {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f2ac49a9bedb996086057b75bf93538240538c6d9b38e57c82d51f75a73409d2"}, + {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e66cc454f97053b79c2ab09c17fbe3c825ea6b4de20baf1be28919460dd7877f"}, + {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:5a3ba5f9a0dfed20337d3e966dc359784c9f96503674c2faf015f7fe8e96798c"}, + {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6321899477db90bdeb9299ac3627a6a53c7399c8cd58d25da094007402b039ab"}, + {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76e4753701248476e6286f2ef492af900ea67d9706a0155335a40ea21bf3b2f5"}, + {file = "frozenlist-1.5.0-cp310-cp310-win32.whl", hash = "sha256:977701c081c0241d0955c9586ffdd9ce44f7a7795df39b9151cd9a6fd0ce4cfb"}, + {file = "frozenlist-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:189f03b53e64144f90990d29a27ec4f7997d91ed3d01b51fa39d2dbe77540fd4"}, + {file = "frozenlist-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fd74520371c3c4175142d02a976aee0b4cb4a7cc912a60586ffd8d5929979b30"}, + {file = "frozenlist-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2f3f7a0fbc219fb4455264cae4d9f01ad41ae6ee8524500f381de64ffaa077d5"}, + {file = "frozenlist-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f47c9c9028f55a04ac254346e92977bf0f166c483c74b4232bee19a6697e4778"}, + {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0996c66760924da6e88922756d99b47512a71cfd45215f3570bf1e0b694c206a"}, + {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2fe128eb4edeabe11896cb6af88fca5346059f6c8d807e3b910069f39157869"}, + {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a8ea951bbb6cacd492e3948b8da8c502a3f814f5d20935aae74b5df2b19cf3d"}, + {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de537c11e4aa01d37db0d403b57bd6f0546e71a82347a97c6a9f0dcc532b3a45"}, + {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c2623347b933fcb9095841f1cc5d4ff0b278addd743e0e966cb3d460278840d"}, + {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cee6798eaf8b1416ef6909b06f7dc04b60755206bddc599f52232606e18179d3"}, + {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f5f9da7f5dbc00a604fe74aa02ae7c98bcede8a3b8b9666f9f86fc13993bc71a"}, + {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:90646abbc7a5d5c7c19461d2e3eeb76eb0b204919e6ece342feb6032c9325ae9"}, + {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:bdac3c7d9b705d253b2ce370fde941836a5f8b3c5c2b8fd70940a3ea3af7f4f2"}, + {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03d33c2ddbc1816237a67f66336616416e2bbb6beb306e5f890f2eb22b959cdf"}, + {file = "frozenlist-1.5.0-cp311-cp311-win32.whl", hash = "sha256:237f6b23ee0f44066219dae14c70ae38a63f0440ce6750f868ee08775073f942"}, + {file = "frozenlist-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:0cc974cc93d32c42e7b0f6cf242a6bd941c57c61b618e78b6c0a96cb72788c1d"}, + {file = "frozenlist-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:31115ba75889723431aa9a4e77d5f398f5cf976eea3bdf61749731f62d4a4a21"}, + {file = "frozenlist-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7437601c4d89d070eac8323f121fcf25f88674627505334654fd027b091db09d"}, + {file = "frozenlist-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7948140d9f8ece1745be806f2bfdf390127cf1a763b925c4a805c603df5e697e"}, + {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feeb64bc9bcc6b45c6311c9e9b99406660a9c05ca8a5b30d14a78555088b0b3a"}, + {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:683173d371daad49cffb8309779e886e59c2f369430ad28fe715f66d08d4ab1a"}, + {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d57d8f702221405a9d9b40f9da8ac2e4a1a8b5285aac6100f3393675f0a85ee"}, + {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c72000fbcc35b129cb09956836c7d7abf78ab5416595e4857d1cae8d6251a6"}, + {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000a77d6034fbad9b6bb880f7ec073027908f1b40254b5d6f26210d2dab1240e"}, + {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d7f5a50342475962eb18b740f3beecc685a15b52c91f7d975257e13e029eca9"}, + {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:87f724d055eb4785d9be84e9ebf0f24e392ddfad00b3fe036e43f489fafc9039"}, + {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6e9080bb2fb195a046e5177f10d9d82b8a204c0736a97a153c2466127de87784"}, + {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b93d7aaa36c966fa42efcaf716e6b3900438632a626fb09c049f6a2f09fc631"}, + {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:52ef692a4bc60a6dd57f507429636c2af8b6046db8b31b18dac02cbc8f507f7f"}, + {file = "frozenlist-1.5.0-cp312-cp312-win32.whl", hash = "sha256:29d94c256679247b33a3dc96cce0f93cbc69c23bf75ff715919332fdbb6a32b8"}, + {file = "frozenlist-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:8969190d709e7c48ea386db202d708eb94bdb29207a1f269bab1196ce0dcca1f"}, + {file = "frozenlist-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a1a048f9215c90973402e26c01d1cff8a209e1f1b53f72b95c13db61b00f953"}, + {file = "frozenlist-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dd47a5181ce5fcb463b5d9e17ecfdb02b678cca31280639255ce9d0e5aa67af0"}, + {file = "frozenlist-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1431d60b36d15cda188ea222033eec8e0eab488f39a272461f2e6d9e1a8e63c2"}, + {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6482a5851f5d72767fbd0e507e80737f9c8646ae7fd303def99bfe813f76cf7f"}, + {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44c49271a937625619e862baacbd037a7ef86dd1ee215afc298a417ff3270608"}, + {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12f78f98c2f1c2429d42e6a485f433722b0061d5c0b0139efa64f396efb5886b"}, + {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce3aa154c452d2467487765e3adc730a8c153af77ad84096bc19ce19a2400840"}, + {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b7dc0c4338e6b8b091e8faf0db3168a37101943e687f373dce00959583f7439"}, + {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45e0896250900b5aa25180f9aec243e84e92ac84bd4a74d9ad4138ef3f5c97de"}, + {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:561eb1c9579d495fddb6da8959fd2a1fca2c6d060d4113f5844b433fc02f2641"}, + {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:df6e2f325bfee1f49f81aaac97d2aa757c7646534a06f8f577ce184afe2f0a9e"}, + {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:140228863501b44b809fb39ec56b5d4071f4d0aa6d216c19cbb08b8c5a7eadb9"}, + {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7707a25d6a77f5d27ea7dc7d1fc608aa0a478193823f88511ef5e6b8a48f9d03"}, + {file = "frozenlist-1.5.0-cp313-cp313-win32.whl", hash = "sha256:31a9ac2b38ab9b5a8933b693db4939764ad3f299fcaa931a3e605bc3460e693c"}, + {file = "frozenlist-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:11aabdd62b8b9c4b84081a3c246506d1cddd2dd93ff0ad53ede5defec7886b28"}, + {file = "frozenlist-1.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:dd94994fc91a6177bfaafd7d9fd951bc8689b0a98168aa26b5f543868548d3ca"}, + {file = "frozenlist-1.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0da8bbec082bf6bf18345b180958775363588678f64998c2b7609e34719b10"}, + {file = "frozenlist-1.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:73f2e31ea8dd7df61a359b731716018c2be196e5bb3b74ddba107f694fbd7604"}, + {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:828afae9f17e6de596825cf4228ff28fbdf6065974e5ac1410cecc22f699d2b3"}, + {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1577515d35ed5649d52ab4319db757bb881ce3b2b796d7283e6634d99ace307"}, + {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2150cc6305a2c2ab33299453e2968611dacb970d2283a14955923062c8d00b10"}, + {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a72b7a6e3cd2725eff67cd64c8f13335ee18fc3c7befc05aed043d24c7b9ccb9"}, + {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c16d2fa63e0800723139137d667e1056bee1a1cf7965153d2d104b62855e9b99"}, + {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:17dcc32fc7bda7ce5875435003220a457bcfa34ab7924a49a1c19f55b6ee185c"}, + {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:97160e245ea33d8609cd2b8fd997c850b56db147a304a262abc2b3be021a9171"}, + {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f1e6540b7fa044eee0bb5111ada694cf3dc15f2b0347ca125ee9ca984d5e9e6e"}, + {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:91d6c171862df0a6c61479d9724f22efb6109111017c87567cfeb7b5d1449fdf"}, + {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c1fac3e2ace2eb1052e9f7c7db480818371134410e1f5c55d65e8f3ac6d1407e"}, + {file = "frozenlist-1.5.0-cp38-cp38-win32.whl", hash = "sha256:b97f7b575ab4a8af9b7bc1d2ef7f29d3afee2226bd03ca3875c16451ad5a7723"}, + {file = "frozenlist-1.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:374ca2dabdccad8e2a76d40b1d037f5bd16824933bf7bcea3e59c891fd4a0923"}, + {file = "frozenlist-1.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9bbcdfaf4af7ce002694a4e10a0159d5a8d20056a12b05b45cea944a4953f972"}, + {file = "frozenlist-1.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1893f948bf6681733aaccf36c5232c231e3b5166d607c5fa77773611df6dc336"}, + {file = "frozenlist-1.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2b5e23253bb709ef57a8e95e6ae48daa9ac5f265637529e4ce6b003a37b2621f"}, + {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f253985bb515ecd89629db13cb58d702035ecd8cfbca7d7a7e29a0e6d39af5f"}, + {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04a5c6babd5e8fb7d3c871dc8b321166b80e41b637c31a995ed844a6139942b6"}, + {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9fe0f1c29ba24ba6ff6abf688cb0b7cf1efab6b6aa6adc55441773c252f7411"}, + {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:226d72559fa19babe2ccd920273e767c96a49b9d3d38badd7c91a0fdeda8ea08"}, + {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15b731db116ab3aedec558573c1a5eec78822b32292fe4f2f0345b7f697745c2"}, + {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:366d8f93e3edfe5a918c874702f78faac300209a4d5bf38352b2c1bdc07a766d"}, + {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1b96af8c582b94d381a1c1f51ffaedeb77c821c690ea5f01da3d70a487dd0a9b"}, + {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c03eff4a41bd4e38415cbed054bbaff4a075b093e2394b6915dca34a40d1e38b"}, + {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:50cf5e7ee9b98f22bdecbabf3800ae78ddcc26e4a435515fc72d97903e8488e0"}, + {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1e76bfbc72353269c44e0bc2cfe171900fbf7f722ad74c9a7b638052afe6a00c"}, + {file = "frozenlist-1.5.0-cp39-cp39-win32.whl", hash = "sha256:666534d15ba8f0fda3f53969117383d5dc021266b3c1a42c9ec4855e4b58b9d3"}, + {file = "frozenlist-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:5c28f4b5dbef8a0d8aad0d4de24d1e9e981728628afaf4ea0792f5d0939372f0"}, + {file = "frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3"}, + {file = "frozenlist-1.5.0.tar.gz", hash = "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817"}, +] + +[[package]] +name = "fsspec" +version = "2025.3.0" +description = "File-system specification" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "fsspec-2025.3.0-py3-none-any.whl", hash = "sha256:efb87af3efa9103f94ca91a7f8cb7a4df91af9f74fc106c9c7ea0efd7277c1b3"}, + {file = "fsspec-2025.3.0.tar.gz", hash = "sha256:a935fd1ea872591f2b5148907d103488fc523295e6c64b835cfad8c3eca44972"}, +] + +[package.dependencies] +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[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" @@ -298,7 +657,6 @@ description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "googleapis_common_protos-1.67.0-py2.py3-none-any.whl", hash = "sha256:579de760800d13616f51cf8be00c876f00a9f146d3e6510e19d1f4111758b741"}, {file = "googleapis_common_protos-1.67.0.tar.gz", hash = "sha256:21398025365f138be356d5923e9168737d94d46a72aefee4a6110a1f23463c86"}, @@ -317,7 +675,6 @@ description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" optional = false python-versions = ">=3.7" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, @@ -330,7 +687,6 @@ description = "A minimal low-level HTTP client." optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd"}, {file = "httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c"}, @@ -353,7 +709,6 @@ description = "The next generation HTTP client." optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0"}, {file = "httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2"}, @@ -367,7 +722,7 @@ idna = "*" sniffio = "*" [package.extras] -brotli = ["brotli", "brotlicffi"] +brotli = ["brotli ; platform_python_implementation == \"CPython\"", "brotlicffi ; platform_python_implementation != \"CPython\""] cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] http2 = ["h2 (>=3,<5)"] socks = ["socksio (==1.*)"] @@ -380,7 +735,6 @@ description = "File identification library for Python" optional = false python-versions = ">=3.9" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "identify-2.6.7-py2.py3-none-any.whl", hash = "sha256:155931cb617a401807b09ecec6635d6c692d180090a1cedca8ef7d58ba5b6aa0"}, {file = "identify-2.6.7.tar.gz", hash = "sha256:3fa266b42eba321ee0b2bb0936a6a6b9e36a1351cbb69055b3082f4193035684"}, @@ -396,7 +750,6 @@ description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -412,7 +765,6 @@ description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"}, {file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"}, @@ -422,12 +774,12 @@ files = [ zipp = ">=3.20" [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] perf = ["ipython"] -test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +test = ["flufl.flake8", "importlib-resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] type = ["pytest-mypy"] [[package]] @@ -437,12 +789,23 @@ description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] +[[package]] +name = "jmespath" +version = "1.0.1" +description = "JSON Matching Expressions" +optional = false +python-versions = ">=3.7" +groups = ["main"] +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 = "logfire" version = "3.5.3" @@ -450,7 +813,6 @@ description = "The best Python observability tool! 🪵🔥" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "logfire-3.5.3-py3-none-any.whl", hash = "sha256:d06186aaf08fc9d144a71548ba3e3a3fcbe4dddd84241243957167b870c4a731"}, {file = "logfire-3.5.3.tar.gz", hash = "sha256:cb4863cde51a4784fcdf78ac178c2a6f739b1c6c6061bb8662edf7eed7b643ac"}, @@ -488,6 +850,25 @@ starlette = ["opentelemetry-instrumentation-starlette (>=0.42b0)"] system-metrics = ["opentelemetry-instrumentation-system-metrics (>=0.42b0)"] wsgi = ["opentelemetry-instrumentation-wsgi (>=0.42b0)"] +[[package]] +name = "loguru" +version = "0.7.3" +description = "Python logging made (stupidly) simple" +optional = false +python-versions = "<4.0,>=3.5" +groups = ["main"] +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" @@ -495,7 +876,6 @@ description = "Python port of markdown-it. Markdown parsing, done right!" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" 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"}, @@ -521,12 +901,116 @@ description = "Markdown URL utilities" optional = false python-versions = ">=3.7" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" 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 = "multidict" +version = "6.2.0" +description = "multidict implementation" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "multidict-6.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b9f6392d98c0bd70676ae41474e2eecf4c7150cb419237a41f8f96043fcb81d1"}, + {file = "multidict-6.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3501621d5e86f1a88521ea65d5cad0a0834c77b26f193747615b7c911e5422d2"}, + {file = "multidict-6.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:32ed748ff9ac682eae7859790d3044b50e3076c7d80e17a44239683769ff485e"}, + {file = "multidict-6.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc826b9a8176e686b67aa60fd6c6a7047b0461cae5591ea1dc73d28f72332a8a"}, + {file = "multidict-6.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:214207dcc7a6221d9942f23797fe89144128a71c03632bf713d918db99bd36de"}, + {file = "multidict-6.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:05fefbc3cddc4e36da209a5e49f1094bbece9a581faa7f3589201fd95df40e5d"}, + {file = "multidict-6.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e851e6363d0dbe515d8de81fd544a2c956fdec6f8a049739562286727d4a00c3"}, + {file = "multidict-6.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32c9b4878f48be3e75808ea7e499d6223b1eea6d54c487a66bc10a1871e3dc6a"}, + {file = "multidict-6.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7243c5a6523c5cfeca76e063efa5f6a656d1d74c8b1fc64b2cd1e84e507f7e2a"}, + {file = "multidict-6.2.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0e5a644e50ef9fb87878d4d57907f03a12410d2aa3b93b3acdf90a741df52c49"}, + {file = "multidict-6.2.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0dc25a3293c50744796e87048de5e68996104d86d940bb24bc3ec31df281b191"}, + {file = "multidict-6.2.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:a49994481b99cd7dedde07f2e7e93b1d86c01c0fca1c32aded18f10695ae17eb"}, + {file = "multidict-6.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:641cf2e3447c9ecff2f7aa6e9eee9eaa286ea65d57b014543a4911ff2799d08a"}, + {file = "multidict-6.2.0-cp310-cp310-win32.whl", hash = "sha256:0c383d28857f66f5aebe3e91d6cf498da73af75fbd51cedbe1adfb85e90c0460"}, + {file = "multidict-6.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:a33273a541f1e1a8219b2a4ed2de355848ecc0254264915b9290c8d2de1c74e1"}, + {file = "multidict-6.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:84e87a7d75fa36839a3a432286d719975362d230c70ebfa0948549cc38bd5b46"}, + {file = "multidict-6.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8de4d42dffd5ced9117af2ce66ba8722402541a3aa98ffdf78dde92badb68932"}, + {file = "multidict-6.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e7d91a230c7f8af86c904a5a992b8c064b66330544693fd6759c3d6162382ecf"}, + {file = "multidict-6.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f6cad071960ba1914fa231677d21b1b4a3acdcce463cee41ea30bc82e6040cf"}, + {file = "multidict-6.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f74f2fc51555f4b037ef278efc29a870d327053aba5cb7d86ae572426c7cccc"}, + {file = "multidict-6.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:14ed9ed1bfedd72a877807c71113deac292bf485159a29025dfdc524c326f3e1"}, + {file = "multidict-6.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ac3fcf9a2d369bd075b2c2965544036a27ccd277fc3c04f708338cc57533081"}, + {file = "multidict-6.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fc6af8e39f7496047c7876314f4317736eac82bf85b54c7c76cf1a6f8e35d98"}, + {file = "multidict-6.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5f8cb1329f42fadfb40d6211e5ff568d71ab49be36e759345f91c69d1033d633"}, + {file = "multidict-6.2.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5389445f0173c197f4a3613713b5fb3f3879df1ded2a1a2e4bc4b5b9c5441b7e"}, + {file = "multidict-6.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:94a7bb972178a8bfc4055db80c51efd24baefaced5e51c59b0d598a004e8305d"}, + {file = "multidict-6.2.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:da51d8928ad8b4244926fe862ba1795f0b6e68ed8c42cd2f822d435db9c2a8f4"}, + {file = "multidict-6.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:063be88bd684782a0715641de853e1e58a2f25b76388538bd62d974777ce9bc2"}, + {file = "multidict-6.2.0-cp311-cp311-win32.whl", hash = "sha256:52b05e21ff05729fbea9bc20b3a791c3c11da61649ff64cce8257c82a020466d"}, + {file = "multidict-6.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:1e2a2193d3aa5cbf5758f6d5680a52aa848e0cf611da324f71e5e48a9695cc86"}, + {file = "multidict-6.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:437c33561edb6eb504b5a30203daf81d4a9b727e167e78b0854d9a4e18e8950b"}, + {file = "multidict-6.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9f49585f4abadd2283034fc605961f40c638635bc60f5162276fec075f2e37a4"}, + {file = "multidict-6.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5dd7106d064d05896ce28c97da3f46caa442fe5a43bc26dfb258e90853b39b44"}, + {file = "multidict-6.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e25b11a0417475f093d0f0809a149aff3943c2c56da50fdf2c3c88d57fe3dfbd"}, + {file = "multidict-6.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac380cacdd3b183338ba63a144a34e9044520a6fb30c58aa14077157a033c13e"}, + {file = "multidict-6.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:61d5541f27533f803a941d3a3f8a3d10ed48c12cf918f557efcbf3cd04ef265c"}, + {file = "multidict-6.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:facaf11f21f3a4c51b62931feb13310e6fe3475f85e20d9c9fdce0d2ea561b87"}, + {file = "multidict-6.2.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:095a2eabe8c43041d3e6c2cb8287a257b5f1801c2d6ebd1dd877424f1e89cf29"}, + {file = "multidict-6.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a0cc398350ef31167e03f3ca7c19313d4e40a662adcb98a88755e4e861170bdd"}, + {file = "multidict-6.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7c611345bbe7cb44aabb877cb94b63e86f2d0db03e382667dbd037866d44b4f8"}, + {file = "multidict-6.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8cd1a0644ccaf27e9d2f6d9c9474faabee21f0578fe85225cc5af9a61e1653df"}, + {file = "multidict-6.2.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:89b3857652183b8206a891168af47bac10b970d275bba1f6ee46565a758c078d"}, + {file = "multidict-6.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:125dd82b40f8c06d08d87b3510beaccb88afac94e9ed4a6f6c71362dc7dbb04b"}, + {file = "multidict-6.2.0-cp312-cp312-win32.whl", hash = "sha256:76b34c12b013d813e6cb325e6bd4f9c984db27758b16085926bbe7ceeaace626"}, + {file = "multidict-6.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:0b183a959fb88ad1be201de2c4bdf52fa8e46e6c185d76201286a97b6f5ee65c"}, + {file = "multidict-6.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5c5e7d2e300d5cb3b2693b6d60d3e8c8e7dd4ebe27cd17c9cb57020cac0acb80"}, + {file = "multidict-6.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:256d431fe4583c5f1e0f2e9c4d9c22f3a04ae96009b8cfa096da3a8723db0a16"}, + {file = "multidict-6.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a3c0ff89fe40a152e77b191b83282c9664357dce3004032d42e68c514ceff27e"}, + {file = "multidict-6.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef7d48207926edbf8b16b336f779c557dd8f5a33035a85db9c4b0febb0706817"}, + {file = "multidict-6.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3c099d3899b14e1ce52262eb82a5f5cb92157bb5106bf627b618c090a0eadc"}, + {file = "multidict-6.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e16e7297f29a544f49340012d6fc08cf14de0ab361c9eb7529f6a57a30cbfda1"}, + {file = "multidict-6.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:042028348dc5a1f2be6c666437042a98a5d24cee50380f4c0902215e5ec41844"}, + {file = "multidict-6.2.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:08549895e6a799bd551cf276f6e59820aa084f0f90665c0f03dd3a50db5d3c48"}, + {file = "multidict-6.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4ccfd74957ef53fa7380aaa1c961f523d582cd5e85a620880ffabd407f8202c0"}, + {file = "multidict-6.2.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:83b78c680d4b15d33042d330c2fa31813ca3974197bddb3836a5c635a5fd013f"}, + {file = "multidict-6.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b4c153863dd6569f6511845922c53e39c8d61f6e81f228ad5443e690fca403de"}, + {file = "multidict-6.2.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:98aa8325c7f47183b45588af9c434533196e241be0a4e4ae2190b06d17675c02"}, + {file = "multidict-6.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9e658d1373c424457ddf6d55ec1db93c280b8579276bebd1f72f113072df8a5d"}, + {file = "multidict-6.2.0-cp313-cp313-win32.whl", hash = "sha256:3157126b028c074951839233647bd0e30df77ef1fedd801b48bdcad242a60f4e"}, + {file = "multidict-6.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:2e87f1926e91855ae61769ba3e3f7315120788c099677e0842e697b0bfb659f2"}, + {file = "multidict-6.2.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:2529ddbdaa424b2c6c2eb668ea684dd6b75b839d0ad4b21aad60c168269478d7"}, + {file = "multidict-6.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:13551d0e2d7201f0959725a6a769b6f7b9019a168ed96006479c9ac33fe4096b"}, + {file = "multidict-6.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d1996ee1330e245cd3aeda0887b4409e3930524c27642b046e4fae88ffa66c5e"}, + {file = "multidict-6.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c537da54ce4ff7c15e78ab1292e5799d0d43a2108e006578a57f531866f64025"}, + {file = "multidict-6.2.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f249badb360b0b4d694307ad40f811f83df4da8cef7b68e429e4eea939e49dd"}, + {file = "multidict-6.2.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48d39b1824b8d6ea7de878ef6226efbe0773f9c64333e1125e0efcfdd18a24c7"}, + {file = "multidict-6.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b99aac6bb2c37db336fa03a39b40ed4ef2818bf2dfb9441458165ebe88b793af"}, + {file = "multidict-6.2.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07bfa8bc649783e703263f783f73e27fef8cd37baaad4389816cf6a133141331"}, + {file = "multidict-6.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b2c00ad31fbc2cbac85d7d0fcf90853b2ca2e69d825a2d3f3edb842ef1544a2c"}, + {file = "multidict-6.2.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:0d57a01a2a9fa00234aace434d8c131f0ac6e0ac6ef131eda5962d7e79edfb5b"}, + {file = "multidict-6.2.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:abf5b17bc0cf626a8a497d89ac691308dbd825d2ac372aa990b1ca114e470151"}, + {file = "multidict-6.2.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:f7716f7e7138252d88607228ce40be22660d6608d20fd365d596e7ca0738e019"}, + {file = "multidict-6.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d5a36953389f35f0a4e88dc796048829a2f467c9197265504593f0e420571547"}, + {file = "multidict-6.2.0-cp313-cp313t-win32.whl", hash = "sha256:e653d36b1bf48fa78c7fcebb5fa679342e025121ace8c87ab05c1cefd33b34fc"}, + {file = "multidict-6.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ca23db5fb195b5ef4fd1f77ce26cadefdf13dba71dab14dadd29b34d457d7c44"}, + {file = "multidict-6.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b4f3d66dd0354b79761481fc15bdafaba0b9d9076f1f42cc9ce10d7fcbda205a"}, + {file = "multidict-6.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6e2a2d6749e1ff2c9c76a72c6530d5baa601205b14e441e6d98011000f47a7ac"}, + {file = "multidict-6.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cca83a629f77402cfadd58352e394d79a61c8015f1694b83ab72237ec3941f88"}, + {file = "multidict-6.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:781b5dd1db18c9e9eacc419027b0acb5073bdec9de1675c0be25ceb10e2ad133"}, + {file = "multidict-6.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cf8d370b2fea27fb300825ec3984334f7dd54a581bde6456799ba3776915a656"}, + {file = "multidict-6.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:25bb96338512e2f46f615a2bb7c6012fe92a4a5ebd353e5020836a7e33120349"}, + {file = "multidict-6.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19e2819b0b468174de25c0ceed766606a07cedeab132383f1e83b9a4e96ccb4f"}, + {file = "multidict-6.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6aed763b6a1b28c46c055692836879328f0b334a6d61572ee4113a5d0c859872"}, + {file = "multidict-6.2.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a1133414b771619aa3c3000701c11b2e4624a7f492f12f256aedde97c28331a2"}, + {file = "multidict-6.2.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:639556758c36093b35e2e368ca485dada6afc2bd6a1b1207d85ea6dfc3deab27"}, + {file = "multidict-6.2.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:163f4604e76639f728d127293d24c3e208b445b463168af3d031b92b0998bb90"}, + {file = "multidict-6.2.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2325105e16d434749e1be8022f942876a936f9bece4ec41ae244e3d7fae42aaf"}, + {file = "multidict-6.2.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e4371591e621579cb6da8401e4ea405b33ff25a755874a3567c4075ca63d56e2"}, + {file = "multidict-6.2.0-cp39-cp39-win32.whl", hash = "sha256:d1175b0e0d6037fab207f05774a176d71210ebd40b1c51f480a04b65ec5c786d"}, + {file = "multidict-6.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:ad81012b24b88aad4c70b2cbc2dad84018783221b7f923e926f4690ff8569da3"}, + {file = "multidict-6.2.0-py3-none-any.whl", hash = "sha256:5d26547423e5e71dcc562c4acdc134b900640a39abd9066d7326a7cc2324c530"}, + {file = "multidict-6.2.0.tar.gz", hash = "sha256:0085b0afb2446e57050140240a8595846ed64d1cbd26cef936bfab3192c673b8"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""} + [[package]] name = "mypy" version = "1.15.0" @@ -534,7 +1018,6 @@ description = "Optional static typing for Python" optional = false python-versions = ">=3.9" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "mypy-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:979e4e1a006511dacf628e36fadfecbcc0160a8af6ca7dad2f5025529e082c13"}, {file = "mypy-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c4bb0e1bd29f7d34efcccd71cf733580191e9a264a2202b0239da95984c5b559"}, @@ -589,7 +1072,6 @@ description = "Type system extensions for programs checked with the mypy type ch optional = false python-versions = ">=3.5" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" 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"}, @@ -602,7 +1084,6 @@ description = "Node.js virtual environment builder" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, @@ -614,8 +1095,7 @@ version = "2.2.3" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.10" -groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +groups = ["main", "dev"] files = [ {file = "numpy-2.2.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:cbc6472e01952d3d1b2772b720428f8b90e2deea8344e854df22b0618e9cce71"}, {file = "numpy-2.2.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdfe0c22692a30cd830c0755746473ae66c4a8f2e7bd508b35fb3b6a0813d787"}, @@ -681,7 +1161,6 @@ description = "OpenTelemetry Python API" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "opentelemetry_api-1.30.0-py3-none-any.whl", hash = "sha256:d5f5284890d73fdf47f843dda3210edf37a38d66f44f2b5aedc1e89ed455dc09"}, {file = "opentelemetry_api-1.30.0.tar.gz", hash = "sha256:375893400c1435bf623f7dfb3bcd44825fe6b56c34d0667c542ea8257b1a1240"}, @@ -698,7 +1177,6 @@ description = "OpenTelemetry Protobuf encoding" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "opentelemetry_exporter_otlp_proto_common-1.30.0-py3-none-any.whl", hash = "sha256:5468007c81aa9c44dc961ab2cf368a29d3475977df83b4e30aeed42aa7bc3b38"}, {file = "opentelemetry_exporter_otlp_proto_common-1.30.0.tar.gz", hash = "sha256:ddbfbf797e518411857d0ca062c957080279320d6235a279f7b64ced73c13897"}, @@ -714,7 +1192,6 @@ description = "OpenTelemetry Collector Protobuf over HTTP Exporter" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "opentelemetry_exporter_otlp_proto_http-1.30.0-py3-none-any.whl", hash = "sha256:9578e790e579931c5ffd50f1e6975cbdefb6a0a0a5dea127a6ae87df10e0a589"}, {file = "opentelemetry_exporter_otlp_proto_http-1.30.0.tar.gz", hash = "sha256:c3ae75d4181b1e34a60662a6814d0b94dd33b628bee5588a878bed92cee6abdc"}, @@ -736,7 +1213,6 @@ description = "Instrumentation Tools & Auto Instrumentation for OpenTelemetry Py optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "opentelemetry_instrumentation-0.51b0-py3-none-any.whl", hash = "sha256:c6de8bd26b75ec8b0e54dff59e198946e29de6a10ec65488c357d4b34aa5bdcf"}, {file = "opentelemetry_instrumentation-0.51b0.tar.gz", hash = "sha256:4ca266875e02f3988536982467f7ef8c32a38b8895490ddce9ad9604649424fa"}, @@ -755,7 +1231,6 @@ description = "OpenTelemetry Python Proto" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "opentelemetry_proto-1.30.0-py3-none-any.whl", hash = "sha256:c6290958ff3ddacc826ca5abbeb377a31c2334387352a259ba0df37c243adc11"}, {file = "opentelemetry_proto-1.30.0.tar.gz", hash = "sha256:afe5c9c15e8b68d7c469596e5b32e8fc085eb9febdd6fb4e20924a93a0389179"}, @@ -771,7 +1246,6 @@ description = "OpenTelemetry Python SDK" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "opentelemetry_sdk-1.30.0-py3-none-any.whl", hash = "sha256:14fe7afc090caad881addb6926cec967129bd9260c4d33ae6a217359f6b61091"}, {file = "opentelemetry_sdk-1.30.0.tar.gz", hash = "sha256:c9287a9e4a7614b9946e933a67168450b9ab35f08797eb9bc77d998fa480fa18"}, @@ -789,7 +1263,6 @@ description = "OpenTelemetry Semantic Conventions" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "opentelemetry_semantic_conventions-0.51b0-py3-none-any.whl", hash = "sha256:fdc777359418e8d06c86012c3dc92c88a6453ba662e941593adb062e48c2eeae"}, {file = "opentelemetry_semantic_conventions-0.51b0.tar.gz", hash = "sha256:3fabf47f35d1fd9aebcdca7e6802d86bd5ebc3bc3408b7e3248dde6e87a18c47"}, @@ -806,7 +1279,6 @@ description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" groups = ["main", "dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, @@ -819,7 +1291,6 @@ description = "Powerful data structures for data analysis, time series, and stat optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" 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"}, @@ -900,6 +1371,22 @@ 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 = "platformdirs" version = "4.3.6" @@ -907,7 +1394,6 @@ description = "A small Python package for determining appropriate platform-speci optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, @@ -925,7 +1411,6 @@ description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, @@ -942,7 +1427,6 @@ description = "A framework for managing and maintaining multi-language pre-commi optional = false python-versions = ">=3.9" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {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"}, @@ -955,6 +1439,114 @@ nodeenv = ">=0.11.1" pyyaml = ">=5.1" virtualenv = ">=20.10.0" +[[package]] +name = "propcache" +version = "0.3.0" +description = "Accelerated property cache" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "propcache-0.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:efa44f64c37cc30c9f05932c740a8b40ce359f51882c70883cc95feac842da4d"}, + {file = "propcache-0.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2383a17385d9800b6eb5855c2f05ee550f803878f344f58b6e194de08b96352c"}, + {file = "propcache-0.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d3e7420211f5a65a54675fd860ea04173cde60a7cc20ccfbafcccd155225f8bc"}, + {file = "propcache-0.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3302c5287e504d23bb0e64d2a921d1eb4a03fb93a0a0aa3b53de059f5a5d737d"}, + {file = "propcache-0.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7e2e068a83552ddf7a39a99488bcba05ac13454fb205c847674da0352602082f"}, + {file = "propcache-0.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d913d36bdaf368637b4f88d554fb9cb9d53d6920b9c5563846555938d5450bf"}, + {file = "propcache-0.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ee1983728964d6070ab443399c476de93d5d741f71e8f6e7880a065f878e0b9"}, + {file = "propcache-0.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:36ca5e9a21822cc1746023e88f5c0af6fce3af3b85d4520efb1ce4221bed75cc"}, + {file = "propcache-0.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9ecde3671e62eeb99e977f5221abcf40c208f69b5eb986b061ccec317c82ebd0"}, + {file = "propcache-0.3.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:d383bf5e045d7f9d239b38e6acadd7b7fdf6c0087259a84ae3475d18e9a2ae8b"}, + {file = "propcache-0.3.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:8cb625bcb5add899cb8ba7bf716ec1d3e8f7cdea9b0713fa99eadf73b6d4986f"}, + {file = "propcache-0.3.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:5fa159dcee5dba00c1def3231c249cf261185189205073bde13797e57dd7540a"}, + {file = "propcache-0.3.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:a7080b0159ce05f179cfac592cda1a82898ca9cd097dacf8ea20ae33474fbb25"}, + {file = "propcache-0.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ed7161bccab7696a473fe7ddb619c1d75963732b37da4618ba12e60899fefe4f"}, + {file = "propcache-0.3.0-cp310-cp310-win32.whl", hash = "sha256:bf0d9a171908f32d54f651648c7290397b8792f4303821c42a74e7805bfb813c"}, + {file = "propcache-0.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:42924dc0c9d73e49908e35bbdec87adedd651ea24c53c29cac103ede0ea1d340"}, + {file = "propcache-0.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9ddd49258610499aab83b4f5b61b32e11fce873586282a0e972e5ab3bcadee51"}, + {file = "propcache-0.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2578541776769b500bada3f8a4eeaf944530516b6e90c089aa368266ed70c49e"}, + {file = "propcache-0.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8074c5dd61c8a3e915fa8fc04754fa55cfa5978200d2daa1e2d4294c1f136aa"}, + {file = "propcache-0.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b58229a844931bca61b3a20efd2be2a2acb4ad1622fc026504309a6883686fbf"}, + {file = "propcache-0.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e45377d5d6fefe1677da2a2c07b024a6dac782088e37c0b1efea4cfe2b1be19b"}, + {file = "propcache-0.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ec5060592d83454e8063e487696ac3783cc48c9a329498bafae0d972bc7816c9"}, + {file = "propcache-0.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15010f29fbed80e711db272909a074dc79858c6d28e2915704cfc487a8ac89c6"}, + {file = "propcache-0.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a254537b9b696ede293bfdbc0a65200e8e4507bc9f37831e2a0318a9b333c85c"}, + {file = "propcache-0.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2b975528998de037dfbc10144b8aed9b8dd5a99ec547f14d1cb7c5665a43f075"}, + {file = "propcache-0.3.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:19d36bb351ad5554ff20f2ae75f88ce205b0748c38b146c75628577020351e3c"}, + {file = "propcache-0.3.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6032231d4a5abd67c7f71168fd64a47b6b451fbcb91c8397c2f7610e67683810"}, + {file = "propcache-0.3.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6985a593417cdbc94c7f9c3403747335e450c1599da1647a5af76539672464d3"}, + {file = "propcache-0.3.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:6a1948df1bb1d56b5e7b0553c0fa04fd0e320997ae99689488201f19fa90d2e7"}, + {file = "propcache-0.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8319293e85feadbbfe2150a5659dbc2ebc4afdeaf7d98936fb9a2f2ba0d4c35c"}, + {file = "propcache-0.3.0-cp311-cp311-win32.whl", hash = "sha256:63f26258a163c34542c24808f03d734b338da66ba91f410a703e505c8485791d"}, + {file = "propcache-0.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:cacea77ef7a2195f04f9279297684955e3d1ae4241092ff0cfcef532bb7a1c32"}, + {file = "propcache-0.3.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e53d19c2bf7d0d1e6998a7e693c7e87300dd971808e6618964621ccd0e01fe4e"}, + {file = "propcache-0.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a61a68d630e812b67b5bf097ab84e2cd79b48c792857dc10ba8a223f5b06a2af"}, + {file = "propcache-0.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fb91d20fa2d3b13deea98a690534697742029f4fb83673a3501ae6e3746508b5"}, + {file = "propcache-0.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67054e47c01b7b349b94ed0840ccae075449503cf1fdd0a1fdd98ab5ddc2667b"}, + {file = "propcache-0.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:997e7b8f173a391987df40f3b52c423e5850be6f6df0dcfb5376365440b56667"}, + {file = "propcache-0.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d663fd71491dde7dfdfc899d13a067a94198e90695b4321084c6e450743b8c7"}, + {file = "propcache-0.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8884ba1a0fe7210b775106b25850f5e5a9dc3c840d1ae9924ee6ea2eb3acbfe7"}, + {file = "propcache-0.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa806bbc13eac1ab6291ed21ecd2dd426063ca5417dd507e6be58de20e58dfcf"}, + {file = "propcache-0.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6f4d7a7c0aff92e8354cceca6fe223973ddf08401047920df0fcb24be2bd5138"}, + {file = "propcache-0.3.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:9be90eebc9842a93ef8335291f57b3b7488ac24f70df96a6034a13cb58e6ff86"}, + {file = "propcache-0.3.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:bf15fc0b45914d9d1b706f7c9c4f66f2b7b053e9517e40123e137e8ca8958b3d"}, + {file = "propcache-0.3.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5a16167118677d94bb48bfcd91e420088854eb0737b76ec374b91498fb77a70e"}, + {file = "propcache-0.3.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:41de3da5458edd5678b0f6ff66691507f9885f5fe6a0fb99a5d10d10c0fd2d64"}, + {file = "propcache-0.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:728af36011bb5d344c4fe4af79cfe186729efb649d2f8b395d1572fb088a996c"}, + {file = "propcache-0.3.0-cp312-cp312-win32.whl", hash = "sha256:6b5b7fd6ee7b54e01759f2044f936dcf7dea6e7585f35490f7ca0420fe723c0d"}, + {file = "propcache-0.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:2d15bc27163cd4df433e75f546b9ac31c1ba7b0b128bfb1b90df19082466ff57"}, + {file = "propcache-0.3.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a2b9bf8c79b660d0ca1ad95e587818c30ccdb11f787657458d6f26a1ea18c568"}, + {file = "propcache-0.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b0c1a133d42c6fc1f5fbcf5c91331657a1ff822e87989bf4a6e2e39b818d0ee9"}, + {file = "propcache-0.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bb2f144c6d98bb5cbc94adeb0447cfd4c0f991341baa68eee3f3b0c9c0e83767"}, + {file = "propcache-0.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1323cd04d6e92150bcc79d0174ce347ed4b349d748b9358fd2e497b121e03c8"}, + {file = "propcache-0.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b812b3cb6caacd072276ac0492d249f210006c57726b6484a1e1805b3cfeea0"}, + {file = "propcache-0.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:742840d1d0438eb7ea4280f3347598f507a199a35a08294afdcc560c3739989d"}, + {file = "propcache-0.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c6e7e4f9167fddc438cd653d826f2222222564daed4116a02a184b464d3ef05"}, + {file = "propcache-0.3.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a94ffc66738da99232ddffcf7910e0f69e2bbe3a0802e54426dbf0714e1c2ffe"}, + {file = "propcache-0.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3c6ec957025bf32b15cbc6b67afe233c65b30005e4c55fe5768e4bb518d712f1"}, + {file = "propcache-0.3.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:549722908de62aa0b47a78b90531c022fa6e139f9166be634f667ff45632cc92"}, + {file = "propcache-0.3.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5d62c4f6706bff5d8a52fd51fec6069bef69e7202ed481486c0bc3874912c787"}, + {file = "propcache-0.3.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:24c04f8fbf60094c531667b8207acbae54146661657a1b1be6d3ca7773b7a545"}, + {file = "propcache-0.3.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:7c5f5290799a3f6539cc5e6f474c3e5c5fbeba74a5e1e5be75587746a940d51e"}, + {file = "propcache-0.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4fa0e7c9c3cf7c276d4f6ab9af8adddc127d04e0fcabede315904d2ff76db626"}, + {file = "propcache-0.3.0-cp313-cp313-win32.whl", hash = "sha256:ee0bd3a7b2e184e88d25c9baa6a9dc609ba25b76daae942edfb14499ac7ec374"}, + {file = "propcache-0.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:1c8f7d896a16da9455f882870a507567d4f58c53504dc2d4b1e1d386dfe4588a"}, + {file = "propcache-0.3.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e560fd75aaf3e5693b91bcaddd8b314f4d57e99aef8a6c6dc692f935cc1e6bbf"}, + {file = "propcache-0.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:65a37714b8ad9aba5780325228598a5b16c47ba0f8aeb3dc0514701e4413d7c0"}, + {file = "propcache-0.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:07700939b2cbd67bfb3b76a12e1412405d71019df00ca5697ce75e5ef789d829"}, + {file = "propcache-0.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c0fdbdf6983526e269e5a8d53b7ae3622dd6998468821d660d0daf72779aefa"}, + {file = "propcache-0.3.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:794c3dd744fad478b6232289c866c25406ecdfc47e294618bdf1697e69bd64a6"}, + {file = "propcache-0.3.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4544699674faf66fb6b4473a1518ae4999c1b614f0b8297b1cef96bac25381db"}, + {file = "propcache-0.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fddb8870bdb83456a489ab67c6b3040a8d5a55069aa6f72f9d872235fbc52f54"}, + {file = "propcache-0.3.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f857034dc68d5ceb30fb60afb6ff2103087aea10a01b613985610e007053a121"}, + {file = "propcache-0.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:02df07041e0820cacc8f739510078f2aadcfd3fc57eaeeb16d5ded85c872c89e"}, + {file = "propcache-0.3.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f47d52fd9b2ac418c4890aad2f6d21a6b96183c98021f0a48497a904199f006e"}, + {file = "propcache-0.3.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9ff4e9ecb6e4b363430edf2c6e50173a63e0820e549918adef70515f87ced19a"}, + {file = "propcache-0.3.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ecc2920630283e0783c22e2ac94427f8cca29a04cfdf331467d4f661f4072dac"}, + {file = "propcache-0.3.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:c441c841e82c5ba7a85ad25986014be8d7849c3cfbdb6004541873505929a74e"}, + {file = "propcache-0.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6c929916cbdb540d3407c66f19f73387f43e7c12fa318a66f64ac99da601bcdf"}, + {file = "propcache-0.3.0-cp313-cp313t-win32.whl", hash = "sha256:0c3e893c4464ebd751b44ae76c12c5f5c1e4f6cbd6fbf67e3783cd93ad221863"}, + {file = "propcache-0.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:75e872573220d1ee2305b35c9813626e620768248425f58798413e9c39741f46"}, + {file = "propcache-0.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:03c091bb752349402f23ee43bb2bff6bd80ccab7c9df6b88ad4322258d6960fc"}, + {file = "propcache-0.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:46ed02532cb66612d42ae5c3929b5e98ae330ea0f3900bc66ec5f4862069519b"}, + {file = "propcache-0.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:11ae6a8a01b8a4dc79093b5d3ca2c8a4436f5ee251a9840d7790dccbd96cb649"}, + {file = "propcache-0.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df03cd88f95b1b99052b52b1bb92173229d7a674df0ab06d2b25765ee8404bce"}, + {file = "propcache-0.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:03acd9ff19021bd0567582ac88f821b66883e158274183b9e5586f678984f8fe"}, + {file = "propcache-0.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd54895e4ae7d32f1e3dd91261df46ee7483a735017dc6f987904f194aa5fd14"}, + {file = "propcache-0.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26a67e5c04e3119594d8cfae517f4b9330c395df07ea65eab16f3d559b7068fe"}, + {file = "propcache-0.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee25f1ac091def37c4b59d192bbe3a206298feeb89132a470325bf76ad122a1e"}, + {file = "propcache-0.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:58e6d2a5a7cb3e5f166fd58e71e9a4ff504be9dc61b88167e75f835da5764d07"}, + {file = "propcache-0.3.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:be90c94570840939fecedf99fa72839aed70b0ced449b415c85e01ae67422c90"}, + {file = "propcache-0.3.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:49ea05212a529c2caffe411e25a59308b07d6e10bf2505d77da72891f9a05641"}, + {file = "propcache-0.3.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:119e244ab40f70a98c91906d4c1f4c5f2e68bd0b14e7ab0a06922038fae8a20f"}, + {file = "propcache-0.3.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:507c5357a8d8b4593b97fb669c50598f4e6cccbbf77e22fa9598aba78292b4d7"}, + {file = "propcache-0.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:8526b0941ec5a40220fc4dfde76aed58808e2b309c03e9fa8e2260083ef7157f"}, + {file = "propcache-0.3.0-cp39-cp39-win32.whl", hash = "sha256:7cedd25e5f678f7738da38037435b340694ab34d424938041aa630d8bac42663"}, + {file = "propcache-0.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:bf4298f366ca7e1ad1d21bbb58300a6985015909964077afd37559084590c929"}, + {file = "propcache-0.3.0-py3-none-any.whl", hash = "sha256:67dda3c7325691c2081510e92c561f465ba61b975f481735aefdfc845d2cd043"}, + {file = "propcache-0.3.0.tar.gz", hash = "sha256:a8fd93de4e1d278046345f49e2238cdb298589325849b2645d4a94c53faeffc5"}, +] + [[package]] name = "protobuf" version = "5.29.3" @@ -962,7 +1554,6 @@ description = "" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "protobuf-5.29.3-cp310-abi3-win32.whl", hash = "sha256:3ea51771449e1035f26069c4c7fd51fba990d07bc55ba80701c78f886bf9c888"}, {file = "protobuf-5.29.3-cp310-abi3-win_amd64.whl", hash = "sha256:a4fa6f80816a9a0678429e84973f2f98cbc218cca434abe8db2ad0bffc98503a"}, @@ -984,7 +1575,6 @@ description = "Python library for Apache Arrow" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pyarrow-19.0.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:fc28912a2dc924dddc2087679cc8b7263accc71b9ff025a1362b004711661a69"}, {file = "pyarrow-19.0.1-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:fca15aabbe9b8355800d923cc2e82c8ef514af321e18b437c3d782aa884eaeec"}, @@ -1040,7 +1630,6 @@ description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pydantic-2.10.6-py3-none-any.whl", hash = "sha256:427d664bf0b8a2b34ff5dd0f5a18df00591adcee7198fbd71981054cef37b584"}, {file = "pydantic-2.10.6.tar.gz", hash = "sha256:ca5daa827cce33de7a42be142548b0096bf05a7e7b365aebfa5f8eeec7128236"}, @@ -1053,7 +1642,7 @@ typing-extensions = ">=4.12.2" [package.extras] email = ["email-validator (>=2.0.0)"] -timezone = ["tzdata"] +timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows\""] [[package]] name = "pydantic-core" @@ -1062,7 +1651,6 @@ description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pydantic_core-2.27.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2d367ca20b2f14095a8f4fa1210f5a7b78b8a20009ecced6b12818f455b1e9fa"}, {file = "pydantic_core-2.27.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:491a2b73db93fab69731eaee494f320faa4e093dbed776be1a829c2eb222c34c"}, @@ -1176,7 +1764,6 @@ description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c"}, {file = "pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f"}, @@ -1192,7 +1779,6 @@ description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"}, {file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"}, @@ -1216,7 +1802,6 @@ description = "Pytest support for asyncio" optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pytest_asyncio-0.24.0-py3-none-any.whl", hash = "sha256:a811296ed596b69bf0b6f3dc40f83bcaf341b155a269052d82efa2b25ac7037b"}, {file = "pytest_asyncio-0.24.0.tar.gz", hash = "sha256:d081d828e576d85f875399194281e92bf8a68d60d72d1a2faf2feddb6c46b276"}, @@ -1236,7 +1821,6 @@ description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" 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"}, @@ -1252,7 +1836,6 @@ description = "Universally unique lexicographically sortable identifier" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "python_ulid-3.0.0-py3-none-any.whl", hash = "sha256:e4c4942ff50dbd79167ad01ac725ec58f924b4018025ce22c858bfcff99a5e31"}, {file = "python_ulid-3.0.0.tar.gz", hash = "sha256:e50296a47dc8209d28629a22fc81ca26c00982c78934bd7766377ba37ea49a9f"}, @@ -1268,7 +1851,6 @@ description = "World timezone definitions, modern and historical" optional = false python-versions = "*" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "pytz-2025.1-py2.py3-none-any.whl", hash = "sha256:89dd22dca55b46eac6eda23b2d72721bf1bdfef212645d81513ef5d03038de57"}, {file = "pytz-2025.1.tar.gz", hash = "sha256:c2db42be2a2518b28e65f9207c4d05e6ff547d1efa4086469ef855e4ab70178e"}, @@ -1281,7 +1863,6 @@ description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" 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"}, @@ -1345,7 +1926,6 @@ description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, @@ -1368,7 +1948,6 @@ description = "Render rich text, tables, progress bars, syntax highlighting, mar optional = false python-versions = ">=3.8.0" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90"}, {file = "rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098"}, @@ -1389,7 +1968,6 @@ description = "ruamel.yaml is a YAML parser/emitter that supports roundtrip pres optional = false python-versions = ">=3.7" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" 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"}, @@ -1409,7 +1987,7 @@ description = "C version of reader, parser and emitter for ruamel.yaml derived f optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version <= \"3.11\" and platform_python_implementation == \"CPython\" or python_version >= \"3.12\" and platform_python_implementation == \"CPython\" and python_version < \"3.13\"" +markers = "platform_python_implementation == \"CPython\" and python_version < \"3.13\"" files = [ {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:11f891336688faf5156a36293a9c362bdc7c88f03a8a027c2c1d8e0bcde998e5"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:a606ef75a60ecf3d924613892cc603b154178ee25abb3055db5062da811fd969"}, @@ -1461,7 +2039,6 @@ description = "An extremely fast Python linter and code formatter, written in Ru optional = false python-versions = ">=3.7" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "ruff-0.1.15-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:5fe8d54df166ecc24106db7dd6a68d44852d14eb0729ea4672bb4d96c320b7df"}, {file = "ruff-0.1.15-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6f0bfbb53c4b4de117ac4d6ddfd33aa5fc31beeaa21d23c45c6dd249faf9126f"}, @@ -1482,6 +2059,27 @@ files = [ {file = "ruff-0.1.15.tar.gz", hash = "sha256:f6dfa8c1b21c913c326919056c390966648b680966febcb796cc9d1aaab8564e"}, ] +[[package]] +name = "s3fs" +version = "2025.3.0" +description = "Convenient Filesystem interface over S3" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "s3fs-2025.3.0-py3-none-any.whl", hash = "sha256:88d803615baa04945156ca0e1498009b7acd3132c07198bd81b3e874846e0aa2"}, + {file = "s3fs-2025.3.0.tar.gz", hash = "sha256:446dd539eb0d0678209723cb7ad1bedbb172185b0d34675b09be1ad81843a644"}, +] + +[package.dependencies] +aiobotocore = ">=2.5.4,<3.0.0" +aiohttp = "<4.0.0a0 || >4.0.0a0,<4.0.0a1 || >4.0.0a1" +fsspec = "==2025.3.0.*" + +[package.extras] +awscli = ["aiobotocore[awscli] (>=2.5.4,<3.0.0)"] +boto3 = ["aiobotocore[boto3] (>=2.5.4,<3.0.0)"] + [[package]] name = "six" version = "1.17.0" @@ -1489,7 +2087,6 @@ description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, @@ -1502,7 +2099,6 @@ description = "Sniff out which async library your code is running under" optional = false python-versions = ">=3.7" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"}, {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, @@ -1558,12 +2154,38 @@ description = "Typing stubs for protobuf" optional = false python-versions = ">=3.9" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" 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"}, ] +[[package]] +name = "types-pytz" +version = "2025.1.0.20250318" +description = "Typing stubs for pytz" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "types_pytz-2025.1.0.20250318-py3-none-any.whl", hash = "sha256:04dba4907c5415777083f9548693c6d9f80ec53adcaff55a38526a3f8ddcae04"}, + {file = "types_pytz-2025.1.0.20250318.tar.gz", hash = "sha256:97e0e35184c6fe14e3a5014512057f2c57bb0c6582d63c1cfcc4809f82180449"}, +] + +[[package]] +name = "types-requests" +version = "2.32.0.20250306" +description = "Typing stubs for requests" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "types_requests-2.32.0.20250306-py3-none-any.whl", hash = "sha256:25f2cbb5c8710b2022f8bbee7b2b66f319ef14aeea2f35d80f18c9dbf3b60a0b"}, + {file = "types_requests-2.32.0.20250306.tar.gz", hash = "sha256:0962352694ec5b2f95fda877ee60a159abdf84a0fc6fdace599f20acb41a03d1"}, +] + +[package.dependencies] +urllib3 = ">=2" + [[package]] name = "typing-extensions" version = "4.12.2" @@ -1571,7 +2193,6 @@ description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" groups = ["main", "dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" 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"}, @@ -1584,7 +2205,6 @@ description = "Provider of IANA time zone data" optional = false python-versions = ">=2" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "tzdata-2025.1-py2.py3-none-any.whl", hash = "sha256:7e127113816800496f027041c570f50bcd464a020098a3b6b199517772303639"}, {file = "tzdata-2025.1.tar.gz", hash = "sha256:24894909e88cdb28bd1636c6887801df64cb485bd593f2fd83ef29075a81d694"}, @@ -1596,15 +2216,14 @@ version = "2.3.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" -groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" +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"}, ] [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -1616,7 +2235,6 @@ description = "Virtual Python Environment builder" optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "virtualenv-20.29.2-py3-none-any.whl", hash = "sha256:febddfc3d1ea571bdb1dc0f98d7b45d24def7428214d4fb73cc486c9568cce6a"}, {file = "virtualenv-20.29.2.tar.gz", hash = "sha256:fdaabebf6d03b5ba83ae0a02cfe96f48a716f4fae556461d180825866f75b728"}, @@ -1629,7 +2247,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)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] +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\""] + +[[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"] +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" @@ -1638,7 +2272,6 @@ description = "Module for decorators, wrappers and monkey patching." optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3d57c572081fed831ad2d26fd430d565b76aa277ed1d30ff4d40670b1c0dd984"}, {file = "wrapt-1.17.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b5e251054542ae57ac7f3fba5d10bfff615b6c2fb09abeb37d2f1463f841ae22"}, @@ -1721,6 +2354,103 @@ files = [ {file = "wrapt-1.17.2.tar.gz", hash = "sha256:41388e9d4d1522446fe79d3213196bd9e3b301a336965b9e27ca2788ebd122f3"}, ] +[[package]] +name = "yarl" +version = "1.18.3" +description = "Yet another URL library" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7df647e8edd71f000a5208fe6ff8c382a1de8edfbccdbbfe649d263de07d8c34"}, + {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c69697d3adff5aa4f874b19c0e4ed65180ceed6318ec856ebc423aa5850d84f7"}, + {file = "yarl-1.18.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:602d98f2c2d929f8e697ed274fbadc09902c4025c5a9963bf4e9edfc3ab6f7ed"}, + {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c654d5207c78e0bd6d749f6dae1dcbbfde3403ad3a4b11f3c5544d9906969dde"}, + {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5094d9206c64181d0f6e76ebd8fb2f8fe274950a63890ee9e0ebfd58bf9d787b"}, + {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35098b24e0327fc4ebdc8ffe336cee0a87a700c24ffed13161af80124b7dc8e5"}, + {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3236da9272872443f81fedc389bace88408f64f89f75d1bdb2256069a8730ccc"}, + {file = "yarl-1.18.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2c08cc9b16f4f4bc522771d96734c7901e7ebef70c6c5c35dd0f10845270bcd"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80316a8bd5109320d38eef8833ccf5f89608c9107d02d2a7f985f98ed6876990"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c1e1cc06da1491e6734f0ea1e6294ce00792193c463350626571c287c9a704db"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fea09ca13323376a2fdfb353a5fa2e59f90cd18d7ca4eaa1fd31f0a8b4f91e62"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e3b9fd71836999aad54084906f8663dffcd2a7fb5cdafd6c37713b2e72be1760"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:757e81cae69244257d125ff31663249b3013b5dc0a8520d73694aed497fb195b"}, + {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b1771de9944d875f1b98a745bc547e684b863abf8f8287da8466cf470ef52690"}, + {file = "yarl-1.18.3-cp310-cp310-win32.whl", hash = "sha256:8874027a53e3aea659a6d62751800cf6e63314c160fd607489ba5c2edd753cf6"}, + {file = "yarl-1.18.3-cp310-cp310-win_amd64.whl", hash = "sha256:93b2e109287f93db79210f86deb6b9bbb81ac32fc97236b16f7433db7fc437d8"}, + {file = "yarl-1.18.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8503ad47387b8ebd39cbbbdf0bf113e17330ffd339ba1144074da24c545f0069"}, + {file = "yarl-1.18.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02ddb6756f8f4517a2d5e99d8b2f272488e18dd0bfbc802f31c16c6c20f22193"}, + {file = "yarl-1.18.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:67a283dd2882ac98cc6318384f565bffc751ab564605959df4752d42483ad889"}, + {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d980e0325b6eddc81331d3f4551e2a333999fb176fd153e075c6d1c2530aa8a8"}, + {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b643562c12680b01e17239be267bc306bbc6aac1f34f6444d1bded0c5ce438ca"}, + {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c017a3b6df3a1bd45b9fa49a0f54005e53fbcad16633870104b66fa1a30a29d8"}, + {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75674776d96d7b851b6498f17824ba17849d790a44d282929c42dbb77d4f17ae"}, + {file = "yarl-1.18.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ccaa3a4b521b780a7e771cc336a2dba389a0861592bbce09a476190bb0c8b4b3"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2d06d3005e668744e11ed80812e61efd77d70bb7f03e33c1598c301eea20efbb"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:9d41beda9dc97ca9ab0b9888cb71f7539124bc05df02c0cff6e5acc5a19dcc6e"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ba23302c0c61a9999784e73809427c9dbedd79f66a13d84ad1b1943802eaaf59"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6748dbf9bfa5ba1afcc7556b71cda0d7ce5f24768043a02a58846e4a443d808d"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0b0cad37311123211dc91eadcb322ef4d4a66008d3e1bdc404808992260e1a0e"}, + {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0fb2171a4486bb075316ee754c6d8382ea6eb8b399d4ec62fde2b591f879778a"}, + {file = "yarl-1.18.3-cp311-cp311-win32.whl", hash = "sha256:61b1a825a13bef4a5f10b1885245377d3cd0bf87cba068e1d9a88c2ae36880e1"}, + {file = "yarl-1.18.3-cp311-cp311-win_amd64.whl", hash = "sha256:b9d60031cf568c627d028239693fd718025719c02c9f55df0a53e587aab951b5"}, + {file = "yarl-1.18.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1dd4bdd05407ced96fed3d7f25dbbf88d2ffb045a0db60dbc247f5b3c5c25d50"}, + {file = "yarl-1.18.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7c33dd1931a95e5d9a772d0ac5e44cac8957eaf58e3c8da8c1414de7dd27c576"}, + {file = "yarl-1.18.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25b411eddcfd56a2f0cd6a384e9f4f7aa3efee14b188de13048c25b5e91f1640"}, + {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:436c4fc0a4d66b2badc6c5fc5ef4e47bb10e4fd9bf0c79524ac719a01f3607c2"}, + {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e35ef8683211db69ffe129a25d5634319a677570ab6b2eba4afa860f54eeaf75"}, + {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84b2deecba4a3f1a398df819151eb72d29bfeb3b69abb145a00ddc8d30094512"}, + {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e5a1fea0fd4f5bfa7440a47eff01d9822a65b4488f7cff83155a0f31a2ecba"}, + {file = "yarl-1.18.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0e883008013c0e4aef84dcfe2a0b172c4d23c2669412cf5b3371003941f72bb"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5a3f356548e34a70b0172d8890006c37be92995f62d95a07b4a42e90fba54272"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ccd17349166b1bee6e529b4add61727d3f55edb7babbe4069b5764c9587a8cc6"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b958ddd075ddba5b09bb0be8a6d9906d2ce933aee81100db289badbeb966f54e"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c7d79f7d9aabd6011004e33b22bc13056a3e3fb54794d138af57f5ee9d9032cb"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4891ed92157e5430874dad17b15eb1fda57627710756c27422200c52d8a4e393"}, + {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ce1af883b94304f493698b00d0f006d56aea98aeb49d75ec7d98cd4a777e9285"}, + {file = "yarl-1.18.3-cp312-cp312-win32.whl", hash = "sha256:f91c4803173928a25e1a55b943c81f55b8872f0018be83e3ad4938adffb77dd2"}, + {file = "yarl-1.18.3-cp312-cp312-win_amd64.whl", hash = "sha256:7e2ee16578af3b52ac2f334c3b1f92262f47e02cc6193c598502bd46f5cd1477"}, + {file = "yarl-1.18.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:90adb47ad432332d4f0bc28f83a5963f426ce9a1a8809f5e584e704b82685dcb"}, + {file = "yarl-1.18.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:913829534200eb0f789d45349e55203a091f45c37a2674678744ae52fae23efa"}, + {file = "yarl-1.18.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ef9f7768395923c3039055c14334ba4d926f3baf7b776c923c93d80195624782"}, + {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a19f62ff30117e706ebc9090b8ecc79aeb77d0b1f5ec10d2d27a12bc9f66d0"}, + {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e17c9361d46a4d5addf777c6dd5eab0715a7684c2f11b88c67ac37edfba6c482"}, + {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a74a13a4c857a84a845505fd2d68e54826a2cd01935a96efb1e9d86c728e186"}, + {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41f7ce59d6ee7741af71d82020346af364949314ed3d87553763a2df1829cc58"}, + {file = "yarl-1.18.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f52a265001d830bc425f82ca9eabda94a64a4d753b07d623a9f2863fde532b53"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:82123d0c954dc58db301f5021a01854a85bf1f3bb7d12ae0c01afc414a882ca2"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2ec9bbba33b2d00999af4631a3397d1fd78290c48e2a3e52d8dd72db3a067ac8"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fbd6748e8ab9b41171bb95c6142faf068f5ef1511935a0aa07025438dd9a9bc1"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:877d209b6aebeb5b16c42cbb377f5f94d9e556626b1bfff66d7b0d115be88d0a"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b464c4ab4bfcb41e3bfd3f1c26600d038376c2de3297760dfe064d2cb7ea8e10"}, + {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8d39d351e7faf01483cc7ff7c0213c412e38e5a340238826be7e0e4da450fdc8"}, + {file = "yarl-1.18.3-cp313-cp313-win32.whl", hash = "sha256:61ee62ead9b68b9123ec24bc866cbef297dd266175d53296e2db5e7f797f902d"}, + {file = "yarl-1.18.3-cp313-cp313-win_amd64.whl", hash = "sha256:578e281c393af575879990861823ef19d66e2b1d0098414855dd367e234f5b3c"}, + {file = "yarl-1.18.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:61e5e68cb65ac8f547f6b5ef933f510134a6bf31bb178be428994b0cb46c2a04"}, + {file = "yarl-1.18.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fe57328fbc1bfd0bd0514470ac692630f3901c0ee39052ae47acd1d90a436719"}, + {file = "yarl-1.18.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a440a2a624683108a1b454705ecd7afc1c3438a08e890a1513d468671d90a04e"}, + {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09c7907c8548bcd6ab860e5f513e727c53b4a714f459b084f6580b49fa1b9cee"}, + {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b4f6450109834af88cb4cc5ecddfc5380ebb9c228695afc11915a0bf82116789"}, + {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9ca04806f3be0ac6d558fffc2fdf8fcef767e0489d2684a21912cc4ed0cd1b8"}, + {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77a6e85b90a7641d2e07184df5557132a337f136250caafc9ccaa4a2a998ca2c"}, + {file = "yarl-1.18.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6333c5a377c8e2f5fae35e7b8f145c617b02c939d04110c76f29ee3676b5f9a5"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0b3c92fa08759dbf12b3a59579a4096ba9af8dd344d9a813fc7f5070d86bbab1"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:4ac515b860c36becb81bb84b667466885096b5fc85596948548b667da3bf9f24"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:045b8482ce9483ada4f3f23b3774f4e1bf4f23a2d5c912ed5170f68efb053318"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:a4bb030cf46a434ec0225bddbebd4b89e6471814ca851abb8696170adb163985"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:54d6921f07555713b9300bee9c50fb46e57e2e639027089b1d795ecd9f7fa910"}, + {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1d407181cfa6e70077df3377938c08012d18893f9f20e92f7d2f314a437c30b1"}, + {file = "yarl-1.18.3-cp39-cp39-win32.whl", hash = "sha256:ac36703a585e0929b032fbaab0707b75dc12703766d0b53486eabd5139ebadd5"}, + {file = "yarl-1.18.3-cp39-cp39-win_amd64.whl", hash = "sha256:ba87babd629f8af77f557b61e49e7c7cac36f22f871156b91e10a6e9d4f829e9"}, + {file = "yarl-1.18.3-py3-none-any.whl", hash = "sha256:b57f4f58099328dfb26c6a771d09fb20dbbae81d20cfb66141251ea063bd101b"}, + {file = "yarl-1.18.3.tar.gz", hash = "sha256:ac1801c45cbf77b6c99242eeff4fffb5e4e73a800b5c4ad4fc0be5def634d2e1"}, +] + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" +propcache = ">=0.2.0" + [[package]] name = "zipp" version = "3.21.0" @@ -1728,21 +2458,20 @@ description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.9" groups = ["main"] -markers = "python_version <= \"3.11\" or python_version >= \"3.12\"" files = [ {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"}, {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] -test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +test = ["big-O", "importlib-resources ; python_version < \"3.9\"", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = "^3.10" -content-hash = "a9a91a8812ed382c7b1b6a76bd231f27208acfeb687135fddbf52f8e4fd54eb6" +content-hash = "d8a57713ce1fa2468ce3e79f2ecb9fb3f87f50fc9422d6cc781bfdbcaece4ff0" diff --git a/pyproject.toml b/pyproject.toml index d5f11d2a..5218e66a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,8 @@ 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 = "^2025.3.0"} [tool.pytest.ini_options] asyncio_mode = "auto" @@ -31,6 +33,8 @@ pre-commit = "^3.8.0" pytest = "^8.3.3" pytest-asyncio = "^0.24.0" types-protobuf = "^5.29.1.20250208" +pandas-stubs = "^2.2.3.250308" +types-requests = "^2.32.0.20250306" [build-system] requires = ["poetry-core>=1.0.0"] From 6573e60f816e21b6aa10037bd5ce6ebece6906da Mon Sep 17 00:00:00 2001 From: monoxgas Date: Tue, 8 Apr 2025 22:23:21 -0600 Subject: [PATCH 06/24] Initial serialization code --- dreadnode/object.py | 10 +- dreadnode/serialization.py | 585 +++++++++++++++++++++++++++++++++++++ dreadnode/util.py | 22 ++ 3 files changed, 610 insertions(+), 7 deletions(-) create mode 100644 dreadnode/serialization.py create mode 100644 dreadnode/util.py diff --git a/dreadnode/object.py b/dreadnode/object.py index 1606156b..50534d60 100644 --- a/dreadnode/object.py +++ b/dreadnode/object.py @@ -16,12 +16,8 @@ class ObjectRef: @dataclass -class _Object: +class ObjectUri: hash: str - - -@dataclass -class ObjectUri(_Object): uri: str size: int type: t.Literal["uri"] = "uri" @@ -29,10 +25,10 @@ class ObjectUri(_Object): @dataclass -class ObjectVal(_Object): +class ObjectVal: + hash: str value: t.Any type: t.Literal["val"] = "val" - hint: str attributes: JsonDict | None = None diff --git a/dreadnode/serialization.py b/dreadnode/serialization.py new file mode 100644 index 00000000..fffcf8a6 --- /dev/null +++ b/dreadnode/serialization.py @@ -0,0 +1,585 @@ +import base64 +import contextlib +import dataclasses +import datetime +import io +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: + 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 + + return obj.model_dump(mode="json"), obj.model_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() + obj.save(buffer, format="PNG") + + return _handle_bytes( + buffer.getvalue(), + _seen, + { + "x-python-datatype": "PIL.Image", + "format": "png", + }, + ) + + +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 + + 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 + + return handlers + + +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) + + # 10. Final Fallback: Use safe_repr + return safe_repr(obj), { + "type": "string", + "title": obj_type.__name__, + "x-python-datatype": "unknown", + } + + +# --- Public API Function --- + + +def serialize(obj: t.Any) -> tuple[JsonValue, JsonDict]: + """ + 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: + A tuple containing: + - The serialized JSON-compatible representation of the object. + - A JSON Schema describing the structure and format of the + serialized representation. + """ + return _serialize(obj) diff --git a/dreadnode/util.py b/dreadnode/util.py new file mode 100644 index 00000000..ad28db47 --- /dev/null +++ b/dreadnode/util.py @@ -0,0 +1,22 @@ +import typing as t + + +def safe_repr(obj: t.Any) -> str: + """ + Return some kind of non-empty string representation of an object, catching exceptions. + + Taken from `logfire`. + """ + + 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 "" From e693cbda7d7ec38903876f232ab81c4efeb6ab30 Mon Sep 17 00:00:00 2001 From: monoxgas Date: Wed, 9 Apr 2025 16:39:30 -0600 Subject: [PATCH 07/24] Plug serialization and schema generation into log_object. Rename kind -> label to avoid clashing with OTEL stuff --- dreadnode/main.py | 40 +++---- dreadnode/object.py | 192 +-------------------------------- dreadnode/serialization.py | 47 ++++++-- dreadnode/task.py | 16 +-- dreadnode/tracing/constants.py | 6 +- dreadnode/tracing/span.py | 159 +++++++++++++++++---------- dreadnode/types.py | 8 ++ 7 files changed, 186 insertions(+), 282 deletions(-) diff --git a/dreadnode/main.py b/dreadnode/main.py index 53e3c391..341cc57c 100644 --- a/dreadnode/main.py +++ b/dreadnode/main.py @@ -144,7 +144,8 @@ def initialize(self) -> None: 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}).", @@ -160,7 +161,6 @@ def initialize(self) -> None: metric_readers.append(FileMetricReader(config)) if self.token is not None: - self.server = self.server or DEFAULT_SERVER_URL self._api = ApiClient(self.server, self.token) headers = {"User-Agent": f"dreadnode/{VERSION}", "X-Api-Key": self.token} @@ -253,7 +253,7 @@ def task( *, scorers: None = None, name: str | None = None, - kind: str | None = None, + label: str | None = None, log_params: t.Sequence[str] | t.Literal[True] | None = None, log_inputs: t.Sequence[str] | t.Literal[True] | None = None, log_output: bool = True, @@ -268,7 +268,7 @@ def task( *, scorers: t.Sequence[Scorer[R] | ScorerCallable[R]], name: str | None = None, - kind: str | None = None, + label: str | None = None, log_params: t.Sequence[str] | t.Literal[True] | None = None, log_inputs: t.Sequence[str] | t.Literal[True] | None = None, log_output: bool = True, @@ -282,7 +282,7 @@ def task( *, scorers: t.Sequence[Scorer[t.Any] | ScorerCallable[R]] | None = None, name: str | None = None, - kind: str | None = None, + label: str | None = None, log_params: t.Sequence[str] | t.Literal[True] | None = None, log_inputs: t.Sequence[str] | t.Literal[True] | None = None, log_output: bool = True, @@ -302,10 +302,10 @@ def make_task(func: t.Callable[P, t.Awaitable[R]] | t.Callable[P, R]) -> Task[P, ) _name = name or func_name - _kind = kind or func_name + _label = label or func_name - # conform our kind for sanity - _kind = re.sub(r"[\W_]+", "_", _kind.lower()) + # conform our label for sanity + _label = re.sub(r"[\W_]+", "_", _label.lower()) _attributes = attributes or {} _attributes["code.function"] = func_name @@ -313,7 +313,9 @@ def make_task(func: t.Callable[P, t.Awaitable[R]] | t.Callable[P, R]) -> Task[P, _attributes["code.lineno"] = func.__code__.co_firstlineno with contextlib.suppress(Exception): _attributes.update( - get_filepath_attribute(inspect.getsourcefile(func)), # type: ignore [arg-type] + get_filepath_attribute( + inspect.getsourcefile(func), # type: ignore [arg-type] + ), ) return Task( @@ -331,7 +333,7 @@ def make_task(func: t.Callable[P, t.Awaitable[R]] | t.Callable[P, R]) -> Task[P, log_params=log_params, log_inputs=log_inputs, log_output=log_output, - kind=_kind, + label=_label, ) return make_task @@ -340,7 +342,7 @@ def task_span( self, name: str, *, - kind: str | None = None, + label: str | None = None, params: AnyDict | None = None, tags: t.Sequence[str] | None = None, **attributes: t.Any, @@ -348,10 +350,10 @@ def task_span( if (run := current_run_span.get()) is None: raise RuntimeError("task_span() must be called within a run") - kind = kind or re.sub(r"[\W_]+", "_", name.lower()) + label = label or re.sub(r"[\W_]+", "_", name.lower()) return TaskSpan( name=name, - kind=kind, + label=label, attributes=attributes, params=params, tags=tags, @@ -488,7 +490,7 @@ def log_input( name: str, value: JsonValue, *, - kind: str | None = None, + label: str | None = None, to: ToObject = "task-or-run", **attributes: t.Any, ) -> None: @@ -501,12 +503,12 @@ def log_input( raise RuntimeError( "log_inputs() with to='task-or-run' must be called within a run or a task", ) - target.log_input(name, value, kind=kind, **attributes) + target.log_input(name, value, label=label, **attributes) elif to == "run": if run is None: raise RuntimeError("log_inputs() with to='run' must be called within a run") - run.log_input(name, value, kind=kind, **attributes) + run.log_input(name, value, label=label, **attributes) def log_inputs( self, @@ -521,7 +523,7 @@ def log_output( name: str, value: t.Any, *, - kind: str | None = None, + label: str | None = None, to: ToObject = "task-or-run", **attributes: JsonValue, ) -> None: @@ -534,12 +536,12 @@ def log_output( raise RuntimeError( "log_output() with to='task-or-run' must be called within a run or a task", ) - target.log_output(name, value, kind=kind, **attributes) + target.log_output(name, value, label=label, **attributes) elif to == "run": if run is None: raise RuntimeError("log_output() with to='run' must be called within a run") - run.log_output(name, value, kind=kind, **attributes) + run.log_output(name, value, label=label, **attributes) def log_outputs( self, diff --git a/dreadnode/object.py b/dreadnode/object.py index 50534d60..708beecd 100644 --- a/dreadnode/object.py +++ b/dreadnode/object.py @@ -1,215 +1,29 @@ -import contextlib -import hashlib import typing as t from dataclasses import dataclass -from pydantic import TypeAdapter - -from dreadnode.types import JsonDict - @dataclass class ObjectRef: name: str - kind: str + label: str hash: str @dataclass class ObjectUri: hash: str + schema_hash: str uri: str size: int type: t.Literal["uri"] = "uri" - attributes: JsonDict | None = None @dataclass class ObjectVal: hash: str + schema_hash: str value: t.Any type: t.Literal["val"] = "val" - attributes: JsonDict | None = None Object = ObjectUri | ObjectVal - - -def _hash(data: bytes) -> str: - return hashlib.md5(data).hexdigest()[:16] # noqa: S324 - - -def universal_hash(obj: t.Any) -> str: - # Type adapter for most common objects - with contextlib.suppress(Exception): - json_str = TypeAdapter(t.Any).dump_json(obj) - return _hash(json_str) - - # Numpy - with contextlib.suppress(Exception): - import numpy as np - - if isinstance(obj, np.ndarray): - return _hash(obj.tobytes()) - - # Pandas - with contextlib.suppress(Exception): - import pandas as pd - - if isinstance(obj, pd.DataFrame | pd.Series): - return _hash(pd.util.hash_pandas_object(obj).to_numpy().tobytes()) - - # Hashable objects - with contextlib.suppress(Exception): - return _hash(hash(obj).to_bytes(16, "big")) - - # Try repr() - commenting for now as it's probably not stable - # with contextlib.suppress(Exception): - # return g_hash_func(repr(obj).encode()).hexdigest() - - # Fallback to id() - return _hash(id(obj).to_bytes(16, "big")) - - -# Storage - - -# class ObjectStorage(abc.ABC): -# async def setup(self) -> None: -# """Setup the artifact storage manager.""" - -# @staticmethod -# @abstractmethod -# async def _save_artifact( -# self, -# run_state: RunState, -# artifact_id: uuid.UUID, -# data: ArtifactDataTypes, -# original_type: ArtifactOriginalTypes, -# ) -> ArtifactSaved: -# """ -# Asynchronously saves an artifact. -# Args: -# run_state (RunState): The current state of the run. -# artifact_id (uuid.UUID): The unique identifier of the artifact. -# data (ArtifactDataTypes): The data of the artifact to be saved. -# original_type (ArtifactOriginalTypes): The original type of the artifact. -# Returns: -# ArtifactSaved: The result of the artifact save operation. -# """ - -# @abstractmethod -# async def _get_artifact(self, uri: str) -> ArtifactDataTypes: -# """ -# Asynchronously retrieves an artifact from the given path. -# Args: -# path (str | Path): The path to the artifact. -# Returns: -# ArtifactDataTypes: The data type of the retrieved artifact. -# """ - -# @abstractmethod -# async def _delete_artifact(self, uri: str | Path) -> bool: -# """ -# Asynchronously deletes an artifact at the given path. -# Args: -# path (str | Path): The path to the artifact to be deleted. -# Returns: -# bool: True if the artifact was successfully deleted, False otherwise. -# """ - - -# class LocalArtifactRepository(ArtifactRepository): -# """Store artifacts in the local filesystem.""" - -# def __init__(self, base_path: Path | str): -# self.base_path = Path(base_path) -# self.base_path.mkdir(parents=True, exist_ok=True) - -# def _get_run_artifact_dir(self, run_id: str) -> Path: -# """Get the artifact directory for a run.""" -# run_dir = self.base_path / run_id / "artifacts" -# run_dir.mkdir(parents=True, exist_ok=True) -# return run_dir - -# def log_artifact(self, run_id: str, artifact: Artifact) -> None: -# """Save an artifact to the repository.""" -# artifact_dir = self._get_run_artifact_dir(run_id) - -# # Handle artifact path with possible subdirectories -# if artifact.path: -# dest_dir = artifact_dir / os.path.dirname(artifact.path) -# dest_dir.mkdir(parents=True, exist_ok=True) -# dest_path = artifact_dir / artifact.path -# else: -# dest_path = artifact_dir / os.path.basename(str(artifact.local_path or "artifact")) - -# # Save the artifact -# if artifact.local_path: -# if artifact.local_path.is_dir(): -# self.log_artifacts(run_id, artifact.local_path, artifact.path) -# else: -# with open(artifact.local_path, "rb") as src, open(dest_path, "wb") as dst: -# dst.write(src.read()) -# elif artifact.content: -# with open(dest_path, "wb") as f: -# f.write(artifact.content) -# else: -# raise ValueError("Artifact has no content to save") - -# def log_artifacts(self, run_id: str, local_dir: Path, artifact_path: str | None = None) -> None: -# """Save a directory of artifacts.""" -# artifact_dir = self._get_run_artifact_dir(run_id) - -# if artifact_path: -# dest_dir = artifact_dir / artifact_path -# else: -# dest_dir = artifact_dir - -# dest_dir.mkdir(parents=True, exist_ok=True) - -# for root, _, files in os.walk(local_dir): -# for file in files: -# src_path = Path(root) / file -# rel_path = src_path.relative_to(local_dir) -# dst_path = dest_dir / rel_path - -# dst_path.parent.mkdir(parents=True, exist_ok=True) -# with open(src_path, "rb") as src, open(dst_path, "wb") as dst: -# dst.write(src.read()) - -# def list_artifacts(self, run_id: str, path: str | None = None) -> list[ArtifactInfo]: -# """List artifacts for a run.""" -# artifact_dir = self._get_run_artifact_dir(run_id) - -# if path: -# list_dir = artifact_dir / path -# else: -# list_dir = artifact_dir - -# if not list_dir.exists(): -# return [] - -# artifact_infos = [] -# for item in list_dir.iterdir(): -# rel_path = item.relative_to(artifact_dir) -# info = ArtifactInfo( -# path=str(rel_path), -# size=item.stat().st_size if item.is_file() else 0, -# is_dir=item.is_dir(), -# ) -# artifact_infos.append(info) - -# return artifact_infos - -# def download_artifact(self, run_id: str, path: str) -> Path: -# """Download an artifact (local copy for consistency with remote repos).""" -# artifact_path = self._get_run_artifact_dir(run_id) / path -# return artifact_path - -# def get_artifact_uri(self, run_id: str, path: str | None = None) -> str: -# """Get the URI for an artifact.""" -# if path: -# return f"file://{self._get_run_artifact_dir(run_id) / path}" -# else: -# return f"file://{self._get_run_artifact_dir(run_id)}" diff --git a/dreadnode/serialization.py b/dreadnode/serialization.py index fffcf8a6..9709a60f 100644 --- a/dreadnode/serialization.py +++ b/dreadnode/serialization.py @@ -2,7 +2,9 @@ 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 @@ -479,6 +481,19 @@ def _get_handlers() -> dict[type, HandlerFunc]: 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 @@ -499,6 +514,9 @@ def _get_handlers() -> dict[type, HandlerFunc]: return handlers +# Core functions + + def _serialize(obj: t.Any, seen: set[int] | None = None) -> tuple[JsonValue, JsonDict]: # noqa: PLR0911 # Primitives early @@ -556,7 +574,8 @@ def _serialize(obj: t.Any, seen: set[int] | None = None) -> tuple[JsonValue, Jso if hasattr(obj, "asdict"): # e.g., namedtuple return _serialize(obj.asdict(), seen) - # 10. Final Fallback: Use safe_repr + # Fallback to repr + return safe_repr(obj), { "type": "string", "title": obj_type.__name__, @@ -564,10 +583,15 @@ def _serialize(obj: t.Any, seen: set[int] | None = None) -> tuple[JsonValue, Jso } -# --- Public API Function --- +@dataclasses.dataclass +class Serialized: + data: str + data_hash: str + schema: JsonDict + schema_hash: str -def serialize(obj: t.Any) -> tuple[JsonValue, JsonDict]: +def serialize(obj: t.Any) -> Serialized: """ Serializes a Python object into a JSON-compatible structure and generates a corresponding JSON Schema, ensuring consistency between @@ -577,9 +601,16 @@ def serialize(obj: t.Any) -> tuple[JsonValue, JsonDict]: obj: The Python object to process. Returns: - A tuple containing: - - The serialized JSON-compatible representation of the object. - - A JSON Schema describing the structure and format of the - serialized representation. + An object containing the serialized data, schema, and their hashes. """ - return _serialize(obj) + serialized, schema = _serialize(obj) + serialized_str = json.dumps(serialized, separators=(",", ":")) + schema_str = json.dumps(schema, separators=(",", ":")) + data_hash = hashlib.sha1(serialized_str.encode()).hexdigest()[:16] # noqa: S324 (using sha1 for speed) + schema_hash = hashlib.sha1(schema_str.encode()).hexdigest()[:16] # noqa: S324 + return Serialized( + data=serialized_str, + schema=schema, + data_hash=data_hash, + schema_hash=schema_hash, + ) diff --git a/dreadnode/task.py b/dreadnode/task.py index 7faea1a4..4ca99fe2 100644 --- a/dreadnode/task.py +++ b/dreadnode/task.py @@ -62,7 +62,7 @@ class Task(t.Generic[P, R]): tracer: Tracer name: str - kind: str + label: str attributes: dict[str, t.Any] func: t.Callable[P, R] scorers: list[Scorer[R]] @@ -86,7 +86,7 @@ def clone(self) -> "Task[P, R]": return Task( tracer=self.tracer, name=self.name, - kind=self.kind, + label=self.label, attributes=self.attributes.copy(), func=self.func, scorers=self.scorers.copy(), @@ -99,13 +99,13 @@ def with_( scorers: t.Sequence[Scorer[R] | ScorerCallable[R]] | None = None, name: str | None = None, tags: t.Sequence[str] | None = None, - kind: str | None = None, + label: str | None = None, append: bool = False, **attributes: t.Any, ) -> "Task[P, R]": task = self.clone() task.name = name or task.name - task.kind = kind or task.kind + task.label = label or task.label new_scorers = [Scorer.from_callable(self.tracer, scorer) for scorer in (scorers or [])] new_tags = list(tags or []) @@ -141,7 +141,7 @@ async def run(self, *args: P.args, **kwargs: P.kwargs) -> TaskSpan[R]: with TaskSpan[R]( name=self.name, - kind=self.kind, + label=self.label, attributes=self.attributes, params=params, tags=self.tags, @@ -149,7 +149,7 @@ async def run(self, *args: P.args, **kwargs: P.kwargs) -> TaskSpan[R]: tracer=self.tracer, ) as span: for name, value in inputs.items(): - span.log_input(name, value, kind=f"{self.kind}.input.{name}") + span.log_input(name, value, label=f"{self.label}.input.{name}") output = t.cast(R | t.Awaitable[R], self.func(*args, **kwargs)) if inspect.isawaitable(output): @@ -158,7 +158,7 @@ async def run(self, *args: P.args, **kwargs: P.kwargs) -> TaskSpan[R]: span.output = output if self.log_output: - span.log_output("output", output, kind=f"{self.kind}.output") + span.log_output("output", output, label=f"{self.label}.output") for scorer in self.scorers: metric = await scorer(span.output) @@ -190,7 +190,7 @@ async def try_run(self, *args: P.args, **kwargs: P.kwargs) -> TaskSpan[R] | None return await self.run(*args, **kwargs) except Exception: # noqa: BLE001 warn_at_user_stacklevel( - f"Task '{self.name}' ({self.kind}) failed:\n{traceback.format_exc()}", + f"Task '{self.name}' ({self.label}) failed:\n{traceback.format_exc()}", TaskFailedWarning, ) return None diff --git a/dreadnode/tracing/constants.py b/dreadnode/tracing/constants.py index b54cc5ec..8f5dc281 100644 --- a/dreadnode/tracing/constants.py +++ b/dreadnode/tracing/constants.py @@ -7,15 +7,15 @@ SPAN_ATTRIBUTE_VERSION = f"{SPAN_NAMESPACE}.version" SPAN_ATTRIBUTE_TYPE = f"{SPAN_NAMESPACE}.type" SPAN_ATTRIBUTE_SCHEMA = f"{SPAN_NAMESPACE}.schema" -SPAN_ATTRIBUTE_KIND = f"{SPAN_NAMESPACE}.kind" +SPAN_ATTRIBUTE_LABEL = f"{SPAN_NAMESPACE}.label" SPAN_ATTRIBUTE_TAGS_ = f"{SPAN_NAMESPACE}.tags" -SPAN_ATTRIBUTE_KIND = f"{SPAN_NAMESPACE}.kind" 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" @@ -27,7 +27,7 @@ EVENT_NAME_OBJECT_METRIC = f"{SPAN_NAMESPACE}.object.metric" EVENT_NAME_OBJECT_LINK = f"{SPAN_NAMESPACE}.object.link" -EVENT_ATTRIBUTE_OBJECT_KIND = f"{SPAN_NAMESPACE}.object.kind" +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" diff --git a/dreadnode/tracing/span.py b/dreadnode/tracing/span.py index c4db8eae..5c6f55bd 100644 --- a/dreadnode/tracing/span.py +++ b/dreadnode/tracing/span.py @@ -22,14 +22,15 @@ from ulid import ULID from dreadnode.metric import Metric, MetricDict -from dreadnode.object import ObjectRef, universal_hash -from dreadnode.types import AnyDict, JsonDict, JsonValue +from dreadnode.object import Object, ObjectRef, ObjectVal +from dreadnode.serialization import 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_KIND, + EVENT_ATTRIBUTE_OBJECT_LABEL, EVENT_ATTRIBUTE_ORIGIN_SPAN_ID, EVENT_NAME_OBJECT, EVENT_NAME_OBJECT_INPUT, @@ -38,9 +39,10 @@ EVENT_NAME_OBJECT_OUTPUT, METRIC_ATTRIBUTE_SOURCE_HASH, SPAN_ATTRIBUTE_INPUTS, - SPAN_ATTRIBUTE_KIND, + SPAN_ATTRIBUTE_LABEL, SPAN_ATTRIBUTE_LARGE_ATTRIBUTES, SPAN_ATTRIBUTE_METRICS, + SPAN_ATTRIBUTE_OBJECT_SCHEMAS, SPAN_ATTRIBUTE_OBJECTS, SPAN_ATTRIBUTE_OUTPUTS, SPAN_ATTRIBUTE_PARAMS, @@ -71,15 +73,16 @@ def __init__( attributes: AnyDict, tracer: Tracer, *, - kind: str | None = None, + 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_KIND: kind or "", + SPAN_ATTRIBUTE_LABEL: self._label, SPAN_ATTRIBUTE_TAGS_: uniquify_sequence(tags or ()), **attributes, } @@ -125,7 +128,10 @@ def __exit__( if not self._span.is_recording(): return - self._span.set_attribute(SPAN_ATTRIBUTE_SCHEMA, attributes_json_schema(self._schema)) + 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] @@ -156,10 +162,18 @@ def tags(self) -> tuple[str, ...]: 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) -> None: + def set_attribute( + self, + key: str, + value: t.Any, + *, + schema: bool = True, + raw: bool = False, + ) -> None: self._added_attributes = True - self._schema[key] = create_json_schema(value, set()) - otel_value = self._pre_attributes[key] = prepare_otlp_attribute(value) + 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 @@ -228,7 +242,8 @@ def __init__( ) -> None: self._params = params or {} self._metrics = metrics or {} - self._objects: AnyDict = {} + self._objects: dict[str, Object] = {} + self._object_schemas: dict[str, JsonDict] = {} self._inputs: list[ObjectRef] = [] self._outputs: list[ObjectRef] = [] self.project = project @@ -261,13 +276,18 @@ def __exit__( traceback: types.TracebackType | None, ) -> None: self.set_attribute(SPAN_ATTRIBUTE_PARAMS, self._params) - self.set_attribute(SPAN_ATTRIBUTE_INPUTS, self._inputs) - self.set_attribute(SPAN_ATTRIBUTE_METRICS, self._metrics) - self.set_attribute(SPAN_ATTRIBUTE_OBJECTS, self._objects) - self.set_attribute(SPAN_ATTRIBUTE_OUTPUTS, self._outputs) + 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_OUTPUTS, self._outputs, schema=False) # Mark our objects attribute as large so it's stored separately - self.set_attribute(SPAN_ATTRIBUTE_LARGE_ATTRIBUTES, [SPAN_ATTRIBUTE_OBJECTS]) + 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: @@ -307,23 +327,38 @@ def log_object( self, value: t.Any, *, - kind: str | None = None, + label: str | None = None, event_name: str = EVENT_NAME_OBJECT, **attributes: JsonValue, ) -> str: - hash_ = universal_hash(value) - self._objects[hash_] = value + serialized = serialize(value) + object_ = ObjectVal( + hash=serialized.data_hash, + value=serialized.data, + schema_hash=serialized.schema_hash, + ) + + # check size and offload to s3 if needed + # if len(serialized.data) > 1 * 1024 * 1024: + # pass + + if serialized.data_hash not in self._objects: + self._objects[serialized.data_hash] = object_ + + if serialized.schema_hash not in self._object_schemas: + self._object_schemas[serialized.schema_hash] = serialized.schema + attributes = { **attributes, - EVENT_ATTRIBUTE_OBJECT_HASH: hash_, + 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 kind is not None: - attributes[EVENT_ATTRIBUTE_OBJECT_KIND] = kind + if label is not None: + attributes[EVENT_ATTRIBUTE_OBJECT_LABEL] = label self.log_event(name=event_name, attributes=attributes) - return hash_ + return object_.hash def get_object(self, hash_: str) -> t.Any: return self._objects[hash_] @@ -366,12 +401,17 @@ def log_input( name: str, value: t.Any, *, - kind: str | None = None, + label: str | None = None, **attributes: JsonValue, ) -> None: - kind = kind or re.sub(r"\W+", "_", name.lower()) - hash_ = self.log_object(value, kind=kind, event_name=EVENT_NAME_OBJECT_INPUT, **attributes) - self._inputs.append(ObjectRef(name, kind=kind, hash=hash_)) + 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_)) @property def metrics(self) -> MetricDict: @@ -386,7 +426,8 @@ def log_metric( step: int = 0, origin: t.Any | None = None, timestamp: datetime | None = None, - ) -> None: ... + ) -> None: + ... @t.overload def log_metric( @@ -395,7 +436,8 @@ def log_metric( value: Metric, *, origin: t.Any | None = None, - ) -> None: ... + ) -> None: + ... def log_metric( self, @@ -415,7 +457,7 @@ def log_metric( if origin is not None: origin_hash = self.log_object( origin, - kind=key, + label=key, event_name=EVENT_NAME_OBJECT_METRIC, ) metric.attributes[METRIC_ATTRIBUTE_SOURCE_HASH] = origin_hash @@ -433,12 +475,17 @@ def log_output( name: str, value: t.Any, *, - kind: str | None = None, + label: str | None = None, **attributes: JsonValue, ) -> None: - kind = kind or re.sub(r"\W+", "_", name.lower()) - hash_ = self.log_object(value, kind=kind, event_name=EVENT_NAME_OBJECT_OUTPUT, **attributes) - self._outputs.append(ObjectRef(name, kind=kind, hash=hash_)) + 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]): @@ -449,7 +496,7 @@ def __init__( run_id: str, tracer: Tracer, *, - kind: str | None = None, + label: str | None = None, params: AnyDict | None = None, metrics: MetricDict | None = None, tags: t.Sequence[str] | None = None, @@ -459,7 +506,7 @@ def __init__( self._inputs: list[ObjectRef] = [] self._outputs: list[ObjectRef] = [] - self._output: R | None = None # For the python output + self._output: R | Unset = UNSET # For the python output self._context_token: Token[TaskSpan[t.Any] | None] | None = None # contextvars context @@ -471,7 +518,7 @@ def __init__( SPAN_ATTRIBUTE_OUTPUTS: self._outputs, **attributes, } - super().__init__(name, attributes, tracer, type="task", kind=kind, tags=tags) + super().__init__(name, attributes, tracer, type="task", label=label, tags=tags) def __enter__(self) -> te.Self: self._parent_task = current_task_span.get() @@ -492,9 +539,9 @@ def __exit__( traceback: types.TracebackType | None, ) -> None: self.set_attribute(SPAN_ATTRIBUTE_PARAMS, self._params) - self.set_attribute(SPAN_ATTRIBUTE_INPUTS, self._inputs) - self.set_attribute(SPAN_ATTRIBUTE_METRICS, self._metrics) - self.set_attribute(SPAN_ATTRIBUTE_OUTPUTS, self._outputs) + 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) @@ -519,8 +566,8 @@ def outputs(self) -> AnyDict: @property def output(self) -> R: - if self._output is None: - raise ValueError("Task output is not set") + if isinstance(self._output, Unset): + raise TypeError("Task output is not set") return self._output @output.setter @@ -532,17 +579,17 @@ def log_output( name: str, value: t.Any, *, - kind: str | None = None, + label: str | None = None, **attributes: JsonValue, ) -> None: - kind = kind or re.sub(r"\W+", "_", name.lower()) + label = label or re.sub(r"\W+", "_", name.lower()) hash_ = self.run.log_object( value, - kind=kind, + label=label, event_name=EVENT_NAME_OBJECT_OUTPUT, **attributes, ) - self._outputs.append(ObjectRef(name, kind=kind, hash=hash_)) + self._outputs.append(ObjectRef(name, label=label, hash=hash_)) @property def params(self) -> AnyDict: @@ -563,17 +610,17 @@ def log_input( name: str, value: t.Any, *, - kind: str | None = None, + label: str | None = None, **attributes: JsonValue, ) -> None: - kind = kind or re.sub(r"\W+", "_", name.lower()) + label = label or re.sub(r"\W+", "_", name.lower()) hash_ = self.run.log_object( value, - kind=kind, + label=label, event_name=EVENT_NAME_OBJECT_INPUT, **attributes, ) - self._inputs.append(ObjectRef(name, kind=kind, hash=hash_)) + self._inputs.append(ObjectRef(name, label=label, hash=hash_)) @property def metrics(self) -> dict[str, list[Metric]]: @@ -588,7 +635,8 @@ def log_metric( step: int = 0, origin: t.Any | None = None, timestamp: datetime | None = None, - ) -> None: ... + ) -> None: + ... @t.overload def log_metric( @@ -597,7 +645,8 @@ def log_metric( value: Metric, *, origin: t.Any | None = None, - ) -> None: ... + ) -> None: + ... def log_metric( self, @@ -617,7 +666,7 @@ def log_metric( if origin is not None: origin_hash = self.run.log_object( origin, - kind=key, + label=key, event_name=EVENT_NAME_OBJECT_METRIC, ) metric.attributes[METRIC_ATTRIBUTE_SOURCE_HASH] = origin_hash @@ -625,11 +674,11 @@ def log_metric( self._metrics.setdefault(key, []).append(metric) # For every metric we log, also log it to the run - # with our `kind` as a prefix. + # 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.kind}.{key}", metric) + run.log_metric(f"{self._label}.{key}", metric) def get_average_metric_value(self, key: str | None = None) -> float: metrics = ( diff --git a/dreadnode/types.py b/dreadnode/types.py index 0a8377f0..030b3d35 100644 --- a/dreadnode/types.py +++ b/dreadnode/types.py @@ -15,3 +15,11 @@ JsonDict = dict[str, JsonValue] AnyDict = dict[str, t.Any] + + +class Unset: + def __bool__(self) -> t.Literal[False]: + return False + + +UNSET: Unset = Unset() From a05a672bad3022c5a72af4b41b66597591240dd3 Mon Sep 17 00:00:00 2001 From: Raja Sekhar Rao Dheekonda Date: Thu, 10 Apr 2025 14:53:08 -0700 Subject: [PATCH 08/24] Add logic to write the objects to s3 --- dreadnode/constants.py | 3 ++ dreadnode/main.py | 14 ++++++++- dreadnode/tracing/span.py | 64 ++++++++++++++++++++++++++++----------- 3 files changed, 63 insertions(+), 18 deletions(-) diff --git a/dreadnode/constants.py b/dreadnode/constants.py index e8936aa5..8b2b2f1f 100644 --- a/dreadnode/constants.py +++ b/dreadnode/constants.py @@ -11,3 +11,6 @@ DEFAULT_SERVER_URL = "https://platform.dreadnode.io" DEFAULT_LOCAL_OBJECT_DIR = ".dreadnode/objects" + +# Default values for the S3 storage +MAX_INLINE_OBJECT_BYTES = 1 * 1024 * 1024 # 1MB diff --git a/dreadnode/main.py b/dreadnode/main.py index 341cc57c..5d227adb 100644 --- a/dreadnode/main.py +++ b/dreadnode/main.py @@ -19,6 +19,7 @@ from opentelemetry.exporter.otlp.proto.http import Compression from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter from opentelemetry.sdk.trace.export import BatchSpanProcessor +from s3fs import S3FileSystem # type: ignore [import-untyped] from dreadnode.api.client import ApiClient from dreadnode.constants import ( @@ -393,7 +394,16 @@ def run( if name is None: name = f"{coolname.generate_slug(2)}-{random.randint(100, 999)}" # noqa: S311 - + api_client = self.api() + credentials = api_client.get_user_data_credentials() + s3_file_system = S3FileSystem( + key=credentials.access_key_id, + secret=credentials.secret_access_key, + token=credentials.session_token, + endpoint_url=credentials.endpoint, + client_kwargs={"region_name": credentials.region} + ) + prefix_path = f"{credentials.bucket}/{credentials.prefix}/" return RunSpan( name=name, project=project or self.project or "default", @@ -401,6 +411,8 @@ def run( tracer=self._get_tracer(), params=params, tags=tags, + file_system=s3_file_system, + prefix_path=prefix_path, ) def push_update(self) -> None: diff --git a/dreadnode/tracing/span.py b/dreadnode/tracing/span.py index 5c6f55bd..6691bdec 100644 --- a/dreadnode/tracing/span.py +++ b/dreadnode/tracing/span.py @@ -6,6 +6,7 @@ from datetime import datetime, timezone 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, @@ -21,9 +22,10 @@ from opentelemetry.util import types as otel_types from ulid import ULID +from dreadnode.constants import MAX_INLINE_OBJECT_BYTES from dreadnode.metric import Metric, MetricDict -from dreadnode.object import Object, ObjectRef, ObjectVal -from dreadnode.serialization import serialize +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 @@ -235,6 +237,8 @@ def __init__( 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, @@ -252,6 +256,8 @@ def __init__( 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()), @@ -332,23 +338,21 @@ def log_object( **attributes: JsonValue, ) -> str: serialized = serialize(value) - object_ = ObjectVal( - hash=serialized.data_hash, - value=serialized.data, - schema_hash=serialized.schema_hash, - ) + data_hash = serialized.data_hash + schema_hash = serialized.schema_hash - # check size and offload to s3 if needed - # if len(serialized.data) > 1 * 1024 * 1024: - # pass + # Store object if we haven't already + if data_hash not in self._objects: + self._objects[data_hash] = self._create_object(serialized) - if serialized.data_hash not in self._objects: - self._objects[serialized.data_hash] = object_ + object_ = self._objects[data_hash] - if serialized.schema_hash not in self._object_schemas: - self._object_schemas[serialized.schema_hash] = serialized.schema + # Store schema if new + if schema_hash not in self._object_schemas: + self._object_schemas[schema_hash] = serialized.schema - attributes = { + # Build event attributes + event_attributes = { **attributes, EVENT_ATTRIBUTE_OBJECT_HASH: object_.hash, EVENT_ATTRIBUTE_ORIGIN_SPAN_ID: trace_api.format_span_id( @@ -356,10 +360,36 @@ def log_object( ), } if label is not None: - attributes[EVENT_ATTRIBUTE_OBJECT_LABEL] = label - self.log_event(name=event_name, attributes=attributes) + event_attributes[EVENT_ATTRIBUTE_OBJECT_LABEL] = label + + self.log_event(name=event_name, attributes=event_attributes) return object_.hash + def _create_object(self, serialized: Serialized) -> Object: + """Create an ObjectVal or ObjectUri depending on size.""" + data = serialized.data + data_hash = serialized.data_hash + schema_hash = serialized.schema_hash + + if len(data) <= 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}" + with self._file_system.open(full_path, "w") as f: + f.write(data) + + return ObjectUri( + hash=data_hash, + uri=self._file_system.unstrip_protocol(full_path), + schema_hash=schema_hash, + size=len(data), + ) + def get_object(self, hash_: str) -> t.Any: return self._objects[hash_] From 90f7e44fb76f23bac33e53be4f7afb2b155bdb80 Mon Sep 17 00:00:00 2001 From: monoxgas Date: Tue, 15 Apr 2025 13:50:04 -0600 Subject: [PATCH 09/24] Fixes for v1 compat API changes --- dreadnode/api/client.py | 7 +- dreadnode/api/models.py | 64 ++- dreadnode/main.py | 37 +- poetry.lock | 972 ++++++++++++++++++++-------------------- 4 files changed, 577 insertions(+), 503 deletions(-) diff --git a/dreadnode/api/client.py b/dreadnode/api/client.py index 431df624..518d17e4 100644 --- a/dreadnode/api/client.py +++ b/dreadnode/api/client.py @@ -6,8 +6,8 @@ import httpx import pandas as pd from pydantic import BaseModel -from ulid import ULID from rich import print as rich_print +from ulid import ULID from dreadnode.version import VERSION @@ -114,6 +114,11 @@ def request( 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()] diff --git a/dreadnode/api/models.py b/dreadnode/api/models.py index 3f411745..0c11913a 100644 --- a/dreadnode/api/models.py +++ b/dreadnode/api/models.py @@ -7,6 +7,21 @@ 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 @@ -84,6 +99,36 @@ class Metric(BaseModel): 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 @@ -94,12 +139,13 @@ class Run(BaseModel): status: SpanStatus exception: SpanException | None tags: set[str] - params: dict[str, t.Any] + params: AnyDict metrics: dict[str, list[Metric]] - inputs: AnyDict - outputs: AnyDict - objects: AnyDict - schema_: dict[str, t.Any] = Field(alias="schema") + inputs: list[ObjectRef] + outputs: list[ObjectRef] + objects: dict[str, Object] + object_schemas: AnyDict + schema_: AnyDict = Field(alias="schema") class Task(BaseModel): @@ -113,11 +159,11 @@ class Task(BaseModel): status: SpanStatus exception: SpanException | None tags: set[str] - params: dict[str, t.Any] + params: AnyDict metrics: dict[str, list[Metric]] - inputs: AnyDict - outputs: AnyDict - schema_: dict[str, t.Any] = Field(alias="schema") + 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] diff --git a/dreadnode/main.py b/dreadnode/main.py index 5d227adb..2a3cde2b 100644 --- a/dreadnode/main.py +++ b/dreadnode/main.py @@ -13,6 +13,7 @@ import coolname # type: ignore [import-untyped] import logfire +from fsspec.implementations.local import LocalFileSystem # type: ignore [import-untyped] 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 @@ -45,6 +46,7 @@ from .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 @@ -101,6 +103,9 @@ 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( @@ -164,6 +169,13 @@ def initialize(self) -> None: if self.token is not None: self._api = ApiClient(self.server, self.token) + try: + self._api.list_projects() + except Exception as e: # noqa: BLE001 + 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( @@ -189,6 +201,16 @@ def initialize(self) -> None: # ) # ) + credentials = self._api.get_user_data_credentials() + self._fs = S3FileSystem( + key=credentials.access_key_id, + secret=credentials.secret_access_key, + token=credentials.session_token, + endpoint_url=credentials.endpoint, + client_kwargs={"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, @@ -394,16 +416,7 @@ def run( if name is None: name = f"{coolname.generate_slug(2)}-{random.randint(100, 999)}" # noqa: S311 - api_client = self.api() - credentials = api_client.get_user_data_credentials() - s3_file_system = S3FileSystem( - key=credentials.access_key_id, - secret=credentials.secret_access_key, - token=credentials.session_token, - endpoint_url=credentials.endpoint, - client_kwargs={"region_name": credentials.region} - ) - prefix_path = f"{credentials.bucket}/{credentials.prefix}/" + return RunSpan( name=name, project=project or self.project or "default", @@ -411,8 +424,8 @@ def run( tracer=self._get_tracer(), params=params, tags=tags, - file_system=s3_file_system, - prefix_path=prefix_path, + file_system=self._fs, + prefix_path=self._fs_prefix, ) def push_update(self) -> None: diff --git a/poetry.lock b/poetry.lock index 47b034c0..00cf7db0 100644 --- a/poetry.lock +++ b/poetry.lock @@ -39,93 +39,93 @@ files = [ [[package]] name = "aiohttp" -version = "3.11.14" +version = "3.11.16" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "aiohttp-3.11.14-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e2bc827c01f75803de77b134afdbf74fa74b62970eafdf190f3244931d7a5c0d"}, - {file = "aiohttp-3.11.14-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e365034c5cf6cf74f57420b57682ea79e19eb29033399dd3f40de4d0171998fa"}, - {file = "aiohttp-3.11.14-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c32593ead1a8c6aabd58f9d7ee706e48beac796bb0cb71d6b60f2c1056f0a65f"}, - {file = "aiohttp-3.11.14-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4e7c7ec4146a94a307ca4f112802a8e26d969018fabed526efc340d21d3e7d0"}, - {file = "aiohttp-3.11.14-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c8b2df9feac55043759aa89f722a967d977d80f8b5865a4153fc41c93b957efc"}, - {file = "aiohttp-3.11.14-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c7571f99525c76a6280f5fe8e194eeb8cb4da55586c3c61c59c33a33f10cfce7"}, - {file = "aiohttp-3.11.14-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b59d096b5537ec7c85954cb97d821aae35cfccce3357a2cafe85660cc6295628"}, - {file = "aiohttp-3.11.14-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b42dbd097abb44b3f1156b4bf978ec5853840802d6eee2784857be11ee82c6a0"}, - {file = "aiohttp-3.11.14-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b05774864c87210c531b48dfeb2f7659407c2dda8643104fb4ae5e2c311d12d9"}, - {file = "aiohttp-3.11.14-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:4e2e8ef37d4bc110917d038807ee3af82700a93ab2ba5687afae5271b8bc50ff"}, - {file = "aiohttp-3.11.14-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e9faafa74dbb906b2b6f3eb9942352e9e9db8d583ffed4be618a89bd71a4e914"}, - {file = "aiohttp-3.11.14-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:7e7abe865504f41b10777ac162c727af14e9f4db9262e3ed8254179053f63e6d"}, - {file = "aiohttp-3.11.14-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:4848ae31ad44330b30f16c71e4f586cd5402a846b11264c412de99fa768f00f3"}, - {file = "aiohttp-3.11.14-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2d0b46abee5b5737cb479cc9139b29f010a37b1875ee56d142aefc10686a390b"}, - {file = "aiohttp-3.11.14-cp310-cp310-win32.whl", hash = "sha256:a0d2c04a623ab83963576548ce098baf711a18e2c32c542b62322a0b4584b990"}, - {file = "aiohttp-3.11.14-cp310-cp310-win_amd64.whl", hash = "sha256:5409a59d5057f2386bb8b8f8bbcfb6e15505cedd8b2445db510563b5d7ea1186"}, - {file = "aiohttp-3.11.14-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f296d637a50bb15fb6a229fbb0eb053080e703b53dbfe55b1e4bb1c5ed25d325"}, - {file = "aiohttp-3.11.14-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ec6cd1954ca2bbf0970f531a628da1b1338f594bf5da7e361e19ba163ecc4f3b"}, - {file = "aiohttp-3.11.14-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:572def4aad0a4775af66d5a2b5923c7de0820ecaeeb7987dcbccda2a735a993f"}, - {file = "aiohttp-3.11.14-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c68e41c4d576cd6aa6c6d2eddfb32b2acfb07ebfbb4f9da991da26633a3db1a"}, - {file = "aiohttp-3.11.14-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99b8bbfc8111826aa8363442c0fc1f5751456b008737ff053570f06a151650b3"}, - {file = "aiohttp-3.11.14-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b0a200e85da5c966277a402736a96457b882360aa15416bf104ca81e6f5807b"}, - {file = "aiohttp-3.11.14-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d173c0ac508a2175f7c9a115a50db5fd3e35190d96fdd1a17f9cb10a6ab09aa1"}, - {file = "aiohttp-3.11.14-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:413fe39fd929329f697f41ad67936f379cba06fcd4c462b62e5b0f8061ee4a77"}, - {file = "aiohttp-3.11.14-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:65c75b14ee74e8eeff2886321e76188cbe938d18c85cff349d948430179ad02c"}, - {file = "aiohttp-3.11.14-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:321238a42ed463848f06e291c4bbfb3d15ba5a79221a82c502da3e23d7525d06"}, - {file = "aiohttp-3.11.14-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:59a05cdc636431f7ce843c7c2f04772437dd816a5289f16440b19441be6511f1"}, - {file = "aiohttp-3.11.14-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:daf20d9c3b12ae0fdf15ed92235e190f8284945563c4b8ad95b2d7a31f331cd3"}, - {file = "aiohttp-3.11.14-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:05582cb2d156ac7506e68b5eac83179faedad74522ed88f88e5861b78740dc0e"}, - {file = "aiohttp-3.11.14-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:12c5869e7ddf6b4b1f2109702b3cd7515667b437da90a5a4a50ba1354fe41881"}, - {file = "aiohttp-3.11.14-cp311-cp311-win32.whl", hash = "sha256:92868f6512714efd4a6d6cb2bfc4903b997b36b97baea85f744229f18d12755e"}, - {file = "aiohttp-3.11.14-cp311-cp311-win_amd64.whl", hash = "sha256:bccd2cb7aa5a3bfada72681bdb91637094d81639e116eac368f8b3874620a654"}, - {file = "aiohttp-3.11.14-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:70ab0f61c1a73d3e0342cedd9a7321425c27a7067bebeeacd509f96695b875fc"}, - {file = "aiohttp-3.11.14-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:602d4db80daf4497de93cb1ce00b8fc79969c0a7cf5b67bec96fa939268d806a"}, - {file = "aiohttp-3.11.14-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3a8a0d127c10b8d89e69bbd3430da0f73946d839e65fec00ae48ca7916a31948"}, - {file = "aiohttp-3.11.14-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca9f835cdfedcb3f5947304e85b8ca3ace31eef6346d8027a97f4de5fb687534"}, - {file = "aiohttp-3.11.14-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8aa5c68e1e68fff7cd3142288101deb4316b51f03d50c92de6ea5ce646e6c71f"}, - {file = "aiohttp-3.11.14-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b512f1de1c688f88dbe1b8bb1283f7fbeb7a2b2b26e743bb2193cbadfa6f307"}, - {file = "aiohttp-3.11.14-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc9253069158d57e27d47a8453d8a2c5a370dc461374111b5184cf2f147a3cc3"}, - {file = "aiohttp-3.11.14-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b2501f1b981e70932b4a552fc9b3c942991c7ae429ea117e8fba57718cdeed0"}, - {file = "aiohttp-3.11.14-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:28a3d083819741592685762d51d789e6155411277050d08066537c5edc4066e6"}, - {file = "aiohttp-3.11.14-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:0df3788187559c262922846087e36228b75987f3ae31dd0a1e5ee1034090d42f"}, - {file = "aiohttp-3.11.14-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:9e73fa341d8b308bb799cf0ab6f55fc0461d27a9fa3e4582755a3d81a6af8c09"}, - {file = "aiohttp-3.11.14-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:51ba80d473eb780a329d73ac8afa44aa71dfb521693ccea1dea8b9b5c4df45ce"}, - {file = "aiohttp-3.11.14-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:8d1dd75aa4d855c7debaf1ef830ff2dfcc33f893c7db0af2423ee761ebffd22b"}, - {file = "aiohttp-3.11.14-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41cf0cefd9e7b5c646c2ef529c8335e7eafd326f444cc1cdb0c47b6bc836f9be"}, - {file = "aiohttp-3.11.14-cp312-cp312-win32.whl", hash = "sha256:948abc8952aff63de7b2c83bfe3f211c727da3a33c3a5866a0e2cf1ee1aa950f"}, - {file = "aiohttp-3.11.14-cp312-cp312-win_amd64.whl", hash = "sha256:3b420d076a46f41ea48e5fcccb996f517af0d406267e31e6716f480a3d50d65c"}, - {file = "aiohttp-3.11.14-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:8d14e274828561db91e4178f0057a915f3af1757b94c2ca283cb34cbb6e00b50"}, - {file = "aiohttp-3.11.14-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f30fc72daf85486cdcdfc3f5e0aea9255493ef499e31582b34abadbfaafb0965"}, - {file = "aiohttp-3.11.14-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4edcbe34e6dba0136e4cabf7568f5a434d89cc9de5d5155371acda275353d228"}, - {file = "aiohttp-3.11.14-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a7169ded15505f55a87f8f0812c94c9412623c744227b9e51083a72a48b68a5"}, - {file = "aiohttp-3.11.14-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad1f2fb9fe9b585ea4b436d6e998e71b50d2b087b694ab277b30e060c434e5db"}, - {file = "aiohttp-3.11.14-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:20412c7cc3720e47a47e63c0005f78c0c2370020f9f4770d7fc0075f397a9fb0"}, - {file = "aiohttp-3.11.14-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dd9766da617855f7e85f27d2bf9a565ace04ba7c387323cd3e651ac4329db91"}, - {file = "aiohttp-3.11.14-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:599b66582f7276ebefbaa38adf37585e636b6a7a73382eb412f7bc0fc55fb73d"}, - {file = "aiohttp-3.11.14-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b41693b7388324b80f9acfabd479bd1c84f0bc7e8f17bab4ecd9675e9ff9c734"}, - {file = "aiohttp-3.11.14-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:86135c32d06927339c8c5e64f96e4eee8825d928374b9b71a3c42379d7437058"}, - {file = "aiohttp-3.11.14-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:04eb541ce1e03edc1e3be1917a0f45ac703e913c21a940111df73a2c2db11d73"}, - {file = "aiohttp-3.11.14-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dc311634f6f28661a76cbc1c28ecf3b3a70a8edd67b69288ab7ca91058eb5a33"}, - {file = "aiohttp-3.11.14-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:69bb252bfdca385ccabfd55f4cd740d421dd8c8ad438ded9637d81c228d0da49"}, - {file = "aiohttp-3.11.14-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2b86efe23684b58a88e530c4ab5b20145f102916bbb2d82942cafec7bd36a647"}, - {file = "aiohttp-3.11.14-cp313-cp313-win32.whl", hash = "sha256:b9c60d1de973ca94af02053d9b5111c4fbf97158e139b14f1be68337be267be6"}, - {file = "aiohttp-3.11.14-cp313-cp313-win_amd64.whl", hash = "sha256:0a29be28e60e5610d2437b5b2fed61d6f3dcde898b57fb048aa5079271e7f6f3"}, - {file = "aiohttp-3.11.14-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:14fc03508359334edc76d35b2821832f092c8f092e4b356e74e38419dfe7b6de"}, - {file = "aiohttp-3.11.14-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:92007c89a8cb7be35befa2732b0b32bf3a394c1b22ef2dff0ef12537d98a7bda"}, - {file = "aiohttp-3.11.14-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6d3986112e34eaa36e280dc8286b9dd4cc1a5bcf328a7f147453e188f6fe148f"}, - {file = "aiohttp-3.11.14-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:749f1eb10e51dbbcdba9df2ef457ec060554842eea4d23874a3e26495f9e87b1"}, - {file = "aiohttp-3.11.14-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:781c8bd423dcc4641298c8c5a2a125c8b1c31e11f828e8d35c1d3a722af4c15a"}, - {file = "aiohttp-3.11.14-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:997b57e38aa7dc6caab843c5e042ab557bc83a2f91b7bd302e3c3aebbb9042a1"}, - {file = "aiohttp-3.11.14-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a8b0321e40a833e381d127be993b7349d1564b756910b28b5f6588a159afef3"}, - {file = "aiohttp-3.11.14-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8778620396e554b758b59773ab29c03b55047841d8894c5e335f12bfc45ebd28"}, - {file = "aiohttp-3.11.14-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e906da0f2bcbf9b26cc2b144929e88cb3bf943dd1942b4e5af066056875c7618"}, - {file = "aiohttp-3.11.14-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:87f0e003fb4dd5810c7fbf47a1239eaa34cd929ef160e0a54c570883125c4831"}, - {file = "aiohttp-3.11.14-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:7f2dadece8b85596ac3ab1ec04b00694bdd62abc31e5618f524648d18d9dd7fa"}, - {file = "aiohttp-3.11.14-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:fe846f0a98aa9913c2852b630cd39b4098f296e0907dd05f6c7b30d911afa4c3"}, - {file = "aiohttp-3.11.14-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ced66c5c6ad5bcaf9be54560398654779ec1c3695f1a9cf0ae5e3606694a000a"}, - {file = "aiohttp-3.11.14-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a40087b82f83bd671cbeb5f582c233d196e9653220404a798798bfc0ee189fff"}, - {file = "aiohttp-3.11.14-cp39-cp39-win32.whl", hash = "sha256:95d7787f2bcbf7cb46823036a8d64ccfbc2ffc7d52016b4044d901abceeba3db"}, - {file = "aiohttp-3.11.14-cp39-cp39-win_amd64.whl", hash = "sha256:22a8107896877212130c58f74e64b77f7007cb03cea8698be317272643602d45"}, - {file = "aiohttp-3.11.14.tar.gz", hash = "sha256:d6edc538c7480fa0a3b2bdd705f8010062d74700198da55d16498e1b49549b9c"}, + {file = "aiohttp-3.11.16-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb46bb0f24813e6cede6cc07b1961d4b04f331f7112a23b5e21f567da4ee50aa"}, + {file = "aiohttp-3.11.16-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:54eb3aead72a5c19fad07219acd882c1643a1027fbcdefac9b502c267242f955"}, + {file = "aiohttp-3.11.16-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:38bea84ee4fe24ebcc8edeb7b54bf20f06fd53ce4d2cc8b74344c5b9620597fd"}, + {file = "aiohttp-3.11.16-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0666afbe984f6933fe72cd1f1c3560d8c55880a0bdd728ad774006eb4241ecd"}, + {file = "aiohttp-3.11.16-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ba92a2d9ace559a0a14b03d87f47e021e4fa7681dc6970ebbc7b447c7d4b7cd"}, + {file = "aiohttp-3.11.16-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ad1d59fd7114e6a08c4814983bb498f391c699f3c78712770077518cae63ff7"}, + {file = "aiohttp-3.11.16-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b88a2bf26965f2015a771381624dd4b0839034b70d406dc74fd8be4cc053e3"}, + {file = "aiohttp-3.11.16-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:576f5ca28d1b3276026f7df3ec841ae460e0fc3aac2a47cbf72eabcfc0f102e1"}, + {file = "aiohttp-3.11.16-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a2a450bcce4931b295fc0848f384834c3f9b00edfc2150baafb4488c27953de6"}, + {file = "aiohttp-3.11.16-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:37dcee4906454ae377be5937ab2a66a9a88377b11dd7c072df7a7c142b63c37c"}, + {file = "aiohttp-3.11.16-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4d0c970c0d602b1017e2067ff3b7dac41c98fef4f7472ec2ea26fd8a4e8c2149"}, + {file = "aiohttp-3.11.16-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:004511d3413737700835e949433536a2fe95a7d0297edd911a1e9705c5b5ea43"}, + {file = "aiohttp-3.11.16-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:c15b2271c44da77ee9d822552201180779e5e942f3a71fb74e026bf6172ff287"}, + {file = "aiohttp-3.11.16-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ad9509ffb2396483ceacb1eee9134724443ee45b92141105a4645857244aecc8"}, + {file = "aiohttp-3.11.16-cp310-cp310-win32.whl", hash = "sha256:634d96869be6c4dc232fc503e03e40c42d32cfaa51712aee181e922e61d74814"}, + {file = "aiohttp-3.11.16-cp310-cp310-win_amd64.whl", hash = "sha256:938f756c2b9374bbcc262a37eea521d8a0e6458162f2a9c26329cc87fdf06534"}, + {file = "aiohttp-3.11.16-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8cb0688a8d81c63d716e867d59a9ccc389e97ac7037ebef904c2b89334407180"}, + {file = "aiohttp-3.11.16-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0ad1fb47da60ae1ddfb316f0ff16d1f3b8e844d1a1e154641928ea0583d486ed"}, + {file = "aiohttp-3.11.16-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:df7db76400bf46ec6a0a73192b14c8295bdb9812053f4fe53f4e789f3ea66bbb"}, + {file = "aiohttp-3.11.16-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc3a145479a76ad0ed646434d09216d33d08eef0d8c9a11f5ae5cdc37caa3540"}, + {file = "aiohttp-3.11.16-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d007aa39a52d62373bd23428ba4a2546eed0e7643d7bf2e41ddcefd54519842c"}, + {file = "aiohttp-3.11.16-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6ddd90d9fb4b501c97a4458f1c1720e42432c26cb76d28177c5b5ad4e332601"}, + {file = "aiohttp-3.11.16-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a2f451849e6b39e5c226803dcacfa9c7133e9825dcefd2f4e837a2ec5a3bb98"}, + {file = "aiohttp-3.11.16-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8df6612df74409080575dca38a5237282865408016e65636a76a2eb9348c2567"}, + {file = "aiohttp-3.11.16-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:78e6e23b954644737e385befa0deb20233e2dfddf95dd11e9db752bdd2a294d3"}, + {file = "aiohttp-3.11.16-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:696ef00e8a1f0cec5e30640e64eca75d8e777933d1438f4facc9c0cdf288a810"}, + {file = "aiohttp-3.11.16-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e3538bc9fe1b902bef51372462e3d7c96fce2b566642512138a480b7adc9d508"}, + {file = "aiohttp-3.11.16-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:3ab3367bb7f61ad18793fea2ef71f2d181c528c87948638366bf1de26e239183"}, + {file = "aiohttp-3.11.16-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:56a3443aca82abda0e07be2e1ecb76a050714faf2be84256dae291182ba59049"}, + {file = "aiohttp-3.11.16-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:61c721764e41af907c9d16b6daa05a458f066015abd35923051be8705108ed17"}, + {file = "aiohttp-3.11.16-cp311-cp311-win32.whl", hash = "sha256:3e061b09f6fa42997cf627307f220315e313ece74907d35776ec4373ed718b86"}, + {file = "aiohttp-3.11.16-cp311-cp311-win_amd64.whl", hash = "sha256:745f1ed5e2c687baefc3c5e7b4304e91bf3e2f32834d07baaee243e349624b24"}, + {file = "aiohttp-3.11.16-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:911a6e91d08bb2c72938bc17f0a2d97864c531536b7832abee6429d5296e5b27"}, + {file = "aiohttp-3.11.16-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6ac13b71761e49d5f9e4d05d33683bbafef753e876e8e5a7ef26e937dd766713"}, + {file = "aiohttp-3.11.16-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fd36c119c5d6551bce374fcb5c19269638f8d09862445f85a5a48596fd59f4bb"}, + {file = "aiohttp-3.11.16-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d489d9778522fbd0f8d6a5c6e48e3514f11be81cb0a5954bdda06f7e1594b321"}, + {file = "aiohttp-3.11.16-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69a2cbd61788d26f8f1e626e188044834f37f6ae3f937bd9f08b65fc9d7e514e"}, + {file = "aiohttp-3.11.16-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd464ba806e27ee24a91362ba3621bfc39dbbb8b79f2e1340201615197370f7c"}, + {file = "aiohttp-3.11.16-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ce63ae04719513dd2651202352a2beb9f67f55cb8490c40f056cea3c5c355ce"}, + {file = "aiohttp-3.11.16-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09b00dd520d88eac9d1768439a59ab3d145065c91a8fab97f900d1b5f802895e"}, + {file = "aiohttp-3.11.16-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7f6428fee52d2bcf96a8aa7b62095b190ee341ab0e6b1bcf50c615d7966fd45b"}, + {file = "aiohttp-3.11.16-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:13ceac2c5cdcc3f64b9015710221ddf81c900c5febc505dbd8f810e770011540"}, + {file = "aiohttp-3.11.16-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fadbb8f1d4140825069db3fedbbb843290fd5f5bc0a5dbd7eaf81d91bf1b003b"}, + {file = "aiohttp-3.11.16-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6a792ce34b999fbe04a7a71a90c74f10c57ae4c51f65461a411faa70e154154e"}, + {file = "aiohttp-3.11.16-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:f4065145bf69de124accdd17ea5f4dc770da0a6a6e440c53f6e0a8c27b3e635c"}, + {file = "aiohttp-3.11.16-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fa73e8c2656a3653ae6c307b3f4e878a21f87859a9afab228280ddccd7369d71"}, + {file = "aiohttp-3.11.16-cp312-cp312-win32.whl", hash = "sha256:f244b8e541f414664889e2c87cac11a07b918cb4b540c36f7ada7bfa76571ea2"}, + {file = "aiohttp-3.11.16-cp312-cp312-win_amd64.whl", hash = "sha256:23a15727fbfccab973343b6d1b7181bfb0b4aa7ae280f36fd2f90f5476805682"}, + {file = "aiohttp-3.11.16-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a3814760a1a700f3cfd2f977249f1032301d0a12c92aba74605cfa6ce9f78489"}, + {file = "aiohttp-3.11.16-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9b751a6306f330801665ae69270a8a3993654a85569b3469662efaad6cf5cc50"}, + {file = "aiohttp-3.11.16-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ad497f38a0d6c329cb621774788583ee12321863cd4bd9feee1effd60f2ad133"}, + {file = "aiohttp-3.11.16-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca37057625693d097543bd88076ceebeb248291df9d6ca8481349efc0b05dcd0"}, + {file = "aiohttp-3.11.16-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a5abcbba9f4b463a45c8ca8b7720891200658f6f46894f79517e6cd11f3405ca"}, + {file = "aiohttp-3.11.16-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f420bfe862fb357a6d76f2065447ef6f484bc489292ac91e29bc65d2d7a2c84d"}, + {file = "aiohttp-3.11.16-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58ede86453a6cf2d6ce40ef0ca15481677a66950e73b0a788917916f7e35a0bb"}, + {file = "aiohttp-3.11.16-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fdec0213244c39973674ca2a7f5435bf74369e7d4e104d6c7473c81c9bcc8c4"}, + {file = "aiohttp-3.11.16-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:72b1b03fb4655c1960403c131740755ec19c5898c82abd3961c364c2afd59fe7"}, + {file = "aiohttp-3.11.16-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:780df0d837276276226a1ff803f8d0fa5f8996c479aeef52eb040179f3156cbd"}, + {file = "aiohttp-3.11.16-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ecdb8173e6c7aa09eee342ac62e193e6904923bd232e76b4157ac0bfa670609f"}, + {file = "aiohttp-3.11.16-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a6db7458ab89c7d80bc1f4e930cc9df6edee2200127cfa6f6e080cf619eddfbd"}, + {file = "aiohttp-3.11.16-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2540ddc83cc724b13d1838026f6a5ad178510953302a49e6d647f6e1de82bc34"}, + {file = "aiohttp-3.11.16-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3b4e6db8dc4879015b9955778cfb9881897339c8fab7b3676f8433f849425913"}, + {file = "aiohttp-3.11.16-cp313-cp313-win32.whl", hash = "sha256:493910ceb2764f792db4dc6e8e4b375dae1b08f72e18e8f10f18b34ca17d0979"}, + {file = "aiohttp-3.11.16-cp313-cp313-win_amd64.whl", hash = "sha256:42864e70a248f5f6a49fdaf417d9bc62d6e4d8ee9695b24c5916cb4bb666c802"}, + {file = "aiohttp-3.11.16-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bbcba75fe879ad6fd2e0d6a8d937f34a571f116a0e4db37df8079e738ea95c71"}, + {file = "aiohttp-3.11.16-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:87a6e922b2b2401e0b0cf6b976b97f11ec7f136bfed445e16384fbf6fd5e8602"}, + {file = "aiohttp-3.11.16-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccf10f16ab498d20e28bc2b5c1306e9c1512f2840f7b6a67000a517a4b37d5ee"}, + {file = "aiohttp-3.11.16-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb3d0cc5cdb926090748ea60172fa8a213cec728bd6c54eae18b96040fcd6227"}, + {file = "aiohttp-3.11.16-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d07502cc14ecd64f52b2a74ebbc106893d9a9717120057ea9ea1fd6568a747e7"}, + {file = "aiohttp-3.11.16-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:776c8e959a01e5e8321f1dec77964cb6101020a69d5a94cd3d34db6d555e01f7"}, + {file = "aiohttp-3.11.16-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0902e887b0e1d50424112f200eb9ae3dfed6c0d0a19fc60f633ae5a57c809656"}, + {file = "aiohttp-3.11.16-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e87fd812899aa78252866ae03a048e77bd11b80fb4878ce27c23cade239b42b2"}, + {file = "aiohttp-3.11.16-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0a950c2eb8ff17361abd8c85987fd6076d9f47d040ebffce67dce4993285e973"}, + {file = "aiohttp-3.11.16-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:c10d85e81d0b9ef87970ecbdbfaeec14a361a7fa947118817fcea8e45335fa46"}, + {file = "aiohttp-3.11.16-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:7951decace76a9271a1ef181b04aa77d3cc309a02a51d73826039003210bdc86"}, + {file = "aiohttp-3.11.16-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:14461157d8426bcb40bd94deb0450a6fa16f05129f7da546090cebf8f3123b0f"}, + {file = "aiohttp-3.11.16-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9756d9b9d4547e091f99d554fbba0d2a920aab98caa82a8fb3d3d9bee3c9ae85"}, + {file = "aiohttp-3.11.16-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:87944bd16b7fe6160607f6a17808abd25f17f61ae1e26c47a491b970fb66d8cb"}, + {file = "aiohttp-3.11.16-cp39-cp39-win32.whl", hash = "sha256:92b7ee222e2b903e0a4b329a9943d432b3767f2d5029dbe4ca59fb75223bbe2e"}, + {file = "aiohttp-3.11.16-cp39-cp39-win_amd64.whl", hash = "sha256:17ae4664031aadfbcb34fd40ffd90976671fa0c0286e6c4113989f78bebab37a"}, + {file = "aiohttp-3.11.16.tar.gz", hash = "sha256:16f8a2c9538c14a557b4d309ed4d0a7c60f0253e8ed7b6c9a2859a7582f8b1b8"}, ] [package.dependencies] @@ -203,8 +203,8 @@ sniffio = ">=1.1" typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] -doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] -test = ["anyio[trio]", "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\""] +doc = ["Sphinx (>=8.2,<9.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] +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]] @@ -393,7 +393,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 = ["main", "dev"] -groups = ["dev"] markers = "sys_platform == \"win32\"" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, @@ -491,14 +490,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"] 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] @@ -610,14 +609,14 @@ files = [ [[package]] name = "fsspec" -version = "2025.3.0" +version = "2025.3.2" description = "File-system specification" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" groups = ["main"] files = [ - {file = "fsspec-2025.3.0-py3-none-any.whl", hash = "sha256:efb87af3efa9103f94ca91a7f8cb7a4df91af9f74fc106c9c7ea0efd7277c1b3"}, - {file = "fsspec-2025.3.0.tar.gz", hash = "sha256:a935fd1ea872591f2b5148907d103488fc523295e6c64b835cfad8c3eca44972"}, + {file = "fsspec-2025.3.2-py3-none-any.whl", hash = "sha256:2daf8dc3d1dfa65b6aa37748d112773a7a08416f6c70d96b264c96476ecaf711"}, + {file = "fsspec-2025.3.2.tar.gz", hash = "sha256:e52c77ef398680bbd6a98c0e628fbc469491282981209907bbc8aea76a04fdc6"}, ] [package.dependencies] @@ -653,14 +652,14 @@ 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] @@ -683,14 +682,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"] 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] @@ -730,14 +729,14 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "identify" -version = "2.6.7" +version = "2.6.9" 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.9-py2.py3-none-any.whl", hash = "sha256:c98b4322da415a8e5a70ff6e51fbc2d2932c015532d77e9f8537b4ba7813b150"}, + {file = "identify-2.6.9.tar.gz", hash = "sha256:d40dfe3142a1421d8518e3d3985ef5ac42890683e32306ad614a29490abeb6bf"}, ] [package.extras] @@ -779,19 +778,19 @@ cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] perf = ["ipython"] -test = ["flufl.flake8", "importlib-resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +test = ["flufl.flake8", "importlib_resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] 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.7" +python-versions = ">=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 = "iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760"}, + {file = "iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7"}, ] [[package]] @@ -808,21 +807,21 @@ files = [ [[package]] name = "logfire" -version = "3.12.0" +version = "3.14.0" description = "The best Python observability tool! 🪵🔥" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "logfire-3.12.0-py3-none-any.whl", hash = "sha256:d1812d96e5a88d3aa364947075af7a8bc2791e6e2474893fe02ce57b2a417a69"}, - {file = "logfire-3.12.0.tar.gz", hash = "sha256:1c8c21f33e94a63106c38bdf51c0bad554430d0581f1cfbb0e8bb1741a5bd43b"}, + {file = "logfire-3.14.0-py3-none-any.whl", hash = "sha256:4f95cf98a7c29cd7cd00e093ba75ce1e4e19e5069acda8b1577a4b7790e0237a"}, + {file = "logfire-3.14.0.tar.gz", hash = "sha256:afdd23386a8a57da7ab97938cc5eec17928ce9195907b85860d906f04c5d33e3"}, ] [package.dependencies] executing = ">=2.0.1" -opentelemetry-exporter-otlp-proto-http = ">=1.21.0,<1.32.0" +opentelemetry-exporter-otlp-proto-http = ">=1.21.0,<1.33.0" opentelemetry-instrumentation = ">=0.41b0" -opentelemetry-sdk = ">=1.21.0,<1.32.0" +opentelemetry-sdk = ">=1.21.0,<1.33.0" protobuf = ">=4.23.4" rich = ">=13.4.2" tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} @@ -908,104 +907,116 @@ files = [ [[package]] name = "multidict" -version = "6.2.0" +version = "6.4.3" description = "multidict implementation" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "multidict-6.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b9f6392d98c0bd70676ae41474e2eecf4c7150cb419237a41f8f96043fcb81d1"}, - {file = "multidict-6.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3501621d5e86f1a88521ea65d5cad0a0834c77b26f193747615b7c911e5422d2"}, - {file = "multidict-6.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:32ed748ff9ac682eae7859790d3044b50e3076c7d80e17a44239683769ff485e"}, - {file = "multidict-6.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc826b9a8176e686b67aa60fd6c6a7047b0461cae5591ea1dc73d28f72332a8a"}, - {file = "multidict-6.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:214207dcc7a6221d9942f23797fe89144128a71c03632bf713d918db99bd36de"}, - {file = "multidict-6.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:05fefbc3cddc4e36da209a5e49f1094bbece9a581faa7f3589201fd95df40e5d"}, - {file = "multidict-6.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e851e6363d0dbe515d8de81fd544a2c956fdec6f8a049739562286727d4a00c3"}, - {file = "multidict-6.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32c9b4878f48be3e75808ea7e499d6223b1eea6d54c487a66bc10a1871e3dc6a"}, - {file = "multidict-6.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7243c5a6523c5cfeca76e063efa5f6a656d1d74c8b1fc64b2cd1e84e507f7e2a"}, - {file = "multidict-6.2.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0e5a644e50ef9fb87878d4d57907f03a12410d2aa3b93b3acdf90a741df52c49"}, - {file = "multidict-6.2.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0dc25a3293c50744796e87048de5e68996104d86d940bb24bc3ec31df281b191"}, - {file = "multidict-6.2.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:a49994481b99cd7dedde07f2e7e93b1d86c01c0fca1c32aded18f10695ae17eb"}, - {file = "multidict-6.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:641cf2e3447c9ecff2f7aa6e9eee9eaa286ea65d57b014543a4911ff2799d08a"}, - {file = "multidict-6.2.0-cp310-cp310-win32.whl", hash = "sha256:0c383d28857f66f5aebe3e91d6cf498da73af75fbd51cedbe1adfb85e90c0460"}, - {file = "multidict-6.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:a33273a541f1e1a8219b2a4ed2de355848ecc0254264915b9290c8d2de1c74e1"}, - {file = "multidict-6.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:84e87a7d75fa36839a3a432286d719975362d230c70ebfa0948549cc38bd5b46"}, - {file = "multidict-6.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8de4d42dffd5ced9117af2ce66ba8722402541a3aa98ffdf78dde92badb68932"}, - {file = "multidict-6.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e7d91a230c7f8af86c904a5a992b8c064b66330544693fd6759c3d6162382ecf"}, - {file = "multidict-6.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f6cad071960ba1914fa231677d21b1b4a3acdcce463cee41ea30bc82e6040cf"}, - {file = "multidict-6.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f74f2fc51555f4b037ef278efc29a870d327053aba5cb7d86ae572426c7cccc"}, - {file = "multidict-6.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:14ed9ed1bfedd72a877807c71113deac292bf485159a29025dfdc524c326f3e1"}, - {file = "multidict-6.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ac3fcf9a2d369bd075b2c2965544036a27ccd277fc3c04f708338cc57533081"}, - {file = "multidict-6.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fc6af8e39f7496047c7876314f4317736eac82bf85b54c7c76cf1a6f8e35d98"}, - {file = "multidict-6.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5f8cb1329f42fadfb40d6211e5ff568d71ab49be36e759345f91c69d1033d633"}, - {file = "multidict-6.2.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:5389445f0173c197f4a3613713b5fb3f3879df1ded2a1a2e4bc4b5b9c5441b7e"}, - {file = "multidict-6.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:94a7bb972178a8bfc4055db80c51efd24baefaced5e51c59b0d598a004e8305d"}, - {file = "multidict-6.2.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:da51d8928ad8b4244926fe862ba1795f0b6e68ed8c42cd2f822d435db9c2a8f4"}, - {file = "multidict-6.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:063be88bd684782a0715641de853e1e58a2f25b76388538bd62d974777ce9bc2"}, - {file = "multidict-6.2.0-cp311-cp311-win32.whl", hash = "sha256:52b05e21ff05729fbea9bc20b3a791c3c11da61649ff64cce8257c82a020466d"}, - {file = "multidict-6.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:1e2a2193d3aa5cbf5758f6d5680a52aa848e0cf611da324f71e5e48a9695cc86"}, - {file = "multidict-6.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:437c33561edb6eb504b5a30203daf81d4a9b727e167e78b0854d9a4e18e8950b"}, - {file = "multidict-6.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9f49585f4abadd2283034fc605961f40c638635bc60f5162276fec075f2e37a4"}, - {file = "multidict-6.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5dd7106d064d05896ce28c97da3f46caa442fe5a43bc26dfb258e90853b39b44"}, - {file = "multidict-6.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e25b11a0417475f093d0f0809a149aff3943c2c56da50fdf2c3c88d57fe3dfbd"}, - {file = "multidict-6.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac380cacdd3b183338ba63a144a34e9044520a6fb30c58aa14077157a033c13e"}, - {file = "multidict-6.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:61d5541f27533f803a941d3a3f8a3d10ed48c12cf918f557efcbf3cd04ef265c"}, - {file = "multidict-6.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:facaf11f21f3a4c51b62931feb13310e6fe3475f85e20d9c9fdce0d2ea561b87"}, - {file = "multidict-6.2.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:095a2eabe8c43041d3e6c2cb8287a257b5f1801c2d6ebd1dd877424f1e89cf29"}, - {file = "multidict-6.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a0cc398350ef31167e03f3ca7c19313d4e40a662adcb98a88755e4e861170bdd"}, - {file = "multidict-6.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:7c611345bbe7cb44aabb877cb94b63e86f2d0db03e382667dbd037866d44b4f8"}, - {file = "multidict-6.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8cd1a0644ccaf27e9d2f6d9c9474faabee21f0578fe85225cc5af9a61e1653df"}, - {file = "multidict-6.2.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:89b3857652183b8206a891168af47bac10b970d275bba1f6ee46565a758c078d"}, - {file = "multidict-6.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:125dd82b40f8c06d08d87b3510beaccb88afac94e9ed4a6f6c71362dc7dbb04b"}, - {file = "multidict-6.2.0-cp312-cp312-win32.whl", hash = "sha256:76b34c12b013d813e6cb325e6bd4f9c984db27758b16085926bbe7ceeaace626"}, - {file = "multidict-6.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:0b183a959fb88ad1be201de2c4bdf52fa8e46e6c185d76201286a97b6f5ee65c"}, - {file = "multidict-6.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5c5e7d2e300d5cb3b2693b6d60d3e8c8e7dd4ebe27cd17c9cb57020cac0acb80"}, - {file = "multidict-6.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:256d431fe4583c5f1e0f2e9c4d9c22f3a04ae96009b8cfa096da3a8723db0a16"}, - {file = "multidict-6.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a3c0ff89fe40a152e77b191b83282c9664357dce3004032d42e68c514ceff27e"}, - {file = "multidict-6.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef7d48207926edbf8b16b336f779c557dd8f5a33035a85db9c4b0febb0706817"}, - {file = "multidict-6.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3c099d3899b14e1ce52262eb82a5f5cb92157bb5106bf627b618c090a0eadc"}, - {file = "multidict-6.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e16e7297f29a544f49340012d6fc08cf14de0ab361c9eb7529f6a57a30cbfda1"}, - {file = "multidict-6.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:042028348dc5a1f2be6c666437042a98a5d24cee50380f4c0902215e5ec41844"}, - {file = "multidict-6.2.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:08549895e6a799bd551cf276f6e59820aa084f0f90665c0f03dd3a50db5d3c48"}, - {file = "multidict-6.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4ccfd74957ef53fa7380aaa1c961f523d582cd5e85a620880ffabd407f8202c0"}, - {file = "multidict-6.2.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:83b78c680d4b15d33042d330c2fa31813ca3974197bddb3836a5c635a5fd013f"}, - {file = "multidict-6.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b4c153863dd6569f6511845922c53e39c8d61f6e81f228ad5443e690fca403de"}, - {file = "multidict-6.2.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:98aa8325c7f47183b45588af9c434533196e241be0a4e4ae2190b06d17675c02"}, - {file = "multidict-6.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9e658d1373c424457ddf6d55ec1db93c280b8579276bebd1f72f113072df8a5d"}, - {file = "multidict-6.2.0-cp313-cp313-win32.whl", hash = "sha256:3157126b028c074951839233647bd0e30df77ef1fedd801b48bdcad242a60f4e"}, - {file = "multidict-6.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:2e87f1926e91855ae61769ba3e3f7315120788c099677e0842e697b0bfb659f2"}, - {file = "multidict-6.2.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:2529ddbdaa424b2c6c2eb668ea684dd6b75b839d0ad4b21aad60c168269478d7"}, - {file = "multidict-6.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:13551d0e2d7201f0959725a6a769b6f7b9019a168ed96006479c9ac33fe4096b"}, - {file = "multidict-6.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d1996ee1330e245cd3aeda0887b4409e3930524c27642b046e4fae88ffa66c5e"}, - {file = "multidict-6.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c537da54ce4ff7c15e78ab1292e5799d0d43a2108e006578a57f531866f64025"}, - {file = "multidict-6.2.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0f249badb360b0b4d694307ad40f811f83df4da8cef7b68e429e4eea939e49dd"}, - {file = "multidict-6.2.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48d39b1824b8d6ea7de878ef6226efbe0773f9c64333e1125e0efcfdd18a24c7"}, - {file = "multidict-6.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b99aac6bb2c37db336fa03a39b40ed4ef2818bf2dfb9441458165ebe88b793af"}, - {file = "multidict-6.2.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07bfa8bc649783e703263f783f73e27fef8cd37baaad4389816cf6a133141331"}, - {file = "multidict-6.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b2c00ad31fbc2cbac85d7d0fcf90853b2ca2e69d825a2d3f3edb842ef1544a2c"}, - {file = "multidict-6.2.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:0d57a01a2a9fa00234aace434d8c131f0ac6e0ac6ef131eda5962d7e79edfb5b"}, - {file = "multidict-6.2.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:abf5b17bc0cf626a8a497d89ac691308dbd825d2ac372aa990b1ca114e470151"}, - {file = "multidict-6.2.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:f7716f7e7138252d88607228ce40be22660d6608d20fd365d596e7ca0738e019"}, - {file = "multidict-6.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d5a36953389f35f0a4e88dc796048829a2f467c9197265504593f0e420571547"}, - {file = "multidict-6.2.0-cp313-cp313t-win32.whl", hash = "sha256:e653d36b1bf48fa78c7fcebb5fa679342e025121ace8c87ab05c1cefd33b34fc"}, - {file = "multidict-6.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:ca23db5fb195b5ef4fd1f77ce26cadefdf13dba71dab14dadd29b34d457d7c44"}, - {file = "multidict-6.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b4f3d66dd0354b79761481fc15bdafaba0b9d9076f1f42cc9ce10d7fcbda205a"}, - {file = "multidict-6.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6e2a2d6749e1ff2c9c76a72c6530d5baa601205b14e441e6d98011000f47a7ac"}, - {file = "multidict-6.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cca83a629f77402cfadd58352e394d79a61c8015f1694b83ab72237ec3941f88"}, - {file = "multidict-6.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:781b5dd1db18c9e9eacc419027b0acb5073bdec9de1675c0be25ceb10e2ad133"}, - {file = "multidict-6.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cf8d370b2fea27fb300825ec3984334f7dd54a581bde6456799ba3776915a656"}, - {file = "multidict-6.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:25bb96338512e2f46f615a2bb7c6012fe92a4a5ebd353e5020836a7e33120349"}, - {file = "multidict-6.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19e2819b0b468174de25c0ceed766606a07cedeab132383f1e83b9a4e96ccb4f"}, - {file = "multidict-6.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6aed763b6a1b28c46c055692836879328f0b334a6d61572ee4113a5d0c859872"}, - {file = "multidict-6.2.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a1133414b771619aa3c3000701c11b2e4624a7f492f12f256aedde97c28331a2"}, - {file = "multidict-6.2.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:639556758c36093b35e2e368ca485dada6afc2bd6a1b1207d85ea6dfc3deab27"}, - {file = "multidict-6.2.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:163f4604e76639f728d127293d24c3e208b445b463168af3d031b92b0998bb90"}, - {file = "multidict-6.2.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:2325105e16d434749e1be8022f942876a936f9bece4ec41ae244e3d7fae42aaf"}, - {file = "multidict-6.2.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e4371591e621579cb6da8401e4ea405b33ff25a755874a3567c4075ca63d56e2"}, - {file = "multidict-6.2.0-cp39-cp39-win32.whl", hash = "sha256:d1175b0e0d6037fab207f05774a176d71210ebd40b1c51f480a04b65ec5c786d"}, - {file = "multidict-6.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:ad81012b24b88aad4c70b2cbc2dad84018783221b7f923e926f4690ff8569da3"}, - {file = "multidict-6.2.0-py3-none-any.whl", hash = "sha256:5d26547423e5e71dcc562c4acdc134b900640a39abd9066d7326a7cc2324c530"}, - {file = "multidict-6.2.0.tar.gz", hash = "sha256:0085b0afb2446e57050140240a8595846ed64d1cbd26cef936bfab3192c673b8"}, + {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] @@ -1156,14 +1167,14 @@ files = [ [[package]] name = "opentelemetry-api" -version = "1.31.1" +version = "1.32.0" 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.0-py3-none-any.whl", hash = "sha256:15df743c765078611f376037b0d9111ec5c1febf2ec9440cdd919370faa1ce55"}, + {file = "opentelemetry_api-1.32.0.tar.gz", hash = "sha256:2623280c916f9b19cad0aa4280cb171265f19fd2909b0d47e4f06f7c83b02cb5"}, ] [package.dependencies] @@ -1172,68 +1183,68 @@ importlib-metadata = ">=6.0,<8.7.0" [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.31.1" +version = "1.32.0" 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.0-py3-none-any.whl", hash = "sha256:277a63a18768b3b460d082a489f6f80d4ae2c1e6b185bb701c6bd4e91405e4bd"}, + {file = "opentelemetry_exporter_otlp_proto_common-1.32.0.tar.gz", hash = "sha256:2bca672f2a279c4f517115e635c0cc1269d07b2982a36681c521f7e56179a222"}, ] [package.dependencies] -opentelemetry-proto = "1.31.1" +opentelemetry-proto = "1.32.0" [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.31.1" +version = "1.32.0" 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.0-py3-none-any.whl", hash = "sha256:e2ffecd6d2220eaf1291a46339f109bc0a57ee7c4c6abb8174df418bf00ce01f"}, + {file = "opentelemetry_exporter_otlp_proto_http-1.32.0.tar.gz", hash = "sha256:a5dfd94603da86e313e4f4fb8d181fd3b64a7c2a9c7b408c3653d2b1bc68d14f"}, ] [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.0" +opentelemetry-proto = "1.32.0" +opentelemetry-sdk = ">=1.32.0,<1.33.0" requests = ">=2.7,<3.0" [[package]] name = "opentelemetry-instrumentation" -version = "0.52b1" +version = "0.53b0" 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.53b0-py3-none-any.whl", hash = "sha256:70600778fd567c9c5fbfca181378ae179c0dec3ff613171707d3d77c360ff105"}, + {file = "opentelemetry_instrumentation-0.53b0.tar.gz", hash = "sha256:f2c21d71a3cdf28c656e3d90d247ee7558fb9b0239b3d9e9190266499dbed9d2"}, ] [package.dependencies] opentelemetry-api = ">=1.4,<2.0" -opentelemetry-semantic-conventions = "0.52b1" +opentelemetry-semantic-conventions = "0.53b0" packaging = ">=18.0" wrapt = ">=1.0.0,<2.0.0" [[package]] name = "opentelemetry-proto" -version = "1.31.1" +version = "1.32.0" 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.0-py3-none-any.whl", hash = "sha256:f699269dc037e18fba05442580a8682c9fbd0f4c7f5addfed82c44be0c53c5ff"}, + {file = "opentelemetry_proto-1.32.0.tar.gz", hash = "sha256:f8b70ae52f4ef8a4e4c0760e87c9071e07ece2618c080d4839bef44c0156cd44"}, ] [package.dependencies] @@ -1241,36 +1252,36 @@ protobuf = ">=5.0,<6.0" [[package]] name = "opentelemetry-sdk" -version = "1.31.1" +version = "1.32.0" 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.0-py3-none-any.whl", hash = "sha256:ed252d035c22a15536c1f603ca089298daab60850fc2f5ddfa95d95cc1c043ea"}, + {file = "opentelemetry_sdk-1.32.0.tar.gz", hash = "sha256:5ff07fb371d1ab1189fa7047702e2e888b5403c5efcbb18083cae0d5aa5f58d2"}, ] [package.dependencies] -opentelemetry-api = "1.31.1" -opentelemetry-semantic-conventions = "0.52b1" +opentelemetry-api = "1.32.0" +opentelemetry-semantic-conventions = "0.53b0" typing-extensions = ">=3.7.4" [[package]] name = "opentelemetry-semantic-conventions" -version = "0.52b1" +version = "0.53b0" 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.53b0-py3-none-any.whl", hash = "sha256:561da89f766ab51615c0e72b12329e0a1bc16945dbd62c8646ffc74e36a1edff"}, + {file = "opentelemetry_semantic_conventions-0.53b0.tar.gz", hash = "sha256:05b7908e1da62d72f9bf717ed25c72f566fe005a2dd260c61b11e025f2552cf6"}, ] [package.dependencies] deprecated = ">=1.2.6" -opentelemetry-api = "1.31.1" +opentelemetry-api = "1.32.0" [[package]] name = "packaging" @@ -1389,20 +1400,20 @@ types-pytz = ">=2022.1.1" [[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" @@ -1422,14 +1433,14 @@ testing = ["pytest", "pytest-benchmark"] [[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] @@ -1441,110 +1452,110 @@ virtualenv = ">=20.10.0" [[package]] name = "propcache" -version = "0.3.0" +version = "0.3.1" description = "Accelerated property cache" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "propcache-0.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:efa44f64c37cc30c9f05932c740a8b40ce359f51882c70883cc95feac842da4d"}, - {file = "propcache-0.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2383a17385d9800b6eb5855c2f05ee550f803878f344f58b6e194de08b96352c"}, - {file = "propcache-0.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d3e7420211f5a65a54675fd860ea04173cde60a7cc20ccfbafcccd155225f8bc"}, - {file = "propcache-0.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3302c5287e504d23bb0e64d2a921d1eb4a03fb93a0a0aa3b53de059f5a5d737d"}, - {file = "propcache-0.3.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7e2e068a83552ddf7a39a99488bcba05ac13454fb205c847674da0352602082f"}, - {file = "propcache-0.3.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d913d36bdaf368637b4f88d554fb9cb9d53d6920b9c5563846555938d5450bf"}, - {file = "propcache-0.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ee1983728964d6070ab443399c476de93d5d741f71e8f6e7880a065f878e0b9"}, - {file = "propcache-0.3.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:36ca5e9a21822cc1746023e88f5c0af6fce3af3b85d4520efb1ce4221bed75cc"}, - {file = "propcache-0.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9ecde3671e62eeb99e977f5221abcf40c208f69b5eb986b061ccec317c82ebd0"}, - {file = "propcache-0.3.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:d383bf5e045d7f9d239b38e6acadd7b7fdf6c0087259a84ae3475d18e9a2ae8b"}, - {file = "propcache-0.3.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:8cb625bcb5add899cb8ba7bf716ec1d3e8f7cdea9b0713fa99eadf73b6d4986f"}, - {file = "propcache-0.3.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:5fa159dcee5dba00c1def3231c249cf261185189205073bde13797e57dd7540a"}, - {file = "propcache-0.3.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:a7080b0159ce05f179cfac592cda1a82898ca9cd097dacf8ea20ae33474fbb25"}, - {file = "propcache-0.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ed7161bccab7696a473fe7ddb619c1d75963732b37da4618ba12e60899fefe4f"}, - {file = "propcache-0.3.0-cp310-cp310-win32.whl", hash = "sha256:bf0d9a171908f32d54f651648c7290397b8792f4303821c42a74e7805bfb813c"}, - {file = "propcache-0.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:42924dc0c9d73e49908e35bbdec87adedd651ea24c53c29cac103ede0ea1d340"}, - {file = "propcache-0.3.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9ddd49258610499aab83b4f5b61b32e11fce873586282a0e972e5ab3bcadee51"}, - {file = "propcache-0.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2578541776769b500bada3f8a4eeaf944530516b6e90c089aa368266ed70c49e"}, - {file = "propcache-0.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d8074c5dd61c8a3e915fa8fc04754fa55cfa5978200d2daa1e2d4294c1f136aa"}, - {file = "propcache-0.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b58229a844931bca61b3a20efd2be2a2acb4ad1622fc026504309a6883686fbf"}, - {file = "propcache-0.3.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e45377d5d6fefe1677da2a2c07b024a6dac782088e37c0b1efea4cfe2b1be19b"}, - {file = "propcache-0.3.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ec5060592d83454e8063e487696ac3783cc48c9a329498bafae0d972bc7816c9"}, - {file = "propcache-0.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15010f29fbed80e711db272909a074dc79858c6d28e2915704cfc487a8ac89c6"}, - {file = "propcache-0.3.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a254537b9b696ede293bfdbc0a65200e8e4507bc9f37831e2a0318a9b333c85c"}, - {file = "propcache-0.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2b975528998de037dfbc10144b8aed9b8dd5a99ec547f14d1cb7c5665a43f075"}, - {file = "propcache-0.3.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:19d36bb351ad5554ff20f2ae75f88ce205b0748c38b146c75628577020351e3c"}, - {file = "propcache-0.3.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6032231d4a5abd67c7f71168fd64a47b6b451fbcb91c8397c2f7610e67683810"}, - {file = "propcache-0.3.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6985a593417cdbc94c7f9c3403747335e450c1599da1647a5af76539672464d3"}, - {file = "propcache-0.3.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:6a1948df1bb1d56b5e7b0553c0fa04fd0e320997ae99689488201f19fa90d2e7"}, - {file = "propcache-0.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8319293e85feadbbfe2150a5659dbc2ebc4afdeaf7d98936fb9a2f2ba0d4c35c"}, - {file = "propcache-0.3.0-cp311-cp311-win32.whl", hash = "sha256:63f26258a163c34542c24808f03d734b338da66ba91f410a703e505c8485791d"}, - {file = "propcache-0.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:cacea77ef7a2195f04f9279297684955e3d1ae4241092ff0cfcef532bb7a1c32"}, - {file = "propcache-0.3.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e53d19c2bf7d0d1e6998a7e693c7e87300dd971808e6618964621ccd0e01fe4e"}, - {file = "propcache-0.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a61a68d630e812b67b5bf097ab84e2cd79b48c792857dc10ba8a223f5b06a2af"}, - {file = "propcache-0.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fb91d20fa2d3b13deea98a690534697742029f4fb83673a3501ae6e3746508b5"}, - {file = "propcache-0.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67054e47c01b7b349b94ed0840ccae075449503cf1fdd0a1fdd98ab5ddc2667b"}, - {file = "propcache-0.3.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:997e7b8f173a391987df40f3b52c423e5850be6f6df0dcfb5376365440b56667"}, - {file = "propcache-0.3.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d663fd71491dde7dfdfc899d13a067a94198e90695b4321084c6e450743b8c7"}, - {file = "propcache-0.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8884ba1a0fe7210b775106b25850f5e5a9dc3c840d1ae9924ee6ea2eb3acbfe7"}, - {file = "propcache-0.3.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa806bbc13eac1ab6291ed21ecd2dd426063ca5417dd507e6be58de20e58dfcf"}, - {file = "propcache-0.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6f4d7a7c0aff92e8354cceca6fe223973ddf08401047920df0fcb24be2bd5138"}, - {file = "propcache-0.3.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:9be90eebc9842a93ef8335291f57b3b7488ac24f70df96a6034a13cb58e6ff86"}, - {file = "propcache-0.3.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:bf15fc0b45914d9d1b706f7c9c4f66f2b7b053e9517e40123e137e8ca8958b3d"}, - {file = "propcache-0.3.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5a16167118677d94bb48bfcd91e420088854eb0737b76ec374b91498fb77a70e"}, - {file = "propcache-0.3.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:41de3da5458edd5678b0f6ff66691507f9885f5fe6a0fb99a5d10d10c0fd2d64"}, - {file = "propcache-0.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:728af36011bb5d344c4fe4af79cfe186729efb649d2f8b395d1572fb088a996c"}, - {file = "propcache-0.3.0-cp312-cp312-win32.whl", hash = "sha256:6b5b7fd6ee7b54e01759f2044f936dcf7dea6e7585f35490f7ca0420fe723c0d"}, - {file = "propcache-0.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:2d15bc27163cd4df433e75f546b9ac31c1ba7b0b128bfb1b90df19082466ff57"}, - {file = "propcache-0.3.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a2b9bf8c79b660d0ca1ad95e587818c30ccdb11f787657458d6f26a1ea18c568"}, - {file = "propcache-0.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b0c1a133d42c6fc1f5fbcf5c91331657a1ff822e87989bf4a6e2e39b818d0ee9"}, - {file = "propcache-0.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bb2f144c6d98bb5cbc94adeb0447cfd4c0f991341baa68eee3f3b0c9c0e83767"}, - {file = "propcache-0.3.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1323cd04d6e92150bcc79d0174ce347ed4b349d748b9358fd2e497b121e03c8"}, - {file = "propcache-0.3.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b812b3cb6caacd072276ac0492d249f210006c57726b6484a1e1805b3cfeea0"}, - {file = "propcache-0.3.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:742840d1d0438eb7ea4280f3347598f507a199a35a08294afdcc560c3739989d"}, - {file = "propcache-0.3.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c6e7e4f9167fddc438cd653d826f2222222564daed4116a02a184b464d3ef05"}, - {file = "propcache-0.3.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a94ffc66738da99232ddffcf7910e0f69e2bbe3a0802e54426dbf0714e1c2ffe"}, - {file = "propcache-0.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3c6ec957025bf32b15cbc6b67afe233c65b30005e4c55fe5768e4bb518d712f1"}, - {file = "propcache-0.3.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:549722908de62aa0b47a78b90531c022fa6e139f9166be634f667ff45632cc92"}, - {file = "propcache-0.3.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:5d62c4f6706bff5d8a52fd51fec6069bef69e7202ed481486c0bc3874912c787"}, - {file = "propcache-0.3.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:24c04f8fbf60094c531667b8207acbae54146661657a1b1be6d3ca7773b7a545"}, - {file = "propcache-0.3.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:7c5f5290799a3f6539cc5e6f474c3e5c5fbeba74a5e1e5be75587746a940d51e"}, - {file = "propcache-0.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4fa0e7c9c3cf7c276d4f6ab9af8adddc127d04e0fcabede315904d2ff76db626"}, - {file = "propcache-0.3.0-cp313-cp313-win32.whl", hash = "sha256:ee0bd3a7b2e184e88d25c9baa6a9dc609ba25b76daae942edfb14499ac7ec374"}, - {file = "propcache-0.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:1c8f7d896a16da9455f882870a507567d4f58c53504dc2d4b1e1d386dfe4588a"}, - {file = "propcache-0.3.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:e560fd75aaf3e5693b91bcaddd8b314f4d57e99aef8a6c6dc692f935cc1e6bbf"}, - {file = "propcache-0.3.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:65a37714b8ad9aba5780325228598a5b16c47ba0f8aeb3dc0514701e4413d7c0"}, - {file = "propcache-0.3.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:07700939b2cbd67bfb3b76a12e1412405d71019df00ca5697ce75e5ef789d829"}, - {file = "propcache-0.3.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c0fdbdf6983526e269e5a8d53b7ae3622dd6998468821d660d0daf72779aefa"}, - {file = "propcache-0.3.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:794c3dd744fad478b6232289c866c25406ecdfc47e294618bdf1697e69bd64a6"}, - {file = "propcache-0.3.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4544699674faf66fb6b4473a1518ae4999c1b614f0b8297b1cef96bac25381db"}, - {file = "propcache-0.3.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fddb8870bdb83456a489ab67c6b3040a8d5a55069aa6f72f9d872235fbc52f54"}, - {file = "propcache-0.3.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f857034dc68d5ceb30fb60afb6ff2103087aea10a01b613985610e007053a121"}, - {file = "propcache-0.3.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:02df07041e0820cacc8f739510078f2aadcfd3fc57eaeeb16d5ded85c872c89e"}, - {file = "propcache-0.3.0-cp313-cp313t-musllinux_1_2_armv7l.whl", hash = "sha256:f47d52fd9b2ac418c4890aad2f6d21a6b96183c98021f0a48497a904199f006e"}, - {file = "propcache-0.3.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9ff4e9ecb6e4b363430edf2c6e50173a63e0820e549918adef70515f87ced19a"}, - {file = "propcache-0.3.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:ecc2920630283e0783c22e2ac94427f8cca29a04cfdf331467d4f661f4072dac"}, - {file = "propcache-0.3.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:c441c841e82c5ba7a85ad25986014be8d7849c3cfbdb6004541873505929a74e"}, - {file = "propcache-0.3.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6c929916cbdb540d3407c66f19f73387f43e7c12fa318a66f64ac99da601bcdf"}, - {file = "propcache-0.3.0-cp313-cp313t-win32.whl", hash = "sha256:0c3e893c4464ebd751b44ae76c12c5f5c1e4f6cbd6fbf67e3783cd93ad221863"}, - {file = "propcache-0.3.0-cp313-cp313t-win_amd64.whl", hash = "sha256:75e872573220d1ee2305b35c9813626e620768248425f58798413e9c39741f46"}, - {file = "propcache-0.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:03c091bb752349402f23ee43bb2bff6bd80ccab7c9df6b88ad4322258d6960fc"}, - {file = "propcache-0.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:46ed02532cb66612d42ae5c3929b5e98ae330ea0f3900bc66ec5f4862069519b"}, - {file = "propcache-0.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:11ae6a8a01b8a4dc79093b5d3ca2c8a4436f5ee251a9840d7790dccbd96cb649"}, - {file = "propcache-0.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df03cd88f95b1b99052b52b1bb92173229d7a674df0ab06d2b25765ee8404bce"}, - {file = "propcache-0.3.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:03acd9ff19021bd0567582ac88f821b66883e158274183b9e5586f678984f8fe"}, - {file = "propcache-0.3.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd54895e4ae7d32f1e3dd91261df46ee7483a735017dc6f987904f194aa5fd14"}, - {file = "propcache-0.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26a67e5c04e3119594d8cfae517f4b9330c395df07ea65eab16f3d559b7068fe"}, - {file = "propcache-0.3.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee25f1ac091def37c4b59d192bbe3a206298feeb89132a470325bf76ad122a1e"}, - {file = "propcache-0.3.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:58e6d2a5a7cb3e5f166fd58e71e9a4ff504be9dc61b88167e75f835da5764d07"}, - {file = "propcache-0.3.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:be90c94570840939fecedf99fa72839aed70b0ced449b415c85e01ae67422c90"}, - {file = "propcache-0.3.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:49ea05212a529c2caffe411e25a59308b07d6e10bf2505d77da72891f9a05641"}, - {file = "propcache-0.3.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:119e244ab40f70a98c91906d4c1f4c5f2e68bd0b14e7ab0a06922038fae8a20f"}, - {file = "propcache-0.3.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:507c5357a8d8b4593b97fb669c50598f4e6cccbbf77e22fa9598aba78292b4d7"}, - {file = "propcache-0.3.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:8526b0941ec5a40220fc4dfde76aed58808e2b309c03e9fa8e2260083ef7157f"}, - {file = "propcache-0.3.0-cp39-cp39-win32.whl", hash = "sha256:7cedd25e5f678f7738da38037435b340694ab34d424938041aa630d8bac42663"}, - {file = "propcache-0.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:bf4298f366ca7e1ad1d21bbb58300a6985015909964077afd37559084590c929"}, - {file = "propcache-0.3.0-py3-none-any.whl", hash = "sha256:67dda3c7325691c2081510e92c561f465ba61b975f481735aefdfc845d2cd043"}, - {file = "propcache-0.3.0.tar.gz", hash = "sha256:a8fd93de4e1d278046345f49e2238cdb298589325849b2645d4a94c53faeffc5"}, + {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]] @@ -1797,21 +1808,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]] @@ -1987,7 +1998,7 @@ description = "C version of reader, parser and emitter for ruamel.yaml derived f optional = false python-versions = ">=3.9" groups = ["main"] -markers = "platform_python_implementation == \"CPython\" and python_version < \"3.13\"" +markers = "platform_python_implementation == \"CPython\"" files = [ {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:11f891336688faf5156a36293a9c362bdc7c88f03a8a027c2c1d8e0bcde998e5"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:a606ef75a60ecf3d924613892cc603b154178ee25abb3055db5062da811fd969"}, @@ -1995,7 +2006,6 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f66efbc1caa63c088dead1c4170d148eabc9b80d95fb75b6c92ac0aad2437d76"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22353049ba4181685023b25b5b51a574bce33e7f51c759371a7422dcae5402a6"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:932205970b9f9991b34f55136be327501903f7c66830e9760a8ffb15b07f05cd"}, - {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a52d48f4e7bf9005e8f0a89209bf9a73f7190ddf0489eee5eb51377385f59f2a"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win32.whl", hash = "sha256:3eac5a91891ceb88138c113f9db04f3cebdae277f5d44eaa3651a4f573e6a5da"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win_amd64.whl", hash = "sha256:ab007f2f5a87bd08ab1499bdf96f3d5c6ad4dcfa364884cb4549aa0154b13a28"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6"}, @@ -2004,7 +2014,6 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2"}, - {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1492a6051dab8d912fc2adeef0e8c72216b24d57bd896ea607cb90bb0c4981d3"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win32.whl", hash = "sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win_amd64.whl", hash = "sha256:a274fb2cb086c7a3dea4322ec27f4cb5cc4b6298adb583ab0e211a4682f241eb"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:20b0f8dc160ba83b6dcc0e256846e1a02d044e13f7ea74a3d1d56ede4e48c632"}, @@ -2013,7 +2022,6 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:749c16fcc4a2b09f28843cda5a193e0283e47454b63ec4b81eaa2242f50e4ccd"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bf165fef1f223beae7333275156ab2022cffe255dcc51c27f066b4370da81e31"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:32621c177bbf782ca5a18ba4d7af0f1082a3f6e517ac2a18b3974d4edf349680"}, - {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b82a7c94a498853aa0b272fd5bc67f29008da798d4f93a2f9f289feb8426a58d"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win32.whl", hash = "sha256:e8c4ebfcfd57177b572e2040777b8abc537cdef58a2120e830124946aa9b42c5"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win_amd64.whl", hash = "sha256:0467c5965282c62203273b838ae77c0d29d7638c8a4e3a1c8bdd3602c10904e4"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4c8c5d82f50bb53986a5e02d1b3092b03622c02c2eb78e29bec33fd9593bae1a"}, @@ -2022,7 +2030,6 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96777d473c05ee3e5e3c3e999f5d23c6f4ec5b0c38c098b3a5229085f74236c6"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:3bc2a80e6420ca8b7d3590791e2dfc709c88ab9152c00eeb511c9875ce5778bf"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e188d2699864c11c36cdfdada94d781fd5d6b0071cd9c427bceb08ad3d7c70e1"}, - {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f6f3eac23941b32afccc23081e1f50612bdbe4e982012ef4f5797986828cd01"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win32.whl", hash = "sha256:6442cb36270b3afb1b4951f060eccca1ce49f3d087ca1ca4563a6eb479cb3de6"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win_amd64.whl", hash = "sha256:e5b8daf27af0b90da7bb903a876477a9e6d7270be6146906b276605997c7e9a3"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:fc4b630cd3fa2cf7fce38afa91d7cfe844a9f75d7f0f36393fa98815e911d987"}, @@ -2031,7 +2038,6 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2f1c3765db32be59d18ab3953f43ab62a761327aafc1594a2a1fbe038b8b8a7"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d85252669dc32f98ebcd5d36768f5d4faeaeaa2d655ac0473be490ecdae3c285"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e143ada795c341b56de9418c58d028989093ee611aa27ffb9b7f609c00d813ed"}, - {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2c59aa6170b990d8d2719323e628aaf36f3bfbc1c26279c0eeeb24d05d2d11c7"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win32.whl", hash = "sha256:beffaed67936fbbeffd10966a4eb53c402fafd3d6833770516bf7314bc6ffa12"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win_amd64.whl", hash = "sha256:040ae85536960525ea62868b642bdb0c2cc6021c9f9d507810c0c604e66f5a7b"}, {file = "ruamel.yaml.clib-0.2.12.tar.gz", hash = "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f"}, @@ -2039,48 +2045,47 @@ files = [ [[package]] name = "ruff" -version = "0.11.5" +version = "0.1.15" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" groups = ["dev"] files = [ - {file = "ruff-0.11.5-py3-none-linux_armv6l.whl", hash = "sha256:2561294e108eb648e50f210671cc56aee590fb6167b594144401532138c66c7b"}, - {file = "ruff-0.11.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ac12884b9e005c12d0bd121f56ccf8033e1614f736f766c118ad60780882a077"}, - {file = "ruff-0.11.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:4bfd80a6ec559a5eeb96c33f832418bf0fb96752de0539905cf7b0cc1d31d779"}, - {file = "ruff-0.11.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0947c0a1afa75dcb5db4b34b070ec2bccee869d40e6cc8ab25aca11a7d527794"}, - {file = "ruff-0.11.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ad871ff74b5ec9caa66cb725b85d4ef89b53f8170f47c3406e32ef040400b038"}, - {file = "ruff-0.11.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6cf918390cfe46d240732d4d72fa6e18e528ca1f60e318a10835cf2fa3dc19f"}, - {file = "ruff-0.11.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:56145ee1478582f61c08f21076dc59153310d606ad663acc00ea3ab5b2125f82"}, - {file = "ruff-0.11.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e5f66f8f1e8c9fc594cbd66fbc5f246a8d91f916cb9667e80208663ec3728304"}, - {file = "ruff-0.11.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80b4df4d335a80315ab9afc81ed1cff62be112bd165e162b5eed8ac55bfc8470"}, - {file = "ruff-0.11.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3068befab73620b8a0cc2431bd46b3cd619bc17d6f7695a3e1bb166b652c382a"}, - {file = "ruff-0.11.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f5da2e710a9641828e09aa98b92c9ebbc60518fdf3921241326ca3e8f8e55b8b"}, - {file = "ruff-0.11.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ef39f19cb8ec98cbc762344921e216f3857a06c47412030374fffd413fb8fd3a"}, - {file = "ruff-0.11.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b2a7cedf47244f431fd11aa5a7e2806dda2e0c365873bda7834e8f7d785ae159"}, - {file = "ruff-0.11.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:81be52e7519f3d1a0beadcf8e974715b2dfc808ae8ec729ecfc79bddf8dbb783"}, - {file = "ruff-0.11.5-py3-none-win32.whl", hash = "sha256:e268da7b40f56e3eca571508a7e567e794f9bfcc0f412c4b607931d3af9c4afe"}, - {file = "ruff-0.11.5-py3-none-win_amd64.whl", hash = "sha256:6c6dc38af3cfe2863213ea25b6dc616d679205732dc0fb673356c2d69608f800"}, - {file = "ruff-0.11.5-py3-none-win_arm64.whl", hash = "sha256:67e241b4314f4eacf14a601d586026a962f4002a475aa702c69980a38087aa4e"}, - {file = "ruff-0.11.5.tar.gz", hash = "sha256:cae2e2439cb88853e421901ec040a758960b576126dab520fa08e9de431d1bef"}, + {file = "ruff-0.1.15-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:5fe8d54df166ecc24106db7dd6a68d44852d14eb0729ea4672bb4d96c320b7df"}, + {file = "ruff-0.1.15-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6f0bfbb53c4b4de117ac4d6ddfd33aa5fc31beeaa21d23c45c6dd249faf9126f"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0d432aec35bfc0d800d4f70eba26e23a352386be3a6cf157083d18f6f5881c8"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9405fa9ac0e97f35aaddf185a1be194a589424b8713e3b97b762336ec79ff807"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c66ec24fe36841636e814b8f90f572a8c0cb0e54d8b5c2d0e300d28a0d7bffec"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:6f8ad828f01e8dd32cc58bc28375150171d198491fc901f6f98d2a39ba8e3ff5"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86811954eec63e9ea162af0ffa9f8d09088bab51b7438e8b6488b9401863c25e"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fd4025ac5e87d9b80e1f300207eb2fd099ff8200fa2320d7dc066a3f4622dc6b"}, + {file = "ruff-0.1.15-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b17b93c02cdb6aeb696effecea1095ac93f3884a49a554a9afa76bb125c114c1"}, + {file = "ruff-0.1.15-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ddb87643be40f034e97e97f5bc2ef7ce39de20e34608f3f829db727a93fb82c5"}, + {file = "ruff-0.1.15-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:abf4822129ed3a5ce54383d5f0e964e7fef74a41e48eb1dfad404151efc130a2"}, + {file = "ruff-0.1.15-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6c629cf64bacfd136c07c78ac10a54578ec9d1bd2a9d395efbee0935868bf852"}, + {file = "ruff-0.1.15-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1bab866aafb53da39c2cadfb8e1c4550ac5340bb40300083eb8967ba25481447"}, + {file = "ruff-0.1.15-py3-none-win32.whl", hash = "sha256:2417e1cb6e2068389b07e6fa74c306b2810fe3ee3476d5b8a96616633f40d14f"}, + {file = "ruff-0.1.15-py3-none-win_amd64.whl", hash = "sha256:3837ac73d869efc4182d9036b1405ef4c73d9b1f88da2413875e34e0d6919587"}, + {file = "ruff-0.1.15-py3-none-win_arm64.whl", hash = "sha256:9a933dfb1c14ec7a33cceb1e49ec4a16b51ce3c20fd42663198746efc0427360"}, + {file = "ruff-0.1.15.tar.gz", hash = "sha256:f6dfa8c1b21c913c326919056c390966648b680966febcb796cc9d1aaab8564e"}, ] [[package]] name = "s3fs" -version = "2025.3.0" +version = "2025.3.2" description = "Convenient Filesystem interface over S3" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "s3fs-2025.3.0-py3-none-any.whl", hash = "sha256:88d803615baa04945156ca0e1498009b7acd3132c07198bd81b3e874846e0aa2"}, - {file = "s3fs-2025.3.0.tar.gz", hash = "sha256:446dd539eb0d0678209723cb7ad1bedbb172185b0d34675b09be1ad81843a644"}, + {file = "s3fs-2025.3.2-py3-none-any.whl", hash = "sha256:81eae3f37b4b04bcc08845d7bcc607c6ca45878813ef7e6a28d77b2688417130"}, + {file = "s3fs-2025.3.2.tar.gz", hash = "sha256:6798f896ec76dd3bfd8beb89f0bb7c5263cb2760e038bae0978505cd172a307c"}, ] [package.dependencies] aiobotocore = ">=2.5.4,<3.0.0" aiohttp = "<4.0.0a0 || >4.0.0a0,<4.0.0a1 || >4.0.0a1" -fsspec = "==2025.3.0.*" +fsspec = "==2025.3.2.*" [package.extras] awscli = ["aiobotocore[awscli] (>=2.5.4,<3.0.0)"] @@ -2155,38 +2160,38 @@ files = [ [[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.1.0.20250318" +version = "2025.2.0.20250326" description = "Typing stubs for pytz" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "types_pytz-2025.1.0.20250318-py3-none-any.whl", hash = "sha256:04dba4907c5415777083f9548693c6d9f80ec53adcaff55a38526a3f8ddcae04"}, - {file = "types_pytz-2025.1.0.20250318.tar.gz", hash = "sha256:97e0e35184c6fe14e3a5014512057f2c57bb0c6582d63c1cfcc4809f82180449"}, + {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.20250306" +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.20250306-py3-none-any.whl", hash = "sha256:25f2cbb5c8710b2022f8bbee7b2b66f319ef14aeea2f35d80f18c9dbf3b60a0b"}, - {file = "types_requests-2.32.0.20250306.tar.gz", hash = "sha256:0962352694ec5b2f95fda877ee60a159abdf84a0fc6fdace599f20acb41a03d1"}, + {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] @@ -2194,14 +2199,14 @@ urllib3 = ">=2" [[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]] @@ -2233,14 +2238,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] @@ -2251,14 +2256,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] @@ -2268,7 +2273,7 @@ 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" @@ -2377,100 +2382,105 @@ files = [ [[package]] name = "yarl" -version = "1.18.3" +version = "1.19.0" description = "Yet another URL library" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7df647e8edd71f000a5208fe6ff8c382a1de8edfbccdbbfe649d263de07d8c34"}, - {file = "yarl-1.18.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c69697d3adff5aa4f874b19c0e4ed65180ceed6318ec856ebc423aa5850d84f7"}, - {file = "yarl-1.18.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:602d98f2c2d929f8e697ed274fbadc09902c4025c5a9963bf4e9edfc3ab6f7ed"}, - {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c654d5207c78e0bd6d749f6dae1dcbbfde3403ad3a4b11f3c5544d9906969dde"}, - {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5094d9206c64181d0f6e76ebd8fb2f8fe274950a63890ee9e0ebfd58bf9d787b"}, - {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35098b24e0327fc4ebdc8ffe336cee0a87a700c24ffed13161af80124b7dc8e5"}, - {file = "yarl-1.18.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3236da9272872443f81fedc389bace88408f64f89f75d1bdb2256069a8730ccc"}, - {file = "yarl-1.18.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2c08cc9b16f4f4bc522771d96734c7901e7ebef70c6c5c35dd0f10845270bcd"}, - {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:80316a8bd5109320d38eef8833ccf5f89608c9107d02d2a7f985f98ed6876990"}, - {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c1e1cc06da1491e6734f0ea1e6294ce00792193c463350626571c287c9a704db"}, - {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:fea09ca13323376a2fdfb353a5fa2e59f90cd18d7ca4eaa1fd31f0a8b4f91e62"}, - {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e3b9fd71836999aad54084906f8663dffcd2a7fb5cdafd6c37713b2e72be1760"}, - {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:757e81cae69244257d125ff31663249b3013b5dc0a8520d73694aed497fb195b"}, - {file = "yarl-1.18.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b1771de9944d875f1b98a745bc547e684b863abf8f8287da8466cf470ef52690"}, - {file = "yarl-1.18.3-cp310-cp310-win32.whl", hash = "sha256:8874027a53e3aea659a6d62751800cf6e63314c160fd607489ba5c2edd753cf6"}, - {file = "yarl-1.18.3-cp310-cp310-win_amd64.whl", hash = "sha256:93b2e109287f93db79210f86deb6b9bbb81ac32fc97236b16f7433db7fc437d8"}, - {file = "yarl-1.18.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8503ad47387b8ebd39cbbbdf0bf113e17330ffd339ba1144074da24c545f0069"}, - {file = "yarl-1.18.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:02ddb6756f8f4517a2d5e99d8b2f272488e18dd0bfbc802f31c16c6c20f22193"}, - {file = "yarl-1.18.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:67a283dd2882ac98cc6318384f565bffc751ab564605959df4752d42483ad889"}, - {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d980e0325b6eddc81331d3f4551e2a333999fb176fd153e075c6d1c2530aa8a8"}, - {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b643562c12680b01e17239be267bc306bbc6aac1f34f6444d1bded0c5ce438ca"}, - {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c017a3b6df3a1bd45b9fa49a0f54005e53fbcad16633870104b66fa1a30a29d8"}, - {file = "yarl-1.18.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75674776d96d7b851b6498f17824ba17849d790a44d282929c42dbb77d4f17ae"}, - {file = "yarl-1.18.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ccaa3a4b521b780a7e771cc336a2dba389a0861592bbce09a476190bb0c8b4b3"}, - {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2d06d3005e668744e11ed80812e61efd77d70bb7f03e33c1598c301eea20efbb"}, - {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:9d41beda9dc97ca9ab0b9888cb71f7539124bc05df02c0cff6e5acc5a19dcc6e"}, - {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ba23302c0c61a9999784e73809427c9dbedd79f66a13d84ad1b1943802eaaf59"}, - {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:6748dbf9bfa5ba1afcc7556b71cda0d7ce5f24768043a02a58846e4a443d808d"}, - {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:0b0cad37311123211dc91eadcb322ef4d4a66008d3e1bdc404808992260e1a0e"}, - {file = "yarl-1.18.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0fb2171a4486bb075316ee754c6d8382ea6eb8b399d4ec62fde2b591f879778a"}, - {file = "yarl-1.18.3-cp311-cp311-win32.whl", hash = "sha256:61b1a825a13bef4a5f10b1885245377d3cd0bf87cba068e1d9a88c2ae36880e1"}, - {file = "yarl-1.18.3-cp311-cp311-win_amd64.whl", hash = "sha256:b9d60031cf568c627d028239693fd718025719c02c9f55df0a53e587aab951b5"}, - {file = "yarl-1.18.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:1dd4bdd05407ced96fed3d7f25dbbf88d2ffb045a0db60dbc247f5b3c5c25d50"}, - {file = "yarl-1.18.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7c33dd1931a95e5d9a772d0ac5e44cac8957eaf58e3c8da8c1414de7dd27c576"}, - {file = "yarl-1.18.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25b411eddcfd56a2f0cd6a384e9f4f7aa3efee14b188de13048c25b5e91f1640"}, - {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:436c4fc0a4d66b2badc6c5fc5ef4e47bb10e4fd9bf0c79524ac719a01f3607c2"}, - {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e35ef8683211db69ffe129a25d5634319a677570ab6b2eba4afa860f54eeaf75"}, - {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84b2deecba4a3f1a398df819151eb72d29bfeb3b69abb145a00ddc8d30094512"}, - {file = "yarl-1.18.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e5a1fea0fd4f5bfa7440a47eff01d9822a65b4488f7cff83155a0f31a2ecba"}, - {file = "yarl-1.18.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0e883008013c0e4aef84dcfe2a0b172c4d23c2669412cf5b3371003941f72bb"}, - {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5a3f356548e34a70b0172d8890006c37be92995f62d95a07b4a42e90fba54272"}, - {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:ccd17349166b1bee6e529b4add61727d3f55edb7babbe4069b5764c9587a8cc6"}, - {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b958ddd075ddba5b09bb0be8a6d9906d2ce933aee81100db289badbeb966f54e"}, - {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c7d79f7d9aabd6011004e33b22bc13056a3e3fb54794d138af57f5ee9d9032cb"}, - {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4891ed92157e5430874dad17b15eb1fda57627710756c27422200c52d8a4e393"}, - {file = "yarl-1.18.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ce1af883b94304f493698b00d0f006d56aea98aeb49d75ec7d98cd4a777e9285"}, - {file = "yarl-1.18.3-cp312-cp312-win32.whl", hash = "sha256:f91c4803173928a25e1a55b943c81f55b8872f0018be83e3ad4938adffb77dd2"}, - {file = "yarl-1.18.3-cp312-cp312-win_amd64.whl", hash = "sha256:7e2ee16578af3b52ac2f334c3b1f92262f47e02cc6193c598502bd46f5cd1477"}, - {file = "yarl-1.18.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:90adb47ad432332d4f0bc28f83a5963f426ce9a1a8809f5e584e704b82685dcb"}, - {file = "yarl-1.18.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:913829534200eb0f789d45349e55203a091f45c37a2674678744ae52fae23efa"}, - {file = "yarl-1.18.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ef9f7768395923c3039055c14334ba4d926f3baf7b776c923c93d80195624782"}, - {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88a19f62ff30117e706ebc9090b8ecc79aeb77d0b1f5ec10d2d27a12bc9f66d0"}, - {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e17c9361d46a4d5addf777c6dd5eab0715a7684c2f11b88c67ac37edfba6c482"}, - {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a74a13a4c857a84a845505fd2d68e54826a2cd01935a96efb1e9d86c728e186"}, - {file = "yarl-1.18.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:41f7ce59d6ee7741af71d82020346af364949314ed3d87553763a2df1829cc58"}, - {file = "yarl-1.18.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f52a265001d830bc425f82ca9eabda94a64a4d753b07d623a9f2863fde532b53"}, - {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:82123d0c954dc58db301f5021a01854a85bf1f3bb7d12ae0c01afc414a882ca2"}, - {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2ec9bbba33b2d00999af4631a3397d1fd78290c48e2a3e52d8dd72db3a067ac8"}, - {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:fbd6748e8ab9b41171bb95c6142faf068f5ef1511935a0aa07025438dd9a9bc1"}, - {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:877d209b6aebeb5b16c42cbb377f5f94d9e556626b1bfff66d7b0d115be88d0a"}, - {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:b464c4ab4bfcb41e3bfd3f1c26600d038376c2de3297760dfe064d2cb7ea8e10"}, - {file = "yarl-1.18.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8d39d351e7faf01483cc7ff7c0213c412e38e5a340238826be7e0e4da450fdc8"}, - {file = "yarl-1.18.3-cp313-cp313-win32.whl", hash = "sha256:61ee62ead9b68b9123ec24bc866cbef297dd266175d53296e2db5e7f797f902d"}, - {file = "yarl-1.18.3-cp313-cp313-win_amd64.whl", hash = "sha256:578e281c393af575879990861823ef19d66e2b1d0098414855dd367e234f5b3c"}, - {file = "yarl-1.18.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:61e5e68cb65ac8f547f6b5ef933f510134a6bf31bb178be428994b0cb46c2a04"}, - {file = "yarl-1.18.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fe57328fbc1bfd0bd0514470ac692630f3901c0ee39052ae47acd1d90a436719"}, - {file = "yarl-1.18.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a440a2a624683108a1b454705ecd7afc1c3438a08e890a1513d468671d90a04e"}, - {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09c7907c8548bcd6ab860e5f513e727c53b4a714f459b084f6580b49fa1b9cee"}, - {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b4f6450109834af88cb4cc5ecddfc5380ebb9c228695afc11915a0bf82116789"}, - {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9ca04806f3be0ac6d558fffc2fdf8fcef767e0489d2684a21912cc4ed0cd1b8"}, - {file = "yarl-1.18.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77a6e85b90a7641d2e07184df5557132a337f136250caafc9ccaa4a2a998ca2c"}, - {file = "yarl-1.18.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6333c5a377c8e2f5fae35e7b8f145c617b02c939d04110c76f29ee3676b5f9a5"}, - {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0b3c92fa08759dbf12b3a59579a4096ba9af8dd344d9a813fc7f5070d86bbab1"}, - {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:4ac515b860c36becb81bb84b667466885096b5fc85596948548b667da3bf9f24"}, - {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:045b8482ce9483ada4f3f23b3774f4e1bf4f23a2d5c912ed5170f68efb053318"}, - {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:a4bb030cf46a434ec0225bddbebd4b89e6471814ca851abb8696170adb163985"}, - {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:54d6921f07555713b9300bee9c50fb46e57e2e639027089b1d795ecd9f7fa910"}, - {file = "yarl-1.18.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1d407181cfa6e70077df3377938c08012d18893f9f20e92f7d2f314a437c30b1"}, - {file = "yarl-1.18.3-cp39-cp39-win32.whl", hash = "sha256:ac36703a585e0929b032fbaab0707b75dc12703766d0b53486eabd5139ebadd5"}, - {file = "yarl-1.18.3-cp39-cp39-win_amd64.whl", hash = "sha256:ba87babd629f8af77f557b61e49e7c7cac36f22f871156b91e10a6e9d4f829e9"}, - {file = "yarl-1.18.3-py3-none-any.whl", hash = "sha256:b57f4f58099328dfb26c6a771d09fb20dbbae81d20cfb66141251ea063bd101b"}, - {file = "yarl-1.18.3.tar.gz", hash = "sha256:ac1801c45cbf77b6c99242eeff4fffb5e4e73a800b5c4ad4fc0be5def634d2e1"}, + {file = "yarl-1.19.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0bae32f8ebd35c04d6528cedb4a26b8bf25339d3616b04613b97347f919b76d3"}, + {file = "yarl-1.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8015a076daf77823e7ebdcba474156587391dab4e70c732822960368c01251e6"}, + {file = "yarl-1.19.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9973ac95327f5d699eb620286c39365990b240031672b5c436a4cd00539596c5"}, + {file = "yarl-1.19.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd4b5fbd7b9dde785cfeb486b8cca211a0b138d4f3a7da27db89a25b3c482e5c"}, + {file = "yarl-1.19.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:75460740005de5a912b19f657848aef419387426a40f581b1dc9fac0eb9addb5"}, + {file = "yarl-1.19.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:57abd66ca913f2cfbb51eb3dbbbac3648f1f6983f614a4446e0802e241441d2a"}, + {file = "yarl-1.19.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:46ade37911b7c99ce28a959147cb28bffbd14cea9e7dd91021e06a8d2359a5aa"}, + {file = "yarl-1.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8346ec72ada749a6b5d82bff7be72578eab056ad7ec38c04f668a685abde6af0"}, + {file = "yarl-1.19.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e4cb14a6ee5b6649ccf1c6d648b4da9220e8277d4d4380593c03cc08d8fe937"}, + {file = "yarl-1.19.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:66fc1c2926a73a2fb46e4b92e3a6c03904d9bc3a0b65e01cb7d2b84146a8bd3b"}, + {file = "yarl-1.19.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:5a70201dd1e0a4304849b6445a9891d7210604c27e67da59091d5412bc19e51c"}, + {file = "yarl-1.19.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e4807aab1bdeab6ae6f296be46337a260ae4b1f3a8c2fcd373e236b4b2b46efd"}, + {file = "yarl-1.19.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ae584afe81a1de4c1bb06672481050f0d001cad13163e3c019477409f638f9b7"}, + {file = "yarl-1.19.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:30eaf4459df6e91f21b2999d1ee18f891bcd51e3cbe1de301b4858c84385895b"}, + {file = "yarl-1.19.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0e617d45d03c8dec0dfce6f51f3e1b8a31aa81aaf4a4d1442fdb232bcf0c6d8c"}, + {file = "yarl-1.19.0-cp310-cp310-win32.whl", hash = "sha256:32ba32d0fa23893fd8ea8d05bdb05de6eb19d7f2106787024fd969f4ba5466cb"}, + {file = "yarl-1.19.0-cp310-cp310-win_amd64.whl", hash = "sha256:545575ecfcd465891b51546c2bcafdde0acd2c62c2097d8d71902050b20e4922"}, + {file = "yarl-1.19.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:163ff326680de5f6d4966954cf9e3fe1bf980f5fee2255e46e89b8cf0f3418b5"}, + {file = "yarl-1.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a626c4d9cca298d1be8625cff4b17004a9066330ac82d132bbda64a4c17c18d3"}, + {file = "yarl-1.19.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:961c3e401ea7f13d02b8bb7cb0c709152a632a6e14cdc8119e9c6ee5596cd45d"}, + {file = "yarl-1.19.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a39d7b807ab58e633ed760f80195cbd145b58ba265436af35f9080f1810dfe64"}, + {file = "yarl-1.19.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c4228978fb59c6b10f60124ba8e311c26151e176df364e996f3f8ff8b93971b5"}, + {file = "yarl-1.19.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9ba536b17ecf3c74a94239ec1137a3ad3caea8c0e4deb8c8d2ffe847d870a8c5"}, + {file = "yarl-1.19.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a251e00e445d2e9df7b827c9843c0b87f58a3254aaa3f162fb610747491fe00f"}, + {file = "yarl-1.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9b92431d8b4d4ca5ccbfdbac95b05a3a6cd70cd73aa62f32f9627acfde7549c"}, + {file = "yarl-1.19.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec2f56edaf476f70b5831bbd59700b53d9dd011b1f77cd4846b5ab5c5eafdb3f"}, + {file = "yarl-1.19.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:acf9b92c4245ac8b59bc7ec66a38d3dcb8d1f97fac934672529562bb824ecadb"}, + {file = "yarl-1.19.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:57711f1465c06fee8825b95c0b83e82991e6d9425f9a042c3c19070a70ac92bf"}, + {file = "yarl-1.19.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:528e86f5b1de0ad8dd758ddef4e0ed24f5d946d4a1cef80ffb2d4fca4e10f122"}, + {file = "yarl-1.19.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:3b77173663e075d9e5a57e09d711e9da2f3266be729ecca0b8ae78190990d260"}, + {file = "yarl-1.19.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d8717924cf0a825b62b1a96fc7d28aab7f55a81bf5338b8ef41d7a76ab9223e9"}, + {file = "yarl-1.19.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0df9f0221a78d858793f40cbea3915c29f969c11366646a92ca47e080a14f881"}, + {file = "yarl-1.19.0-cp311-cp311-win32.whl", hash = "sha256:8b3ade62678ee2c7c10dcd6be19045135e9badad53108f7d2ed14896ee396045"}, + {file = "yarl-1.19.0-cp311-cp311-win_amd64.whl", hash = "sha256:0626ee31edb23ac36bdffe607231de2cca055ad3a5e2dc5da587ef8bc6a321bc"}, + {file = "yarl-1.19.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7b687c334da3ff8eab848c9620c47a253d005e78335e9ce0d6868ed7e8fd170b"}, + {file = "yarl-1.19.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b0fe766febcf523a2930b819c87bb92407ae1368662c1bc267234e79b20ff894"}, + {file = "yarl-1.19.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:742ceffd3c7beeb2b20d47cdb92c513eef83c9ef88c46829f88d5b06be6734ee"}, + {file = "yarl-1.19.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2af682a1e97437382ee0791eacbf540318bd487a942e068e7e0a6c571fadbbd3"}, + {file = "yarl-1.19.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:63702f1a098d0eaaea755e9c9d63172be1acb9e2d4aeb28b187092bcc9ca2d17"}, + {file = "yarl-1.19.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3560dcba3c71ae7382975dc1e912ee76e50b4cd7c34b454ed620d55464f11876"}, + {file = "yarl-1.19.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:68972df6a0cc47c8abaf77525a76ee5c5f6ea9bbdb79b9565b3234ded3c5e675"}, + {file = "yarl-1.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5684e7ff93ea74e47542232bd132f608df4d449f8968fde6b05aaf9e08a140f9"}, + {file = "yarl-1.19.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8182ad422bfacdebd4759ce3adc6055c0c79d4740aea1104e05652a81cd868c6"}, + {file = "yarl-1.19.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aee5b90a5a9b71ac57400a7bdd0feaa27c51e8f961decc8d412e720a004a1791"}, + {file = "yarl-1.19.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:8c0b2371858d5a814b08542d5d548adb03ff2d7ab32f23160e54e92250961a72"}, + {file = "yarl-1.19.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cd430c2b7df4ae92498da09e9b12cad5bdbb140d22d138f9e507de1aa3edfea3"}, + {file = "yarl-1.19.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a93208282c0ccdf73065fd76c6c129bd428dba5ff65d338ae7d2ab27169861a0"}, + {file = "yarl-1.19.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:b8179280cdeb4c36eb18d6534a328f9d40da60d2b96ac4a295c5f93e2799e9d9"}, + {file = "yarl-1.19.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eda3c2b42dc0c389b7cfda2c4df81c12eeb552019e0de28bde8f913fc3d1fcf3"}, + {file = "yarl-1.19.0-cp312-cp312-win32.whl", hash = "sha256:57f3fed859af367b9ca316ecc05ce79ce327d6466342734305aa5cc380e4d8be"}, + {file = "yarl-1.19.0-cp312-cp312-win_amd64.whl", hash = "sha256:5507c1f7dd3d41251b67eecba331c8b2157cfd324849879bebf74676ce76aff7"}, + {file = "yarl-1.19.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:59281b9ed27bc410e0793833bcbe7fc149739d56ffa071d1e0fe70536a4f7b61"}, + {file = "yarl-1.19.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d27a6482ad5e05e8bafd47bf42866f8a1c0c3345abcb48d4511b3c29ecc197dc"}, + {file = "yarl-1.19.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7a8e19fd5a6fdf19a91f2409665c7a089ffe7b9b5394ab33c0eec04cbecdd01f"}, + {file = "yarl-1.19.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cda34ab19099c3a1685ad48fe45172536610c312b993310b5f1ca3eb83453b36"}, + {file = "yarl-1.19.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7908a25d33f94852b479910f9cae6cdb9e2a509894e8d5f416c8342c0253c397"}, + {file = "yarl-1.19.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e66c14d162bac94973e767b24de5d7e6c5153f7305a64ff4fcba701210bcd638"}, + {file = "yarl-1.19.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c03607bf932aa4cfae371e2dc9ca8b76faf031f106dac6a6ff1458418140c165"}, + {file = "yarl-1.19.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9931343d1c1f4e77421687b6b94bbebd8a15a64ab8279adf6fbb047eff47e536"}, + {file = "yarl-1.19.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:262087a8a0d73e1d169d45c2baf968126f93c97cf403e1af23a7d5455d52721f"}, + {file = "yarl-1.19.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:70f384921c24e703d249a6ccdabeb57dd6312b568b504c69e428a8dd3e8e68ca"}, + {file = "yarl-1.19.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:756b9ea5292a2c180d1fe782a377bc4159b3cfefaca7e41b5b0a00328ef62fa9"}, + {file = "yarl-1.19.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cbeb9c145d534c240a63b6ecc8a8dd451faeb67b3dc61d729ec197bb93e29497"}, + {file = "yarl-1.19.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:087ae8f8319848c18e0d114d0f56131a9c017f29200ab1413b0137ad7c83e2ae"}, + {file = "yarl-1.19.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362f5480ba527b6c26ff58cff1f229afe8b7fdd54ee5ffac2ab827c1a75fc71c"}, + {file = "yarl-1.19.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f408d4b4315e814e5c3668094e33d885f13c7809cbe831cbdc5b1bb8c7a448f4"}, + {file = "yarl-1.19.0-cp313-cp313-win32.whl", hash = "sha256:24e4c367ad69988a2283dd45ea88172561ca24b2326b9781e164eb46eea68345"}, + {file = "yarl-1.19.0-cp313-cp313-win_amd64.whl", hash = "sha256:0110f91c57ab43d1538dfa92d61c45e33b84df9257bd08fcfcda90cce931cbc9"}, + {file = "yarl-1.19.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:85ac908cd5a97bbd3048cca9f1bf37b932ea26c3885099444f34b0bf5d5e9fa6"}, + {file = "yarl-1.19.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6ba0931b559f1345df48a78521c31cfe356585670e8be22af84a33a39f7b9221"}, + {file = "yarl-1.19.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5bc503e1c1fee1b86bcb58db67c032957a52cae39fe8ddd95441f414ffbab83e"}, + {file = "yarl-1.19.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d995122dcaf180fd4830a9aa425abddab7c0246107c21ecca2fa085611fa7ce9"}, + {file = "yarl-1.19.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:217f69e60a14da4eed454a030ea8283f8fbd01a7d6d81e57efb865856822489b"}, + {file = "yarl-1.19.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aad67c8f13a4b79990082f72ef09c078a77de2b39899aabf3960a48069704973"}, + {file = "yarl-1.19.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dff065a1a8ed051d7e641369ba1ad030d5a707afac54cf4ede7069b959898835"}, + {file = "yarl-1.19.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ada882e26b16ee651ab6544ce956f2f4beaed38261238f67c2a96db748e17741"}, + {file = "yarl-1.19.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:67a56b1acc7093451ea2de0687aa3bd4e58d6b4ef6cbeeaad137b45203deaade"}, + {file = "yarl-1.19.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e97d2f0a06b39e231e59ebab0e6eec45c7683b339e8262299ac952707bdf7688"}, + {file = "yarl-1.19.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:a5288adb7c59d0f54e4ad58d86fb06d4b26e08a59ed06d00a1aac978c0e32884"}, + {file = "yarl-1.19.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1efbf4d03e6eddf5da27752e0b67a8e70599053436e9344d0969532baa99df53"}, + {file = "yarl-1.19.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:f228f42f29cc87db67020f7d71624102b2c837686e55317b16e1d3ef2747a993"}, + {file = "yarl-1.19.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c515f7dd60ca724e4c62b34aeaa603188964abed2eb66bb8e220f7f104d5a187"}, + {file = "yarl-1.19.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:4815ec6d3d68a96557fa71bd36661b45ac773fb50e5cfa31a7e843edb098f060"}, + {file = "yarl-1.19.0-cp39-cp39-win32.whl", hash = "sha256:9fac2dd1c5ecb921359d9546bc23a6dcc18c6acd50c6d96f118188d68010f497"}, + {file = "yarl-1.19.0-cp39-cp39-win_amd64.whl", hash = "sha256:5864f539ce86b935053bfa18205fa08ce38e9a40ea4d51b19ce923345f0ed5db"}, + {file = "yarl-1.19.0-py3-none-any.whl", hash = "sha256:a727101eb27f66727576630d02985d8a065d09cd0b5fcbe38a5793f71b2a97ef"}, + {file = "yarl-1.19.0.tar.gz", hash = "sha256:01e02bb80ae0dbed44273c304095295106e1d9470460e773268a27d11e594892"}, ] [package.dependencies] idna = ">=2.0" multidict = ">=4.0" -propcache = ">=0.2.0" +propcache = ">=0.2.1" [[package]] name = "zipp" @@ -2494,5 +2504,5 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" -python-versions = "^3.10" -content-hash = "d8a57713ce1fa2468ce3e79f2ecb9fb3f87f50fc9422d6cc781bfdbcaece4ff0" +python-versions = ">=3.10,<3.13" +content-hash = "9fc72ac7cc2c1e45cb496713d33411da48022a2a908a3a5d02e325f74e6dad2d" From 6e3e8658b81b0b08574372a595d63e898f5cf131 Mon Sep 17 00:00:00 2001 From: monoxgas Date: Thu, 17 Apr 2025 21:36:36 -0600 Subject: [PATCH 10/24] Fixes for pydantic schema generation failures in serialization --- dreadnode/serialization.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/dreadnode/serialization.py b/dreadnode/serialization.py index 9709a60f..25f8c7af 100644 --- a/dreadnode/serialization.py +++ b/dreadnode/serialization.py @@ -328,7 +328,16 @@ def _handle_pydantic_model(obj: t.Any, _seen: set[int]) -> tuple[JsonValue, Json if not isinstance(obj, pydantic.BaseModel): return safe_repr(obj), UNKNOWN_OBJECT_SCHEMA - return obj.model_dump(mode="json"), obj.model_json_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( From a4b664632afffa52b80ecfd850e6466af1389bf2 Mon Sep 17 00:00:00 2001 From: monoxgas Date: Fri, 18 Apr 2025 17:22:33 -0600 Subject: [PATCH 11/24] Fixing some serialization behavior related to primitives and Nones. Reduced some nested json serialization. --- dreadnode/constants.py | 2 +- dreadnode/serialization.py | 31 +++++++++++++++++++++++++------ dreadnode/task.py | 3 ++- dreadnode/tracing/span.py | 9 +++++---- 4 files changed, 33 insertions(+), 12 deletions(-) diff --git a/dreadnode/constants.py b/dreadnode/constants.py index 8b2b2f1f..70c70642 100644 --- a/dreadnode/constants.py +++ b/dreadnode/constants.py @@ -13,4 +13,4 @@ DEFAULT_LOCAL_OBJECT_DIR = ".dreadnode/objects" # Default values for the S3 storage -MAX_INLINE_OBJECT_BYTES = 1 * 1024 * 1024 # 1MB +MAX_INLINE_OBJECT_BYTES = 10 * 1024 # 10KB diff --git a/dreadnode/serialization.py b/dreadnode/serialization.py index 25f8c7af..1f9d3f6b 100644 --- a/dreadnode/serialization.py +++ b/dreadnode/serialization.py @@ -594,12 +594,17 @@ def _serialize(obj: t.Any, seen: set[int] | None = None) -> tuple[JsonValue, Jso @dataclasses.dataclass class Serialized: - data: str + 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 @@ -613,13 +618,27 @@ def serialize(obj: t.Any) -> Serialized: An object containing the serialized data, schema, and their hashes. """ serialized, schema = _serialize(obj) - serialized_str = json.dumps(serialized, separators=(",", ":")) + + 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 = hashlib.sha1(serialized_str.encode()).hexdigest()[:16] # noqa: S324 (using sha1 for speed) - schema_hash = hashlib.sha1(schema_str.encode()).hexdigest()[:16] # noqa: S324 + + 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_str, - schema=schema, + 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 c4442a4a..4ca99fe2 100644 --- a/dreadnode/task.py +++ b/dreadnode/task.py @@ -39,7 +39,8 @@ def top_n( ... @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, diff --git a/dreadnode/tracing/span.py b/dreadnode/tracing/span.py index 6691bdec..231520f8 100644 --- a/dreadnode/tracing/span.py +++ b/dreadnode/tracing/span.py @@ -368,10 +368,11 @@ def log_object( def _create_object(self, serialized: Serialized) -> Object: """Create an ObjectVal or ObjectUri depending on size.""" data = serialized.data + data_len = serialized.data_len data_hash = serialized.data_hash schema_hash = serialized.schema_hash - if len(data) <= MAX_INLINE_OBJECT_BYTES: + if data is None or data_len <= MAX_INLINE_OBJECT_BYTES: return ObjectVal( hash=data_hash, value=data, @@ -380,14 +381,14 @@ def _create_object(self, serialized: Serialized) -> Object: # Offload to file system (e.g., S3) full_path = f"{self._prefix_path.rstrip('/')}/{data_hash}" - with self._file_system.open(full_path, "w") as f: - f.write(data) + with self._file_system.open(full_path, "wb") as f: + f.write(serialized.data_bytes) return ObjectUri( hash=data_hash, uri=self._file_system.unstrip_protocol(full_path), schema_hash=schema_hash, - size=len(data), + size=data_len, ) def get_object(self, hash_: str) -> t.Any: From ec00aac151ed68c411895c6ddb527d161d921473 Mon Sep 17 00:00:00 2001 From: monoxgas Date: Sun, 20 Apr 2025 13:24:54 -0600 Subject: [PATCH 12/24] Lots of docstring writing --- dreadnode/context.py | 182 ------------- dreadnode/integrations/transformers.py | 17 +- dreadnode/main.py | 362 ++++++++++++++++++++++++- dreadnode/metric.py | 73 ++++- dreadnode/storage.py | 164 ----------- dreadnode/task.py | 176 +++++++++++- 6 files changed, 617 insertions(+), 357 deletions(-) delete mode 100644 dreadnode/context.py delete mode 100644 dreadnode/storage.py diff --git a/dreadnode/context.py b/dreadnode/context.py deleted file mode 100644 index 2b88ea39..00000000 --- a/dreadnode/context.py +++ /dev/null @@ -1,182 +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, - ) -> None: - self.id_ = id_ - self.default = default - super().__init__(cast=False, 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}: {e!s}") - - 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: - 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] - if len(matching) > 1 and strict: - raise ValueError( - f"Multiple {type_.__name__} objects exist. Use a specific id to get one of the values.", - ) - elif 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] - if len(matching) > 1 and strict: - raise ValueError( - f"Multiple {type_.__name__} objects exist. Use a specific id to get one of the values.", - ) - elif 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: object) -> 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 - - -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..a54d71c9 100644 --- a/dreadnode/integrations/transformers.py +++ b/dreadnode/integrations/transformers.py @@ -27,6 +27,13 @@ def _clean_keys(data: dict[str, t.Any]) -> dict[str, t.Any]: 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 +74,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 +107,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 2a3cde2b..a5d240d6 100644 --- a/dreadnode/main.py +++ b/dreadnode/main.py @@ -65,6 +65,14 @@ class DreadnodeConfigWarning(UserWarning): @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] @@ -110,16 +118,40 @@ def __init__( def configure( self, + *, server: str | None = None, token: str | None = None, - local_dir: str | Path | t.Literal[False] = False, # noqa: FBT002 + local_dir: str | Path | t.Literal[False] = False, project: str | None = None, service_name: str | None = None, service_version: str | None = None, - console: logfire.ConsoleOptions | t.Literal[False, True] = True, # noqa: FBT002 + console: logfire.ConsoleOptions | t.Literal[False, True] = True, 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) or os.environ.get(ENV_SERVER) @@ -144,6 +176,11 @@ 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 @@ -154,7 +191,8 @@ def initialize(self) -> None: 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, ) @@ -229,9 +267,20 @@ 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, *, 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) @@ -251,6 +300,15 @@ def _get_tracer(self, *, is_span_tracer: bool = True) -> Tracer: ) 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 @@ -263,6 +321,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, @@ -312,6 +392,33 @@ def task( tags: t.Sequence[str] | None = None, **attributes: t.Any, ) -> t.Callable[[t.Callable[P, t.Awaitable[R]] | t.Callable[P, R]], Task[P, R]]: + """ + 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]: func = inspect.unwrap(func) @@ -370,6 +477,28 @@ def task_span( tags: t.Sequence[str] | 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_span() must be called within a run") @@ -391,6 +520,34 @@ def scorer( 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(), @@ -411,6 +568,31 @@ def run( 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: A dictionary of attributes to attach to the run. + """ if not self._initialized: self.initialize() @@ -429,15 +611,71 @@ def run( ) 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, *, to: ToObject = "task-or-run") -> None: + """ + Log a single parameter 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_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}) def log_params(self, to: ToObject = "task-or-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() @@ -465,6 +703,29 @@ def log_metric( 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 @@ -476,6 +737,27 @@ def log_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. + """ ... def log_metric( @@ -519,6 +801,25 @@ def log_input( 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() @@ -540,6 +841,11 @@ def log_inputs( 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) @@ -552,6 +858,26 @@ def log_output( 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() @@ -573,10 +899,36 @@ def log_outputs( 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) 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") diff --git a/dreadnode/metric.py b/dreadnode/metric.py index dcc20539..2188b961 100644 --- a/dreadnode/metric.py +++ b/dreadnode/metric.py @@ -13,10 +13,18 @@ @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( @@ -25,7 +33,23 @@ def from_many( step: int = 0, **attributes: JsonValue, ) -> "Metric": - "Create a composite metric from individual values and weights." + """ + 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} @@ -43,9 +67,17 @@ 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( @@ -55,8 +87,21 @@ def from_callable( *, name: str | None = None, tags: t.Sequence[str] | None = None, - attributes: dict[str, t.Any] | 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() @@ -84,15 +129,34 @@ def __post_init__(self) -> None: 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( @@ -108,9 +172,12 @@ async def __call__(self, object: T) -> Metric: if not isinstance(metric, Metric): metric = Metric( float(metric), - step=0, # Do we integrate an increment/state system here? + 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/storage.py b/dreadnode/storage.py deleted file mode 100644 index e995b870..00000000 --- a/dreadnode/storage.py +++ /dev/null @@ -1,164 +0,0 @@ -# New storage.py module -import io -import os -from abc import ABC, abstractmethod - -import fsspec - -from .artifact import Artifact, ArtifactMetadata - - -class ArtifactStorage(ABC): - """Base class for artifact storage.""" - - @abstractmethod - def save_artifact(self, run_id: str, artifact: Artifact) -> str: - """Save an artifact and return its URI.""" - pass - - @abstractmethod - def get_artifact(self, run_id: str, artifact_path: str) -> io.BytesIO: - """Get an artifact's content.""" - pass - - @abstractmethod - def list_artifacts(self, run_id: str, path: str | None = None) -> list[ArtifactMetadata]: - """List artifacts for a run.""" - pass - - def get_artifact_uri(self, run_id: str, artifact_path: str) -> str: - """Get a URI for referencing an artifact.""" - return f"artifact://{run_id}/{artifact_path}" - - -class FsspecArtifactStorage(ArtifactStorage): - """Artifact storage using fsspec.""" - - def __init__(self, base_uri: str): - """Initialize with a URI that fsspec can handle.""" - self.base_uri = base_uri - # Make sure base URI ends with a slash - if not self.base_uri.endswith("/"): - self.base_uri += "/" - - # Create content store for deduplication - self.content_store_uri = f"{self.base_uri}content_store/" - fs, _, _ = fsspec.get_fs_token_paths(self.content_store_uri) - fs.makedirs(self.content_store_uri, exist_ok=True) - - def _get_run_path(self, run_id: str, artifact_path: str | None = None) -> str: - """Get the full path for an artifact in a run.""" - if artifact_path: - return f"{self.base_uri}{run_id}/artifacts/{artifact_path}" - else: - return f"{self.base_uri}{run_id}/artifacts/" - - def _get_content_path(self, content_hash: str) -> str: - """Get the path for a content hash in the content store.""" - return f"{self.content_store_uri}{content_hash}" - - def save_artifact(self, run_id: str, artifact: Artifact) -> str: - """Save an artifact using content addressing for deduplication.""" - # Set run_id if not already set - if artifact.run_id is None: - artifact.run_id = run_id - - # Get content hash path - content_path = self._get_content_path(artifact.metadata.content_hash) - - # Check if content already exists - fs, _, _ = fsspec.get_fs_token_paths(content_path) - - # Save content if it doesn't exist - if not fs.exists(content_path): - if artifact.local_path: - with open(artifact.local_path, "rb") as f_src, fs.open(content_path, "wb") as f_dest: - f_dest.write(f_src.read()) - elif artifact.content: - with fs.open(content_path, "wb") as f: - f.write(artifact.content) - else: - raise ValueError("Artifact has no content to save") - - # Now create the artifact reference - artifact_path = self._get_run_path(run_id, artifact.metadata.path) - - # Ensure parent directories exist - parent_dir = os.path.dirname(artifact_path) - fs.makedirs(parent_dir, exist_ok=True) - - # Create the artifact reference (a JSON file with metadata) - metadata = { - "content_hash": artifact.metadata.content_hash, - "size": artifact.metadata.size, - "content_type": artifact.metadata.content_type, - "description": artifact.metadata.description, - "created_at": artifact.metadata.created_at.isoformat(), - "custom_metadata": artifact.metadata.custom_metadata, - } - - # Write metadata - with fs.open(f"{artifact_path}.meta", "w") as f: - import json - - json.dump(metadata, f) - - return artifact.metadata.path - - def get_artifact(self, run_id: str, artifact_path: str) -> io.BytesIO: - """Get an artifact's content.""" - # Get metadata to find content hash - meta_path = f"{self._get_run_path(run_id, artifact_path)}.meta" - fs, _, _ = fsspec.get_fs_token_paths(meta_path) - - if not fs.exists(meta_path): - raise FileNotFoundError(f"Artifact not found: {artifact_path}") - - # Read metadata to get content hash - with fs.open(meta_path, "r") as f: - import json - - metadata = json.load(f) - - # Get content from content store - content_path = self._get_content_path(metadata["content_hash"]) - - # Read content - with fs.open(content_path, "rb") as f: - return io.BytesIO(f.read()) - - def list_artifacts(self, run_id: str, path: str | None = None) -> list[ArtifactMetadata]: - """List artifacts for a run.""" - base_path = self._get_run_path(run_id, path) - fs, _, _ = fsspec.get_fs_token_paths(base_path) - - if not fs.exists(base_path): - return [] - - artifacts = [] - - # List all .meta files in the directory - for item in fs.glob(f"{base_path}/**/*.meta"): - # Read metadata - with fs.open(item, "r") as f: - import json - - metadata = json.load(f) - - # Get artifact path (strip .meta from the end) - rel_path = os.path.relpath(item[:-5], self._get_run_path(run_id, "")) - - # Create artifact metadata - artifact_meta = ArtifactMetadata( - path=rel_path, - content_hash=metadata["content_hash"], - size=metadata["size"], - content_type=metadata["content_type"], - description=metadata.get("description", ""), - created_at=datetime.fromisoformat(metadata["created_at"]), - custom_metadata=metadata.get("custom_metadata", {}), - ) - - artifacts.append(artifact_meta) - - return artifacts diff --git a/dreadnode/task.py b/dreadnode/task.py index 4ca99fe2..7148d5c9 100644 --- a/dreadnode/task.py +++ b/dreadnode/task.py @@ -23,7 +23,20 @@ class TaskGeneratorWarning(UserWarning): 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]": + """ + 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), ) @@ -49,6 +62,17 @@ def top_n( 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_]) @@ -59,18 +83,33 @@ def top_n( @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] + "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] | t.Literal[True] | None = None + "Whether to log all, or specific, incoming arguments to the function as parameters." log_inputs: t.Sequence[str] | t.Literal[True] | None = None + "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__ = inspect.signature(self.func) @@ -83,14 +122,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_( @@ -100,12 +148,35 @@ def with_( name: str | None = None, tags: t.Sequence[str] | None = None, label: str | None = None, + log_params: t.Sequence[str] | t.Literal[True] | None = None, + log_inputs: t.Sequence[str] | t.Literal[True] | 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 []) @@ -122,6 +193,16 @@ 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") @@ -174,18 +255,63 @@ async def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R: # 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]: + """ + 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] 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: # noqa: BLE001 @@ -196,17 +322,65 @@ async def try_run(self, *args: P.args, **kwargs: P.kwargs) -> TaskSpan[R] | None 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]: + """ + 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]: + """ + 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] From 77bbc9355729bb789c65abf30ee649870d6a356e Mon Sep 17 00:00:00 2001 From: Raja Sekhar Rao Dheekonda <43563047+rdheekonda@users.noreply.github.com> Date: Mon, 21 Apr 2025 11:07:25 -0700 Subject: [PATCH 13/24] Add audio and video serialize handlers (#3) --- dreadnode/artifact.py | 68 ---------- dreadnode/serialization.py | 92 +++++++++++++- poetry.lock | 252 +++++++++++++++++++++++++++++++++++++ pyproject.toml | 2 + 4 files changed, 344 insertions(+), 70 deletions(-) delete mode 100644 dreadnode/artifact.py diff --git a/dreadnode/artifact.py b/dreadnode/artifact.py deleted file mode 100644 index 27269238..00000000 --- a/dreadnode/artifact.py +++ /dev/null @@ -1,68 +0,0 @@ -import mimetypes -import typing as t -from dataclasses import dataclass - -from dreadnode.object import ObjectRef -from dreadnode.types import AnyDict - -ArtifactHint = t.Literal[ - "markdown", - "table", - "code", - "image", - "csv", - "dataframe", - "notebook", - "file", -] - - -@dataclass -class Artifact(ObjectRef): - hint: ArtifactHint - description: str - attributes: AnyDict - mime_type: str - extension: str - - -def get_mime_type_and_extension(data: t.Any) -> tuple[str, str]: - """Get the file extension and MIME type for the given data. - - This method determines the file extension and MIME type for the given data based on its content. - - Args: - data: The data to determine the file extension and MIME type for. - - Returns: - The file extension and MIME type for the given data. - """ - mime_type: str - extension: str - - if isinstance(data, str): - mime_type = "text/plain" - extension = ".txt" - - elif isinstance(data, dict): - mime_type = "application/json" - extension = ".json" - - # Handle binary data - elif isinstance(data, bytes): - # Use python-magic to detect file type - mime = magic.Magic(mime=True) - mime_type = mime.from_buffer(data) - - # Get extension from MIME type - extension = mimetypes.guess_extension(mime_type) or "" - if not extension and mime_type == "image/jpeg": - extension = ".jpg" # Common case - - # Handle other types - else: - mime_type = "application/octet-stream" - extension = ".bin" - - # get the original - return ArtifactMetadata(mime_type=mime_type, extension=extension) diff --git a/dreadnode/serialization.py b/dreadnode/serialization.py index 1f9d3f6b..895bcb8e 100644 --- a/dreadnode/serialization.py +++ b/dreadnode/serialization.py @@ -398,18 +398,94 @@ def _handle_pil_image( return safe_repr(obj), UNKNOWN_OBJECT_SCHEMA buffer = io.BytesIO() - obj.save(buffer, format="PNG") + 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": "png", + "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] + + 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 VideoFileClip # type: ignore[import-untyped] + + 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, + ) + with open(temp_file.name, "rb") as f: + raw_bytes_data = f.read() + + 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] @@ -520,6 +596,18 @@ def _get_handlers() -> dict[type, HandlerFunc]: 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 diff --git a/poetry.lock b/poetry.lock index 00cf7db0..3dc25a8e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -398,6 +398,7 @@ 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\"", dev = "sys_platform == \"win32\""} [[package]] name = "coolname" @@ -411,6 +412,18 @@ files = [ {file = "coolname-2.2.0.tar.gz", hash = "sha256:6c5d5731759104479e7ca195a9b64f7900ac5bead40183c09323c7d0be9e75c7"}, ] +[[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" @@ -757,6 +770,57 @@ 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" @@ -905,6 +969,32 @@ files = [ {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" @@ -1398,6 +1488,104 @@ files = [ 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.7" @@ -1450,6 +1638,21 @@ 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" @@ -1768,6 +1971,18 @@ files = [ [package.dependencies] typing-extensions = ">=4.6.0,<4.7.0 || >4.7.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" @@ -1840,6 +2055,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"] +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" @@ -2158,6 +2388,28 @@ 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"] +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 = "types-protobuf" version = "5.29.1.20250403" diff --git a/pyproject.toml b/pyproject.toml index c23098d5..0b24f936 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,6 +55,8 @@ pandas = "^2.2.3" pyarrow = "^19.0.1" loguru = "^0.7.3" fsspec = {extras = ["s3"], version = "^2025.3.0"} +pydub = "^0.25.1" +moviepy = "^2.1.2" [tool.poetry.group.dev.dependencies] mypy = "^1.8.0" From 4ffe2e315236ebf6bcf2e278a8a1aa42f171cdf7 Mon Sep 17 00:00:00 2001 From: monoxgas Date: Mon, 21 Apr 2025 17:01:41 -0600 Subject: [PATCH 14/24] Adjust Task prop extraction for decorator nesting --- dreadnode/task.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dreadnode/task.py b/dreadnode/task.py index 7148d5c9..36da22fe 100644 --- a/dreadnode/task.py +++ b/dreadnode/task.py @@ -112,8 +112,9 @@ class Task(t.Generic[P, R]): "Whether to automatically log the result of the function as an output." def __post_init__(self) -> None: - self.__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) def _bind_args(self, *args: P.args, **kwargs: P.kwargs) -> dict[str, t.Any]: signature = inspect.signature(self.func) From fdf23e1ccae2431aa8dff8023c36dc49cb3f4c86 Mon Sep 17 00:00:00 2001 From: monoxgas Date: Mon, 21 Apr 2025 21:08:21 -0600 Subject: [PATCH 15/24] README updates --- .github/CONTRIBUTING.md | 8 --- .github/labeler.yaml | 1 + .pre-commit-config.yaml | 12 ----- README.md | 104 ++++++++++++++++++++++++++++++++++--- scripts/generate_readme.py | 103 ------------------------------------ templates/README.md.j2 | 13 ----- 6 files changed, 97 insertions(+), 144 deletions(-) delete mode 100755 scripts/generate_readme.py delete mode 100644 templates/README.md.j2 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/.pre-commit-config.yaml b/.pre-commit-config.yaml index b602daaf..8c1d7d84 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/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 %} From ac579e5bebf698e56857929261f8391003537572 Mon Sep 17 00:00:00 2001 From: monoxgas Date: Tue, 22 Apr 2025 15:55:26 -0600 Subject: [PATCH 16/24] hotfix: Trying to correct endpoint_url issues --- dreadnode/main.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/dreadnode/main.py b/dreadnode/main.py index a5d240d6..ff37db35 100644 --- a/dreadnode/main.py +++ b/dreadnode/main.py @@ -244,8 +244,10 @@ def initialize(self) -> None: key=credentials.access_key_id, secret=credentials.secret_access_key, token=credentials.session_token, - endpoint_url=credentials.endpoint, - client_kwargs={"region_name": credentials.region}, + client_kwargs={ + "endpoint_url": credentials.endpoint, + "region_name": credentials.region, + }, ) self._fs_prefix = f"{credentials.bucket}/{credentials.prefix}/" From 92a09f0c965f9f9d0d15c042e0a6045d20f61603 Mon Sep 17 00:00:00 2001 From: monoxgas Date: Tue, 22 Apr 2025 23:38:29 -0600 Subject: [PATCH 17/24] Fixing @task decorator typing --- dreadnode/main.py | 105 +++++++++++++++++++++++++++++++------- dreadnode/tracing/span.py | 26 +++++++--- 2 files changed, 106 insertions(+), 25 deletions(-) diff --git a/dreadnode/main.py b/dreadnode/main.py index b03594f1..ca959651 100644 --- a/dreadnode/main.py +++ b/dreadnode/main.py @@ -1,5 +1,3 @@ -from __future__ import annotations - import contextlib import inspect import os @@ -34,7 +32,11 @@ ) 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.exporters import ( + FileExportConfig, + FileMetricReader, + FileSpanExporter, +) from dreadnode.tracing.span import ( RunSpan, Span, @@ -42,6 +44,10 @@ current_run_span, current_task_span, ) +from dreadnode.types import ( + AnyDict, + JsonValue, +) from dreadnode.version import VERSION if t.TYPE_CHECKING: @@ -50,10 +56,6 @@ from opentelemetry.sdk.trace import SpanProcessor from opentelemetry.trace import Tracer - from dreadnode.types import ( - AnyDict, - JsonValue, - ) ToObject = t.Literal["task-or-run", "run"] @@ -293,7 +295,7 @@ def api(self, *, server: str | None = None, token: str | None = None) -> ApiClie return self._api - def _get_tracer(self, *, is_span_tracer: bool = True) -> Tracer: + def _get_tracer(self, *, is_span_tracer: bool = True) -> "Tracer": return self._logfire._tracer_provider.get_tracer( # noqa: SLF001 self.otel_scope, VERSION, @@ -351,6 +353,53 @@ 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, @@ -363,7 +412,7 @@ def task( log_output: bool = True, tags: t.Sequence[str] | None = None, **attributes: t.Any, - ) -> t.Callable[[t.Callable[P, t.Awaitable[R]] | t.Callable[P, R]], Task[P, R]]: + ) -> TaskDecorator: ... @t.overload @@ -378,13 +427,13 @@ def task( log_output: bool = True, tags: t.Sequence[str] | None = None, **attributes: t.Any, - ) -> t.Callable[[t.Callable[P, t.Awaitable[R]] | t.Callable[P, 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] | t.Literal[True] | None = None, @@ -392,7 +441,7 @@ def task( log_output: bool = True, tags: t.Sequence[str] | None = None, **attributes: t.Any, - ) -> t.Callable[[t.Callable[P, t.Awaitable[R]] | t.Callable[P, R]], Task[P, R]]: + ) -> TaskDecorator: """ Create a new task from a function. @@ -420,10 +469,14 @@ async def my_task(x: int) -> int: A new Task object. """ - def make_task(func: t.Callable[P, t.Awaitable[R]] | t.Callable[P, R]) -> Task[P, R]: + def make_task( + func: t.Callable[P, t.Awaitable[R]] | t.Callable[P, R], + ) -> Task[P, R]: unwrapped = inspect.unwrap(func) - if inspect.isgeneratorfunction(unwrapped) or inspect.isasyncgenfunction(unwrapped): + if inspect.isgeneratorfunction(unwrapped) or inspect.isasyncgenfunction( + unwrapped, + ): raise TypeError("@task cannot be applied to generators") func_name = getattr( @@ -631,7 +684,13 @@ def push_update(self) -> None: run.push_update() - def log_param(self, key: str, value: JsonValue, *, to: ToObject = "task-or-run") -> None: + def log_param( + self, + key: str, + value: JsonValue, + *, + to: ToObject = "task-or-run", + ) -> None: """ Log a single parameter to the current task or run. @@ -690,7 +749,9 @@ def log_params(self, to: ToObject = "task-or-run", **params: JsonValue) -> None: elif to == "run": if run is None: - raise RuntimeError("log_params() with to='run' must be called within a run") + raise RuntimeError( + "log_params() with to='run' must be called within a run", + ) run.log_params(**params) @t.overload @@ -790,7 +851,9 @@ def log_metric( elif to == "run": if run is None: - raise RuntimeError("log_metric() with to='run' must be called within a run") + raise RuntimeError( + "log_metric() with to='run' must be called within a run", + ) run.log_metric(key, metric, origin=origin) def log_input( @@ -834,7 +897,9 @@ async def my_task(x: int) -> int: elif to == "run": if run is None: - raise RuntimeError("log_inputs() with to='run' must be called within a run") + raise RuntimeError( + "log_inputs() with to='run' must be called within a run", + ) run.log_input(name, value, label=label, **attributes) def log_inputs( @@ -892,7 +957,9 @@ async def my_task(x: int) -> int: elif to == "run": if run is None: - raise RuntimeError("log_output() with to='run' must be called within a run") + raise RuntimeError( + "log_output() with to='run' must be called within a run", + ) run.log_output(name, value, label=label, **attributes) def log_outputs( diff --git a/dreadnode/tracing/span.py b/dreadnode/tracing/span.py index 231520f8..96bb5e2e 100644 --- a/dreadnode/tracing/span.py +++ b/dreadnode/tracing/span.py @@ -65,7 +65,10 @@ "current_task_span", default=None, ) -current_run_span: ContextVar["RunSpan | None"] = ContextVar("current_run_span", default=None) +current_run_span: ContextVar["RunSpan | None"] = ContextVar( + "current_run_span", + default=None, +) class Span(ReadableSpan): @@ -285,7 +288,11 @@ def __exit__( 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_OBJECT_SCHEMAS, + self._object_schemas, + schema=False, + ) self.set_attribute(SPAN_ATTRIBUTE_OUTPUTS, self._outputs, schema=False) # Mark our objects attribute as large so it's stored separately @@ -394,7 +401,12 @@ def _create_object(self, serialized: Serialized) -> Object: 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: + def link_objects( + self, + object_hash: str, + link_hash: str, + **attributes: JsonValue, + ) -> None: self.log_event( name=EVENT_NAME_OBJECT_LINK, attributes={ @@ -420,8 +432,8 @@ def log_params(self, **params: t.Any) -> None: for key, value in params.items(): self._params[key] = value - if self._span is None: - return + # Always push updates for run params + self.push_update() @property def inputs(self) -> AnyDict: @@ -722,7 +734,9 @@ def get_average_metric_value(self, key: str | None = None) -> float: ) -def prepare_otlp_attributes(attributes: AnyDict) -> dict[str, otel_types.AttributeValue]: +def prepare_otlp_attributes( + attributes: AnyDict, +) -> dict[str, otel_types.AttributeValue]: return {key: prepare_otlp_attribute(value) for key, value in attributes.items()} From 65f2dc052e10922e3368ae922703f98f12a8f098 Mon Sep 17 00:00:00 2001 From: monoxgas Date: Wed, 23 Apr 2025 00:50:22 -0600 Subject: [PATCH 18/24] Object linking for tasks. --- dreadnode/main.py | 52 +++++++++---------- dreadnode/task.py | 103 +++++++++++++++++++++++++++++--------- dreadnode/tracing/span.py | 6 ++- 3 files changed, 107 insertions(+), 54 deletions(-) diff --git a/dreadnode/main.py b/dreadnode/main.py index ca959651..cb0d7a65 100644 --- a/dreadnode/main.py +++ b/dreadnode/main.py @@ -11,7 +11,9 @@ import coolname # type: ignore [import-untyped] import logfire -from fsspec.implementations.local import LocalFileSystem # type: ignore [import-untyped] +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 @@ -155,8 +157,12 @@ def configure( self._initialized = False - 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) + 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) @@ -363,42 +369,36 @@ class TaskDecorator(t.Protocol): def __call__( self, func: t.Callable[P, t.Awaitable[R]], - ) -> Task[P, R]: - ... + ) -> Task[P, R]: ... @t.overload def __call__( self, func: t.Callable[P, R], - ) -> Task[P, R]: - ... + ) -> Task[P, R]: ... def __call__( self, func: t.Callable[P, t.Awaitable[R]] | t.Callable[P, R], - ) -> Task[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]: - ... + ) -> Task[P, R]: ... @t.overload def __call__( self, func: t.Callable[P, R], - ) -> Task[P, R]: - ... + ) -> Task[P, R]: ... def __call__( self, func: t.Callable[P, t.Awaitable[R]] | t.Callable[P, R], - ) -> Task[P, R]: - ... + ) -> Task[P, R]: ... @t.overload def task( @@ -407,13 +407,12 @@ def task( scorers: None = None, name: str | None = None, label: str | None = None, - log_params: t.Sequence[str] | t.Literal[True] | None = None, - log_inputs: t.Sequence[str] | t.Literal[True] | 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, - ) -> TaskDecorator: - ... + ) -> TaskDecorator: ... @t.overload def task( @@ -422,13 +421,12 @@ def task( scorers: t.Sequence[Scorer[R] | ScorerCallable[R]], name: str | None = None, label: str | None = None, - log_params: t.Sequence[str] | t.Literal[True] | None = None, - log_inputs: t.Sequence[str] | t.Literal[True] | 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, - ) -> ScoredTaskDecorator[R]: - ... + ) -> ScoredTaskDecorator[R]: ... def task( self, @@ -436,8 +434,8 @@ def task( scorers: t.Sequence[Scorer[t.Any] | ScorerCallable[t.Any]] | None = None, name: str | None = None, label: str | None = None, - log_params: t.Sequence[str] | t.Literal[True] | None = None, - log_inputs: t.Sequence[str] | t.Literal[True] | 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, @@ -645,7 +643,7 @@ def 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: A dictionary of attributes to attach to the run. + **attributes: Additional attributes to attach to the run span. """ if not self._initialized: self.initialize() @@ -713,7 +711,7 @@ def log_param( """ self.log_params(to=to, **{key: value}) - def log_params(self, to: ToObject = "task-or-run", **params: JsonValue) -> None: + def log_params(self, to: ToObject = "run", **params: JsonValue) -> None: """ Log multiple parameters to the current task or run. diff --git a/dreadnode/task.py b/dreadnode/task.py index 36da22fe..eb92c9b7 100644 --- a/dreadnode/task.py +++ b/dreadnode/task.py @@ -38,7 +38,11 @@ def sorted(self, *, reverse: bool = True) -> "TaskSpanList[R]": A new TaskSpanList sorted by average metric value. """ return TaskSpanList( - sorted(self, key=lambda span: span.get_average_metric_value(), reverse=reverse), + sorted( + self, + key=lambda span: span.get_average_metric_value(), + reverse=reverse, + ), ) @t.overload @@ -48,12 +52,16 @@ def top_n( *, as_outputs: t.Literal[False] = False, reverse: bool = True, - ) -> "TaskSpanList[R]": - ... + ) -> "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, @@ -104,15 +112,19 @@ class Task(t.Generic[P, R]): tags: list[str] "A list of tags to attach to the task span." - log_params: t.Sequence[str] | t.Literal[True] | None = None + 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] | t.Literal[True] | None = None + 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) @@ -149,8 +161,8 @@ def with_( name: str | None = None, tags: t.Sequence[str] | None = None, label: str | None = None, - log_params: t.Sequence[str] | t.Literal[True] | None = None, - log_inputs: t.Sequence[str] | t.Literal[True] | 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, @@ -179,7 +191,9 @@ def with_( 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_scorers = [ + Scorer.from_callable(self.tracer, scorer) for scorer in (scorers or []) + ] new_tags = list(tags or []) if append: @@ -210,28 +224,37 @@ async def run(self, *args: P.args, **kwargs: P.kwargs) -> TaskSpan[R]: bound_args = self._bind_args(*args, **kwargs) - params = ( + 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 or [])} + 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 = ( + 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 or [])} + 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, - params=params, + params=params_to_log, tags=self.tags, run_id=run.run_id, tracer=self.tracer, ) as span: - for name, value in inputs.items(): + 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)) if inspect.isawaitable(output): @@ -240,11 +263,17 @@ async def run(self, *args: P.args, **kwargs: P.kwargs) -> TaskSpan[R]: span.output = output if self.log_output: - span.log_output("output", output, label=f"{self.label}.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: - metric = await scorer(span.output) - span.log_metric(scorer.name, metric, origin=span.output) + metric = await scorer(output) + span.log_metric(scorer.name, metric, origin=output) return span @@ -255,7 +284,12 @@ async def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R: # 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. @@ -285,7 +319,13 @@ async def map(self, count: int, *args: P.args, **kwargs: P.kwargs) -> list[R]: spans = await self.map_run(count, *args, **kwargs) 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. @@ -337,7 +377,12 @@ async def try_(self, *args: P.args, **kwargs: P.kwargs) -> R | None: 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]: + 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. @@ -350,10 +395,18 @@ async def try_map_run(self, count: int, *args: P.args, **kwargs: P.kwargs) -> Ta Returns: A TaskSpanList associated with each task execution. """ - spans = await asyncio.gather(*[self.try_run(*args, **kwargs) for _ in range(count)]) + 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. diff --git a/dreadnode/tracing/span.py b/dreadnode/tracing/span.py index 96bb5e2e..8b712172 100644 --- a/dreadnode/tracing/span.py +++ b/dreadnode/tracing/span.py @@ -624,7 +624,7 @@ def log_output( *, label: str | None = None, **attributes: JsonValue, - ) -> None: + ) -> str: label = label or re.sub(r"\W+", "_", name.lower()) hash_ = self.run.log_object( value, @@ -633,6 +633,7 @@ def log_output( **attributes, ) self._outputs.append(ObjectRef(name, label=label, hash=hash_)) + return hash_ @property def params(self) -> AnyDict: @@ -655,7 +656,7 @@ def log_input( *, label: str | None = None, **attributes: JsonValue, - ) -> None: + ) -> str: label = label or re.sub(r"\W+", "_", name.lower()) hash_ = self.run.log_object( value, @@ -664,6 +665,7 @@ def log_input( **attributes, ) self._inputs.append(ObjectRef(name, label=label, hash=hash_)) + return hash_ @property def metrics(self) -> dict[str, list[Metric]]: From 09f252a7dc586a977ca08627f72acebb26f66370 Mon Sep 17 00:00:00 2001 From: monoxgas Date: Wed, 23 Apr 2025 00:51:35 -0600 Subject: [PATCH 19/24] Remove dead ext folder --- dreadnode/ext/__init__.py | 0 dreadnode/ext/fsspec.py | 38 -------------------------------------- 2 files changed, 38 deletions(-) delete mode 100644 dreadnode/ext/__init__.py delete mode 100644 dreadnode/ext/fsspec.py diff --git a/dreadnode/ext/__init__.py b/dreadnode/ext/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/dreadnode/ext/fsspec.py b/dreadnode/ext/fsspec.py deleted file mode 100644 index 210ba2ea..00000000 --- a/dreadnode/ext/fsspec.py +++ /dev/null @@ -1,38 +0,0 @@ -import typing as t - -from fsspec import register_implementation # type: ignore [import-untyped] -from fsspec.implementations.dirfs import DirFileSystem # type: ignore [import-untyped] -from s3fs import S3FileSystem # type: ignore [import-untyped] - -from dreadnode.api.client import ApiClient - - -class DreadnodeS3Filesystem(DirFileSystem): # type: ignore [misc] - def __init__( - self, - api_client: ApiClient | None = None, - dirfs_options: dict[str, t.Any] | None = None, - **s3fs_options: t.Any, - ) -> None: - from dreadnode.main import DEFAULT_INSTANCE - - self.api_client = api_client or DEFAULT_INSTANCE.api() - credentials = self.api_client.get_user_data_credentials() - - s3_fs = S3FileSystem( - key=credentials.access_key_id, - secret=credentials.secret_access_key, - token=credentials.session_token, - endpoint_url=credentials.endpoint, - client_kwargs={"region_name": credentials.region}, - **s3fs_options, - ) - - super().__init__( - path=f"{credentials.bucket}/{credentials.prefix}/", - fs=s3_fs, - **(dirfs_options or {}), - ) - - -register_implementation("dn", DreadnodeS3Filesystem) From 7a0e4109702d9ff4bd53a7f74bdc00f0ff9e3344 Mon Sep 17 00:00:00 2001 From: Raja Sekhar Rao Dheekonda <43563047+rdheekonda@users.noreply.github.com> Date: Wed, 23 Apr 2025 08:46:05 -0700 Subject: [PATCH 20/24] Add Log Artifact for Files and Directories (#18) * Implement log artifact handles dierctory and individual file * Add example notebook; working log artifact with directory and file; cleanup * Implement smart artifact merging, removed unnecessary info from otel, leveraged s3fs put for batch uploading, adress pr feedback --- .gitignore | 1 + dreadnode/__init__.py | 1 + dreadnode/main.py | 50 ++ dreadnode/storage/__init__.py | 0 dreadnode/storage/artifact_storage.py | 137 +++++ dreadnode/tracing/artifact_merger.py | 575 +++++++++++++++++++++ dreadnode/tracing/artifact_tree_builder.py | 457 ++++++++++++++++ dreadnode/tracing/span.py | 66 ++- examples/log_artifact.ipynb | 237 +++++++++ 9 files changed, 1520 insertions(+), 4 deletions(-) create mode 100644 dreadnode/storage/__init__.py create mode 100644 dreadnode/storage/artifact_storage.py create mode 100644 dreadnode/tracing/artifact_merger.py create mode 100644 dreadnode/tracing/artifact_tree_builder.py create mode 100644 examples/log_artifact.ipynb 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/dreadnode/__init__.py b/dreadnode/__init__.py index e7fa4c49..a7bbd9cd 100644 --- a/dreadnode/__init__.py +++ b/dreadnode/__init__.py @@ -24,6 +24,7 @@ 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 diff --git a/dreadnode/main.py b/dreadnode/main.py index cb0d7a65..f7f2b963 100644 --- a/dreadnode/main.py +++ b/dreadnode/main.py @@ -854,6 +854,56 @@ def log_metric( ) run.log_metric(key, metric, origin=origin) + def log_artifact( + self, + local_uri: str | Path, + *, + to: ToObject = "run", + ) -> 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) + + # 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 to != "run": + raise RuntimeError("Artifacts can only be logged to runs") + + run = current_run_span.get() + + if run is None: + raise RuntimeError("log_artifact() with to='run' must be called within a run") + + run.log_artifact(local_uri=local_uri) + def log_input( self, name: str, diff --git a/dreadnode/storage/__init__.py b/dreadnode/storage/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/dreadnode/storage/artifact_storage.py b/dreadnode/storage/artifact_storage.py new file mode 100644 index 00000000..ff695e37 --- /dev/null +++ b/dreadnode/storage/artifact_storage.py @@ -0,0 +1,137 @@ +""" +Artifact storage implementation for fsspec-compatible file systems. +Provides efficient uploading of files and directories with deduplication. +""" + +import hashlib +import logging +from pathlib import Path + +import fsspec + +logger = logging.getLogger(__name__) + +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 + """ + try: + if not self._file_system.exists(target_key): + logger.info(f"Storing file: {file_path} to {target_key}") + self._file_system.put(str(file_path), target_key) + logger.info(f"File successfully stored at {target_key}") + else: + logger.info(f"File already exists at {target_key}, skipping upload.") + except Exception: + logger.exception(f"An error occurred while storing the file: {file_path}.") + raise + + 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 [] + + try: + logger.info(f"Batch uploading {len(source_paths)} files") + + 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.info(f"Batch upload completed for {len(srcs)} files") + else: + logger.info("All files already exist, skipping upload") + + return [str(self._file_system.unstrip_protocol(target)) for target in target_paths] + + except Exception as e: + logger.exception(f"Error during batch upload of files: {e}") + raise + + 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/tracing/artifact_merger.py b/dreadnode/tracing/artifact_merger.py new file mode 100644 index 00000000..a4468621 --- /dev/null +++ b/dreadnode/tracing/artifact_merger.py @@ -0,0 +1,575 @@ +""" +Utility for merging artifact tree structures while preserving directory hierarchy. +""" + +import hashlib +import os +from typing import Optional, cast + +from dreadnode.tracing.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 os.path.basename(child["dir_path"]) == 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: Optional[DirectoryNode] = 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"]) + else: + child_hash = self._update_directory_hash(cast(DirectoryNode, child)) + 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/tracing/artifact_tree_builder.py b/dreadnode/tracing/artifact_tree_builder.py new file mode 100644 index 00000000..be991595 --- /dev/null +++ b/dreadnode/tracing/artifact_tree_builder.py @@ -0,0 +1,457 @@ +""" +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 logging import getLogger +from pathlib import Path +from typing import Literal, TypedDict, Union + +from dreadnode.storage.artifact_storage import ArtifactStorage + +logger = getLogger(__name__) + + +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.info(f"Processing directory: {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.info(f"Uploading {len(source_paths)} files in batch") + 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: + try: + rel_path = file_path.relative_to(base_dir) + parts = rel_path.parts + except ValueError: + logger.warning(f"File {file_path} is not relative to base directory {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/tracing/span.py b/dreadnode/tracing/span.py index 8b712172..1aa82e02 100644 --- a/dreadnode/tracing/span.py +++ b/dreadnode/tracing/span.py @@ -1,9 +1,11 @@ +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] @@ -26,6 +28,9 @@ from dreadnode.metric import Metric, MetricDict from dreadnode.object import Object, ObjectRef, ObjectUri, ObjectVal from dreadnode.serialization import Serialized, serialize +from dreadnode.storage.artifact_storage import ArtifactStorage +from dreadnode.tracing.artifact_merger import ArtifactMerger +from dreadnode.tracing.artifact_tree_builder import ArtifactTreeBuilder, DirectoryNode from dreadnode.types import UNSET, AnyDict, JsonDict, JsonValue, Unset from dreadnode.version import VERSION @@ -40,6 +45,7 @@ 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, @@ -58,6 +64,8 @@ SpanType, ) +logger = logging.getLogger(__name__) + R = t.TypeVar("R") @@ -253,6 +261,13 @@ def __init__( 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) @@ -293,7 +308,7 @@ def __exit__( self._object_schemas, schema=False, ) - self.set_attribute(SPAN_ATTRIBUTE_OUTPUTS, self._outputs, 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( @@ -372,6 +387,25 @@ def log_object( self.log_event(name=event_name, attributes=event_attributes) return object_.hash + def _store_file_by_hash(self, data: str | 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. Can be a string or bytes. + 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) + mode = "w" if isinstance(data, str) else "wb" + with self._file_system.open(full_path, mode) 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 @@ -388,12 +422,12 @@ def _create_object(self, serialized: Serialized) -> Object: # Offload to file system (e.g., S3) full_path = f"{self._prefix_path.rstrip('/')}/{data_hash}" - with self._file_system.open(full_path, "wb") as f: - f.write(serialized.data_bytes) + + object_uri = self._store_file_by_hash(data, full_path) return ObjectUri( hash=data_hash, - uri=self._file_system.unstrip_protocol(full_path), + uri=object_uri, schema_hash=schema_hash, size=data_len, ) @@ -456,6 +490,30 @@ def log_input( ) 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 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 +} From d30cafaacc723cb3502f27602d91040d91ab7a4f Mon Sep 17 00:00:00 2001 From: monoxgas Date: Wed, 23 Apr 2025 12:58:54 -0600 Subject: [PATCH 21/24] Moved some artifact components around. Fixed typing/linting errors. Cleaned up logging, warning, and internal error handling --- dreadnode/{storage => artifact}/__init__.py | 0 .../artifact_merger.py => artifact/merger.py} | 10 +- .../storage.py} | 51 +++---- .../tree_builder.py} | 12 +- dreadnode/integrations/transformers.py | 10 +- dreadnode/main.py | 135 ++++++++---------- dreadnode/serialization.py | 23 ++- dreadnode/storage/base.py | 40 ------ dreadnode/task.py | 14 +- dreadnode/tracing/exporters.py | 16 ++- dreadnode/tracing/span.py | 39 +++-- dreadnode/util.py | 132 ++++++++++++++++- 12 files changed, 269 insertions(+), 213 deletions(-) rename dreadnode/{storage => artifact}/__init__.py (100%) rename dreadnode/{tracing/artifact_merger.py => artifact/merger.py} (98%) rename dreadnode/{storage/artifact_storage.py => artifact/storage.py} (68%) rename dreadnode/{tracing/artifact_tree_builder.py => artifact/tree_builder.py} (97%) delete mode 100644 dreadnode/storage/base.py diff --git a/dreadnode/storage/__init__.py b/dreadnode/artifact/__init__.py similarity index 100% rename from dreadnode/storage/__init__.py rename to dreadnode/artifact/__init__.py diff --git a/dreadnode/tracing/artifact_merger.py b/dreadnode/artifact/merger.py similarity index 98% rename from dreadnode/tracing/artifact_merger.py rename to dreadnode/artifact/merger.py index a4468621..139d9253 100644 --- a/dreadnode/tracing/artifact_merger.py +++ b/dreadnode/artifact/merger.py @@ -3,10 +3,10 @@ """ import hashlib -import os -from typing import Optional, cast +from pathlib import Path +from typing import cast -from dreadnode.tracing.artifact_tree_builder import DirectoryNode, FileNode +from dreadnode.artifact.tree_builder import DirectoryNode, FileNode class ArtifactMerger: @@ -186,7 +186,7 @@ def _place_tree_at_path( # Look for existing directory next_node = None for child in current["children"]: - if child["type"] == "dir" and os.path.basename(child["dir_path"]) == part: + if child["type"] == "dir" and Path(child["dir_path"]).name == part: next_node = child break @@ -340,7 +340,7 @@ def _update_file_in_tree( return True return False - def _build_maps(self, new_tree: Optional[DirectoryNode] = None) -> None: + def _build_maps(self, new_tree: DirectoryNode | None = None) -> None: """ Build or rebuild the path and hash maps. diff --git a/dreadnode/storage/artifact_storage.py b/dreadnode/artifact/storage.py similarity index 68% rename from dreadnode/storage/artifact_storage.py rename to dreadnode/artifact/storage.py index ff695e37..43f538c3 100644 --- a/dreadnode/storage/artifact_storage.py +++ b/dreadnode/artifact/storage.py @@ -4,12 +4,11 @@ """ import hashlib -import logging from pathlib import Path -import fsspec +import fsspec # type: ignore[import-untyped] -logger = logging.getLogger(__name__) +from dreadnode.util import logger CHUNK_SIZE = 8 * 1024 * 1024 # 8MB @@ -43,16 +42,11 @@ def store_file(self, file_path: Path, target_key: str) -> str: Returns: Full URI with protocol to the stored file """ - try: - if not self._file_system.exists(target_key): - logger.info(f"Storing file: {file_path} to {target_key}") - self._file_system.put(str(file_path), target_key) - logger.info(f"File successfully stored at {target_key}") - else: - logger.info(f"File already exists at {target_key}, skipping upload.") - except Exception: - logger.exception(f"An error occurred while storing the file: {file_path}.") - raise + 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)) @@ -70,28 +64,23 @@ def batch_upload_files(self, source_paths: list[str], target_paths: list[str]) - if not source_paths: return [] - try: - logger.info(f"Batch uploading {len(source_paths)} files") - - srcs = [] - dsts = [] + logger.debug("Batch uploading %d files", len(source_paths)) - for src, dst in zip(source_paths, target_paths, strict=False): - if not self._file_system.exists(dst): - srcs.append(src) - dsts.append(dst) + srcs = [] + dsts = [] - if srcs: - self._file_system.put(srcs, dsts) - logger.info(f"Batch upload completed for {len(srcs)} files") - else: - logger.info("All files already exist, skipping upload") + for src, dst in zip(source_paths, target_paths, strict=False): + if not self._file_system.exists(dst): + srcs.append(src) + dsts.append(dst) - return [str(self._file_system.unstrip_protocol(target)) for target in target_paths] + 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") - except Exception as e: - logger.exception(f"Error during batch upload of files: {e}") - raise + 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: """ diff --git a/dreadnode/tracing/artifact_tree_builder.py b/dreadnode/artifact/tree_builder.py similarity index 97% rename from dreadnode/tracing/artifact_tree_builder.py rename to dreadnode/artifact/tree_builder.py index be991595..81fb791e 100644 --- a/dreadnode/tracing/artifact_tree_builder.py +++ b/dreadnode/artifact/tree_builder.py @@ -6,13 +6,11 @@ import hashlib import os from dataclasses import dataclass -from logging import getLogger from pathlib import Path from typing import Literal, TypedDict, Union -from dreadnode.storage.artifact_storage import ArtifactStorage - -logger = getLogger(__name__) +from dreadnode.artifact.storage import ArtifactStorage +from dreadnode.util import logger class FileNode(TypedDict): @@ -104,7 +102,7 @@ def _process_directory(self, dir_path: Path) -> DirectoryNode: Returns: DirectoryNode: A hierarchical tree structure representing the directory and its contents. """ - logger.info(f"Processing directory: {dir_path}") + logger.debug("Processing directory: %s", dir_path) all_files: list[Path] = [] for root, _, files in os.walk(dir_path): @@ -158,7 +156,7 @@ def _process_directory(self, dir_path: Path) -> DirectoryNode: file_hash_cache[file_hash] = file_node if source_paths: - logger.info(f"Uploading {len(source_paths)} files in batch") + 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 @@ -269,7 +267,7 @@ def _build_tree_structure( rel_path = file_path.relative_to(base_dir) parts = rel_path.parts except ValueError: - logger.warning(f"File {file_path} is not relative to base directory {base_dir}") + logger.debug("File %s is not relative to base directory %s", file_path, base_dir) continue # File in the root directory diff --git a/dreadnode/integrations/transformers.py b/dreadnode/integrations/transformers.py index a54d71c9..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,14 +15,16 @@ 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 diff --git a/dreadnode/main.py b/dreadnode/main.py index f7f2b963..5a6551f0 100644 --- a/dreadnode/main.py +++ b/dreadnode/main.py @@ -50,6 +50,7 @@ AnyDict, JsonValue, ) +from dreadnode.util import handle_internal_errors from dreadnode.version import VERSION if t.TYPE_CHECKING: @@ -66,6 +67,10 @@ class DreadnodeConfigWarning(UserWarning): pass +class DreadnodeUsageWarning(UserWarning): + pass + + @dataclass class Dreadnode: """ @@ -157,12 +162,8 @@ def configure( self._initialized = False - 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) - ) + 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) @@ -308,6 +309,7 @@ def _get_tracer(self, *, is_span_tracer: bool = True) -> "Tracer": is_span_tracer=is_span_tracer, ) + @handle_internal_errors() def shutdown(self) -> None: """ Shutdown any associate OpenTelemetry components and flush any pending spans. @@ -369,36 +371,42 @@ class TaskDecorator(t.Protocol): def __call__( self, func: t.Callable[P, t.Awaitable[R]], - ) -> Task[P, R]: ... + ) -> Task[P, R]: + ... @t.overload def __call__( self, func: t.Callable[P, R], - ) -> Task[P, R]: ... + ) -> Task[P, R]: + ... def __call__( self, func: t.Callable[P, t.Awaitable[R]] | t.Callable[P, R], - ) -> Task[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]: ... + ) -> Task[P, R]: + ... @t.overload def __call__( self, func: t.Callable[P, R], - ) -> Task[P, R]: ... + ) -> Task[P, R]: + ... def __call__( self, func: t.Callable[P, t.Awaitable[R]] | t.Callable[P, R], - ) -> Task[P, R]: ... + ) -> Task[P, R]: + ... @t.overload def task( @@ -412,7 +420,8 @@ def task( log_output: bool = True, tags: t.Sequence[str] | None = None, **attributes: t.Any, - ) -> TaskDecorator: ... + ) -> TaskDecorator: + ... @t.overload def task( @@ -426,7 +435,8 @@ def task( log_output: bool = True, tags: t.Sequence[str] | None = None, **attributes: t.Any, - ) -> ScoredTaskDecorator[R]: ... + ) -> ScoredTaskDecorator[R]: + ... def task( self, @@ -552,7 +562,7 @@ def task_span( A TaskSpan object. """ if (run := current_run_span.get()) is None: - raise RuntimeError("task_span() must be called within a run") + raise RuntimeError("Task spans must be created within a run") label = label or re.sub(r"[\W_]+", "_", name.lower()) return TaskSpan( @@ -662,6 +672,7 @@ def run( prefix_path=self._fs_prefix, ) + @handle_internal_errors() def push_update(self) -> None: """ Push any pending metric or parameter data to the server. @@ -682,6 +693,7 @@ def push_update(self) -> None: run.push_update() + @handle_internal_errors() def log_param( self, key: str, @@ -711,6 +723,7 @@ def log_param( """ 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. @@ -737,20 +750,11 @@ def log_params(self, to: ToObject = "run", **params: JsonValue) -> None: task = current_task_span.get() run = current_run_span.get() - if to == "task-or-run": - target = task or run - if target is None: - raise RuntimeError( - "log_params() with to='task-or-run' must be called within a run or a task", - ) - target.log_params(**params) + 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") - elif to == "run": - if run is None: - raise RuntimeError( - "log_params() with to='run' must be called within a run", - ) - run.log_params(**params) + target.log_params(**params) @t.overload def log_metric( @@ -820,6 +824,7 @@ def log_metric( """ ... + @handle_internal_errors() def log_metric( self, key: str, @@ -833,32 +838,21 @@ def log_metric( 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) - if to == "task-or-run": - target = task or run - if target is None: - raise RuntimeError( - "log_metric() with to='task-or-run' must be called within a run or a task", - ) - target.log_metric(key, metric, origin=origin) - - elif to == "run": - if run is None: - raise RuntimeError( - "log_metric() with to='run' must be called within a run", - ) - run.log_metric(key, metric, origin=origin) - + @handle_internal_errors() def log_artifact( self, local_uri: str | Path, - *, - to: ToObject = "run", ) -> None: """ Log a file or directory artifact to the current run. @@ -893,17 +887,12 @@ def log_artifact( local_uri: The local path to the file to upload. to: The target object to log the artifact to. Only "run" is supported. """ - - if to != "run": - raise RuntimeError("Artifacts can only be logged to runs") - - run = current_run_span.get() - - if run is None: - raise RuntimeError("log_artifact() with to='run' must be called within a run") + if (run := current_run_span.get()) is None: + raise RuntimeError("log_artifact() must be called within a run") run.log_artifact(local_uri=local_uri) + @handle_internal_errors() def log_input( self, name: str, @@ -935,21 +924,13 @@ async def my_task(x: int) -> int: task = current_task_span.get() run = current_run_span.get() - if to == "task-or-run": - target = task or run - if target is None: - raise RuntimeError( - "log_inputs() with to='task-or-run' must be called within a run or a task", - ) - target.log_input(name, value, label=label, **attributes) + 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") - elif to == "run": - if run is None: - raise RuntimeError( - "log_inputs() with to='run' must be called within a run", - ) - run.log_input(name, value, label=label, **attributes) + target.log_input(name, value, label=label, **attributes) + @handle_internal_errors() def log_inputs( self, to: ToObject = "task-or-run", @@ -963,6 +944,7 @@ def log_inputs( for name, value in inputs.items(): self.log_input(name, value, to=to) + @handle_internal_errors() def log_output( self, name: str, @@ -995,21 +977,15 @@ async def my_task(x: int) -> int: task = current_task_span.get() run = current_run_span.get() - if to == "task-or-run": - target = task or run - if target is None: - raise RuntimeError( - "log_output() with to='task-or-run' must be called within a run or a task", - ) - target.log_output(name, value, label=label, **attributes) + 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", + ) - elif to == "run": - if run is None: - raise RuntimeError( - "log_output() with to='run' must be called within a run", - ) - run.log_output(name, value, label=label, **attributes) + target.log_output(name, value, label=label, **attributes) + @handle_internal_errors() def log_outputs( self, to: ToObject = "task-or-run", @@ -1023,6 +999,7 @@ def log_outputs( 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. diff --git a/dreadnode/serialization.py b/dreadnode/serialization.py index 895bcb8e..e68ce2bd 100644 --- a/dreadnode/serialization.py +++ b/dreadnode/serialization.py @@ -419,7 +419,7 @@ def _handle_pydub_audio_segment( obj: t.Any, _seen: set[int], ) -> tuple[JsonValue, JsonDict]: - from pydub import AudioSegment # type: ignore[import-untyped] + 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 @@ -431,12 +431,12 @@ def _handle_pydub_audio_segment( # 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, - } + "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) @@ -448,7 +448,9 @@ def _handle_moviepy_video_clip( import tempfile from pathlib import Path - from moviepy import VideoFileClip # type: ignore[import-untyped] + 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 @@ -465,8 +467,7 @@ def _handle_moviepy_video_clip( obj.write_videofile( temp_file.name, ) - with open(temp_file.name, "rb") as f: - raw_bytes_data = f.read() + raw_bytes_data = Path(temp_file.name).read_bytes() schema = { "x-python-datatype": "moviepy.VideoFileClip", @@ -597,13 +598,11 @@ def _get_handlers() -> dict[type, HandlerFunc]: 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 diff --git a/dreadnode/storage/base.py b/dreadnode/storage/base.py deleted file mode 100644 index d55ab607..00000000 --- a/dreadnode/storage/base.py +++ /dev/null @@ -1,40 +0,0 @@ -import abc -from pathlib import Path - -from dreadnode.constants import DEFAULT_LOCAL_OBJECT_DIR - - -class ObjectStore(abc.ABC): - @abc.abstractmethod - async def store(self, data: bytes, hash: str) -> str: ... - - @abc.abstractmethod - def exists(self, uri: str) -> bool: ... - - @abc.abstractmethod - async def get(self, uri: str) -> bytes: ... - - -class LocalObjectStore(ObjectStore): - def __init__(self, base_path: Path | str = DEFAULT_LOCAL_OBJECT_DIR): - self.base_path = Path(base_path) - self.base_path.mkdir(parents=True, exist_ok=True) - - def _resolve_uri(self, uri: str) -> Path: - if not uri.startswith("file://"): - raise ValueError(f"Invalid uri for local storage: {uri}") - uri_without_scheme = uri[7:] - return Path(uri_without_scheme) - - async def store(self, data: bytes, hash: str) -> str: - path = self.base_path / hash - path.write_bytes(data) - return path.absolute().as_uri() - - def exists(self, uri: str) -> bool: - if not uri.startswith("file://"): - return False - return self._resolve_uri(uri).exists() - - async def get(self, uri: str) -> bytes: - return self._resolve_uri(uri).read_bytes() diff --git a/dreadnode/task.py b/dreadnode/task.py index eb92c9b7..c802bcf9 100644 --- a/dreadnode/task.py +++ b/dreadnode/task.py @@ -52,7 +52,8 @@ def top_n( *, as_outputs: t.Literal[False] = False, reverse: bool = True, - ) -> "TaskSpanList[R]": ... + ) -> "TaskSpanList[R]": + ... @t.overload def top_n( @@ -61,7 +62,8 @@ def top_n( *, as_outputs: t.Literal[True], reverse: bool = True, - ) -> list[R]: ... + ) -> list[R]: + ... def top_n( self, @@ -191,9 +193,7 @@ def with_( 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_scorers = [Scorer.from_callable(self.tracer, scorer) for scorer in (scorers or [])] new_tags = list(tags or []) if append: @@ -264,7 +264,9 @@ async def run(self, *args: P.args, **kwargs: P.kwargs) -> TaskSpan[R]: if self.log_output: output_object_hash = span.log_output( - "output", output, label=f"{self.label}.output" + "output", + output, + label=f"{self.label}.output", ) # Link the output to the inputs diff --git a/dreadnode/tracing/exporters.py b/dreadnode/tracing/exporters.py index 2f543f77..007188a6 100644 --- a/dreadnode/tracing/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: @@ -63,8 +65,8 @@ 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, @@ -98,8 +100,8 @@ def export(self, spans: Sequence[ReadableSpan]) -> SpanExportResult: with self._lock: self.file.write(json_str + "\n") self.file.flush() - 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 @@ -137,10 +139,10 @@ 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, diff --git a/dreadnode/tracing/span.py b/dreadnode/tracing/span.py index 1aa82e02..cdbf53d5 100644 --- a/dreadnode/tracing/span.py +++ b/dreadnode/tracing/span.py @@ -24,13 +24,13 @@ 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.storage.artifact_storage import ArtifactStorage -from dreadnode.tracing.artifact_merger import ArtifactMerger -from dreadnode.tracing.artifact_tree_builder import ArtifactTreeBuilder, DirectoryNode from dreadnode.types import UNSET, AnyDict, JsonDict, JsonValue, Unset from dreadnode.version import VERSION @@ -186,7 +186,9 @@ def set_attribute( 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) + 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 @@ -387,12 +389,12 @@ def log_object( self.log_event(name=event_name, attributes=event_attributes) return object_.hash - def _store_file_by_hash(self, data: str | bytes, full_path: str) -> str: + 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. Can be a string or bytes. + data: Content to write. full_path: The path in the object store (e.g., S3 key or local path). Returns: @@ -400,8 +402,7 @@ def _store_file_by_hash(self, data: str | bytes, full_path: str) -> str: """ if not self._file_system.exists(full_path): logger.debug("Storing new object at: %s", full_path) - mode = "w" if isinstance(data, str) else "wb" - with self._file_system.open(full_path, mode) as f: + with self._file_system.open(full_path, "wb") as f: f.write(data) return str(self._file_system.unstrip_protocol(full_path)) @@ -409,11 +410,12 @@ def _store_file_by_hash(self, data: str | bytes, full_path: str) -> str: 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_len <= MAX_INLINE_OBJECT_BYTES: + if data is None or data_bytes is None or data_len <= MAX_INLINE_OBJECT_BYTES: return ObjectVal( hash=data_hash, value=data, @@ -422,8 +424,7 @@ def _create_object(self, serialized: Serialized) -> Object: # Offload to file system (e.g., S3) full_path = f"{self._prefix_path.rstrip('/')}/{data_hash}" - - object_uri = self._store_file_by_hash(data, full_path) + object_uri = self._store_file_by_hash(data_bytes, full_path) return ObjectUri( hash=data_hash, @@ -527,8 +528,7 @@ def log_metric( step: int = 0, origin: t.Any | None = None, timestamp: datetime | None = None, - ) -> None: - ... + ) -> None: ... @t.overload def log_metric( @@ -537,8 +537,7 @@ def log_metric( value: Metric, *, origin: t.Any | None = None, - ) -> None: - ... + ) -> None: ... def log_metric( self, @@ -609,7 +608,9 @@ def __init__( self._output: R | Unset = UNSET # For the python output - self._context_token: Token[TaskSpan[t.Any] | None] | None = None # contextvars context + self._context_token: Token[TaskSpan[t.Any] | None] | None = ( + None # contextvars context + ) attributes = { SPAN_ATTRIBUTE_RUN_ID: str(run_id), @@ -738,8 +739,7 @@ def log_metric( step: int = 0, origin: t.Any | None = None, timestamp: datetime | None = None, - ) -> None: - ... + ) -> None: ... @t.overload def log_metric( @@ -748,8 +748,7 @@ def log_metric( value: Metric, *, origin: t.Any | None = None, - ) -> None: - ... + ) -> None: ... def log_metric( self, diff --git a/dreadnode/util.py b/dreadnode/util.py index ad28db47..aa25a60c 100644 --- a/dreadnode/util.py +++ b/dreadnode/util.py @@ -1,11 +1,35 @@ +# 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. - - Taken from `logfire`. """ try: @@ -20,3 +44,107 @@ def safe_repr(obj: t.Any) -> str: 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 + + 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__ From 2a97753dbb24e095e441646f8b7a38cc643e6804 Mon Sep 17 00:00:00 2001 From: monoxgas Date: Wed, 23 Apr 2025 13:01:56 -0600 Subject: [PATCH 22/24] Additional logging updates for the api client --- dreadnode/api/client.py | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/dreadnode/api/client.py b/dreadnode/api/client.py index 518d17e4..c9d5585f 100644 --- a/dreadnode/api/client.py +++ b/dreadnode/api/client.py @@ -1,14 +1,13 @@ import io import json import typing as t -from logging import getLogger import httpx import pandas as pd from pydantic import BaseModel -from rich import print as rich_print from ulid import ULID +from dreadnode.util import logger from dreadnode.version import VERSION from .models import ( @@ -23,8 +22,6 @@ UserDataCredentials, ) -logger = getLogger(__name__) - ModelT = t.TypeVar("ModelT", bound=BaseModel) @@ -59,20 +56,20 @@ def __init__( 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.""" From d30438ea257ea7e2a6ac070f8d2d1905b8cd361e Mon Sep 17 00:00:00 2001 From: monoxgas Date: Wed, 23 Apr 2025 13:18:22 -0600 Subject: [PATCH 23/24] Version to v1.0.0-rc.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8fd66669..ac36043f 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" From aa4b4a44dbf012a630898ea83cc96857d08b3dac Mon Sep 17 00:00:00 2001 From: Brian Greunke Date: Wed, 23 Apr 2025 21:38:20 -0500 Subject: [PATCH 24/24] chore: linting - updated ruff version - marked noqa for existing errors for future work - fixed hooks linting errors - updated config for test and hook linting excludes --- .hooks/check_pinned_hash_dependencies.py | 18 +- .hooks/generate_pr_description.py | 26 +- dreadnode/api/client.py | 2 +- dreadnode/artifact/merger.py | 32 +- dreadnode/artifact/tree_builder.py | 2 +- dreadnode/main.py | 31 +- dreadnode/metric.py | 2 +- dreadnode/serialization.py | 2 +- dreadnode/task.py | 10 +- dreadnode/tracing/span.py | 8 +- dreadnode/util.py | 2 +- poetry.lock | 2560 ++++++++++++++++++---- pyproject.toml | 47 +- 13 files changed, 2178 insertions(+), 564 deletions(-) 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/dreadnode/api/client.py b/dreadnode/api/client.py index c9d5585f..674f8c1f 100644 --- a/dreadnode/api/client.py +++ b/dreadnode/api/client.py @@ -76,7 +76,7 @@ def _get_error_message(self, response: httpx.Response) -> str: try: obj = response.json() - return f'{response.status_code}: {obj.get("detail", json.dumps(obj))}' + return f"{response.status_code}: {obj.get('detail', json.dumps(obj))}" except Exception: # noqa: BLE001 return str(response.content) diff --git a/dreadnode/artifact/merger.py b/dreadnode/artifact/merger.py index 139d9253..f7ddef98 100644 --- a/dreadnode/artifact/merger.py +++ b/dreadnode/artifact/merger.py @@ -248,14 +248,14 @@ def _handle_overlaps( 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), + 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) + 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 @@ -333,7 +333,7 @@ def _update_file_in_tree( return True if child["type"] == "dir" and self._update_file_in_tree( - cast(DirectoryNode, child), + cast("DirectoryNode", child), old_file, new_file, ): @@ -400,7 +400,7 @@ def _build_path_and_hash_maps( """ if node["type"] == "dir": # Add directory to path map - dir_node = cast(DirectoryNode, node) + dir_node = cast("DirectoryNode", node) dir_path = dir_node["dir_path"] path_map[dir_path] = dir_node @@ -409,7 +409,7 @@ def _build_path_and_hash_maps( 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_node = cast("FileNode", node) file_path = file_node["final_real_path"] path_map[file_path] = file_node @@ -448,13 +448,13 @@ def _merge_directory_nodes(self, target_dir: DirectoryNode, source_dir: Director if source_child["type"] == "dir": self._merge_directory_child( target_dir, - cast(DirectoryNode, source_child), + cast("DirectoryNode", source_child), path_to_index, ) else: # file self._merge_file_child( target_dir, - cast(FileNode, source_child), + cast("FileNode", source_child), path_to_index, hash_to_index, ) @@ -474,9 +474,9 @@ def _build_indices(self, dir_node: DirectoryNode) -> tuple[dict[str, int], dict[ for i, child in enumerate(dir_node["children"]): if child["type"] == "dir": - path_to_index[cast(DirectoryNode, child)["dir_path"]] = i + path_to_index[cast("DirectoryNode", child)["dir_path"]] = i else: # file - file_child = cast(FileNode, child) + file_child = cast("FileNode", child) path_to_index[file_child["final_real_path"]] = i hash_to_index[file_child["hash"]] = i @@ -496,7 +496,7 @@ def _merge_directory_child( existing_child = target_dir["children"][index] if existing_child["type"] == "dir": self._merge_directory_nodes( - cast(DirectoryNode, existing_child), + cast("DirectoryNode", existing_child), source_dir, ) else: @@ -522,14 +522,14 @@ def _merge_file_child( 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) + 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) + 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: @@ -562,9 +562,9 @@ def _update_directory_hash(self, dir_node: DirectoryNode) -> str: for child in dir_node["children"]: if child["type"] == "file": - child_hashes.append(cast(FileNode, child)["hash"]) + child_hashes.append(cast(FileNode, child)["hash"]) # noqa: TC006 else: - child_hash = self._update_directory_hash(cast(DirectoryNode, child)) + 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 diff --git a/dreadnode/artifact/tree_builder.py b/dreadnode/artifact/tree_builder.py index 81fb791e..af37bafa 100644 --- a/dreadnode/artifact/tree_builder.py +++ b/dreadnode/artifact/tree_builder.py @@ -262,7 +262,7 @@ def _build_tree_structure( } dir_structure[root_dir_path] = root_node - for file_path in file_nodes_by_path: + for file_path in file_nodes_by_path: # noqa: PLC0206 try: rel_path = file_path.relative_to(base_dir) parts = rel_path.parts diff --git a/dreadnode/main.py b/dreadnode/main.py index 5a6551f0..e6d93a9a 100644 --- a/dreadnode/main.py +++ b/dreadnode/main.py @@ -217,7 +217,7 @@ def initialize(self) -> None: try: self._api.list_projects() - except Exception as e: # noqa: BLE001 + except Exception as e: raise RuntimeError( "Failed to authenticate with the provided server and token", ) from e @@ -371,42 +371,36 @@ class TaskDecorator(t.Protocol): def __call__( self, func: t.Callable[P, t.Awaitable[R]], - ) -> Task[P, R]: - ... + ) -> Task[P, R]: ... @t.overload def __call__( self, func: t.Callable[P, R], - ) -> Task[P, R]: - ... + ) -> Task[P, R]: ... def __call__( self, func: t.Callable[P, t.Awaitable[R]] | t.Callable[P, R], - ) -> Task[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]: - ... + ) -> Task[P, R]: ... @t.overload def __call__( self, func: t.Callable[P, R], - ) -> Task[P, R]: - ... + ) -> Task[P, R]: ... def __call__( self, func: t.Callable[P, t.Awaitable[R]] | t.Callable[P, R], - ) -> Task[P, R]: - ... + ) -> Task[P, R]: ... @t.overload def task( @@ -420,8 +414,7 @@ def task( log_output: bool = True, tags: t.Sequence[str] | None = None, **attributes: t.Any, - ) -> TaskDecorator: - ... + ) -> TaskDecorator: ... @t.overload def task( @@ -435,8 +428,7 @@ def task( log_output: bool = True, tags: t.Sequence[str] | None = None, **attributes: t.Any, - ) -> ScoredTaskDecorator[R]: - ... + ) -> ScoredTaskDecorator[R]: ... def task( self, @@ -514,7 +506,7 @@ def make_task( tracer=self._get_tracer(), name=_name, attributes=_attributes, - func=t.cast(t.Callable[P, R], func), + func=t.cast("t.Callable[P, R]", func), scorers=[ scorer if isinstance(scorer, Scorer) @@ -790,7 +782,6 @@ def log_metric( 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( @@ -822,7 +813,7 @@ def log_metric( 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( diff --git a/dreadnode/metric.py b/dreadnode/metric.py index 2188b961..bce03cbc 100644 --- a/dreadnode/metric.py +++ b/dreadnode/metric.py @@ -83,7 +83,7 @@ class Scorer(t.Generic[T]): def from_callable( cls, tracer: Tracer, - func: ScorerCallable[T] | "Scorer[T]", + func: ScorerCallable[T] | "Scorer[T]", # noqa: TC010 *, name: str | None = None, tags: t.Sequence[str] | None = None, diff --git a/dreadnode/serialization.py b/dreadnode/serialization.py index e68ce2bd..8964ac8c 100644 --- a/dreadnode/serialization.py +++ b/dreadnode/serialization.py @@ -76,7 +76,7 @@ def _handle_sequence( non_empty_schemas_found = True schema: JsonDict = {"type": "array"} - if obj_type != list: + 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__) diff --git a/dreadnode/task.py b/dreadnode/task.py index c802bcf9..f58f9746 100644 --- a/dreadnode/task.py +++ b/dreadnode/task.py @@ -52,8 +52,7 @@ def top_n( *, as_outputs: t.Literal[False] = False, reverse: bool = True, - ) -> "TaskSpanList[R]": - ... + ) -> "TaskSpanList[R]": ... @t.overload def top_n( @@ -62,8 +61,7 @@ def top_n( *, as_outputs: t.Literal[True], reverse: bool = True, - ) -> list[R]: - ... + ) -> list[R]: ... def top_n( self, @@ -85,7 +83,7 @@ def top_n( """ sorted_ = self.sorted(reverse=reverse)[:n] return ( - t.cast(list[R], [span.output for span in sorted_]) + t.cast(list[R], [span.output for span in sorted_]) # noqa: TC006 if as_outputs else TaskSpanList(sorted_) ) @@ -256,7 +254,7 @@ async def run(self, *args: P.args, **kwargs: P.kwargs) -> TaskSpan[R]: for name, value in inputs_to_log.items() ] - output = t.cast(R | t.Awaitable[R], self.func(*args, **kwargs)) + output = t.cast(R | t.Awaitable[R], self.func(*args, **kwargs)) # noqa: TC006 if inspect.isawaitable(output): output = await output diff --git a/dreadnode/tracing/span.py b/dreadnode/tracing/span.py index cdbf53d5..7e590fe1 100644 --- a/dreadnode/tracing/span.py +++ b/dreadnode/tracing/span.py @@ -186,9 +186,7 @@ def set_attribute( 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) - ) + 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 @@ -608,9 +606,7 @@ def __init__( self._output: R | Unset = UNSET # For the python output - self._context_token: Token[TaskSpan[t.Any] | None] | None = ( - None # contextvars context - ) + self._context_token: Token[TaskSpan[t.Any] | None] | None = None # contextvars context attributes = { SPAN_ATTRIBUTE_RUN_ID: str(run_id), diff --git a/dreadnode/util.py b/dreadnode/util.py index aa25a60c..30fdd233 100644 --- a/dreadnode/util.py +++ b/dreadnode/util.py @@ -54,7 +54,7 @@ def log_internal_error() -> None: reraise = False if reraise: - raise + raise # noqa: PLE0704 with suppress_instrumentation(): # prevent infinite recursion from the logging integration logger.exception( diff --git a/poetry.lock b/poetry.lock index 3dc25a8e..2f86c5df 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,37 +1,12 @@ # This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. -[[package]] -name = "aiobotocore" -version = "2.21.1" -description = "Async client for aws services using botocore and aiohttp" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "aiobotocore-2.21.1-py3-none-any.whl", hash = "sha256:bd7c49a6d6f8a3d9444b0a94417c8da13813b5c7eec1c4f0ec2db7e8ce8f23e7"}, - {file = "aiobotocore-2.21.1.tar.gz", hash = "sha256:010357f43004413e92a9d066bb0db1f241aeb29ffed306e9197061ffc94e6577"}, -] - -[package.dependencies] -aiohttp = ">=3.9.2,<4.0.0" -aioitertools = ">=0.5.1,<1.0.0" -botocore = ">=1.37.0,<1.37.2" -jmespath = ">=0.7.1,<2.0.0" -multidict = ">=6.0.0,<7.0.0" -python-dateutil = ">=2.1,<3.0.0" -wrapt = ">=1.10.10,<2.0.0" - -[package.extras] -awscli = ["awscli (>=1.38.0,<1.38.2)"] -boto3 = ["boto3 (>=1.37.0,<1.37.2)"] - [[package]] name = "aiohappyeyeballs" version = "2.6.1" description = "Happy Eyeballs for asyncio" optional = false python-versions = ">=3.9" -groups = ["main"] +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"}, @@ -39,93 +14,93 @@ files = [ [[package]] name = "aiohttp" -version = "3.11.16" +version = "3.11.18" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "dev"] files = [ - {file = "aiohttp-3.11.16-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb46bb0f24813e6cede6cc07b1961d4b04f331f7112a23b5e21f567da4ee50aa"}, - {file = "aiohttp-3.11.16-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:54eb3aead72a5c19fad07219acd882c1643a1027fbcdefac9b502c267242f955"}, - {file = "aiohttp-3.11.16-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:38bea84ee4fe24ebcc8edeb7b54bf20f06fd53ce4d2cc8b74344c5b9620597fd"}, - {file = "aiohttp-3.11.16-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0666afbe984f6933fe72cd1f1c3560d8c55880a0bdd728ad774006eb4241ecd"}, - {file = "aiohttp-3.11.16-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ba92a2d9ace559a0a14b03d87f47e021e4fa7681dc6970ebbc7b447c7d4b7cd"}, - {file = "aiohttp-3.11.16-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ad1d59fd7114e6a08c4814983bb498f391c699f3c78712770077518cae63ff7"}, - {file = "aiohttp-3.11.16-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b88a2bf26965f2015a771381624dd4b0839034b70d406dc74fd8be4cc053e3"}, - {file = "aiohttp-3.11.16-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:576f5ca28d1b3276026f7df3ec841ae460e0fc3aac2a47cbf72eabcfc0f102e1"}, - {file = "aiohttp-3.11.16-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a2a450bcce4931b295fc0848f384834c3f9b00edfc2150baafb4488c27953de6"}, - {file = "aiohttp-3.11.16-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:37dcee4906454ae377be5937ab2a66a9a88377b11dd7c072df7a7c142b63c37c"}, - {file = "aiohttp-3.11.16-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4d0c970c0d602b1017e2067ff3b7dac41c98fef4f7472ec2ea26fd8a4e8c2149"}, - {file = "aiohttp-3.11.16-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:004511d3413737700835e949433536a2fe95a7d0297edd911a1e9705c5b5ea43"}, - {file = "aiohttp-3.11.16-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:c15b2271c44da77ee9d822552201180779e5e942f3a71fb74e026bf6172ff287"}, - {file = "aiohttp-3.11.16-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ad9509ffb2396483ceacb1eee9134724443ee45b92141105a4645857244aecc8"}, - {file = "aiohttp-3.11.16-cp310-cp310-win32.whl", hash = "sha256:634d96869be6c4dc232fc503e03e40c42d32cfaa51712aee181e922e61d74814"}, - {file = "aiohttp-3.11.16-cp310-cp310-win_amd64.whl", hash = "sha256:938f756c2b9374bbcc262a37eea521d8a0e6458162f2a9c26329cc87fdf06534"}, - {file = "aiohttp-3.11.16-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8cb0688a8d81c63d716e867d59a9ccc389e97ac7037ebef904c2b89334407180"}, - {file = "aiohttp-3.11.16-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0ad1fb47da60ae1ddfb316f0ff16d1f3b8e844d1a1e154641928ea0583d486ed"}, - {file = "aiohttp-3.11.16-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:df7db76400bf46ec6a0a73192b14c8295bdb9812053f4fe53f4e789f3ea66bbb"}, - {file = "aiohttp-3.11.16-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc3a145479a76ad0ed646434d09216d33d08eef0d8c9a11f5ae5cdc37caa3540"}, - {file = "aiohttp-3.11.16-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d007aa39a52d62373bd23428ba4a2546eed0e7643d7bf2e41ddcefd54519842c"}, - {file = "aiohttp-3.11.16-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6ddd90d9fb4b501c97a4458f1c1720e42432c26cb76d28177c5b5ad4e332601"}, - {file = "aiohttp-3.11.16-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a2f451849e6b39e5c226803dcacfa9c7133e9825dcefd2f4e837a2ec5a3bb98"}, - {file = "aiohttp-3.11.16-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8df6612df74409080575dca38a5237282865408016e65636a76a2eb9348c2567"}, - {file = "aiohttp-3.11.16-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:78e6e23b954644737e385befa0deb20233e2dfddf95dd11e9db752bdd2a294d3"}, - {file = "aiohttp-3.11.16-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:696ef00e8a1f0cec5e30640e64eca75d8e777933d1438f4facc9c0cdf288a810"}, - {file = "aiohttp-3.11.16-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e3538bc9fe1b902bef51372462e3d7c96fce2b566642512138a480b7adc9d508"}, - {file = "aiohttp-3.11.16-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:3ab3367bb7f61ad18793fea2ef71f2d181c528c87948638366bf1de26e239183"}, - {file = "aiohttp-3.11.16-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:56a3443aca82abda0e07be2e1ecb76a050714faf2be84256dae291182ba59049"}, - {file = "aiohttp-3.11.16-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:61c721764e41af907c9d16b6daa05a458f066015abd35923051be8705108ed17"}, - {file = "aiohttp-3.11.16-cp311-cp311-win32.whl", hash = "sha256:3e061b09f6fa42997cf627307f220315e313ece74907d35776ec4373ed718b86"}, - {file = "aiohttp-3.11.16-cp311-cp311-win_amd64.whl", hash = "sha256:745f1ed5e2c687baefc3c5e7b4304e91bf3e2f32834d07baaee243e349624b24"}, - {file = "aiohttp-3.11.16-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:911a6e91d08bb2c72938bc17f0a2d97864c531536b7832abee6429d5296e5b27"}, - {file = "aiohttp-3.11.16-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6ac13b71761e49d5f9e4d05d33683bbafef753e876e8e5a7ef26e937dd766713"}, - {file = "aiohttp-3.11.16-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fd36c119c5d6551bce374fcb5c19269638f8d09862445f85a5a48596fd59f4bb"}, - {file = "aiohttp-3.11.16-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d489d9778522fbd0f8d6a5c6e48e3514f11be81cb0a5954bdda06f7e1594b321"}, - {file = "aiohttp-3.11.16-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69a2cbd61788d26f8f1e626e188044834f37f6ae3f937bd9f08b65fc9d7e514e"}, - {file = "aiohttp-3.11.16-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd464ba806e27ee24a91362ba3621bfc39dbbb8b79f2e1340201615197370f7c"}, - {file = "aiohttp-3.11.16-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ce63ae04719513dd2651202352a2beb9f67f55cb8490c40f056cea3c5c355ce"}, - {file = "aiohttp-3.11.16-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09b00dd520d88eac9d1768439a59ab3d145065c91a8fab97f900d1b5f802895e"}, - {file = "aiohttp-3.11.16-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7f6428fee52d2bcf96a8aa7b62095b190ee341ab0e6b1bcf50c615d7966fd45b"}, - {file = "aiohttp-3.11.16-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:13ceac2c5cdcc3f64b9015710221ddf81c900c5febc505dbd8f810e770011540"}, - {file = "aiohttp-3.11.16-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:fadbb8f1d4140825069db3fedbbb843290fd5f5bc0a5dbd7eaf81d91bf1b003b"}, - {file = "aiohttp-3.11.16-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6a792ce34b999fbe04a7a71a90c74f10c57ae4c51f65461a411faa70e154154e"}, - {file = "aiohttp-3.11.16-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:f4065145bf69de124accdd17ea5f4dc770da0a6a6e440c53f6e0a8c27b3e635c"}, - {file = "aiohttp-3.11.16-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fa73e8c2656a3653ae6c307b3f4e878a21f87859a9afab228280ddccd7369d71"}, - {file = "aiohttp-3.11.16-cp312-cp312-win32.whl", hash = "sha256:f244b8e541f414664889e2c87cac11a07b918cb4b540c36f7ada7bfa76571ea2"}, - {file = "aiohttp-3.11.16-cp312-cp312-win_amd64.whl", hash = "sha256:23a15727fbfccab973343b6d1b7181bfb0b4aa7ae280f36fd2f90f5476805682"}, - {file = "aiohttp-3.11.16-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a3814760a1a700f3cfd2f977249f1032301d0a12c92aba74605cfa6ce9f78489"}, - {file = "aiohttp-3.11.16-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9b751a6306f330801665ae69270a8a3993654a85569b3469662efaad6cf5cc50"}, - {file = "aiohttp-3.11.16-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ad497f38a0d6c329cb621774788583ee12321863cd4bd9feee1effd60f2ad133"}, - {file = "aiohttp-3.11.16-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca37057625693d097543bd88076ceebeb248291df9d6ca8481349efc0b05dcd0"}, - {file = "aiohttp-3.11.16-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a5abcbba9f4b463a45c8ca8b7720891200658f6f46894f79517e6cd11f3405ca"}, - {file = "aiohttp-3.11.16-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f420bfe862fb357a6d76f2065447ef6f484bc489292ac91e29bc65d2d7a2c84d"}, - {file = "aiohttp-3.11.16-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58ede86453a6cf2d6ce40ef0ca15481677a66950e73b0a788917916f7e35a0bb"}, - {file = "aiohttp-3.11.16-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fdec0213244c39973674ca2a7f5435bf74369e7d4e104d6c7473c81c9bcc8c4"}, - {file = "aiohttp-3.11.16-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:72b1b03fb4655c1960403c131740755ec19c5898c82abd3961c364c2afd59fe7"}, - {file = "aiohttp-3.11.16-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:780df0d837276276226a1ff803f8d0fa5f8996c479aeef52eb040179f3156cbd"}, - {file = "aiohttp-3.11.16-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ecdb8173e6c7aa09eee342ac62e193e6904923bd232e76b4157ac0bfa670609f"}, - {file = "aiohttp-3.11.16-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:a6db7458ab89c7d80bc1f4e930cc9df6edee2200127cfa6f6e080cf619eddfbd"}, - {file = "aiohttp-3.11.16-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2540ddc83cc724b13d1838026f6a5ad178510953302a49e6d647f6e1de82bc34"}, - {file = "aiohttp-3.11.16-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3b4e6db8dc4879015b9955778cfb9881897339c8fab7b3676f8433f849425913"}, - {file = "aiohttp-3.11.16-cp313-cp313-win32.whl", hash = "sha256:493910ceb2764f792db4dc6e8e4b375dae1b08f72e18e8f10f18b34ca17d0979"}, - {file = "aiohttp-3.11.16-cp313-cp313-win_amd64.whl", hash = "sha256:42864e70a248f5f6a49fdaf417d9bc62d6e4d8ee9695b24c5916cb4bb666c802"}, - {file = "aiohttp-3.11.16-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bbcba75fe879ad6fd2e0d6a8d937f34a571f116a0e4db37df8079e738ea95c71"}, - {file = "aiohttp-3.11.16-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:87a6e922b2b2401e0b0cf6b976b97f11ec7f136bfed445e16384fbf6fd5e8602"}, - {file = "aiohttp-3.11.16-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccf10f16ab498d20e28bc2b5c1306e9c1512f2840f7b6a67000a517a4b37d5ee"}, - {file = "aiohttp-3.11.16-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb3d0cc5cdb926090748ea60172fa8a213cec728bd6c54eae18b96040fcd6227"}, - {file = "aiohttp-3.11.16-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d07502cc14ecd64f52b2a74ebbc106893d9a9717120057ea9ea1fd6568a747e7"}, - {file = "aiohttp-3.11.16-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:776c8e959a01e5e8321f1dec77964cb6101020a69d5a94cd3d34db6d555e01f7"}, - {file = "aiohttp-3.11.16-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0902e887b0e1d50424112f200eb9ae3dfed6c0d0a19fc60f633ae5a57c809656"}, - {file = "aiohttp-3.11.16-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e87fd812899aa78252866ae03a048e77bd11b80fb4878ce27c23cade239b42b2"}, - {file = "aiohttp-3.11.16-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0a950c2eb8ff17361abd8c85987fd6076d9f47d040ebffce67dce4993285e973"}, - {file = "aiohttp-3.11.16-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:c10d85e81d0b9ef87970ecbdbfaeec14a361a7fa947118817fcea8e45335fa46"}, - {file = "aiohttp-3.11.16-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:7951decace76a9271a1ef181b04aa77d3cc309a02a51d73826039003210bdc86"}, - {file = "aiohttp-3.11.16-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:14461157d8426bcb40bd94deb0450a6fa16f05129f7da546090cebf8f3123b0f"}, - {file = "aiohttp-3.11.16-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9756d9b9d4547e091f99d554fbba0d2a920aab98caa82a8fb3d3d9bee3c9ae85"}, - {file = "aiohttp-3.11.16-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:87944bd16b7fe6160607f6a17808abd25f17f61ae1e26c47a491b970fb66d8cb"}, - {file = "aiohttp-3.11.16-cp39-cp39-win32.whl", hash = "sha256:92b7ee222e2b903e0a4b329a9943d432b3767f2d5029dbe4ca59fb75223bbe2e"}, - {file = "aiohttp-3.11.16-cp39-cp39-win_amd64.whl", hash = "sha256:17ae4664031aadfbcb34fd40ffd90976671fa0c0286e6c4113989f78bebab37a"}, - {file = "aiohttp-3.11.16.tar.gz", hash = "sha256:16f8a2c9538c14a557b4d309ed4d0a7c60f0253e8ed7b6c9a2859a7582f8b1b8"}, + {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] @@ -141,29 +116,13 @@ 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 = "aioitertools" -version = "0.12.0" -description = "itertools and builtins for AsyncIO and mixed iterables" -optional = false -python-versions = ">=3.8" -groups = ["main"] -files = [ - {file = "aioitertools-0.12.0-py3-none-any.whl", hash = "sha256:fc1f5fac3d737354de8831cbba3eb04f79dd649d8f3afb4c5b114925e662a796"}, - {file = "aioitertools-0.12.0.tar.gz", hash = "sha256:c2a9055b4fbb7705f561b9d86053e8af5d10cc845d22c32008c43490b2d8dd6b"}, -] - -[package.extras] -dev = ["attribution (==1.8.0)", "black (==24.8.0)", "build (>=1.2)", "coverage (==7.6.1)", "flake8 (==7.1.1)", "flit (==3.9.0)", "mypy (==1.11.2)", "ufmt (==2.7.1)", "usort (==1.0.8.post1)"] -docs = ["sphinx (==8.0.2)", "sphinx-mdinclude (==0.6.2)"] - [[package]] name = "aiosignal" version = "1.3.2" description = "aiosignal: a list of registered asynchronous callbacks" optional = false python-versions = ">=3.9" -groups = ["main"] +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"}, @@ -178,7 +137,7 @@ 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"}, @@ -190,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"}, @@ -213,7 +172,7 @@ version = "5.0.1" description = "Timeout context manager for asyncio programs" optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "dev"] markers = "python_version < \"3.11\"" files = [ {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"}, @@ -226,7 +185,7 @@ version = "25.3.0" description = "Classes Without Boilerplate" optional = false python-versions = ">=3.8" -groups = ["main"] +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"}, @@ -240,16 +199,464 @@ docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphi 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.37.1" +version = "1.38.1" description = "Low-level, data-driven core of boto 3." optional = false -python-versions = ">=3.8" -groups = ["main"] +python-versions = ">=3.9" +groups = ["main", "dev"] files = [ - {file = "botocore-1.37.1-py3-none-any.whl", hash = "sha256:c1db1bfc5d8c6b3b6d1ca6794f605294b4264e82a7e727b88e0fef9c2b9fbb9c"}, - {file = "botocore-1.37.1.tar.gz", hash = "sha256:b194db8fb2a0ffba53568c364ae26166e7eec0445496b2ac86a6e142f3dd982f"}, + {file = "botocore-1.38.1-py3-none-any.whl", hash = "sha256:b1673975e3c42d0e2d1804f9f73e88961e95eac371c8f8c0a0d7e661ec3c90c3"}, + {file = "botocore-1.38.1.tar.gz", hash = "sha256:c2eb42eeaa502f236ba894a65ea7f7241711150cc450b9d59fbbad41e741adc0"}, ] [package.dependencies] @@ -260,13 +667,31 @@ urllib3 = {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version > [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" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe"}, {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, @@ -290,7 +715,7 @@ version = "3.4.1" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "charset_normalizer-3.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:91b36a978b5ae0ee86c394f5a54d6ef44db1de0815eb43de826d41d21e4af3de"}, {file = "charset_normalizer-3.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7461baadb4dc00fd9e0acbe254e3d7d2112e7f92ced2adc96e54ef6501c5f176"}, @@ -386,6 +811,21 @@ 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" @@ -393,12 +833,11 @@ 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 = ["main", "dev"] -markers = "sys_platform == \"win32\"" 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\"", dev = "sys_platform == \"win32\""} +markers = {main = "sys_platform == \"win32\" or platform_system == \"Windows\""} [[package]] name = "coolname" @@ -412,6 +851,50 @@ 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" @@ -442,6 +925,22 @@ wrapt = ">=1.10,<2" [package.extras] dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "setuptools ; python_version >= \"3.12\"", "tox"] +[[package]] +name = "dill" +version = "0.3.8" +description = "serialize all of Python" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "dill-0.3.8-py3-none-any.whl", hash = "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7"}, + {file = "dill-0.3.8.tar.gz", hash = "sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca"}, +] + +[package.extras] +graph = ["objgraph (>=1.7.2)"] +profile = ["gprof2dot (>=2022.7.29)"] + [[package]] name = "distlib" version = "0.3.9" @@ -454,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" @@ -507,7 +1078,7 @@ 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.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de"}, {file = "filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2"}, @@ -520,119 +1091,132 @@ typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""] [[package]] name = "frozenlist" -version = "1.5.0" +version = "1.6.0" description = "A list-like structure which implements collections.abc.MutableSequence" optional = false -python-versions = ">=3.8" -groups = ["main"] +python-versions = ">=3.9" +groups = ["main", "dev"] files = [ - {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5b6a66c18b5b9dd261ca98dffcb826a525334b2f29e7caa54e182255c5f6a65a"}, - {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d1b3eb7b05ea246510b43a7e53ed1653e55c2121019a97e60cad7efb881a97bb"}, - {file = "frozenlist-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:15538c0cbf0e4fa11d1e3a71f823524b0c46299aed6e10ebb4c2089abd8c3bec"}, - {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e79225373c317ff1e35f210dd5f1344ff31066ba8067c307ab60254cd3a78ad5"}, - {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9272fa73ca71266702c4c3e2d4a28553ea03418e591e377a03b8e3659d94fa76"}, - {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:498524025a5b8ba81695761d78c8dd7382ac0b052f34e66939c42df860b8ff17"}, - {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92b5278ed9d50fe610185ecd23c55d8b307d75ca18e94c0e7de328089ac5dcba"}, - {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f3c8c1dacd037df16e85227bac13cca58c30da836c6f936ba1df0c05d046d8d"}, - {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f2ac49a9bedb996086057b75bf93538240538c6d9b38e57c82d51f75a73409d2"}, - {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e66cc454f97053b79c2ab09c17fbe3c825ea6b4de20baf1be28919460dd7877f"}, - {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:5a3ba5f9a0dfed20337d3e966dc359784c9f96503674c2faf015f7fe8e96798c"}, - {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6321899477db90bdeb9299ac3627a6a53c7399c8cd58d25da094007402b039ab"}, - {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76e4753701248476e6286f2ef492af900ea67d9706a0155335a40ea21bf3b2f5"}, - {file = "frozenlist-1.5.0-cp310-cp310-win32.whl", hash = "sha256:977701c081c0241d0955c9586ffdd9ce44f7a7795df39b9151cd9a6fd0ce4cfb"}, - {file = "frozenlist-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:189f03b53e64144f90990d29a27ec4f7997d91ed3d01b51fa39d2dbe77540fd4"}, - {file = "frozenlist-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fd74520371c3c4175142d02a976aee0b4cb4a7cc912a60586ffd8d5929979b30"}, - {file = "frozenlist-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2f3f7a0fbc219fb4455264cae4d9f01ad41ae6ee8524500f381de64ffaa077d5"}, - {file = "frozenlist-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f47c9c9028f55a04ac254346e92977bf0f166c483c74b4232bee19a6697e4778"}, - {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0996c66760924da6e88922756d99b47512a71cfd45215f3570bf1e0b694c206a"}, - {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2fe128eb4edeabe11896cb6af88fca5346059f6c8d807e3b910069f39157869"}, - {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a8ea951bbb6cacd492e3948b8da8c502a3f814f5d20935aae74b5df2b19cf3d"}, - {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de537c11e4aa01d37db0d403b57bd6f0546e71a82347a97c6a9f0dcc532b3a45"}, - {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c2623347b933fcb9095841f1cc5d4ff0b278addd743e0e966cb3d460278840d"}, - {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cee6798eaf8b1416ef6909b06f7dc04b60755206bddc599f52232606e18179d3"}, - {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f5f9da7f5dbc00a604fe74aa02ae7c98bcede8a3b8b9666f9f86fc13993bc71a"}, - {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:90646abbc7a5d5c7c19461d2e3eeb76eb0b204919e6ece342feb6032c9325ae9"}, - {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:bdac3c7d9b705d253b2ce370fde941836a5f8b3c5c2b8fd70940a3ea3af7f4f2"}, - {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03d33c2ddbc1816237a67f66336616416e2bbb6beb306e5f890f2eb22b959cdf"}, - {file = "frozenlist-1.5.0-cp311-cp311-win32.whl", hash = "sha256:237f6b23ee0f44066219dae14c70ae38a63f0440ce6750f868ee08775073f942"}, - {file = "frozenlist-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:0cc974cc93d32c42e7b0f6cf242a6bd941c57c61b618e78b6c0a96cb72788c1d"}, - {file = "frozenlist-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:31115ba75889723431aa9a4e77d5f398f5cf976eea3bdf61749731f62d4a4a21"}, - {file = "frozenlist-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7437601c4d89d070eac8323f121fcf25f88674627505334654fd027b091db09d"}, - {file = "frozenlist-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7948140d9f8ece1745be806f2bfdf390127cf1a763b925c4a805c603df5e697e"}, - {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feeb64bc9bcc6b45c6311c9e9b99406660a9c05ca8a5b30d14a78555088b0b3a"}, - {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:683173d371daad49cffb8309779e886e59c2f369430ad28fe715f66d08d4ab1a"}, - {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d57d8f702221405a9d9b40f9da8ac2e4a1a8b5285aac6100f3393675f0a85ee"}, - {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c72000fbcc35b129cb09956836c7d7abf78ab5416595e4857d1cae8d6251a6"}, - {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000a77d6034fbad9b6bb880f7ec073027908f1b40254b5d6f26210d2dab1240e"}, - {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d7f5a50342475962eb18b740f3beecc685a15b52c91f7d975257e13e029eca9"}, - {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:87f724d055eb4785d9be84e9ebf0f24e392ddfad00b3fe036e43f489fafc9039"}, - {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6e9080bb2fb195a046e5177f10d9d82b8a204c0736a97a153c2466127de87784"}, - {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b93d7aaa36c966fa42efcaf716e6b3900438632a626fb09c049f6a2f09fc631"}, - {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:52ef692a4bc60a6dd57f507429636c2af8b6046db8b31b18dac02cbc8f507f7f"}, - {file = "frozenlist-1.5.0-cp312-cp312-win32.whl", hash = "sha256:29d94c256679247b33a3dc96cce0f93cbc69c23bf75ff715919332fdbb6a32b8"}, - {file = "frozenlist-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:8969190d709e7c48ea386db202d708eb94bdb29207a1f269bab1196ce0dcca1f"}, - {file = "frozenlist-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a1a048f9215c90973402e26c01d1cff8a209e1f1b53f72b95c13db61b00f953"}, - {file = "frozenlist-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dd47a5181ce5fcb463b5d9e17ecfdb02b678cca31280639255ce9d0e5aa67af0"}, - {file = "frozenlist-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1431d60b36d15cda188ea222033eec8e0eab488f39a272461f2e6d9e1a8e63c2"}, - {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6482a5851f5d72767fbd0e507e80737f9c8646ae7fd303def99bfe813f76cf7f"}, - {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44c49271a937625619e862baacbd037a7ef86dd1ee215afc298a417ff3270608"}, - {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12f78f98c2f1c2429d42e6a485f433722b0061d5c0b0139efa64f396efb5886b"}, - {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce3aa154c452d2467487765e3adc730a8c153af77ad84096bc19ce19a2400840"}, - {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b7dc0c4338e6b8b091e8faf0db3168a37101943e687f373dce00959583f7439"}, - {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45e0896250900b5aa25180f9aec243e84e92ac84bd4a74d9ad4138ef3f5c97de"}, - {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:561eb1c9579d495fddb6da8959fd2a1fca2c6d060d4113f5844b433fc02f2641"}, - {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:df6e2f325bfee1f49f81aaac97d2aa757c7646534a06f8f577ce184afe2f0a9e"}, - {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:140228863501b44b809fb39ec56b5d4071f4d0aa6d216c19cbb08b8c5a7eadb9"}, - {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7707a25d6a77f5d27ea7dc7d1fc608aa0a478193823f88511ef5e6b8a48f9d03"}, - {file = "frozenlist-1.5.0-cp313-cp313-win32.whl", hash = "sha256:31a9ac2b38ab9b5a8933b693db4939764ad3f299fcaa931a3e605bc3460e693c"}, - {file = "frozenlist-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:11aabdd62b8b9c4b84081a3c246506d1cddd2dd93ff0ad53ede5defec7886b28"}, - {file = "frozenlist-1.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:dd94994fc91a6177bfaafd7d9fd951bc8689b0a98168aa26b5f543868548d3ca"}, - {file = "frozenlist-1.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0da8bbec082bf6bf18345b180958775363588678f64998c2b7609e34719b10"}, - {file = "frozenlist-1.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:73f2e31ea8dd7df61a359b731716018c2be196e5bb3b74ddba107f694fbd7604"}, - {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:828afae9f17e6de596825cf4228ff28fbdf6065974e5ac1410cecc22f699d2b3"}, - {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1577515d35ed5649d52ab4319db757bb881ce3b2b796d7283e6634d99ace307"}, - {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2150cc6305a2c2ab33299453e2968611dacb970d2283a14955923062c8d00b10"}, - {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a72b7a6e3cd2725eff67cd64c8f13335ee18fc3c7befc05aed043d24c7b9ccb9"}, - {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c16d2fa63e0800723139137d667e1056bee1a1cf7965153d2d104b62855e9b99"}, - {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:17dcc32fc7bda7ce5875435003220a457bcfa34ab7924a49a1c19f55b6ee185c"}, - {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:97160e245ea33d8609cd2b8fd997c850b56db147a304a262abc2b3be021a9171"}, - {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f1e6540b7fa044eee0bb5111ada694cf3dc15f2b0347ca125ee9ca984d5e9e6e"}, - {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:91d6c171862df0a6c61479d9724f22efb6109111017c87567cfeb7b5d1449fdf"}, - {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c1fac3e2ace2eb1052e9f7c7db480818371134410e1f5c55d65e8f3ac6d1407e"}, - {file = "frozenlist-1.5.0-cp38-cp38-win32.whl", hash = "sha256:b97f7b575ab4a8af9b7bc1d2ef7f29d3afee2226bd03ca3875c16451ad5a7723"}, - {file = "frozenlist-1.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:374ca2dabdccad8e2a76d40b1d037f5bd16824933bf7bcea3e59c891fd4a0923"}, - {file = "frozenlist-1.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9bbcdfaf4af7ce002694a4e10a0159d5a8d20056a12b05b45cea944a4953f972"}, - {file = "frozenlist-1.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1893f948bf6681733aaccf36c5232c231e3b5166d607c5fa77773611df6dc336"}, - {file = "frozenlist-1.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2b5e23253bb709ef57a8e95e6ae48daa9ac5f265637529e4ce6b003a37b2621f"}, - {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f253985bb515ecd89629db13cb58d702035ecd8cfbca7d7a7e29a0e6d39af5f"}, - {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04a5c6babd5e8fb7d3c871dc8b321166b80e41b637c31a995ed844a6139942b6"}, - {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9fe0f1c29ba24ba6ff6abf688cb0b7cf1efab6b6aa6adc55441773c252f7411"}, - {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:226d72559fa19babe2ccd920273e767c96a49b9d3d38badd7c91a0fdeda8ea08"}, - {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15b731db116ab3aedec558573c1a5eec78822b32292fe4f2f0345b7f697745c2"}, - {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:366d8f93e3edfe5a918c874702f78faac300209a4d5bf38352b2c1bdc07a766d"}, - {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1b96af8c582b94d381a1c1f51ffaedeb77c821c690ea5f01da3d70a487dd0a9b"}, - {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c03eff4a41bd4e38415cbed054bbaff4a075b093e2394b6915dca34a40d1e38b"}, - {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:50cf5e7ee9b98f22bdecbabf3800ae78ddcc26e4a435515fc72d97903e8488e0"}, - {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1e76bfbc72353269c44e0bc2cfe171900fbf7f722ad74c9a7b638052afe6a00c"}, - {file = "frozenlist-1.5.0-cp39-cp39-win32.whl", hash = "sha256:666534d15ba8f0fda3f53969117383d5dc021266b3c1a42c9ec4855e4b58b9d3"}, - {file = "frozenlist-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:5c28f4b5dbef8a0d8aad0d4de24d1e9e981728628afaf4ea0792f5d0939372f0"}, - {file = "frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3"}, - {file = "frozenlist-1.5.0.tar.gz", hash = "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817"}, + {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 = "2025.3.2" +version = "2024.12.0" description = "File-system specification" optional = false -python-versions = ">=3.9" -groups = ["main"] +python-versions = ">=3.8" +groups = ["main", "dev"] files = [ - {file = "fsspec-2025.3.2-py3-none-any.whl", hash = "sha256:2daf8dc3d1dfa65b6aa37748d112773a7a08416f6c70d96b264c96476ecaf711"}, - {file = "fsspec-2025.3.2.tar.gz", hash = "sha256:e52c77ef398680bbd6a98c0e628fbc469491282981209907bbc8aea76a04fdc6"}, + {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] @@ -659,7 +1243,7 @@ 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[dataframe,test]", "moto[server] (>4,<5)", "pytest-timeout", "xarray"] +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"] @@ -687,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"}, @@ -699,7 +1283,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.8-py3-none-any.whl", hash = "sha256:5254cf149bcb5f75e9d1b2b9f729ea4a4b883d1ad7379fc632b727cec23674be"}, {file = "httpcore-1.0.8.tar.gz", hash = "sha256:86e94505ed24ea06514883fd44d2bc02d90e77e7979c8eb71b90f41d364a1bad"}, @@ -721,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"}, @@ -740,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.9" +version = "2.6.10" description = "File identification library for Python" optional = false python-versions = ">=3.9" groups = ["dev"] files = [ - {file = "identify-2.6.9-py2.py3-none-any.whl", hash = "sha256:c98b4322da415a8e5a70ff6e51fbc2d2932c015532d77e9f8537b4ba7813b150"}, - {file = "identify-2.6.9.tar.gz", hash = "sha256:d40dfe3142a1421d8518e3d3985ef5ac42890683e32306ad614a29490abeb6bf"}, + {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] @@ -761,7 +1381,7 @@ version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -827,7 +1447,7 @@ 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"}, @@ -857,18 +1477,216 @@ files = [ {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"] +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 = "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.14.0" @@ -913,13 +1731,25 @@ 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"] +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"}, @@ -938,7 +1768,7 @@ 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"}, @@ -957,13 +1787,84 @@ 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"}, @@ -1001,7 +1902,7 @@ version = "6.4.3" description = "multidict implementation" optional = false python-versions = ">=3.9" -groups = ["main"] +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"}, @@ -1112,6 +2013,31 @@ files = [ [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" @@ -1166,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]] @@ -1192,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", "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 = "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 = "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.32.0" +version = "1.32.1" description = "OpenTelemetry Python API" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "opentelemetry_api-1.32.0-py3-none-any.whl", hash = "sha256:15df743c765078611f376037b0d9111ec5c1febf2ec9440cdd919370faa1ce55"}, - {file = "opentelemetry_api-1.32.0.tar.gz", hash = "sha256:2623280c916f9b19cad0aa4280cb171265f19fd2909b0d47e4f06f7c83b02cb5"}, + {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] @@ -1273,68 +2241,68 @@ importlib-metadata = ">=6.0,<8.7.0" [[package]] name = "opentelemetry-exporter-otlp-proto-common" -version = "1.32.0" +version = "1.32.1" description = "OpenTelemetry Protobuf encoding" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "opentelemetry_exporter_otlp_proto_common-1.32.0-py3-none-any.whl", hash = "sha256:277a63a18768b3b460d082a489f6f80d4ae2c1e6b185bb701c6bd4e91405e4bd"}, - {file = "opentelemetry_exporter_otlp_proto_common-1.32.0.tar.gz", hash = "sha256:2bca672f2a279c4f517115e635c0cc1269d07b2982a36681c521f7e56179a222"}, + {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.32.0" +opentelemetry-proto = "1.32.1" [[package]] name = "opentelemetry-exporter-otlp-proto-http" -version = "1.32.0" +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.32.0-py3-none-any.whl", hash = "sha256:e2ffecd6d2220eaf1291a46339f109bc0a57ee7c4c6abb8174df418bf00ce01f"}, - {file = "opentelemetry_exporter_otlp_proto_http-1.32.0.tar.gz", hash = "sha256:a5dfd94603da86e313e4f4fb8d181fd3b64a7c2a9c7b408c3653d2b1bc68d14f"}, + {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.32.0" -opentelemetry-proto = "1.32.0" -opentelemetry-sdk = ">=1.32.0,<1.33.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.53b0" +version = "0.53b1" description = "Instrumentation Tools & Auto Instrumentation for OpenTelemetry Python" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "opentelemetry_instrumentation-0.53b0-py3-none-any.whl", hash = "sha256:70600778fd567c9c5fbfca181378ae179c0dec3ff613171707d3d77c360ff105"}, - {file = "opentelemetry_instrumentation-0.53b0.tar.gz", hash = "sha256:f2c21d71a3cdf28c656e3d90d247ee7558fb9b0239b3d9e9190266499dbed9d2"}, + {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.53b0" +opentelemetry-semantic-conventions = "0.53b1" packaging = ">=18.0" wrapt = ">=1.0.0,<2.0.0" [[package]] name = "opentelemetry-proto" -version = "1.32.0" +version = "1.32.1" description = "OpenTelemetry Python Proto" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "opentelemetry_proto-1.32.0-py3-none-any.whl", hash = "sha256:f699269dc037e18fba05442580a8682c9fbd0f4c7f5addfed82c44be0c53c5ff"}, - {file = "opentelemetry_proto-1.32.0.tar.gz", hash = "sha256:f8b70ae52f4ef8a4e4c0760e87c9071e07ece2618c080d4839bef44c0156cd44"}, + {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] @@ -1342,47 +2310,47 @@ protobuf = ">=5.0,<6.0" [[package]] name = "opentelemetry-sdk" -version = "1.32.0" +version = "1.32.1" description = "OpenTelemetry Python SDK" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "opentelemetry_sdk-1.32.0-py3-none-any.whl", hash = "sha256:ed252d035c22a15536c1f603ca089298daab60850fc2f5ddfa95d95cc1c043ea"}, - {file = "opentelemetry_sdk-1.32.0.tar.gz", hash = "sha256:5ff07fb371d1ab1189fa7047702e2e888b5403c5efcbb18083cae0d5aa5f58d2"}, + {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.32.0" -opentelemetry-semantic-conventions = "0.53b0" +opentelemetry-api = "1.32.1" +opentelemetry-semantic-conventions = "0.53b1" typing-extensions = ">=3.7.4" [[package]] name = "opentelemetry-semantic-conventions" -version = "0.53b0" +version = "0.53b1" description = "OpenTelemetry Semantic Conventions" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "opentelemetry_semantic_conventions-0.53b0-py3-none-any.whl", hash = "sha256:561da89f766ab51615c0e72b12329e0a1bc16945dbd62c8646ffc74e36a1edff"}, - {file = "opentelemetry_semantic_conventions-0.53b0.tar.gz", hash = "sha256:05b7908e1da62d72f9bf717ed25c72f566fe005a2dd260c61b11e025f2552cf6"}, + {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.32.0" +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]] @@ -1391,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"}, @@ -1619,6 +2587,18 @@ 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 = "3.8.0" @@ -1659,7 +2639,7 @@ version = "0.3.1" description = "Accelerated property cache" optional = false python-versions = ">=3.9" -groups = ["main"] +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"}, @@ -1843,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"}, @@ -1865,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"}, @@ -1971,6 +2951,26 @@ 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" @@ -1989,7 +2989,7 @@ 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"}, @@ -2046,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"}, @@ -2061,7 +3061,7 @@ 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"] +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"}, @@ -2091,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"}, @@ -2103,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"}, @@ -2160,13 +3160,134 @@ 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" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" -groups = ["main"] +groups = ["main", "dev"] files = [ {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, @@ -2188,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"}, @@ -2202,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"}, @@ -2227,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"}, @@ -2236,6 +3514,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f66efbc1caa63c088dead1c4170d148eabc9b80d95fb75b6c92ac0aad2437d76"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:22353049ba4181685023b25b5b51a574bce33e7f51c759371a7422dcae5402a6"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:932205970b9f9991b34f55136be327501903f7c66830e9760a8ffb15b07f05cd"}, + {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a52d48f4e7bf9005e8f0a89209bf9a73f7190ddf0489eee5eb51377385f59f2a"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win32.whl", hash = "sha256:3eac5a91891ceb88138c113f9db04f3cebdae277f5d44eaa3651a4f573e6a5da"}, {file = "ruamel.yaml.clib-0.2.12-cp310-cp310-win_amd64.whl", hash = "sha256:ab007f2f5a87bd08ab1499bdf96f3d5c6ad4dcfa364884cb4549aa0154b13a28"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:4a6679521a58256a90b0d89e03992c15144c5f3858f40d7c18886023d7943db6"}, @@ -2244,6 +3523,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:811ea1594b8a0fb466172c384267a4e5e367298af6b228931f273b111f17ef52"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cf12567a7b565cbf65d438dec6cfbe2917d3c1bdddfce84a9930b7d35ea59642"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7dd5adc8b930b12c8fc5b99e2d535a09889941aa0d0bd06f4749e9a9397c71d2"}, + {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1492a6051dab8d912fc2adeef0e8c72216b24d57bd896ea607cb90bb0c4981d3"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win32.whl", hash = "sha256:bd0a08f0bab19093c54e18a14a10b4322e1eacc5217056f3c063bd2f59853ce4"}, {file = "ruamel.yaml.clib-0.2.12-cp311-cp311-win_amd64.whl", hash = "sha256:a274fb2cb086c7a3dea4322ec27f4cb5cc4b6298adb583ab0e211a4682f241eb"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:20b0f8dc160ba83b6dcc0e256846e1a02d044e13f7ea74a3d1d56ede4e48c632"}, @@ -2252,6 +3532,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:749c16fcc4a2b09f28843cda5a193e0283e47454b63ec4b81eaa2242f50e4ccd"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bf165fef1f223beae7333275156ab2022cffe255dcc51c27f066b4370da81e31"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:32621c177bbf782ca5a18ba4d7af0f1082a3f6e517ac2a18b3974d4edf349680"}, + {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b82a7c94a498853aa0b272fd5bc67f29008da798d4f93a2f9f289feb8426a58d"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win32.whl", hash = "sha256:e8c4ebfcfd57177b572e2040777b8abc537cdef58a2120e830124946aa9b42c5"}, {file = "ruamel.yaml.clib-0.2.12-cp312-cp312-win_amd64.whl", hash = "sha256:0467c5965282c62203273b838ae77c0d29d7638c8a4e3a1c8bdd3602c10904e4"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4c8c5d82f50bb53986a5e02d1b3092b03622c02c2eb78e29bec33fd9593bae1a"}, @@ -2260,6 +3541,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96777d473c05ee3e5e3c3e999f5d23c6f4ec5b0c38c098b3a5229085f74236c6"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:3bc2a80e6420ca8b7d3590791e2dfc709c88ab9152c00eeb511c9875ce5778bf"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e188d2699864c11c36cdfdada94d781fd5d6b0071cd9c427bceb08ad3d7c70e1"}, + {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f6f3eac23941b32afccc23081e1f50612bdbe4e982012ef4f5797986828cd01"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win32.whl", hash = "sha256:6442cb36270b3afb1b4951f060eccca1ce49f3d087ca1ca4563a6eb479cb3de6"}, {file = "ruamel.yaml.clib-0.2.12-cp313-cp313-win_amd64.whl", hash = "sha256:e5b8daf27af0b90da7bb903a876477a9e6d7270be6146906b276605997c7e9a3"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:fc4b630cd3fa2cf7fce38afa91d7cfe844a9f75d7f0f36393fa98815e911d987"}, @@ -2268,6 +3550,7 @@ files = [ {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2f1c3765db32be59d18ab3953f43ab62a761327aafc1594a2a1fbe038b8b8a7"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d85252669dc32f98ebcd5d36768f5d4faeaeaa2d655ac0473be490ecdae3c285"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e143ada795c341b56de9418c58d028989093ee611aa27ffb9b7f609c00d813ed"}, + {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:2c59aa6170b990d8d2719323e628aaf36f3bfbc1c26279c0eeeb24d05d2d11c7"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win32.whl", hash = "sha256:beffaed67936fbbeffd10966a4eb53c402fafd3d6833770516bf7314bc6ffa12"}, {file = "ruamel.yaml.clib-0.2.12-cp39-cp39-win_amd64.whl", hash = "sha256:040ae85536960525ea62868b642bdb0c2cc6021c9f9d507810c0c604e66f5a7b"}, {file = "ruamel.yaml.clib-0.2.12.tar.gz", hash = "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f"}, @@ -2275,51 +3558,77 @@ files = [ [[package]] name = "ruff" -version = "0.1.15" +version = "0.11.6" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" groups = ["dev"] files = [ - {file = "ruff-0.1.15-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:5fe8d54df166ecc24106db7dd6a68d44852d14eb0729ea4672bb4d96c320b7df"}, - {file = "ruff-0.1.15-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:6f0bfbb53c4b4de117ac4d6ddfd33aa5fc31beeaa21d23c45c6dd249faf9126f"}, - {file = "ruff-0.1.15-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0d432aec35bfc0d800d4f70eba26e23a352386be3a6cf157083d18f6f5881c8"}, - {file = "ruff-0.1.15-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9405fa9ac0e97f35aaddf185a1be194a589424b8713e3b97b762336ec79ff807"}, - {file = "ruff-0.1.15-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c66ec24fe36841636e814b8f90f572a8c0cb0e54d8b5c2d0e300d28a0d7bffec"}, - {file = "ruff-0.1.15-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:6f8ad828f01e8dd32cc58bc28375150171d198491fc901f6f98d2a39ba8e3ff5"}, - {file = "ruff-0.1.15-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86811954eec63e9ea162af0ffa9f8d09088bab51b7438e8b6488b9401863c25e"}, - {file = "ruff-0.1.15-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fd4025ac5e87d9b80e1f300207eb2fd099ff8200fa2320d7dc066a3f4622dc6b"}, - {file = "ruff-0.1.15-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b17b93c02cdb6aeb696effecea1095ac93f3884a49a554a9afa76bb125c114c1"}, - {file = "ruff-0.1.15-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ddb87643be40f034e97e97f5bc2ef7ce39de20e34608f3f829db727a93fb82c5"}, - {file = "ruff-0.1.15-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:abf4822129ed3a5ce54383d5f0e964e7fef74a41e48eb1dfad404151efc130a2"}, - {file = "ruff-0.1.15-py3-none-musllinux_1_2_i686.whl", hash = "sha256:6c629cf64bacfd136c07c78ac10a54578ec9d1bd2a9d395efbee0935868bf852"}, - {file = "ruff-0.1.15-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1bab866aafb53da39c2cadfb8e1c4550ac5340bb40300083eb8967ba25481447"}, - {file = "ruff-0.1.15-py3-none-win32.whl", hash = "sha256:2417e1cb6e2068389b07e6fa74c306b2810fe3ee3476d5b8a96616633f40d14f"}, - {file = "ruff-0.1.15-py3-none-win_amd64.whl", hash = "sha256:3837ac73d869efc4182d9036b1405ef4c73d9b1f88da2413875e34e0d6919587"}, - {file = "ruff-0.1.15-py3-none-win_arm64.whl", hash = "sha256:9a933dfb1c14ec7a33cceb1e49ec4a16b51ce3c20fd42663198746efc0427360"}, - {file = "ruff-0.1.15.tar.gz", hash = "sha256:f6dfa8c1b21c913c326919056c390966648b680966febcb796cc9d1aaab8564e"}, + {file = "ruff-0.11.6-py3-none-linux_armv6l.whl", hash = "sha256:d84dcbe74cf9356d1bdb4a78cf74fd47c740bf7bdeb7529068f69b08272239a1"}, + {file = "ruff-0.11.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9bc583628e1096148011a5d51ff3c836f51899e61112e03e5f2b1573a9b726de"}, + {file = "ruff-0.11.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f2959049faeb5ba5e3b378709e9d1bf0cab06528b306b9dd6ebd2a312127964a"}, + {file = "ruff-0.11.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63c5d4e30d9d0de7fedbfb3e9e20d134b73a30c1e74b596f40f0629d5c28a193"}, + {file = "ruff-0.11.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:26a4b9a4e1439f7d0a091c6763a100cef8fbdc10d68593df6f3cfa5abdd9246e"}, + {file = "ruff-0.11.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b5edf270223dd622218256569636dc3e708c2cb989242262fe378609eccf1308"}, + {file = "ruff-0.11.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f55844e818206a9dd31ff27f91385afb538067e2dc0beb05f82c293ab84f7d55"}, + {file = "ruff-0.11.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d8f782286c5ff562e4e00344f954b9320026d8e3fae2ba9e6948443fafd9ffc"}, + {file = "ruff-0.11.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:01c63ba219514271cee955cd0adc26a4083df1956d57847978383b0e50ffd7d2"}, + {file = "ruff-0.11.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15adac20ef2ca296dd3d8e2bedc6202ea6de81c091a74661c3666e5c4c223ff6"}, + {file = "ruff-0.11.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:4dd6b09e98144ad7aec026f5588e493c65057d1b387dd937d7787baa531d9bc2"}, + {file = "ruff-0.11.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:45b2e1d6c0eed89c248d024ea95074d0e09988d8e7b1dad8d3ab9a67017a5b03"}, + {file = "ruff-0.11.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:bd40de4115b2ec4850302f1a1d8067f42e70b4990b68838ccb9ccd9f110c5e8b"}, + {file = "ruff-0.11.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:77cda2dfbac1ab73aef5e514c4cbfc4ec1fbef4b84a44c736cc26f61b3814cd9"}, + {file = "ruff-0.11.6-py3-none-win32.whl", hash = "sha256:5151a871554be3036cd6e51d0ec6eef56334d74dfe1702de717a995ee3d5b287"}, + {file = "ruff-0.11.6-py3-none-win_amd64.whl", hash = "sha256:cce85721d09c51f3b782c331b0abd07e9d7d5f775840379c640606d3159cae0e"}, + {file = "ruff-0.11.6-py3-none-win_arm64.whl", hash = "sha256:3567ba0d07fb170b1b48d944715e3294b77f5b7679e8ba258199a250383ccb79"}, + {file = "ruff-0.11.6.tar.gz", hash = "sha256:bec8bcc3ac228a45ccc811e45f7eb61b950dbf4cf31a67fa89352574b01c7d79"}, ] [[package]] name = "s3fs" -version = "2025.3.2" +version = "0.4.2" description = "Convenient Filesystem interface over S3" optional = false -python-versions = ">=3.9" +python-versions = ">= 3.5" groups = ["main"] files = [ - {file = "s3fs-2025.3.2-py3-none-any.whl", hash = "sha256:81eae3f37b4b04bcc08845d7bcc607c6ca45878813ef7e6a28d77b2688417130"}, - {file = "s3fs-2025.3.2.tar.gz", hash = "sha256:6798f896ec76dd3bfd8beb89f0bb7c5263cb2760e038bae0978505cd172a307c"}, + {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] -aiobotocore = ">=2.5.4,<3.0.0" -aiohttp = "<4.0.0a0 || >4.0.0a0,<4.0.0a1 || >4.0.0a1" -fsspec = "==2025.3.2.*" +botocore = ">=1.37.4,<2.0a.0" [package.extras] -awscli = ["aiobotocore[awscli] (>=2.5.4,<3.0.0)"] -boto3 = ["aiobotocore[boto3] (>=2.5.4,<3.0.0)"] +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" @@ -2327,7 +3636,7 @@ 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"}, @@ -2339,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" @@ -2394,7 +3784,7 @@ version = "4.67.1" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" -groups = ["main"] +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"}, @@ -2410,6 +3800,36 @@ 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.20250403" @@ -2449,6 +3869,18 @@ files = [ [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.13.2" @@ -2467,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"}, @@ -2482,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"}, @@ -2533,7 +3965,7 @@ version = "1.2.0" description = "A small Python utility to set file creation time on Windows" optional = false python-versions = ">=3.5" -groups = ["main"] +groups = ["main", "dev"] markers = "sys_platform == \"win32\"" files = [ {file = "win32_setctime-1.2.0-py3-none-any.whl", hash = "sha256:95d644c4e708aba81dc3704a116d8cbc974d70b3bdb8be1d150e36be6e9d1390"}, @@ -2632,101 +4064,263 @@ 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.19.0" +version = "1.20.0" description = "Yet another URL library" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "dev"] files = [ - {file = "yarl-1.19.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0bae32f8ebd35c04d6528cedb4a26b8bf25339d3616b04613b97347f919b76d3"}, - {file = "yarl-1.19.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8015a076daf77823e7ebdcba474156587391dab4e70c732822960368c01251e6"}, - {file = "yarl-1.19.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9973ac95327f5d699eb620286c39365990b240031672b5c436a4cd00539596c5"}, - {file = "yarl-1.19.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd4b5fbd7b9dde785cfeb486b8cca211a0b138d4f3a7da27db89a25b3c482e5c"}, - {file = "yarl-1.19.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:75460740005de5a912b19f657848aef419387426a40f581b1dc9fac0eb9addb5"}, - {file = "yarl-1.19.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:57abd66ca913f2cfbb51eb3dbbbac3648f1f6983f614a4446e0802e241441d2a"}, - {file = "yarl-1.19.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:46ade37911b7c99ce28a959147cb28bffbd14cea9e7dd91021e06a8d2359a5aa"}, - {file = "yarl-1.19.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8346ec72ada749a6b5d82bff7be72578eab056ad7ec38c04f668a685abde6af0"}, - {file = "yarl-1.19.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e4cb14a6ee5b6649ccf1c6d648b4da9220e8277d4d4380593c03cc08d8fe937"}, - {file = "yarl-1.19.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:66fc1c2926a73a2fb46e4b92e3a6c03904d9bc3a0b65e01cb7d2b84146a8bd3b"}, - {file = "yarl-1.19.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:5a70201dd1e0a4304849b6445a9891d7210604c27e67da59091d5412bc19e51c"}, - {file = "yarl-1.19.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e4807aab1bdeab6ae6f296be46337a260ae4b1f3a8c2fcd373e236b4b2b46efd"}, - {file = "yarl-1.19.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:ae584afe81a1de4c1bb06672481050f0d001cad13163e3c019477409f638f9b7"}, - {file = "yarl-1.19.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:30eaf4459df6e91f21b2999d1ee18f891bcd51e3cbe1de301b4858c84385895b"}, - {file = "yarl-1.19.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0e617d45d03c8dec0dfce6f51f3e1b8a31aa81aaf4a4d1442fdb232bcf0c6d8c"}, - {file = "yarl-1.19.0-cp310-cp310-win32.whl", hash = "sha256:32ba32d0fa23893fd8ea8d05bdb05de6eb19d7f2106787024fd969f4ba5466cb"}, - {file = "yarl-1.19.0-cp310-cp310-win_amd64.whl", hash = "sha256:545575ecfcd465891b51546c2bcafdde0acd2c62c2097d8d71902050b20e4922"}, - {file = "yarl-1.19.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:163ff326680de5f6d4966954cf9e3fe1bf980f5fee2255e46e89b8cf0f3418b5"}, - {file = "yarl-1.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a626c4d9cca298d1be8625cff4b17004a9066330ac82d132bbda64a4c17c18d3"}, - {file = "yarl-1.19.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:961c3e401ea7f13d02b8bb7cb0c709152a632a6e14cdc8119e9c6ee5596cd45d"}, - {file = "yarl-1.19.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a39d7b807ab58e633ed760f80195cbd145b58ba265436af35f9080f1810dfe64"}, - {file = "yarl-1.19.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:c4228978fb59c6b10f60124ba8e311c26151e176df364e996f3f8ff8b93971b5"}, - {file = "yarl-1.19.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9ba536b17ecf3c74a94239ec1137a3ad3caea8c0e4deb8c8d2ffe847d870a8c5"}, - {file = "yarl-1.19.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a251e00e445d2e9df7b827c9843c0b87f58a3254aaa3f162fb610747491fe00f"}, - {file = "yarl-1.19.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9b92431d8b4d4ca5ccbfdbac95b05a3a6cd70cd73aa62f32f9627acfde7549c"}, - {file = "yarl-1.19.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec2f56edaf476f70b5831bbd59700b53d9dd011b1f77cd4846b5ab5c5eafdb3f"}, - {file = "yarl-1.19.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:acf9b92c4245ac8b59bc7ec66a38d3dcb8d1f97fac934672529562bb824ecadb"}, - {file = "yarl-1.19.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:57711f1465c06fee8825b95c0b83e82991e6d9425f9a042c3c19070a70ac92bf"}, - {file = "yarl-1.19.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:528e86f5b1de0ad8dd758ddef4e0ed24f5d946d4a1cef80ffb2d4fca4e10f122"}, - {file = "yarl-1.19.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:3b77173663e075d9e5a57e09d711e9da2f3266be729ecca0b8ae78190990d260"}, - {file = "yarl-1.19.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:d8717924cf0a825b62b1a96fc7d28aab7f55a81bf5338b8ef41d7a76ab9223e9"}, - {file = "yarl-1.19.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:0df9f0221a78d858793f40cbea3915c29f969c11366646a92ca47e080a14f881"}, - {file = "yarl-1.19.0-cp311-cp311-win32.whl", hash = "sha256:8b3ade62678ee2c7c10dcd6be19045135e9badad53108f7d2ed14896ee396045"}, - {file = "yarl-1.19.0-cp311-cp311-win_amd64.whl", hash = "sha256:0626ee31edb23ac36bdffe607231de2cca055ad3a5e2dc5da587ef8bc6a321bc"}, - {file = "yarl-1.19.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7b687c334da3ff8eab848c9620c47a253d005e78335e9ce0d6868ed7e8fd170b"}, - {file = "yarl-1.19.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b0fe766febcf523a2930b819c87bb92407ae1368662c1bc267234e79b20ff894"}, - {file = "yarl-1.19.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:742ceffd3c7beeb2b20d47cdb92c513eef83c9ef88c46829f88d5b06be6734ee"}, - {file = "yarl-1.19.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2af682a1e97437382ee0791eacbf540318bd487a942e068e7e0a6c571fadbbd3"}, - {file = "yarl-1.19.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:63702f1a098d0eaaea755e9c9d63172be1acb9e2d4aeb28b187092bcc9ca2d17"}, - {file = "yarl-1.19.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3560dcba3c71ae7382975dc1e912ee76e50b4cd7c34b454ed620d55464f11876"}, - {file = "yarl-1.19.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:68972df6a0cc47c8abaf77525a76ee5c5f6ea9bbdb79b9565b3234ded3c5e675"}, - {file = "yarl-1.19.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5684e7ff93ea74e47542232bd132f608df4d449f8968fde6b05aaf9e08a140f9"}, - {file = "yarl-1.19.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8182ad422bfacdebd4759ce3adc6055c0c79d4740aea1104e05652a81cd868c6"}, - {file = "yarl-1.19.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:aee5b90a5a9b71ac57400a7bdd0feaa27c51e8f961decc8d412e720a004a1791"}, - {file = "yarl-1.19.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:8c0b2371858d5a814b08542d5d548adb03ff2d7ab32f23160e54e92250961a72"}, - {file = "yarl-1.19.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:cd430c2b7df4ae92498da09e9b12cad5bdbb140d22d138f9e507de1aa3edfea3"}, - {file = "yarl-1.19.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:a93208282c0ccdf73065fd76c6c129bd428dba5ff65d338ae7d2ab27169861a0"}, - {file = "yarl-1.19.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:b8179280cdeb4c36eb18d6534a328f9d40da60d2b96ac4a295c5f93e2799e9d9"}, - {file = "yarl-1.19.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:eda3c2b42dc0c389b7cfda2c4df81c12eeb552019e0de28bde8f913fc3d1fcf3"}, - {file = "yarl-1.19.0-cp312-cp312-win32.whl", hash = "sha256:57f3fed859af367b9ca316ecc05ce79ce327d6466342734305aa5cc380e4d8be"}, - {file = "yarl-1.19.0-cp312-cp312-win_amd64.whl", hash = "sha256:5507c1f7dd3d41251b67eecba331c8b2157cfd324849879bebf74676ce76aff7"}, - {file = "yarl-1.19.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:59281b9ed27bc410e0793833bcbe7fc149739d56ffa071d1e0fe70536a4f7b61"}, - {file = "yarl-1.19.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d27a6482ad5e05e8bafd47bf42866f8a1c0c3345abcb48d4511b3c29ecc197dc"}, - {file = "yarl-1.19.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7a8e19fd5a6fdf19a91f2409665c7a089ffe7b9b5394ab33c0eec04cbecdd01f"}, - {file = "yarl-1.19.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cda34ab19099c3a1685ad48fe45172536610c312b993310b5f1ca3eb83453b36"}, - {file = "yarl-1.19.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7908a25d33f94852b479910f9cae6cdb9e2a509894e8d5f416c8342c0253c397"}, - {file = "yarl-1.19.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e66c14d162bac94973e767b24de5d7e6c5153f7305a64ff4fcba701210bcd638"}, - {file = "yarl-1.19.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c03607bf932aa4cfae371e2dc9ca8b76faf031f106dac6a6ff1458418140c165"}, - {file = "yarl-1.19.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9931343d1c1f4e77421687b6b94bbebd8a15a64ab8279adf6fbb047eff47e536"}, - {file = "yarl-1.19.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:262087a8a0d73e1d169d45c2baf968126f93c97cf403e1af23a7d5455d52721f"}, - {file = "yarl-1.19.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:70f384921c24e703d249a6ccdabeb57dd6312b568b504c69e428a8dd3e8e68ca"}, - {file = "yarl-1.19.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:756b9ea5292a2c180d1fe782a377bc4159b3cfefaca7e41b5b0a00328ef62fa9"}, - {file = "yarl-1.19.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cbeb9c145d534c240a63b6ecc8a8dd451faeb67b3dc61d729ec197bb93e29497"}, - {file = "yarl-1.19.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:087ae8f8319848c18e0d114d0f56131a9c017f29200ab1413b0137ad7c83e2ae"}, - {file = "yarl-1.19.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362f5480ba527b6c26ff58cff1f229afe8b7fdd54ee5ffac2ab827c1a75fc71c"}, - {file = "yarl-1.19.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f408d4b4315e814e5c3668094e33d885f13c7809cbe831cbdc5b1bb8c7a448f4"}, - {file = "yarl-1.19.0-cp313-cp313-win32.whl", hash = "sha256:24e4c367ad69988a2283dd45ea88172561ca24b2326b9781e164eb46eea68345"}, - {file = "yarl-1.19.0-cp313-cp313-win_amd64.whl", hash = "sha256:0110f91c57ab43d1538dfa92d61c45e33b84df9257bd08fcfcda90cce931cbc9"}, - {file = "yarl-1.19.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:85ac908cd5a97bbd3048cca9f1bf37b932ea26c3885099444f34b0bf5d5e9fa6"}, - {file = "yarl-1.19.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6ba0931b559f1345df48a78521c31cfe356585670e8be22af84a33a39f7b9221"}, - {file = "yarl-1.19.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5bc503e1c1fee1b86bcb58db67c032957a52cae39fe8ddd95441f414ffbab83e"}, - {file = "yarl-1.19.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d995122dcaf180fd4830a9aa425abddab7c0246107c21ecca2fa085611fa7ce9"}, - {file = "yarl-1.19.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:217f69e60a14da4eed454a030ea8283f8fbd01a7d6d81e57efb865856822489b"}, - {file = "yarl-1.19.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aad67c8f13a4b79990082f72ef09c078a77de2b39899aabf3960a48069704973"}, - {file = "yarl-1.19.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dff065a1a8ed051d7e641369ba1ad030d5a707afac54cf4ede7069b959898835"}, - {file = "yarl-1.19.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ada882e26b16ee651ab6544ce956f2f4beaed38261238f67c2a96db748e17741"}, - {file = "yarl-1.19.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:67a56b1acc7093451ea2de0687aa3bd4e58d6b4ef6cbeeaad137b45203deaade"}, - {file = "yarl-1.19.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e97d2f0a06b39e231e59ebab0e6eec45c7683b339e8262299ac952707bdf7688"}, - {file = "yarl-1.19.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:a5288adb7c59d0f54e4ad58d86fb06d4b26e08a59ed06d00a1aac978c0e32884"}, - {file = "yarl-1.19.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1efbf4d03e6eddf5da27752e0b67a8e70599053436e9344d0969532baa99df53"}, - {file = "yarl-1.19.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:f228f42f29cc87db67020f7d71624102b2c837686e55317b16e1d3ef2747a993"}, - {file = "yarl-1.19.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:c515f7dd60ca724e4c62b34aeaa603188964abed2eb66bb8e220f7f104d5a187"}, - {file = "yarl-1.19.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:4815ec6d3d68a96557fa71bd36661b45ac773fb50e5cfa31a7e843edb098f060"}, - {file = "yarl-1.19.0-cp39-cp39-win32.whl", hash = "sha256:9fac2dd1c5ecb921359d9546bc23a6dcc18c6acd50c6d96f118188d68010f497"}, - {file = "yarl-1.19.0-cp39-cp39-win_amd64.whl", hash = "sha256:5864f539ce86b935053bfa18205fa08ce38e9a40ea4d51b19ce923345f0ed5db"}, - {file = "yarl-1.19.0-py3-none-any.whl", hash = "sha256:a727101eb27f66727576630d02985d8a065d09cd0b5fcbe38a5793f71b2a97ef"}, - {file = "yarl-1.19.0.tar.gz", hash = "sha256:01e02bb80ae0dbed44273c304095295106e1d9470460e773268a27d11e594892"}, + {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] @@ -2740,7 +4334,7 @@ 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"}, @@ -2757,4 +4351,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.1" python-versions = ">=3.10,<3.13" -content-hash = "9fc72ac7cc2c1e45cb496713d33411da48022a2a908a3a5d02e325f74e6dad2d" +content-hash = "4b25745b97ea325431c42046f891900e49ae922433a4b64733ee7a8c1bf2cb60" diff --git a/pyproject.toml b/pyproject.toml index ac36043f..074e17b8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,19 +54,24 @@ coolname = "^2.2.0" pandas = "^2.2.3" pyarrow = "^19.0.1" loguru = "^0.7.3" -fsspec = {extras = ["s3"], version = "^2025.3.0"} +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.1.14" +ruff = "^0.11.6" pre-commit = "^3.8.0" pytest = "^8.3.3" pytest-asyncio = "^0.24.0" types-protobuf = "^5.29.1.20250208" 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" @@ -82,23 +87,35 @@ emoji = "🐍" target-version = "py310" line-length = 100 extend-exclude = [ - "*.ipynb" # jupyter notebooks + "*.ipynb", # jupyter notebooks ] [tool.ruff.lint] -select = [ "ALL" ] +select = ["ALL"] ignore = [ - "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 + "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 ] [tool.ruff.format] -skip-magic-trailing-comma = false \ No newline at end of file +skip-magic-trailing-comma = false + +[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... +]