From 555e4203682b03eb62dcad3bc82d680ec231c2c5 Mon Sep 17 00:00:00 2001 From: gautam8387 Date: Tue, 14 Jul 2026 17:27:41 +0200 Subject: [PATCH 1/3] Add compound label decomposition and Hungarian cytescore_compound scoring; add ontology name columns and fix pair_index shadowing in compound pairs; update docs, notebook, and Modal worker/status handling; add Modal smoke scripts and tests; bump dependencies and production pyproject metadata --- .gitignore | 2 + README.md | 13 +- cyteonto/README.md | 132 ++-- cyteonto/config.py | 2 + cyteonto/cyteonto.py | 310 ++++++++- cyteonto/describe.py | 203 +++++- cyteonto/models.py | 16 + docs/FILE_MANAGEMENT.md | 18 +- docs/WORKFLOW.md | 90 ++- modal_app/README.md | 39 +- modal_app/api.py | 30 +- modal_app/worker.py | 45 +- notebooks/quick_tutorial.ipynb | 890 ++++++++++++++++++++++++-- pyproject.toml | 40 +- scripts/test_modal_compound_labels.sh | 71 ++ scripts/test_modal_simple_labels.sh | 70 ++ tests/README.md | 8 +- tests/test_cyteonto.py | 412 +++++++++++- tests/test_modal_status.py | 97 +++ uv.lock | 778 ++++++++++++---------- 20 files changed, 2704 insertions(+), 562 deletions(-) create mode 100755 scripts/test_modal_compound_labels.sh create mode 100755 scripts/test_modal_simple_labels.sh create mode 100644 tests/test_modal_status.py diff --git a/.gitignore b/.gitignore index 2fb549c..5ab5f7c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,8 +4,10 @@ tmp/ .env .mypy_cache/ .pytest_cache/ +.ruff_cache/ *.csv *.owl *.npz *.npz.meta *.json +cyteonto_results/ diff --git a/README.md b/README.md index e1e6c97..5de2ac8 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,14 @@ `cyteonto` compares two sets of cell type annotations against the [Cell Ontology (CL)](https://obofoundry.org/ontology/cl.html). Given label lists from a study author and one or more annotation algorithms, it: -1. Generates a structured description for every label with an LLM. -2. Embeds those descriptions with a configured embedding model. -3. Matches each embedding to the closest CL term. -4. Scores each author/algorithm pair using an ontology-aware similarity metric (default: a Gaussian kernel on the cosine similarity of the CL term embeddings). -5. Returns a tidy DataFrame with one row per `(algorithm, pair_index)`. +1. Decomposes mixture labels (doublets, mixed populations) into cell-type parts with an LLM when needed. +2. Generates a structured description for every label (or part) with an LLM. +3. Embeds those descriptions with a configured embedding model. +4. Matches each embedding to the closest CL term. +5. Scores each author/algorithm pair using an ontology-aware similarity metric (default: a Gaussian kernel on the cosine similarity of the CL term embeddings). Compound pairs use Hungarian bipartite matching with an optional coverage penalty when part counts differ. +6. Returns a tidy DataFrame with one row per `(algorithm, pair_index)`. -Updated ReadMe: [cyteonto/README.md](cyteonto/README.md). More documentation to follow! +Updated ReadMe: [cyteonto/README.md](cyteonto/README.md). Process flow and file layout: [docs/WORKFLOW.md](docs/WORKFLOW.md), [docs/FILE_MANAGEMENT.md](docs/FILE_MANAGEMENT.md). ## Modal Service diff --git a/cyteonto/README.md b/cyteonto/README.md index d08867a..e70b6db 100644 --- a/cyteonto/README.md +++ b/cyteonto/README.md @@ -4,11 +4,12 @@ Semantic comparison of cell type annotations against the [Cell Ontology (CL)](ht Given two parallel lists of cell type labels (one from the study author, one from an annotation algorithm), the package: -1. Generates a structured description for each label using an LLM. -2. Embeds those descriptions with a configured embedding model. -3. Matches each embedding to the closest CL term via cosine similarity. -4. Scores each author/algorithm pair using an ontology-aware similarity metric (default: kernelised cosine on the CL term embeddings). -5. Returns a tidy `pandas.DataFrame` with per-pair scores. +1. Decomposes mixture labels into cell-type parts with an LLM when the label names multiple types. +2. Generates a structured description for each label or part using an LLM. +3. Embeds those descriptions with a configured embedding model. +4. Matches each embedding to the closest CL term via cosine similarity. +5. Scores each author/algorithm pair using an ontology-aware similarity metric (default: kernelised cosine on the CL term embeddings). Compound pairs use Hungarian match mean with a coverage penalty when part counts differ. +6. Returns a tidy `pandas.DataFrame` with per-pair scores. All LLM descriptions and embeddings are persisted on disk and reused across runs. @@ -22,10 +23,10 @@ cyteonto/ ├── config.py Environment variables (API keys, log level) ├── logger.py Loguru configuration ├── paths.py PathConfig, single source of truth for file locations -├── models.py CellDescription, EmbdConfig, AgentUsage +├── models.py CellDescription, LabelDecomposition, EmbdConfig, AgentUsage ├── storage.py NPZ and JSON read/write (ontology + user variants) ├── embed.py Async HTTP embedding generation -├── describe.py LLM description generation with optional PubMed tool +├── describe.py LLM description and compound-label decomposition ├── ontology.py OntologyMapping (CSV) and OntologySimilarity (OWL + metrics) ├── cyteonto.py CyteOnto orchestrator class └── data/ Shipped and generated data (see "On-disk layout") @@ -102,7 +103,7 @@ print(df) print("run_id used:", df["run_id"].iloc[0]) ``` -If you omit `run_id`, the call generates one of the form `run-` and logs it at INFO level. Pick it up from the logs or from `df["run_id"].iloc[0]`. The same id is used on disk under `user_files/embeddings//` and `user_files/descriptions//`. +If you omit `run_id`, the call generates one of the form `run-` and logs it at INFO level. Pick it up from the logs or from `df["run_id"].iloc[0]`. The same id is used on disk under `user_files/embeddings//`, `user_files/descriptions//`, and `user_files/decompositions//`. To remove the cached user artifacts for that run later: @@ -123,12 +124,14 @@ One row per `(algorithm, pair_index)`: | `pair_index` | int | Position inside the label list, starting at 0. | | `author_label` | str | The author label for this pair. | | `algorithm_label` | str | The algorithm label for this pair. | -| `author_ontology_id` | str or None | Best CL match for the author label, or `None` if below threshold. | -| `author_embedding_similarity` | float | Cosine similarity between the author embedding and its CL match. | -| `algorithm_ontology_id` | str or None | Best CL match for the algorithm label. | -| `algorithm_embedding_similarity` | float | Cosine similarity between the algorithm embedding and its CL match. | -| `cytescore_similarity` | float | Score under the chosen `metric`; `0.0` if either side is unmatched. | -| `similarity_method` | str | `cytescore`, `string_similarity`, `partial_match`, or `no_matches`. | +| `author_ontology_id` | str | Best CL match for the author label. For compound pairs, semicolon-separated ids from the Hungarian assignment only. Empty string if unmatched. | +| `author_ontology_name` | str | Primary CSV label (or OWL fallback) for each id in `author_ontology_id`. | +| `author_embedding_similarity` | float | Mean cosine similarity between author part embeddings and their CL matches. | +| `algorithm_ontology_id` | str | Best CL match for the algorithm label. Same compound rules as author. | +| `algorithm_ontology_name` | str | Names for ids in `algorithm_ontology_id`. | +| `algorithm_embedding_similarity` | float | Mean cosine similarity between algorithm part embeddings and their CL matches. | +| `cytescore_similarity` | float | Score under the chosen `metric`; `0.0` if either side is unmatched. | +| `similarity_method` | str | `cytescore`, `cytescore_compound`, `string_similarity`, `partial_match`, `no_matches`, or `empty`. | --- @@ -269,14 +272,37 @@ Constraints: Call flow: -1. `_embed_user_labels(author_labels, kind="author", identifier="author")` returns a `(N, D)` NumPy array. -2. `_match(author_emb)` returns the best CL id and similarity for each row. -3. `_ensure_similarity()` lazy-loads the OWL file and the ontology embeddings into `OntologySimilarity`. -4. For each `(algo_name, algo_labels)`: - - `_embed_user_labels(algo_labels, kind="algorithm", identifier=algo_name)`. - - `_match(algo_emb)`. - - For each index `i`, `similarity.similarity(author_id, algo_id, metric=..., metric_params=...)` if both ids are present; otherwise `0.0`. -5. Rows are concatenated into a DataFrame with columns from `RESULT_COLUMNS`. +1. `_resolve_label_parts` decomposes unique labels via LLM (cached per `run_id` under `decompositions/`). +2. `_embed_user_labels` on the union of sanitized parts per side (author, then each algorithm). +3. `_match` returns the best CL id and similarity for each part. +4. `_ensure_similarity()` lazy-loads the OWL file and the ontology embeddings into `OntologySimilarity`. +5. For each aligned pair index: + - **Simple pair** (one part on each side): single `OntologySimilarity.similarity` call; `similarity_method` from `_method_for`. + - **Compound pair** (more than one part on either side): build an m×n score matrix, run Hungarian max-weight matching, average assigned scores; multiply by `min(m,n)/max(m,n)` when `m ≠ n`; `similarity_method = cytescore_compound`. +6. Rows are concatenated into a DataFrame with columns from `RESULT_COLUMNS`. + +### Compound label scoring + +Mixture labels such as doublets or mixed populations are poor matches when embedded as a single string. `compare` therefore: + +1. Calls `decompose_labels` to split a label into one or more cell-type parts (semicolon-separated synonyms stay as one part). +2. Embeds and matches each unique part. +3. Scores compound pairs with **Hungarian match mean**: + - Build matrix `S` where `S[i,j]` is the cytescore between author part `i` and algorithm part `j`. + - Pick `k = min(m,n)` one-to-one assignments that maximize total score. + - `match_mean` = mean of assigned cell scores. + - If `m ≠ n`, multiply by coverage `min(m,n) / max(m,n)`. +4. Writes `similarity_method = cytescore_compound`. Ontology ids and names list only the matched assignment pairs (semicolon-separated when `k > 1`). + +Illustrative scores (see `notebooks/quick_tutorial.ipynb`): + +| Scenario | m×n | Typical score | +|----------|-----|---------------| +| Same compound | 2×2 | ~1.0 | +| One shared type | 2×2 | ~0.5 | +| No shared types | 2×2 | ~0.07 | +| Partial overlap, extra author type | 3×2 | ~0.35 | +| Author doublet vs single type | 2×1 | best match × 0.5 | ### `compare_anndata` (async) @@ -299,7 +325,7 @@ cyto.purge_stale(run_id=None) -> int # deletes user NPZs that lack the inline `labels` key (legacy format) ``` -Scope is `data/user_files/embeddings/` and the matching `descriptions/` subtree (optionally filtered by `run_id`). +Scope is `data/user_files/embeddings/` and the matching `descriptions/` subtree (optionally filtered by `run_id`). Decomposition caches live under `decompositions//` and are not removed by `clear_run` today. ### `usage` @@ -311,25 +337,23 @@ Scope is `data/user_files/embeddings/` and the matching `descriptions/` subtree ``` CyteOnto.compare - ├─ _embed_user_labels(author_labels, kind="author", identifier="author") - │ ├─ storage.load_descriptions(path) (drop any cached blanks) - │ ├─ storage.load_user_embeddings(path) (cache hit only when labels match AND every description is non-blank) - │ ├─ describe.describe_cells(missing_or_blank) (LLM calls for anything new, plus a 2nd pass for blanks) - │ │ └─ describe.describe_cell (per label, tenacity-retried agent.run with per-attempt timeout) - │ ├─ embed.embed_texts(sentences, EmbdConfig) (label text is used as a fallback for still-blank slots) - │ ├─ storage.save_descriptions (blanks are filtered out) + ├─ _resolve_label_parts(author_labels) (LLM decompose + decompositions JSON cache) + ├─ _embed_user_labels(unique author parts, ...) + │ ├─ storage.load_descriptions(path) + │ ├─ storage.load_user_embeddings(path) + │ ├─ describe.describe_cells(missing_or_blank) + │ ├─ embed.embed_texts(sentences, EmbdConfig) + │ ├─ storage.save_descriptions │ └─ storage.save_user_embeddings - ├─ _match(author_emb) - │ ├─ storage.load_ontology_embeddings (cached on the instance) - │ └─ sklearn.cosine_similarity + ├─ _match(author_part_emb) ├─ _ensure_similarity - │ └─ OntologySimilarity(owl_path, embeddings_path) - │ ├─ owlready2.get_ontology(...).load() - │ └─ np.load(ontology_embeddings_npz) └─ for each algorithm: - ├─ _embed_user_labels(algo_labels, kind="algorithm", identifier=algo_name) - ├─ _match(algo_emb) - └─ OntologySimilarity.similarity(a_id, g_id, metric, metric_params) + ├─ _resolve_label_parts(algo_labels) + ├─ _embed_user_labels(unique algo parts, ...) + ├─ _match(algo_part_emb) + └─ for each pair_index: + ├─ simple: OntologySimilarity.similarity(a_id, g_id, ...) + └─ compound: build S, _hungarian_match_mean(S) → cytescore_compound ``` --- @@ -392,7 +416,11 @@ Usage limits default to `request_limit=50, input_tokens_limit=60_000`. Override 2. If any slot came back blank, sleeps `second_pass_wait_seconds` and reruns `describe_cell` only for those labels. This gives a transient outage a full second set of 4 retries without requiring the caller to rerun anything. 3. Emits a single end-of-batch summary at `INFO` on full success, or `WARNING` if anything stayed blank (listing the first ten offending labels and truncating the rest). -Tune this by editing the constants at the top of `describe.py` or by passing `second_pass_wait_seconds` explicitly; the other values are module-level for now. +### Compound label decomposition + +`describe.decompose_label` and `describe.decompose_labels` call a dedicated LLM agent with `output_type=LabelDecomposition`. The model decides whether a label names multiple cell types (doublets, mixed populations) and returns sanitized parts. Semicolon-separated synonyms stay as one part. Results are cached per `run_id` under `user_files/decompositions//decompositions_.json`. + +Tune description batching by editing the constants at the top of `describe.py` or by passing `second_pass_wait_seconds` explicitly; the other values are module-level for now. ### `CellDescription` schema @@ -414,6 +442,16 @@ Tune this by editing the constants at the top of `describe.py` or by passing `se `CellDescription.blank(label)` returns a zeroed instance used as a fallback after an unrecoverable LLM failure. +### `LabelDecomposition` schema + +| Field | Type | Notes | +|----------------|-------------|-------| +| `initialLabel` | `str` | Input label, copied verbatim. | +| `isCompound` | `bool` | `true` when the label names more than one cell type. | +| `parts` | `list[str]` | Sanitized cell-type parts. For non-compound labels, a single element equal to the label. | + +Used only for decomposition; descriptions are generated per part afterward. + ### PubMed tool `describe.get_pubmed_abstracts(query, max_results=5)` hits NCBI eutils, returns a `list[str]`, and swallows all errors (returning `[]`). `NCBI_API_KEY` raises the rate limit when set. @@ -516,10 +554,13 @@ Set via `PathConfig(data_dir=..., user_dir=...)`: │ └── / │ ├── author/author_embeddings__.npz │ └── algorithm/_embeddings__.npz -└── descriptions/ +├── descriptions/ +│ └── / +│ ├── author/author_descriptions_.json +│ └── algorithm/_descriptions_.json +└── decompositions/ └── / - ├── author/author_descriptions_.json - └── algorithm/_descriptions_.json + └── decompositions_.json ``` Filename rules (`ModelArtifactKey.filename_segment`, `paths._clean_identifier`): @@ -541,6 +582,7 @@ Pre-v3 caches (`descriptions_moonshotai-Kimi-K2.6.json`, etc.) are ignored. Dele | Ontology embeddings NPZ | File missing, any description was regenerated on this call, or `force_regenerate=True`. | | User author/algorithm embeddings | File missing, `labels` array inside the NPZ differs from the request, or any description for a requested label is missing or blank. | | User author/algorithm descriptions| Per-label: any label missing from the existing JSON or cached as blank is regenerated; non-blank existing entries are kept. | +| Label decompositions JSON | Per-label: any label missing from the JSON is decomposed via LLM; keyed by raw label string under `decompositions`. | Per-label description caching means that adding one new label to a run does not recompute descriptions for existing labels; only the new label hits the LLM. @@ -560,7 +602,9 @@ When `use_cache=False` is passed to `compare`, all cache lookups are skipped and - User embedding generation failure: `compare` raises `RuntimeError` with the offending identifier. - Label length mismatch between author and algorithm lists: `compare` raises `ValueError`. - Duplicate algorithm name or an algorithm named `"author"`: `compare` raises `ValueError`. -- Ontology match below `min_match_similarity`: CL id stored as `None`, `similarity_method` becomes `partial_match` or `no_matches`. +- Ontology match below `min_match_similarity`: CL id stored as empty string, `similarity_method` becomes `partial_match` or `no_matches`. +- Both labels empty on a pair: `similarity_method = empty`, ontology fields blank. +- One label empty on a pair: non-empty side keeps its ontology id and name; score `0.0`; `similarity_method = empty`. - OWL class not found for a CL id during hierarchy metrics: falls back to `simple` similarity with a warning. - Per-label LLM failure: the label gets a blank `CellDescription` after 4 attempts and a second-pass retry; the run continues. The blank is not persisted to the descriptions JSON and will be retried on the next call. The corresponding embedding row is computed using the raw label text so the NPZ stays aligned with the requested label list. - Per-request embedding failure: the whole `embed_texts` call returns `None` after 3 tenacity retries, which surfaces as a `RuntimeError` from `_embed_user_labels`. diff --git a/cyteonto/config.py b/cyteonto/config.py index 162f3df..c21790c 100644 --- a/cyteonto/config.py +++ b/cyteonto/config.py @@ -78,8 +78,10 @@ class Config: "author_label", "algorithm_label", "author_ontology_id", + "author_ontology_name", "author_embedding_similarity", "algorithm_ontology_id", + "algorithm_ontology_name", "algorithm_embedding_similarity", "cytescore_similarity", "similarity_method", diff --git a/cyteonto/cyteonto.py b/cyteonto/cyteonto.py index 11cf620..5f96e94 100644 --- a/cyteonto/cyteonto.py +++ b/cyteonto/cyteonto.py @@ -1,20 +1,23 @@ """High-level CyteOnto orchestrator.""" +import json import os import shutil import uuid +from datetime import datetime, timezone from pathlib import Path from typing import Any, Sequence import numpy as np import pandas as pd # type: ignore from pydantic_ai import Agent +from scipy.optimize import linear_sum_assignment from sklearn.metrics.pairwise import cosine_similarity # type: ignore from tqdm.auto import tqdm # type: ignore from . import storage from .config import Config -from .describe import describe_cells +from .describe import decompose_labels, describe_cells from .embed import embed_texts from .logger import logger from .models import ( @@ -26,7 +29,7 @@ ModelPairUsage, ) from .ontology import OntologyMapping, OntologySimilarity -from .paths import PathConfig, _clean_identifier +from .paths import PathConfig, _clean_identifier, artifact_key_segment config = Config() @@ -44,6 +47,39 @@ def _is_empty(label: str) -> bool: return not label or not label.strip() +def _unique_parts(labels: list[str], parts_map: dict[str, list[str]]) -> list[str]: + seen: set[str] = set() + out: list[str] = [] + for lbl in labels: + if _is_empty(lbl): + continue + for part in parts_map.get(lbl, [lbl]): + if part not in seen: + seen.add(part) + out.append(part) + return out + + +def _hungarian_match_mean( + scores: np.ndarray, +) -> tuple[float, list[tuple[int, int]]]: + """Max-weight bipartite match mean, with coverage penalty when m != n.""" + if scores.size == 0: + return 0.0, [] + m, n = scores.shape + k = min(m, n) + if k == 0: + return 0.0, [] + + row_ind, col_ind = linear_sum_assignment(1.0 - scores) + assignments = list(zip(row_ind.tolist(), col_ind.tolist())) + match_mean = float(scores[row_ind, col_ind].sum()) / k + if m == n: + return match_mean, assignments + coverage = k / max(m, n) + return match_mean * coverage, assignments + + class CyteOnto: """Compare two sets of cell type annotations against the Cell Ontology. @@ -477,6 +513,91 @@ def _method_for(a_id: str | None, b_id: str | None, hierarchy_score: float) -> s return "cytescore" return "string_similarity" + def _ontology_name_for_id(self, ontology_id: str) -> str: + labels = self.mapping.labels_for_id(ontology_id) + if labels: + return labels[0] + cls = self._ensure_similarity()._find_class(ontology_id) + if cls is not None: + owl_labels = getattr(cls, "label", None) + if owl_labels: + if isinstance(owl_labels, list): + return str(owl_labels[0]) + return str(owl_labels) + return "" + + def _ontology_names_for_ids(self, ids_joined: str) -> str: + if not ids_joined: + return "" + names: list[str] = [] + for oid in dict.fromkeys(ids_joined.split(";")): + oid = oid.strip() + if not oid: + continue + name = self._ontology_name_for_id(oid) + if name: + names.append(name) + return ";".join(names) + + async def _resolve_label_parts( + self, + labels: list[str], + run_id: str, + use_cache: bool, + ) -> dict[str, list[str]]: + """Map each label to one or more cell-type parts (LLM + on-disk cache). + + Non-compound labels map to themselves, e.g. ``"T cell" -> ["T cell"]``. + Compound labels map to sanitized parts, e.g. + ``"AT2 cell-plasma cell doublet" -> ["AT2 cell", "plasma cell"]``. + """ + unique = list(dict.fromkeys(lbl for lbl in labels if not _is_empty(lbl))) + if not unique: + return {} + + cache_path = ( + self.paths.user_dir + / "decompositions" + / _clean_identifier(run_id) + / f"decompositions_{artifact_key_segment(self.llm_key)}.json" + ) + cached: dict[str, list[str]] = {} + if use_cache and cache_path.exists(): + try: + raw = json.loads(cache_path.read_text(encoding="utf-8")) + if raw.get("schemaVersion") == config.SCHEMA_VERSION: + cached = raw.get("decompositions", {}) + except Exception as e: + logger.warning(f"Failed to load decompositions from {cache_path}: {e}") + + missing = [lbl for lbl in unique if lbl not in cached] + if missing: + logger.info(f"Decomposing {len(missing)} labels ({len(cached)} cached)") + decomps, sub_usage = await decompose_labels( + base_agent=self.agent, + labels=missing, + max_concurrent=self.max_description_concurrency, + reasoning=self.reasoning, + ) + self.usage.merge(sub_usage) + for dec in decomps: + parts = dec.parts if dec.parts else [dec.initialLabel] + cached[dec.initialLabel] = parts + + cache_path.parent.mkdir(parents=True, exist_ok=True) + envelope = { + "schemaVersion": config.SCHEMA_VERSION, + "artifactKey": self.llm_key.model_dump(mode="json"), + "updatedAt": datetime.now(timezone.utc).isoformat(), + "decompositions": cached, + } + cache_path.write_text( + json.dumps(envelope, indent=2, ensure_ascii=False), + encoding="utf-8", + ) + + return {lbl: cached.get(lbl, [lbl]) for lbl in unique} + # public API async def compare( @@ -541,14 +662,22 @@ async def compare( f"algorithms={len(algo_items)}, metric='{metric}')" ) + # Compound labels (e.g. "AT2 cell–plasma cell doublet") embed poorly as one + # string and often score near zero. Decompose each label into cell-type parts + # via LLM, then embed and match each part separately. + author_parts_map = await self._resolve_label_parts( + author_labels, run_id, use_cache + ) + author_parts_list = _unique_parts(author_labels, author_parts_map) author_emb = await self._embed_user_labels( - labels=author_labels, + labels=author_parts_list, run_id=run_id, kind="author", identifier="author", use_cache=use_cache, ) author_matches = self._match(author_emb, min_similarity=min_match_similarity) + author_lookup = dict(zip(author_parts_list, author_matches)) author_empty = [_is_empty(lbl) for lbl in author_labels] similarity = self._ensure_similarity() @@ -560,31 +689,166 @@ async def compare( f"{len(algo_labels)} vs {len(author_labels)} author labels" ) + algo_parts_map = await self._resolve_label_parts( + algo_labels, run_id, use_cache + ) + algo_parts_list = _unique_parts(algo_labels, algo_parts_map) algo_emb = await self._embed_user_labels( - labels=algo_labels, + labels=algo_parts_list, run_id=run_id, kind="algorithm", identifier=algo_name, use_cache=use_cache, ) algo_matches = self._match(algo_emb, min_similarity=min_match_similarity) + algo_lookup = dict(zip(algo_parts_list, algo_matches)) algo_empty = [_is_empty(lbl) for lbl in algo_labels] for i, (a_lbl, g_lbl) in enumerate(zip(author_labels, algo_labels)): - a_id, a_sim = ("", 0.0) if author_empty[i] else author_matches[i] - g_id, g_sim = ("", 0.0) if algo_empty[i] else algo_matches[i] - hier = ( - similarity.similarity( - a_id, g_id, metric=metric, metric_params=metric_params + if author_empty[i] and algo_empty[i]: + rows.append( + { + "run_id": run_id, + "algorithm": algo_name, + "pair_index": i, + "author_label": a_lbl, + "algorithm_label": g_lbl, + "author_ontology_id": "", + "author_ontology_name": "", + "author_embedding_similarity": 0.0, + "algorithm_ontology_id": "", + "algorithm_ontology_name": "", + "algorithm_embedding_similarity": 0.0, + "cytescore_similarity": 0.0, + "similarity_method": "empty", + } ) - if a_id and g_id - else 0.0 - ) - method = ( - "empty" - if (author_empty[i] or algo_empty[i]) - else self._method_for(a_id, g_id, hier) - ) + continue + + if author_empty[i] or algo_empty[i]: + if author_empty[i]: + g_parts = algo_parts_map[g_lbl] + g_part_sims = [algo_lookup[gp][1] for gp in g_parts] + g_part_ids = [ + algo_lookup[gp][0] for gp in g_parts if algo_lookup[gp][0] + ] + g_id_out = ";".join(dict.fromkeys(g_part_ids)) + rows.append( + { + "run_id": run_id, + "algorithm": algo_name, + "pair_index": i, + "author_label": a_lbl, + "algorithm_label": g_lbl, + "author_ontology_id": "", + "author_ontology_name": "", + "author_embedding_similarity": 0.0, + "algorithm_ontology_id": g_id_out, + "algorithm_ontology_name": self._ontology_names_for_ids( + g_id_out + ), + "algorithm_embedding_similarity": round( + sum(g_part_sims) / len(g_part_sims), 4 + ), + "cytescore_similarity": 0.0, + "similarity_method": "empty", + } + ) + else: + a_parts = author_parts_map[a_lbl] + a_part_sims = [author_lookup[ap][1] for ap in a_parts] + a_part_ids = [ + author_lookup[ap][0] + for ap in a_parts + if author_lookup[ap][0] + ] + a_id_out = ";".join(dict.fromkeys(a_part_ids)) + rows.append( + { + "run_id": run_id, + "algorithm": algo_name, + "pair_index": i, + "author_label": a_lbl, + "algorithm_label": g_lbl, + "author_ontology_id": a_id_out, + "author_ontology_name": self._ontology_names_for_ids( + a_id_out + ), + "author_embedding_similarity": round( + sum(a_part_sims) / len(a_part_sims), 4 + ), + "algorithm_ontology_id": "", + "algorithm_ontology_name": "", + "algorithm_embedding_similarity": 0.0, + "cytescore_similarity": 0.0, + "similarity_method": "empty", + } + ) + continue + + a_parts = author_parts_map[a_lbl] + g_parts = algo_parts_map[g_lbl] + m, n = len(a_parts), len(g_parts) + + a_part_sims = [author_lookup[ap][1] for ap in a_parts] + g_part_sims = [algo_lookup[gp][1] for gp in g_parts] + a_sim_mean = sum(a_part_sims) / len(a_part_sims) + g_sim_mean = sum(g_part_sims) / len(g_part_sims) + is_compound = m > 1 or n > 1 + + if not is_compound: + a_id, _ = author_lookup[a_parts[0]] + g_id, _ = algo_lookup[g_parts[0]] + hier = ( + similarity.similarity( + a_id, g_id, metric=metric, metric_params=metric_params + ) + if a_id and g_id + else 0.0 + ) + a_id_out = a_id or "" + g_id_out = g_id or "" + method = self._method_for(a_id, g_id, hier) + else: + # Hungarian match mean on an m x n score matrix S. Pick one-to-one + # assignments that maximize total score, then average those k pairs. + # When m != n, multiply by coverage = min(m,n) / max(m,n). + # + # 2x2 same compound (diagonal ~1, off-diagonal ~0): + # cytescore_similarity ~ 1.0 + # + # 3x2 partial overlap (author AT2+plasma+NK, algo AT2+T cell): + # match_mean ~ 0.53, coverage 2/3, cytescore_similarity ~ 0.35 + score_matrix = np.zeros((m, n), dtype=np.float64) + for ai, ap in enumerate(a_parts): + a_id, _ = author_lookup[ap] + for aj, gp in enumerate(g_parts): + g_id, _ = algo_lookup[gp] + score_matrix[ai, aj] = ( + similarity.similarity( + a_id, + g_id, + metric=metric, + metric_params=metric_params, + ) + if a_id and g_id + else 0.0 + ) + + hier, assignments = _hungarian_match_mean(score_matrix) + method = "cytescore_compound" + a_matched_ids: list[str] = [] + g_matched_ids: list[str] = [] + for ai, aj in assignments: + a_id = author_lookup[a_parts[ai]][0] + g_id = algo_lookup[g_parts[aj]][0] + if a_id: + a_matched_ids.append(a_id) + if g_id: + g_matched_ids.append(g_id) + a_id_out = ";".join(dict.fromkeys(a_matched_ids)) + g_id_out = ";".join(dict.fromkeys(g_matched_ids)) + rows.append( { "run_id": run_id, @@ -592,10 +856,14 @@ async def compare( "pair_index": i, "author_label": a_lbl, "algorithm_label": g_lbl, - "author_ontology_id": a_id, - "author_embedding_similarity": round(a_sim, 4), - "algorithm_ontology_id": g_id, - "algorithm_embedding_similarity": round(g_sim, 4), + "author_ontology_id": a_id_out, + "author_ontology_name": self._ontology_names_for_ids(a_id_out), + "author_embedding_similarity": round(a_sim_mean, 4), + "algorithm_ontology_id": g_id_out, + "algorithm_ontology_name": self._ontology_names_for_ids( + g_id_out + ), + "algorithm_embedding_similarity": round(g_sim_mean, 4), "cytescore_similarity": round(hier, 4), "similarity_method": method, } diff --git a/cyteonto/describe.py b/cyteonto/describe.py index e8f6bc3..45ab432 100644 --- a/cyteonto/describe.py +++ b/cyteonto/describe.py @@ -23,7 +23,7 @@ from .config import Config from .logger import logger -from .models import AgentUsage, CellDescription +from .models import AgentUsage, CellDescription, LabelDecomposition config = Config() @@ -372,3 +372,204 @@ async def run_one(i: int) -> tuple[int, CellDescription, AgentUsage]: logger.info(f"describe_cells summary: {total}/{total} ok") return results, total_usage + + +def _normalize_decomposition( + label: str, output: LabelDecomposition | None +) -> LabelDecomposition: + if output is None or not output.parts: + return LabelDecomposition.single(label) + if not output.isCompound: + return LabelDecomposition.single(label) + cleaned = [p.strip() for p in output.parts if p and p.strip()] + if len(cleaned) < 2: + return LabelDecomposition.single(label) + return LabelDecomposition(initialLabel=label, isCompound=True, parts=cleaned) + + +def _build_decompose_prompt(label: str) -> str: + return dedent( + f""" + Task: decide whether a cell-type annotation label refers to one cell + type or multiple distinct cell types. + + Input label: + {label} + + Context: labels come from single-cell RNA-seq cluster annotations. + Some labels name a single cell type; others name a mixture such as a + doublet or mixed population. + + Rules: + - Semicolon-separated text lists synonyms for one cell type. Do not + split those. + - Commas and hyphens inside a single cell-type name do not indicate + multiple types. + - When the label names multiple distinct cell types, set isCompound to + true and list each type in parts as a short sanitized name suitable + for ontology lookup. Remove mixture qualifiers such as "doublet", + "mixed population", or "contamination" from part names. + - When the label names one cell type, set isCompound to false and set + parts to a list containing only the original label verbatim. + + initialLabel + Copy the input label exactly as given. + + isCompound + true when the label names multiple distinct cell types; false + otherwise. + + parts + When isCompound is false, must contain exactly one element equal + to initialLabel. + When isCompound is true, must contain two or more sanitized + cell-type names. + """ + ).strip() + + +def _build_decompose_agent(base_agent: Agent, reasoning: bool = False) -> Agent: + model_settings: dict[str, Any] = {} + if not reasoning: + provider = getattr(base_agent.model, "provider", None) + base_url = getattr(provider, "base_url", None) if provider is not None else None + is_fireworks = ( + isinstance(base_url, str) + and base_url.rstrip("/") == "https://api.fireworks.ai/inference/v1" + ) + if is_fireworks: + model_settings = { + "extra_body": { + "thinking": {"type": "disabled"}, + "reasoning_effort": None, + }, + } + else: + model_settings = { + "thinking": False, + "extra_body": { + "chat_template_kwargs": {"thinking": False}, + "reasoning": {"enabled": False}, + }, + } + + return Agent( + base_agent.model, + output_type=LabelDecomposition, # type:ignore + name="LabelDecompositionAgent", + model_settings=model_settings, + system_prompt=( + "You classify cell-type annotation labels as single or compound " + "for ontology matching." + ), + ) + + +@retry( + stop=stop_after_attempt(config.RETRY_ATTEMPTS), + wait=wait_exponential(min=config.RETRY_WAIT_MIN, max=config.RETRY_WAIT_MAX), + retry=retry_if_exception_type(config._RETRYABLE_EXCEPTIONS), + before_sleep=_log_before_sleep, + reraise=False, +) +async def _run_decompose_once( + agent: Agent, + prompt: str, + usage_limits: UsageLimits, +) -> tuple[LabelDecomposition, dict[str, int], int, int, int, int]: + result = await asyncio.wait_for( + agent.run(prompt, usage_limits=usage_limits), + timeout=config.PER_ATTEMPT_TIMEOUT, + ) + tool_counts: dict[str, int] = {} + for msg in result.all_messages(): + for part in getattr(msg, "parts", []): + if isinstance(part, ToolCallPart): + tool_counts[part.tool_name] = tool_counts.get(part.tool_name, 0) + 1 + usage = result.usage() + return ( # type: ignore[return-value] + result.output, + tool_counts, + usage.requests, + usage.input_tokens or 0, + usage.output_tokens or 0, + usage.total_tokens or 0, + ) + + +async def decompose_label( + base_agent: Agent, + label: str, + usage_limits: UsageLimits = config.DEFAULT_USAGE_LIMITS, + reasoning: bool = False, +) -> tuple[LabelDecomposition, AgentUsage]: + """Decompose a label into one or more cell-type parts.""" + agent = _build_decompose_agent(base_agent, reasoning) + usage = AgentUsage(agentName=agent.name or "LabelDecompositionAgent") + try: + ( + output, + tool_counts, + req, + in_tok, + out_tok, + total_tok, + ) = await _run_decompose_once( + agent, _build_decompose_prompt(label), usage_limits + ) + except UsageLimitExceeded as e: + logger.error(f"Usage limit exceeded while decomposing '{label}': {e}") + return LabelDecomposition.single(label), usage + except Exception as e: + logger.error(f"Decomposition failed for '{label}': {_format_exception(e)}") + return LabelDecomposition.single(label), usage + + usage.modelName = str(agent.model.model_name) # type: ignore + usage.requests = req + usage.inputTokens = in_tok + usage.outputTokens = out_tok + usage.totalTokens = total_tok + usage.toolUsage = tool_counts + return _normalize_decomposition(label, output), usage + + +async def decompose_labels( + base_agent: Agent, + labels: list[str], + max_concurrent: int = config.MAX_CONCURRENT_DESCRIPTIONS, + reasoning: bool = False, +) -> tuple[list[LabelDecomposition], AgentUsage]: + """Decompose many labels concurrently, preserving input order.""" + if not labels: + return [], AgentUsage(agentName="LabelDecompositionAgent") + + total = len(labels) + sem = asyncio.Semaphore(max_concurrent) + total_usage = AgentUsage(agentName="LabelDecompositionAgent") + results: list[LabelDecomposition] = [ + LabelDecomposition.single(lbl) for lbl in labels + ] + + async def run_one(i: int) -> tuple[int, LabelDecomposition, AgentUsage]: + lbl = labels[i] + async with sem: + dec, usage = await decompose_label(base_agent, lbl, reasoning=reasoning) + return i, dec, usage + + logger.info(f"Decomposing {total} cell labels") + tasks = [run_one(i) for i in range(total)] + done = 0 + for coro in asyncio.as_completed(tasks): + try: + i, dec, usage = await coro + results[i] = dec + total_usage.merge(usage) + done += 1 + if done == total or done % max(1, total // 20) == 0: + logger.info(f"Decomposition progress: {done}/{total}") + except Exception as e: + logger.error(f"Decomposition task crashed: {_format_exception(e)}") + + compound_count = sum(1 for d in results if d.isCompound) + logger.info(f"decompose_labels summary: {total} labels, {compound_count} compound") + return results, total_usage diff --git a/cyteonto/models.py b/cyteonto/models.py index adcb984..a5548e7 100644 --- a/cyteonto/models.py +++ b/cyteonto/models.py @@ -117,6 +117,22 @@ def get_blank(cls) -> "CellDescription": return cls.blank() +class LabelDecomposition(BaseModel): + """Structured output for splitting compound cell-type labels.""" + + initialLabel: str = Field(description="The input label, copied verbatim") + isCompound: bool = Field( + description="True when the label names multiple distinct cell types" + ) + parts: list[str] = Field( + description="Sanitized cell-type names; one element when not compound" + ) + + @classmethod + def single(cls, label: str) -> "LabelDecomposition": + return cls(initialLabel=label, isCompound=False, parts=[label]) + + class DescriptionFileEnvelope(BaseModel): """On-disk wrapper for a descriptions JSON file (schema v3).""" diff --git a/docs/FILE_MANAGEMENT.md b/docs/FILE_MANAGEMENT.md index d2b784e..7991b77 100644 --- a/docs/FILE_MANAGEMENT.md +++ b/docs/FILE_MANAGEMENT.md @@ -25,13 +25,16 @@ Default roots: `cyteonto/data/` for shipped and generated ontology data, `cyteon │ └── algorithm/ │ ├── _embeddings__.npz │ └── _embeddings__.npz -└── descriptions/ +├── descriptions/ +│ └── / +│ ├── author/ +│ │ └── author_descriptions_.json +│ └── algorithm/ +│ ├── _descriptions_.json +│ └── _descriptions_.json +└── decompositions/ └── / - ├── author/ - │ └── author_descriptions_.json - └── algorithm/ - ├── _descriptions_.json - └── _descriptions_.json + └── decompositions_.json ``` `` and algorithm names are passed through `_clean_identifier` (e.g. `sample.run.001` becomes `sample_run_001`). Model names use `_clean_model` (slashes and colons become hyphens; case and dots are preserved). @@ -53,6 +56,7 @@ Default roots: `cyteonto/data/` for shipped and generated ontology data, `cyteon | Author embeddings | `author_embeddings__.npz` | | Algorithm descriptions | `_descriptions_.json` | | Algorithm embeddings | `_embeddings__.npz` | +| Label decompositions | `decompositions_.json` (under `decompositions//`, not per kind) | `kind` is either `author` or `algorithm`. For author rows, `identifier` is always `author`. @@ -80,6 +84,7 @@ Custom `data_dir` must still contain `cell_ontology/cl.owl` and `cell_ontology/c | Ontology embeddings | NPZ exists and no descriptions were regenerated on this `from_config` call | | User descriptions | Label already in JSON with a non-blank `CellDescription` | | User embeddings | NPZ exists, embedded `labels` array matches the request exactly, and every label has a non-blank description | +| Label decompositions | JSON exists under `decompositions//`, schema version matches, and every requested label is present | Blank LLM failures are not written to JSON; those labels are retried on the next call. Embeddings for those positions may still use the raw label text so array shape stays aligned. @@ -130,6 +135,7 @@ tree cyteonto/data/user_files/ # Remove one run rm -rf cyteonto/data/user_files/embeddings/my_run_id/ rm -rf cyteonto/data/user_files/descriptions/my_run_id/ +rm -rf cyteonto/data/user_files/decompositions/my_run_id/ du -sh cyteonto/data/user_files/embeddings/*/ ``` diff --git a/docs/WORKFLOW.md b/docs/WORKFLOW.md index 1fd5138..e3e8f11 100644 --- a/docs/WORKFLOW.md +++ b/docs/WORKFLOW.md @@ -5,8 +5,8 @@ CyteOnto compares parallel lists of cell type labels (author reference vs one or ## End-to-end flow 1. **Setup (once per model pair)** — `await CyteOnto.from_config(agent, embedding, llm)` ensures CL term descriptions and ontology embeddings exist on disk. -2. **Compare (per analysis)** — `await cyto.compare(author_labels, algorithms={...}, run_id=...)` describes and embeds user labels, matches them to CL terms, and scores each author/algorithm pair. -3. **Persist and reuse** — Descriptions (JSON) and embeddings (NPZ) are written under `user_files/` keyed by `run_id`, so reruns with the same labels skip redundant LLM and embedding calls. +2. **Compare (per analysis)** — `await cyto.compare(author_labels, algorithms={...}, run_id=...)` decomposes mixture labels when needed, describes and embeds label parts, matches them to CL terms, and scores each author/algorithm pair. +3. **Persist and reuse** — Descriptions (JSON), embeddings (NPZ), and label decompositions (JSON) are written under `user_files/` keyed by `run_id`, so reruns with the same labels skip redundant LLM calls. ## Setup: `from_config` @@ -23,11 +23,15 @@ If both artifacts already exist and every CL id has a non-blank description, set For each call: 1. **Resolve `run_id`** — Use the value you pass, or an auto-generated `run-` (logged at INFO and stored in every result row). -2. **Author labels** — Load cached descriptions/embeddings when possible; generate missing ones via LLM; embed; save under `user_files/...//author/`. -3. **Match to CL** — Cosine similarity between user embeddings and the precomputed ontology embedding matrix; matches below `min_match_similarity` (default `0.1`) are treated as unmatched (`None`). -4. **Per algorithm** — Repeat describe/embed/cache/match for each algorithm label list (same length as `author_labels`). -5. **Pair scoring** — When both sides have a CL id, `OntologySimilarity.similarity(..., metric=...)` produces `cytescore_similarity` (default metric: `cosine_kernel`). When either side is unmatched, the score is `0.0`. -6. **Results** — A `pandas.DataFrame` with one row per `(algorithm, pair_index)`. +2. **Decompose labels** — Unique non-empty labels on each side are passed through `decompose_labels`. Mixture labels (doublets, mixed populations) split into cell-type parts; simple labels map to a single part. Cached under `user_files/decompositions//`. +3. **Describe and embed parts** — Load cached descriptions/embeddings when possible; generate missing ones via LLM; embed unique parts; save under `user_files/...//author/` and per-algorithm paths. +4. **Match to CL** — Cosine similarity between part embeddings and the precomputed ontology embedding matrix. Matches below `min_match_similarity` (default `0.1`) are treated as unmatched (empty ontology id). +5. **Per algorithm** — Repeat decompose/describe/embed/cache/match for each algorithm label list (same length as `author_labels`). +6. **Pair scoring** — For each aligned index: + - **Simple pair** (one part on each side): `OntologySimilarity.similarity(...)` when both parts matched; otherwise `0.0` with `partial_match` or `no_matches`. + - **Compound pair** (more than one part on either side): build an m×n cytescore matrix, Hungarian max-weight matching, mean of assigned scores; multiply by `min(m,n)/max(m,n)` when `m ≠ n`; `similarity_method = cytescore_compound`. + - **Empty labels**: both empty → `empty`; one empty → non-empty side keeps ontology fields, score `0.0`, `similarity_method = empty`. +7. **Results** — A `pandas.DataFrame` with one row per `(algorithm, pair_index)`. Pass `use_cache=False` to skip on-disk lookups and regenerate everything for that call. @@ -39,19 +43,34 @@ Pass `use_cache=False` to skip on-disk lookups and regenerate everything for tha | `algorithm` | Key from the `algorithms` mapping | | `pair_index` | Index into the parallel label lists (0-based) | | `author_label`, `algorithm_label` | Raw input strings | -| `author_ontology_id`, `algorithm_ontology_id` | Best CL match, or `None` if below threshold | -| `author_embedding_similarity`, `algorithm_embedding_similarity` | Cosine similarity to the matched CL embedding | -| `cytescore_similarity` | Score from the chosen metric when both ids exist; else `0.0` | +| `author_ontology_id`, `algorithm_ontology_id` | Best CL match per part; semicolon-separated for compound pairs (matched assignment only). Empty string if unmatched. | +| `author_ontology_name`, `algorithm_ontology_name` | Primary CSV label (or OWL fallback) for each id above | +| `author_embedding_similarity`, `algorithm_embedding_similarity` | Mean cosine similarity of parts to their CL matches | +| `cytescore_similarity` | Score from the chosen metric when applicable; else `0.0` | | `similarity_method` | How the row was classified (see below) | ### `similarity_method` values | Value | When | |-------|------| -| `cytescore` | Both labels matched valid `CL:` ids; hierarchy/embedding metric applied | +| `cytescore` | Simple pair; both parts matched valid `CL:` ids; hierarchy/embedding metric applied | +| `cytescore_compound` | Compound pair; Hungarian match mean (with coverage penalty when part counts differ) | | `partial_match` | Exactly one side matched the ontology | | `no_matches` | Neither side matched | | `string_similarity` | Both ids present but not standard `CL:` prefixes (rare) | +| `empty` | One or both raw labels are empty | + +### Compound scoring (summary) + +When either side has more than one part after decomposition: + +1. Score every author-part vs algorithm-part pair with the chosen `metric`. +2. Select `k = min(m,n)` assignments that maximize total score (Hungarian algorithm). +3. `match_mean` = mean of the `k` assigned scores. +4. If `m ≠ n`, multiply by coverage `min(m,n) / max(m,n)`. +5. Ontology ids and names in the result list only the matched pairs. + +See `notebooks/quick_tutorial.ipynb` for worked examples (2×2 same compound ~1.0, 3×2 partial overlap ~0.35). ## Run organization @@ -59,7 +78,7 @@ Comparisons are grouped by **`run_id`**, not by a separate “study” concept: - Reuse the same `run_id` when you rerun with the same labels to hit the cache. - Use a new `run_id` for a distinct analysis so caches stay isolated. -- Delete artifacts with `cyto.clear_run(run_id)` (optionally scoped to author or a single algorithm). +- Delete embeddings and descriptions with `cyto.clear_run(run_id)` (optionally scoped to author or a single algorithm). Decomposition JSON under `decompositions//` is separate; remove it manually if needed. Identifiers are normalized for paths (`/`, `:`, spaces, `.` replaced) — see [FILE_MANAGEMENT.md](FILE_MANAGEMENT.md). @@ -67,7 +86,7 @@ Identifiers are normalized for paths (`/`, `:`, spaces, `.` replaced) — see [F The default `metric="cosine_kernel"` applies a Gaussian hill on raw embedding cosine between the two matched CL term vectors. Other options include `cosine_direct`, OWL hierarchy metrics (`path`, `set:jaccard`, ...), and `simple` (string fallback on CL id strings). See the metrics table in [cyteonto/README.md](../cyteonto/README.md#similarity-metrics). -Pair-level `cytescore_similarity` is only computed when **both** author and algorithm labels map to CL ids above the match threshold. +For simple pairs, `cytescore_similarity` requires both parts to map to CL ids above the match threshold. Compound pairs use the Hungarian path described above. ## Workflow diagram (overview) @@ -83,15 +102,17 @@ flowchart TD subgraph compare ["Compare (per run_id)"] A["Input: author + algorithm labels"] - B["LLM descriptions for user labels"] + D0["LLM decompose mixture labels"] + B["LLM descriptions for label parts"] C["Embed description text"] - D["Match to CL via cosine similarity"] - E["OntologySimilarity metric per pair"] + D["Match parts to CL via cosine similarity"] + E["Score per pair (simple or Hungarian compound)"] F["Results DataFrame"] - A --> B --> C --> D --> E --> F + A --> D0 --> B --> C --> D --> E --> F end setup --> compare + D0 --> D0a["Cache decompositions JSON"] B --> B1["Cache descriptions JSON"] C --> C1["Cache embeddings NPZ"] D --> D1["Ontology embedding matrix"] @@ -101,21 +122,26 @@ flowchart TD ```mermaid flowchart TD - A["Author label + algorithm label"] --> B["Describe + embed each label"] - B --> C["Match each embedding to nearest CL term"] - - C --> D{"Both above min_match_similarity?"} - D -->|Yes| E["metric e.g. cosine_kernel on CL ids"] - D -->|No| F["cytescore_similarity = 0.0"] - - E --> G["similarity_method: cytescore"] - F --> H{"Which side matched?"} - H -->|Neither| I["no_matches"] - H -->|One| J["partial_match"] - - G --> K["Result row"] - I --> K - J --> K + A["Author label + algorithm label"] --> B["Decompose each label into parts"] + B --> C["Describe + embed each unique part"] + C --> D["Match each part to nearest CL term"] + + D --> E{"Compound pair?"} + E -->|No| F{"Both parts matched?"} + F -->|Yes| G["metric e.g. cosine_kernel on CL ids"] + F -->|No| H["cytescore_similarity = 0.0"] + G --> I["similarity_method: cytescore"] + H --> J{"Which side matched?"} + J -->|Neither| K["no_matches"] + J -->|One| L["partial_match"] + + E -->|Yes| M["Hungarian match mean + coverage"] + M --> N["similarity_method: cytescore_compound"] + + I --> O["Result row"] + K --> O + L --> O + N --> O ``` ## AnnData entry point diff --git a/modal_app/README.md b/modal_app/README.md index e8cd769..4e53bb4 100644 --- a/modal_app/README.md +++ b/modal_app/README.md @@ -246,12 +246,14 @@ Returns `404` if the `runId` is unknown and `409` if the run is not in state `co | `pair_index` | `int` | Position in the label list, starting at 0. | | `author_label` | `str` | Author label for this pair. | | `algorithm_label` | `str` | Algorithm label for this pair. | -| `author_ontology_id` | `str \| null` | Best CL match for the author label. | -| `author_embedding_similarity` | `float` | Cosine similarity to that CL term. | -| `algorithm_ontology_id` | `str \| null` | Best CL match for the algorithm label. | -| `algorithm_embedding_similarity` | `float` | Cosine similarity to that CL term. | -| `cytescore_similarity` | `float` | Score under the chosen `metric`; `0.0` if either side is unmatched. | -| `similarity_method` | `str` | `cytescore`, `string_similarity`, `partial_match`, or `no_matches`. | +| `author_ontology_id` | `str` | Best CL match for the author label. Semicolon-separated for compound pairs (matched assignment only). Empty string if unmatched. | +| `author_ontology_name` | `str` | Primary CSV label (or OWL fallback) for each id in `author_ontology_id`. | +| `author_embedding_similarity` | `float` | Mean cosine similarity of author parts to their CL matches. | +| `algorithm_ontology_id` | `str` | Best CL match for the algorithm label. Same compound rules as author. | +| `algorithm_ontology_name` | `str` | Names for ids in `algorithm_ontology_id`. | +| `algorithm_embedding_similarity` | `float` | Mean cosine similarity of algorithm parts to their CL matches. | +| `cytescore_similarity` | `float` | Score under the chosen `metric`; `0.0` when scoring does not apply. | +| `similarity_method` | `str` | `cytescore`, `cytescore_compound`, `string_similarity`, `partial_match`, `no_matches`, or `empty`. | ### GET `/health` @@ -423,16 +425,21 @@ Mounted at `/cyteonto_data` inside every container: │ └── descriptions/ │ └── descriptions_.json └── user_files/ - └── / - ├── status.json - ├── result.csv - ├── result.json - ├── embeddings/ - │ ├── author/author_embeddings__.npz - │ └── algorithm/_embeddings__.npz - └── descriptions/ - ├── author/author_descriptions_.json - └── algorithm/_descriptions_.json + ├── / + │ ├── status.json + │ ├── result.csv + │ └── result.json + ├── embeddings/ + │ └── / + │ ├── author/author_embeddings__.npz + │ └── algorithm/_embeddings__.npz + ├── descriptions/ + │ └── / + │ ├── author/author_descriptions_.json + │ └── algorithm/_descriptions_.json + └── decompositions/ + └── / + └── decompositions_.json ``` `` is the cleaned LLM model name (for example `moonshotai-Kimi-K2.6`) and `` is the cleaned embedding model name. Descriptions and embeddings are cached per text and embedding model, so switching either model produces a fresh cache without invalidating the existing one. diff --git a/modal_app/api.py b/modal_app/api.py index 61264bf..d785ddd 100644 --- a/modal_app/api.py +++ b/modal_app/api.py @@ -4,6 +4,7 @@ import uuid from datetime import datetime, timezone from pathlib import Path +from threading import Lock from typing import Any from fastapi import FastAPI, HTTPException @@ -16,6 +17,7 @@ app_config = AppConfig() cyte_config = CyteConfig() +_volume_lock = Lock() def _utc_now() -> str: @@ -27,21 +29,25 @@ def _status_path(run_id: str) -> Path: def _read_status(run_id: str, volume) -> dict[str, Any] | None: - volume.reload() - path = _status_path(run_id) - if not path.exists(): - return None - try: - return json.loads(path.read_text()) - except Exception: - return None + with _volume_lock: + volume.reload() + path = _status_path(run_id) + if not path.exists(): + return None + try: + return json.loads(path.read_text()) + except (OSError, json.JSONDecodeError): + return None def _write_status(run_id: str, data: dict[str, Any], volume) -> None: - path = _status_path(run_id) - path.parent.mkdir(parents=True, exist_ok=True) - path.write_text(json.dumps(data, indent=2)) - volume.commit() + with _volume_lock: + path = _status_path(run_id) + path.parent.mkdir(parents=True, exist_ok=True) + temporary_path = path.with_name(f".{path.name}.tmp") + temporary_path.write_text(json.dumps(data, indent=2)) + temporary_path.replace(path) + volume.commit() def create_app(volume, run_compare_fn) -> FastAPI: diff --git a/modal_app/worker.py b/modal_app/worker.py index 97abd4a..bd1cc81 100644 --- a/modal_app/worker.py +++ b/modal_app/worker.py @@ -1,5 +1,6 @@ """Compare-job worker body. Runs inside the worker Modal container.""" +import asyncio import json import os import traceback @@ -16,6 +17,18 @@ app_config = AppConfig() cyte_config = CyteConfig() +_INITIAL_STATUS_FIELDS = frozenset( + { + "runId", + "state", + "createdAt", + "numAuthorLabels", + "numAlgorithms", + } +) +_STATUS_LOAD_ATTEMPTS = 5 +_STATUS_LOAD_RETRY_SECONDS = 0.2 + def _env_api_key(provider: str) -> str: env_var = cyte_config.PROVIDER_API_KEY_ENV.get(provider) @@ -67,20 +80,40 @@ def _result_paths(run_id: str) -> tuple[Path, Path]: return base / "result.csv", base / "result.json" -def _read_status(run_id: str) -> dict[str, Any]: +def _read_status(run_id: str) -> dict[str, Any] | None: path = _status_path(run_id) if not path.exists(): - return {"runId": run_id} + return None try: return json.loads(path.read_text()) - except Exception: - return {"runId": run_id} + except (OSError, json.JSONDecodeError): + return None def _write_status(run_id: str, status: dict[str, Any]) -> None: path = _status_path(run_id) path.parent.mkdir(parents=True, exist_ok=True) - path.write_text(json.dumps(status, indent=2)) + temporary_path = path.with_name(f".{path.name}.tmp") + temporary_path.write_text(json.dumps(status, indent=2)) + temporary_path.replace(path) + + +async def _load_initial_status(run_id: str, volume) -> dict[str, Any]: + for attempt in range(_STATUS_LOAD_ATTEMPTS): + await volume.reload.aio() + status = _read_status(run_id) + if ( + status is not None + and status.get("runId") == run_id + and _INITIAL_STATUS_FIELDS <= status.keys() + ): + return status + if attempt < _STATUS_LOAD_ATTEMPTS - 1: + await asyncio.sleep(_STATUS_LOAD_RETRY_SECONDS) + + raise RuntimeError( + f"Committed initial status is missing or incomplete for run '{run_id}'" + ) _LLM_BASE_URLS: dict[str, str | None] = { @@ -136,7 +169,7 @@ async def run_compare_job(run_id: str, payload: dict[str, Any], volume) -> None: from cyteonto.logger import logger from cyteonto.models import EmbdConfig, LlmConfig - status = _read_status(run_id) + status = await _load_initial_status(run_id, volume) status.update({"state": "running", "startedAt": _utc_now()}) _write_status(run_id, status) await volume.commit.aio() diff --git a/notebooks/quick_tutorial.ipynb b/notebooks/quick_tutorial.ipynb index a77d0f8..8dbb9d2 100644 --- a/notebooks/quick_tutorial.ipynb +++ b/notebooks/quick_tutorial.ipynb @@ -21,20 +21,9 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "True" - ] - }, - "execution_count": 1, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "import sys\n", "from pathlib import Path\n", @@ -54,12 +43,15 @@ "source": [ "import os\n", "\n", + "import numpy as np\n", + "import pandas as pd\n", "from pydantic_ai import Agent\n", "from pydantic_ai.models.openai import OpenAIChatModel\n", "from pydantic_ai.providers.openai import OpenAIProvider\n", "\n", "from cyteonto import CyteOnto, EmbdConfig, LlmConfig\n", - "from cyteonto.config import Config" + "from cyteonto.config import Config\n", + "from cyteonto.cyteonto import _hungarian_match_mean" ] }, { @@ -176,7 +168,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 6, "id": "abe54d99", "metadata": {}, "outputs": [ @@ -207,8 +199,10 @@ " author_label\n", " algorithm_label\n", " author_ontology_id\n", + " author_ontology_name\n", " author_embedding_similarity\n", " algorithm_ontology_id\n", + " algorithm_ontology_name\n", " algorithm_embedding_similarity\n", " cytescore_similarity\n", " similarity_method\n", @@ -222,11 +216,13 @@ " 0\n", " animal stem cell\n", " stem cell\n", + " CL:0002248\n", + " pluripotent stem cell\n", + " 0.8119\n", " CL:0000034\n", - " 0.8436\n", - " CL:0002322\n", - " 0.8425\n", - " 0.4042\n", + " stem cell\n", + " 0.8255\n", + " 0.5450\n", " cytescore\n", " \n", " \n", @@ -237,9 +233,11 @@ " BFU-E\n", " blast forming unit erythroid\n", " CL:0000038\n", - " 0.9127\n", + " erythroid progenitor cell\n", + " 0.8626\n", " CL:0000038\n", - " 0.8490\n", + " erythroid progenitor cell\n", + " 0.8249\n", " 1.0000\n", " cytescore\n", " \n", @@ -251,9 +249,11 @@ " neutrophilic granuloblast\n", " spermatogonium\n", " CL:0000042\n", - " 0.9029\n", + " neutrophilic myeloblast\n", + " 0.8360\n", " CL:0000020\n", - " 0.9487\n", + " spermatogonium\n", + " 0.9061\n", " 0.1077\n", " cytescore\n", " \n", @@ -264,11 +264,13 @@ " 0\n", " animal stem cell\n", " neuronal receptor cell\n", - " CL:0000034\n", - " 0.8436\n", + " CL:0002248\n", + " pluripotent stem cell\n", + " 0.8119\n", " CL:0000006\n", - " 0.9302\n", - " 0.0856\n", + " neuronal receptor cell\n", + " 0.8635\n", + " 0.0671\n", " cytescore\n", " \n", " \n", @@ -279,10 +281,12 @@ " BFU-E\n", " stem cell\n", " CL:0000038\n", - " 0.9127\n", - " CL:0002322\n", - " 0.8700\n", - " 0.1660\n", + " erythroid progenitor cell\n", + " 0.8626\n", + " CL:0000034\n", + " stem cell\n", + " 0.7925\n", + " 0.1231\n", " cytescore\n", " \n", " \n", @@ -293,9 +297,11 @@ " neutrophilic granuloblast\n", " ovum\n", " CL:0000042\n", - " 0.9029\n", + " neutrophilic myeloblast\n", + " 0.8360\n", " CL:0000025\n", - " 0.8893\n", + " egg cell\n", + " 0.8905\n", " 0.0837\n", " cytescore\n", " \n", @@ -312,32 +318,40 @@ "4 quick_tutorial_demo method_b 1 BFU-E \n", "5 quick_tutorial_demo method_b 2 neutrophilic granuloblast \n", "\n", - " algorithm_label author_ontology_id \\\n", - "0 stem cell CL:0000034 \n", - "1 blast forming unit erythroid CL:0000038 \n", - "2 spermatogonium CL:0000042 \n", - "3 neuronal receptor cell CL:0000034 \n", - "4 stem cell CL:0000038 \n", - "5 ovum CL:0000042 \n", + " algorithm_label author_ontology_id author_ontology_name \\\n", + "0 stem cell CL:0002248 pluripotent stem cell \n", + "1 blast forming unit erythroid CL:0000038 erythroid progenitor cell \n", + "2 spermatogonium CL:0000042 neutrophilic myeloblast \n", + "3 neuronal receptor cell CL:0002248 pluripotent stem cell \n", + "4 stem cell CL:0000038 erythroid progenitor cell \n", + "5 ovum CL:0000042 neutrophilic myeloblast \n", "\n", " author_embedding_similarity algorithm_ontology_id \\\n", - "0 0.8436 CL:0002322 \n", - "1 0.9127 CL:0000038 \n", - "2 0.9029 CL:0000020 \n", - "3 0.8436 CL:0000006 \n", - "4 0.9127 CL:0002322 \n", - "5 0.9029 CL:0000025 \n", + "0 0.8119 CL:0000034 \n", + "1 0.8626 CL:0000038 \n", + "2 0.8360 CL:0000020 \n", + "3 0.8119 CL:0000006 \n", + "4 0.8626 CL:0000034 \n", + "5 0.8360 CL:0000025 \n", + "\n", + " algorithm_ontology_name algorithm_embedding_similarity \\\n", + "0 stem cell 0.8255 \n", + "1 erythroid progenitor cell 0.8249 \n", + "2 spermatogonium 0.9061 \n", + "3 neuronal receptor cell 0.8635 \n", + "4 stem cell 0.7925 \n", + "5 egg cell 0.8905 \n", "\n", - " algorithm_embedding_similarity cytescore_similarity similarity_method \n", - "0 0.8425 0.4042 cytescore \n", - "1 0.8490 1.0000 cytescore \n", - "2 0.9487 0.1077 cytescore \n", - "3 0.9302 0.0856 cytescore \n", - "4 0.8700 0.1660 cytescore \n", - "5 0.8893 0.0837 cytescore " + " cytescore_similarity similarity_method \n", + "0 0.5450 cytescore \n", + "1 1.0000 cytescore \n", + "2 0.1077 cytescore \n", + "3 0.0671 cytescore \n", + "4 0.1231 cytescore \n", + "5 0.0837 cytescore " ] }, - "execution_count": 9, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -348,7 +362,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "metadata": {}, "outputs": [ { @@ -357,7 +371,7 @@ "text": [ "run_id: quick_tutorial_demo\n", "model pair usage: {'llmTier': 'primary', 'embeddingTier': 'primary', 'llmProvider': 'nebius', 'llmModel': 'moonshotai/Kimi-K2.6', 'embeddingProvider': 'nebius', 'embeddingModel': 'Qwen/Qwen3-Embedding-8B'}\n", - "LLM usage: {'agentName': 'CyteOnto', 'modelName': 'moonshotai/Kimi-K2.6', 'requests': 2916, 'inputTokens': 1713333, 'outputTokens': 505560, 'totalTokens': 2218893, 'toolUsage': {'final_result': 3140}}\n" + "LLM usage: {'agentName': 'CyteOnto', 'modelName': 'moonshotai/Kimi-K2.6', 'requests': 18, 'inputTokens': 8859, 'outputTokens': 1862, 'totalTokens': 10721, 'toolUsage': {'final_result': 19}}\n" ] } ], @@ -369,43 +383,789 @@ }, { "cell_type": "markdown", + "id": "d2a9d5a1", "metadata": {}, "source": [ - "## Cache utilities\n", + "## Compound label scoring\n", "\n", - "Artifacts are stored under `cyteonto/data/user_files//` keyed by provider and model (schema v3)." + "Mixture labels (doublets, mixed populations) are decomposed by the LLM into cell-type parts before scoring. Each input pair still produces **one row**.\n", + "\n", + "For compound pairs (`similarity_method == \"cytescore_compound\"`):\n", + "\n", + "1. Build an **m×n** cytescore matrix `S` between author parts (rows) and algorithm parts (columns).\n", + "2. Run **max-weight bipartite matching** (Hungarian algorithm): pick `k = min(m, n)` one-to-one pairs that maximize total score.\n", + "3. **Match mean** = average score of the assigned pairs.\n", + "4. If **m ≠ n**, multiply by **coverage** = `min(m, n) / max(m, n)`.\n", + "\n", + "Ontology IDs and names in the result list only the **matched** assignment pairs (semicolon-separated when `k > 1`).\n", + "\n", + "| Scenario | m×n | Illustrative score |\n", + "|----------|-----|--------------------|\n", + "| Same compound (two overlap) | 2×2 | ~1.0 |\n", + "| One shared type | 2×2 | ~0.5 |\n", + "| No shared types | 2×2 | ~0.07 |\n", + "| Partial overlap, extra author type | 3×2 | ~0.35 |\n", + "| Author doublet vs single type | 2×1 | best match × 0.5 |\n", + "\n", + "The next cell runs these matrices with `_hungarian_match_mean` using illustrative scores (no API calls). The section after that runs live comparisons for the same scenarios." + ] + }, + { + "cell_type": "markdown", + "id": "9376d486", + "metadata": {}, + "source": [ + "### Synthetic scoring examples (no API)\n", + "\n", + "Scores below are **illustrative** cytescores placed in `S` to show how the metric behaves. Live labels in the next section use the real pipeline (decompose → embed → match → Hungarian)." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "b4c2dd21", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
scenariomncoverageassignmentsillustrative_cytescore
02x1_coverage210.5000[(1, 0)]0.4600
12x2_two_overlap221.0000[(0, 0), (1, 1)]0.9750
22x2_one_overlap221.0000[(0, 0), (1, 1)]0.5300
32x2_zero_overlap221.0000[(0, 0), (1, 1)]0.0650
43x2_one_overlap320.6667[(0, 0), (1, 1)]0.3533
\n", + "
" + ], + "text/plain": [ + " scenario m n coverage assignments illustrative_cytescore\n", + "0 2x1_coverage 2 1 0.5000 [(1, 0)] 0.4600\n", + "1 2x2_two_overlap 2 2 1.0000 [(0, 0), (1, 1)] 0.9750\n", + "2 2x2_one_overlap 2 2 1.0000 [(0, 0), (1, 1)] 0.5300\n", + "3 2x2_zero_overlap 2 2 1.0000 [(0, 0), (1, 1)] 0.0650\n", + "4 3x2_one_overlap 3 2 0.6667 [(0, 0), (1, 1)] 0.3533" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def hungarian_demo(scenario: str, scores: list[list[float]]) -> dict:\n", + " matrix = np.array(scores, dtype=np.float64)\n", + " m, n = matrix.shape\n", + " final, assignments = _hungarian_match_mean(matrix)\n", + " return {\n", + " \"scenario\": scenario,\n", + " \"m\": m,\n", + " \"n\": n,\n", + " \"coverage\": min(m, n) / max(m, n) if max(m, n) else 0.0,\n", + " \"assignments\": assignments,\n", + " \"illustrative_cytescore\": round(final, 4),\n", + " \"matrix\": matrix,\n", + " }\n", + "\n", + "\n", + "demo_scenarios = [\n", + " (\"2x1_coverage\", [[0.05], [0.92]]),\n", + " (\"2x2_two_overlap\", [[0.98, 0.06], [0.05, 0.97]]),\n", + " (\"2x2_one_overlap\", [[0.98, 0.05], [0.06, 0.08]]),\n", + " (\"2x2_zero_overlap\", [[0.06, 0.05], [0.08, 0.07]]),\n", + " (\"3x2_one_overlap\", [[0.98, 0.05], [0.06, 0.08], [0.05, 0.07]]),\n", + "]\n", + "\n", + "demos = [hungarian_demo(name, matrix) for name, matrix in demo_scenarios]\n", + "\n", + "demo_df = pd.DataFrame(\n", + " [\n", + " {\n", + " \"scenario\": d[\"scenario\"],\n", + " \"m\": d[\"m\"],\n", + " \"n\": d[\"n\"],\n", + " \"coverage\": round(d[\"coverage\"], 4),\n", + " \"assignments\": d[\"assignments\"],\n", + " \"illustrative_cytescore\": d[\"illustrative_cytescore\"],\n", + " }\n", + " for d in demos\n", + " ]\n", + ")\n", + "demo_df" + ] + }, + { + "cell_type": "markdown", + "id": "cd1fda09", + "metadata": {}, + "source": [ + "### Live compound comparisons\n", + "\n", + "Each row below is a deliberate scenario. After `compare`, a `scenario` column is attached for readability." ] }, { "cell_type": "code", "execution_count": null, + "id": "6273fd69", "metadata": {}, "outputs": [], "source": [ - "cyto.cache_stats(run_id=\"quick_tutorial_demo\")" + "compound_scenarios = [\n", + " (\"2x1_coverage\", \"AT2 cell-plasma cell doublet\", \"Plasma cell\"),\n", + " (\"2x2_two_overlap\", \"AT2 cell-plasma cell doublet\", \"AT2 cell / Plasma cell\"),\n", + " (\"2x2_one_overlap\", \"AT2 cell-plasma cell doublet\", \"AT2 cell / T cell\"),\n", + " (\"2x2_zero_overlap\", \"AT2 cell-plasma cell doublet\", \"T cell / B cell\"),\n", + " (\"3x2_one_overlap\", \"AT2 cell / plasma cell / NK cell\", \"AT2 cell / T cell\"),\n", + " (\"simple_control\", \"T cell\", \"T cell\"),\n", + "]\n", + "\n", + "compound_author_labels = [author for _, author, _ in compound_scenarios]\n", + "compound_algorithm_labels = [algo for _, _, algo in compound_scenarios]\n", + "\n", + "compound_df = await cyto.compare(\n", + " author_labels=compound_author_labels,\n", + " algorithms={\"scenarios\": compound_algorithm_labels},\n", + " run_id=\"quick_tutorial_compound\",\n", + " metric=\"cosine_kernel\",\n", + ")\n", + "compound_df.insert(0, \"scenario\", [name for name, _, _ in compound_scenarios])" ] }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 10, + "id": "d493be8d", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
scenariorun_idalgorithmpair_indexauthor_labelalgorithm_labelauthor_ontology_idauthor_ontology_nameauthor_embedding_similarityalgorithm_ontology_idalgorithm_ontology_namealgorithm_embedding_similaritycytescore_similaritysimilarity_method
02x1_coveragequick_tutorial_compoundscenarios0AT2 cell-plasma cell doubletPlasma cellCL:0000946antibody secreting cell0.9124CL:0000946antibody secreting cell0.90230.5000cytescore_compound
12x2_two_overlapquick_tutorial_compoundscenarios1AT2 cell-plasma cell doubletAT2 cell / Plasma cellCL:0002063;CL:0000946pulmonary alveolar type 2 cell;antibody secret...0.9124CL:0002063;CL:0000946pulmonary alveolar type 2 cell;antibody secret...0.89791.0000cytescore_compound
22x2_one_overlapquick_tutorial_compoundscenarios2AT2 cell-plasma cell doubletAT2 cell / T cellCL:0002063;CL:0000946pulmonary alveolar type 2 cell;antibody secret...0.9124CL:0002063;CL:0000084pulmonary alveolar type 2 cell;T cell0.86840.5984cytescore_compound
32x2_zero_overlapquick_tutorial_compoundscenarios3AT2 cell-plasma cell doubletT cell / B cellCL:0002063;CL:0000946pulmonary alveolar type 2 cell;antibody secret...0.9124CL:0000084;CL:0000236T cell;B cell0.85760.3334cytescore_compound
43x2_one_overlapquick_tutorial_compoundscenarios4AT2 cell / plasma cell / NK cellAT2 cell / T cellCL:0002063;CL:0000623pulmonary alveolar type 2 cell;natural killer ...0.8855CL:0002063;CL:0000084pulmonary alveolar type 2 cell;T cell0.86840.4121cytescore_compound
5simple_controlquick_tutorial_compoundscenarios5T cellT cellCL:0000084T cell0.8753CL:0000084T cell0.84351.0000cytescore
\n", + "
" + ], + "text/plain": [ + " scenario run_id algorithm pair_index \\\n", + "0 2x1_coverage quick_tutorial_compound scenarios 0 \n", + "1 2x2_two_overlap quick_tutorial_compound scenarios 1 \n", + "2 2x2_one_overlap quick_tutorial_compound scenarios 2 \n", + "3 2x2_zero_overlap quick_tutorial_compound scenarios 3 \n", + "4 3x2_one_overlap quick_tutorial_compound scenarios 4 \n", + "5 simple_control quick_tutorial_compound scenarios 5 \n", + "\n", + " author_label algorithm_label \\\n", + "0 AT2 cell-plasma cell doublet Plasma cell \n", + "1 AT2 cell-plasma cell doublet AT2 cell / Plasma cell \n", + "2 AT2 cell-plasma cell doublet AT2 cell / T cell \n", + "3 AT2 cell-plasma cell doublet T cell / B cell \n", + "4 AT2 cell / plasma cell / NK cell AT2 cell / T cell \n", + "5 T cell T cell \n", + "\n", + " author_ontology_id author_ontology_name \\\n", + "0 CL:0000946 antibody secreting cell \n", + "1 CL:0002063;CL:0000946 pulmonary alveolar type 2 cell;antibody secret... \n", + "2 CL:0002063;CL:0000946 pulmonary alveolar type 2 cell;antibody secret... \n", + "3 CL:0002063;CL:0000946 pulmonary alveolar type 2 cell;antibody secret... \n", + "4 CL:0002063;CL:0000623 pulmonary alveolar type 2 cell;natural killer ... \n", + "5 CL:0000084 T cell \n", + "\n", + " author_embedding_similarity algorithm_ontology_id \\\n", + "0 0.9124 CL:0000946 \n", + "1 0.9124 CL:0002063;CL:0000946 \n", + "2 0.9124 CL:0002063;CL:0000084 \n", + "3 0.9124 CL:0000084;CL:0000236 \n", + "4 0.8855 CL:0002063;CL:0000084 \n", + "5 0.8753 CL:0000084 \n", + "\n", + " algorithm_ontology_name \\\n", + "0 antibody secreting cell \n", + "1 pulmonary alveolar type 2 cell;antibody secret... \n", + "2 pulmonary alveolar type 2 cell;T cell \n", + "3 T cell;B cell \n", + "4 pulmonary alveolar type 2 cell;T cell \n", + "5 T cell \n", + "\n", + " algorithm_embedding_similarity cytescore_similarity similarity_method \n", + "0 0.9023 0.5000 cytescore_compound \n", + "1 0.8979 1.0000 cytescore_compound \n", + "2 0.8684 0.5984 cytescore_compound \n", + "3 0.8576 0.3334 cytescore_compound \n", + "4 0.8684 0.4121 cytescore_compound \n", + "5 0.8435 1.0000 cytescore " + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "# cyto.clear_run(\"quick_tutorial_demo\") # uncomment to delete cached files for this run" + "compound_df" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "0288ec49", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
scenariomncoverageassignmentsillustrative_cytescoreauthor_labelalgorithm_labelauthor_ontology_namealgorithm_ontology_namelive_cytescoresimilarity_method
02x1_coverage2.01.00.5000[(1, 0)]0.4600AT2 cell-plasma cell doubletPlasma cellantibody secreting cellantibody secreting cell0.5000cytescore_compound
12x2_one_overlap2.02.01.0000[(0, 0), (1, 1)]0.5300AT2 cell-plasma cell doubletAT2 cell / T cellpulmonary alveolar type 2 cell;antibody secret...pulmonary alveolar type 2 cell;T cell0.5984cytescore_compound
22x2_two_overlap2.02.01.0000[(0, 0), (1, 1)]0.9750AT2 cell-plasma cell doubletAT2 cell / Plasma cellpulmonary alveolar type 2 cell;antibody secret...pulmonary alveolar type 2 cell;antibody secret...1.0000cytescore_compound
32x2_zero_overlap2.02.01.0000[(0, 0), (1, 1)]0.0650AT2 cell-plasma cell doubletT cell / B cellpulmonary alveolar type 2 cell;antibody secret...T cell;B cell0.3334cytescore_compound
43x2_one_overlap3.02.00.6667[(0, 0), (1, 1)]0.3533AT2 cell / plasma cell / NK cellAT2 cell / T cellpulmonary alveolar type 2 cell;natural killer ...pulmonary alveolar type 2 cell;T cell0.4121cytescore_compound
5simple_controlNaNNaNNaNNaNNaNT cellT cellT cellT cell1.0000cytescore
\n", + "
" + ], + "text/plain": [ + " scenario m n coverage assignments \\\n", + "0 2x1_coverage 2.0 1.0 0.5000 [(1, 0)] \n", + "1 2x2_one_overlap 2.0 2.0 1.0000 [(0, 0), (1, 1)] \n", + "2 2x2_two_overlap 2.0 2.0 1.0000 [(0, 0), (1, 1)] \n", + "3 2x2_zero_overlap 2.0 2.0 1.0000 [(0, 0), (1, 1)] \n", + "4 3x2_one_overlap 3.0 2.0 0.6667 [(0, 0), (1, 1)] \n", + "5 simple_control NaN NaN NaN NaN \n", + "\n", + " illustrative_cytescore author_label \\\n", + "0 0.4600 AT2 cell-plasma cell doublet \n", + "1 0.5300 AT2 cell-plasma cell doublet \n", + "2 0.9750 AT2 cell-plasma cell doublet \n", + "3 0.0650 AT2 cell-plasma cell doublet \n", + "4 0.3533 AT2 cell / plasma cell / NK cell \n", + "5 NaN T cell \n", + "\n", + " algorithm_label author_ontology_name \\\n", + "0 Plasma cell antibody secreting cell \n", + "1 AT2 cell / T cell pulmonary alveolar type 2 cell;antibody secret... \n", + "2 AT2 cell / Plasma cell pulmonary alveolar type 2 cell;antibody secret... \n", + "3 T cell / B cell pulmonary alveolar type 2 cell;antibody secret... \n", + "4 AT2 cell / T cell pulmonary alveolar type 2 cell;natural killer ... \n", + "5 T cell T cell \n", + "\n", + " algorithm_ontology_name live_cytescore \\\n", + "0 antibody secreting cell 0.5000 \n", + "1 pulmonary alveolar type 2 cell;T cell 0.5984 \n", + "2 pulmonary alveolar type 2 cell;antibody secret... 1.0000 \n", + "3 T cell;B cell 0.3334 \n", + "4 pulmonary alveolar type 2 cell;T cell 0.4121 \n", + "5 T cell 1.0000 \n", + "\n", + " similarity_method \n", + "0 cytescore_compound \n", + "1 cytescore_compound \n", + "2 cytescore_compound \n", + "3 cytescore_compound \n", + "4 cytescore_compound \n", + "5 cytescore " + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "summary_cols = [\n", + " \"scenario\",\n", + " \"author_label\",\n", + " \"algorithm_label\",\n", + " \"author_ontology_name\",\n", + " \"algorithm_ontology_name\",\n", + " \"cytescore_similarity\",\n", + " \"similarity_method\",\n", + "]\n", + "\n", + "live_summary = compound_df[summary_cols].rename(\n", + " columns={\"cytescore_similarity\": \"live_cytescore\"}\n", + ")\n", + "\n", + "pd.merge(demo_df, live_summary, on=\"scenario\", how=\"outer\")" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "f7d49d07", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
scenarioauthor_ontology_idalgorithm_ontology_idauthor_ontology_namealgorithm_ontology_namecytescore_similarity
02x1_coverageCL:0000946CL:0000946antibody secreting cellantibody secreting cell0.5000
12x2_two_overlapCL:0002063;CL:0000946CL:0002063;CL:0000946pulmonary alveolar type 2 cell;antibody secret...pulmonary alveolar type 2 cell;antibody secret...1.0000
22x2_one_overlapCL:0002063;CL:0000946CL:0002063;CL:0000084pulmonary alveolar type 2 cell;antibody secret...pulmonary alveolar type 2 cell;T cell0.5984
32x2_zero_overlapCL:0002063;CL:0000946CL:0000084;CL:0000236pulmonary alveolar type 2 cell;antibody secret...T cell;B cell0.3334
43x2_one_overlapCL:0002063;CL:0000623CL:0002063;CL:0000084pulmonary alveolar type 2 cell;natural killer ...pulmonary alveolar type 2 cell;T cell0.4121
\n", + "
" + ], + "text/plain": [ + " scenario author_ontology_id algorithm_ontology_id \\\n", + "0 2x1_coverage CL:0000946 CL:0000946 \n", + "1 2x2_two_overlap CL:0002063;CL:0000946 CL:0002063;CL:0000946 \n", + "2 2x2_one_overlap CL:0002063;CL:0000946 CL:0002063;CL:0000084 \n", + "3 2x2_zero_overlap CL:0002063;CL:0000946 CL:0000084;CL:0000236 \n", + "4 3x2_one_overlap CL:0002063;CL:0000623 CL:0002063;CL:0000084 \n", + "\n", + " author_ontology_name \\\n", + "0 antibody secreting cell \n", + "1 pulmonary alveolar type 2 cell;antibody secret... \n", + "2 pulmonary alveolar type 2 cell;antibody secret... \n", + "3 pulmonary alveolar type 2 cell;antibody secret... \n", + "4 pulmonary alveolar type 2 cell;natural killer ... \n", + "\n", + " algorithm_ontology_name cytescore_similarity \n", + "0 antibody secreting cell 0.5000 \n", + "1 pulmonary alveolar type 2 cell;antibody secret... 1.0000 \n", + "2 pulmonary alveolar type 2 cell;T cell 0.5984 \n", + "3 T cell;B cell 0.3334 \n", + "4 pulmonary alveolar type 2 cell;T cell 0.4121 " + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "compound_df[compound_df[\"similarity_method\"] == \"cytescore_compound\"][\n", + " [\n", + " \"scenario\",\n", + " \"author_ontology_id\",\n", + " \"algorithm_ontology_id\",\n", + " \"author_ontology_name\",\n", + " \"algorithm_ontology_name\",\n", + " \"cytescore_similarity\",\n", + " ]\n", + "]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Cache utilities\n", + "\n", + "Artifacts are stored under `cyteonto/data/user_files//` keyed by provider and model (schema v3)." ] }, { "cell_type": "code", "execution_count": null, - "id": "3870a0f7", "metadata": {}, "outputs": [], - "source": [] + "source": [ + "cyto.cache_stats(run_id=\"quick_tutorial_demo\")" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "# cyto.clear_run(\"quick_tutorial_demo\") # uncomment to delete cached files for this run" + ] } ], "metadata": { "kernelspec": { - "display_name": ".venv", + "display_name": ".venv (3.13.9)", "language": "python", "name": "python3" }, diff --git a/pyproject.toml b/pyproject.toml index 5470431..ee74e80 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,29 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + [project] name = "cyteonto" -version = "0.1.0" -description = "Add your description here" -readme = "README.md" +dynamic = ["version"] +description = "Semantic comparison of cell type annotations against the Cell Ontology using LLMs and embedding-based similarity." +readme = { file = "README.md", content-type = "text/markdown" } +license = "MIT" +license-files = ["LICENSE"] requires-python = ">=3.13" +authors = [ + { name = "Nygen Analytics" }, +] +keywords = [ + "bioinformatics", + "cell-ontology", + "cell-type", + "llm", + "single-cell", + "transcriptomics", + "cell-label" +] dependencies = [ - "aiohttp>=3.12.15", + "aiohttp>=3.14.0", "anndata>=0.12.2", "asyncio>=4.0.0", "fastapi>=0.116.1", @@ -22,7 +40,7 @@ dependencies = [ "pandas>=2.3.1", "pyarrow>=24.0.0", "pydantic>=2.11.7", - "pydantic-ai>=0.7.4", + "pydantic-ai>=1.102.0,<2.0.0", "pytest>=8.4.1", "pytest-asyncio>=1.1.0", "python-dotenv>=1.1.1", @@ -34,11 +52,23 @@ dependencies = [ "xmltodict>=0.14.2", ] +[project.urls] +Homepage = "https://www.nygen.io/products/cytetype" +Documentation = "https://github.com/NygenAnalytics/CyteOnto/blob/master/cyteonto/README.md" +Repository = "https://github.com/NygenAnalytics/CyteOnto" +Issues = "https://github.com/NygenAnalytics/CyteOnto/issues" +Changelog = "https://github.com/NygenAnalytics/CyteOnto/releases" + [dependency-groups] dev = [ "seaborn>=0.13.2", ] +[tool.hatch.version] +path = "cyteonto/__init__.py" +[tool.hatch.build.targets.wheel] +packages = ["cyteonto"] + [tool.pytest.ini_options] testpaths = ["tests"] asyncio_mode = "auto" diff --git a/scripts/test_modal_compound_labels.sh b/scripts/test_modal_compound_labels.sh new file mode 100755 index 0000000..807ba0c --- /dev/null +++ b/scripts/test_modal_compound_labels.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash + +set -euo pipefail + +CYTEONTO_URL="${CYTEONTO_URL:-https://cyteonto.nygen.io}" +POLL_INTERVAL="${POLL_INTERVAL:-5}" + +command -v curl >/dev/null || { + echo "curl is required" >&2 + exit 1 +} +command -v jq >/dev/null || { + echo "jq is required" >&2 + exit 1 +} + +response="$( + curl -fsS -X POST "${CYTEONTO_URL}/compare" \ + -H 'Content-Type: application/json' \ + --data-binary @- <<'JSON' +{ + "authorLabels": [ + "AT2 cell-plasma cell doublet", + "AT2 cell-plasma cell doublet", + "AT2 cell-plasma cell doublet", + "AT2 cell-plasma cell doublet", + "AT2 cell / plasma cell / NK cell", + "T cell" + ], + "algorithms": { + "scenarios": [ + "Plasma cell", + "AT2 cell / Plasma cell", + "AT2 cell / T cell", + "T cell / B cell", + "AT2 cell / T cell", + "T cell" + ] + }, + "metric": "cosine_kernel" +} +JSON +)" + +run_id="$(jq -er '.runId' <<<"$response")" +echo "Submitted ${run_id}" + +while true; do + status="$(curl -fsS "${CYTEONTO_URL}/status/${run_id}")" + state="$(jq -er '.state' <<<"$status")" + echo "State: ${state}" + + case "$state" in + completed) + curl -fsS "${CYTEONTO_URL}/result/${run_id}?format=json" | jq + break + ;; + failed) + jq >&2 <<<"$status" + exit 1 + ;; + queued|running) + sleep "$POLL_INTERVAL" + ;; + *) + echo "Unexpected state: ${state}" >&2 + jq >&2 <<<"$status" + exit 1 + ;; + esac +done diff --git a/scripts/test_modal_simple_labels.sh b/scripts/test_modal_simple_labels.sh new file mode 100755 index 0000000..d46b85f --- /dev/null +++ b/scripts/test_modal_simple_labels.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash + +set -euo pipefail + +CYTEONTO_URL="${CYTEONTO_URL:-https://cyteonto.nygen.io}" +POLL_INTERVAL="${POLL_INTERVAL:-5}" + +command -v curl >/dev/null || { + echo "curl is required" >&2 + exit 1 +} +command -v jq >/dev/null || { + echo "jq is required" >&2 + exit 1 +} + +response="$( + curl -fsS -X POST "${CYTEONTO_URL}/compare" \ + -H 'Content-Type: application/json' \ + --data-binary @- <<'JSON' +{ + "authorLabels": [ + "animal stem cell", + "BFU-E", + "neutrophilic granuloblast" + ], + "algorithms": { + "method_a": [ + "stem cell", + "blast forming unit erythroid", + "spermatogonium" + ], + "method_b": [ + "neuronal receptor cell", + "stem cell", + "ovum" + ] + }, + "metric": "cosine_kernel" +} +JSON +)" + +run_id="$(jq -er '.runId' <<<"$response")" +echo "Submitted ${run_id}" + +while true; do + status="$(curl -fsS "${CYTEONTO_URL}/status/${run_id}")" + state="$(jq -er '.state' <<<"$status")" + echo "State: ${state}" + + case "$state" in + completed) + curl -fsS "${CYTEONTO_URL}/result/${run_id}?format=json" | jq + break + ;; + failed) + jq >&2 <<<"$status" + exit 1 + ;; + queued|running) + sleep "$POLL_INTERVAL" + ;; + *) + echo "Unexpected state: ${state}" >&2 + jq >&2 <<<"$status" + exit 1 + ;; + esac +done diff --git a/tests/README.md b/tests/README.md index 0297bda..b4fc385 100644 --- a/tests/README.md +++ b/tests/README.md @@ -18,7 +18,7 @@ tests/ ├── test_embed.py # Embedding HTTP helpers and orchestration ├── test_describe.py # Prompt building and error formatting ├── test_pubmed.py # PubMed abstract retrieval -└── test_cyteonto.py # Orchestrator pure units (matching, key resolution) +└── test_cyteonto.py # Orchestrator units (decomposition, Hungarian compound scoring, compare) ``` ## Running @@ -33,6 +33,12 @@ uv run pytest -v # Single test uv run pytest tests/test_models.py::TestCellDescription::test_to_sentence +# Compound scoring and compare integration +uv run pytest tests/test_cyteonto.py::TestHungarianMatchMean tests/test_cyteonto.py::TestCompareCompoundLabels -v + +# Label decomposition helpers (in test_cyteonto.py) +uv run pytest tests/test_cyteonto.py -k "Decompos or decompose" -v + # Drop into debugger on failure uv run pytest --pdb ``` diff --git a/tests/test_cyteonto.py b/tests/test_cyteonto.py index 2e3fdc3..b80a6b6 100644 --- a/tests/test_cyteonto.py +++ b/tests/test_cyteonto.py @@ -1,14 +1,26 @@ """Tests for cyteonto.cyteonto pure units (no live agents or network).""" from pathlib import Path -from unittest.mock import AsyncMock, Mock +from unittest.mock import AsyncMock, Mock, patch import numpy as np import pytest from cyteonto import storage -from cyteonto.cyteonto import CyteOnto, _api_key_for_provider, _is_empty -from cyteonto.models import AgentUsage, CellDescription +from cyteonto.cyteonto import ( + CyteOnto, + _api_key_for_provider, + _hungarian_match_mean, + _is_empty, + _unique_parts, +) +from cyteonto.describe import ( + _normalize_decomposition, + decompose_label, + decompose_labels, +) +from cyteonto.models import AgentUsage, CellDescription, LabelDecomposition +from cyteonto.ontology import OntologyMapping class TestApiKeyForProvider: @@ -142,14 +154,36 @@ async def test_empty_labels_not_described_and_zero_vectors(self, monkeypatch): np.testing.assert_array_equal(result[2], np.zeros(2, dtype=np.float32)) +def _mock_mapping_for_compare(inst: CyteOnto) -> None: + name_by_id = { + "CL:0000001": ["T cell"], + "CL:0000002": ["NK cell"], + "CL:0000003": ["B cell"], + } + inst.mapping = Mock() + inst.mapping.labels_for_id = lambda oid: name_by_id.get(oid, []) + + class TestCompareEmptyHandling: @pytest.mark.asyncio async def test_empty_positions_get_blank_id_zero_score_empty_method(self): inst = object.__new__(CyteOnto) + _mock_mapping_for_compare(inst) + inst._resolve_label_parts = AsyncMock( + side_effect=lambda labels, run_id, use_cache: { + lbl: [lbl] for lbl in labels if lbl.strip() + } + ) inst._embed_user_labels = AsyncMock( return_value=np.zeros((2, 2), dtype=np.float32) ) - inst._match = Mock(return_value=[("CL:0000001", 0.95), ("CL:0000002", 0.80)]) + inst._match = Mock( + side_effect=[ + [("CL:0000001", 0.95)], + [("CL:0000001", 0.95)], + [("CL:0000002", 0.80)], + ] + ) sim = Mock() sim.similarity.return_value = 0.9 inst._ensure_similarity = Mock(return_value=sim) @@ -166,6 +200,7 @@ def row(algo: str, idx: int) -> dict: # Both labels present -> normal cytescore path. r = row("algo0", 0) assert r["author_ontology_id"] == "CL:0000001" + assert r["author_ontology_name"] == "T cell" assert r["algorithm_ontology_id"] == "CL:0000001" assert r["cytescore_similarity"] == 0.9 assert r["similarity_method"] == "cytescore" @@ -182,6 +217,7 @@ def row(algo: str, idx: int) -> dict: # Only algorithm label empty -> author side kept, algorithm blanked. r = row("algo1", 0) assert r["author_ontology_id"] == "CL:0000001" + assert r["author_ontology_name"] == "T cell" assert r["author_embedding_similarity"] == 0.95 assert r["algorithm_ontology_id"] == "" assert r["algorithm_embedding_similarity"] == 0.0 @@ -192,6 +228,374 @@ def row(algo: str, idx: int) -> dict: r = row("algo1", 1) assert r["author_ontology_id"] == "" assert r["algorithm_ontology_id"] == "CL:0000002" + assert r["algorithm_ontology_name"] == "NK cell" assert r["algorithm_embedding_similarity"] == 0.80 assert r["cytescore_similarity"] == 0.0 assert r["similarity_method"] == "empty" + + +class TestUniqueParts: + def test_deduplicates_parts_across_labels(self): + parts_map = { + "A/B": ["A", "B"], + "B": ["B"], + } + assert _unique_parts(["A/B", "B"], parts_map) == ["A", "B"] + + +class TestNormalizeDecomposition: + def test_single_label_fallback_on_none(self): + out = _normalize_decomposition("T cell", None) + assert out == LabelDecomposition.single("T cell") + + def test_non_compound_forces_single_part(self): + out = _normalize_decomposition( + "T cell", + LabelDecomposition( + initialLabel="T cell", isCompound=False, parts=["T", "cell"] + ), + ) + assert out.parts == ["T cell"] + assert out.isCompound is False + + def test_compound_requires_two_parts(self): + out = _normalize_decomposition( + "A/B", + LabelDecomposition(initialLabel="A/B", isCompound=True, parts=["A"]), + ) + assert out.parts == ["A/B"] + + def test_compound_keeps_cleaned_parts(self): + out = _normalize_decomposition( + "A/B", + LabelDecomposition(initialLabel="A/B", isCompound=True, parts=["A", " B "]), + ) + assert out.isCompound is True + assert out.parts == ["A", "B"] + + +class TestDecomposeLabels: + @pytest.mark.asyncio + async def test_compound_input(self, mock_base_agent): + compound = LabelDecomposition( + initialLabel="AT2 cell–plasma cell doublet", + isCompound=True, + parts=["AT2 cell", "plasma cell"], + ) + mock_agent = Mock() + mock_agent.name = "LabelDecompositionAgent" + mock_agent.model.model_name = "test-model" + with ( + patch( + "cyteonto.describe._build_decompose_agent", + return_value=mock_agent, + ), + patch( + "cyteonto.describe._run_decompose_once", + new=AsyncMock( + return_value=(compound, {}, 1, 10, 5, 15), + ), + ), + ): + dec, usage = await decompose_label( + mock_base_agent, "AT2 cell–plasma cell doublet" + ) + assert dec.isCompound is True + assert dec.parts == ["AT2 cell", "plasma cell"] + assert usage.requests == 1 + + @pytest.mark.asyncio + async def test_simple_input(self, mock_base_agent): + single = LabelDecomposition( + initialLabel="T cell", isCompound=False, parts=["T cell"] + ) + mock_agent = Mock() + mock_agent.name = "LabelDecompositionAgent" + mock_agent.model.model_name = "test-model" + with ( + patch( + "cyteonto.describe._build_decompose_agent", + return_value=mock_agent, + ), + patch( + "cyteonto.describe._run_decompose_once", + new=AsyncMock(return_value=(single, {}, 1, 8, 4, 12)), + ), + ): + dec, _ = await decompose_label(mock_base_agent, "T cell") + assert dec.isCompound is False + assert dec.parts == ["T cell"] + + @pytest.mark.asyncio + async def test_semicolon_synonym_stays_single(self, mock_base_agent): + single = LabelDecomposition( + initialLabel="T cell;T lymphocyte", + isCompound=False, + parts=["T cell;T lymphocyte"], + ) + mock_agent = Mock() + mock_agent.name = "LabelDecompositionAgent" + mock_agent.model.model_name = "test-model" + with ( + patch( + "cyteonto.describe._build_decompose_agent", + return_value=mock_agent, + ), + patch( + "cyteonto.describe._run_decompose_once", + new=AsyncMock(return_value=(single, {}, 1, 8, 4, 12)), + ), + ): + dec, _ = await decompose_label(mock_base_agent, "T cell;T lymphocyte") + assert dec.isCompound is False + assert dec.parts == ["T cell;T lymphocyte"] + + @pytest.mark.asyncio + async def test_failure_falls_back_to_single_label(self, mock_base_agent): + mock_agent = Mock() + mock_agent.name = "LabelDecompositionAgent" + mock_agent.model.model_name = "test-model" + with ( + patch( + "cyteonto.describe._build_decompose_agent", + return_value=mock_agent, + ), + patch( + "cyteonto.describe._run_decompose_once", + new=AsyncMock(side_effect=RuntimeError("provider down")), + ), + ): + dec, _ = await decompose_label(mock_base_agent, "T cell") + assert dec == LabelDecomposition.single("T cell") + + @pytest.mark.asyncio + async def test_batch_preserves_order(self, mock_base_agent): + async def fake_decompose(agent, label, **kwargs): + if "doublet" in label: + return ( + LabelDecomposition( + initialLabel=label, + isCompound=True, + parts=["AT2 cell", "plasma cell"], + ), + AgentUsage(agentName="LabelDecompositionAgent"), + ) + return ( + LabelDecomposition.single(label), + AgentUsage(agentName="LabelDecompositionAgent"), + ) + + with patch("cyteonto.describe.decompose_label", side_effect=fake_decompose): + results, _ = await decompose_labels( + mock_base_agent, + ["T cell", "AT2 cell–plasma cell doublet"], + ) + assert results[0].parts == ["T cell"] + assert results[1].parts == ["AT2 cell", "plasma cell"] + + +class TestHungarianMatchMean: + def test_same_compound_2x2(self): + scores = np.array([[0.98, 0.06], [0.05, 0.97]], dtype=np.float64) + final, assignments = _hungarian_match_mean(scores) + assert len(assignments) == 2 + assert final == pytest.approx(0.975, abs=0.001) + + def test_partial_overlap_3x2_with_coverage(self): + scores = np.array([[0.98, 0.05], [0.06, 0.08], [0.05, 0.07]], dtype=np.float64) + final, _ = _hungarian_match_mean(scores) + assert final == pytest.approx(0.35, abs=0.01) + + def test_no_overlap_2x2(self): + scores = np.array([[0.06, 0.05], [0.08, 0.07]], dtype=np.float64) + final, _ = _hungarian_match_mean(scores) + assert final == pytest.approx(0.065, abs=0.001) + + def test_unequal_1x2_applies_coverage(self): + scores = np.array([[0.05, 0.92]], dtype=np.float64) + final, assignments = _hungarian_match_mean(scores) + assert assignments == [(0, 1)] + assert final == pytest.approx(0.46, abs=0.001) + + def test_empty_matrix(self): + final, assignments = _hungarian_match_mean(np.zeros((0, 2))) + assert final == 0.0 + assert assignments == [] + + +class TestCompareCompoundLabels: + @pytest.mark.asyncio + async def test_hungarian_2x1_with_coverage(self): + inst = object.__new__(CyteOnto) + _mock_mapping_for_compare(inst) + inst._resolve_label_parts = AsyncMock( + side_effect=[ + {"A1/A2": ["A1", "A2"]}, + {"G1": ["G1"]}, + ] + ) + inst._embed_user_labels = AsyncMock( + return_value=np.zeros((3, 2), dtype=np.float32) + ) + inst._match = Mock( + return_value=[ + ("CL:0000001", 0.9), + ("CL:0000002", 0.8), + ("CL:0000003", 0.7), + ] + ) + sim = Mock() + sim.similarity.side_effect = [0.6, 0.2] + inst._ensure_similarity = Mock(return_value=sim) + + df = await inst.compare( + author_labels=["A1/A2"], + algorithms={"algo0": ["G1"]}, + run_id="run-test", + ) + + row = df.iloc[0] + assert row["author_label"] == "A1/A2" + assert row["algorithm_label"] == "G1" + assert row["cytescore_similarity"] == 0.3 + assert row["similarity_method"] == "cytescore_compound" + assert row["author_ontology_id"] == "CL:0000001" + assert row["algorithm_ontology_id"] == "CL:0000001" + assert row["author_ontology_name"] == "T cell" + assert sim.similarity.call_count == 2 + + @pytest.mark.asyncio + async def test_hungarian_2x2_same_compound(self): + inst = object.__new__(CyteOnto) + _mock_mapping_for_compare(inst) + inst._resolve_label_parts = AsyncMock( + side_effect=[ + {"A/B": ["A", "B"]}, + {"A/B": ["A", "B"]}, + ] + ) + inst._embed_user_labels = AsyncMock( + return_value=np.zeros((2, 2), dtype=np.float32) + ) + inst._match = Mock( + side_effect=[ + [("CL:0000001", 0.9), ("CL:0000002", 0.8)], + [("CL:0000001", 0.85), ("CL:0000002", 0.75)], + ] + ) + sim = Mock() + sim.similarity.side_effect = [0.98, 0.06, 0.05, 0.97] + inst._ensure_similarity = Mock(return_value=sim) + + df = await inst.compare( + author_labels=["A/B"], + algorithms={"algo0": ["A/B"]}, + run_id="run-test", + ) + + row = df.iloc[0] + assert row["cytescore_similarity"] == pytest.approx(0.975, abs=0.001) + assert row["similarity_method"] == "cytescore_compound" + assert row["author_ontology_id"] == "CL:0000001;CL:0000002" + assert row["algorithm_ontology_id"] == "CL:0000001;CL:0000002" + assert row["pair_index"] == 0 + + @pytest.mark.asyncio + async def test_compound_pair_index_not_shadowed(self): + """Repeated author strings must keep distinct pair_index values.""" + inst = object.__new__(CyteOnto) + _mock_mapping_for_compare(inst) + doublet = "AT2 cell-plasma cell doublet" + inst._resolve_label_parts = AsyncMock( + side_effect=[ + { + doublet: ["AT2 cell", "plasma cell"], + "T cell": ["T cell"], + }, + { + "Plasma cell": ["Plasma cell"], + "AT2 cell / Plasma cell": ["AT2 cell", "Plasma cell"], + "T cell": ["T cell"], + }, + ] + ) + inst._embed_user_labels = AsyncMock( + return_value=np.zeros((3, 2), dtype=np.float32) + ) + inst._match = Mock( + return_value=[ + ("CL:0000001", 0.9), + ("CL:0000002", 0.8), + ("CL:0000003", 0.7), + ] + ) + sim = Mock() + sim.similarity.return_value = 0.5 + inst._ensure_similarity = Mock(return_value=sim) + + df = await inst.compare( + author_labels=[doublet, doublet, "T cell"], + algorithms={ + "scenarios": ["Plasma cell", "AT2 cell / Plasma cell", "T cell"] + }, + run_id="run-test", + ) + + assert df["pair_index"].tolist() == [0, 1, 2] + assert df["algorithm_label"].tolist() == [ + "Plasma cell", + "AT2 cell / Plasma cell", + "T cell", + ] + + @pytest.mark.asyncio + async def test_non_compound_pair_unchanged(self): + inst = object.__new__(CyteOnto) + _mock_mapping_for_compare(inst) + inst._resolve_label_parts = AsyncMock( + side_effect=[ + {"T cell": ["T cell"]}, + {"B cell": ["B cell"]}, + ] + ) + inst._embed_user_labels = AsyncMock( + return_value=np.zeros((1, 2), dtype=np.float32) + ) + inst._match = Mock(return_value=[("CL:0000001", 0.95)]) + sim = Mock() + sim.similarity.return_value = 0.9 + inst._ensure_similarity = Mock(return_value=sim) + + df = await inst.compare( + author_labels=["T cell"], + algorithms={"algo0": ["B cell"]}, + run_id="run-test", + ) + + row = df.iloc[0] + assert row["cytescore_similarity"] == 0.9 + assert row["similarity_method"] == "cytescore" + assert row["author_ontology_name"] == "T cell" + assert row["algorithm_ontology_name"] == "T cell" + + +class TestOntologyNames: + def test_names_for_multiple_ids(self, sample_ontology_csv_file): + inst = object.__new__(CyteOnto) + inst.mapping = OntologyMapping(sample_ontology_csv_file) + inst.mapping.load() + inst._similarity = Mock() + inst._similarity._find_class = Mock(return_value=None) + + joined = ";".join(["CL:0000001", "CL:0000002"]) + assert inst._ontology_names_for_ids(joined) == "T cell;B cell" + + def test_unknown_id_returns_empty_segment(self): + inst = object.__new__(CyteOnto) + inst.mapping = Mock() + inst.mapping.labels_for_id = Mock(return_value=[]) + similarity = Mock() + similarity._find_class = Mock(return_value=None) + inst._ensure_similarity = Mock(return_value=similarity) + + assert inst._ontology_names_for_ids("CL:9999999") == "" diff --git a/tests/test_modal_status.py b/tests/test_modal_status.py new file mode 100644 index 0000000..fad0bf3 --- /dev/null +++ b/tests/test_modal_status.py @@ -0,0 +1,97 @@ +import json + +import pytest + +from modal_app import api, worker + + +def _queued_status(run_id: str) -> dict: + return { + "runId": run_id, + "state": "queued", + "createdAt": "2026-07-14T12:00:00Z", + "startedAt": None, + "completedAt": None, + "error": None, + "numAuthorLabels": 2, + "numAlgorithms": 1, + "numRows": None, + "resultCsvPath": None, + "resultJsonPath": None, + } + + +class _AsyncReload: + def __init__(self, on_reload): + self.calls = 0 + self.on_reload = on_reload + + async def aio(self): + self.calls += 1 + self.on_reload(self.calls) + + +class _AsyncVolume: + def __init__(self, on_reload): + self.reload = _AsyncReload(on_reload) + + +class _SyncVolume: + def __init__(self): + self.commit_calls = 0 + self.reload_calls = 0 + + def commit(self): + self.commit_calls += 1 + + def reload(self): + self.reload_calls += 1 + + +async def test_worker_reloads_until_initial_status_is_visible(tmp_path, monkeypatch): + run_id = "run-test" + monkeypatch.setattr(worker.app_config, "REMOTE_USER_DIR", str(tmp_path)) + monkeypatch.setattr(worker, "_STATUS_LOAD_RETRY_SECONDS", 0) + + def publish_status(reload_count: int) -> None: + if reload_count != 2: + return + path = tmp_path / run_id / "status.json" + path.parent.mkdir(parents=True) + path.write_text(json.dumps(_queued_status(run_id))) + + volume = _AsyncVolume(publish_status) + + status = await worker._load_initial_status(run_id, volume) + + assert status == _queued_status(run_id) + assert volume.reload.calls == 2 + + +async def test_worker_rejects_incomplete_initial_status(tmp_path, monkeypatch): + run_id = "run-test" + path = tmp_path / run_id / "status.json" + path.parent.mkdir(parents=True) + path.write_text(json.dumps({"runId": run_id})) + + monkeypatch.setattr(worker.app_config, "REMOTE_USER_DIR", str(tmp_path)) + monkeypatch.setattr(worker, "_STATUS_LOAD_ATTEMPTS", 2) + monkeypatch.setattr(worker, "_STATUS_LOAD_RETRY_SECONDS", 0) + volume = _AsyncVolume(lambda _: None) + + with pytest.raises(RuntimeError, match="missing or incomplete"): + await worker._load_initial_status(run_id, volume) + + +def test_api_status_write_is_atomic_and_committed(tmp_path, monkeypatch): + run_id = "run-test" + status = _queued_status(run_id) + volume = _SyncVolume() + monkeypatch.setattr(api.app_config, "REMOTE_USER_DIR", str(tmp_path)) + + api._write_status(run_id, status, volume) + + assert api._read_status(run_id, volume) == status + assert volume.commit_calls == 1 + assert volume.reload_calls == 1 + assert not (tmp_path / run_id / ".status.json.tmp").exists() diff --git a/uv.lock b/uv.lock index ae105ca..a141173 100644 --- a/uv.lock +++ b/uv.lock @@ -8,14 +8,14 @@ resolution-markers = [ [[package]] name = "ag-ui-protocol" -version = "0.1.15" +version = "0.1.19" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/57/71/96c21ae7e2fb9b610c1a90d38bd2de8b6e5b2900a63001f3882f43e519af/ag_ui_protocol-0.1.15.tar.gz", hash = "sha256:5e23c1042c7d4e364d685e68d2fb74d37c16bc83c66d270102d8eaedce56ad82", size = 6269, upload-time = "2026-04-01T15:44:33.136Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a7/10/4ad299267a7d04b89935aa99eef62979758fcf95aee9f8bb5d70c35b1be1/ag_ui_protocol-0.1.19.tar.gz", hash = "sha256:43c27f60d41712dcad0e9e0a203cbdf1c8e248b22417374c5c68321c448af4ea", size = 10720, upload-time = "2026-06-02T17:26:15.627Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/a0/a73398d30bb0f9ad70cd70426151a4a19527a7296e48a3a16a50e1d5db05/ag_ui_protocol-0.1.15-py3-none-any.whl", hash = "sha256:85cde077023ccbc37b5ce2ad953537883c262d210320f201fc2ec4e85408b06a", size = 8661, upload-time = "2026-04-01T15:44:32.079Z" }, + { url = "https://files.pythonhosted.org/packages/4c/0a/bcad8116eb058e4b4a305e3fc37ebd7efc879deeb86b854f1c5b8b6e97dd/ag_ui_protocol-0.1.19-py3-none-any.whl", hash = "sha256:898843b1410d378824da0c6a776486288b9c5828689d0bf563118868e37f390f", size = 13490, upload-time = "2026-06-02T17:26:16.313Z" }, ] [[package]] @@ -41,7 +41,7 @@ wheels = [ [[package]] name = "aiohttp" -version = "3.13.5" +version = "3.14.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohappyeyeballs" }, @@ -52,59 +52,72 @@ dependencies = [ { name = "propcache" }, { name = "yarl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/77/9a/152096d4808df8e4268befa55fba462f440f14beab85e8ad9bf990516918/aiohttp-3.13.5.tar.gz", hash = "sha256:9d98cc980ecc96be6eb4c1994ce35d28d8b1f5e5208a23b421187d1209dbb7d1", size = 7858271, upload-time = "2026-03-31T22:01:03.343Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/78/e9/d76bf503005709e390122d34e15256b88f7008e246c4bdbe915cd4f1adce/aiohttp-3.13.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5029cc80718bbd545123cd8fe5d15025eccaaaace5d0eeec6bd556ad6163d61", size = 742930, upload-time = "2026-03-31T21:58:13.155Z" }, - { url = "https://files.pythonhosted.org/packages/57/00/4b7b70223deaebd9bb85984d01a764b0d7bd6526fcdc73cca83bcbe7243e/aiohttp-3.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4bb6bf5811620003614076bdc807ef3b5e38244f9d25ca5fe888eaccea2a9832", size = 496927, upload-time = "2026-03-31T21:58:15.073Z" }, - { url = "https://files.pythonhosted.org/packages/9c/f5/0fb20fb49f8efdcdce6cd8127604ad2c503e754a8f139f5e02b01626523f/aiohttp-3.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a84792f8631bf5a94e52d9cc881c0b824ab42717165a5579c760b830d9392ac9", size = 497141, upload-time = "2026-03-31T21:58:17.009Z" }, - { url = "https://files.pythonhosted.org/packages/3b/86/b7c870053e36a94e8951b803cb5b909bfbc9b90ca941527f5fcafbf6b0fa/aiohttp-3.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:57653eac22c6a4c13eb22ecf4d673d64a12f266e72785ab1c8b8e5940d0e8090", size = 1732476, upload-time = "2026-03-31T21:58:18.925Z" }, - { url = "https://files.pythonhosted.org/packages/b5/e5/4e161f84f98d80c03a238671b4136e6530453d65262867d989bbe78244d0/aiohttp-3.13.5-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e5e5f7debc7a57af53fdf5c5009f9391d9f4c12867049d509bf7bb164a6e295b", size = 1706507, upload-time = "2026-03-31T21:58:21.094Z" }, - { url = "https://files.pythonhosted.org/packages/d4/56/ea11a9f01518bd5a2a2fcee869d248c4b8a0cfa0bb13401574fa31adf4d4/aiohttp-3.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c719f65bebcdf6716f10e9eff80d27567f7892d8988c06de12bbbd39307c6e3a", size = 1773465, upload-time = "2026-03-31T21:58:23.159Z" }, - { url = "https://files.pythonhosted.org/packages/eb/40/333ca27fb74b0383f17c90570c748f7582501507307350a79d9f9f3c6eb1/aiohttp-3.13.5-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d97f93fdae594d886c5a866636397e2bcab146fd7a132fd6bb9ce182224452f8", size = 1873523, upload-time = "2026-03-31T21:58:25.59Z" }, - { url = "https://files.pythonhosted.org/packages/f0/d2/e2f77eef1acb7111405433c707dc735e63f67a56e176e72e9e7a2cd3f493/aiohttp-3.13.5-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3df334e39d4c2f899a914f1dba283c1aadc311790733f705182998c6f7cae665", size = 1754113, upload-time = "2026-03-31T21:58:27.624Z" }, - { url = "https://files.pythonhosted.org/packages/fb/56/3f653d7f53c89669301ec9e42c95233e2a0c0a6dd051269e6e678db4fdb0/aiohttp-3.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fe6970addfea9e5e081401bcbadf865d2b6da045472f58af08427e108d618540", size = 1562351, upload-time = "2026-03-31T21:58:29.918Z" }, - { url = "https://files.pythonhosted.org/packages/ec/a6/9b3e91eb8ae791cce4ee736da02211c85c6f835f1bdfac0594a8a3b7018c/aiohttp-3.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7becdf835feff2f4f335d7477f121af787e3504b48b449ff737afb35869ba7bb", size = 1693205, upload-time = "2026-03-31T21:58:32.214Z" }, - { url = "https://files.pythonhosted.org/packages/98/fc/bfb437a99a2fcebd6b6eaec609571954de2ed424f01c352f4b5504371dd3/aiohttp-3.13.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:676e5651705ad5d8a70aeb8eb6936c436d8ebbd56e63436cb7dd9bb36d2a9a46", size = 1730618, upload-time = "2026-03-31T21:58:34.728Z" }, - { url = "https://files.pythonhosted.org/packages/e4/b6/c8534862126191a034f68153194c389addc285a0f1347d85096d349bbc15/aiohttp-3.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:9b16c653d38eb1a611cc898c41e76859ca27f119d25b53c12875fd0474ae31a8", size = 1745185, upload-time = "2026-03-31T21:58:36.909Z" }, - { url = "https://files.pythonhosted.org/packages/0b/93/4ca8ee2ef5236e2707e0fd5fecb10ce214aee1ff4ab307af9c558bda3b37/aiohttp-3.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:999802d5fa0389f58decd24b537c54aa63c01c3219ce17d1214cbda3c2b22d2d", size = 1557311, upload-time = "2026-03-31T21:58:39.38Z" }, - { url = "https://files.pythonhosted.org/packages/57/ae/76177b15f18c5f5d094f19901d284025db28eccc5ae374d1d254181d33f4/aiohttp-3.13.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ec707059ee75732b1ba130ed5f9580fe10ff75180c812bc267ded039db5128c6", size = 1773147, upload-time = "2026-03-31T21:58:41.476Z" }, - { url = "https://files.pythonhosted.org/packages/01/a4/62f05a0a98d88af59d93b7fcac564e5f18f513cb7471696ac286db970d6a/aiohttp-3.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2d6d44a5b48132053c2f6cd5c8cb14bc67e99a63594e336b0f2af81e94d5530c", size = 1730356, upload-time = "2026-03-31T21:58:44.049Z" }, - { url = "https://files.pythonhosted.org/packages/e4/85/fc8601f59dfa8c9523808281f2da571f8b4699685f9809a228adcc90838d/aiohttp-3.13.5-cp313-cp313-win32.whl", hash = "sha256:329f292ed14d38a6c4c435e465f48bebb47479fd676a0411936cc371643225cc", size = 432637, upload-time = "2026-03-31T21:58:46.167Z" }, - { url = "https://files.pythonhosted.org/packages/c0/1b/ac685a8882896acf0f6b31d689e3792199cfe7aba37969fa91da63a7fa27/aiohttp-3.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:69f571de7500e0557801c0b51f4780482c0ec5fe2ac851af5a92cfce1af1cb83", size = 458896, upload-time = "2026-03-31T21:58:48.119Z" }, - { url = "https://files.pythonhosted.org/packages/5d/ce/46572759afc859e867a5bc8ec3487315869013f59281ce61764f76d879de/aiohttp-3.13.5-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:eb4639f32fd4a9904ab8fb45bf3383ba71137f3d9d4ba25b3b3f3109977c5b8c", size = 745721, upload-time = "2026-03-31T21:58:50.229Z" }, - { url = "https://files.pythonhosted.org/packages/13/fe/8a2efd7626dbe6049b2ef8ace18ffda8a4dfcbe1bcff3ac30c0c7575c20b/aiohttp-3.13.5-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:7e5dc4311bd5ac493886c63cbf76ab579dbe4641268e7c74e48e774c74b6f2be", size = 497663, upload-time = "2026-03-31T21:58:52.232Z" }, - { url = "https://files.pythonhosted.org/packages/9b/91/cc8cc78a111826c54743d88651e1687008133c37e5ee615fee9b57990fac/aiohttp-3.13.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:756c3c304d394977519824449600adaf2be0ccee76d206ee339c5e76b70ded25", size = 499094, upload-time = "2026-03-31T21:58:54.566Z" }, - { url = "https://files.pythonhosted.org/packages/0a/33/a8362cb15cf16a3af7e86ed11962d5cd7d59b449202dc576cdc731310bde/aiohttp-3.13.5-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecc26751323224cf8186efcf7fbcbc30f4e1d8c7970659daf25ad995e4032a56", size = 1726701, upload-time = "2026-03-31T21:58:56.864Z" }, - { url = "https://files.pythonhosted.org/packages/45/0c/c091ac5c3a17114bd76cbf85d674650969ddf93387876cf67f754204bd77/aiohttp-3.13.5-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:10a75acfcf794edf9d8db50e5a7ec5fc818b2a8d3f591ce93bc7b1210df016d2", size = 1683360, upload-time = "2026-03-31T21:58:59.072Z" }, - { url = "https://files.pythonhosted.org/packages/23/73/bcee1c2b79bc275e964d1446c55c54441a461938e70267c86afaae6fba27/aiohttp-3.13.5-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:0f7a18f258d124cd678c5fe072fe4432a4d5232b0657fca7c1847f599233c83a", size = 1773023, upload-time = "2026-03-31T21:59:01.776Z" }, - { url = "https://files.pythonhosted.org/packages/c7/ef/720e639df03004fee2d869f771799d8c23046dec47d5b81e396c7cda583a/aiohttp-3.13.5-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:df6104c009713d3a89621096f3e3e88cc323fd269dbd7c20afe18535094320be", size = 1853795, upload-time = "2026-03-31T21:59:04.568Z" }, - { url = "https://files.pythonhosted.org/packages/bd/c9/989f4034fb46841208de7aeeac2c6d8300745ab4f28c42f629ba77c2d916/aiohttp-3.13.5-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:241a94f7de7c0c3b616627aaad530fe2cb620084a8b144d3be7b6ecfe95bae3b", size = 1730405, upload-time = "2026-03-31T21:59:07.221Z" }, - { url = "https://files.pythonhosted.org/packages/ce/75/ee1fd286ca7dc599d824b5651dad7b3be7ff8d9a7e7b3fe9820d9180f7db/aiohttp-3.13.5-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c974fb66180e58709b6fc402846f13791240d180b74de81d23913abe48e96d94", size = 1558082, upload-time = "2026-03-31T21:59:09.484Z" }, - { url = "https://files.pythonhosted.org/packages/c3/20/1e9e6650dfc436340116b7aa89ff8cb2bbdf0abc11dfaceaad8f74273a10/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:6e27ea05d184afac78aabbac667450c75e54e35f62238d44463131bd3f96753d", size = 1692346, upload-time = "2026-03-31T21:59:12.068Z" }, - { url = "https://files.pythonhosted.org/packages/d8/40/8ebc6658d48ea630ac7903912fe0dd4e262f0e16825aa4c833c56c9f1f56/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:a79a6d399cef33a11b6f004c67bb07741d91f2be01b8d712d52c75711b1e07c7", size = 1698891, upload-time = "2026-03-31T21:59:14.552Z" }, - { url = "https://files.pythonhosted.org/packages/d8/78/ea0ae5ec8ba7a5c10bdd6e318f1ba5e76fcde17db8275188772afc7917a4/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c632ce9c0b534fbe25b52c974515ed674937c5b99f549a92127c85f771a78772", size = 1742113, upload-time = "2026-03-31T21:59:17.068Z" }, - { url = "https://files.pythonhosted.org/packages/8a/66/9d308ed71e3f2491be1acb8769d96c6f0c47d92099f3bc9119cada27b357/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:fceedde51fbd67ee2bcc8c0b33d0126cc8b51ef3bbde2f86662bd6d5a6f10ec5", size = 1553088, upload-time = "2026-03-31T21:59:19.541Z" }, - { url = "https://files.pythonhosted.org/packages/da/a6/6cc25ed8dfc6e00c90f5c6d126a98e2cf28957ad06fa1036bd34b6f24a2c/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f92995dfec9420bb69ae629abf422e516923ba79ba4403bc750d94fb4a6c68c1", size = 1757976, upload-time = "2026-03-31T21:59:22.311Z" }, - { url = "https://files.pythonhosted.org/packages/c1/2b/cce5b0ffe0de99c83e5e36d8f828e4161e415660a9f3e58339d07cce3006/aiohttp-3.13.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20ae0ff08b1f2c8788d6fb85afcb798654ae6ba0b747575f8562de738078457b", size = 1712444, upload-time = "2026-03-31T21:59:24.635Z" }, - { url = "https://files.pythonhosted.org/packages/6c/cf/9e1795b4160c58d29421eafd1a69c6ce351e2f7c8d3c6b7e4ca44aea1a5b/aiohttp-3.13.5-cp314-cp314-win32.whl", hash = "sha256:b20df693de16f42b2472a9c485e1c948ee55524786a0a34345511afdd22246f3", size = 438128, upload-time = "2026-03-31T21:59:27.291Z" }, - { url = "https://files.pythonhosted.org/packages/22/4d/eaedff67fc805aeba4ba746aec891b4b24cebb1a7d078084b6300f79d063/aiohttp-3.13.5-cp314-cp314-win_amd64.whl", hash = "sha256:f85c6f327bf0b8c29da7d93b1cabb6363fb5e4e160a32fa241ed2dce21b73162", size = 464029, upload-time = "2026-03-31T21:59:29.429Z" }, - { url = "https://files.pythonhosted.org/packages/79/11/c27d9332ee20d68dd164dc12a6ecdef2e2e35ecc97ed6cf0d2442844624b/aiohttp-3.13.5-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:1efb06900858bb618ff5cee184ae2de5828896c448403d51fb633f09e109be0a", size = 778758, upload-time = "2026-03-31T21:59:31.547Z" }, - { url = "https://files.pythonhosted.org/packages/04/fb/377aead2e0a3ba5f09b7624f702a964bdf4f08b5b6728a9799830c80041e/aiohttp-3.13.5-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:fee86b7c4bd29bdaf0d53d14739b08a106fdda809ca5fe032a15f52fae5fe254", size = 512883, upload-time = "2026-03-31T21:59:34.098Z" }, - { url = "https://files.pythonhosted.org/packages/bb/a6/aa109a33671f7a5d3bd78b46da9d852797c5e665bfda7d6b373f56bff2ec/aiohttp-3.13.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:20058e23909b9e65f9da62b396b77dfa95965cbe840f8def6e572538b1d32e36", size = 516668, upload-time = "2026-03-31T21:59:36.497Z" }, - { url = "https://files.pythonhosted.org/packages/79/b3/ca078f9f2fa9563c36fb8ef89053ea2bb146d6f792c5104574d49d8acb63/aiohttp-3.13.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cf20a8d6868cb15a73cab329ffc07291ba8c22b1b88176026106ae39aa6df0f", size = 1883461, upload-time = "2026-03-31T21:59:38.723Z" }, - { url = "https://files.pythonhosted.org/packages/b7/e3/a7ad633ca1ca497b852233a3cce6906a56c3225fb6d9217b5e5e60b7419d/aiohttp-3.13.5-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:330f5da04c987f1d5bdb8ae189137c77139f36bd1cb23779ca1a354a4b027800", size = 1747661, upload-time = "2026-03-31T21:59:41.187Z" }, - { url = "https://files.pythonhosted.org/packages/33/b9/cd6fe579bed34a906d3d783fe60f2fa297ef55b27bb4538438ee49d4dc41/aiohttp-3.13.5-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f1cbf0c7926d315c3c26c2da41fd2b5d2fe01ac0e157b78caefc51a782196cf", size = 1863800, upload-time = "2026-03-31T21:59:43.84Z" }, - { url = "https://files.pythonhosted.org/packages/c0/3f/2c1e2f5144cefa889c8afd5cf431994c32f3b29da9961698ff4e3811b79a/aiohttp-3.13.5-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:53fc049ed6390d05423ba33103ded7281fe897cf97878f369a527070bd95795b", size = 1958382, upload-time = "2026-03-31T21:59:46.187Z" }, - { url = "https://files.pythonhosted.org/packages/66/1d/f31ec3f1013723b3babe3609e7f119c2c2fb6ef33da90061a705ef3e1bc8/aiohttp-3.13.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:898703aa2667e3c5ca4c54ca36cd73f58b7a38ef87a5606414799ebce4d3fd3a", size = 1803724, upload-time = "2026-03-31T21:59:48.656Z" }, - { url = "https://files.pythonhosted.org/packages/0e/b4/57712dfc6f1542f067daa81eb61da282fab3e6f1966fca25db06c4fc62d5/aiohttp-3.13.5-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0494a01ca9584eea1e5fbd6d748e61ecff218c51b576ee1999c23db7066417d8", size = 1640027, upload-time = "2026-03-31T21:59:51.284Z" }, - { url = "https://files.pythonhosted.org/packages/25/3c/734c878fb43ec083d8e31bf029daae1beafeae582d1b35da234739e82ee7/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6cf81fe010b8c17b09495cbd15c1d35afbc8fb405c0c9cf4738e5ae3af1d65be", size = 1806644, upload-time = "2026-03-31T21:59:53.753Z" }, - { url = "https://files.pythonhosted.org/packages/20/a5/f671e5cbec1c21d044ff3078223f949748f3a7f86b14e34a365d74a5d21f/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:c564dd5f09ddc9d8f2c2d0a301cd30a79a2cc1b46dd1a73bef8f0038863d016b", size = 1791630, upload-time = "2026-03-31T21:59:56.239Z" }, - { url = "https://files.pythonhosted.org/packages/0b/63/fb8d0ad63a0b8a99be97deac8c04dacf0785721c158bdf23d679a87aa99e/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:2994be9f6e51046c4f864598fd9abeb4fba6e88f0b2152422c9666dcd4aea9c6", size = 1809403, upload-time = "2026-03-31T21:59:59.103Z" }, - { url = "https://files.pythonhosted.org/packages/59/0c/bfed7f30662fcf12206481c2aac57dedee43fe1c49275e85b3a1e1742294/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:157826e2fa245d2ef46c83ea8a5faf77ca19355d278d425c29fda0beb3318037", size = 1634924, upload-time = "2026-03-31T22:00:02.116Z" }, - { url = "https://files.pythonhosted.org/packages/17/d6/fd518d668a09fd5a3319ae5e984d4d80b9a4b3df4e21c52f02251ef5a32e/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:a8aca50daa9493e9e13c0f566201a9006f080e7c50e5e90d0b06f53146a54500", size = 1836119, upload-time = "2026-03-31T22:00:04.756Z" }, - { url = "https://files.pythonhosted.org/packages/78/b7/15fb7a9d52e112a25b621c67b69c167805cb1f2ab8f1708a5c490d1b52fe/aiohttp-3.13.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3b13560160d07e047a93f23aaa30718606493036253d5430887514715b67c9d9", size = 1772072, upload-time = "2026-03-31T22:00:07.494Z" }, - { url = "https://files.pythonhosted.org/packages/7e/df/57ba7f0c4a553fc2bd8b6321df236870ec6fd64a2a473a8a13d4f733214e/aiohttp-3.13.5-cp314-cp314t-win32.whl", hash = "sha256:9a0f4474b6ea6818b41f82172d799e4b3d29e22c2c520ce4357856fced9af2f8", size = 471819, upload-time = "2026-03-31T22:00:10.277Z" }, - { url = "https://files.pythonhosted.org/packages/62/29/2f8418269e46454a26171bfdd6a055d74febf32234e474930f2f60a17145/aiohttp-3.13.5-cp314-cp314t-win_amd64.whl", hash = "sha256:18a2f6c1182c51baa1d28d68fea51513cb2a76612f038853c0ad3c145423d3d9", size = 505441, upload-time = "2026-03-31T22:00:12.791Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/82/78/8ea7308cac6934de8c74a14f3d5f65d1c89287426688be79538d0e5c013d/aiohttp-3.14.1.tar.gz", hash = "sha256:307f2cff90a764d329e77040603fa032db89c5c24fdad50c4c15334cba744035", size = 7955794, upload-time = "2026-06-07T21:09:35.529Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/97/bd137012dd97e1649162b099135a80e1fd59aaa807b2430fc448d1029aff/aiohttp-3.14.1-cp313-cp313-android_21_arm64_v8a.whl", hash = "sha256:b3a03285a7f9c7b016324574a6d92a1c895da6b978cb8f1deee3ac72bc6da178", size = 506882, upload-time = "2026-06-07T21:07:15.501Z" }, + { url = "https://files.pythonhosted.org/packages/ef/79/e5cc690e9d922a66887ceeaca53a8ffd5a7b0be3816142b7abc433742d89/aiohttp-3.14.1-cp313-cp313-android_21_x86_64.whl", hash = "sha256:2a73f487ab8ef5abbb24b7aa9b73e98eaba9e9e031804ff2416f02eca315ccaf", size = 515270, upload-time = "2026-06-07T21:07:17.53Z" }, + { url = "https://files.pythonhosted.org/packages/fe/22/a73ccbf9dbd6e26dda0b24d5fd5db7da92ee3383a79f47677ffb834c5c5b/aiohttp-3.14.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:915fbb7b41b115192259f8c9ae58f3ddc444d2b5579917270211858e606a4afd", size = 485841, upload-time = "2026-06-07T21:07:19.555Z" }, + { url = "https://files.pythonhosted.org/packages/3b/b9/57ed8eaf596321c2ad747bd480fb1700dbd7177c60dfc9e4c187f629662e/aiohttp-3.14.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:7fb4bdf95b0561a79f259f9d28fbc109728c5ee7f27aff6391f0ca703a329abe", size = 492088, upload-time = "2026-06-07T21:07:21.581Z" }, + { url = "https://files.pythonhosted.org/packages/78/c0/5ebe5270a7c140d7c6f79dcb018640225f14d406c149e4eec04a7d82fe71/aiohttp-3.14.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:1b9748363260121d2927704f5d4fc498150669ca3ae93625986ee89c8f80dcd4", size = 501564, upload-time = "2026-06-07T21:07:23.388Z" }, + { url = "https://files.pythonhosted.org/packages/75/7f/8cdaa24fc7983865e0915153b96a9ac5bcdd3548d64c5a27d17cecccad2d/aiohttp-3.14.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:86a6dab78b0e43e2897a3bbe15745aa60dc5423ca437b7b0b164c069bf91b876", size = 751998, upload-time = "2026-06-07T21:07:25.046Z" }, + { url = "https://files.pythonhosted.org/packages/b2/f4/c4227aacfacc5cb0cc2d119b65301d177912a6842cd64e120c47af76064f/aiohttp-3.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4dfd6e47d3c44c2279907607f73a4240b88c69eb8b90da7e2441a8045dfd21da", size = 510918, upload-time = "2026-06-07T21:07:27.28Z" }, + { url = "https://files.pythonhosted.org/packages/ab/01/a2d5f96cd4e74424864d30bc0a7e44d0a12dacdcfa91b5b2d1bd3dca6bf3/aiohttp-3.14.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:317acd9f8602858dc7d59679812c376c7f0b97bcbbf16e0d6237f54141d8a8a6", size = 508657, upload-time = "2026-06-07T21:07:29.252Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ed/3c0fb5c500fdd8e7ebc10d1889c04384fffa1a9163eac1356088ca9da1b1/aiohttp-3.14.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd869c427324e5cb15195793de951295710db28be7d818247f3097b4ab5d4b96", size = 1757907, upload-time = "2026-06-07T21:07:31.03Z" }, + { url = "https://files.pythonhosted.org/packages/0b/ab/d4c924d9bd5be3050c226612413ce68cb54c70d2c31b661bfc8d9a5b6a70/aiohttp-3.14.1-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:93b032b5ec3255473c143627d21a69ac74ae12f7f33974cb587c564d11b1066f", size = 1737565, upload-time = "2026-06-07T21:07:33.031Z" }, + { url = "https://files.pythonhosted.org/packages/19/2a/37326821ff779084020cdc33224d20b19f42f4183a500ff92022a739eda7/aiohttp-3.14.1-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f234b4deb12f3ad59127e037bc57c40c21e45b45282df7d3a55a0f409f595296", size = 1799018, upload-time = "2026-06-07T21:07:35.003Z" }, + { url = "https://files.pythonhosted.org/packages/b3/4f/6e947ba73e4ce09070761c05ed3a8ceb7c21f5e46798671d8b2aac0e4626/aiohttp-3.14.1-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:9af6779bfb46abf124068327abcdf9ce95c9ef8287a3e8da76ccf2d0f16c28fa", size = 1894416, upload-time = "2026-06-07T21:07:36.956Z" }, + { url = "https://files.pythonhosted.org/packages/9d/6e/dbf1d0625dc711fb2851f4f3c3055c39ed58bae92082d8c627dbe6013736/aiohttp-3.14.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:faccab372e66bc76d5731525e7f1143c922271725b9d38c9f97edcc66266b451", size = 1783881, upload-time = "2026-06-07T21:07:39.063Z" }, + { url = "https://files.pythonhosted.org/packages/44/c2/5e25098a67268ed369483ae7d1a58bd0a13d03aab860d2a0e4a6eb25b046/aiohttp-3.14.1-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f380468b09d2a81633ee863b0ec5648d364bd17bb8ecfb8c2f387f7ac1faf42c", size = 1587572, upload-time = "2026-06-07T21:07:41.058Z" }, + { url = "https://files.pythonhosted.org/packages/2a/bd/cf9cee17e140f942a3de73e658a543aa8fbf35a5fc67a9d2538d52d77f0b/aiohttp-3.14.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:97e704dcd26271f5bda3fa07c3ce0fb76d6d3f8659f4baa1a24442cc9ba177ca", size = 1722137, upload-time = "2026-06-07T21:07:43.014Z" }, + { url = "https://files.pythonhosted.org/packages/89/6d/5684f8c59045c96f81a18cefbc1fbbd79d25b88f1c622f2a5c5c08fcb632/aiohttp-3.14.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:269b76ac5394092b95bc4a098f4fc6c191c083c3bd12775d1e30e663132f6a09", size = 1755953, upload-time = "2026-06-07T21:07:45.933Z" }, + { url = "https://files.pythonhosted.org/packages/a8/40/35caf3170f8359760740a7d9aa0fff2e344bef98e1d1186f5a0f6dec17e6/aiohttp-3.14.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c0b3e614340c889d575451696374c9d17affd54cd607ca0babed8f8c37b9397", size = 1766479, upload-time = "2026-06-07T21:07:48.047Z" }, + { url = "https://files.pythonhosted.org/packages/6d/a1/b0c61e7a137f0d81de49a82023a6df73c3c16d6fefb0f8e4a93d21639002/aiohttp-3.14.1-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:5663ee9257cfa1add7253a7da3035a02f31b6600ec48261585e1800a81533080", size = 1580077, upload-time = "2026-06-07T21:07:50.069Z" }, + { url = "https://files.pythonhosted.org/packages/0b/41/194ea4623693009fcefebef7aef63c141754f153e9cd0d39d3b9e36c175c/aiohttp-3.14.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:603a2c834142172ffddc054067f5ec0ca65d57a0aa98a71bc81952573208e345", size = 1791688, upload-time = "2026-06-07T21:07:52.106Z" }, + { url = "https://files.pythonhosted.org/packages/ba/45/4de841f005cfe1fd63e2a2fe011262c515e2a62aa6994b15947e7d717ac9/aiohttp-3.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cb21957bb8aca671c1765e32f58164cf0c50e6bf41c0bbbd16da20732ecaf588", size = 1761094, upload-time = "2026-06-07T21:07:54.113Z" }, + { url = "https://files.pythonhosted.org/packages/e4/ae/dbce10533d3896d544d5053939ed75b7dc31a1b0973d959b1b5ae21028d6/aiohttp-3.14.1-cp313-cp313-win32.whl", hash = "sha256:e509a55f681e6158c20f70f102f9cf61fb20fbc382272bc6d94b7343f2582780", size = 452662, upload-time = "2026-06-07T21:07:56.06Z" }, + { url = "https://files.pythonhosted.org/packages/7b/d9/0bf1a19362c32f06229da5e7ddfcec91f93474d6307f7a2d3135e9c674dc/aiohttp-3.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:1ac8531b638959718e18c2207fbfe297819875da46a740b29dfa29beba64355a", size = 479748, upload-time = "2026-06-07T21:07:58.319Z" }, + { url = "https://files.pythonhosted.org/packages/22/0a/62e7232dc9484fbec112ceb32efb6a624cc7994ec6e2b019286f17c4e8f2/aiohttp-3.14.1-cp313-cp313-win_arm64.whl", hash = "sha256:250d14af67f6b6a1a4a811049b1afa69d61d617fca6bf33149b3ab1a6dbcf7b8", size = 447723, upload-time = "2026-06-07T21:08:00.154Z" }, + { url = "https://files.pythonhosted.org/packages/c4/a1/5fafa04e1ca91ddb47608699d60649c1c6db3cf41c99e78fc4056f9513db/aiohttp-3.14.1-cp314-cp314-android_24_arm64_v8a.whl", hash = "sha256:7c106c26852ca1c2047c6b80384f17100b4e439af276f21ef3d4e2f450ae7e15", size = 508531, upload-time = "2026-06-07T21:08:02.093Z" }, + { url = "https://files.pythonhosted.org/packages/fa/2e/bfa02f699d87ffc86d5959270b28f1cb410add3ccaced8ed2e0b8a5238fc/aiohttp-3.14.1-cp314-cp314-android_24_x86_64.whl", hash = "sha256:20205f7f5ade7aaec9f4b500549bbc071b046453aed72f9c06dcab87896a83e8", size = 514718, upload-time = "2026-06-07T21:08:04.476Z" }, + { url = "https://files.pythonhosted.org/packages/85/a5/9594ad6289eebbc97d167c44213d557807f90e59115caad24de21ad2c3b1/aiohttp-3.14.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:62a759436b29e677181a9e76bab8b8f689a29cb9c535f45f7c48c9c830d3f8c3", size = 487918, upload-time = "2026-06-07T21:08:06.377Z" }, + { url = "https://files.pythonhosted.org/packages/b4/61/16a32c36c3c49edec122a3dc811f2057df2f94d3b14aa107c8017d981618/aiohttp-3.14.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:2964cbf553df4d7a57348da44d961d871895fc1ee4e8c322b2a95612c7b17fba", size = 494014, upload-time = "2026-06-07T21:08:08.263Z" }, + { url = "https://files.pythonhosted.org/packages/9b/89/3ebcf96ed99c05bec9c434aaac6963fd3cbab4a786ae739908a144d9ce44/aiohttp-3.14.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:237651caadc3a59badd39319c54642b5299e9cc98a3a194310e55d5bb9f5e397", size = 502398, upload-time = "2026-06-07T21:08:10.244Z" }, + { url = "https://files.pythonhosted.org/packages/fd/3d/b74870a0c2d40c355928cd5b96c7a11fa821b8a40fc41365e64479b151fb/aiohttp-3.14.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:896e12dfdbbab9d8f7e16d2b28c6769a60126fa92095d1ebf9473d02593a2448", size = 758018, upload-time = "2026-06-07T21:08:12.447Z" }, + { url = "https://files.pythonhosted.org/packages/d3/66/f42f5c984d99e49c6cff5f26f590750f2e2f7ef1fcfb99966ab5be1b632e/aiohttp-3.14.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:d03f281ed22579314ba00821ce20115a7c0ac430660b4cc05704a3f818b3e004", size = 512462, upload-time = "2026-06-07T21:08:14.624Z" }, + { url = "https://files.pythonhosted.org/packages/e9/a7/248e1aebe0c7810b0271e021a0f2a5eb6e78a051885b3c9df49f42a5802d/aiohttp-3.14.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:07eabb979d236335fed927e137a928c9adfb7df3b9ec7aa31726f133a62be983", size = 512824, upload-time = "2026-06-07T21:08:16.572Z" }, + { url = "https://files.pythonhosted.org/packages/26/97/2aa0e5ba0727dc3bd5aaebb7ccbc510f7dfb7fb961ec87497cd496635ab1/aiohttp-3.14.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4fe1f1087cbadb280b5e1bb054a4f00d1423c74d6626c5e48400d871d34ecefe", size = 1749898, upload-time = "2026-06-07T21:08:18.635Z" }, + { url = "https://files.pythonhosted.org/packages/00/8d/e97f6c96c891d457c8479d92a514ba194d0412f981d72c70341ee18488ed/aiohttp-3.14.1-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:367a9314fdc79dab0fac96e216cb41dd73c85bdca85306ce8999118ba7e0f333", size = 1710114, upload-time = "2026-06-07T21:08:20.892Z" }, + { url = "https://files.pythonhosted.org/packages/6f/e6/aa8d7e863048c8fceb5cd6ce74017311cec3ead07847387e12265fb4444e/aiohttp-3.14.1-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a24f677ebe83749039e7bdf862ff0bbb16818ae4193d4ef96505e269375bcce0", size = 1802541, upload-time = "2026-06-07T21:08:23.044Z" }, + { url = "https://files.pythonhosted.org/packages/83/a8/72193137de57fda4ebfae4563182d082c8856e3b6e9871d0b46f028fb369/aiohttp-3.14.1-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c83afe0ba876be7e943d2e0ba645809ad441575d2840c895c21ee5de93b9377a", size = 1875776, upload-time = "2026-06-07T21:08:25.288Z" }, + { url = "https://files.pythonhosted.org/packages/a0/18/938441025db6769a3464596b2410af3afde0b21eb2f204c6f766f68af4bd/aiohttp-3.14.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:634e385930fb6d2d479cf3aa66515955863b77a5e3c2b5894ca259a25b308602", size = 1760329, upload-time = "2026-06-07T21:08:27.363Z" }, + { url = "https://files.pythonhosted.org/packages/60/29/bf2496b4065e76e09fe48015aaffe5ce161d8f089b06ac6982070f653076/aiohttp-3.14.1-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eeea07c4397bbc57719c4eed8f9c284874d4f175f9b6d57f7a1546b976d455ca", size = 1587293, upload-time = "2026-06-07T21:08:29.805Z" }, + { url = "https://files.pythonhosted.org/packages/49/a2/2136674d52123b1354bd05dd5753c318db47dc0c927cc70b27bab3755456/aiohttp-3.14.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:335c0cc3e3545ce98dcb9cfcb836f40c3411f43fa03dab757597d80c89af8a35", size = 1714756, upload-time = "2026-06-07T21:08:32.094Z" }, + { url = "https://files.pythonhosted.org/packages/a7/b9/e5fd2e6f915503081c0f9b1e8540947037929c70c191da2e4d54b31a21a1/aiohttp-3.14.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:ae6be797afdef264e8a84864a85b196ca06045586481b3df8a967322fd2fa844", size = 1721052, upload-time = "2026-06-07T21:08:34.167Z" }, + { url = "https://files.pythonhosted.org/packages/63/5a/2833e324a2263e104e31e2e91bc5bbee81bc499afd32203faee048a883f0/aiohttp-3.14.1-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:8560b4d712474335d08907db7973f71912d3a9a8f1dee992ec06b5d2fe359496", size = 1766888, upload-time = "2026-06-07T21:08:36.95Z" }, + { url = "https://files.pythonhosted.org/packages/57/fa/dea6511870913162f3b2e8c42a7614eb203a4540b8c2da43e0bfb0548f3c/aiohttp-3.14.1-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7edd08e0a5deb1e8564a2fcd8f4561014a3f05252334671bbf55ddd47db0e5", size = 1581679, upload-time = "2026-06-07T21:08:39.292Z" }, + { url = "https://files.pythonhosted.org/packages/14/bd/3cf0d55e71784b33534e9710a67d382d900598b4787fbce6cc7317f8c42a/aiohttp-3.14.1-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:b6ff7fcee63287ae57b5df3e4f5957ce032122802509246dec1a5bcc55904c95", size = 1782021, upload-time = "2026-06-07T21:08:41.407Z" }, + { url = "https://files.pythonhosted.org/packages/c1/af/14bb5843eccbe234f4dfb78ab73e549d99727247e62ae5d62cbd22eaf5b0/aiohttp-3.14.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6ffbb2f4ec1ceaff7e07d43922954da26b223d188bf30658e561b98e23089444", size = 1742574, upload-time = "2026-06-07T21:08:43.795Z" }, + { url = "https://files.pythonhosted.org/packages/f2/1e/fbeb7af9210a67ac0f9c9bec0f8f4568497924e33137a3d5b48e1cf85f3f/aiohttp-3.14.1-cp314-cp314-win32.whl", hash = "sha256:a9875b46d910cff3ea2f5962f9d266b465459fe634e22556ab9bd6fc1192eea0", size = 457773, upload-time = "2026-06-07T21:08:46.168Z" }, + { url = "https://files.pythonhosted.org/packages/f0/2b/13e8d741a9ec5db7d900c060554cf8352ab85e44e2a4469ebb9d377bda17/aiohttp-3.14.1-cp314-cp314-win_amd64.whl", hash = "sha256:af8b4b81a960eeaf1234971ac3cd0ba5901f3cd42eae42a46b4d089a8b492719", size = 485001, upload-time = "2026-06-07T21:08:48.401Z" }, + { url = "https://files.pythonhosted.org/packages/df/30/491acfa2c4d6c3ff59c49a14fc1b50be3241e25bbb0c84c09e2da4d11395/aiohttp-3.14.1-cp314-cp314-win_arm64.whl", hash = "sha256:cf4491381b1b57425c315a56a439251b1bdac07b2275f19a8c44bc57744532ec", size = 453809, upload-time = "2026-06-07T21:08:50.7Z" }, + { url = "https://files.pythonhosted.org/packages/34/e3/19dbe1a1f4cc6230eb9e314de7fe68053b0992f9302b27d12141a0b5db53/aiohttp-3.14.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:819c054312f1af92947e6a55883d1b66feefab11531a7fc45e0fb9b63880b5c2", size = 793320, upload-time = "2026-06-07T21:08:52.775Z" }, + { url = "https://files.pythonhosted.org/packages/7f/20/1b7182219ba1b108430d6e4dc53d25ae02dcfcf5a045b33af4e8c5167527/aiohttp-3.14.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:10ee9c1753a8f706345b22496c79fbddb5be0599e0823f3738b1534058e25340", size = 529077, upload-time = "2026-06-07T21:08:55Z" }, + { url = "https://files.pythonhosted.org/packages/b9/c8/14ce60ec31a2e5f5274bb17d383a6f7a3aabca31ac04eee05585bbadab16/aiohttp-3.14.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1601cc37baf5750ccacae618ec2daf020769581695550e3b654a911f859c563d", size = 532476, upload-time = "2026-06-07T21:08:57.176Z" }, + { url = "https://files.pythonhosted.org/packages/7e/02/9ac85e081e53da2e061b02fa7758fe0a12d17b8ce2d1f5e6c7cb76730328/aiohttp-3.14.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4d6e0ac9da31c9c04c84e1c0182ad8d6df35965a85cae29cd71d089621b3ae94", size = 1922347, upload-time = "2026-06-07T21:08:59.563Z" }, + { url = "https://files.pythonhosted.org/packages/c0/3e/d3ba07a0ab38b5389e10bec4362d21e10a4f667cba2d79ba30837b3a5059/aiohttp-3.14.1-cp314-cp314t-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:9e8f2d660c350b3d0e259c7a7e3d9b7fc8b41210cbcc3d4a7076ff0a5e5c2fdc", size = 1786465, upload-time = "2026-06-07T21:09:01.909Z" }, + { url = "https://files.pythonhosted.org/packages/0b/cb/e2ee978a00cfb2df829704a69528b18154eba5939f45bc1efa8f33aee4c5/aiohttp-3.14.1-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4691802dda97be727f79d86818acaad7eb8e9252626a1d6b519fedbb92d5e251", size = 1909423, upload-time = "2026-06-07T21:09:04.357Z" }, + { url = "https://files.pythonhosted.org/packages/73/5d/1430334858b1022b58ae50399a918f0bd6fe8fa7fa183598d657ff61e040/aiohttp-3.14.1-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c389c482a7e9b9dc3ee2701ac46c4125297a3818875b9c305ddb603c04828fd1", size = 2001906, upload-time = "2026-06-07T21:09:06.722Z" }, + { url = "https://files.pythonhosted.org/packages/66/4e/560c7472d3d198a23aa5c8b19a5115bf6a9b77b7d3e4bb363da320430ad2/aiohttp-3.14.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fc0cacab7ba4e56f0f81c82a98c09bed2f39c940107b03a34b168bdf7597edd3", size = 1877095, upload-time = "2026-06-07T21:09:09.011Z" }, + { url = "https://files.pythonhosted.org/packages/0d/f1/4745806578d447db4a784a8591e2dae3afdfc2bcb96f8f81271b13df6543/aiohttp-3.14.1-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:979ed4717f59b8bb12e3963378fa285d93d367e15bcd66c721311826d3c44a6c", size = 1676222, upload-time = "2026-06-07T21:09:11.461Z" }, + { url = "https://files.pythonhosted.org/packages/6a/c9/48255813cca749a229ef0ab476004ec623728ad79a9c0840616f6c076325/aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:38e1e7daaea81df51c952e18483f323d878499a1e2bfe564790e0f9701d6f203", size = 1842922, upload-time = "2026-06-07T21:09:14.118Z" }, + { url = "https://files.pythonhosted.org/packages/3d/c0/bbd054e2bee909f529523a5af3891052606af5143c09f5f183ec3b234676/aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:4132e72c608fe9fecb8f409113567605915b83e9bdd3ea56538d2f9cd35002f1", size = 1825035, upload-time = "2026-06-07T21:09:16.447Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ae/90395d4376deceb74e09ec26b6adf7d2015a6f8802d6d84446af860fef04/aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:eefd9cc9b6d4a2db5f00a26bc3e4f9acf71926a6ec557cd56c9c6f27c290b665", size = 1849512, upload-time = "2026-06-07T21:09:18.742Z" }, + { url = "https://files.pythonhosted.org/packages/93/bd/fb25f3049957553d4ce0ba6ae480aa2f592a6985497fca590837d16c1be0/aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:b165790117eea512d7f3fb22f1f6dad3d55a7189571993eb015591c1401276d1", size = 1668571, upload-time = "2026-06-07T21:09:21.458Z" }, + { url = "https://files.pythonhosted.org/packages/3f/22/7f73303d64dd567ff3addca90b556690ed1233a47b8f55d242fb90af3681/aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:ed09c7eb1c391271c2ed0314a51903e72a3acb653d5ccfc264cdf3ef11f8269d", size = 1881159, upload-time = "2026-06-07T21:09:23.813Z" }, + { url = "https://files.pythonhosted.org/packages/44/be/0474c5a8b5640e1e4aa1923430a91f4151be82e511373fe764189b89aef5/aiohttp-3.14.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:99abd37084b82f5830c635fddd0b4993b9742a66eb746dacf433c8590e8f9e3c", size = 1841409, upload-time = "2026-06-07T21:09:26.207Z" }, + { url = "https://files.pythonhosted.org/packages/7b/3c/bb4a7cba26956cb3da4553cc2056cf67be5b5ff6e6d8fa4fbdff73bfb7ae/aiohttp-3.14.1-cp314-cp314t-win32.whl", hash = "sha256:47ddf841cdecc810749921d25606dee45857d12d2ad5ddb7b5bd7eab12e4b365", size = 494166, upload-time = "2026-06-07T21:09:28.505Z" }, + { url = "https://files.pythonhosted.org/packages/8a/84/ec80c2c1f66a952555a9f86df6b33af65108a6febfa0471b69013a12f807/aiohttp-3.14.1-cp314-cp314t-win_amd64.whl", hash = "sha256:5e78b522b7a6e27e0b25d19b247b75039ac4c94f99823e3c9e53ae1603a9f7e9", size = 530255, upload-time = "2026-06-07T21:09:30.843Z" }, + { url = "https://files.pythonhosted.org/packages/2a/71/6e22be134a4061ada85a92951b842f2657f17d926b727f3f94c56ae963d6/aiohttp-3.14.1-cp314-cp314t-win_arm64.whl", hash = "sha256:90d53f1609c29ccc2193945ef732428382a28f78d0456ae4d3daf0d48b74f0f6", size = 469640, upload-time = "2026-06-07T21:09:33.028Z" }, ] [[package]] @@ -159,7 +172,7 @@ wheels = [ [[package]] name = "anthropic" -version = "0.96.0" +version = "0.116.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -171,9 +184,9 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b9/7e/672f533dee813028d2c699bfd2a7f52c9118d7353680d9aa44b9e23f717f/anthropic-0.96.0.tar.gz", hash = "sha256:9de947b737f39452f68aa520f1c2239d44119c9b73b0fb6d4e6ca80f00279ee6", size = 658210, upload-time = "2026-04-16T14:28:02.846Z" } +sdist = { url = "https://files.pythonhosted.org/packages/66/a2/d31f14e28d49bae983a3634e38dfb4b31c50110b5e403596c5c6a20b23f8/anthropic-0.116.0.tar.gz", hash = "sha256:5fc248fbb9fe03ef686f8a774f81586bca31a043260aab88b387ea3660f4a396", size = 949149, upload-time = "2026-07-02T19:08:10.534Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/48/5a/72f33204064b6e87601a71a6baf8d855769f8a0c1eaae8d06a1094872371/anthropic-0.96.0-py3-none-any.whl", hash = "sha256:9a6e335a354602a521cd9e777e92bfd46ba6e115bf9bbfe6135311e8fb2015b2", size = 635930, upload-time = "2026-04-16T14:28:01.436Z" }, + { url = "https://files.pythonhosted.org/packages/c7/dd/2a1e81cf1b163acc340afc4ec74ed1d86f5eed1a809fabdeed3e0997b346/anthropic-0.116.0-py3-none-any.whl", hash = "sha256:6c0a7698e8d652455da3499978279bb2588c7264d0a35be3666009a4258c8256", size = 956896, upload-time = "2026-07-02T19:08:08.756Z" }, ] [[package]] @@ -309,14 +322,15 @@ wheels = [ [[package]] name = "authlib" -version = "1.6.11" +version = "1.7.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cryptography" }, + { name = "joserfc" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/28/10/b325d58ffe86815b399334a101e63bc6fa4e1953921cb23703b48a0a0220/authlib-1.6.11.tar.gz", hash = "sha256:64db35b9b01aeccb4715a6c9a6613a06f2bd7be2ab9d2eb89edd1dfc7580a38f", size = 165359, upload-time = "2026-04-16T07:22:50.279Z" } +sdist = { url = "https://files.pythonhosted.org/packages/36/98/7d93f30d029643c0275dbc0bd6d5a6f670661ee6c9a94d93af7ab4887600/authlib-1.7.2.tar.gz", hash = "sha256:2cea25fefcd4e7173bdf1372c0afc265c8034b23a8cd5dcb6a9164b826c64231", size = 176511, upload-time = "2026-05-06T08:10:23.116Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/57/2f/55fca558f925a51db046e5b929deb317ddb05afed74b22d89f4eca578980/authlib-1.6.11-py2.py3-none-any.whl", hash = "sha256:c8687a9a26451c51a34a06fa17bb97cb15bba46a6a626755e2d7f50da8bff3e3", size = 244469, upload-time = "2026-04-16T07:22:48.413Z" }, + { url = "https://files.pythonhosted.org/packages/fb/95/adcb68e20c34162e9135f370d6e31737719c2b6f94bc953fe7ed1f10fe21/authlib-1.7.2-py2.py3-none-any.whl", hash = "sha256:3e1faedc9d87e7d56a164eca3ccb6ace0d61b94abe83e92242f8dc8bba9b4a9f", size = 259548, upload-time = "2026-05-06T08:10:21.436Z" }, ] [[package]] @@ -352,14 +366,14 @@ wheels = [ [[package]] name = "bleach" -version = "6.3.0" +version = "6.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "webencodings" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/07/18/3c8523962314be6bf4c8989c79ad9531c825210dd13a8669f6b84336e8bd/bleach-6.3.0.tar.gz", hash = "sha256:6f3b91b1c0a02bb9a78b5a454c92506aa0fdf197e1d5e114d2e00c6f64306d22", size = 203533, upload-time = "2025-10-27T17:57:39.211Z" } +sdist = { url = "https://files.pythonhosted.org/packages/48/3c/e12ac860709702bd5ebeb9b56a4fe334f1001246ee1b8f2b7ee28912df7d/bleach-6.4.0.tar.gz", hash = "sha256:4202482733d85cedd04e59fcb2f89f4e4c7c385a78d3c3c23c30446843a37452", size = 204857, upload-time = "2026-06-05T13:01:13.734Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl", hash = "sha256:fe10ec77c93ddf3d13a73b035abaac7a9f5e436513864ccdad516693213c65d6", size = 164437, upload-time = "2025-10-27T17:57:37.538Z" }, + { url = "https://files.pythonhosted.org/packages/58/9d/40b6267367182187139a4000b82a3b287d84d745bccd808e75d916920e9d/bleach-6.4.0-py3-none-any.whl", hash = "sha256:4b6b6a54fff2e69a3dde9d21cc6301220bee3c3cb792187d11403fd795031081", size = 165109, upload-time = "2026-06-05T13:01:12.504Z" }, ] [package.optional-dependencies] @@ -369,30 +383,30 @@ css = [ [[package]] name = "boto3" -version = "1.42.90" +version = "1.43.47" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, { name = "jmespath" }, { name = "s3transfer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/62/17/510f31d7d6190c01725710d95415733e467f4406d450a106f6eacfd3a94d/boto3-1.42.90.tar.gz", hash = "sha256:bafb5bb1dea262ac95f9afb1e415f06a9490f05cb203bdd897d0afdcd17733c6", size = 113174, upload-time = "2026-04-16T20:27:43.012Z" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/76/8c2b062b9a9f27db8d78c3183213efd2cb31493b9a81853025da5254652e/boto3-1.43.47.tar.gz", hash = "sha256:09a8ce4abab4391bf08315e2dcc449b4b33e97ce7654ee9398d9fe4ef73a33da", size = 112681, upload-time = "2026-07-13T19:32:13.511Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/bd/a0c5011a8eddce39a9b613b13a75057bf960ef2145ff4d1583ed81a2599b/boto3-1.42.90-py3-none-any.whl", hash = "sha256:fde7f7bcad6ec8342d6bf18f56d118d0cb6df189310cfaf73e2eb6443b1cb418", size = 140554, upload-time = "2026-04-16T20:27:40.226Z" }, + { url = "https://files.pythonhosted.org/packages/27/86/4aaa23e1433a6f4021258b1d8e4ec129bf691e419339454806807595a93d/boto3-1.43.47-py3-none-any.whl", hash = "sha256:aef0cccd5bc4a443fb3e64fada023f7b5b9da36864cb4255b68cf93067476676", size = 140031, upload-time = "2026-07-13T19:32:11.072Z" }, ] [[package]] name = "botocore" -version = "1.42.90" +version = "1.43.47" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jmespath" }, { name = "python-dateutil" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/86/cf/4eaa0b7ab2ba0b2a0c93e277779b1385127e2f07876a08d698b529affdae/botocore-1.42.90.tar.gz", hash = "sha256:234c39492cd3088acb021d999e3392a4d50238ae3e70b9d9ae1504c30d9009d1", size = 15209231, upload-time = "2026-04-16T20:27:29.323Z" } +sdist = { url = "https://files.pythonhosted.org/packages/76/2c/279bf51f68e85a12323996aa4a7f2a163da84dad949ee751caa318928ce1/botocore-1.43.47.tar.gz", hash = "sha256:9e04d8da7f9cff8a911b14284829f78b74e1ce785444833199837decb5ecc17a", size = 15696311, upload-time = "2026-07-13T19:32:01.095Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/1e/44afcdc3b526b6e1569dd142083c6ed1cb8b92b4141de1c78ded883b449a/botocore-1.42.90-py3-none-any.whl", hash = "sha256:5c95504720346990adc8e3ae1023eb46f9409084b79688e4773ba7099c5fd3db", size = 14892274, upload-time = "2026-04-16T20:27:24.057Z" }, + { url = "https://files.pythonhosted.org/packages/28/c0/6a8f824d613c5e5b8755ce99e3cf83fd1db135f5df02da7ed5bf106abdfd/botocore-1.43.47-py3-none-any.whl", hash = "sha256:4d16559a3a1866fa7b9e651dd1422c1a033497e069f8a73cae5eebafe70ff39c", size = 15382537, upload-time = "2026-07-13T19:31:56.213Z" }, ] [[package]] @@ -557,19 +571,19 @@ wheels = [ [[package]] name = "click" -version = "8.3.2" +version = "8.4.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/57/75/31212c6bf2503fdf920d87fee5d7a86a2e3bcf444984126f13d8e4016804/click-8.3.2.tar.gz", hash = "sha256:14162b8b3b3550a7d479eafa77dfd3c38d9dc8951f6f69c78913a8f9a7540fd5", size = 302856, upload-time = "2026-04-03T19:14:45.118Z" } +sdist = { url = "https://files.pythonhosted.org/packages/76/d4/81420972a676e8ffea40450d8c8c92943e7218a78fe9b64359836cc9876b/click-8.4.2.tar.gz", hash = "sha256:9a6cea6e60b17ebe0a44c5cc636d94f09bd66142c1cd7d8b4cd731c4917a15f6", size = 338000, upload-time = "2026-06-24T17:45:15.148Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e4/20/71885d8b97d4f3dde17b1fdb92dbd4908b00541c5a3379787137285f602e/click-8.3.2-py3-none-any.whl", hash = "sha256:1924d2c27c5653561cd2cae4548d1406039cb79b858b747cfea24924bbc1616d", size = 108379, upload-time = "2026-04-03T19:14:43.505Z" }, + { url = "https://files.pythonhosted.org/packages/fb/e2/79c688af8b210d232694e31e59da9f6ec747bae31c3f5946e4e9b98860d5/click-8.4.2-py3-none-any.whl", hash = "sha256:e6f9f66136c816745b9d65817da91d61d957fb16e02e4dcd0552553c5a197b76", size = 119243, upload-time = "2026-06-24T17:45:13.73Z" }, ] [[package]] name = "cohere" -version = "5.21.1" +version = "7.0.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "fastavro" }, @@ -581,9 +595,9 @@ dependencies = [ { name = "types-requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d2/75/4c346f6e2322e545f8452692304bd4eca15a2a0209ab9af6a0d1a7810b67/cohere-5.21.1.tar.gz", hash = "sha256:e5ade4423b928b01ff2038980e1b62b2a5bb412c8ab83e30882753b810a5509f", size = 191272, upload-time = "2026-03-26T15:09:27.857Z" } +sdist = { url = "https://files.pythonhosted.org/packages/78/23/cbb8ef34b8395ecea3cef2464d6abcb25174853fe8ad948841204f81f9a8/cohere-7.0.5.tar.gz", hash = "sha256:7fc682bb5c408402fc5ce59322bfa3f6aae27bde1ecc2450b0a25370040707ed", size = 208820, upload-time = "2026-06-29T20:30:53.132Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0a/50/5538f02ec6d10fbb84f29c1b18c68ff2a03d7877926a80275efdf8755a9f/cohere-5.21.1-py3-none-any.whl", hash = "sha256:f15592ec60d8cf12f01563db94ec28c388c61269d9617f23c2d6d910e505344e", size = 334262, upload-time = "2026-03-26T15:09:26.284Z" }, + { url = "https://files.pythonhosted.org/packages/e6/eb/64ca464baa34d0fa8144fc09ec7a35c8fcdeea1be878a518e901b0a6032b/cohere-7.0.5-py3-none-any.whl", hash = "sha256:fd98f82be28c969da7625adddede0bb85f8f84de399daf3a98c40c0e7b93e104", size = 352049, upload-time = "2026-06-29T20:30:51.472Z" }, ] [[package]] @@ -661,55 +675,52 @@ wheels = [ [[package]] name = "cryptography" -version = "46.0.7" +version = "49.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/47/93/ac8f3d5ff04d54bc814e961a43ae5b0b146154c89c61b47bb07557679b18/cryptography-46.0.7.tar.gz", hash = "sha256:e4cfd68c5f3e0bfdad0d38e023239b96a2fe84146481852dffbcca442c245aa5", size = 750652, upload-time = "2026-04-08T01:57:54.692Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/5d/4a8f770695d73be252331e60e526291e3df0c9b27556a90a6b47bccca4c2/cryptography-46.0.7-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:ea42cbe97209df307fdc3b155f1b6fa2577c0defa8f1f7d3be7d31d189108ad4", size = 7179869, upload-time = "2026-04-08T01:56:17.157Z" }, - { url = "https://files.pythonhosted.org/packages/5f/45/6d80dc379b0bbc1f9d1e429f42e4cb9e1d319c7a8201beffd967c516ea01/cryptography-46.0.7-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b36a4695e29fe69215d75960b22577197aca3f7a25b9cf9d165dcfe9d80bc325", size = 4275492, upload-time = "2026-04-08T01:56:19.36Z" }, - { url = "https://files.pythonhosted.org/packages/4a/9a/1765afe9f572e239c3469f2cb429f3ba7b31878c893b246b4b2994ffe2fe/cryptography-46.0.7-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5ad9ef796328c5e3c4ceed237a183f5d41d21150f972455a9d926593a1dcb308", size = 4426670, upload-time = "2026-04-08T01:56:21.415Z" }, - { url = "https://files.pythonhosted.org/packages/8f/3e/af9246aaf23cd4ee060699adab1e47ced3f5f7e7a8ffdd339f817b446462/cryptography-46.0.7-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:73510b83623e080a2c35c62c15298096e2a5dc8d51c3b4e1740211839d0dea77", size = 4280275, upload-time = "2026-04-08T01:56:23.539Z" }, - { url = "https://files.pythonhosted.org/packages/0f/54/6bbbfc5efe86f9d71041827b793c24811a017c6ac0fd12883e4caa86b8ed/cryptography-46.0.7-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cbd5fb06b62bd0721e1170273d3f4d5a277044c47ca27ee257025146c34cbdd1", size = 4928402, upload-time = "2026-04-08T01:56:25.624Z" }, - { url = "https://files.pythonhosted.org/packages/2d/cf/054b9d8220f81509939599c8bdbc0c408dbd2bdd41688616a20731371fe0/cryptography-46.0.7-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:420b1e4109cc95f0e5700eed79908cef9268265c773d3a66f7af1eef53d409ef", size = 4459985, upload-time = "2026-04-08T01:56:27.309Z" }, - { url = "https://files.pythonhosted.org/packages/f9/46/4e4e9c6040fb01c7467d47217d2f882daddeb8828f7df800cb806d8a2288/cryptography-46.0.7-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:24402210aa54baae71d99441d15bb5a1919c195398a87b563df84468160a65de", size = 3990652, upload-time = "2026-04-08T01:56:29.095Z" }, - { url = "https://files.pythonhosted.org/packages/36/5f/313586c3be5a2fbe87e4c9a254207b860155a8e1f3cca99f9910008e7d08/cryptography-46.0.7-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:8a469028a86f12eb7d2fe97162d0634026d92a21f3ae0ac87ed1c4a447886c83", size = 4279805, upload-time = "2026-04-08T01:56:30.928Z" }, - { url = "https://files.pythonhosted.org/packages/69/33/60dfc4595f334a2082749673386a4d05e4f0cf4df8248e63b2c3437585f2/cryptography-46.0.7-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:9694078c5d44c157ef3162e3bf3946510b857df5a3955458381d1c7cfc143ddb", size = 4892883, upload-time = "2026-04-08T01:56:32.614Z" }, - { url = "https://files.pythonhosted.org/packages/c7/0b/333ddab4270c4f5b972f980adef4faa66951a4aaf646ca067af597f15563/cryptography-46.0.7-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:42a1e5f98abb6391717978baf9f90dc28a743b7d9be7f0751a6f56a75d14065b", size = 4459756, upload-time = "2026-04-08T01:56:34.306Z" }, - { url = "https://files.pythonhosted.org/packages/d2/14/633913398b43b75f1234834170947957c6b623d1701ffc7a9600da907e89/cryptography-46.0.7-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:91bbcb08347344f810cbe49065914fe048949648f6bd5c2519f34619142bbe85", size = 4410244, upload-time = "2026-04-08T01:56:35.977Z" }, - { url = "https://files.pythonhosted.org/packages/10/f2/19ceb3b3dc14009373432af0c13f46aa08e3ce334ec6eff13492e1812ccd/cryptography-46.0.7-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:5d1c02a14ceb9148cc7816249f64f623fbfee39e8c03b3650d842ad3f34d637e", size = 4674868, upload-time = "2026-04-08T01:56:38.034Z" }, - { url = "https://files.pythonhosted.org/packages/1a/bb/a5c213c19ee94b15dfccc48f363738633a493812687f5567addbcbba9f6f/cryptography-46.0.7-cp311-abi3-win32.whl", hash = "sha256:d23c8ca48e44ee015cd0a54aeccdf9f09004eba9fc96f38c911011d9ff1bd457", size = 3026504, upload-time = "2026-04-08T01:56:39.666Z" }, - { url = "https://files.pythonhosted.org/packages/2b/02/7788f9fefa1d060ca68717c3901ae7fffa21ee087a90b7f23c7a603c32ae/cryptography-46.0.7-cp311-abi3-win_amd64.whl", hash = "sha256:397655da831414d165029da9bc483bed2fe0e75dde6a1523ec2fe63f3c46046b", size = 3488363, upload-time = "2026-04-08T01:56:41.893Z" }, - { url = "https://files.pythonhosted.org/packages/7b/56/15619b210e689c5403bb0540e4cb7dbf11a6bf42e483b7644e471a2812b3/cryptography-46.0.7-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:d151173275e1728cf7839aaa80c34fe550c04ddb27b34f48c232193df8db5842", size = 7119671, upload-time = "2026-04-08T01:56:44Z" }, - { url = "https://files.pythonhosted.org/packages/74/66/e3ce040721b0b5599e175ba91ab08884c75928fbeb74597dd10ef13505d2/cryptography-46.0.7-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:db0f493b9181c7820c8134437eb8b0b4792085d37dbb24da050476ccb664e59c", size = 4268551, upload-time = "2026-04-08T01:56:46.071Z" }, - { url = "https://files.pythonhosted.org/packages/03/11/5e395f961d6868269835dee1bafec6a1ac176505a167f68b7d8818431068/cryptography-46.0.7-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ebd6daf519b9f189f85c479427bbd6e9c9037862cf8fe89ee35503bd209ed902", size = 4408887, upload-time = "2026-04-08T01:56:47.718Z" }, - { url = "https://files.pythonhosted.org/packages/40/53/8ed1cf4c3b9c8e611e7122fb56f1c32d09e1fff0f1d77e78d9ff7c82653e/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:b7b412817be92117ec5ed95f880defe9cf18a832e8cafacf0a22337dc1981b4d", size = 4271354, upload-time = "2026-04-08T01:56:49.312Z" }, - { url = "https://files.pythonhosted.org/packages/50/46/cf71e26025c2e767c5609162c866a78e8a2915bbcfa408b7ca495c6140c4/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:fbfd0e5f273877695cb93baf14b185f4878128b250cc9f8e617ea0c025dfb022", size = 4905845, upload-time = "2026-04-08T01:56:50.916Z" }, - { url = "https://files.pythonhosted.org/packages/c0/ea/01276740375bac6249d0a971ebdf6b4dc9ead0ee0a34ef3b5a88c1a9b0d4/cryptography-46.0.7-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:ffca7aa1d00cf7d6469b988c581598f2259e46215e0140af408966a24cf086ce", size = 4444641, upload-time = "2026-04-08T01:56:52.882Z" }, - { url = "https://files.pythonhosted.org/packages/3d/4c/7d258f169ae71230f25d9f3d06caabcff8c3baf0978e2b7d65e0acac3827/cryptography-46.0.7-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:60627cf07e0d9274338521205899337c5d18249db56865f943cbe753aa96f40f", size = 3967749, upload-time = "2026-04-08T01:56:54.597Z" }, - { url = "https://files.pythonhosted.org/packages/b5/2a/2ea0767cad19e71b3530e4cad9605d0b5e338b6a1e72c37c9c1ceb86c333/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:80406c3065e2c55d7f49a9550fe0c49b3f12e5bfff5dedb727e319e1afb9bf99", size = 4270942, upload-time = "2026-04-08T01:56:56.416Z" }, - { url = "https://files.pythonhosted.org/packages/41/3d/fe14df95a83319af25717677e956567a105bb6ab25641acaa093db79975d/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:c5b1ccd1239f48b7151a65bc6dd54bcfcc15e028c8ac126d3fada09db0e07ef1", size = 4871079, upload-time = "2026-04-08T01:56:58.31Z" }, - { url = "https://files.pythonhosted.org/packages/9c/59/4a479e0f36f8f378d397f4eab4c850b4ffb79a2f0d58704b8fa0703ddc11/cryptography-46.0.7-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d5f7520159cd9c2154eb61eb67548ca05c5774d39e9c2c4339fd793fe7d097b2", size = 4443999, upload-time = "2026-04-08T01:57:00.508Z" }, - { url = "https://files.pythonhosted.org/packages/28/17/b59a741645822ec6d04732b43c5d35e4ef58be7bfa84a81e5ae6f05a1d33/cryptography-46.0.7-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fcd8eac50d9138c1d7fc53a653ba60a2bee81a505f9f8850b6b2888555a45d0e", size = 4399191, upload-time = "2026-04-08T01:57:02.654Z" }, - { url = "https://files.pythonhosted.org/packages/59/6a/bb2e166d6d0e0955f1e9ff70f10ec4b2824c9cfcdb4da772c7dd69cc7d80/cryptography-46.0.7-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:65814c60f8cc400c63131584e3e1fad01235edba2614b61fbfbfa954082db0ee", size = 4655782, upload-time = "2026-04-08T01:57:04.592Z" }, - { url = "https://files.pythonhosted.org/packages/95/b6/3da51d48415bcb63b00dc17c2eff3a651b7c4fed484308d0f19b30e8cb2c/cryptography-46.0.7-cp314-cp314t-win32.whl", hash = "sha256:fdd1736fed309b4300346f88f74cd120c27c56852c3838cab416e7a166f67298", size = 3002227, upload-time = "2026-04-08T01:57:06.91Z" }, - { url = "https://files.pythonhosted.org/packages/32/a8/9f0e4ed57ec9cebe506e58db11ae472972ecb0c659e4d52bbaee80ca340a/cryptography-46.0.7-cp314-cp314t-win_amd64.whl", hash = "sha256:e06acf3c99be55aa3b516397fe42f5855597f430add9c17fa46bf2e0fb34c9bb", size = 3475332, upload-time = "2026-04-08T01:57:08.807Z" }, - { url = "https://files.pythonhosted.org/packages/a7/7f/cd42fc3614386bc0c12f0cb3c4ae1fc2bbca5c9662dfed031514911d513d/cryptography-46.0.7-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:462ad5cb1c148a22b2e3bcc5ad52504dff325d17daf5df8d88c17dda1f75f2a4", size = 7165618, upload-time = "2026-04-08T01:57:10.645Z" }, - { url = "https://files.pythonhosted.org/packages/a5/d0/36a49f0262d2319139d2829f773f1b97ef8aef7f97e6e5bd21455e5a8fb5/cryptography-46.0.7-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:84d4cced91f0f159a7ddacad249cc077e63195c36aac40b4150e7a57e84fffe7", size = 4270628, upload-time = "2026-04-08T01:57:12.885Z" }, - { url = "https://files.pythonhosted.org/packages/8a/6c/1a42450f464dda6ffbe578a911f773e54dd48c10f9895a23a7e88b3e7db5/cryptography-46.0.7-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:128c5edfe5e5938b86b03941e94fac9ee793a94452ad1365c9fc3f4f62216832", size = 4415405, upload-time = "2026-04-08T01:57:14.923Z" }, - { url = "https://files.pythonhosted.org/packages/9a/92/4ed714dbe93a066dc1f4b4581a464d2d7dbec9046f7c8b7016f5286329e2/cryptography-46.0.7-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:5e51be372b26ef4ba3de3c167cd3d1022934bc838ae9eaad7e644986d2a3d163", size = 4272715, upload-time = "2026-04-08T01:57:16.638Z" }, - { url = "https://files.pythonhosted.org/packages/b7/e6/a26b84096eddd51494bba19111f8fffe976f6a09f132706f8f1bf03f51f7/cryptography-46.0.7-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:cdf1a610ef82abb396451862739e3fc93b071c844399e15b90726ef7470eeaf2", size = 4918400, upload-time = "2026-04-08T01:57:19.021Z" }, - { url = "https://files.pythonhosted.org/packages/c7/08/ffd537b605568a148543ac3c2b239708ae0bd635064bab41359252ef88ed/cryptography-46.0.7-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:1d25aee46d0c6f1a501adcddb2d2fee4b979381346a78558ed13e50aa8a59067", size = 4450634, upload-time = "2026-04-08T01:57:21.185Z" }, - { url = "https://files.pythonhosted.org/packages/16/01/0cd51dd86ab5b9befe0d031e276510491976c3a80e9f6e31810cce46c4ad/cryptography-46.0.7-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:cdfbe22376065ffcf8be74dc9a909f032df19bc58a699456a21712d6e5eabfd0", size = 3985233, upload-time = "2026-04-08T01:57:22.862Z" }, - { url = "https://files.pythonhosted.org/packages/92/49/819d6ed3a7d9349c2939f81b500a738cb733ab62fbecdbc1e38e83d45e12/cryptography-46.0.7-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:abad9dac36cbf55de6eb49badd4016806b3165d396f64925bf2999bcb67837ba", size = 4271955, upload-time = "2026-04-08T01:57:24.814Z" }, - { url = "https://files.pythonhosted.org/packages/80/07/ad9b3c56ebb95ed2473d46df0847357e01583f4c52a85754d1a55e29e4d0/cryptography-46.0.7-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:935ce7e3cfdb53e3536119a542b839bb94ec1ad081013e9ab9b7cfd478b05006", size = 4879888, upload-time = "2026-04-08T01:57:26.88Z" }, - { url = "https://files.pythonhosted.org/packages/b8/c7/201d3d58f30c4c2bdbe9b03844c291feb77c20511cc3586daf7edc12a47b/cryptography-46.0.7-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:35719dc79d4730d30f1c2b6474bd6acda36ae2dfae1e3c16f2051f215df33ce0", size = 4449961, upload-time = "2026-04-08T01:57:29.068Z" }, - { url = "https://files.pythonhosted.org/packages/a5/ef/649750cbf96f3033c3c976e112265c33906f8e462291a33d77f90356548c/cryptography-46.0.7-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7bbc6ccf49d05ac8f7d7b5e2e2c33830d4fe2061def88210a126d130d7f71a85", size = 4401696, upload-time = "2026-04-08T01:57:31.029Z" }, - { url = "https://files.pythonhosted.org/packages/41/52/a8908dcb1a389a459a29008c29966c1d552588d4ae6d43f3a1a4512e0ebe/cryptography-46.0.7-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a1529d614f44b863a7b480c6d000fe93b59acee9c82ffa027cfadc77521a9f5e", size = 4664256, upload-time = "2026-04-08T01:57:33.144Z" }, - { url = "https://files.pythonhosted.org/packages/4b/fa/f0ab06238e899cc3fb332623f337a7364f36f4bb3f2534c2bb95a35b132c/cryptography-46.0.7-cp38-abi3-win32.whl", hash = "sha256:f247c8c1a1fb45e12586afbb436ef21ff1e80670b2861a90353d9b025583d246", size = 3013001, upload-time = "2026-04-08T01:57:34.933Z" }, - { url = "https://files.pythonhosted.org/packages/d2/f1/00ce3bde3ca542d1acd8f8cfa38e446840945aa6363f9b74746394b14127/cryptography-46.0.7-cp38-abi3-win_amd64.whl", hash = "sha256:506c4ff91eff4f82bdac7633318a526b1d1309fc07ca76a3ad182cb5b686d6d3", size = 3472985, upload-time = "2026-04-08T01:57:36.714Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/1f/99/d1c90d6041656cc6ee229dc99cd67fd0cd5aec3c5f7d72fffc27cc750054/cryptography-49.0.0.tar.gz", hash = "sha256:f89660a348f4f78a92366240a61404e337586ef7f5909a2fef59ca88ef505493", size = 854345, upload-time = "2026-06-12T20:02:30.512Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9b/22/adf66990e63584a68dfb50c24f48a125c07b1699899381c8151e63ed458c/cryptography-49.0.0-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:966fe0e9c67490071f14c0d2b1cb2dfb3023c5ce39457343931415f08382f2db", size = 4032100, upload-time = "2026-06-12T20:02:32.143Z" }, + { url = "https://files.pythonhosted.org/packages/09/41/3797cfaf69cae04a13ee78ebd83f0678d9c02b4779d21ce24445326f1a69/cryptography-49.0.0-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:36d1709f992593689b45bda411498d62c6e365f2ca00b84657d4dadd24de16db", size = 4692978, upload-time = "2026-06-12T20:01:21.305Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8b/43011f7ebe515a8aa20d61f290a326cd890c2e738e16e59eaff8d9c3a412/cryptography-49.0.0-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0e959b578856a3924bc0cbb710fc12c387b9412a951389f3ca61704a9e25f325", size = 4716422, upload-time = "2026-06-12T20:01:48.566Z" }, + { url = "https://files.pythonhosted.org/packages/4a/91/01ce7303a4579e6d3a6abef01bd322848e9ea7a219adcabc5048b9033571/cryptography-49.0.0-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:53ecee2e23f7169b6117e99fc8a944e5e50f79e69758a83b52a00cb98ab2b2d2", size = 4700503, upload-time = "2026-06-12T20:02:47.091Z" }, + { url = "https://files.pythonhosted.org/packages/62/99/a2c95cf8293f07491e9e27c20cc4dcd18176d944e674679adeb1d0173fd6/cryptography-49.0.0-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:2eda353d8a27bcbcaa4cbed18994a74ab4d19a2ca897db188ea269ab9b71419b", size = 5309779, upload-time = "2026-06-12T20:02:08.987Z" }, + { url = "https://files.pythonhosted.org/packages/20/2c/0622f20ff02b2ef32558733443805dc82fd4c275be01b2d19d14676f3a1b/cryptography-49.0.0-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:2afe9051da7ae7bd5905da5a949280c7d2bb75682e188f650a9d0f2756b834c6", size = 4749683, upload-time = "2026-06-12T20:02:03.335Z" }, + { url = "https://files.pythonhosted.org/packages/a3/5b/c5246635d5fd3b64e0d45ae10e99fd32fe9676a79915ccfe5a61ba9af1a5/cryptography-49.0.0-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:0b82e28ee398a386f0807bba7884d30f25218855690f45115831bcce5d90822c", size = 4337874, upload-time = "2026-06-12T20:02:54.323Z" }, + { url = "https://files.pythonhosted.org/packages/6d/88/05563c7fe2e914e87d1a536d06fe83e66b4e1d95cb593e05aea375531da8/cryptography-49.0.0-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:ccac2bfebc306b862133e3bb71f3f6ee8bb525240089b2d952e4144b3a6d5da7", size = 4700283, upload-time = "2026-06-12T20:01:34.822Z" }, + { url = "https://files.pythonhosted.org/packages/c4/b6/d7696e4e890d6ae1469935164c9e5215c557671cb78d6e3f458ccceaa632/cryptography-49.0.0-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:d0527ce944105f257f605a827d6ebead966c752038b6e8656abb9c5edee6fc68", size = 5265844, upload-time = "2026-06-12T20:01:24.09Z" }, + { url = "https://files.pythonhosted.org/packages/a9/3c/f3ad17eecc1a57b0ba236dc01f90e783c51f4a2f35f64777cc4f47a184b2/cryptography-49.0.0-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:cbc77da8c523d5abd028635ba850a6966fcee2c82e2bf65a41d1d8afe0f98be9", size = 4749290, upload-time = "2026-06-12T20:01:30.848Z" }, + { url = "https://files.pythonhosted.org/packages/4f/01/339573cf1023163a400b0b5d16f6d507de413b9f60be6fd1b77feeaf6737/cryptography-49.0.0-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b87e65d263b3e5d3bb92a57e2a6638e2f31110fa7aa890c7b2dbba42248d0a3f", size = 4834612, upload-time = "2026-06-12T20:01:29.246Z" }, + { url = "https://files.pythonhosted.org/packages/71/fd/577302e213a1be9468f92d1afef66fcf1ef83d516819d9992ca547f592bd/cryptography-49.0.0-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:66ec79c3904820572d7e987abdf304281f141d37ad9a489b8e97066e7b9b6459", size = 4980804, upload-time = "2026-06-12T20:01:42.853Z" }, + { url = "https://files.pythonhosted.org/packages/1f/09/f42b1d190c5ba75f72062a387f8030d1d75f6ab035788f1d9c4b01de6525/cryptography-49.0.0-cp311-abi3-win_amd64.whl", hash = "sha256:e5dfc1e64de5677cec922ffa8da89c546d0415bf6efdf081842e5d44c84e1f0e", size = 3810026, upload-time = "2026-06-12T20:02:39.262Z" }, + { url = "https://files.pythonhosted.org/packages/ec/9e/db72b3ae7fc9cfad53e630e56c6ae83b9b6ff0bf3718ffb8012d20b3aabf/cryptography-49.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:73a205dce83953d131a4aa1e0fd917a2fd1c5b1eef251e9d7152efefcbf5caf7", size = 4013892, upload-time = "2026-06-12T20:02:10.735Z" }, + { url = "https://files.pythonhosted.org/packages/86/12/c48a424f38db03027be9f7ed5c7dc5de9933dbee992865f98b13727a009d/cryptography-49.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:196ecd6a36e4e9aa10270393bb98d8df88fccee0bf1e5128b91ae4eb4375896d", size = 4678835, upload-time = "2026-06-12T20:02:48.743Z" }, + { url = "https://files.pythonhosted.org/packages/68/28/8a3ad4653662c93fc44dc4e5d8fd374c25c42e07b34bbfbadf49cf57a5a8/cryptography-49.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7abcee80084cda3f7691f3eb1ce480d8df49cec637b429aa35986c1de71738aa", size = 4697239, upload-time = "2026-06-12T20:02:56.03Z" }, + { url = "https://files.pythonhosted.org/packages/a8/b2/2193fc74f81aee4f9b62733133b73b5176718932ed8f2e4b03fa040480a6/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:4ae387c9cb68ea569ca17e490d66d8142b81c3cc814bf179974b7d146e490bbb", size = 4685593, upload-time = "2026-06-12T20:02:50.666Z" }, + { url = "https://files.pythonhosted.org/packages/47/f1/1d3eaa243bfc5de4a187b22aa8c048b3e4980bfbe830ac46e6bac2e66947/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:f37d847238971164fdbc68ade6f6574aecc9c0af714190e2083429ff68f4ce9d", size = 5289961, upload-time = "2026-06-12T20:01:46.468Z" }, + { url = "https://files.pythonhosted.org/packages/58/39/2d51306721330c486495853eda1c567880ff036de15a14c4b74f399934af/cryptography-49.0.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:c2bc30226390d60ea19d9f82b19db005fe0452154a23c1c410c12ea801e43561", size = 4731145, upload-time = "2026-06-12T20:02:16.832Z" }, + { url = "https://files.pythonhosted.org/packages/17/50/983e838c7fd0d87fd8c969bcdd328edaf5f756e38df5281637424c155873/cryptography-49.0.0-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:07cab27cc7b7e0fd28e5e26bb9eeedde5c135c868b46de4a27845abe94af6122", size = 4321719, upload-time = "2026-06-12T20:02:52.611Z" }, + { url = "https://files.pythonhosted.org/packages/a7/f5/8f571d7e27c55bce9f76f026143bcb1e040a4233149ecca0bea5fa5dd5f7/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:b20133d204d2bb56ba047642199603876c872026ca53e79c35b83772ab2cc505", size = 4685209, upload-time = "2026-06-12T20:02:07.282Z" }, + { url = "https://files.pythonhosted.org/packages/e7/84/0e27016a6fc5a0886f797018b26aa42f40c09a82332bff77822a451deaaa/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:b970c6da94d5bb18629db453d14f2a1300f6bf59b61e9b82377931ef95504866", size = 5246285, upload-time = "2026-06-12T20:01:32.439Z" }, + { url = "https://files.pythonhosted.org/packages/11/2d/5e1fb307cb5931881516b464c98774b3f2c36b5d4bb9a2830253cf553cad/cryptography-49.0.0-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:d8ecde755e2e91bf773fc94e8c9d730cd7f2007004cb492263a794ec3899a1c8", size = 4730441, upload-time = "2026-06-12T20:02:01.469Z" }, + { url = "https://files.pythonhosted.org/packages/e4/c0/bff5a02ee731d207d6a1ed51732549d8c53d2bc8da1d10ec6f2844201d68/cryptography-49.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e3fb64c420688e5319ae25113a354015abbd8dffbfbc41781a1ea66fc7622ac3", size = 4815869, upload-time = "2026-06-12T20:01:36.574Z" }, + { url = "https://files.pythonhosted.org/packages/b9/26/814681d14248d95d73d5c3eea0c39a94eb8302df966f670a2c60de90974b/cryptography-49.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:32703d93296f5c1f4b53349ad3a250c2cae0fdecd3a3dd5d47e616d8d616af27", size = 4960948, upload-time = "2026-06-12T20:02:18.688Z" }, + { url = "https://files.pythonhosted.org/packages/4c/fe/93ecac273d3738939d023612ad12cca9a3740a5345d69fda04134c43fd96/cryptography-49.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:33cd0565932807baddb67b96dbee92f2c374b5c89dee09fd74079aeb8c8dba61", size = 3799153, upload-time = "2026-06-12T20:01:39.059Z" }, + { url = "https://files.pythonhosted.org/packages/19/2a/5bb823f5bedcf80718cea7fbc95ec5515cca3769633c4b01a32be7f30e7c/cryptography-49.0.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:ec5e529fb80935c94fe7b729f9972b50e351a0e6b50aa294fd5cabb109fcc29a", size = 4025947, upload-time = "2026-06-12T20:01:25.745Z" }, + { url = "https://files.pythonhosted.org/packages/3d/df/40577043ca124e17012f408ddddaeb213b856336ac82ddb3bc915f39e29f/cryptography-49.0.0-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f78ff2c9ed8dc2d036b0f4d640e22522213d047c1b14e61205a7e55c80a494d4", size = 4692429, upload-time = "2026-06-12T20:01:53.628Z" }, + { url = "https://files.pythonhosted.org/packages/2c/99/2d13299eb3dd27b02dcfaafcc91d6b5cb3329f7cbd6d8f51921acd566c1a/cryptography-49.0.0-cp39-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:35b151772baff2c74cba7fa290ceaff4c3b11c0c881eb93eb5dbc05a7cfbba18", size = 4700968, upload-time = "2026-06-12T20:02:45.383Z" }, + { url = "https://files.pythonhosted.org/packages/a5/4d/9c0cd02f95e2602dd5e563da149ee0830abef3537be8b34dc56281ebe27a/cryptography-49.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:0f21641cf4b30fca7aee061ced0ec7ad7b073518088b7c9969a297c0ae796c69", size = 4697758, upload-time = "2026-06-12T20:01:41.13Z" }, + { url = "https://files.pythonhosted.org/packages/24/01/186c825898477d77e2324d5360fefe622ff1d8d1963ec0554e2cada8ec77/cryptography-49.0.0-cp39-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:9e82dcc8e56052715fb18b2429e3bca4823b1629136a2084fc45a9a5cecb9b64", size = 5298863, upload-time = "2026-06-12T20:02:24.579Z" }, + { url = "https://files.pythonhosted.org/packages/b8/7b/62cbbab75d0659865bf0273790031544a0b16c8072d258f9428dcd8190dc/cryptography-49.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:6f2debedf9ca60cf1d5bd466475638af5130f89965605cd818484d19987d3a21", size = 4735983, upload-time = "2026-06-12T20:01:50.14Z" }, + { url = "https://files.pythonhosted.org/packages/6c/72/3e798c064bc39e471008075d0f9bc9daf77a80879c092e4a8e170c585ed4/cryptography-49.0.0-cp39-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:8c25ceb16df5b9435f3f6a9829204985b0e0cbee3b48aacd432c7d2c850b44d9", size = 4334173, upload-time = "2026-06-12T20:01:44.743Z" }, + { url = "https://files.pythonhosted.org/packages/f0/ee/6fca21d1ac73e06f8bef71940abfd4d2f6472b4bca284d770f32bd4086f6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:28d8b15e6275f12c8a207dc309dfa957903c927d08d0cc937ee3f63f200693cc", size = 4697298, upload-time = "2026-06-12T20:02:20.918Z" }, + { url = "https://files.pythonhosted.org/packages/67/d0/a5fcd3515f0bae49a7b6d0413cc1bdccdcc1fc0047037a0d480642cdc5d6/cryptography-49.0.0-cp39-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:6fc361c34fb6aac015ce19435876635e5c6d21db31998b0920f675f131e043b8", size = 5254338, upload-time = "2026-06-12T20:02:22.737Z" }, + { url = "https://files.pythonhosted.org/packages/a0/84/84fe36f19caf857d61cb7fc9c63035a47ffabd84ea12d1d393148efa3615/cryptography-49.0.0-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:2400ef9c9e2299a25614eb1dea3db54a69b1349efd043bfac9c67630d136df36", size = 4735650, upload-time = "2026-06-12T20:02:41.389Z" }, + { url = "https://files.pythonhosted.org/packages/6c/a0/db537264e234f7273a73ec020873d6d6b39dfd8a53db78b550ca8320440e/cryptography-49.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:67e1d20ad9ef3a563c59ef22e7a8a0b8210bd26604369ea4a30a7c66aefe504e", size = 4834820, upload-time = "2026-06-12T20:01:51.847Z" }, + { url = "https://files.pythonhosted.org/packages/93/77/8df9eb486495979bccecd1062e2eaf435250e84437040295b57d09048b0b/cryptography-49.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:42b0684e0e40cf26122427802486f6d93aea593612603a94fbf260c7eb1e9c1b", size = 4967968, upload-time = "2026-06-12T20:02:12.524Z" }, + { url = "https://files.pythonhosted.org/packages/c2/e6/f60198ea8d9dfa15fff9ed4ca02ce362f6eadd9ba757dcc50634c4257b63/cryptography-49.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:026ac7423e6fa66872d3bf889be5974507da3944f866f704fa200eadacd00001", size = 3785547, upload-time = "2026-06-12T20:02:26.847Z" }, ] [[package]] @@ -723,7 +734,7 @@ wheels = [ [[package]] name = "cyclopts" -version = "4.10.2" +version = "4.21.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "attrs" }, @@ -731,15 +742,14 @@ dependencies = [ { name = "rich" }, { name = "rich-rst" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/66/2c/fced34890f6e5a93a4b7afb2c71e8eee2a0719fb26193a0abf159ecb714d/cyclopts-4.10.2.tar.gz", hash = "sha256:d7b950457ef2563596d56331f80cbbbf86a2772535fb8b315c4f03bc7e6127f1", size = 166664, upload-time = "2026-04-08T23:57:45.805Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d9/53/977540be379b0c82550df872912446def343ab0dfa138b8726120427f83d/cyclopts-4.21.0.tar.gz", hash = "sha256:477c18c791c924cca4836f79fce000a7bae45f551e340d9e1654e102c6d9ab9d", size = 190914, upload-time = "2026-07-09T19:07:07.154Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b4/bd/05055d8360cef0757d79367157f3b15c0a0715e81e08f86a04018ec045f0/cyclopts-4.10.2-py3-none-any.whl", hash = "sha256:a1f2d6f8f7afac9456b48f75a40b36658778ddc9c6d406b520d017ae32c990fe", size = 204314, upload-time = "2026-04-08T23:57:46.969Z" }, + { url = "https://files.pythonhosted.org/packages/b4/f6/1c671874560a70d902dd57028a75411c176910de2df3d6a6a533de424131/cyclopts-4.21.0-py3-none-any.whl", hash = "sha256:ded3ddb15b0c815f44d245011fc4cdd5a4809a3bb8202869e9e02195a87c0e18", size = 230066, upload-time = "2026-07-09T19:07:05.326Z" }, ] [[package]] name = "cyteonto" -version = "0.1.0" -source = { virtual = "." } +source = { editable = "." } dependencies = [ { name = "aiohttp" }, { name = "anndata" }, @@ -777,7 +787,7 @@ dev = [ [package.metadata] requires-dist = [ - { name = "aiohttp", specifier = ">=3.12.15" }, + { name = "aiohttp", specifier = ">=3.14.0" }, { name = "anndata", specifier = ">=0.12.2" }, { name = "asyncio", specifier = ">=4.0.0" }, { name = "fastapi", specifier = ">=0.116.1" }, @@ -794,7 +804,7 @@ requires-dist = [ { name = "pandas", specifier = ">=2.3.1" }, { name = "pyarrow", specifier = ">=24.0.0" }, { name = "pydantic", specifier = ">=2.11.7" }, - { name = "pydantic-ai", specifier = ">=0.7.4" }, + { name = "pydantic-ai", specifier = ">=1.102.0,<2.0.0" }, { name = "pytest", specifier = ">=8.4.1" }, { name = "pytest-asyncio", specifier = ">=1.1.0" }, { name = "python-dotenv", specifier = ">=1.1.1" }, @@ -871,15 +881,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a7/5f/ed01f9a3cdffbd5a008556fc7b2a08ddb1cc6ace7effa7340604b1d16699/docstring_parser-0.18.0-py3-none-any.whl", hash = "sha256:b3fcbed555c47d8479be0796ef7e19c2670d428d72e96da63f3a40122860374b", size = 22484, upload-time = "2026-04-14T04:09:18.638Z" }, ] -[[package]] -name = "docutils" -version = "0.22.4" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ae/b6/03bb70946330e88ffec97aefd3ea75ba575cb2e762061e0e62a213befee8/docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968", size = 2291750, upload-time = "2025-12-18T19:00:26.443Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196, upload-time = "2025-12-18T19:00:18.077Z" }, -] - [[package]] name = "donfig" version = "0.8.1.post1" @@ -907,11 +908,11 @@ wheels = [ [[package]] name = "eval-type-backport" -version = "0.3.1" +version = "0.4.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fb/a3/cafafb4558fd638aadfe4121dc6cefb8d743368c085acb2f521df0f3d9d7/eval_type_backport-0.3.1.tar.gz", hash = "sha256:57e993f7b5b69d271e37482e62f74e76a0276c82490cf8e4f0dffeb6b332d5ed", size = 9445, upload-time = "2025-12-02T11:51:42.987Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/15/273a4baf8248d6d76220723c3caf039d283774b31a7c46ba686120145b76/eval_type_backport-0.4.0.tar.gz", hash = "sha256:8397d25e6524c2e67b9576bb0636be27dea2192017711220c534ec2de921e9b0", size = 10260, upload-time = "2026-06-02T13:22:06.059Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cf/22/fdc2e30d43ff853720042fa15baa3e6122722be1a7950a98233ebb55cd71/eval_type_backport-0.3.1-py3-none-any.whl", hash = "sha256:279ab641905e9f11129f56a8a78f493518515b83402b860f6f06dd7c011fdfa8", size = 6063, upload-time = "2025-12-02T11:51:41.665Z" }, + { url = "https://files.pythonhosted.org/packages/50/a7/bb99bf5e6f78736ddb53480f2c3ff3702ffe2196a7c5e1661c03081d398e/eval_type_backport-0.4.0-py3-none-any.whl", hash = "sha256:ad5e2a8db71b6696a56eafb938b0f5a337d3217f256b8e158b469422b4772b20", size = 6432, upload-time = "2026-06-02T13:22:04.827Z" }, ] [[package]] @@ -950,31 +951,34 @@ wheels = [ [[package]] name = "fastavro" -version = "1.12.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/65/8b/fa2d3287fd2267be6261d0177c6809a7fa12c5600ddb33490c8dc29e77b2/fastavro-1.12.1.tar.gz", hash = "sha256:2f285be49e45bc047ab2f6bed040bb349da85db3f3c87880e4b92595ea093b2b", size = 1025661, upload-time = "2025-10-10T15:40:55.41Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bb/57/26d5efef9182392d5ac9f253953c856ccb66e4c549fd3176a1e94efb05c9/fastavro-1.12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:78df838351e4dff9edd10a1c41d1324131ffecbadefb9c297d612ef5363c049a", size = 1000599, upload-time = "2025-10-10T15:41:36.554Z" }, - { url = "https://files.pythonhosted.org/packages/33/cb/8ab55b21d018178eb126007a56bde14fd01c0afc11d20b5f2624fe01e698/fastavro-1.12.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:780476c23175d2ae457c52f45b9ffa9d504593499a36cd3c1929662bf5b7b14b", size = 3335933, upload-time = "2025-10-10T15:41:39.07Z" }, - { url = "https://files.pythonhosted.org/packages/fe/03/9c94ec9bf873eb1ffb0aa694f4e71940154e6e9728ddfdc46046d7e8ced4/fastavro-1.12.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0714b285160fcd515eb0455540f40dd6dac93bdeacdb03f24e8eac3d8aa51f8d", size = 3402066, upload-time = "2025-10-10T15:41:41.608Z" }, - { url = "https://files.pythonhosted.org/packages/75/c8/cb472347c5a584ccb8777a649ebb28278fccea39d005fc7df19996f41df8/fastavro-1.12.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a8bc2dcec5843d499f2489bfe0747999108f78c5b29295d877379f1972a3d41a", size = 3240038, upload-time = "2025-10-10T15:41:43.743Z" }, - { url = "https://files.pythonhosted.org/packages/e1/77/569ce9474c40304b3a09e109494e020462b83e405545b78069ddba5f614e/fastavro-1.12.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:3b1921ac35f3d89090a5816b626cf46e67dbecf3f054131f84d56b4e70496f45", size = 3369398, upload-time = "2025-10-10T15:41:45.719Z" }, - { url = "https://files.pythonhosted.org/packages/4a/1f/9589e35e9ea68035385db7bdbf500d36b8891db474063fb1ccc8215ee37c/fastavro-1.12.1-cp313-cp313-win_amd64.whl", hash = "sha256:5aa777b8ee595b50aa084104cd70670bf25a7bbb9fd8bb5d07524b0785ee1699", size = 444220, upload-time = "2025-10-10T15:41:47.39Z" }, - { url = "https://files.pythonhosted.org/packages/6c/d2/78435fe737df94bd8db2234b2100f5453737cffd29adee2504a2b013de84/fastavro-1.12.1-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:c3d67c47f177e486640404a56f2f50b165fe892cc343ac3a34673b80cc7f1dd6", size = 1086611, upload-time = "2025-10-10T15:41:48.818Z" }, - { url = "https://files.pythonhosted.org/packages/b6/be/428f99b10157230ddac77ec8cc167005b29e2bd5cbe228345192bb645f30/fastavro-1.12.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5217f773492bac43dae15ff2931432bce2d7a80be7039685a78d3fab7df910bd", size = 3541001, upload-time = "2025-10-10T15:41:50.871Z" }, - { url = "https://files.pythonhosted.org/packages/16/08/a2eea4f20b85897740efe44887e1ac08f30dfa4bfc3de8962bdcbb21a5a1/fastavro-1.12.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:469fecb25cba07f2e1bfa4c8d008477cd6b5b34a59d48715e1b1a73f6160097d", size = 3432217, upload-time = "2025-10-10T15:41:53.149Z" }, - { url = "https://files.pythonhosted.org/packages/87/bb/b4c620b9eb6e9838c7f7e4b7be0762834443adf9daeb252a214e9ad3178c/fastavro-1.12.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d71c8aa841ef65cfab709a22bb887955f42934bced3ddb571e98fdbdade4c609", size = 3366742, upload-time = "2025-10-10T15:41:55.237Z" }, - { url = "https://files.pythonhosted.org/packages/3d/d1/e69534ccdd5368350646fea7d93be39e5f77c614cca825c990bd9ca58f67/fastavro-1.12.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:b81fc04e85dfccf7c028e0580c606e33aa8472370b767ef058aae2c674a90746", size = 3383743, upload-time = "2025-10-10T15:41:57.68Z" }, - { url = "https://files.pythonhosted.org/packages/58/54/b7b4a0c3fb5fcba38128542da1b26c4e6d69933c923f493548bdfd63ab6a/fastavro-1.12.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:9445da127751ba65975d8e4bdabf36bfcfdad70fc35b2d988e3950cce0ec0e7c", size = 1001377, upload-time = "2025-10-10T15:41:59.241Z" }, - { url = "https://files.pythonhosted.org/packages/1e/4f/0e589089c7df0d8f57d7e5293fdc34efec9a3b758a0d4d0c99a7937e2492/fastavro-1.12.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ed924233272719b5d5a6a0b4d80ef3345fc7e84fc7a382b6232192a9112d38a6", size = 3320401, upload-time = "2025-10-10T15:42:01.682Z" }, - { url = "https://files.pythonhosted.org/packages/f9/19/260110d56194ae29d7e423a336fccea8bcd103196d00f0b364b732bdb84e/fastavro-1.12.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3616e2f0e1c9265e92954fa099db79c6e7817356d3ff34f4bcc92699ae99697c", size = 3350894, upload-time = "2025-10-10T15:42:04.073Z" }, - { url = "https://files.pythonhosted.org/packages/d0/96/58b0411e8be9694d5972bee3167d6c1fd1fdfdf7ce253c1a19a327208f4f/fastavro-1.12.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:cb0337b42fd3c047fcf0e9b7597bd6ad25868de719f29da81eabb6343f08d399", size = 3229644, upload-time = "2025-10-10T15:42:06.221Z" }, - { url = "https://files.pythonhosted.org/packages/5b/db/38660660eac82c30471d9101f45b3acfdcbadfe42d8f7cdb129459a45050/fastavro-1.12.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:64961ab15b74b7c168717bbece5660e0f3d457837c3cc9d9145181d011199fa7", size = 3329704, upload-time = "2025-10-10T15:42:08.384Z" }, - { url = "https://files.pythonhosted.org/packages/9d/a9/1672910f458ecb30b596c9e59e41b7c00309b602a0494341451e92e62747/fastavro-1.12.1-cp314-cp314-win_amd64.whl", hash = "sha256:792356d320f6e757e89f7ac9c22f481e546c886454a6709247f43c0dd7058004", size = 452911, upload-time = "2025-10-10T15:42:09.795Z" }, - { url = "https://files.pythonhosted.org/packages/dc/8d/2e15d0938ded1891b33eff252e8500605508b799c2e57188a933f0bd744c/fastavro-1.12.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:120aaf82ac19d60a1016afe410935fe94728752d9c2d684e267e5b7f0e70f6d9", size = 3541999, upload-time = "2025-10-10T15:42:11.794Z" }, - { url = "https://files.pythonhosted.org/packages/a7/1c/6dfd082a205be4510543221b734b1191299e6a1810c452b6bc76dfa6968e/fastavro-1.12.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b6a3462934b20a74f9ece1daa49c2e4e749bd9a35fa2657b53bf62898fba80f5", size = 3433972, upload-time = "2025-10-10T15:42:14.485Z" }, - { url = "https://files.pythonhosted.org/packages/24/90/9de694625a1a4b727b1ad0958d220cab25a9b6cf7f16a5c7faa9ea7b2261/fastavro-1.12.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1f81011d54dd47b12437b51dd93a70a9aa17b61307abf26542fc3c13efbc6c51", size = 3368752, upload-time = "2025-10-10T15:42:16.618Z" }, - { url = "https://files.pythonhosted.org/packages/fa/93/b44f67589e4d439913dab6720f7e3507b0fa8b8e56d06f6fc875ced26afb/fastavro-1.12.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:43ded16b3f4a9f1a42f5970c2aa618acb23ea59c4fcaa06680bdf470b255e5a8", size = 3386636, upload-time = "2025-10-10T15:42:18.974Z" }, +version = "1.12.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6e/5b/ccb338db71f347e3bc031d268bf6dc41e5ead63b6997b8e72af92f05e18e/fastavro-1.12.2.tar.gz", hash = "sha256:3c79502d56cf6b76210032e1c53494ddfbc73c140bccf2ef4092b3f0825323ab", size = 1030127, upload-time = "2026-04-24T14:36:01.269Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/39/f489a441d41cc9c0a8449fb1325d7a9c9eb57a5634e6ab19dfb0a1105324/fastavro-1.12.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:57bb6b908cb2e05baab63b04c3a31be3b4545a10bfab9748b8763016b5256704", size = 958566, upload-time = "2026-04-24T14:36:45.49Z" }, + { url = "https://files.pythonhosted.org/packages/31/69/776cc025aee2d02acacb734cf690d2fbc295eaadde1b5d47caf8c77a6a2b/fastavro-1.12.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a007f95cc682f56e6d83f1d17c29c00bf719d6fe8e003282b535af3a1ba09c0", size = 3276390, upload-time = "2026-04-24T14:36:47.875Z" }, + { url = "https://files.pythonhosted.org/packages/8c/bc/b7e15fa788f42cbe65827af2ec06c9ad91bb9f72c213110dbef61b53a5b0/fastavro-1.12.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e90460b0cd21f62be3cb26087e706e2cebb7b3fcef9e05b4473b61bb0415b5e", size = 3372779, upload-time = "2026-04-24T14:36:50.122Z" }, + { url = "https://files.pythonhosted.org/packages/79/c2/98993ca810231fc1397212f48c3d46626983722a24bbaaa5c27ee0963751/fastavro-1.12.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7ccd15966b8218d41b06ec3e7c2556be89a8a693026c771e6564d2e40bbaf8ea", size = 3187591, upload-time = "2026-04-24T14:36:52.451Z" }, + { url = "https://files.pythonhosted.org/packages/c6/bb/c180f340eba6478f1b20deccdd17e2b4a4d5074dafd812e3c4254fd035f7/fastavro-1.12.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:06b6971d3dae10cb34353b857d16ad21ebd6f0ea394e86c96abdcad109005d6e", size = 3320589, upload-time = "2026-04-24T14:36:54.647Z" }, + { url = "https://files.pythonhosted.org/packages/4d/e9/aca0456216b5b8992e7b0a8542711b66799c05bfe24c8e32ef6f56e7eb93/fastavro-1.12.2-cp313-cp313-win_amd64.whl", hash = "sha256:98dfcdfaf1498ae2f0e2fafe900a82e8320cc81d8ae5a95b8b8879eaa3298c39", size = 440883, upload-time = "2026-04-24T14:36:56.585Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7e/984896e716af504927be71b80a1e9661aa96c6f9e1e777d52823aacb99f2/fastavro-1.12.2-cp313-cp313-win_arm64.whl", hash = "sha256:3888ef7a51adc77cdf07251bc762566a1be36211e1cff689f13980f3776a2f36", size = 377536, upload-time = "2026-04-24T14:36:58.274Z" }, + { url = "https://files.pythonhosted.org/packages/e9/42/09a1e1f8d9998d73848a6ff0aad6713ae6abf0dbf99918776f8ef33344a7/fastavro-1.12.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:283dcd3129b632021894425974bedd0eb6db3bbf5994e448ccad10db4d803d31", size = 1049506, upload-time = "2026-04-24T14:36:59.797Z" }, + { url = "https://files.pythonhosted.org/packages/52/ef/80cc16f43919d532f25a707f34b275cccc09dca87a05b000fbbfc8e8f255/fastavro-1.12.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d125e210d5a0a1f701f12c0ecad9a03f1b04b5eddbce6ca36a1fc217da977ef", size = 3495899, upload-time = "2026-04-24T14:37:02.306Z" }, + { url = "https://files.pythonhosted.org/packages/c1/54/a0817d1d0236e9e0233f5c996f450cc795b056b8e06edb531f24b9df82ed/fastavro-1.12.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2d4d66afad78e8f47feaa307728a6b71fe3effc63ba2b9eeb109ee687c9bd397", size = 3399232, upload-time = "2026-04-24T14:37:04.837Z" }, + { url = "https://files.pythonhosted.org/packages/38/0a/650f256c15f5875b6081544b9ba7ed8254329213e7e49e3db0aec68b5bee/fastavro-1.12.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2328ec07925c04c89719e3971c9068a165c7fd474ea87675b1204de0440e71ff", size = 3320222, upload-time = "2026-04-24T14:37:07.281Z" }, + { url = "https://files.pythonhosted.org/packages/f5/54/8351d388f94fbb0870e8cffaae41d3cc607acc8d6a8a6a217e2794829593/fastavro-1.12.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:55dea7e74b834d4b70467fc19c5b9ccb5509fe39abc4d26891187c1b22176423", size = 3337096, upload-time = "2026-04-24T14:37:09.452Z" }, + { url = "https://files.pythonhosted.org/packages/da/eb/b36ba9a88826e8c272df02e2f8b5da717e88b6eb508fddca3ca450043731/fastavro-1.12.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8d37c87826ae7195cfbd20fcd448801f2f563bb38f2691ec6574e39cb9eca6c8", size = 963119, upload-time = "2026-04-24T14:37:11.557Z" }, + { url = "https://files.pythonhosted.org/packages/e1/02/3d7f540fb26ba4ea1f4ebd2783c586614da9ac00906a3092e92fd3f104a2/fastavro-1.12.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4c463a3701f293e30d3d62e71e1989f112028d07f87432baf4507eeb57ec3831", size = 3266238, upload-time = "2026-04-24T14:37:13.84Z" }, + { url = "https://files.pythonhosted.org/packages/4c/0b/b77be56c5109da0fc7dcfd7e6b6752fe0a61d0a5c58c6a65e38b4501946a/fastavro-1.12.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f604ba83498e209fff4c7ecc5063a39421dc538dace694bc592f9f338254f3dc", size = 3324020, upload-time = "2026-04-24T14:37:16.096Z" }, + { url = "https://files.pythonhosted.org/packages/e7/6e/951d41f244107e91bf2f59245b71783c03eaab4bdbc960d58316c19652bb/fastavro-1.12.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bfac2dada8ddc002e8b7d8289d6fad4f070bc1fec20371cec684a7d10d932e96", size = 3170160, upload-time = "2026-04-24T14:37:18.168Z" }, + { url = "https://files.pythonhosted.org/packages/94/6f/2adb571fda448d4afd2466e1cef2963fefdc6b37847da05249983e415f17/fastavro-1.12.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:bc44ba6289fb1f5ee318335958dde6ad6d742dcb4bb8930de843e9024c64b68c", size = 3281842, upload-time = "2026-04-24T14:37:20.833Z" }, + { url = "https://files.pythonhosted.org/packages/17/07/4bad2e96c4c6bae40253be2573cc09c1e5b9ccf821e1ff74e0d33b64bf90/fastavro-1.12.2-cp314-cp314-win_amd64.whl", hash = "sha256:a475418f71c5aed69899813ecccf392429c08c3a63df3030129db71760b0db8f", size = 450903, upload-time = "2026-04-24T14:37:23.059Z" }, + { url = "https://files.pythonhosted.org/packages/5b/b7/180f67ba9a46ba23a1ff6432f48d3087d4f2048579ecc262b00426cb1c63/fastavro-1.12.2-cp314-cp314-win_arm64.whl", hash = "sha256:daec9f9655a1d4636613c47d6d3343f6e039150d66cdce62543e20ca36612a8a", size = 391076, upload-time = "2026-04-24T14:37:24.756Z" }, + { url = "https://files.pythonhosted.org/packages/dd/8f/18f60329b627d2118a4a2b19e8741fbd807d60bf0470554e1bbfb7f1bca3/fastavro-1.12.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:57594b72cf663bbd0f3ad8a319a999fc3d7c71065a6799b2c1d1a6a137894c5b", size = 1055430, upload-time = "2026-05-09T21:53:14.364Z" }, + { url = "https://files.pythonhosted.org/packages/d2/ac/a1fa1fc29df0efc89d4946a743b09bdc9500591b5b92083eaf8e93664916/fastavro-1.12.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:74412132bbfb153cbf704517f2c89f7d3e170feb681b13bceace690f66f8d5fa", size = 3503075, upload-time = "2026-04-24T14:37:26.826Z" }, + { url = "https://files.pythonhosted.org/packages/82/bf/4f669e10b6bc38a731ee3400aed1a1e2d0a3e3cf411e72f6b320d3af0eaf/fastavro-1.12.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e367a84c9133018e0a3bc822abe78d7f1f9a6092991a0ec409468cf4ef260282", size = 3410900, upload-time = "2026-04-24T14:37:29.233Z" }, + { url = "https://files.pythonhosted.org/packages/10/39/ecb19fdae4158a7730b5963fbf1b6d38d74678392d73083be518642af0c1/fastavro-1.12.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:044fafca0853e9ae14009de7763ac9e8e8f8b96f8a4e90bd58b695443266a370", size = 3335637, upload-time = "2026-04-24T14:37:31.472Z" }, + { url = "https://files.pythonhosted.org/packages/32/f1/f21bd5319113e89ceceed2df840df21e9c5150d181db74b6ba80400f9f48/fastavro-1.12.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:afede7324822800e4f90e96b9514188a237a60f35e8e7a10b2129c10c78f6e4d", size = 3356664, upload-time = "2026-04-24T14:37:34.231Z" }, ] [[package]] @@ -988,44 +992,74 @@ wheels = [ [[package]] name = "fastmcp" -version = "3.2.4" +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "fastmcp-slim", extra = ["client", "server"] }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9c/f7/5188565d1b93ad611cbd80bf473e7ad669d1f3b689c4bedcd304e1ec3472/fastmcp-3.4.4.tar.gz", hash = "sha256:378202e26ec15b23819d9a1c0d1b0ebda096bc712720532010a0b82a45c2b1df", size = 28796458, upload-time = "2026-07-09T00:32:41.352Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/67/3cef84ba38a23dca1e1e776bfda8a35ab3c7a6c94a8ca81d0715de6dd3c5/fastmcp-3.4.4-py3-none-any.whl", hash = "sha256:f86f208713212260068cf55c32936839eee856fefc7808e18a032f31eb0f718e", size = 8019, upload-time = "2026-07-09T00:32:39.411Z" }, +] + +[[package]] +name = "fastmcp-slim" +version = "3.4.4" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "platformdirs" }, + { name = "pydantic", extra = ["email"] }, + { name = "pydantic-settings" }, + { name = "python-dotenv" }, + { name = "rich" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/45/79/f35661c6a1d76dfbe17a079f912d96fffcfdd40fad5a9144bb9e7dfb1fdf/fastmcp_slim-3.4.4.tar.gz", hash = "sha256:dcaa3e0be2127d7eacdce592c2ef0039204923dc0ec396454615cb4a3275b078", size = 590203, upload-time = "2026-07-09T00:32:20.531Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/91/321e0b2e9ed70d0628b17ddaec76fc7b09f3e1d5d290f70bf101a2890142/fastmcp_slim-3.4.4-py3-none-any.whl", hash = "sha256:9d3a6327b9ee835188eb7323fc3b5d4cd061631b48da8ece56794bb538972505", size = 765158, upload-time = "2026-07-09T00:32:19.11Z" }, +] + +[package.optional-dependencies] +client = [ + { name = "authlib" }, + { name = "exceptiongroup" }, + { name = "httpx" }, + { name = "mcp" }, + { name = "opentelemetry-api" }, + { name = "py-key-value-aio", extra = ["filetree", "keyring", "memory"] }, + { name = "starlette" }, +] +server = [ { name = "authlib" }, { name = "cyclopts" }, { name = "exceptiongroup" }, { name = "griffelib" }, { name = "httpx" }, + { name = "joserfc" }, { name = "jsonref" }, { name = "jsonschema-path" }, { name = "mcp" }, { name = "openapi-pydantic" }, { name = "opentelemetry-api" }, { name = "packaging" }, - { name = "platformdirs" }, { name = "py-key-value-aio", extra = ["filetree", "keyring", "memory"] }, - { name = "pydantic", extra = ["email"] }, { name = "pyperclip" }, - { name = "python-dotenv" }, + { name = "python-multipart" }, { name = "pyyaml" }, - { name = "rich" }, + { name = "starlette" }, { name = "uncalled-for" }, { name = "uvicorn" }, { name = "watchfiles" }, { name = "websockets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9c/13/29544fbc6dfe45ea38046af0067311e0bad7acc7d1f2ad38bb08f2409fe2/fastmcp-3.2.4.tar.gz", hash = "sha256:083ecb75b44a4169e7fc0f632f94b781bdb0ff877c6b35b9877cbb566fd4d4d1", size = 28746127, upload-time = "2026-04-14T01:42:24.174Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/cf/76/b310d52fa0e30d39bd937eb58ec2c1f1ea1b5f519f0575e9dd9612f01deb/fastmcp-3.2.4-py3-none-any.whl", hash = "sha256:e6c9c429171041455e47ab94bb3f83c4657622a0ec28922f6940053959bd58a9", size = 728599, upload-time = "2026-04-14T01:42:26.85Z" }, -] [[package]] name = "filelock" -version = "3.28.0" +version = "3.29.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d6/17/6e8890271880903e3538660a21d63a6c1fea969ac71d0d6b608b78727fa9/filelock-3.28.0.tar.gz", hash = "sha256:4ed1010aae813c4ee8d9c660e4792475ee60c4a0ba76073ceaf862bd317e3ca6", size = 56474, upload-time = "2026-04-14T22:54:33.625Z" } +sdist = { url = "https://files.pythonhosted.org/packages/35/94/00f2059e4835eace3ae8fde680b932c496f8ec7bdc99168dfa53fb2e6b79/filelock-3.29.7.tar.gz", hash = "sha256:5b481979797ae69e72f0b389d89a80bdd585c260c5b3f1fb9c0a5ba9bb3f195d", size = 71521, upload-time = "2026-07-08T05:46:58.716Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/21/2f728888c45033d34a417bfcd248ea2564c9e08ab1bfd301377cf05d5586/filelock-3.28.0-py3-none-any.whl", hash = "sha256:de9af6712788e7171df1b28b15eba2446c69721433fa427a9bee07b17820a9db", size = 39189, upload-time = "2026-04-14T22:54:32.037Z" }, + { url = "https://files.pythonhosted.org/packages/60/02/be4a57b60c7149b55b9e3b3c13f609cd8eb5307c751f22bd8fb8d262e75b/filelock-3.29.7-py3-none-any.whl", hash = "sha256:987db6f789a3a2a59f55081801b2b3697cb97e2a736b5f1a9e99b559285fbc51", size = 46036, upload-time = "2026-07-08T05:46:57.53Z" }, ] [[package]] @@ -1145,24 +1179,24 @@ wheels = [ [[package]] name = "fsspec" -version = "2026.3.0" +version = "2026.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e1/cf/b50ddf667c15276a9ab15a70ef5f257564de271957933ffea49d2cdbcdfb/fsspec-2026.3.0.tar.gz", hash = "sha256:1ee6a0e28677557f8c2f994e3eea77db6392b4de9cd1f5d7a9e87a0ae9d01b41", size = 313547, upload-time = "2026-03-27T19:11:14.892Z" } +sdist = { url = "https://files.pythonhosted.org/packages/10/a1/ae4e3e5003468d6391d2c77b6fa1cd73bd5d13511d81c642d7b28ac90ed4/fsspec-2026.6.0.tar.gz", hash = "sha256:f5bac145310fe30e16e1471bd6840b2d990d609e872251d7e674241822abf01a", size = 313646, upload-time = "2026-06-16T01:57:28.105Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d5/1f/5f4a3cd9e4440e9d9bc78ad0a91a1c8d46b4d429d5239ebe6793c9fe5c41/fsspec-2026.3.0-py3-none-any.whl", hash = "sha256:d2ceafaad1b3457968ed14efa28798162f1638dbb5d2a6868a2db002a5ee39a4", size = 202595, upload-time = "2026-03-27T19:11:13.595Z" }, + { url = "https://files.pythonhosted.org/packages/e5/22/4222d7ddf3da30f363edaa98e329c2bce6c65497c9cb2810931c8b2c0fbc/fsspec-2026.6.0-py3-none-any.whl", hash = "sha256:02e0b71817df9b2169dc30a16832045764def1191b43dcff5bb85bdee212d2a1", size = 203949, upload-time = "2026-06-16T01:57:26.358Z" }, ] [[package]] name = "genai-prices" -version = "0.0.56" +version = "0.0.71" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "httpx" }, + { name = "httpx2" }, { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/44/6b/94b3018a672c7775edfb485f0fed8f6068fba75e49b067e8a1ac5eb96764/genai_prices-0.0.56.tar.gz", hash = "sha256:ac24b16a84d0ab97539bfa48dfa4649689de8e3ce71c12ebacef29efb1998045", size = 65872, upload-time = "2026-03-20T20:33:00.732Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/e4/5072862613fba039da2b7c981a8649c6c6bbcb2863bd8bc81617c09ce5ee/genai_prices-0.0.71.tar.gz", hash = "sha256:de4db34ec38404f9ef383cb1ab29e204d16ccf27071af0b16d5747ee7affe36b", size = 82105, upload-time = "2026-07-10T00:38:30.491Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/f6/8ef7e4c286deb2709d11ca96a5237caae3ef4876ab3c48095856cfd2df30/genai_prices-0.0.56-py3-none-any.whl", hash = "sha256:dbe86be8f3f556bed1b72209ed36851fec8b01793b3b220f42921a4e7da945f6", size = 68966, upload-time = "2026-03-20T20:33:02.555Z" }, + { url = "https://files.pythonhosted.org/packages/0d/98/c06c1318f6834a26268a2d4280e4183f60c5ea92152aea841feff29826ff/genai_prices-0.0.71-py3-none-any.whl", hash = "sha256:1d13111563af2b1ce43ccfacf77b7ac3216ad704c644408a56e11b181fe0d128", size = 84586, upload-time = "2026-07-10T00:38:29.252Z" }, ] [[package]] @@ -1245,7 +1279,7 @@ wheels = [ [[package]] name = "groq" -version = "1.1.2" +version = "1.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -1255,40 +1289,40 @@ dependencies = [ { name = "sniffio" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/c7/a2153b639062f59f9bc93a1b5507c0c4a6b654b8a9edbf432ec2f4a62d2d/groq-1.1.2.tar.gz", hash = "sha256:9ec2b5b6a1c4856a8c6c38741353c5ab37472a4e3fded02af783750d849cc988", size = 154033, upload-time = "2026-03-25T23:16:10.313Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/3a/31c54e35d694ba9c263939ecae0874c78fc51f22b9fb13d2dab50edf6e3c/groq-1.5.0.tar.gz", hash = "sha256:8648388f8668629490bb0eab11252b4cc43316149fda9c6343e9a9577fe7df88", size = 158239, upload-time = "2026-06-21T22:57:30.202Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/34/b0/83e3892a4597a4b8ebf8a662aeaf314765c4c2340516eb1d049b459b24fc/groq-1.1.2-py3-none-any.whl", hash = "sha256:348cb7a674b6aa7105719b533f6fc48fd32b503bc9256924aaed6dc186f778b5", size = 141700, upload-time = "2026-03-25T23:16:08.998Z" }, + { url = "https://files.pythonhosted.org/packages/ce/c3/cac2aee75198c0382a2e6070619a798de2288e01850abb6e999746bb92e3/groq-1.5.0-py3-none-any.whl", hash = "sha256:a927fd75b4a60cb066c408cb4f9647536caf74e241ea75bc57d1810501b8203f", size = 143690, upload-time = "2026-06-21T22:57:29.213Z" }, ] [[package]] name = "grpcio" -version = "1.80.0" +version = "1.82.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b7/48/af6173dbca4454f4637a4678b67f52ca7e0c1ed7d5894d89d434fecede05/grpcio-1.80.0.tar.gz", hash = "sha256:29aca15edd0688c22ba01d7cc01cb000d72b2033f4a3c72a81a19b56fd143257", size = 12978905, upload-time = "2026-03-30T08:49:10.502Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/2f/3a/7c3c25789e3f069e581dc342e03613c5b1cb012c4e8c7d9d5cf960a75856/grpcio-1.80.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:e9e408fc016dffd20661f0126c53d8a31c2821b5c13c5d67a0f5ed5de93319ad", size = 6017243, upload-time = "2026-03-30T08:47:40.075Z" }, - { url = "https://files.pythonhosted.org/packages/04/19/21a9806eb8240e174fd1ab0cd5b9aa948bb0e05c2f2f55f9d5d7405e6d08/grpcio-1.80.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:92d787312e613754d4d8b9ca6d3297e69994a7912a32fa38c4c4e01c272974b0", size = 12010840, upload-time = "2026-03-30T08:47:43.11Z" }, - { url = "https://files.pythonhosted.org/packages/18/3a/23347d35f76f639e807fb7a36fad3068aed100996849a33809591f26eca6/grpcio-1.80.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8ac393b58aa16991a2f1144ec578084d544038c12242da3a215966b512904d0f", size = 6567644, upload-time = "2026-03-30T08:47:46.806Z" }, - { url = "https://files.pythonhosted.org/packages/ff/40/96e07ecb604a6a67ae6ab151e3e35b132875d98bc68ec65f3e5ab3e781d7/grpcio-1.80.0-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:68e5851ac4b9afe07e7f84483803ad167852570d65326b34d54ca560bfa53fb6", size = 7277830, upload-time = "2026-03-30T08:47:49.643Z" }, - { url = "https://files.pythonhosted.org/packages/9b/e2/da1506ecea1f34a5e365964644b35edef53803052b763ca214ba3870c856/grpcio-1.80.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:873ff5d17d68992ef6605330127425d2fc4e77e612fa3c3e0ed4e668685e3140", size = 6783216, upload-time = "2026-03-30T08:47:52.817Z" }, - { url = "https://files.pythonhosted.org/packages/44/83/3b20ff58d0c3b7f6caaa3af9a4174d4023701df40a3f39f7f1c8e7c48f9d/grpcio-1.80.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2bea16af2750fd0a899bf1abd9022244418b55d1f37da2202249ba4ba673838d", size = 7385866, upload-time = "2026-03-30T08:47:55.687Z" }, - { url = "https://files.pythonhosted.org/packages/47/45/55c507599c5520416de5eefecc927d6a0d7af55e91cfffb2e410607e5744/grpcio-1.80.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba0db34f7e1d803a878284cd70e4c63cb6ae2510ba51937bf8f45ba997cefcf7", size = 8391602, upload-time = "2026-03-30T08:47:58.303Z" }, - { url = "https://files.pythonhosted.org/packages/10/bb/dd06f4c24c01db9cf11341b547d0a016b2c90ed7dbbb086a5710df7dd1d7/grpcio-1.80.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8eb613f02d34721f1acf3626dfdb3545bd3c8505b0e52bf8b5710a28d02e8aa7", size = 7826752, upload-time = "2026-03-30T08:48:01.311Z" }, - { url = "https://files.pythonhosted.org/packages/f9/1e/9d67992ba23371fd63d4527096eb8c6b76d74d52b500df992a3343fd7251/grpcio-1.80.0-cp313-cp313-win32.whl", hash = "sha256:93b6f823810720912fd131f561f91f5fed0fda372b6b7028a2681b8194d5d294", size = 4142310, upload-time = "2026-03-30T08:48:04.594Z" }, - { url = "https://files.pythonhosted.org/packages/cf/e6/283326a27da9e2c3038bc93eeea36fb118ce0b2d03922a9cda6688f53c5b/grpcio-1.80.0-cp313-cp313-win_amd64.whl", hash = "sha256:e172cf795a3ba5246d3529e4d34c53db70e888fa582a8ffebd2e6e48bc0cba50", size = 4882833, upload-time = "2026-03-30T08:48:07.363Z" }, - { url = "https://files.pythonhosted.org/packages/c5/6d/e65307ce20f5a09244ba9e9d8476e99fb039de7154f37fb85f26978b59c3/grpcio-1.80.0-cp314-cp314-linux_armv7l.whl", hash = "sha256:3d4147a97c8344d065d01bbf8b6acec2cf86fb0400d40696c8bdad34a64ffc0e", size = 6017376, upload-time = "2026-03-30T08:48:10.005Z" }, - { url = "https://files.pythonhosted.org/packages/69/10/9cef5d9650c72625a699c549940f0abb3c4bfdb5ed45a5ce431f92f31806/grpcio-1.80.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:d8e11f167935b3eb089ac9038e1a063e6d7dbe995c0bb4a661e614583352e76f", size = 12018133, upload-time = "2026-03-30T08:48:12.927Z" }, - { url = "https://files.pythonhosted.org/packages/04/82/983aabaad82ba26113caceeb9091706a0696b25da004fe3defb5b346e15b/grpcio-1.80.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f14b618fc30de822681ee986cfdcc2d9327229dc4c98aed16896761cacd468b9", size = 6574748, upload-time = "2026-03-30T08:48:16.386Z" }, - { url = "https://files.pythonhosted.org/packages/07/d7/031666ef155aa0bf399ed7e19439656c38bbd143779ae0861b038ce82abd/grpcio-1.80.0-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:4ed39fbdcf9b87370f6e8df4e39ca7b38b3e5e9d1b0013c7b6be9639d6578d14", size = 7277711, upload-time = "2026-03-30T08:48:19.627Z" }, - { url = "https://files.pythonhosted.org/packages/e8/43/f437a78f7f4f1d311804189e8f11fb311a01049b2e08557c1068d470cb2e/grpcio-1.80.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2dcc70e9f0ba987526e8e8603a610fb4f460e42899e74e7a518bf3c68fe1bf05", size = 6785372, upload-time = "2026-03-30T08:48:22.373Z" }, - { url = "https://files.pythonhosted.org/packages/93/3d/f6558e9c6296cb4227faa5c43c54a34c68d32654b829f53288313d16a86e/grpcio-1.80.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:448c884b668b868562b1bda833c5fce6272d26e1926ec46747cda05741d302c1", size = 7395268, upload-time = "2026-03-30T08:48:25.638Z" }, - { url = "https://files.pythonhosted.org/packages/06/21/0fdd77e84720b08843c371a2efa6f2e19dbebf56adc72df73d891f5506f0/grpcio-1.80.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a1dc80fe55685b4a543555e6eef975303b36c8db1023b1599b094b92aa77965f", size = 8392000, upload-time = "2026-03-30T08:48:28.974Z" }, - { url = "https://files.pythonhosted.org/packages/f5/68/67f4947ed55d2e69f2cc199ab9fd85e0a0034d813bbeef84df6d2ba4d4b7/grpcio-1.80.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:31b9ac4ad1aa28ffee5503821fafd09e4da0a261ce1c1281c6c8da0423c83b6e", size = 7828477, upload-time = "2026-03-30T08:48:32.054Z" }, - { url = "https://files.pythonhosted.org/packages/44/b6/8d4096691b2e385e8271911a0de4f35f0a6c7d05aff7098e296c3de86939/grpcio-1.80.0-cp314-cp314-win32.whl", hash = "sha256:367ce30ba67d05e0592470428f0ec1c31714cab9ef19b8f2e37be1f4c7d32fae", size = 4218563, upload-time = "2026-03-30T08:48:34.538Z" }, - { url = "https://files.pythonhosted.org/packages/e5/8c/bbe6baf2557262834f2070cf668515fa308b2d38a4bbf771f8f7872a7036/grpcio-1.80.0-cp314-cp314-win_amd64.whl", hash = "sha256:3b01e1f5464c583d2f567b2e46ff0d516ef979978f72091fd81f5ab7fa6e2e7f", size = 5019457, upload-time = "2026-03-30T08:48:37.308Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/90/bc/656b89387d6f4ed7e0686c7b64c2ae7e554a759aa58122c8e5fb99392c32/grpcio-1.82.1.tar.gz", hash = "sha256:707b24abd90fcb1e45bcc080577da1dbf9971d107490589b9539af8e1e77b4b5", size = 13187300, upload-time = "2026-07-08T12:36:16.588Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/3e/496992d08c0aaa11272eb6228dc8ab947da01fe835de243cd00521bce4c4/grpcio-1.82.1-cp313-cp313-linux_armv7l.whl", hash = "sha256:b454a2d97bfab7565683a02345f86bd182ab69fd7c2bdb7414171e7538f266b1", size = 6146068, upload-time = "2026-07-08T12:35:21.365Z" }, + { url = "https://files.pythonhosted.org/packages/e7/8f/f263d6f14fdba6b56cfadd91fd3e158a52682b72c6016d1f8723d435659f/grpcio-1.82.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:3dde70abfc80b3be11de53ba0d601c439e7fb2afd3583ad1788d1146bec92fdc", size = 11948600, upload-time = "2026-07-08T12:35:24.312Z" }, + { url = "https://files.pythonhosted.org/packages/8c/14/3a02e6ee49c2d85bc15eaae321e0e11ab3542cad3c5b2de121ecce0c4296/grpcio-1.82.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f5523099c98c292ea1ae08e617249db760c56a78f8deae879027fe7d1ffbcbf6", size = 6714591, upload-time = "2026-07-08T12:35:27.027Z" }, + { url = "https://files.pythonhosted.org/packages/69/80/58e3738696f48ab7645347b98d8a7f93d10e00e6218388fbfcd6c9310e3d/grpcio-1.82.1-cp313-cp313-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:5e5c4dc0a59b0f8490a6bdfd6fc8395b9d8ad8a8407c7d67ca7b5bba15c0877f", size = 7454995, upload-time = "2026-07-08T12:35:29.599Z" }, + { url = "https://files.pythonhosted.org/packages/f3/6c/2557c1a889363072fbf2285ecd0e8c44860d4dbd60f017a32537c5b863e2/grpcio-1.82.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c40d94ba820329cc191981bc22fa6f6eed0799c6d921f3c6709521d59d4a2fd7", size = 6888621, upload-time = "2026-07-08T12:35:32.38Z" }, + { url = "https://files.pythonhosted.org/packages/d2/66/907706ccaff1223f1e10fd5b37fc16faead43392fccb4e786e7e390ac141/grpcio-1.82.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4c816180e31e273caaec6f8bd86a8392499d5bbb26f41da44e3dce48bde69095", size = 7505069, upload-time = "2026-07-08T12:35:35.072Z" }, + { url = "https://files.pythonhosted.org/packages/b3/7c/ff97b0d0f635987ee5ec80dfedafa1aad629303745d48e8637d10eec5b80/grpcio-1.82.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e31fd780b261830720cb70b0fd8f0aa51d49e75a66d7464ad2e31d4b765f2580", size = 8535384, upload-time = "2026-07-08T12:35:37.954Z" }, + { url = "https://files.pythonhosted.org/packages/62/9e/a97fddd970a8d1588cade06eca20443761c1858b0ad6590a5c835aa18062/grpcio-1.82.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9d76152d7c31d7210d4a106e5d8b64da5bba5d6abf11be30e2f7b0a0c59bbcbf", size = 7910707, upload-time = "2026-07-08T12:35:40.797Z" }, + { url = "https://files.pythonhosted.org/packages/20/e4/eaba1517888af483a88d449eb7566f0f7f63446d46f339c5891798435875/grpcio-1.82.1-cp313-cp313-win32.whl", hash = "sha256:38e9dcb5258226fb3282630b31b16a968df52c8c6ad514af540646e0a4578f8a", size = 4240363, upload-time = "2026-07-08T12:35:43.298Z" }, + { url = "https://files.pythonhosted.org/packages/b0/42/66a98d47732e35290bef722f6149fed3709cd4cf61166f6f53a12f417302/grpcio-1.82.1-cp313-cp313-win_amd64.whl", hash = "sha256:3dbfb52c36d9511ac2b8e6c94fdde837b393ae520cc321f52a333a2deedf5a90", size = 5000980, upload-time = "2026-07-08T12:35:46.262Z" }, + { url = "https://files.pythonhosted.org/packages/b4/cb/cf9ae9e164c6e6dc8a494faa9771763df9da150eefe19671009624d1559f/grpcio-1.82.1-cp314-cp314-linux_armv7l.whl", hash = "sha256:35f990f7784c8fd2872644f07f96ebb4d9e48e145a190ab80d0280af91a1bfb2", size = 6146901, upload-time = "2026-07-08T12:35:49.261Z" }, + { url = "https://files.pythonhosted.org/packages/3b/2a/eccf26dbcfb7f7cab8027c5490a16c8937c5aa7a2ec20a3eab2cf7a43165/grpcio-1.82.1-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:46536a4a1f4434df3c851b9254ff6fc7df5705b273681a15ca277d5921c178a0", size = 11954756, upload-time = "2026-07-08T12:35:52.196Z" }, + { url = "https://files.pythonhosted.org/packages/ad/75/3b3b4a3cc9f084b026af96e1d3e539b1af29ec7f41ed0dfff3cb99cc8626/grpcio-1.82.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d6650a7c1ebb7921c70e12a385439a8118efb99e669fa9ed31cf25db1843937c", size = 6723087, upload-time = "2026-07-08T12:35:54.973Z" }, + { url = "https://files.pythonhosted.org/packages/9c/8b/b0f0c9b1400a99a4da4c09b114f101b192f8f11192e76f620b8962f5d90b/grpcio-1.82.1-cp314-cp314-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:b8e110c66df5204c0506d6c8787b35d48b8b699ef5aa366d6c4d67325c67fe9a", size = 7454542, upload-time = "2026-07-08T12:35:57.586Z" }, + { url = "https://files.pythonhosted.org/packages/b7/bd/428e38868382aa193697a5aa53973f29c58e58ba4268aa0c86a2715ee58b/grpcio-1.82.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f853eae07235a51a27bb5d6a9a175a59ca55dc9b99edc6ce2f76f07332d333ae", size = 6889588, upload-time = "2026-07-08T12:36:00.012Z" }, + { url = "https://files.pythonhosted.org/packages/49/ce/03e01d5e10259bf5c08ee50570cc94724e79c956f61fd2f09b341af0956c/grpcio-1.82.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:60b0f2c95337694fc094b77d9f60f50566c84b5677393e342eb98daeee242d98", size = 7514166, upload-time = "2026-07-08T12:36:02.693Z" }, + { url = "https://files.pythonhosted.org/packages/ff/59/278b4b600329e2ba3849f3c1ea3c820b3a01b38a7ad184ba09595e8d2733/grpcio-1.82.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:b064fc444812bdaa9825d33c26f8d732d63ee6a5d78557c1faf92c98687fed27", size = 8536166, upload-time = "2026-07-08T12:36:05.349Z" }, + { url = "https://files.pythonhosted.org/packages/44/27/7ccf2ef00f27a8e47a79d641c8ceaf7d3028c7a03d9a97b4c8a9a783c086/grpcio-1.82.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7d7ede11d747b4e1bd05e3bc0260e155b65a88735a895a10f6521f19b889511e", size = 7912572, upload-time = "2026-07-08T12:36:08.393Z" }, + { url = "https://files.pythonhosted.org/packages/0d/be/33742482d2753f2d3a1b7641664b6622262d44f2f3b609f13425dd86d36f/grpcio-1.82.1-cp314-cp314-win32.whl", hash = "sha256:3d21f19838dc255ecbb79321b15ae9b98fbddff4c3d4aedb0a81bdd7f4ab572a", size = 4321856, upload-time = "2026-07-08T12:36:10.899Z" }, + { url = "https://files.pythonhosted.org/packages/cc/67/03329c847172c78ddeb1eb9be6b444fdbc12775a84c958b27e427e7b926d/grpcio-1.82.1-cp314-cp314-win_amd64.whl", hash = "sha256:e20f1edbb15f99e3128ec86433f9785fd5a451d8f115e74fe0056134f092a9d5", size = 5141114, upload-time = "2026-07-08T12:36:13.595Z" }, ] [[package]] @@ -1415,6 +1449,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, ] +[[package]] +name = "httpcore2" +version = "2.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "h11" }, + { name = "truststore" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/53/db/2ad49878b36af4cff7527c1158b083ad6d9350462f1a35685cc3ebfa7c2b/httpcore2-2.6.0.tar.gz", hash = "sha256:95b692b582402ec49b3d84c2343556e4ac4c0962c8b3d39c48d485b9ecc240ab", size = 65592, upload-time = "2026-07-14T10:48:33.816Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/fa/08f483851a70ef10806e3b84240f2a8f923658035b794f7609795895d9ea/httpcore2-2.6.0-py3-none-any.whl", hash = "sha256:c237a45c7eef885cf032cb9b850d59fcf1fa7e00230307f08aab26486a6ed584", size = 81507, upload-time = "2026-07-14T10:48:31.504Z" }, +] + [[package]] name = "httpx" version = "0.28.1" @@ -1439,11 +1486,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d2/fd/6668e5aec43ab844de6fc74927e155a3b37bf40d7c3790e49fc0406b6578/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc", size = 8960, upload-time = "2025-10-10T21:48:21.158Z" }, ] +[[package]] +name = "httpx2" +version = "2.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "httpcore2" }, + { name = "idna" }, + { name = "truststore" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/89/17/1e142bf3c76684232a092e1e4002be07fd3403b1c2dcb15d0012ea300c8f/httpx2-2.6.0.tar.gz", hash = "sha256:5d362fd59562cf2139a60c67bb016587a70b36156a517f176c7cbf1587d1ab22", size = 92736, upload-time = "2026-07-14T10:48:35.12Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/86/7d82f7c6aac32433eaf0c914b8bc870ae25759413b9d7d52ea6aa15f2546/httpx2-2.6.0-py3-none-any.whl", hash = "sha256:6cccc3665d6bceb3c1c4f1422ae7e53fda67a853f0135f09b25ce0d4dcac01e3", size = 88541, upload-time = "2026-07-14T10:48:32.681Z" }, +] + [[package]] name = "huggingface-hub" -version = "1.11.0" +version = "1.18.0" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "click" }, { name = "filelock" }, { name = "fsspec" }, { name = "hf-xet", marker = "platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, @@ -1454,9 +1517,9 @@ dependencies = [ { name = "typer" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/dc/89/e7aa12d8a6b9259bed10671abb25ae6fa437c0f88a86ecbf59617bae7759/huggingface_hub-1.11.0.tar.gz", hash = "sha256:15fb3713c7f9cdff7b808a94fd91664f661ab142796bb48c9cd9493e8d166278", size = 761749, upload-time = "2026-04-16T13:07:39.73Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fb/d8/748ea0a47f0fa15227fe682f7a80826b4b7c096e4818044b8f56d6cb66d6/huggingface_hub-1.18.0.tar.gz", hash = "sha256:f0c5ecd1ef8c6a60f86f61ee278f2c1570ba9e279c9f54de9094210723b3613b", size = 812699, upload-time = "2026-06-05T09:26:33.401Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/37/02/4f3f8997d1ea7fe0146b343e5e14bd065fa87af790d07e5576d31b31cc18/huggingface_hub-1.11.0-py3-none-any.whl", hash = "sha256:42a6de0afbfeb5e022222d36398f029679db4eb4778801aafda32257ae9131ab", size = 645499, upload-time = "2026-04-16T13:07:37.716Z" }, + { url = "https://files.pythonhosted.org/packages/0b/03/40a05316cb6616e5b7efd7773656441ab04b4b022c2199e79bb4622a92a3/huggingface_hub-1.18.0-py3-none-any.whl", hash = "sha256:729be4a976fb706dcc02d176bcda8a3f32bdf21a294e8f4b3dda6fbcbc9c1ab1", size = 684411, upload-time = "2026-06-05T09:26:31.48Z" }, ] [[package]] @@ -1470,11 +1533,11 @@ wheels = [ [[package]] name = "idna" -version = "3.11" +version = "3.18" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +sdist = { url = "https://files.pythonhosted.org/packages/cd/63/9496c57188a2ee585e0f1db071d75089a11e98aa86eb99d9d7618fc1edce/idna-3.18.tar.gz", hash = "sha256:ffb385a7e039654cef1ab9ef32c6fafe283c0c0467bba1d9029738ce4a14a848", size = 196711, upload-time = "2026-06-02T14:34:07.794Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, + { url = "https://files.pythonhosted.org/packages/1e/5e/d4e9f1a599fb8e573b7b87160658329fbf28d19eac2718f51fc3def3aa5a/idna-3.18-py3-none-any.whl", hash = "sha256:7f952cbe720b688055e3f87de14f5c3e5fdaa8bc3928985c4077ca689de849a2", size = 65455, upload-time = "2026-06-02T14:34:06.319Z" }, ] [[package]] @@ -1733,6 +1796,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl", hash = "sha256:5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713", size = 309071, upload-time = "2025-12-15T08:41:44.973Z" }, ] +[[package]] +name = "joserfc" +version = "1.7.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d4/c6/b1cac0280f8efc57626ea8804866b37099f23cae11b1485a42b213245e31/joserfc-1.7.3.tar.gz", hash = "sha256:116955c2587139dba20621fd0bd7fc9255fa960c9fe7f43c43ebef2e801dcfcf", size = 233821, upload-time = "2026-07-08T12:41:42.66Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7d/f5/650b59d1b74f5befb7a7a7e7d7c92a26b94256df3541e2b4914152cd177a/joserfc-1.7.3-py3-none-any.whl", hash = "sha256:7c39f3f2c943dbc03122747fa8ebbd8e156e54904cf25651b452f4d2634a6075", size = 70982, upload-time = "2026-07-08T12:41:41.521Z" }, +] + [[package]] name = "json5" version = "0.14.0" @@ -1744,11 +1819,11 @@ wheels = [ [[package]] name = "jsonpath-python" -version = "1.1.5" +version = "1.1.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/2d/db/2f4ecc24da35c6142b39c353d5b7c16eef955cc94b35a48d3fa47996d7c3/jsonpath_python-1.1.5.tar.gz", hash = "sha256:ceea2efd9e56add09330a2c9631ea3d55297b9619348c1055e5bfb9cb0b8c538", size = 87352, upload-time = "2026-03-17T06:16:40.597Z" } +sdist = { url = "https://files.pythonhosted.org/packages/98/18/4ca8742534a5993ff383f7602e325ce2d5d7cc93d72ac5e1cdedbea8a458/jsonpath_python-1.1.6.tar.gz", hash = "sha256:dded9932b4ec41fb8726e09c83afa4e6be618f938c2db287cc2a81723c639671", size = 88178, upload-time = "2026-05-07T01:26:34.482Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/28/50/1a313fb700526b134c71eb8a225d8b83be0385dbb0204337b4379c698cef/jsonpath_python-1.1.5-py3-none-any.whl", hash = "sha256:a60315404d70a65e76c9a782c84e50600480221d94a58af47b7b4d437351cb4b", size = 14090, upload-time = "2026-03-17T06:16:39.152Z" }, + { url = "https://files.pythonhosted.org/packages/55/8a/1270a6803bd821cbfcdda387eaa13cb41a7b1f7b9bd145979b3bfb9d6cb7/jsonpath_python-1.1.6-py3-none-any.whl", hash = "sha256:a1c50afd8d3fbbaf47a4873bc890dcb3c15da96f5c020327977d844d8731a2d4", size = 14453, upload-time = "2026-05-07T01:26:33.306Z" }, ] [[package]] @@ -1799,16 +1874,17 @@ format-nongpl = [ [[package]] name = "jsonschema-path" -version = "0.4.5" +version = "0.5.0" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "attrs" }, { name = "pathable" }, { name = "pyyaml" }, { name = "referencing" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5b/8a/7e6102f2b8bdc6705a9eb5294f8f6f9ccd3a8420e8e8e19671d1dd773251/jsonschema_path-0.4.5.tar.gz", hash = "sha256:c6cd7d577ae290c7defd4f4029e86fdb248ca1bd41a07557795b3c95e5144918", size = 15113, upload-time = "2026-03-03T09:56:46.87Z" } +sdist = { url = "https://files.pythonhosted.org/packages/39/79/cd02a4df6d9270efdc7d3feefe6edd730b0820c39eeaa107a2faee8322d5/jsonschema_path-0.5.0.tar.gz", hash = "sha256:493b156ba895c97602655b620a8456caa2ce08c1aa389f5a7addec065e6e855c", size = 19597, upload-time = "2026-05-19T20:45:00.971Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/d5/4e96c44f6c1ea3d812cf5391d81a4f5abaa540abf8d04ecd7f66e0ed11df/jsonschema_path-0.4.5-py3-none-any.whl", hash = "sha256:7d77a2c3f3ec569a40efe5c5f942c44c1af2a6f96fe0866794c9ef5b8f87fd65", size = 19368, upload-time = "2026-03-03T09:56:45.39Z" }, + { url = "https://files.pythonhosted.org/packages/04/2c/9e69d73c4297508be9e3b64a970ea3971b3eb8db64ffc5802d40bd25981f/jsonschema_path-0.5.0-py3-none-any.whl", hash = "sha256:2790a070bc7abb08ea3dbe4d340ece4efadf639223001f020c7503229ba068e2", size = 24077, upload-time = "2026-05-19T20:44:59.225Z" }, ] [[package]] @@ -1840,6 +1916,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/38/64/285f20a31679bf547b75602702f7800e74dbabae36ef324f716c02804753/jupyter-1.1.1-py2.py3-none-any.whl", hash = "sha256:7a59533c22af65439b24bbe60373a4e95af8f16ac65a6c00820ad378e3f7cc83", size = 2657, upload-time = "2024-08-30T07:15:47.045Z" }, ] +[[package]] +name = "jupyter-builder" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jupyter-core" }, + { name = "traitlets" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0d/df/db5efc4e28803a1421350445e53d85ec571a4e6928e3524ccc39f4e16584/jupyter_builder-1.1.0.tar.gz", hash = "sha256:b996e8af616900f18724fa34883169d869be2205497940bec78a3a1031eb897d", size = 971142, upload-time = "2026-07-11T07:24:02.4Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/fb/53adbc72931b4429275b160044c3e1bcbc8fbb5ea6b8f318a357834842fb/jupyter_builder-1.1.0-py3-none-any.whl", hash = "sha256:d9d5280e8845f726663545c3a6db55bd205127616eeb8958481091d6cba71b6a", size = 912964, upload-time = "2026-07-11T07:24:00.279Z" }, +] + [[package]] name = "jupyter-client" version = "8.8.0" @@ -1921,7 +2010,7 @@ wheels = [ [[package]] name = "jupyter-server" -version = "2.17.0" +version = "2.20.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -1943,9 +2032,9 @@ dependencies = [ { name = "traitlets" }, { name = "websocket-client" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5b/ac/e040ec363d7b6b1f11304cc9f209dac4517ece5d5e01821366b924a64a50/jupyter_server-2.17.0.tar.gz", hash = "sha256:c38ea898566964c888b4772ae1ed58eca84592e88251d2cfc4d171f81f7e99d5", size = 731949, upload-time = "2025-08-21T14:42:54.042Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/dc/db3a582633170186f8c8b31298d7eb26ad0eb031a1f53476c258b64eed05/jupyter_server-2.20.0.tar.gz", hash = "sha256:b5778ba337d8015a3dc2b80803ecdd5ac18d3797fddf61a50ea5fb472b4ebe14", size = 756523, upload-time = "2026-06-17T12:09:09.435Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl", hash = "sha256:e8cb9c7db4251f51ed307e329b81b72ccf2056ff82d50524debde1ee1870e13f", size = 388221, upload-time = "2025-08-21T14:42:52.034Z" }, + { url = "https://files.pythonhosted.org/packages/f3/71/8c002223e873a870f5c41dc69b0a7c922301123e4a31d5d01ecb700aef77/jupyter_server-2.20.0-py3-none-any.whl", hash = "sha256:c3b67c93c471e947c18b5026f04f21614218adb706df8f48227d3ee8e0a7cdcc", size = 393143, upload-time = "2026-06-17T12:09:07.234Z" }, ] [[package]] @@ -1963,26 +2052,26 @@ wheels = [ [[package]] name = "jupyterlab" -version = "4.5.6" +version = "4.6.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "async-lru" }, { name = "httpx" }, { name = "ipykernel" }, { name = "jinja2" }, + { name = "jupyter-builder" }, { name = "jupyter-core" }, { name = "jupyter-lsp" }, { name = "jupyter-server" }, { name = "jupyterlab-server" }, { name = "notebook-shim" }, { name = "packaging" }, - { name = "setuptools" }, { name = "tornado" }, { name = "traitlets" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ac/d5/730628e03fff2e8a8e8ccdaedde1489ab1309f9a4fa2536248884e30b7c7/jupyterlab-4.5.6.tar.gz", hash = "sha256:642fe2cfe7f0f5922a8a558ba7a0d246c7bc133b708dfe43f7b3a826d163cf42", size = 23970670, upload-time = "2026-03-11T14:17:04.531Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bc/2a/d6af53bfd45a43a5bfe7e40ba47ee7a8921a807daf4bb708e3a295bbb54d/jupyterlab-4.6.1.tar.gz", hash = "sha256:75315982ed28427edaa62bb85eadb5105e4043a757643c910efd787fe6ed0837", size = 28179125, upload-time = "2026-06-29T12:48:45.402Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/1b/dad6fdcc658ed7af26fdf3841e7394072c9549a8b896c381ab49dd11e2d9/jupyterlab-4.5.6-py3-none-any.whl", hash = "sha256:d6b3dac883aa4d9993348e0f8e95b24624f75099aed64eab6a4351a9cdd1e580", size = 12447124, upload-time = "2026-03-11T14:17:00.229Z" }, + { url = "https://files.pythonhosted.org/packages/5a/81/90ac6cc31d248e83a0d1eab343a5e6e68bc783d3f74fbe61640f42a61da4/jupyterlab-4.6.1-py3-none-any.whl", hash = "sha256:85a58546c831f3dce6cf919468c26874c9065e99c42279fb4abb8e1b552a98bb", size = 17164660, upload-time = "2026-06-29T12:48:41.21Z" }, ] [[package]] @@ -2374,7 +2463,7 @@ wheels = [ [[package]] name = "mistralai" -version = "2.4.0" +version = "2.6.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "eval-type-backport" }, @@ -2386,18 +2475,18 @@ dependencies = [ { name = "python-dateutil" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0b/6f/3d402b0bf00014d9a078997ba772e5324287157421c4daa4cf1f74be4425/mistralai-2.4.0.tar.gz", hash = "sha256:ddca732ec7073d686f07775f749f250092a33b1864b40ec1b07db050a0192572", size = 404754, upload-time = "2026-04-16T12:03:34.218Z" } +sdist = { url = "https://files.pythonhosted.org/packages/39/c1/c6452c81a3d7fc0423b4cfcd84840cf7141c93a0c50755abc27a7619a988/mistralai-2.6.0.tar.gz", hash = "sha256:531a86292ad498fc0fcd6dfcd480a3f4db9e92f558e14ffb22172824831e3a6e", size = 518760, upload-time = "2026-07-06T14:40:08.578Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c0/96/08a297dc9914f0ff43eed342827e5a803aa2233663d774de83b03f0c9b43/mistralai-2.4.0-py3-none-any.whl", hash = "sha256:e10846049f2c527d355ce4bebfcf9c94aba1d8350d9d2ead81be9b70d23fd4ab", size = 951501, upload-time = "2026-04-16T12:03:32.656Z" }, + { url = "https://files.pythonhosted.org/packages/e3/d5/6b9c08ab9b5b890970e8949bf00a9f8f82653826de5cc5ad506fe1c1fb73/mistralai-2.6.0-py3-none-any.whl", hash = "sha256:4b7202b0cab84ea03f8ff4a933c8cfc615b3057520d9d76467c07aa23939dab2", size = 1235945, upload-time = "2026-07-06T14:40:06.226Z" }, ] [[package]] name = "mistune" -version = "3.2.0" +version = "3.3.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9d/55/d01f0c4b45ade6536c51170b9043db8b2ec6ddf4a35c7ea3f5f559ac935b/mistune-3.2.0.tar.gz", hash = "sha256:708487c8a8cdd99c9d90eb3ed4c3ed961246ff78ac82f03418f5183ab70e398a", size = 95467, upload-time = "2025-12-23T11:36:34.994Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/a5/2dab368d6950e6808904dec98f54c7e726ee7be4a0c6afe00e6e011bd52d/mistune-3.3.3.tar.gz", hash = "sha256:c4c6c0c840b8637a2e9b8b6d607eb7c8f00888bf14c754409bcd339e848c2477", size = 115363, upload-time = "2026-07-09T06:18:05.268Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl", hash = "sha256:febdc629a3c78616b94393c6580551e0e34cc289987ec6c35ed3f4be42d0eee1", size = 53598, upload-time = "2025-12-23T11:36:33.211Z" }, + { url = "https://files.pythonhosted.org/packages/89/70/b1e4737b84163db5bb1dfde6f216dbfbf32783330a9989c965e121172830/mistune-3.3.3-py3-none-any.whl", hash = "sha256:99de1585e42dcbd826faa9e11a202727a5e202e4e4722a4c69ac1ff615793dd7", size = 63569, upload-time = "2026-07-09T06:18:03.839Z" }, ] [[package]] @@ -2647,18 +2736,19 @@ wheels = [ [[package]] name = "notebook" -version = "7.5.5" +version = "7.6.0" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "jupyter-builder" }, { name = "jupyter-server" }, { name = "jupyterlab" }, { name = "jupyterlab-server" }, { name = "notebook-shim" }, { name = "tornado" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/1f/6d/41052c48d6f6349ca0a7c4d1f6a78464de135e6d18f5829ba2510e62184c/notebook-7.5.5.tar.gz", hash = "sha256:dc0bfab0f2372c8278c457423d3256c34154ac2cc76bf20e9925260c461013c3", size = 14169167, upload-time = "2026-03-11T16:32:51.922Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/44/d5c65783f490298473bb1c05722e05ee2256231389559c2c5ae0a3e5d975/notebook-7.6.0.tar.gz", hash = "sha256:ea13e79e601bf273074895fdfb17dd3f2da916d3c045e0b9c47d18b16ab62481", size = 5497344, upload-time = "2026-06-18T16:18:55.202Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/aa/cbd1deb9f07446241e88f8d5fecccd95b249bca0b4e5482214a4d1714c49/notebook-7.5.5-py3-none-any.whl", hash = "sha256:a7c14dbeefa6592e87f72290ca982e0c10f5bbf3786be2a600fda9da2764a2b8", size = 14578929, upload-time = "2026-03-11T16:32:48.021Z" }, + { url = "https://files.pythonhosted.org/packages/93/d1/e617c40db57ff40e75f43a7d4d1c305e3a54c053ab5cb0534a6c314664f9/notebook-7.6.0-py3-none-any.whl", hash = "sha256:98aa2811b54ac191321d5dfce12ca700f8a511a33a26e4de2fa106a357c43d6a", size = 5544575, upload-time = "2026-06-18T16:18:52.551Z" }, ] [[package]] @@ -2747,7 +2837,7 @@ wheels = [ [[package]] name = "openai" -version = "2.32.0" +version = "2.45.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -2759,9 +2849,9 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ed/59/bdcc6b759b8c42dd73afaf5bf8f902c04b37987a5514dbc1c64dba390fef/openai-2.32.0.tar.gz", hash = "sha256:c54b27a9e4cb8d51f0dd94972ffd1a04437efeb259a9e60d8922b8bd26fe55e0", size = 693286, upload-time = "2026-04-15T22:28:19.434Z" } +sdist = { url = "https://files.pythonhosted.org/packages/78/60/d4219875289b11d2c2f7da93c36283da224a2e55865ed865ab64e0ce9217/openai-2.45.0.tar.gz", hash = "sha256:10d34ca9c5643bce775852fddbfc172505cb1d4de1ccd101696c3ecff358765d", size = 1109653, upload-time = "2026-07-09T18:02:44.091Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/c1/d6e64ccd0536bf616556f0cad2b6d94a8125f508d25cfd814b1d2db4e2f1/openai-2.32.0-py3-none-any.whl", hash = "sha256:4dcc9badeb4bf54ad0d187453742f290226d30150890b7890711bda4f32f192f", size = 1162570, upload-time = "2026-04-15T22:28:17.714Z" }, + { url = "https://files.pythonhosted.org/packages/f1/b0/2291689e3ec4723fbf5bbf3b54afcd7b160f9ddc98ca7aedfd0132af5677/openai-2.45.0-py3-none-any.whl", hash = "sha256:5df105f5f8c9b711fcb9d06d2d3888cebc82506db216484c14a4e53cdf651777", size = 1629470, upload-time = "2026-07-09T18:02:42.21Z" }, ] [[package]] @@ -2973,11 +3063,11 @@ wheels = [ [[package]] name = "pathable" -version = "0.5.0" +version = "0.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/55/b748445cb4ea6b125626f15379be7c96d1035d4fa3e8fee362fa92298abf/pathable-0.5.0.tar.gz", hash = "sha256:d81938348a1cacb525e7c75166270644782c0fb9c8cecc16be033e71427e0ef1", size = 16655, upload-time = "2026-02-20T08:47:00.748Z" } +sdist = { url = "https://files.pythonhosted.org/packages/66/f3/5a20387de9bcd0607871bfc2198ee0e15836da7baa4592ccd7f24c27c986/pathable-0.6.0.tar.gz", hash = "sha256:6404b8b82aef5ff0fd478934137128b99b12212ba35afdde5525ca4f8388ea58", size = 18970, upload-time = "2026-05-19T18:15:11.911Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/52/96/5a770e5c461462575474468e5af931cff9de036e7c2b4fea23c1c58d2cbe/pathable-0.5.0-py3-none-any.whl", hash = "sha256:646e3d09491a6351a0c82632a09c02cdf70a252e73196b36d8a15ba0a114f0a6", size = 16867, upload-time = "2026-02-20T08:46:59.536Z" }, + { url = "https://files.pythonhosted.org/packages/a2/e8/6d75ffd9784bce2e93d1ae4415649427e39a53bb172d4672b2b59c6f0a7b/pathable-0.6.0-py3-none-any.whl", hash = "sha256:82c4ca6c98c502ad12e0d4e9779b6210afee93c38990988c8c5d1b49bdcdf566", size = 18983, upload-time = "2026-05-19T18:15:10.728Z" }, ] [[package]] @@ -3341,19 +3431,19 @@ email = [ [[package]] name = "pydantic-ai" -version = "1.84.0" +version = "1.107.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic-ai-slim", extra = ["ag-ui", "anthropic", "bedrock", "cli", "cohere", "evals", "fastmcp", "google", "groq", "huggingface", "logfire", "mcp", "mistral", "openai", "retries", "spec", "temporal", "ui", "vertexai", "xai"] }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d3/d4/26f9189ed92a51fd711ba95ee8c875d7c169b68923fbb5ef1f5e99efffc0/pydantic_ai-1.84.0.tar.gz", hash = "sha256:3d2c1d501b0eea8b5b5e3fbfa14c8e5ce3d94a19fee8699ebbf6fe3e9fc3b2b8", size = 13025, upload-time = "2026-04-17T00:30:56.076Z" } +sdist = { url = "https://files.pythonhosted.org/packages/87/2f/c8decf5529eb624edd6ee17a6106e91556049e9433328c4fd46388fd56be/pydantic_ai-1.107.1.tar.gz", hash = "sha256:af2604c6cb6659a6f2c69312839542c2d7194349896d2f375828ca440f701078", size = 18415, upload-time = "2026-07-11T03:22:14.142Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8f/b0/0eacb6a674eac634b1a1b7cf628c48ef3951006dc45ab29f07226763c1cd/pydantic_ai-1.84.0-py3-none-any.whl", hash = "sha256:cf1b389ff598466012035854f2b37381ba1a245109962ef4385e8860c20ca67f", size = 7577, upload-time = "2026-04-17T00:30:47.738Z" }, + { url = "https://files.pythonhosted.org/packages/36/a7/2b97b44be0e2cb3f1904969451d90098385295bb884934414a73dd20c146/pydantic_ai-1.107.1-py3-none-any.whl", hash = "sha256:a6ebf7ce54af2af78af1aa36939eb1873b17232d91be0cfc50a65dcec7268865", size = 7586, upload-time = "2026-07-11T03:22:06.643Z" }, ] [[package]] name = "pydantic-ai-slim" -version = "1.84.0" +version = "1.107.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "genai-prices" }, @@ -3364,9 +3454,9 @@ dependencies = [ { name = "pydantic-graph" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/af/3a/7af6a214631296d72c9a60a926047a93b697601f12229793f85f31083365/pydantic_ai_slim-1.84.0.tar.gz", hash = "sha256:5d57435740aa7acc35567454f55b3e8b1d22e9cd48dfe9fafa631da86538a190", size = 561662, upload-time = "2026-04-17T00:30:58.047Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d5/a5/e4a515a5eaa2cef746356e69e706e6cfa4575d1065652b226104db693877/pydantic_ai_slim-1.107.1.tar.gz", hash = "sha256:c4c86aae407e62ea720e8911627219f34f60668dc4c35fdead5ad026efdd00fc", size = 780236, upload-time = "2026-07-11T03:22:16.105Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/1a/6a2e3e53461360fca4ed892821db33a9d349da3bf0561d0500731ce06b33/pydantic_ai_slim-1.84.0-py3-none-any.whl", hash = "sha256:2e7c2d1a1151ff4ec3104de5dbaf6769a9b41eeac2b2a1d55f70278d622d96bd", size = 717880, upload-time = "2026-04-17T00:30:50.545Z" }, + { url = "https://files.pythonhosted.org/packages/92/26/a9f8761ce3789a67c930d2d93742a2c10382a6d1fe99fba0aa67430c7786/pydantic_ai_slim-1.107.1-py3-none-any.whl", hash = "sha256:8eade1c9a1bbdf19c06b4fba9424980b0f446ef2756d2559cc819971ec35cc8f", size = 964387, upload-time = "2026-07-11T03:22:09.178Z" }, ] [package.optional-dependencies] @@ -3403,13 +3493,14 @@ groq = [ { name = "groq" }, ] huggingface = [ + { name = "hf-xet", marker = "platform_machine == 'AMD64' or platform_machine == 'aarch64' or platform_machine == 'amd64' or platform_machine == 'arm64' or platform_machine == 'x86_64'" }, { name = "huggingface-hub" }, ] logfire = [ { name = "logfire", extra = ["httpx"] }, ] mcp = [ - { name = "mcp" }, + { name = "fastmcp-slim", extra = ["client"] }, ] mistral = [ { name = "mistralai" }, @@ -3497,7 +3588,7 @@ wheels = [ [[package]] name = "pydantic-evals" -version = "1.84.0" +version = "1.107.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -3507,14 +3598,14 @@ dependencies = [ { name = "pyyaml" }, { name = "rich" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c6/6c/cbda39c30e30402ba48dacbe52ede64e3caf4ca66bf6f63558f32ec8d8b6/pydantic_evals-1.84.0.tar.gz", hash = "sha256:f236af64bdbc44cab397929c44f78e19f7795cac1f1ee1707b28c12a7afd3f0f", size = 65794, upload-time = "2026-04-17T00:30:59.262Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/03/f9b20f13cca940aadb0eae4fcff12f7ea8a0ce4ad2e60adb2066a24e203c/pydantic_evals-1.107.1.tar.gz", hash = "sha256:6dd431b09882a08c7c5be311e6ffde4ce91c124ab1e5c92da327dcad950ea827", size = 78548, upload-time = "2026-07-11T03:22:17.54Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/42/08/014b3eeef211e89476b61b220e0147de72775f010413bfd4e8fcd1885293/pydantic_evals-1.84.0-py3-none-any.whl", hash = "sha256:48d028705d2c930eda34b7679be83e2a5e9cc6c718198004212c2ea214cdeb62", size = 77711, upload-time = "2026-04-17T00:30:52.115Z" }, + { url = "https://files.pythonhosted.org/packages/be/be/a8a7131fba822d512a9667434a7e7c6def9d866a933213f8df18213efa41/pydantic_evals-1.107.1-py3-none-any.whl", hash = "sha256:aebd9f03d736fde27c8ad1f94574c5c2dbe5787f56234f8083a4fb50fae1c16d", size = 93586, upload-time = "2026-07-11T03:22:10.765Z" }, ] [[package]] name = "pydantic-graph" -version = "1.84.0" +version = "1.107.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "httpx" }, @@ -3522,35 +3613,35 @@ dependencies = [ { name = "pydantic" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/40/b4/56c078aab2b8f06feb272c9bed76b6eb95f430c11dbde57b7fcc787f97a2/pydantic_graph-1.84.0.tar.gz", hash = "sha256:d354185537d243d410fc223329ed2fd8016faf339ac91f0b2e74344bedfc1179", size = 59236, upload-time = "2026-04-17T00:31:00.372Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b9/d0/b10720e987f433d63af6cba046554f84cf20aee696209a90af6d6769b385/pydantic_graph-1.107.1.tar.gz", hash = "sha256:c83b255a67b1e64e460749a61195309738f42895ad5b93da2140d6bff76b14f1", size = 62563, upload-time = "2026-07-11T03:22:18.404Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/49/9b/571e21d0a1c7f4632e2c262bab735469e30a6f70c1cd7a64b7eae80607c0/pydantic_graph-1.84.0-py3-none-any.whl", hash = "sha256:c9f1adb83412e9113c0d8dc070fd22c405ec3fc9efd8e4b84156b580557ced37", size = 73062, upload-time = "2026-04-17T00:30:53.788Z" }, + { url = "https://files.pythonhosted.org/packages/dd/f0/27537e7c7df3dcca6b8ad2bff9a35c6a4f1b4e76d21cd6c6ab931d7ef0bc/pydantic_graph-1.107.1-py3-none-any.whl", hash = "sha256:b5893249b7fc1dc2e60dfc40a08ccd4cc0d1dd6e05aeeee2a50392c5663eac60", size = 80106, upload-time = "2026-07-11T03:22:12.083Z" }, ] [[package]] name = "pydantic-handlebars" -version = "0.1.0" +version = "0.2.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/90/16/d41768bd3fd77e6250c20be11a3e68fee5fff07c3356455e6708f6a60f2a/pydantic_handlebars-0.1.0.tar.gz", hash = "sha256:1931c54946add1b5e3796c9bf6a005ed7662cef0109bb05c352f0b3d031a1260", size = 159826, upload-time = "2026-03-01T20:00:17.497Z" } +sdist = { url = "https://files.pythonhosted.org/packages/96/73/e55a1fe1a8788a5fa82d9209e796f4111e28f2d2fecab7173aa6d80516ad/pydantic_handlebars-0.2.1.tar.gz", hash = "sha256:d4124cfbf7d6e3bded9331a08ccccf6f29f3e3a93665b35b5d6061650aeeb49f", size = 176949, upload-time = "2026-05-25T01:24:38.354Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/99/5f/86b1630be61bdebf253c2f953a6c3f073ec21bb0725565ea3896802e1ca3/pydantic_handlebars-0.1.0-py3-none-any.whl", hash = "sha256:8a436fe8bc607295eb04bec58bd6e2c9498c9e069c557ff0b505e3d568c783bc", size = 40890, upload-time = "2026-03-01T20:00:16.106Z" }, + { url = "https://files.pythonhosted.org/packages/55/11/364bc401f1d8fdb3947079fc43ffdfbfc9132d065981a03a95d2e87440c4/pydantic_handlebars-0.2.1-py3-none-any.whl", hash = "sha256:c713427d6498cf4b66814447d54753a2748f8a8d3a9f00c194192ddb3df61e52", size = 50476, upload-time = "2026-05-25T01:24:37.104Z" }, ] [[package]] name = "pydantic-settings" -version = "2.13.1" +version = "2.14.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "python-dotenv" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/52/6d/fffca34caecc4a3f97bda81b2098da5e8ab7efc9a66e819074a11955d87e/pydantic_settings-2.13.1.tar.gz", hash = "sha256:b4c11847b15237fb0171e1462bf540e294affb9b86db4d9aa5c01730bdbe4025", size = 223826, upload-time = "2026-02-19T13:45:08.055Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/b5/8f48e906c3e0205276e8bd8cb7512217a87b2685304d64be27cad5b3019f/pydantic_settings-2.14.2.tar.gz", hash = "sha256:c19dd64b19097f1de80184f0cc7b0272a13ae6e170cbf240a3e27e381ed14a5f", size = 237700, upload-time = "2026-06-19T13:44:56.324Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/00/4b/ccc026168948fec4f7555b9164c724cf4125eac006e176541483d2c959be/pydantic_settings-2.13.1-py3-none-any.whl", hash = "sha256:d56fd801823dbeae7f0975e1f8c8e25c258eb75d278ea7abb5d9cebb01b56237", size = 58929, upload-time = "2026-02-19T13:45:06.034Z" }, + { url = "https://files.pythonhosted.org/packages/77/c1/6e422f34e569cf8e18df68d1939c81c099d2b61e4f7d9621c8a77560799c/pydantic_settings-2.14.2-py3-none-any.whl", hash = "sha256:a20c97b37910b6550d5ea50fbcc2d4187defe58cd57070b73863d069419c9440", size = 61715, upload-time = "2026-06-19T13:44:55.02Z" }, ] [[package]] @@ -3564,11 +3655,11 @@ wheels = [ [[package]] name = "pyjwt" -version = "2.12.1" +version = "2.13.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c2/27/a3b6e5bf6ff856d2509292e95c8f57f0df7017cf5394921fc4e4ef40308a/pyjwt-2.12.1.tar.gz", hash = "sha256:c74a7a2adf861c04d002db713dd85f84beb242228e671280bf709d765b03672b", size = 102564, upload-time = "2026-03-13T19:27:37.25Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3b/81/58d0ac84e1ef3a3843791d6954d94c0b33d526c75eeb1efbce9d0a4c4077/pyjwt-2.13.0.tar.gz", hash = "sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423", size = 107515, upload-time = "2026-05-21T19:54:36.618Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/7a/8dd906bd22e79e47397a61742927f6747fe93242ef86645ee9092e610244/pyjwt-2.12.1-py3-none-any.whl", hash = "sha256:28ca37c070cad8ba8cd9790cd940535d40274d22f80ab87f3ac6a713e6e8454c", size = 29726, upload-time = "2026-03-13T19:27:35.677Z" }, + { url = "https://files.pythonhosted.org/packages/a3/5e/ecf12fdb62546d64385c158514e9b2b671f7832108ef2ecd2020ce0af2d1/pyjwt-2.13.0-py3-none-any.whl", hash = "sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728", size = 31274, upload-time = "2026-05-21T19:54:35.362Z" }, ] [package.optional-dependencies] @@ -3654,11 +3745,11 @@ wheels = [ [[package]] name = "python-multipart" -version = "0.0.26" +version = "0.0.32" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/88/71/b145a380824a960ebd60e1014256dbb7d2253f2316ff2d73dfd8928ec2c3/python_multipart-0.0.26.tar.gz", hash = "sha256:08fadc45918cd615e26846437f50c5d6d23304da32c341f289a617127b081f17", size = 43501, upload-time = "2026-04-10T14:09:59.473Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/42/55c32bb9b12693c092ad250a0e82edb5b31ddeda6eb772de5f308b3804ad/python_multipart-0.0.32.tar.gz", hash = "sha256:be54b7f3fa167bb83e4fcd936b887b708f4e57fe75911c02aebf53efaf8d938e", size = 46881, upload-time = "2026-06-04T16:18:58.647Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/22/f1925cdda983ab66fc8ec6ec8014b959262747e58bdca26a4e3d1da29d56/python_multipart-0.0.26-py3-none-any.whl", hash = "sha256:c0b169f8c4484c13b0dcf2ef0ec3a4adb255c4b7d18d8e420477d2b1dd03f185", size = 28847, upload-time = "2026-04-10T14:09:58.131Z" }, + { url = "https://files.pythonhosted.org/packages/e1/04/e8135ebd1ad02c56ec633277529b2602ff99ff634be76cdba5744cf554fd/python_multipart-0.0.32-py3-none-any.whl", hash = "sha256:ff6d3f776f16878c894e52e107296ffc890e913c611b1a4ec6c44e2821fe2e23", size = 30042, upload-time = "2026-06-04T16:18:57.319Z" }, ] [[package]] @@ -3935,15 +4026,15 @@ wheels = [ [[package]] name = "rich-rst" -version = "1.3.2" +version = "2.1.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "docutils" }, + { name = "pygments" }, { name = "rich" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bc/6d/a506aaa4a9eaa945ed8ab2b7347859f53593864289853c5d6d62b77246e0/rich_rst-1.3.2.tar.gz", hash = "sha256:a1196fdddf1e364b02ec68a05e8ff8f6914fee10fbca2e6b6735f166bb0da8d4", size = 14936, upload-time = "2025-10-14T16:49:45.332Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/d6/d0b9fafc73b65767200da027acab1db1bdb1048f4fea5ebf659df01c700e/rich_rst-2.1.0.tar.gz", hash = "sha256:f4d117b49697f338769759fa5cacf5197da4888b347b9fda2e50aef5cd8d93bd", size = 302732, upload-time = "2026-07-05T02:59:44.308Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/13/2f/b4530fbf948867702d0a3f27de4a6aab1d156f406d72852ab902c4d04de9/rich_rst-1.3.2-py3-none-any.whl", hash = "sha256:a99b4907cbe118cf9d18b0b44de272efa61f15117c61e39ebdc431baf5df722a", size = 12567, upload-time = "2025-10-14T16:49:42.953Z" }, + { url = "https://files.pythonhosted.org/packages/2a/68/1fc93dd759605b5d00fc98b50200739e41ed32bd22d6ba35ca6c3932371b/rich_rst-2.1.0-py3-none-any.whl", hash = "sha256:7ecd1343ee12c879d0e7ae74c3eb6d263b023d2929c6d114212eb1fd91057255", size = 272987, upload-time = "2026-07-05T02:59:42.792Z" }, ] [[package]] @@ -4039,14 +4130,14 @@ wheels = [ [[package]] name = "s3transfer" -version = "0.16.0" +version = "0.19.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/05/04/74127fc843314818edfa81b5540e26dd537353b123a4edc563109d8f17dd/s3transfer-0.16.0.tar.gz", hash = "sha256:8e990f13268025792229cd52fa10cb7163744bf56e719e0b9cb925ab79abf920", size = 153827, upload-time = "2025-12-01T02:30:59.114Z" } +sdist = { url = "https://files.pythonhosted.org/packages/65/da/4bef7ce7bb989b222aa4785a413896dbec53306dfc59c6ce7d16a7ffbd6a/s3transfer-0.19.1.tar.gz", hash = "sha256:d3d6371dc3f1e5c5427b2b457bcf13bcf87bec334c95aed18642eae61f6926f3", size = 165354, upload-time = "2026-07-10T19:32:04.849Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/51/727abb13f44c1fcf6d145979e1535a35794db0f6e450a0cb46aa24732fe2/s3transfer-0.16.0-py3-none-any.whl", hash = "sha256:18e25d66fed509e3868dc1572b3f427ff947dd2c56f844a5bf09481ad3f3b2fe", size = 86830, upload-time = "2025-12-01T02:30:57.729Z" }, + { url = "https://files.pythonhosted.org/packages/24/23/e84c64ad0e8bc59cd1b2ef98def848deff0ef3456c542afe74d51e9e8c85/s3transfer-0.19.1-py3-none-any.whl", hash = "sha256:d5fd7005ee39307455ad5f310b5ea67f4b1960d7fed5b3671ee50c249de675de", size = 90072, upload-time = "2026-07-10T19:32:03.673Z" }, ] [[package]] @@ -4174,15 +4265,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1c/78/504fdd027da3b84ff1aecd9f6957e65f35134534ccc6da8628eb71e76d3f/send2trash-2.1.0-py3-none-any.whl", hash = "sha256:0da2f112e6d6bb22de6aa6daa7e144831a4febf2a87261451c4ad849fe9a873c", size = 17610, upload-time = "2026-01-14T06:27:35.218Z" }, ] -[[package]] -name = "setuptools" -version = "82.0.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4f/db/cfac1baf10650ab4d1c111714410d2fbb77ac5a616db26775db562c8fab2/setuptools-82.0.1.tar.gz", hash = "sha256:7d872682c5d01cfde07da7bccc7b65469d3dca203318515ada1de5eda35efbf9", size = 1152316, upload-time = "2026-03-09T12:47:17.221Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/76/f789f7a86709c6b087c5a2f52f911838cad707cc613162401badc665acfe/setuptools-82.0.1-py3-none-any.whl", hash = "sha256:a59e362652f08dcd477c78bb6e7bd9d80a7995bc73ce773050228a348ce2e5bb", size = 1006223, upload-time = "2026-03-09T12:47:15.026Z" }, -] - [[package]] name = "shellingham" version = "1.5.4" @@ -4212,11 +4294,11 @@ wheels = [ [[package]] name = "soupsieve" -version = "2.8.3" +version = "2.8.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7b/ae/2d9c981590ed9999a0d91755b47fc74f74de286b0f5cee14c9269041e6c4/soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349", size = 118627, upload-time = "2026-01-20T04:27:02.457Z" } +sdist = { url = "https://files.pythonhosted.org/packages/47/2c/0a5f6f8ee0d5589e48c7640213ed5175d52cf540a06725b628cc1a45d6ce/soupsieve-2.8.4.tar.gz", hash = "sha256:e121fd02e975c695e4e9e8774a5ee35d74714b59307868dcc5319ad2d9e3328e", size = 121110, upload-time = "2026-05-24T13:55:57.154Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016, upload-time = "2026-01-20T04:27:01.012Z" }, + { url = "https://files.pythonhosted.org/packages/5e/f5/0c41cb68dcae6b7de4fac4188a3a9589e21fb31df21ea3a2e888db95e6c9/soupsieve-2.8.4-py3-none-any.whl", hash = "sha256:e7e6b0769c8f51ed59acab6e994b00621096cfb1c640a7509295987388fbaf65", size = 37304, upload-time = "2026-05-24T13:55:55.406Z" }, ] [[package]] @@ -4248,14 +4330,14 @@ wheels = [ [[package]] name = "starlette" -version = "1.0.0" +version = "1.3.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/81/69/17425771797c36cded50b7fe44e850315d039f28b15901ab44839e70b593/starlette-1.0.0.tar.gz", hash = "sha256:6a4beaf1f81bb472fd19ea9b918b50dc3a77a6f2e190a12954b25e6ed5eea149", size = 2655289, upload-time = "2026-03-22T18:29:46.779Z" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/e3/7c1dc7381d9f8ab7d854328ebfa884e62cb3f3d8549ddfd37c7814f42afa/starlette-1.3.1.tar.gz", hash = "sha256:05d0213193f2fbaae60e2ecb593b4add4262ad4e46536b54abe36f11a71724e0", size = 2703240, upload-time = "2026-06-12T09:23:11.602Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/c9/584bc9651441b4ba60cc4d557d8a547b5aff901af35bda3a4ee30c819b82/starlette-1.0.0-py3-none-any.whl", hash = "sha256:d3ec55e0bb321692d275455ddfd3df75fff145d009685eb40dc91fc66b03d38b", size = 72651, upload-time = "2026-03-22T18:29:45.111Z" }, + { url = "https://files.pythonhosted.org/packages/ec/bb/2799cc2ede3ed41131f8975621e7213dfc7ef4acbbaadfa440f32500c370/starlette-1.3.1-py3-none-any.whl", hash = "sha256:c7372aae11c3c3f26a42df7bd626cec2f47d03483d261d369516a615a53714c6", size = 73632, upload-time = "2026-06-12T09:23:10.017Z" }, ] [[package]] @@ -4272,7 +4354,7 @@ wheels = [ [[package]] name = "temporalio" -version = "1.26.0" +version = "1.30.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "nexus-rpc" }, @@ -4280,13 +4362,13 @@ dependencies = [ { name = "types-protobuf" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ae/d4/fa21150a225393f87732ed6fef3cc9735d9e751edc6be415fe6e375105c6/temporalio-1.26.0.tar.gz", hash = "sha256:f4bfb35125e6f5e8c7f7ed1277c7354d812c6fac7ed5f8dbd50536cf289aaaa7", size = 2388994, upload-time = "2026-04-15T23:43:00.911Z" } +sdist = { url = "https://files.pythonhosted.org/packages/00/b0/ad8fc3cd7425c6551a637bf23c798e8fdd8eb7a3ec4fee4f46f7678ba8d2/temporalio-1.30.0.tar.gz", hash = "sha256:7c025919511bb465392d547e48ccb85fd560a995db4ebcc82fdb43cddf088e6f", size = 2686876, upload-time = "2026-07-02T21:04:46.713Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/1e/27/8c421c622d18cc8e034247d5d72b89e6456937344b5bec1de40abef3c085/temporalio-1.26.0-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:5489040c0cf621edeb36984199dd9e4fbd2b3a07d61a4f2a8da1f2cb9820ef26", size = 14221070, upload-time = "2026-04-15T23:42:26.21Z" }, - { url = "https://files.pythonhosted.org/packages/49/7c/d2b691d16ec5db87198c2e08dbfba58e286c096faee15753613a581abdce/temporalio-1.26.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:b18dd85771509c19ef059a31908bcd4e6130d1f67037c4db519702f3f2ad6d4a", size = 13583991, upload-time = "2026-04-15T23:42:34.357Z" }, - { url = "https://files.pythonhosted.org/packages/05/ca/b8728451320ca9d8bb6e1680b9bd23767118f86d5b8644edf2304d533f1b/temporalio-1.26.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46187d5f82ca2ae81f35ea5916a76db0e2f067210dc6b1852c3749475721946e", size = 13808036, upload-time = "2026-04-15T23:42:42.757Z" }, - { url = "https://files.pythonhosted.org/packages/cb/54/3113f5e0ac58655790abac64656373e06191b351d74bfb94692e81bd6784/temporalio-1.26.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03300c3e5237443367ac61bb20bd726c656b3daa50310bdd436599d5bdc7cf97", size = 14336604, upload-time = "2026-04-15T23:42:49.851Z" }, - { url = "https://files.pythonhosted.org/packages/fd/9b/c50840a26af3587c0c8d9af04d9976743e22496996dc1a377efc75dcd316/temporalio-1.26.0-cp310-abi3-win_amd64.whl", hash = "sha256:1c4a0d82f0a3796cbf78864c799f8dca0b94cdaec68e7b8b224c859005686ec4", size = 14525849, upload-time = "2026-04-15T23:42:57.589Z" }, + { url = "https://files.pythonhosted.org/packages/02/39/842fdffe93388dd30ac12a53a698f71cbfb68b3bc938f30f3e5d6a36d4ad/temporalio-1.30.0-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:6773a6b708dee7675fcbb681bf28e48337ce43b8467ceba8f903e78ae68909f8", size = 14520026, upload-time = "2026-07-02T21:04:31.384Z" }, + { url = "https://files.pythonhosted.org/packages/40/f3/a2237d5265eb29de591abeac7610a48616b590a1b923b4919f60ee81adfa/temporalio-1.30.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:4038c3ce2d9acc12fef31dd16ef9be8cc7f721672da5662aa05e3942d0b5c9d1", size = 14018523, upload-time = "2026-07-02T21:04:34.405Z" }, + { url = "https://files.pythonhosted.org/packages/e6/57/dc648d812f4c688bd246a616f0d65c5f03b33675df2effb1b480e1df6d21/temporalio-1.30.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:108d1a56e174eabc18add58316084cdead230e239d3df22bbe999d6954986591", size = 14330502, upload-time = "2026-07-02T21:04:37.39Z" }, + { url = "https://files.pythonhosted.org/packages/1c/e1/dbd57de0f5090891850c2ee5490319834a12b704076b11f32a4a149998d4/temporalio-1.30.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47b156138de30c2cd6723d5bfc06a30abcf680344c5481abb31f2a98a9b1f809", size = 14833364, upload-time = "2026-07-02T21:04:40.454Z" }, + { url = "https://files.pythonhosted.org/packages/30/2a/6d41289c11465ba276a8b417f31d2e469f0fe4b8afacd61d5244151c5fce/temporalio-1.30.0-cp310-abi3-win_amd64.whl", hash = "sha256:3adee28d5ec47bd6309a5eeef7b00126373f119bc5c1b058a34de098542f4da7", size = 15181893, upload-time = "2026-07-02T21:04:43.609Z" }, ] [[package]] @@ -4375,28 +4457,29 @@ wheels = [ [[package]] name = "tokenizers" -version = "0.22.2" +version = "0.23.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "huggingface-hub" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/73/6f/f80cfef4a312e1fb34baf7d85c72d4411afde10978d4657f8cdd811d3ccc/tokenizers-0.22.2.tar.gz", hash = "sha256:473b83b915e547aa366d1eee11806deaf419e17be16310ac0a14077f1e28f917", size = 372115, upload-time = "2026-01-05T10:45:15.988Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/60/21f715d9faba5f5407ff759472ade058ec4a507ad62bcea47cb847239a73/tokenizers-0.23.1.tar.gz", hash = "sha256:1feeeadf865a7915adc25445dea30e9933e593c31bb96c277cee36de227c8bfa", size = 365748, upload-time = "2026-04-27T14:43:25.606Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/92/97/5dbfabf04c7e348e655e907ed27913e03db0923abb5dfdd120d7b25630e1/tokenizers-0.22.2-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:544dd704ae7238755d790de45ba8da072e9af3eea688f698b137915ae959281c", size = 3100275, upload-time = "2026-01-05T10:41:02.158Z" }, - { url = "https://files.pythonhosted.org/packages/2e/47/174dca0502ef88b28f1c9e06b73ce33500eedfac7a7692108aec220464e7/tokenizers-0.22.2-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:1e418a55456beedca4621dbab65a318981467a2b188e982a23e117f115ce5001", size = 2981472, upload-time = "2026-01-05T10:41:00.276Z" }, - { url = "https://files.pythonhosted.org/packages/d6/84/7990e799f1309a8b87af6b948f31edaa12a3ed22d11b352eaf4f4b2e5753/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2249487018adec45d6e3554c71d46eb39fa8ea67156c640f7513eb26f318cec7", size = 3290736, upload-time = "2026-01-05T10:40:32.165Z" }, - { url = "https://files.pythonhosted.org/packages/78/59/09d0d9ba94dcd5f4f1368d4858d24546b4bdc0231c2354aa31d6199f0399/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:25b85325d0815e86e0bac263506dd114578953b7b53d7de09a6485e4a160a7dd", size = 3168835, upload-time = "2026-01-05T10:40:38.847Z" }, - { url = "https://files.pythonhosted.org/packages/47/50/b3ebb4243e7160bda8d34b731e54dd8ab8b133e50775872e7a434e524c28/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfb88f22a209ff7b40a576d5324bf8286b519d7358663db21d6246fb17eea2d5", size = 3521673, upload-time = "2026-01-05T10:40:56.614Z" }, - { url = "https://files.pythonhosted.org/packages/e0/fa/89f4cb9e08df770b57adb96f8cbb7e22695a4cb6c2bd5f0c4f0ebcf33b66/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c774b1276f71e1ef716e5486f21e76333464f47bece56bbd554485982a9e03e", size = 3724818, upload-time = "2026-01-05T10:40:44.507Z" }, - { url = "https://files.pythonhosted.org/packages/64/04/ca2363f0bfbe3b3d36e95bf67e56a4c88c8e3362b658e616d1ac185d47f2/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df6c4265b289083bf710dff49bc51ef252f9d5be33a45ee2bed151114a56207b", size = 3379195, upload-time = "2026-01-05T10:40:51.139Z" }, - { url = "https://files.pythonhosted.org/packages/2e/76/932be4b50ef6ccedf9d3c6639b056a967a86258c6d9200643f01269211ca/tokenizers-0.22.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:369cc9fc8cc10cb24143873a0d95438bb8ee257bb80c71989e3ee290e8d72c67", size = 3274982, upload-time = "2026-01-05T10:40:58.331Z" }, - { url = "https://files.pythonhosted.org/packages/1d/28/5f9f5a4cc211b69e89420980e483831bcc29dade307955cc9dc858a40f01/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:29c30b83d8dcd061078b05ae0cb94d3c710555fbb44861139f9f83dcca3dc3e4", size = 9478245, upload-time = "2026-01-05T10:41:04.053Z" }, - { url = "https://files.pythonhosted.org/packages/6c/fb/66e2da4704d6aadebf8cb39f1d6d1957df667ab24cff2326b77cda0dcb85/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:37ae80a28c1d3265bb1f22464c856bd23c02a05bb211e56d0c5301a435be6c1a", size = 9560069, upload-time = "2026-01-05T10:45:10.673Z" }, - { url = "https://files.pythonhosted.org/packages/16/04/fed398b05caa87ce9b1a1bb5166645e38196081b225059a6edaff6440fac/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:791135ee325f2336f498590eb2f11dc5c295232f288e75c99a36c5dbce63088a", size = 9899263, upload-time = "2026-01-05T10:45:12.559Z" }, - { url = "https://files.pythonhosted.org/packages/05/a1/d62dfe7376beaaf1394917e0f8e93ee5f67fea8fcf4107501db35996586b/tokenizers-0.22.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:38337540fbbddff8e999d59970f3c6f35a82de10053206a7562f1ea02d046fa5", size = 10033429, upload-time = "2026-01-05T10:45:14.333Z" }, - { url = "https://files.pythonhosted.org/packages/fd/18/a545c4ea42af3df6effd7d13d250ba77a0a86fb20393143bbb9a92e434d4/tokenizers-0.22.2-cp39-abi3-win32.whl", hash = "sha256:a6bf3f88c554a2b653af81f3204491c818ae2ac6fbc09e76ef4773351292bc92", size = 2502363, upload-time = "2026-01-05T10:45:20.593Z" }, - { url = "https://files.pythonhosted.org/packages/65/71/0670843133a43d43070abeb1949abfdef12a86d490bea9cd9e18e37c5ff7/tokenizers-0.22.2-cp39-abi3-win_amd64.whl", hash = "sha256:c9ea31edff2968b44a88f97d784c2f16dc0729b8b143ed004699ebca91f05c48", size = 2747786, upload-time = "2026-01-05T10:45:18.411Z" }, - { url = "https://files.pythonhosted.org/packages/72/f4/0de46cfa12cdcbcd464cc59fde36912af405696f687e53a091fb432f694c/tokenizers-0.22.2-cp39-abi3-win_arm64.whl", hash = "sha256:9ce725d22864a1e965217204946f830c37876eee3b2ba6fc6255e8e903d5fcbc", size = 2612133, upload-time = "2026-01-05T10:45:17.232Z" }, + { url = "https://files.pythonhosted.org/packages/87/39/b87a87d5bb9470610b80a2d31df42fcffeaf35118b8b97952b2aff598cc7/tokenizers-0.23.1-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:e03d6ffcbe0d56ee9c1ccd070e70a13fa750727c0277e138152acbc0252c2224", size = 3146732, upload-time = "2026-04-27T14:43:15.427Z" }, + { url = "https://files.pythonhosted.org/packages/e2/6a/068ed9f6e444c9d7e9d55ce134181325700f3d7f30410721bdc8f848d727/tokenizers-0.23.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:e0948bbb1ac1d7cdfc9fb6d62c596e3b7550036ad60ecd654a66ad273326324e", size = 3054954, upload-time = "2026-04-27T14:43:13.745Z" }, + { url = "https://files.pythonhosted.org/packages/6c/36/e006edf031154cba92b8416057d92c3abe3635e4c4b0aa0b5b9bb39dde70/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bf13402aff9bc533c89cb849ec3b412dc3fbeacc9744840e423d7bf3f7dc0e3", size = 3374081, upload-time = "2026-04-27T14:43:01.241Z" }, + { url = "https://files.pythonhosted.org/packages/a2/ef/7735d226f9c7f874a6bee5e3f27fb25ecabdf207d37b8cf45286d0795893/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f836ca703b89ae07919a309f9651f7a88fd5a33d5f718ba5ad0870ec0256bad6", size = 3247641, upload-time = "2026-04-27T14:43:03.856Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d9/24827036f6e21297bfffda0768e58eb6096a4f411e932964a01707857931/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae848657742035523fdf261773630cb819a26995fcd3d9ecae0c1daf6e5a4959", size = 3585624, upload-time = "2026-04-27T14:43:10.664Z" }, + { url = "https://files.pythonhosted.org/packages/0c/9a/22f3582b3a4f49358293a5206e25317621ee4526bfe9cdaa0f07a12e770e/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:53b09e85775d5187941e7bab30e941b4134ab4a7dd8c68e783d231fb7ca27c51", size = 3844062, upload-time = "2026-04-27T14:43:05.643Z" }, + { url = "https://files.pythonhosted.org/packages/7e/65/b8f8814eef95800f20721384136d9a1d22241d50b2874357cb70542c392f/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ea5a0ce170074329faaa8ea3f6400ecde604b6678192688533af80980daae71a", size = 3460098, upload-time = "2026-04-27T14:43:08.854Z" }, + { url = "https://files.pythonhosted.org/packages/0d/d5/1353e5f677ec27c2494fb6a6725e82d56c985f53e90ec511369e7e4f02c6/tokenizers-0.23.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5075b405006415ea148a992d093699c66eb01952bf59f4d5727089a98bda45a4", size = 3346235, upload-time = "2026-04-27T14:43:12.377Z" }, + { url = "https://files.pythonhosted.org/packages/71/89/39b6b8fc073fb6d413d0147aa333dc7eff7be65639ac9d19930a0b21bf33/tokenizers-0.23.1-cp310-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:56f3a77de629917652f876294dc9fe6bad4a0c43bc229dc72e59bb23a0f4729a", size = 3426398, upload-time = "2026-04-27T14:43:07.264Z" }, + { url = "https://files.pythonhosted.org/packages/0f/80/127c854da64827e5b79264ce524993a90dddcb320e5cd42412c5c02f9e8a/tokenizers-0.23.1-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9d10a6d957ef01896dc274e890eee27d41bd0e74ef31e60616f0fc311345184e", size = 9823279, upload-time = "2026-04-27T14:43:17.222Z" }, + { url = "https://files.pythonhosted.org/packages/fe/ba/44c2502feb1a058f096ddfb4e0996ef3225a01a388e1a9b094e91689fe93/tokenizers-0.23.1-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:1974288a609c343774f1b897c8b482c791ab17b75ab5c8c2b1737565c1d82288", size = 9644986, upload-time = "2026-04-27T14:43:19.45Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c1/464019a9fb059870bfe4eebb4ba12208f3042035e258bf5e782906bd3847/tokenizers-0.23.1-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:120468fb4c24faf0543c835a4fabafa4deb3f20a035c9b6e83d0b553a97615d4", size = 9976181, upload-time = "2026-04-27T14:43:21.463Z" }, + { url = "https://files.pythonhosted.org/packages/79/94/3ac1432bda31626071e9b6a12709b97ae05131c804b94c8f3ac622c5da32/tokenizers-0.23.1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e3d8f40ea6268047de7046906326abed5134f27d4e8447b23763afe5808c8a96", size = 10113853, upload-time = "2026-04-27T14:43:23.617Z" }, + { url = "https://files.pythonhosted.org/packages/6a/dd/631b21433c771b1382535326f0eca80b9c9cee2e64961dd993bc9ac4669e/tokenizers-0.23.1-cp310-abi3-win32.whl", hash = "sha256:93120a930b919416da7cd10a2f606ac9919cc69cacae7980fa2140e277660948", size = 2536263, upload-time = "2026-04-27T14:43:29.888Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/2553f72aaf65a2797d4229e37fa7fbe38ffbf3e32912d31bdd78b3323e59/tokenizers-0.23.1-cp310-abi3-win_amd64.whl", hash = "sha256:e7bfaf995c1bdbbd21d13539decb6650967013759318627d85daeb7881af16b7", size = 2798223, upload-time = "2026-04-27T14:43:28.51Z" }, + { url = "https://files.pythonhosted.org/packages/cd/2b/2be299bab55fc595e3d38567edb1a87f86e594842968fa9515a07bdcf422/tokenizers-0.23.1-cp310-abi3-win_arm64.whl", hash = "sha256:a26197957d8e4425dfba746315f3c425ea00cfa8367c5fbc4ec73447893dcea9", size = 2664127, upload-time = "2026-04-27T14:43:26.949Z" }, ] [[package]] @@ -4446,19 +4529,19 @@ wheels = [ [[package]] name = "tornado" -version = "6.5.5" +version = "6.5.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f8/f1/3173dfa4a18db4a9b03e5d55325559dab51ee653763bb8745a75af491286/tornado-6.5.5.tar.gz", hash = "sha256:192b8f3ea91bd7f1f50c06955416ed76c6b72f96779b962f07f911b91e8d30e9", size = 516006, upload-time = "2026-03-10T21:31:02.067Z" } +sdist = { url = "https://files.pythonhosted.org/packages/64/24/95ec527ad67b76d59299e5465b3935d05e4294b7e0290a3924b7487df30b/tornado-6.5.7.tar.gz", hash = "sha256:66c513a76cda70d53907bc27cf1447557699c2e95aa48ba27a442ff61c3ddfc2", size = 519252, upload-time = "2026-06-08T17:34:51.232Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/59/8c/77f5097695f4dd8255ecbd08b2a1ed8ba8b953d337804dd7080f199e12bf/tornado-6.5.5-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:487dc9cc380e29f58c7ab88f9e27cdeef04b2140862e5076a66fb6bb68bb1bfa", size = 445983, upload-time = "2026-03-10T21:30:44.28Z" }, - { url = "https://files.pythonhosted.org/packages/ab/5e/7625b76cd10f98f1516c36ce0346de62061156352353ef2da44e5c21523c/tornado-6.5.5-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:65a7f1d46d4bb41df1ac99f5fcb685fb25c7e61613742d5108b010975a9a6521", size = 444246, upload-time = "2026-03-10T21:30:46.571Z" }, - { url = "https://files.pythonhosted.org/packages/b2/04/7b5705d5b3c0fab088f434f9c83edac1573830ca49ccf29fb83bf7178eec/tornado-6.5.5-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e74c92e8e65086b338fd56333fb9a68b9f6f2fe7ad532645a290a464bcf46be5", size = 447229, upload-time = "2026-03-10T21:30:48.273Z" }, - { url = "https://files.pythonhosted.org/packages/34/01/74e034a30ef59afb4097ef8659515e96a39d910b712a89af76f5e4e1f93c/tornado-6.5.5-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:435319e9e340276428bbdb4e7fa732c2d399386d1de5686cb331ec8eee754f07", size = 448192, upload-time = "2026-03-10T21:30:51.22Z" }, - { url = "https://files.pythonhosted.org/packages/be/00/fe9e02c5a96429fce1a1d15a517f5d8444f9c412e0bb9eadfbe3b0fc55bf/tornado-6.5.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3f54aa540bdbfee7b9eb268ead60e7d199de5021facd276819c193c0fb28ea4e", size = 448039, upload-time = "2026-03-10T21:30:53.52Z" }, - { url = "https://files.pythonhosted.org/packages/82/9e/656ee4cec0398b1d18d0f1eb6372c41c6b889722641d84948351ae19556d/tornado-6.5.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:36abed1754faeb80fbd6e64db2758091e1320f6bba74a4cf8c09cd18ccce8aca", size = 447445, upload-time = "2026-03-10T21:30:55.541Z" }, - { url = "https://files.pythonhosted.org/packages/5a/76/4921c00511f88af86a33de770d64141170f1cfd9c00311aea689949e274e/tornado-6.5.5-cp39-abi3-win32.whl", hash = "sha256:dd3eafaaeec1c7f2f8fdcd5f964e8907ad788fe8a5a32c4426fbbdda621223b7", size = 448582, upload-time = "2026-03-10T21:30:57.142Z" }, - { url = "https://files.pythonhosted.org/packages/2c/23/f6c6112a04d28eed765e374435fb1a9198f73e1ec4b4024184f21faeb1ad/tornado-6.5.5-cp39-abi3-win_amd64.whl", hash = "sha256:6443a794ba961a9f619b1ae926a2e900ac20c34483eea67be4ed8f1e58d3ef7b", size = 448990, upload-time = "2026-03-10T21:30:58.857Z" }, - { url = "https://files.pythonhosted.org/packages/b7/c8/876602cbc96469911f0939f703453c1157b0c826ecb05bdd32e023397d4e/tornado-6.5.5-cp39-abi3-win_arm64.whl", hash = "sha256:2c9a876e094109333f888539ddb2de4361743e5d21eece20688e3e351e4990a6", size = 448016, upload-time = "2026-03-10T21:31:00.43Z" }, + { url = "https://files.pythonhosted.org/packages/02/dc/c7043cab6fed8ae159fc1923ce829ada35c4dbd797d408a43858ffaf9639/tornado-6.5.7-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:148b2eb15c2c765a50796172c1e499649b35f30d2e3c3d3e15913cfa56bfb163", size = 448543, upload-time = "2026-06-08T17:34:38.052Z" }, + { url = "https://files.pythonhosted.org/packages/92/4f/090b1431e5a43df696feceffc268c5383cc079ecb5f08ce58f917109aafe/tornado-6.5.7-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9da38de27f1da3b78a966f0dae12b5a1ea9afe72ca805d84ff06508272ddf100", size = 446707, upload-time = "2026-06-08T17:34:39.594Z" }, + { url = "https://files.pythonhosted.org/packages/37/d8/ef374952fd5da67d4463122c2b8e5a96536ec10b4b339254c6dcde81d01c/tornado-6.5.7-cp39-abi3-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8d759e71906ee783f8867b93bf26a265743da4c1e2f4a018464c1ba019862972", size = 449774, upload-time = "2026-06-08T17:34:41.204Z" }, + { url = "https://files.pythonhosted.org/packages/35/37/d434c73f4c6e014b745b9b37085f34f40c022f007efff3d7fe65991899f3/tornado-6.5.7-cp39-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a46347a18f23fb92b396beebe0fb78f61dda0cc302445202c16203d8a18848b", size = 450745, upload-time = "2026-06-08T17:34:42.531Z" }, + { url = "https://files.pythonhosted.org/packages/b6/2b/56b9aff361d7f1ab728a805ec7d7ea835f8807afa9f5cc690ea0e630efb9/tornado-6.5.7-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:7778b30bef919231265e91c69963ce0f49a1e9c07ac900bbe75b19ce2575ba92", size = 450578, upload-time = "2026-06-08T17:34:43.787Z" }, + { url = "https://files.pythonhosted.org/packages/02/30/a7444fb23aa76860a14198fab96ac79f1866b0a6e19e26c4381b0938e50f/tornado-6.5.7-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:e726f0c75da7726eec023aa62751ff8878bd2737e34fbdd33b1ae5897d2200f5", size = 449985, upload-time = "2026-06-08T17:34:45.326Z" }, + { url = "https://files.pythonhosted.org/packages/5c/42/5f0e56c01e8d9d36f4e23f367b85ae6cae0c1ecddd5e6977d8388ad27488/tornado-6.5.7-cp39-abi3-win32.whl", hash = "sha256:f8de3bf12d3efdd0cbe7c8887868198f8a91415e3f29fcf258d9b8eb7b1d9ae4", size = 451047, upload-time = "2026-06-08T17:34:46.784Z" }, + { url = "https://files.pythonhosted.org/packages/c9/a4/b393076ffb21b469eec5b328a0534cf03a3b90bfc6b1f09507cdd075d938/tornado-6.5.7-cp39-abi3-win_amd64.whl", hash = "sha256:de942f843533a039ef9fa3d9c88c7cd8a7c94553fb5ad0154270989b3d99a2c4", size = 451485, upload-time = "2026-06-08T17:34:48.248Z" }, + { url = "https://files.pythonhosted.org/packages/71/2e/7b1c769803121b809112cf9a00681c472eae1d80e32d7ec0e0bd61d0d0e1/tornado-6.5.7-cp39-abi3-win_arm64.whl", hash = "sha256:ff934fce95643af5f11efdae618eaa73d469dc588641e5c8d19295a0c65c4796", size = 450506, upload-time = "2026-06-08T17:34:49.702Z" }, ] [[package]] @@ -4482,6 +4565,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/00/c0/8f5d070730d7836adc9c9b6408dec68c6ced86b304a9b26a14df072a6e8c/traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f", size = 85359, upload-time = "2024-04-19T11:11:46.763Z" }, ] +[[package]] +name = "truststore" +version = "0.10.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/a3/1585216310e344e8102c22482f6060c7a6ea0322b63e026372e6dcefcfd6/truststore-0.10.4.tar.gz", hash = "sha256:9d91bd436463ad5e4ee4aba766628dd6cd7010cf3e2461756b3303710eebc301", size = 26169, upload-time = "2025-08-12T18:49:02.73Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/97/56608b2249fe206a67cd573bc93cd9896e1efb9e98bce9c163bcdc704b88/truststore-0.10.4-py3-none-any.whl", hash = "sha256:adaeaecf1cbb5f4de3b1959b42d41f6fab57b2b1666adb59e89cb0b53361d981", size = 18660, upload-time = "2025-08-12T18:49:01.46Z" }, +] + [[package]] name = "typer" version = "0.24.1" @@ -4508,23 +4600,23 @@ wheels = [ [[package]] name = "types-protobuf" -version = "6.32.1.20260221" +version = "7.34.1.20260518" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5f/e2/9aa4a3b2469508bd7b4e2ae11cbedaf419222a09a1b94daffcd5efca4023/types_protobuf-6.32.1.20260221.tar.gz", hash = "sha256:6d5fb060a616bfb076cbb61b4b3c3969f5fc8bec5810f9a2f7e648ee5cbcbf6e", size = 64408, upload-time = "2026-02-21T03:55:13.916Z" } +sdist = { url = "https://files.pythonhosted.org/packages/29/59/e2b13b499d15e6720150c4b1a8d91e31fcacf716b432397475b3151ff7e4/types_protobuf-7.34.1.20260518.tar.gz", hash = "sha256:28cfaded25889cb83ebfb63cfb0a43628f0b6f3785767bec17287dc6468795f2", size = 68936, upload-time = "2026-05-18T06:01:47.332Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/e8/1fd38926f9cf031188fbc5a96694203ea6f24b0e34bd64a225ec6f6291ba/types_protobuf-6.32.1.20260221-py3-none-any.whl", hash = "sha256:da7cdd947975964a93c30bfbcc2c6841ee646b318d3816b033adc2c4eb6448e4", size = 77956, upload-time = "2026-02-21T03:55:12.894Z" }, + { url = "https://files.pythonhosted.org/packages/2a/1f/ec5caf72c2e3b688ca3927e0979a04ddad19e1afc4bf1c199bd743e0f419/types_protobuf-7.34.1.20260518-py3-none-any.whl", hash = "sha256:a0a5337413347166439c0e07cbc26c6164d091401c6f01b1dfd8cdb966c4dd8f", size = 85992, upload-time = "2026-05-18T06:01:45.696Z" }, ] [[package]] name = "types-requests" -version = "2.33.0.20260408" +version = "2.33.0.20260712" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/69/6a/749dc53a54a3f35842c1f8197b3ca6b54af6d7458a1bfc75f6629b6da666/types_requests-2.33.0.20260408.tar.gz", hash = "sha256:95b9a86376807a216b2fb412b47617b202091c3ea7c078f47cc358d5528ccb7b", size = 23882, upload-time = "2026-04-08T04:34:49.33Z" } +sdist = { url = "https://files.pythonhosted.org/packages/db/51/703318f7b7be8bee126ec13bf615050f932d0179b8784420f3a0199cc769/types_requests-2.33.0.20260712.tar.gz", hash = "sha256:2141b67ab534a5c5cd2dac5034f2a35f42e699c5bf185eee608c5246a069d7fb", size = 25084, upload-time = "2026-07-12T05:14:20.455Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/90/b8/78fd6c037de4788c040fdd323b3369804400351b7827473920f6c1d03c10/types_requests-2.33.0.20260408-py3-none-any.whl", hash = "sha256:81f31d5ea4acb39f03be7bc8bed569ba6d5a9c5d97e89f45ac43d819b68ca50f", size = 20739, upload-time = "2026-04-08T04:34:48.325Z" }, + { url = "https://files.pythonhosted.org/packages/62/e7/010c87f559e216d83f9dc51e939633fd0d0ead3377340181ab0e223cd3b5/types_requests-2.33.0.20260712-py3-none-any.whl", hash = "sha256:de027e28c171d3da529689cbfa023b0b4eab188c8dfa22fd834eebd2cee6e7bb", size = 21392, upload-time = "2026-07-12T05:14:19.616Z" }, ] [[package]] @@ -4568,11 +4660,11 @@ wheels = [ [[package]] name = "uncalled-for" -version = "0.3.1" +version = "0.3.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e1/68/35c1d87e608940badbcfeb630347aa0509897284684f61fab6423d02b253/uncalled_for-0.3.1.tar.gz", hash = "sha256:5e412ac6708f04b56bef5867b5dcf6690ebce4eb7316058d9c50787492bb4bca", size = 49693, upload-time = "2026-04-07T13:05:06.462Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/82/345cc927f7fbdae6065e7768759932fcc827fc20b29b45dfbafa2f1f7da4/uncalled_for-0.3.2.tar.gz", hash = "sha256:89f5dbcd71e2b8f47c030b1fa302e6cce2ec795d1ac565eeb6525c5fe55cb8a2", size = 50032, upload-time = "2026-05-06T13:38:25.204Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/11/e1/7ec67882ad8fc9f86384bef6421fa252c9cbe5744f8df6ce77afc9eca1f5/uncalled_for-0.3.1-py3-none-any.whl", hash = "sha256:074cdc92da8356278f93d0ded6f2a66dd883dbecaf9bc89437646ee2289cc200", size = 11361, upload-time = "2026-04-07T13:05:05.341Z" }, + { url = "https://files.pythonhosted.org/packages/3b/25/2c87754f3a9e692315f7b811244090e68f362979fc8886b3fbd2985a1d8c/uncalled_for-0.3.2-py3-none-any.whl", hash = "sha256:0ff60b142c7d1f8070bde9d42afaa70aedc77dcc10998c227687e9c15713418e", size = 11444, upload-time = "2026-05-06T13:38:24.025Z" }, ] [[package]] @@ -4586,11 +4678,11 @@ wheels = [ [[package]] name = "urllib3" -version = "2.6.3" +version = "2.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } +sdist = { url = "https://files.pythonhosted.org/packages/53/0c/06f8b233b8fd13b9e5ee11424ef85419ba0d8ba0b3138bf360be2ff56953/urllib3-2.7.0.tar.gz", hash = "sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c", size = 433602, upload-time = "2026-05-07T16:13:18.596Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, + { url = "https://files.pythonhosted.org/packages/7f/3e/5db95bcf282c52709639744ca2a8b149baccf648e39c8cc87553df9eae0c/urllib3-2.7.0-py3-none-any.whl", hash = "sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897", size = 131087, upload-time = "2026-05-07T16:13:17.151Z" }, ] [[package]] @@ -4794,7 +4886,7 @@ wheels = [ [[package]] name = "xai-sdk" -version = "1.11.0" +version = "1.17.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohttp" }, @@ -4806,9 +4898,9 @@ dependencies = [ { name = "pydantic" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/49/32/bb8385f7a3b05ce406b689aa000c9a34289caa1526f1c093a1cefc0d9695/xai_sdk-1.11.0.tar.gz", hash = "sha256:ca87a830d310fb8e06fba44fb2a8c5cdf0d9f716b61126eddd51b7f416a63932", size = 404313, upload-time = "2026-03-27T18:23:10.091Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/79/722beabb2ffbac413e4e776230ee56db2710e83c02f038f5769f9da748c1/xai_sdk-1.17.0.tar.gz", hash = "sha256:ec695ad8b459a4080c01f6e7bb8abf38ed6a4ec69532bfdd73c4de14db1d485c", size = 452803, upload-time = "2026-06-12T03:44:13.329Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/76/86d9a3589c725ce825d2ed3e7cb3ecf7f956d3fd015353d52197bb341bcd/xai_sdk-1.11.0-py3-none-any.whl", hash = "sha256:fe58ce6d8f8115ae8bd57ded57bcd847d0bb7cb28bb7b236abefd4626df1ed8d", size = 251388, upload-time = "2026-03-27T18:23:08.573Z" }, + { url = "https://files.pythonhosted.org/packages/d9/ad/99ad3209727f6e0e280681c05aa6a49feb130926d939bdb4767ee825b0da/xai_sdk-1.17.0-py3-none-any.whl", hash = "sha256:88a6a53181fa13d55662e3296dafd2a3cf258803450b7d82c7318b8c64e8a485", size = 273997, upload-time = "2026-06-12T03:44:11.828Z" }, ] [[package]] From 4f1c224c5b815340df85ff9b2f22d4edcee6d0c6 Mon Sep 17 00:00:00 2001 From: gautam8387 Date: Tue, 14 Jul 2026 17:43:34 +0200 Subject: [PATCH 2/3] wip --- .gitignore | 1 + scripts/test_modal_compound_labels.sh | 2 ++ scripts/test_modal_simple_labels.sh | 2 ++ 3 files changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index 5ab5f7c..d71f105 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ tmp/ *.npz.meta *.json cyteonto_results/ +dist/ diff --git a/scripts/test_modal_compound_labels.sh b/scripts/test_modal_compound_labels.sh index 807ba0c..f6bcbce 100755 --- a/scripts/test_modal_compound_labels.sh +++ b/scripts/test_modal_compound_labels.sh @@ -69,3 +69,5 @@ while true; do ;; esac done + +# run-25b173d8-bd0b-4b26-96b3-0c548dd356cc diff --git a/scripts/test_modal_simple_labels.sh b/scripts/test_modal_simple_labels.sh index d46b85f..e1c68a4 100755 --- a/scripts/test_modal_simple_labels.sh +++ b/scripts/test_modal_simple_labels.sh @@ -68,3 +68,5 @@ while true; do ;; esac done + +# run-7355b0fd-377f-49af-bab2-2658b2142ae8 From a001446d8f795ad2f96456acffe01df0ca582f67 Mon Sep 17 00:00:00 2001 From: gautam8387 Date: Tue, 14 Jul 2026 17:51:44 +0200 Subject: [PATCH 3/3] wip --- cyteonto/describe.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cyteonto/describe.py b/cyteonto/describe.py index 45ab432..380c103 100644 --- a/cyteonto/describe.py +++ b/cyteonto/describe.py @@ -250,7 +250,7 @@ async def _run_once( for part in getattr(msg, "parts", []): if isinstance(part, ToolCallPart): tool_counts[part.tool_name] = tool_counts.get(part.tool_name, 0) + 1 - usage = result.usage() + usage = result.usage return ( # type: ignore[return-value] result.output, tool_counts, @@ -486,7 +486,7 @@ async def _run_decompose_once( for part in getattr(msg, "parts", []): if isinstance(part, ToolCallPart): tool_counts[part.tool_name] = tool_counts.get(part.tool_name, 0) + 1 - usage = result.usage() + usage = result.usage return ( # type: ignore[return-value] result.output, tool_counts,