diff --git a/.agents/skills/codacy-issues-fetcher/SKILL.md b/.agents/skills/codacy-issues-fetcher/SKILL.md new file mode 100644 index 0000000..11d23ec --- /dev/null +++ b/.agents/skills/codacy-issues-fetcher/SKILL.md @@ -0,0 +1,54 @@ +--- +name: codacy-issues-fetcher +description: Retrieve and format Codacy analysis issues by running `scripts/codacy_issues.py` in the ExStruct workspace. Use when users ask to inspect repository or pull-request Codacy findings, filter by severity, or produce structured issue output for review and fix planning. +--- + +# Codacy Issues Fetcher + +Run `scripts/codacy_issues.py` as the primary interface to Codacy issue retrieval. +Avoid reimplementing API calls unless the script itself must be changed. + +## Workflow + +1. Confirm prerequisites. +- Run from repository root so `scripts/codacy_issues.py` is reachable. +- Ensure `CODACY_API_TOKEN` is set and valid. +- Prefer explicit `org` and `repo` if user provides them; otherwise rely on Git `origin` auto-detection. + +2. Choose scope and severity. +- Repository scope: omit `--pr`. +- Pull request scope: pass `--pr `. +- Severity filter (`--min-level`): `Error`, `High`, `Warning`, `Info`. +- Provider (`--provider`): `gh`, `gl`, `bb` (default is effectively `gh`). + +3. Run one of the command patterns. + +```powershell +# Repository issues (explicit target) +python scripts/codacy_issues.py --provider gh --min-level Warning + +# Pull request issues (explicit target) +python scripts/codacy_issues.py --pr --provider gh --min-level Warning + +# Pull request issues (auto-detect org/repo from git origin) +python scripts/codacy_issues.py --pr --min-level Warning +``` + +4. Parse output JSON and respond with actionable summary. +- Trust payload fields: `scope`, `organization`, `repository`, `pullRequest`, `minLevel`, `total`, `issues`. +- `issues` entries are formatted as: + ` | : | | | ` +- Report high-severity findings first, then summarize counts. + +## Error Handling + +- `HTTP 401` / `Unauthorized`: token invalid or missing permissions. Ask user to set or refresh `CODACY_API_TOKEN`. +- `CODACY_API_TOKEN is not set`: export the environment variable before retrying. +- `Invalid --provider`: use only `gh`, `gl`, or `bb`. +- Segment validation errors (`Invalid org/repo/pr`): sanitize input and rerun. + +## Output Policy + +- Return concise triage-ready results, not raw command logs. +- Include the exact command you used when reproducibility matters. +- If no issues match the selected `--min-level`, state that explicitly. diff --git a/.agents/skills/codacy-issues-fetcher/agents/openai.yaml b/.agents/skills/codacy-issues-fetcher/agents/openai.yaml new file mode 100644 index 0000000..703b240 --- /dev/null +++ b/.agents/skills/codacy-issues-fetcher/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Codacy Issues Fetcher" + short_description: "Run codacy_issues.py to fetch and triage Codacy findings" + default_prompt: "Use $codacy-issues-fetcher to collect Codacy issues for this repository or PR and summarize the highest-priority fixes." diff --git a/.bandit b/.bandit new file mode 100644 index 0000000..953c879 --- /dev/null +++ b/.bandit @@ -0,0 +1,2 @@ +[bandit] +exclude_dirs = tests,benchmark diff --git a/.codacy.yml b/.codacy.yml index da332f7..1f1b4b8 100644 --- a/.codacy.yml +++ b/.codacy.yml @@ -1,2 +1,3 @@ exclude_paths: - "benchmark/**" + - "tests/**" diff --git a/AGENTS.md b/AGENTS.md index 6a7fb01..df297d9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -47,14 +47,10 @@ AI は必ず次のルールに従ってコードを生成してください。 ### 2.4 import の順序 -``` - 1. 標準ライブラリ 2. サードパーティ 3. exstruct 内のモジュール -``` - ### 2.5 Docstring(Google スタイル) 関数・クラスには必ず Docstring を付ける。 @@ -111,51 +107,7 @@ AI は必要であれば **禁止事項を回避するためにリファクタ --- -# 5. AI に依頼するときの標準プロンプト(Developer Prompt) - -AI に実装依頼する際は、以下のフォーマットが推奨されます。 - -``` - -あなたは ExStruct のコア開発エンジニアです。 -mypy strict + Ruff check を完全に通過するコードのみ生成してください。 - -遵守事項: - -- 型ヒント完全 -- 境界は BaseModel、内部は dataclass を返す -- 1 関数 = 1 責務 -- import 順序厳守 -- Google スタイルの docstring -- 外部ライブラリ(xlwings, pandas)は Any 扱い -- 複雑度を抑えること -- 必要に応じて補助関数を追加して良い - -出力は Python コードのみ。 - -``` - ---- - -# 6. AI にエラー修正を依頼する際のルール - -mypy / Ruff のエラーが出た場合、AI への指示は次のテンプレートを使用します: - -``` - -以下は mypy / Ruff のエラーメッセージです。 -すべてのエラーが解消されるようにコードを修正してください。 - -<エラー全文> - -``` - -AI は差分修正タスクが得意なため、 -この方式が最も効率的かつ正確です。 - ---- - -# 7. AI の責務外(人間が担当する領域) +# 5. AI の責務外(人間が担当する領域) AI は以下の領域は担当しません。人間が判断します。 @@ -168,45 +120,17 @@ AI は以下の領域は担当しません。人間が判断します。 --- -# 8. AI を開発パイプラインと組み合わせる例 - -推奨フロー: - -1. 人間:仕様を記述 -2. AI:コード生成(テンプレート+ガイドラインに従う) -3. 自動:`mypy strict` チェック -4. 自動:`ruff check --fix` -5. AI:エラー出たら修正 -6. 人間:レビューしてマージ - -このサイクルにより **高速 & 高品質の開発が可能** になります。 - ---- - -# 9. 最終目標 - -AI エージェントが ExStruct のコードを書く場合でも: - -- バグのない型安全なコード -- 一貫したコーディングスタイル -- 仕様の誤解を最小化した実装 -- 人間が安心してレビューできるコード - -を常に提供できるようにする。 - -この AGENTS ガイドラインは -**Codex / ChatGPT を“チームメンバー”として扱うための基盤** です。 - ---- - -# 10. 各種仕様の確認 - -AI エージェントは必要に応じて以下のドキュメントを参照して ExStruct の開発をする +# 6. 遵守すべき作業手順 -- 処理アーキテクチャ: `docs/architecture/pipeline.md` -- プロジェクトアーキテクチャ: `docs/contributors/architecture.md` -- コーディングガイドライン: `docs/agents/CODING_GUIDELINES.md` -- データモデル: `docs/agents/DATA_MODEL.md` -- タスク: `docs/agents/TASKS.md` +AI はコード生成前に以下の手順を必ず踏んでください。 -**以上。AI はこのガイドラインに従って ExStruct の開発に参加してください。** +1. **要件の理解**:仕様書や設計資料を読み込み、要件を完全に理解する +2. **設計の検討**:必要に応じて関数分割やモデル設計を検討する。 +3. **仕様の定義**: 要件に基づいて、関数の引数・戻り値の型を定義する。 +4. **タスクの割り当て**: 各タスクを明確に定義し、実装順序を決定する。 +5. **コード実装**: 上記の基準を守りながらコードを実装する。 +6. **コードレビュー**: 自動生成されたコードを自己レビューし、品質基準を満たしているか確認する。 +7. **テストコードの生成**: 必要に応じて、テストコードも生成する。 +8. **テストの実行**: 生成されたテストコードを実行し、期待通りの動作を確認する。 +9. **静的解析**: `uv run task precommit-run` を実行し、mypy / Ruff エラーがないことを確認する。 +10. **ドキュメントの更新**: 変更があれば、関連するドキュメントも更新する。 \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 851081c..6bae0a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,21 @@ All notable changes to this project are documented in this file. This changelog - _No unreleased changes yet._ +## [0.4.4] - 2026-02-16 + +### Added + +- Added an MVP of Excel editing for MCP via `exstruct_patch`, including atomic apply semantics and expanded operations: `set_range_values`, `fill_formula`, `set_value_if`, and `set_formula_if`. +- Added direct A1-oriented MCP read tools for extracted JSON: `exstruct_read_range`, `exstruct_read_cells`, and `exstruct_read_formulas`. +- Added patch safety/review options: `dry_run`, `return_inverse_ops`, `preflight_formula_check`, and `auto_formula`. + +### Changed + +- Improved `exstruct_patch` input compatibility: `ops` now accepts both object lists (recommended) and JSON object strings. +- Enabled `alpha_col` support more broadly across extraction/read flows, and added `merged_ranges` output support for alpha-column mode. +- Updated MCP documentation and chunking guidance, including clearer error messages and mode guidance. +- Changed MCP default conflict policy to `overwrite` for output handling. + ## [0.4.2] - 2026-01-23 ### Changed diff --git a/README.md b/README.md index 20e5283..04eb201 100644 --- a/README.md +++ b/README.md @@ -73,11 +73,13 @@ exstruct-mcp --root C:\data --log-file C:\logs\exstruct-mcp.log --on-conflict re Available tools: - `exstruct_extract` +- `exstruct_patch` - `exstruct_read_json_chunk` - `exstruct_validate_input` Notes: +- In MCP, `exstruct_extract` defaults to `options.alpha_col=true` (column keys: `A`, `B`, ...). Set `options.alpha_col=false` for legacy 0-based numeric string keys. - Logs go to stderr (and optionally `--log-file`) to avoid contaminating stdio responses. - On Windows with Excel, standard/verbose can use COM for richer extraction. On non-Windows, COM is unavailable and extraction uses openpyxl-based fallbacks. diff --git a/docs/README.en.md b/docs/README.en.md index c7a99cb..d47dcc8 100644 --- a/docs/README.en.md +++ b/docs/README.en.md @@ -73,11 +73,13 @@ exstruct-mcp --root C:\data --log-file C:\logs\exstruct-mcp.log --on-conflict re Available tools: - `exstruct_extract` +- `exstruct_patch` - `exstruct_read_json_chunk` - `exstruct_validate_input` Notes: +- In MCP, `exstruct_extract` defaults to `options.alpha_col=true` (column keys: `A`, `B`, ...). Set `options.alpha_col=false` for legacy 0-based numeric string keys. - Logs go to stderr (and optionally `--log-file`) to avoid contaminating stdio responses. - On Windows with Excel, standard/verbose can use COM for richer extraction. On non-Windows, COM is unavailable and extraction uses openpyxl-based fallbacks. diff --git a/docs/README.ja.md b/docs/README.ja.md index f19b011..328597e 100644 --- a/docs/README.ja.md +++ b/docs/README.ja.md @@ -70,9 +70,16 @@ exstruct-mcp --root C:\data --log-file C:\logs\exstruct-mcp.log --on-conflict re 利用可能なツール: - `exstruct_extract` +- `exstruct_patch` - `exstruct_read_json_chunk` +- `exstruct_read_range` +- `exstruct_read_cells` +- `exstruct_read_formulas` - `exstruct_validate_input` +- `exstruct_read_range` / `exstruct_read_cells` / `exstruct_read_formulas` は v0.4.4 で追加され、MCPサーバー実装とテストに登録済みです。 +- MCPでは `exstruct_extract` の `options.alpha_col=true` が既定です(列キーは `A`, `B`, ...)。従来の0始まり数値キーが必要な場合は `options.alpha_col=false` を指定してください。 + 注意点: - 標準入出力の応答を汚染しないよう、ログは標準エラー出力(およびオプションで`--log-file`で指定したファイル)に出力されます。 diff --git a/docs/agents/API_DESIGN.md b/docs/agents/API_DESIGN.md deleted file mode 100644 index fe0b43a..0000000 --- a/docs/agents/API_DESIGN.md +++ /dev/null @@ -1,68 +0,0 @@ -# API Design — ExStruct - -ExStruct のトップレベル Python API 設計。 - -## 基本 API - -```python -import exstruct as xs - -data = xs.extract("file.xlsx") -``` - -### 戻り値 - -`WorkbookData`(Pydantic model) - ---- - -## CLI - -```bash -exstruct file.xlsx --format json > output.json -``` - -オプション: - -- `--image` → PNG 出力 -- `--pdf` → PDF 出力 -- `--dpi` → PNG 解像度 -- `--sheets-dir` → シートごとの分割出力 -- `--multiple` → 複数ファイル処理 - ---- - -## JSON/YAML/TOON 出力 - -```python -data = xs.extract("file.xlsx") - -xs.export("file.xlsx") -data.to_json(pretty=True) -data.to_yaml() -data.to_toon() -data.save("file.json") -data["Sheet1"] # WorkbookData.__getitem__ -for name, sheet in data: # WorkbookData.__iter__ - print(name, len(sheet.rows)) - -# ExStructEngine (per-instance options) -from exstruct import ExStructEngine, FilterOptions, FormatOptions, OutputOptions, StructOptions -engine = ExStructEngine( - options=StructOptions(mode="standard"), - output=OutputOptions( - format=FormatOptions(pretty=True), - filters=FilterOptions(include_shapes=False), - ), -) -wb = engine.extract("file.xlsx") -engine.export(wb, "filtered.json") -``` - ---- - -## 今後拡張予定 - -- `xs.detect_tables()` -- `xs.semantic_shapes()` -- `xs.smartart()` diff --git a/docs/agents/FEATURE_SPEC.md b/docs/agents/FEATURE_SPEC.md index c92baed..c78a1bf 100644 --- a/docs/agents/FEATURE_SPEC.md +++ b/docs/agents/FEATURE_SPEC.md @@ -1,100 +1,697 @@ # Feature Spec for AI Agent (Phase-by-Phase) -本ドキュメントは AI エージェント向けに、段階的に実装を進めるための仕様メモです。 +本ドキュメントは、ExStruct MCP に新規追加する編集ツール `exstruct_patch` の MVP 仕様を定義します。 --- -## MCPサーバー機能追加 +## 1. 目的・スコープ -### 目的 +- **MVP対象**: セル値更新 / 数式更新 / 新規シート追加 +- **非対象**: 図形・チャート編集、大規模レイアウト変更 +- **再計算**: 行わない(Excel側の再計算に委ねる) -- MCP クライアント(Codex / Claude / VS Code Copilot / Gemini CLI 等)から ExStruct を「ツール」として安全に呼び出せるようにする -- 推論はエージェント側で行い、MCP は制御面(実行・結果参照)に徹する +--- + +## 2. ツールI/F定義(OpenAPI / Pydantic想定) + +### Tool名 + +- `exstruct_patch` + +### Input + +- `xlsx_path: str` +- `ops: list[PatchOp]` +- `out_dir: str | None` +- `out_name: str | None` +- `on_conflict: "overwrite" | "skip" | "rename" | None` +- `auto_formula: bool`(default: false) + +### Output + +- `out_path: str` +- `patch_diff: list[PatchDiffItem]` +- `warnings: list[str]` +- `error: PatchErrorDetail | null` + +--- + +## 3. 操作種別(PatchOp) + +### set_value + +- `op: "set_value"` +- `sheet: str` +- `cell: str` (A1形式) +- `value: str | int | float | None`(nullでクリア) + +### set_formula + +- `op: "set_formula"` +- `sheet: str` +- `cell: str` (A1形式) +- `formula: str`(必ず `=` から開始) + +### add_sheet + +- `op: "add_sheet"` +- `sheet: str`(新規シート名) + +--- + +## 4. バリデーション規則 + +- `xlsx_path` + - 存在するファイルであること + - 許可パス内であること(PathPolicy) + - 拡張子は `.xlsx` / `.xlsm` / `.xls`(`.xls` は COM 利用可能環境のみ) +- `sheet` + - 空文字禁止 + - `add_sheet` は既存名と重複禁止 + - `set_value` / `set_formula` は存在シートのみ +- `cell` + - A1形式を必須(例: `B3`) +- `set_value` + - `value` が `=` から始まる場合は通常は拒否 + - `auto_formula=true` のときは `set_formula` 相当として処理 +- `set_formula` + - `formula` は必ず `=` から開始 + +--- + +## 5. 実行セマンティクス + +- opsは **順序通り** に評価・適用 +- **全て成功した場合のみ保存**(原子性) +- 失敗時は **Excelを変更せずエラー** を返す +- `add_sheet` 実行後にのみ、そのシートへの `set_value` / `set_formula` を許可 + +--- + +## 6. 出力差分(patch_diff) + +最低限の構造: + +- `op: "set_value" | "set_formula" | "add_sheet"` +- `op_index: int` +- `sheet: str` +- `cell: str | null` +- `before: PatchValue | null` +- `after: PatchValue | null` +- `status: "applied" | "skipped"` + +`PatchValue`: + +- `kind: "value" | "formula" | "sheet"` +- `value: str | int | float | null` + +`PatchErrorDetail`: + +- `op_index: int` +- `op: "set_value" | "set_formula" | "add_sheet"` +- `sheet: str` +- `cell: str | null` +- `message: str` + +--- + +## 7. バックエンド方針 + +- **Windows + COM利用可能**: COM優先 +- **それ以外**: openpyxl +- openpyxl使用時は、図形/チャート等の保持制限を `warnings` で通知 + +--- + +## 8. 競合ポリシー + +- `on_conflict` 未指定時は **サーバー起動時の `--on-conflict` 設定値** を既定値とする +- `skip` の場合: + - `patch_diff` は空配列 + - `warnings` にスキップ理由を記載 + +出力名の既定: + +- `out_name` 未指定時は `"{stem}_patched{suffix}"` + +--- + +## 9. エラー処理 + +- パス違反 / セル不正 / 数式不正: `ValueError` +- シート不在 / 操作不正: `PatchErrorDetail` を `error` に格納 +- 読み込み不能: `FileNotFoundError` / `OSError` +- バックエンド例外: `RuntimeError` + +--- + +## 10. 例 + +### set_value + +```json +{ + "xlsx_path": "book.xlsx", + "ops": [ + { + "op": "set_value", + "sheet": "フォーム", + "cell": "B2", + "value": "山田太郎" + } + ] +} +``` + +### set_formula + +```json +{ + "xlsx_path": "book.xlsx", + "ops": [ + { + "op": "set_formula", + "sheet": "計算", + "cell": "C10", + "formula": "=SUM(Sheet1!A:A)" + } + ] +} +``` + +### add_sheet + +```json +{ + "xlsx_path": "book.xlsx", + "ops": [ + { "op": "add_sheet", "sheet": "売上集計" }, + { "op": "set_value", "sheet": "売上集計", "cell": "A1", "value": "月" } + ] +} +``` + +--- + +## 11. レビュー対応仕様(MVP修正) + +### 11.1 `on_conflict` 既定値の統一 + +- サーバーCLI引数 `--on-conflict` の既定値・指定値は、`exstruct_extract` と `exstruct_patch` の双方に同一に適用する +- `exstruct_patch` で固定値 `rename` を持たない +- ツール呼び出しで `on_conflict` が未指定の場合: + - サーバー起動時の `--on-conflict` を適用 +- ツール呼び出しで `on_conflict` が指定された場合: + - ツール引数を最優先する + +### 11.2 出力ディレクトリ生成の明確化 + +- `exstruct_patch` は保存前に `out_path.parent.mkdir(parents=True, exist_ok=True)` 相当を実行する +- `out_dir` が存在しない場合でも保存可能にする + +### 11.3 `.xls` サポート条件の明確化 + +- `.xls` は **COM利用可能環境のみ編集対象** とする +- COM利用不可時の `.xls` は `ValueError` を返す(メッセージで「COM必須」を明示) +- バリデーション規則の拡張子記述を次に更新する: + - `.xlsx` / `.xlsm`: 常時対象 + - `.xls`: 条件付き対象(Windows + COM) + +### 11.4 後方互換性 + +- 既存の `PatchOp` / `PatchResult` スキーマは変更しない +- 変更はデフォルト解決順序・保存前処理・エラーメッセージの明確化に限定する + +### 11.5 テスト要件(レビュー対応分) + +- `exstruct_patch` で `on_conflict` 未指定時にサーバー既定値が反映されること +- `exstruct_patch` で `on_conflict` 指定時にツール引数が優先されること +- 未作成 `out_dir` 指定で保存成功すること +- `.xls` + COM不可時に期待どおり `ValueError` となること(メッセージ確認を含む) + +--- + +## 12. 次フェーズ仕様(便利機能 1〜5) + +### 12.1 対象機能 + +- `dry_run`(非破壊プレビュー) +- `undo` 用逆パッチ生成 +- 範囲操作(値一括設定 / 数式フィル) +- 条件付き更新(CAS) +- 数式ヘルスチェック + +### 12.2 ツールI/F拡張 + +`exstruct_patch` の Input に以下を追加: + +- `dry_run: bool = false` +- `return_inverse_ops: bool = false` +- `preflight_formula_check: bool = false` + +`exstruct_patch` の Output に以下を追加: + +- `inverse_ops: list[PatchOp]`(default: `[]`) +- `formula_issues: list[FormulaIssue]`(default: `[]`) + +`FormulaIssue`: + +- `sheet: str` +- `cell: str` +- `level: "warning" | "error"` +- `code: "invalid_token" | "ref_error" | "name_error" | "div0_error" | "value_error" | "na_error" | "circular_ref_suspected"` +- `message: str` + +### 12.3 操作種別拡張(PatchOp) + +#### set_range_values + +- `op: "set_range_values"` +- `sheet: str` +- `range: str`(A1範囲形式、例: `B2:D4`) +- `values: list[list[str | int | float | None]]` +- `shape(values)` と `range` サイズが一致しない場合は `ValueError` + +#### fill_formula + +- `op: "fill_formula"` +- `sheet: str` +- `range: str`(縦または横の連続範囲) +- `base_cell: str` +- `formula: str`(`=` 始まり) +- `formula` を `base_cell` に置いたときの相対参照を、`range` 内に展開する + +#### set_value_if + +- `op: "set_value_if"` +- `sheet: str` +- `cell: str` +- `expected: str | int | float | None` +- `value: str | int | float | None` +- 現在値が `expected` と一致した場合のみ更新 +- 不一致時は `PatchDiffItem.status="skipped"` とし、`warnings` に理由を追加 + +#### set_formula_if + +- `op: "set_formula_if"` +- `sheet: str` +- `cell: str` +- `expected: str | int | float | None` +- `formula: str`(`=` 始まり) +- 条件判定は `set_value_if` と同様 + +### 12.4 実行セマンティクス(追加) -### スコープ(MVP) +- `dry_run=true` の場合: + - 保存しない + - `patch_diff` / `warnings` / `formula_issues` / `inverse_ops` のみ返す + - `out_path` は保存予定パスを返す +- `return_inverse_ops=true` の場合: + - `applied` な変更のみ逆操作を生成する + - 逆操作順は「適用順の逆順」とする +- `preflight_formula_check=true` の場合: + - 適用後ワークブックを走査し `formula_issues` を返す + - `level=error` がある場合の扱い: + - `dry_run=false`: 変更は保存せず `error` を返す + - `dry_run=true`: 保存なしのため `error` は返さず、問題一覧のみ返す -- stdio トランスポートの MCP サーバー -- ツール: `exstruct_extract` -- 抽出結果は **必ずファイル出力**(MCP 応答はパス + 軽いメタ情報) -- 安全なパス制約(allowlist / deny glob) +### 12.5 バリデーション規則(追加) -### 前提・制約 +- `range` は A1 範囲形式(例: `A1:C3`)必須 +- `fill_formula.range` は単一行または単一列のみ(MVP制約) +- 条件付き更新の `expected` 比較は「内部正規化後の値一致」で判定 +- `set_value_if` で `value` が `=` 始まりかつ `auto_formula=false` の場合は既存 `set_value` と同様に拒否 -- 1MB 程度の Excel を想定 -- 処理時間は長くなっても高品質重視 -- Windows 以外は COM なしの簡易読み取り(ライブラリのスタンスに準拠) +### 12.6 後方互換性 -### 出力 JSON の仕様 +- 既存 `set_value` / `set_formula` / `add_sheet` の仕様は変更しない +- 新規フィールドは optional 追加のみで、既存クライアントを破壊しない -- `mode` で出力粒度を選択: `light` / `standard` / `verbose` -- 互換方針: 追加は OK、破壊的変更は NG +### 12.7 受け入れ基準 -#### `light` +- `dry_run=true` でファイル更新時刻が変化しない +- `return_inverse_ops=true` で、逆パッチ適用により元状態へ戻せる +- `set_range_values` がサイズ不一致を検知できる +- `set_value_if` / `set_formula_if` が不一致時に `skipped` を返せる +- `preflight_formula_check=true` で `#REF!` 等を検出して `formula_issues` に反映できる -- 軽量メタデータ中心(シート名、件数、主要範囲など) -- 大きなセル本文や詳細構造は含めない +--- + +## 13. ABC列名キー出力オプション(`alpha_col`) + +### 13.1 背景・目的 + +AIエージェントがExStruct MCPで抽出した結果をもとに `exstruct_patch` でセル編集を行う際、 +`CellRow.c` のキーが 0-based 数値文字列(`"0"`, `"12"`, `"18"` 等)のため、 +Excel上の列名(A, M, S 等)との対応が直感的に取れず、**誤ったセル座標への書き込みが頻発する**。 + +本機能は、抽出結果の `CellRow.c` キーを Excel 列名形式(A, B, ..., Z, AA, AB, ...)で +出力するオプションを提供し、AIエージェントの座標理解精度を大幅に改善する。 + +### 13.2 スコープ -#### `standard` +- **対象**: `CellRow.c` のキー、`CellRow.links` のキー +- **非対象**: `MergedCells.items` / `PrintArea` / `formulas_map` / `colors_map` の列インデックス(整数のまま) +- **変換方式**: 0-based 数値 → Excel列名(`0` → `A`, `1` → `B`, `25` → `Z`, `26` → `AA`) -- 通常運用向けの基本情報 -- セル情報は要約・圧縮前提 +### 13.3 I/F -#### `verbose` +#### 本体 API(`ExStructEngine` / `extract()` / `process_excel()`) -- 詳細な構造情報を含む -- 大容量になりやすいため、ファイル出力+チャンク取得前提 +- `StructOptions` に `alpha_col: bool = False` を追加 +- `True` の場合、抽出結果の `CellRow.c` / `CellRow.links` のキーを ABC 形式に変換する -### MCP ツール仕様(案) +#### CLI -#### `exstruct_extract` +- `--alpha-col` フラグを追加(`action="store_true"`) +- `process_excel()` に `alpha_col` パラメータを追加 -- 入力: `xlsx_path`, `mode`, `format`, `out_dir?`, `out_name?`, `options?` -- 出力: `out_path`, `workbook_meta`, `warnings`, `engine` -- 実装: 内部 API を優先、フォールバックで CLI サブプロセス +#### MCP ツール(`exstruct_extract`) -#### `exstruct_read_json_chunk`(実用化フェーズ) +- `ExtractOptions` に `alpha_col: bool = False` を追加 +- `ExtractToolInput.options.alpha_col` をエンジンに連携 -- 入力: `out_path`, `sheet?`, `max_bytes?`, `filter?`, `cursor?` -- 出力: `chunk`, `next_cursor?` -- 方針: 返却サイズを抑制し、段階的に取得できること +### 13.4 変換ユーティリティ -#### `exstruct_validate_input`(実用化フェーズ) +- `exstruct.models.col_index_to_alpha(index: int) -> str` を新設 + - 0-based 整数 → Excel 列名文字列 + - 例: `0` → `"A"`, `25` → `"Z"`, `26` → `"AA"`, `701` → `"ZZ"` +- `exstruct.models.convert_row_keys_to_alpha(row: CellRow) -> CellRow` を新設 + - `CellRow` の `c` / `links` キーを一括変換した新しい `CellRow` を返す +- `exstruct.models.convert_sheet_keys_to_alpha(sheet: SheetData) -> SheetData` を新設 + - `SheetData.rows` 内の全 `CellRow` を変換した新しい `SheetData` を返す -- 入力: `xlsx_path` -- 出力: `is_readable`, `warnings`, `errors` +### 13.5 変換タイミング -### サーバー設計 +- `extract_workbook()` でデータ抽出が完了した **後**、シリアライズの **前** に変換する +- パイプライン上の位置: `ExStructEngine.extract()` の返却直前 -- stdio 優先 -- ログは stderr / ファイル(stdio を汚さない) -- `--root` によりアクセス範囲を固定 -- `--deny-glob` により防御的に除外 -- `--on-conflict` で出力衝突方針を指定(overwrite / skip / rename) +### 13.6 出力例 -### ディレクトリ構成(案) +#### `alpha_col=False`(デフォルト: 従来通り) +```json +{ "r": 6, "c": { "0": "氏 名", "12": "生年月日", "18": "年" } } ``` -src/exstruct/ - mcp/ - __init__.py - server.py # MCP server entrypoint (stdio) - tools.py # tool definitions + handlers - io.py # path validation, safe read/write - extract_runner.py # internal API call or subprocess fallback - chunk_reader.py # JSON partial read / pointer / sheet filters + +#### `alpha_col=True` + +```json +{ "r": 6, "c": { "A": "氏 名", "M": "生年月日", "S": "年" } } ``` +### 13.7 後方互換性 + +- デフォルト `False` のため、既存の出力は一切変更されない +- `alpha_col=True` は opt-in のみ +- `_safe_col_index()` 等の下流処理は数値キーを前提としているが、変換はシリアライズ直前のため影響なし + +### 13.8 受け入れ基準 + +- `col_index_to_alpha(0)` → `"A"`, `col_index_to_alpha(25)` → `"Z"`, `col_index_to_alpha(26)` → `"AA"` +- `alpha_col=True` で抽出した `CellRow.c` のキーがすべて A-Z 形式 +- `alpha_col=True` で `CellRow.links` のキーも同様に変換される +- `alpha_col=False`(デフォルト)で従来通り数値キー +- CLI `--alpha-col` フラグが正しく `process_excel()` に伝播する +- MCP `exstruct_extract` の `options.alpha_col` がエンジンに伝播する + +### 13.9 `merged_ranges` 出力(`alpha_col` 時) + +#### 背景 + +`alpha_col=True` 時に `CellRow.c` / `links` は ABC 列名になるため、`merged_cells.items` +(`(r1, c1, r2, c2, v)` の数値列インデックス)との参照体系が混在する。 +AIエージェントの編集座標推論の一貫性を高めるため、A1形式の `merged_ranges` を別フィールドで出力する。 + +#### 仕様 + +- `SheetData` に `merged_ranges: list[str]` を追加する(例: `["A1:C3", "F10:G12"]`) +- `alpha_col=True` のときのみ `merged_ranges` を生成する +- `alpha_col=True` では `merged_cells` は出力しない(可読性優先) +- `alpha_col=False` のときは従来どおり `merged_cells` を出力し、`merged_ranges` は空とする + +#### 受け入れ基準(追加) + +- `alpha_col=True` かつ `merged_cells` が存在するシートで `merged_ranges` が出力される +- `merged_ranges` の各要素は `"A1:C3"` 形式で、`items` と同じ範囲を表す +- `alpha_col=True` では `merged_cells` は出力されない +- `alpha_col=False` では `merged_ranges` は空(実質非出力)となる + --- -## 今後のオプション検討メモ +## 14. `exstruct_patch.ops` 入力スキーマ整合(AIエージェント運用改善) + +### 14.1 背景 + +一部のMCPクライアントは、`ops` を `PatchOp` オブジェクト配列ではなく、 +JSON文字列配列として送信する場合がある。 +この差異により、AIエージェントがツール定義どおりに呼び出しても +入力バリデーションで失敗するケースがある。 + +### 14.2 目的 + +- `exstruct_patch` の受け口を明確化し、実装とツール入力スキーマの体験差を解消する +- AIエージェントがクライアント差異を意識せず patch を実行できるようにする -- 表検知スコアリングの閾値を CLI/環境変数で調整可能にする -- 出力モード(light/standard/verbose)に応じてテーブル候補数を制限するオプション +### 14.3 入力仕様 + +- 正式入力: `ops: list[object]`(各要素は `PatchOp` 相当のJSONオブジェクト) +- 互換入力: `ops: list[str]`(各要素は `PatchOp` オブジェクトを表すJSON文字列) +- サーバー内部で次を行う: + - object はそのまま採用 + - string は `json.loads()` で object に変換 + - 変換後に `PatchToolInput`(`ops: list[PatchOp]`)で厳密検証 + +### 14.4 エラー仕様 + +- JSON文字列のパース失敗時: + - `ValueError` + - メッセージに失敗した `ops[index]` を含める + - 「object 形式の推奨入力例」を併記する +- JSONとして有効でもobjectでない場合(配列/数値など): + - `ValueError` + - 「JSON object 必須」を明記する + +### 14.5 後方互換性 + +- 既存の object 配列クライアントは変更不要 +- string 配列クライアントはサーバー側で吸収し、破壊的変更を回避する +- `PatchOp` 自体のスキーマと patch 実行ロジックは変更しない + +### 14.6 受け入れ基準 + +- object 配列入力で従来どおり patch が実行できる +- JSON文字列配列入力でも patch が実行できる +- 不正JSON文字列で `ops[index]` を含む明確なエラーが返る +- object 以外のJSON型入力で「object必須」エラーが返る --- -## 実装方針 +## 15. MCP モード/チャンク指定ガイド改善 + +### 15.1 背景 + +MCP運用時、次の迷いが発生しやすい。 + +- `exstruct_extract.mode` に無効値を入れて失敗する +- `exstruct_read_json_chunk` で `sheet` 未指定のまま再試行して失敗する +- `max_bytes` の調整方針が不明でリトライ回数が増える +- `alpha_col=true` 出力時に `filter.cols` が使いにくい + +### 15.2 目的 + +- モード指定とチャンク指定の「最短成功パターン」を文書化する +- エラー時に次の行動が分かるメッセージへ改善する +- `alpha_col=true` でも列フィルタを直感的に使えるようにする + +### 15.3 スコープ + +- `docs/mcp.md` の実運用ガイド追加 +- `src/exstruct/mcp/server.py` のツールDocstring詳細化 +- `src/exstruct/mcp/chunk_reader.py` のエラーメッセージ改善 +- `src/exstruct/mcp/chunk_reader.py` の `filter.cols` を英字列キー対応 +- 関連テスト追加 + +### 15.4 仕様 + +#### 15.4.1 ドキュメント強化(`docs/mcp.md`) + +- `validate_input -> extract(standard) -> read_json_chunk` の推奨3ステップを追加 +- `mode` の許容値(`light`/`standard`/`verbose`)と用途を表形式で明記 +- `read_json_chunk` の主要引数(`sheet`, `max_bytes`, `filter`, `cursor`)を具体化 +- エラー/警告ごとの再試行手順を表形式で明記 + +#### 15.4.2 ツール説明の詳細化(`server.py`) + +- `exstruct_extract` Docstringに `mode` の意味と許容値を明記 +- `exstruct_read_json_chunk` Docstringに `filter.rows/cols` の + 1-based inclusive 仕様と `max_bytes` 調整指針を明記 + +#### 15.4.3 エラーメッセージ改善(`chunk_reader.py`) + +- 大きすぎる生JSONに対するエラー文言を、`sheet`/`filter` 指定例付きへ変更 +- 複数シート時の `sheet` 必須エラーに、利用可能シート名と再実行ヒントを追加 + +#### 15.4.4 `filter.cols` の英字列キー対応(`chunk_reader.py`) + +- 列キーが数値文字列(`"0"`)に加え英字列名(`"A"`, `"AA"`)でも判定可能にする +- `filter.cols` は従来どおり 1-based inclusive の列番号指定を維持する +- 英字列名は大文字小文字を区別しない + +### 15.5 後方互換性 + +- 既存の `filter.cols` 数値キー挙動は維持する +- `read_json_chunk` の入出力スキーマは変更しない +- 変更はガイド追加・メッセージ改善・列キー判定拡張に限定する + +### 15.6 受け入れ基準 + +- `docs/mcp.md` だけで、初回利用者が mode/chunk 再試行を完了できる +- `Output is too large` エラーに次アクションが含まれる +- `Sheet is required when multiple sheets exist` エラーにシート候補が含まれる +- `filter.cols` が数値キー・英字キー双方で機能する +- 既存テストと追加テストが通過する + +--- + +## 16. 読み取りツール拡張(`read_range` / `read_cells` / `read_formulas`) + +### 16.1 背景 + +`exstruct_read_json_chunk` は汎用性が高い一方、確認作業では以下が冗長になりやすい。 + +- 範囲取得に `sheet` / `filter` / `cursor` の組み合わせが必要 +- 単発セル確認でもチャンク前提の手順が必要 +- 数式点検で `formulas_map` から座標を手作業で読む必要がある + +### 16.2 目的 + +- `A1:G10` のような直感的指定で値確認できるようにする +- 指定セル群のピンポイント確認を1コールで完結させる +- 数式点検を専用ツールで高速化する + +### 16.3 スコープ + +- 新規MCPツールを3つ追加する + - `exstruct_read_range` + - `exstruct_read_cells` + - `exstruct_read_formulas` +- 既存の `exstruct_extract` / `exstruct_read_json_chunk` は後方互換を維持し、変更しない + +### 16.4 共通方針 + +- 入力元は `exstruct_extract` で生成された `out_path`(JSON)とする +- シート選択ルールは `read_json_chunk` と同一 + - `sheet` 指定あり: そのシートを使用 + - `sheet` 省略かつ単一シート: 自動選択 + - `sheet` 省略かつ複数シート: エラー +- 参照座標はA1形式で統一する +- `alpha_col=true` 出力(英字キー)と数値キー出力の両方を透過的に扱う + +### 16.5 ツールI/F + +#### 16.5.1 `exstruct_read_range` + +Input: + +- `out_path: str` +- `sheet: str | None = None` +- `range: str`(A1範囲。例: `A1:G10`) +- `include_formulas: bool = false` +- `include_empty: bool = true` +- `max_cells: int = 10000` + +Output: + +- `book_name: str | None` +- `sheet_name: str` +- `range: str` +- `cells: list[CellReadItem]` +- `warnings: list[str]` + +`CellReadItem`: + +- `cell: str`(A1) +- `value: str | int | float | bool | None` +- `formula: str | None`(`include_formulas=true` の場合のみ設定) + +#### 16.5.2 `exstruct_read_cells` + +Input: + +- `out_path: str` +- `sheet: str | None = None` +- `addresses: list[str]`(A1セル番地。例: `["J98","J124"]`) +- `include_formulas: bool = true` + +Output: + +- `book_name: str | None` +- `sheet_name: str` +- `cells: list[CellReadItem]`(入力順を維持) +- `missing_cells: list[str]`(シート範囲外など) +- `warnings: list[str]` + +#### 16.5.3 `exstruct_read_formulas` + +Input: + +- `out_path: str` +- `sheet: str | None = None` +- `range: str | None = None`(未指定時はシート全体) +- `include_values: bool = false` + +Output: + +- `book_name: str | None` +- `sheet_name: str` +- `range: str | None` +- `formulas: list[FormulaReadItem]` +- `warnings: list[str]` + +`FormulaReadItem`: + +- `cell: str`(A1) +- `formula: str` +- `value: str | int | float | bool | None`(`include_values=true` の場合のみ設定) + +### 16.6 バリデーション規則 + +- `out_path` は存在ファイルかつ許可パス内であること +- `range` はA1範囲形式であること(`A1:G10`) +- `addresses` は空配列禁止、各要素がA1セル形式であること +- `max_cells` は正数で、展開セル数が上限を超えた場合は `ValueError` +- `exstruct_read_formulas` で `formulas_map` が無い場合: + - エラーではなく空結果 + `warnings` で `mode=verbose` 推奨を返す + +### 16.7 実行セマンティクス + +- 1回の呼び出しでJSONを1回だけ読み込み・パースする +- セル値参照は `sheet.rows[].c` から取得する +- 数式参照は `sheet.formulas_map` を主データソースとする +- 返却順: + - `read_range`: 行優先(top-left -> bottom-right) + - `read_cells`: 入力順 + - `read_formulas`: セル番地昇順(row, col) + +### 16.8 エラー処理 + +- A1形式不正: `ValueError` +- シート不在: `ValueError` +- 複数シートで `sheet` 未指定: `ValueError`(候補名を含める) +- JSON構造不正: `ValueError` + +### 16.9 後方互換性 + +- 既存ツールの入出力スキーマは変更しない +- 新規ツール追加のみ(破壊的変更なし) +- `read_json_chunk` の既存運用はそのまま利用可能 + +### 16.10 受け入れ基準 -- 小さなステップごとにテスト追加、または既存フィクスチャで手動確認 -- 短い関数・責務分割でスコアリング調整をしやすくする -- 外部公開前なので、破壊的変更はコメントや仕様に明示して段階的に移行する +- `exstruct_read_range(range="A1:G10")` で70セルを取得できる +- `exstruct_read_cells(addresses=["J98","J124"])` で2セルを入力順に取得できる +- `exstruct_read_formulas(range="J2:J201")` で対象範囲の式一覧を取得できる +- `alpha_col=true/false` どちらのJSONでも同じ結果を返せる +- 不正A1指定で明確なエラーが返る +- `uv run task precommit-run` が通過する diff --git a/docs/agents/RAG_INTEGRATION.md b/docs/agents/RAG_INTEGRATION.md deleted file mode 100644 index a442a29..0000000 --- a/docs/agents/RAG_INTEGRATION.md +++ /dev/null @@ -1,34 +0,0 @@ -# RAG Integration — How to Use ExStruct in AI Pipelines - -ExStruct の想定用途は RAG(Retrieval Augmented Generation)です。 - -## 推奨フロー - -```txt -Excel → ExStruct(JSON) → Chunking → VectorDB → LLM -``` - -## JSON そのまま格納(構造優先) - -- ChartType / AutoShapeType / Coordinates が強い検索キー -- テーブル検索が精確 -- Lookup 型 RAG に最適 - -## Markdown 化して格納(生成品質優先) - -- LLM が読みやすい -- 回答が自然になる - -## ハイブリッド設計(最も推奨) - -```txt -VectorDB #1: Structural JSON -VectorDB #2: Markdown Summary -``` - -RAG 実行時: - -- #1 で構造マッチ -- #2 で自然言語マッチ - -両方合わせて LLM に送る。 diff --git a/docs/agents/TASKS.md b/docs/agents/TASKS.md index 497ec31..504b130 100644 --- a/docs/agents/TASKS.md +++ b/docs/agents/TASKS.md @@ -2,30 +2,112 @@ 未完了 [ ], 完了 [x] -## MCPサーバー(MVP) - -- [x] 仕様反映: `docs/agents/FEATURE_SPEC.md` を更新 -- [x] 依存追加: `pyproject.toml` に `exstruct[mcp]` の extras を追加 -- [x] エントリポイント: `exstruct-mcp = exstruct.mcp.server:main` を定義 -- [x] MCP 基盤: `src/exstruct/mcp/server.py` を追加(stdio サーバー起動) -- [x] ツール定義: `src/exstruct/mcp/tools.py` に `exstruct_extract` を実装 -- [x] パス制約: `src/exstruct/mcp/io.py` に allowlist / deny glob を実装 -- [x] 抽出実行: `src/exstruct/mcp/extract_runner.py` に内部 API 優先の実行層を実装 -- [x] 出力モデル: Pydantic で入出力モデルを定義(mypy strict / Ruff 遵守) -- [x] ログ: stderr / ファイル出力の設定を追加 -- [x] ドキュメント: README または docs に起動例(`exstruct-mcp --root ...`)を追記 - -## MCPサーバー(実用化) - -- [x] `exstruct_read_json_chunk` を追加(大容量 JSON 対応) -- [x] `exstruct_validate_input` を追加(事前検証) -- [x] `--on-conflict` の出力衝突ポリシー実装 -- [x] Windows/非Windows の読み取り差分を明文化 -- [x] 最低限のテスト追加(パス制約 / 入出力モデル / 例外) - -## PR #47 レビュー対応 - -- [x] cells.py の列幅縮小ヒューリスティックを再検討(遅い行に境界があるケースで早期縮小しない方針に修正) -- [x] 上記修正に対応するテストを追加(遅い行・右端に表があるケースを openpyxl で検証) -- [x] Codecov 指摘の不足分を埋めるテスト追加(mcp: chunk_reader/extract_runner/server/tools/validate_input/io、core/cells) -- [x] CodeRabbit: Docstring coverage 80% を満たすよう不足分の docstring を追加 +- [x] `FEATURE_SPEC.md` に exstruct_patch の詳細仕様(I/F、検証、差分、エラー)を記載 +- [x] `mcp/patch_runner.py` を新設し、`PatchRequest/Result` を定義 +- [x] `mcp/tools.py` に `PatchToolInput/Output` と `run_patch_tool` を追加 +- [x] `mcp/server.py` に `exstruct_patch` 登録を追加 +- [x] パス制御・競合ポリシー・保存ロジックを実装 +- [x] openpyxlベースの適用処理を実装 +- [x] COM利用時の分岐(可能なら)とwarning出力 +- [x] ユニットテスト(非COM)を追加 + +## レビュー対応タスク(MVP修正) + +- [x] `mcp/server.py`: `exstruct_patch` の `on_conflict` 解決で固定 `rename` を廃止し、サーバー既定値(`default_on_conflict`)を利用する +- [x] `mcp/patch_runner.py`: 保存前に出力先ディレクトリ作成処理を追加する(未作成 `out_dir` 対応) +- [x] `mcp/patch_runner.py`: `.xls` + COM不可時のエラーメッセージを「COM必須」が明確な文言に調整する +- [x] `docs/agents/FEATURE_SPEC.md`: 拡張子要件を「`.xls` はCOM利用可能環境のみ」に更新する(本文整合) +- [x] `tests/mcp/test_server.py`: patch ツールの `on_conflict` 既定値伝播を検証するテストを追加/更新する +- [x] `tests/mcp/test_patch_runner.py`: 未作成 `out_dir` 保存成功ケースを追加する +- [x] `tests/mcp/test_patch_runner.py`: `.xls` + COM不可エラーの期待値(型・文言)を追加する +- [x] `ruff check .` / `mypy src/exstruct --strict` / 関連pytest を実行し、結果を記録する + +## 次フェーズタスク(便利機能 1〜5) + +- [x] `mcp/patch_runner.py`: `PatchRequest` に `dry_run` / `return_inverse_ops` / `preflight_formula_check` を追加する +- [x] `mcp/patch_runner.py`: `PatchResult` に `inverse_ops` / `formula_issues` を追加する +- [x] `mcp/patch_runner.py`: `PatchOp` に `set_range_values` / `fill_formula` / `set_value_if` / `set_formula_if` を追加する +- [x] `mcp/patch_runner.py`: A1範囲パース・範囲サイズ検証・値行列検証ユーティリティを実装する +- [x] `mcp/patch_runner.py`: `set_range_values` の openpyxl 実装を追加する +- [x] `mcp/patch_runner.py`: `fill_formula` の相対参照展開ロジックを実装する(単一行/列制約) +- [x] `mcp/patch_runner.py`: `set_value_if` / `set_formula_if` の比較・`skipped` 差分出力を実装する +- [x] `mcp/patch_runner.py`: 逆パッチ生成ロジック(`applied` のみ、逆順)を実装する +- [x] `mcp/patch_runner.py`: `dry_run=true` で非保存実行に分岐する +- [x] `mcp/patch_runner.py`: 数式ヘルスチェック(`#REF!` 等)を実装し、`formula_issues` に反映する +- [x] `mcp/patch_runner.py`: `preflight_formula_check=true` かつ `level=error` の保存抑止ルールを実装する +- [x] `mcp/tools.py`: `PatchToolInput/Output` の新規フィールドをツールI/Fへ公開する +- [x] `mcp/server.py`: `exstruct_patch` ツール引数に新規3フラグを追加し、ハンドラへ連携する +- [x] `docs/agents/FEATURE_SPEC.md`: 実装差分(命名、エラーコード、制約)を最終反映する +- [x] `tests/mcp/test_patch_runner.py`: `dry_run` 非保存・逆パッチ・範囲操作・条件付き更新・数式検査の単体テストを追加する +- [x] `tests/mcp/test_tools_handlers.py`: 入出力モデル拡張の受け渡しテストを追加する +- [x] `tests/mcp/test_server.py`: 新規ツール引数の受け渡しテストを追加する +- [x] `ruff check .` / `mypy src/exstruct --strict` / 関連pytest を実行し、結果を記録する + +## ABC列名キー出力オプション(`alpha_col`) + +- [x] `models/__init__.py`: `col_index_to_alpha()` 変換ユーティリティを追加する +- [x] `models/__init__.py`: `convert_row_keys_to_alpha()` / `convert_sheet_keys_to_alpha()` を追加する +- [x] `engine.py`: `StructOptions` に `alpha_col: bool = False` を追加する +- [x] `engine.py`: `ExStructEngine.extract()` で `alpha_col=True` 時に変換を適用する +- [x] `__init__.py`: `extract()` / `process_excel()` に `alpha_col` パラメータを追加する +- [x] `cli/main.py`: `--alpha-col` CLI フラグを追加する +- [x] `mcp/extract_runner.py`: `ExtractOptions` に `alpha_col` を追加し、`process_excel()` へ連携する +- [x] `mcp/server.py`: `exstruct_extract` ツールに `alpha_col` パラメータを追加する +- [x] `tests/models/`: `col_index_to_alpha()` の単体テストを追加する +- [x] `tests/models/`: `convert_row_keys_to_alpha()` / `convert_sheet_keys_to_alpha()` のテストを追加する +- [x] `tests/engine/`: `alpha_col=True` でのエンジン抽出テストを追加する +- [x] `tests/cli/`: `--alpha-col` CLI フラグのテストを追加する +- [x] `tests/mcp/`: MCP extract の `alpha_col` 連携テストを追加する +- [x] `ruff check .` / `mypy src/exstruct --strict` / 関連pytest を実行し、結果を記録する + +## `merged_ranges` 出力(`alpha_col`時) + +- [x] `docs/agents/FEATURE_SPEC.md`: `merged_ranges` 仕様に更新する +- [x] `models/__init__.py`: `SheetData` に `merged_ranges` フィールドを追加する +- [x] `models/__init__.py`: `alpha_col=True` 変換時に `merged_cells.items` から `merged_ranges` を生成する +- [x] `models/__init__.py`: `alpha_col=True` 変換時に `merged_cells` を非表示化する +- [x] `tests/models/test_alpha_col.py`: `merged_ranges` 生成と非生成(alpha_col=False相当)を検証する +- [x] `tests/engine/test_engine_alpha_col.py`: エンジン経由で `merged_ranges` が出力されることを検証する +- [x] `uv run task precommit-run` を実行し、静的解析・型検査の通過を確認する + +## `exstruct_patch.ops` 入力スキーマ整合(AIエージェント運用改善) + +- [x] `docs/agents/FEATURE_SPEC.md`: `ops` の正式入力(object配列)と互換入力(JSON文字列配列)を定義する +- [x] `mcp/server.py`: `ops` 正規化ヘルパー(object/string両対応)を追加する +- [x] `mcp/server.py`: `_patch_tool` で正規化済み `ops` を `PatchToolInput` に渡す +- [x] `mcp/server.py`: 不正JSON時に `ops[index]` と入力例を含むエラー文言を返す +- [x] `tests/mcp/test_server.py`: `ops` 正規化ヘルパーの単体テスト(成功/失敗)を追加する +- [x] `tests/mcp/test_server.py`: `exstruct_patch` が JSON文字列配列 `ops` を受理できることを検証する +- [x] `tests/mcp/test_server.py`: 不正JSON文字列 `ops` が明確な `ValueError` になることを検証する +- [x] `ruff check src/exstruct/mcp/server.py tests/mcp/test_server.py` を実行し、静的解析を確認する +- [x] `pytest tests/mcp/test_server.py` を実行し、回帰がないことを確認する + +## MCP モード/チャンク指定ガイド改善 + +- [x] `docs/agents/FEATURE_SPEC.md`: mode/chunk ガイド改善仕様(目的/スコープ/受け入れ基準)を定義する +- [x] `docs/mcp.md`: 推奨3ステップ(validate/extract/chunk)と mode 比較表を追加する +- [x] `docs/mcp.md`: chunk パラメータ説明、エラー別リトライ表、cursor利用手順を追加する +- [x] `src/exstruct/mcp/server.py`: `exstruct_extract` Docstring に mode の許容値と意図を追記する +- [x] `src/exstruct/mcp/server.py`: `exstruct_read_json_chunk` Docstring に `filter.rows/cols` と `max_bytes` 指針を追記する +- [x] `src/exstruct/mcp/chunk_reader.py`: 大容量時エラー文言に再実行ヒントを追加する +- [x] `src/exstruct/mcp/chunk_reader.py`: 複数シート時エラー文言に利用可能シート名を追加する +- [x] `src/exstruct/mcp/chunk_reader.py`: `filter.cols` を英字列キー(`A`, `AA`)対応に拡張する +- [x] `tests/mcp/test_chunk_reader.py`: 英字列キー対応と新エラーメッセージのテストを追加する +- [x] `uv run task precommit-run` を実行し、静的解析・型検査・テスト通過を確認する + +## 読み取りツール拡張(`read_range` / `read_cells` / `read_formulas`) + +- [x] `docs/agents/FEATURE_SPEC.md`: 新規3ツールのI/F・バリデーション・受け入れ基準を定義する +- [x] `src/exstruct/mcp/sheet_reader.py`: A1セル/範囲パーサーと共通読み取りロジックを実装する +- [x] `src/exstruct/mcp/sheet_reader.py`: `read_range`(A1範囲指定)を実装する +- [x] `src/exstruct/mcp/sheet_reader.py`: `read_cells`(複数セル指定)を実装する +- [x] `src/exstruct/mcp/sheet_reader.py`: `read_formulas`(数式一覧+任意で値)を実装する +- [x] `src/exstruct/mcp/tools.py`: `ReadRangeToolInput/Output`、`ReadCellsToolInput/Output`、`ReadFormulasToolInput/Output` を追加する +- [x] `src/exstruct/mcp/tools.py`: `run_read_range_tool`、`run_read_cells_tool`、`run_read_formulas_tool` を追加する +- [x] `src/exstruct/mcp/server.py`: `exstruct_read_range`、`exstruct_read_cells`、`exstruct_read_formulas` を登録する +- [x] `src/exstruct/mcp/server.py`: Docstringに利用例(`A1:G10`、`[\"J98\",\"J124\"]`)を追加する +- [x] `tests/mcp/test_sheet_reader.py`: A1パース、範囲上限、alpha_col互換、エラー系テストを追加する +- [x] `tests/mcp/test_tools_handlers.py`: 新規3ツールの入出力ハンドリングテストを追加する +- [x] `tests/mcp/test_server.py`: 新規3ツールの登録・引数連携テストを追加する +- [x] `docs/mcp.md`: 新規3ツールの使用手順と推奨フロー(extract→read_*)を追記する +- [x] `uv run task precommit-run` を実行し、静的解析・型検査・テスト通過を確認する diff --git a/docs/mcp.md b/docs/mcp.md index 7bd23ab..45070e4 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -6,7 +6,9 @@ so AI agents can call it safely as a tool. ## What it provides - Convert Excel into structured JSON (file output) +- Edit Excel by applying patch operations (cell/sheet updates) - Read large JSON outputs in chunks +- Read A1 ranges / specific cells / formulas directly from extracted JSON - Pre-validate input files ## Installation @@ -33,14 +35,147 @@ exstruct-mcp --root C:\\data --log-file C:\\logs\\exstruct-mcp.log --on-conflict ## Tools - `exstruct_extract` +- `exstruct_patch` - `exstruct_read_json_chunk` +- `exstruct_read_range` +- `exstruct_read_cells` +- `exstruct_read_formulas` - `exstruct_validate_input` +### `exstruct_extract` defaults and mode guide + +- `options.alpha_col` defaults to `true` in MCP (column keys become `A`, `B`, ...). +- Set `options.alpha_col=false` if you need legacy 0-based numeric string keys. +- `mode` is an extraction detail level (not sheet scope): + +| Mode | When to use | Main output characteristics | +|---|---|---| +| `light` | Fast, structure-first extraction | cells + table candidates + print areas | +| `standard` | Default for most agent flows | balanced detail and size | +| `verbose` | Need the richest metadata | adds links/maps and richer metadata | + +## Quick start for agents (recommended) + +1. Validate file readability with `exstruct_validate_input` +2. Run `exstruct_extract` with `mode="standard"` +3. Read the result with `exstruct_read_json_chunk` using `sheet` and `max_bytes` + +Example sequence: + +```json +{ "tool": "exstruct_validate_input", "xlsx_path": "C:\\data\\book.xlsx" } +``` + +```json +{ "tool": "exstruct_extract", "xlsx_path": "C:\\data\\book.xlsx", "mode": "standard", "format": "json" } +``` + +```json +{ "tool": "exstruct_read_json_chunk", "out_path": "C:\\data\\book.json", "sheet": "Sheet1", "max_bytes": 50000 } +``` + ## Basic flow 1. Call `exstruct_extract` to generate the output JSON file 2. Use `exstruct_read_json_chunk` to read only the parts you need +## Direct read tools (A1-oriented) + +Use these tools when you already know the target addresses and want faster, +less verbose reads than chunk traversal. + +- `exstruct_read_range` + - Read a rectangular A1 range (example: `A1:G10`) + - Optional: `include_formulas`, `include_empty`, `max_cells` +- `exstruct_read_cells` + - Read specific cells in one call (example: `["J98", "J124"]`) + - Optional: `include_formulas` +- `exstruct_read_formulas` + - Read formulas only (optionally restricted by A1 range) + - Optional: `include_values` + +Examples: + +```json +{ + "tool": "exstruct_read_range", + "out_path": "C:\\data\\book.json", + "sheet": "Data", + "range": "A1:G10" +} +``` + +```json +{ + "tool": "exstruct_read_cells", + "out_path": "C:\\data\\book.json", + "sheet": "Data", + "addresses": ["J98", "J124"] +} +``` + +```json +{ + "tool": "exstruct_read_formulas", + "out_path": "C:\\data\\book.json", + "sheet": "Data", + "range": "J2:J201", + "include_values": true +} +``` + +## Chunking guide + +### Key parameters + +- `sheet`: target sheet name. Strongly recommended when workbook has multiple sheets. +- `max_bytes`: chunk size budget in bytes. Start at `50_000`; increase (for example `120_000`) if chunks are too small. +- `filter.rows`: `[start, end]` (1-based, inclusive). +- `filter.cols`: `[start, end]` (1-based, inclusive). Works for both numeric keys (`"0"`, `"1"`) and alpha keys (`"A"`, `"B"`). +- `cursor`: pagination cursor (`next_cursor` from the previous response). + +### Retry guide by error/warning + +| Message | Meaning | Next action | +|---|---|---| +| `Output is too large...` | Whole JSON cannot fit in one response | Retry with `sheet`, or narrow with `filter.rows`/`filter.cols` | +| `Sheet is required when multiple sheets exist...` | Workbook has multiple sheets and target is ambiguous | Pick one value from `workbook_meta.sheet_names` and set `sheet` | +| `Base payload exceeds max_bytes...` | Even metadata-only payload is larger than `max_bytes` | Increase `max_bytes` | +| `max_bytes too small...` | Row payload is too large for the current size | Increase `max_bytes`, or narrow row/col filters | + +### Cursor example + +1. Call without `cursor` +2. If response has `next_cursor`, call again with that cursor +3. Repeat until `next_cursor` is `null` + +## Edit flow (patch) + +1. Inspect workbook structure with `exstruct_extract` (and `exstruct_read_json_chunk` if needed) +2. Build patch operations (`ops`) for target cells/sheets +3. Call `exstruct_patch` to apply edits +4. Re-run `exstruct_extract` to verify results if needed + +### `exstruct_patch` highlights + +- Atomic apply: all operations succeed, or no changes are saved +- `ops` accepts an object list as the canonical form. + For compatibility, JSON object strings in `ops` are also accepted and normalized. +- Supports: + - `set_value` + - `set_formula` + - `add_sheet` + - `set_range_values` + - `fill_formula` + - `set_value_if` + - `set_formula_if` +- Useful flags: + - `dry_run`: compute diff only (no file write) + - `return_inverse_ops`: return undo operations + - `preflight_formula_check`: detect formula issues before save + - `auto_formula`: treat `=...` in `set_value` as formula +- Conflict handling follows server `--on-conflict` unless overridden per tool call + ## AI agent configuration examples ### Codex diff --git a/docs/release-notes/v0.4.4.md b/docs/release-notes/v0.4.4.md new file mode 100644 index 0000000..7969efd --- /dev/null +++ b/docs/release-notes/v0.4.4.md @@ -0,0 +1,34 @@ +# v0.4.4 Release Notes + +This release delivers an MVP for Excel editing on the MCP server, plus new +direct-read tools to make agent workflows faster and more precise. + +## Highlights + +- Added Excel edit MVP via `exstruct_patch` with atomic apply semantics: + - Core operations: `set_value`, `set_formula`, `add_sheet` + - Extended operations: `set_range_values`, `fill_formula`, + `set_value_if`, `set_formula_if` + - Safety and review options: `dry_run`, `return_inverse_ops`, + `preflight_formula_check`, `auto_formula` + - Better output handling: output directory creation, structured errors, and + default conflict policy set to `overwrite` + - Compatibility improvement: `ops` now accepts both object lists + (recommended) and JSON object strings +- Added direct A1-oriented read tools for extracted JSON: + - `exstruct_read_range` + - `exstruct_read_cells` + - `exstruct_read_formulas` +- Improved MCP extraction/read consistency: + - Added `alpha_col` support (Excel-style column keys like `A`, `B`, ...) + across CLI/MCP extraction paths + - Added `merged_ranges` output for `alpha_col` mode + - Clarified MCP mode/chunk guidance and improved chunk-reader error messages +- Expanded MCP documentation, tool schemas, and tests for patch/read flows. + +## Notes + +- In MCP, `options.alpha_col` now defaults to `true`. Set + `options.alpha_col=false` if you need legacy numeric string column keys. +- `exstruct_patch` follows server-level `--on-conflict` unless overridden in + the tool call. diff --git a/pyproject.toml b/pyproject.toml index 128e8ed..d09640e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "exstruct" -version = "0.4.2" +version = "0.4.4" description = "Excel to structured JSON (tables, shapes, charts) for LLM/RAG pipelines" readme = "README.md" license = { file = "LICENSE" } diff --git a/src/exstruct/__init__.py b/src/exstruct/__init__.py index 6bdfd75..844b2e2 100644 --- a/src/exstruct/__init__.py +++ b/src/exstruct/__init__.py @@ -41,6 +41,10 @@ Shape, SheetData, WorkbookData, + col_index_to_alpha, + convert_row_keys_to_alpha, + convert_sheet_keys_to_alpha, + convert_workbook_keys_to_alpha, ) from .render import export_pdf, export_sheet_images @@ -82,19 +86,26 @@ "ColorsOptions", "serialize_workbook", "export_auto_page_breaks", + "col_index_to_alpha", + "convert_row_keys_to_alpha", + "convert_sheet_keys_to_alpha", + "convert_workbook_keys_to_alpha", ] ExtractionMode = Literal["light", "standard", "verbose"] -def extract(file_path: str | Path, mode: ExtractionMode = "standard") -> WorkbookData: +def extract( + file_path: str | Path, mode: ExtractionMode = "standard", *, alpha_col: bool = False +) -> WorkbookData: """ Extracts an Excel workbook into a WorkbookData structure. Parameters: file_path (str | Path): Path to the workbook file (.xlsx, .xlsm, .xls). mode (ExtractionMode): Extraction detail level. "light" includes cells and table detection only (no COM, shapes/charts empty; print areas via openpyxl). "standard" includes texted shapes, arrows, charts (COM if available) and print areas. "verbose" also includes shape/chart sizes, cell link map, colors map, and formulas map. + alpha_col: When True, convert CellRow column keys to Excel-style ABC names (A, B, ..., Z, AA, ...) instead of 0-based numeric strings. Returns: WorkbookData: Parsed workbook representation containing sheets, rows, shapes, charts, and print areas. @@ -108,6 +119,7 @@ def extract(file_path: str | Path, mode: ExtractionMode = "standard") -> Workboo include_cell_links=include_links, include_colors_map=include_colors_map, include_formulas_map=include_formulas_map, + alpha_col=alpha_col, ) ) return engine.extract(file_path, mode=mode) @@ -317,6 +329,8 @@ def process_excel( print_areas_dir: str | Path | None = None, auto_page_breaks_dir: str | Path | None = None, stream: TextIO | None = None, + *, + alpha_col: bool = False, ) -> None: """ Convenience wrapper: extract -> serialize (file or stdout) -> optional PDF/PNG. @@ -335,6 +349,8 @@ def process_excel( print_areas_dir: Directory to write per-print-area files (string or Path). auto_page_breaks_dir: Directory to write per-auto-page-break files (COM only). stream: IO override when output_path is None. + alpha_col: When True, convert CellRow column keys to Excel-style + ABC names (A, B, ...) instead of 0-based numeric strings. Raises: ValueError: If an unsupported format or mode is given. @@ -353,7 +369,7 @@ def process_excel( >>> process_excel(Path("input.xlsx"), output_path=Path("out.json"), pdf=True) # doctest: +SKIP """ engine = ExStructEngine( - options=StructOptions(mode=mode), + options=StructOptions(mode=mode, alpha_col=alpha_col), output=OutputOptions( format=FormatOptions(fmt=out_fmt, pretty=pretty, indent=indent), filters=FilterOptions( diff --git a/src/exstruct/cli/main.py b/src/exstruct/cli/main.py index 4a69923..e53d83f 100644 --- a/src/exstruct/cli/main.py +++ b/src/exstruct/cli/main.py @@ -109,6 +109,11 @@ def build_parser( availability if availability is not None else get_com_availability() ) _add_auto_page_breaks_argument(parser, resolved_availability) + parser.add_argument( + "--alpha-col", + action="store_true", + help="Output column keys as Excel-style ABC names (A, B, ..., Z, AA, ...) instead of 0-based indices.", + ) return parser @@ -143,6 +148,7 @@ def main(argv: list[str] | None = None) -> int: sheets_dir=args.sheets_dir, print_areas_dir=args.print_areas_dir, auto_page_breaks_dir=getattr(args, "auto_page_breaks_dir", None), + alpha_col=args.alpha_col, ) return 0 except Exception as e: diff --git a/src/exstruct/engine.py b/src/exstruct/engine.py index 7a97ff9..3469c53 100644 --- a/src/exstruct/engine.py +++ b/src/exstruct/engine.py @@ -17,7 +17,7 @@ save_sheets, serialize_workbook, ) -from .models import SheetData, WorkbookData +from .models import SheetData, WorkbookData, convert_workbook_keys_to_alpha from .render import export_pdf, export_sheet_images ExtractionMode = Literal["light", "standard", "verbose"] @@ -76,6 +76,8 @@ class StructOptions: include_merged_cells: Whether to extract merged cell ranges. include_merged_values_in_rows: Whether to keep merged values in rows. colors: Color extraction options. + alpha_col: When True, convert CellRow column keys to Excel-style + ABC names (A, B, ..., Z, AA, ...) instead of 0-based numeric strings. """ mode: ExtractionMode = "standard" @@ -88,6 +90,7 @@ class StructOptions: include_merged_cells: bool | None = None # None -> auto: light=False, others=True include_merged_values_in_rows: bool = True colors: ColorsOptions = field(default_factory=ColorsOptions) + alpha_col: bool = False class FormatOptions(BaseModel): @@ -314,6 +317,9 @@ def _filter_sheet( merged_cells=sheet.merged_cells if self.output.filters.include_merged_cells else None, + merged_ranges=sheet.merged_ranges + if self.output.filters.include_merged_cells + else [], ) def _filter_workbook( @@ -383,7 +389,7 @@ def extract( ) normalized_file_path = self._ensure_path(file_path) with self._table_params_scope(): - return extract_workbook( + workbook = extract_workbook( normalized_file_path, mode=chosen_mode, include_cell_links=self.options.include_cell_links, @@ -396,6 +402,9 @@ def extract( include_merged_cells=self.options.include_merged_cells, include_merged_values_in_rows=self.options.include_merged_values_in_rows, ) + if self.options.alpha_col: + workbook = convert_workbook_keys_to_alpha(workbook) + return workbook def serialize( self, diff --git a/src/exstruct/errors.py b/src/exstruct/errors.py index c291b13..7fba81f 100644 --- a/src/exstruct/errors.py +++ b/src/exstruct/errors.py @@ -2,7 +2,7 @@ from __future__ import annotations -from enum import Enum +from enum import StrEnum class ExstructError(Exception): @@ -37,7 +37,7 @@ class PrintAreaError(ExstructError, ValueError): """Raised when print-area specific processing fails (also a ValueError for compatibility).""" -class FallbackReason(str, Enum): +class FallbackReason(StrEnum): """Reason codes for extraction fallbacks.""" LIGHT_MODE = "light_mode" diff --git a/src/exstruct/mcp/__init__.py b/src/exstruct/mcp/__init__.py index bbdfd8c..4be3bc5 100644 --- a/src/exstruct/mcp/__init__.py +++ b/src/exstruct/mcp/__init__.py @@ -16,15 +16,50 @@ run_extract, ) from .io import PathPolicy +from .patch_runner import ( + FormulaIssue, + PatchDiffItem, + PatchErrorDetail, + PatchOp, + PatchRequest, + PatchResult, + PatchValue, + run_patch, +) +from .sheet_reader import ( + CellReadItem, + FormulaReadItem, + ReadCellsRequest, + ReadCellsResult, + ReadFormulasRequest, + ReadFormulasResult, + ReadRangeRequest, + ReadRangeResult, + read_cells, + read_formulas, + read_range, +) from .tools import ( ExtractToolInput, ExtractToolOutput, + PatchToolInput, + PatchToolOutput, + ReadCellsToolInput, + ReadCellsToolOutput, + ReadFormulasToolInput, + ReadFormulasToolOutput, ReadJsonChunkToolInput, ReadJsonChunkToolOutput, + ReadRangeToolInput, + ReadRangeToolOutput, ValidateInputToolInput, ValidateInputToolOutput, run_extract_tool, + run_patch_tool, + run_read_cells_tool, + run_read_formulas_tool, run_read_json_chunk_tool, + run_read_range_tool, run_validate_input_tool, ) from .validate_input import ( @@ -39,12 +74,35 @@ "ExtractOptions", "ExtractToolInput", "ExtractToolOutput", + "FormulaIssue", + "FormulaReadItem", + "PatchDiffItem", + "PatchErrorDetail", + "PatchOp", + "PatchRequest", + "PatchResult", + "PatchToolInput", + "PatchToolOutput", + "PatchValue", "PathPolicy", + "ReadCellsRequest", + "ReadCellsResult", + "ReadCellsToolInput", + "ReadCellsToolOutput", + "ReadFormulasRequest", + "ReadFormulasResult", + "ReadFormulasToolInput", + "ReadFormulasToolOutput", "ReadJsonChunkFilter", "ReadJsonChunkRequest", "ReadJsonChunkResult", "ReadJsonChunkToolInput", "ReadJsonChunkToolOutput", + "ReadRangeRequest", + "ReadRangeResult", + "ReadRangeToolInput", + "ReadRangeToolOutput", + "CellReadItem", "ValidateInputRequest", "ValidateInputResult", "ValidateInputToolInput", @@ -54,6 +112,14 @@ "validate_input", "run_extract", "run_extract_tool", + "run_patch", + "run_patch_tool", + "read_cells", + "read_formulas", "run_read_json_chunk_tool", + "read_range", + "run_read_cells_tool", + "run_read_formulas_tool", + "run_read_range_tool", "run_validate_input_tool", ] diff --git a/src/exstruct/mcp/chunk_reader.py b/src/exstruct/mcp/chunk_reader.py index 408c9f9..4e81378 100644 --- a/src/exstruct/mcp/chunk_reader.py +++ b/src/exstruct/mcp/chunk_reader.py @@ -145,7 +145,10 @@ def _chunk_raw_text(text: str, max_bytes: int) -> ReadJsonChunkResult: payload_bytes = text.encode("utf-8") if len(payload_bytes) <= max_bytes: return ReadJsonChunkResult(chunk=text, next_cursor=None, warnings=[]) - raise ValueError("Output is too large. Specify sheet or filter to chunk.") + raise ValueError( + "Output is too large. Retry with `sheet` (e.g., sheet='Sheet1') " + "or `filter` (e.g., filter.rows=[1,200]) to chunk the result." + ) def _select_sheet( @@ -173,7 +176,12 @@ def _select_sheet( if len(sheets) == 1: only_name = next(iter(sheets.keys())) return only_name, sheets[only_name] - raise ValueError("Sheet is required when multiple sheets exist.") + names = ", ".join(str(name) for name in sheets.keys()) + raise ValueError( + "Sheet is required when multiple sheets exist. " + f"Available sheets: {names}. " + "Retry with `sheet=''`." + ) def _extract_rows(sheet_data: dict[str, Any]) -> list[dict[str, Any]]: @@ -281,13 +289,42 @@ def _col_in_range(key: str, start: int, end: int) -> bool: Returns: True if the column index is within range. """ - try: - index = int(key) - except (TypeError, ValueError): + index = _parse_col_index(key) + if index is None: return False return start <= index <= end +def _parse_col_index(key: str) -> int | None: + """Parse a column key into a 0-based index. + + Supports both legacy numeric keys ("0", "1", ...) and + alpha keys ("A", "B", ..., "AA", ...) emitted by alpha_col mode. + + Args: + key: Column key string. + + Returns: + 0-based column index, or None when parsing fails. + """ + try: + return int(key) + except (TypeError, ValueError): + pass + + normalized = key.strip().upper() + if not normalized.isalpha(): + return None + + acc = 0 + for ch in normalized: + code = ord(ch) + if code < ord("A") or code > ord("Z"): + return None + acc = acc * 26 + (code - ord("A") + 1) + return acc - 1 + + def _row_index(row: dict[str, Any]) -> int: """Extract row index from a row dictionary. diff --git a/src/exstruct/mcp/extract_runner.py b/src/exstruct/mcp/extract_runner.py index 2b287ec..387bc00 100644 --- a/src/exstruct/mcp/extract_runner.py +++ b/src/exstruct/mcp/extract_runner.py @@ -38,6 +38,10 @@ class ExtractOptions(BaseModel): auto_page_breaks_dir: Path | None = Field( default=None, description="Directory for auto page-break outputs." ) + alpha_col: bool = Field( + default=True, + description="When true, convert CellRow column keys to Excel-style ABC names (A, B, ..., Z, AA, ...) instead of 0-based indices. MCP default is true.", + ) class ExtractRequest(BaseModel): @@ -118,6 +122,7 @@ def run_extract( sheets_dir=sheets_dir, print_areas_dir=print_areas_dir, auto_page_breaks_dir=auto_page_breaks_dir, + alpha_col=options.alpha_col, ) meta, meta_warnings = _try_read_workbook_meta(resolved_input) warnings.extend(meta_warnings) diff --git a/src/exstruct/mcp/patch_runner.py b/src/exstruct/mcp/patch_runner.py new file mode 100644 index 0000000..e952373 --- /dev/null +++ b/src/exstruct/mcp/patch_runner.py @@ -0,0 +1,1315 @@ +from __future__ import annotations + +from collections.abc import Iterator +from contextlib import contextmanager +from pathlib import Path +import re +from typing import Literal, Protocol, runtime_checkable + +from pydantic import BaseModel, Field, field_validator, model_validator +import xlwings as xw + +from exstruct.cli.availability import get_com_availability + +from .extract_runner import OnConflictPolicy +from .io import PathPolicy + +PatchOpType = Literal[ + "set_value", + "set_formula", + "add_sheet", + "set_range_values", + "fill_formula", + "set_value_if", + "set_formula_if", +] +PatchStatus = Literal["applied", "skipped"] +PatchValueKind = Literal["value", "formula", "sheet"] +FormulaIssueLevel = Literal["warning", "error"] +FormulaIssueCode = Literal[ + "invalid_token", + "ref_error", + "name_error", + "div0_error", + "value_error", + "na_error", + "circular_ref_suspected", +] + +_ALLOWED_EXTENSIONS = {".xlsx", ".xlsm", ".xls"} +_A1_PATTERN = re.compile(r"^[A-Za-z]{1,3}[1-9][0-9]*$") +_A1_RANGE_PATTERN = re.compile(r"^[A-Za-z]{1,3}[1-9][0-9]*:[A-Za-z]{1,3}[1-9][0-9]*$") + + +@runtime_checkable +class OpenpyxlCellProtocol(Protocol): + """Protocol for openpyxl cell access used by patch runner.""" + + value: str | int | float | None + data_type: str | None + + +@runtime_checkable +class OpenpyxlWorksheetProtocol(Protocol): + """Protocol for openpyxl worksheet access used by patch runner.""" + + def __getitem__(self, key: str) -> OpenpyxlCellProtocol: ... + + +@runtime_checkable +class OpenpyxlWorkbookProtocol(Protocol): + """Protocol for openpyxl workbook access used by patch runner.""" + + sheetnames: list[str] + + def __getitem__(self, key: str) -> OpenpyxlWorksheetProtocol: ... + + def create_sheet(self, title: str) -> OpenpyxlWorksheetProtocol: ... + + def save(self, filename: str | Path) -> None: ... + + def close(self) -> None: ... + + +@runtime_checkable +class XlwingsRangeProtocol(Protocol): + """Protocol for xlwings range access used by patch runner.""" + + value: str | int | float | None + formula: str | None + + +@runtime_checkable +class XlwingsSheetProtocol(Protocol): + """Protocol for xlwings sheet access used by patch runner.""" + + name: str + + def range(self, cell: str) -> XlwingsRangeProtocol: ... + + +@runtime_checkable +class XlwingsSheetsProtocol(Protocol): + """Protocol for xlwings sheets collection.""" + + def __iter__(self) -> Iterator[XlwingsSheetProtocol]: ... + + def __len__(self) -> int: ... + + def __getitem__(self, index: int) -> XlwingsSheetProtocol: ... + + def add( + self, name: str, after: XlwingsSheetProtocol | None = None + ) -> XlwingsSheetProtocol: ... + + +@runtime_checkable +class XlwingsWorkbookProtocol(Protocol): + """Protocol for xlwings workbook access used by patch runner.""" + + sheets: XlwingsSheetsProtocol + + def save(self, filename: str) -> None: ... + + def close(self) -> None: ... + + +class PatchOp(BaseModel): + """Single patch operation for an Excel workbook. + + Operation types and their required fields: + + - ``set_value``: Set a cell value. Requires ``sheet``, ``cell``, ``value``. + - ``set_formula``: Set a cell formula. Requires ``sheet``, ``cell``, ``formula`` (must start with ``=``). + - ``add_sheet``: Add a new worksheet. Requires ``sheet`` (new sheet name). No ``cell``/``value``/``formula``. + - ``set_range_values``: Set values for a rectangular range. Requires ``sheet``, ``range`` (e.g. ``A1:C3``), ``values`` (2D list matching range shape). + - ``fill_formula``: Fill a formula across a single row or column. Requires ``sheet``, ``range``, ``base_cell``, ``formula``. + - ``set_value_if``: Conditionally set value. Requires ``sheet``, ``cell``, ``value``. ``expected`` is optional; ``null`` matches an empty cell. Skips if current value != expected. + - ``set_formula_if``: Conditionally set formula. Requires ``sheet``, ``cell``, ``formula``. ``expected`` is optional; ``null`` matches an empty cell. Skips if current value != expected. + """ + + op: PatchOpType = Field( + description=( + "Operation type: 'set_value', 'set_formula', 'add_sheet', " + "'set_range_values', 'fill_formula', 'set_value_if', or 'set_formula_if'." + ) + ) + sheet: str = Field( + description="Target sheet name. For add_sheet, this is the new sheet name." + ) + cell: str | None = Field( + default=None, + description="Cell reference in A1 notation (e.g. 'B2'). Required for set_value, set_formula, set_value_if, set_formula_if.", + ) + range: str | None = Field( + default=None, + description="Range reference in A1 notation (e.g. 'A1:C3'). Required for set_range_values and fill_formula.", + ) + base_cell: str | None = Field( + default=None, + description="Base cell for formula translation in fill_formula (e.g. 'C2').", + ) + expected: str | int | float | None = Field( + default=None, + description="Expected current value for conditional ops (set_value_if, set_formula_if). Operation is skipped if mismatch.", + ) + value: str | int | float | None = Field( + default=None, + description="Value to set. Use null to clear a cell. For set_value and set_value_if.", + ) + values: list[list[str | int | float | None]] | None = Field( + default=None, + description="2D list of values for set_range_values. Shape must match the range dimensions.", + ) + formula: str | None = Field( + default=None, + description="Formula string starting with '=' (e.g. '=SUM(A1:A10)'). For set_formula, set_formula_if, fill_formula.", + ) + + @field_validator("sheet") + @classmethod + def _validate_sheet(cls, value: str) -> str: + if not value.strip(): + raise ValueError("sheet must not be empty.") + return value + + @field_validator("cell") + @classmethod + def _validate_cell(cls, value: str | None) -> str | None: + if value is None: + return None + candidate = value.strip() + if not _A1_PATTERN.match(candidate): + raise ValueError(f"Invalid cell reference: {value}") + return candidate.upper() + + @field_validator("base_cell") + @classmethod + def _validate_base_cell(cls, value: str | None) -> str | None: + if value is None: + return None + candidate = value.strip() + if not _A1_PATTERN.match(candidate): + raise ValueError(f"Invalid base_cell reference: {value}") + return candidate.upper() + + @field_validator("range") + @classmethod + def _validate_range(cls, value: str | None) -> str | None: + if value is None: + return None + candidate = value.strip() + if not _A1_RANGE_PATTERN.match(candidate): + raise ValueError(f"Invalid range reference: {value}") + start, end = candidate.split(":", maxsplit=1) + return f"{start.upper()}:{end.upper()}" + + @model_validator(mode="after") + def _validate_op(self) -> PatchOp: + if self.op == "add_sheet": + _validate_add_sheet(self) + return self + if self.op == "set_value": + _validate_cell_required(self) + _validate_set_value(self) + return self + if self.op == "set_formula": + _validate_cell_required(self) + _validate_set_formula(self) + return self + if self.op == "set_range_values": + _validate_set_range_values(self) + return self + if self.op == "fill_formula": + _validate_fill_formula(self) + return self + if self.op == "set_value_if": + _validate_cell_required(self) + _validate_set_value_if(self) + return self + if self.op == "set_formula_if": + _validate_cell_required(self) + _validate_set_formula_if(self) + return self + return self + + +def _validate_add_sheet(op: PatchOp) -> None: + """Validate add_sheet operation.""" + if op.cell is not None: + raise ValueError("add_sheet does not accept cell.") + if op.range is not None: + raise ValueError("add_sheet does not accept range.") + if op.base_cell is not None: + raise ValueError("add_sheet does not accept base_cell.") + if op.expected is not None: + raise ValueError("add_sheet does not accept expected.") + if op.value is not None: + raise ValueError("add_sheet does not accept value.") + if op.values is not None: + raise ValueError("add_sheet does not accept values.") + if op.formula is not None: + raise ValueError("add_sheet does not accept formula.") + + +def _validate_cell_required(op: PatchOp) -> None: + """Validate that the operation has a cell value.""" + if op.cell is None: + raise ValueError(f"{op.op} requires cell.") + + +def _validate_set_value(op: PatchOp) -> None: + """Validate set_value operation.""" + if op.range is not None: + raise ValueError("set_value does not accept range.") + if op.base_cell is not None: + raise ValueError("set_value does not accept base_cell.") + if op.expected is not None: + raise ValueError("set_value does not accept expected.") + if op.values is not None: + raise ValueError("set_value does not accept values.") + if op.formula is not None: + raise ValueError("set_value does not accept formula.") + + +def _validate_set_formula(op: PatchOp) -> None: + """Validate set_formula operation.""" + if op.range is not None: + raise ValueError("set_formula does not accept range.") + if op.base_cell is not None: + raise ValueError("set_formula does not accept base_cell.") + if op.expected is not None: + raise ValueError("set_formula does not accept expected.") + if op.values is not None: + raise ValueError("set_formula does not accept values.") + if op.value is not None: + raise ValueError("set_formula does not accept value.") + if op.formula is None: + raise ValueError("set_formula requires formula.") + if not op.formula.startswith("="): + raise ValueError("set_formula requires formula starting with '='.") + + +def _validate_set_range_values(op: PatchOp) -> None: + """Validate set_range_values operation.""" + if op.cell is not None: + raise ValueError("set_range_values does not accept cell.") + if op.base_cell is not None: + raise ValueError("set_range_values does not accept base_cell.") + if op.expected is not None: + raise ValueError("set_range_values does not accept expected.") + if op.formula is not None: + raise ValueError("set_range_values does not accept formula.") + if op.range is None: + raise ValueError("set_range_values requires range.") + if op.values is None: + raise ValueError("set_range_values requires values.") + if not op.values: + raise ValueError("set_range_values requires non-empty values.") + if not all(op.values): + raise ValueError("set_range_values values rows must not be empty.") + expected_width = len(op.values[0]) + if any(len(row) != expected_width for row in op.values): + raise ValueError("set_range_values requires rectangular values.") + + +def _validate_fill_formula(op: PatchOp) -> None: + """Validate fill_formula operation.""" + if op.cell is not None: + raise ValueError("fill_formula does not accept cell.") + if op.expected is not None: + raise ValueError("fill_formula does not accept expected.") + if op.value is not None: + raise ValueError("fill_formula does not accept value.") + if op.values is not None: + raise ValueError("fill_formula does not accept values.") + if op.range is None: + raise ValueError("fill_formula requires range.") + if op.base_cell is None: + raise ValueError("fill_formula requires base_cell.") + if op.formula is None: + raise ValueError("fill_formula requires formula.") + if not op.formula.startswith("="): + raise ValueError("fill_formula requires formula starting with '='.") + + +def _validate_set_value_if(op: PatchOp) -> None: + """Validate set_value_if operation.""" + if op.formula is not None: + raise ValueError("set_value_if does not accept formula.") + if op.range is not None: + raise ValueError("set_value_if does not accept range.") + if op.values is not None: + raise ValueError("set_value_if does not accept values.") + if op.base_cell is not None: + raise ValueError("set_value_if does not accept base_cell.") + + +def _validate_set_formula_if(op: PatchOp) -> None: + """Validate set_formula_if operation.""" + if op.value is not None: + raise ValueError("set_formula_if does not accept value.") + if op.range is not None: + raise ValueError("set_formula_if does not accept range.") + if op.values is not None: + raise ValueError("set_formula_if does not accept values.") + if op.base_cell is not None: + raise ValueError("set_formula_if does not accept base_cell.") + if op.formula is None: + raise ValueError("set_formula_if requires formula.") + if not op.formula.startswith("="): + raise ValueError("set_formula_if requires formula starting with '='.") + + +class PatchValue(BaseModel): + """Normalized before/after value in patch diff.""" + + kind: PatchValueKind + value: str | int | float | None + + +class PatchDiffItem(BaseModel): + """Applied change record for patch operations.""" + + op_index: int + op: PatchOpType + sheet: str + cell: str | None = None + before: PatchValue | None = None + after: PatchValue | None = None + status: PatchStatus = "applied" + + +class PatchErrorDetail(BaseModel): + """Structured error details for patch failures.""" + + op_index: int + op: PatchOpType + sheet: str + cell: str | None + message: str + + +class FormulaIssue(BaseModel): + """Formula health-check finding.""" + + sheet: str + cell: str + level: FormulaIssueLevel + code: FormulaIssueCode + message: str + + +class PatchRequest(BaseModel): + """Input model for ExStruct MCP patch.""" + + xlsx_path: Path + ops: list[PatchOp] + out_dir: Path | None = None + out_name: str | None = None + on_conflict: OnConflictPolicy = "overwrite" + auto_formula: bool = False + dry_run: bool = False + return_inverse_ops: bool = False + preflight_formula_check: bool = False + + +class PatchResult(BaseModel): + """Output model for ExStruct MCP patch.""" + + out_path: str + patch_diff: list[PatchDiffItem] = Field(default_factory=list) + inverse_ops: list[PatchOp] = Field(default_factory=list) + formula_issues: list[FormulaIssue] = Field(default_factory=list) + warnings: list[str] = Field(default_factory=list) + error: PatchErrorDetail | None = None + + +def run_patch( + request: PatchRequest, *, policy: PathPolicy | None = None +) -> PatchResult: + """Run a patch operation and write the updated workbook. + + Args: + request: Patch request payload. + policy: Optional path policy for access control. + + Returns: + Patch result with output path and diff. + + Raises: + FileNotFoundError: If the input file does not exist. + ValueError: If validation fails or the path violates policy. + RuntimeError: If a backend operation fails. + """ + resolved_input = _resolve_input_path(request.xlsx_path, policy=policy) + _ensure_supported_extension(resolved_input) + output_path = _resolve_output_path( + resolved_input, + out_dir=request.out_dir, + out_name=request.out_name, + policy=policy, + ) + output_path, warning, skipped = _apply_conflict_policy( + output_path, request.on_conflict + ) + warnings: list[str] = [] + if warning: + warnings.append(warning) + if skipped and not request.dry_run: + return PatchResult( + out_path=str(output_path), + patch_diff=[], + inverse_ops=[], + formula_issues=[], + warnings=warnings, + ) + if skipped and request.dry_run: + warnings.append( + "Dry-run mode ignores on_conflict=skip and simulates patch without writing." + ) + + com = get_com_availability() + if resolved_input.suffix.lower() == ".xls" and not com.available: + raise ValueError( + ".xls editing requires Windows Excel COM (xlwings) in this environment." + ) + + use_openpyxl = _requires_openpyxl_backend(request) + if use_openpyxl and com.available: + warnings.append("Using openpyxl backend for extended patch features.") + + _ensure_output_dir(output_path) + if com.available and not use_openpyxl: + try: + diff = _apply_ops_xlwings( + resolved_input, + output_path, + request.ops, + request.auto_formula, + ) + return PatchResult( + out_path=str(output_path), + patch_diff=diff, + inverse_ops=[], + formula_issues=[], + warnings=warnings, + ) + except PatchOpError as exc: + return PatchResult( + out_path=str(output_path), + patch_diff=[], + inverse_ops=[], + formula_issues=[], + warnings=warnings, + error=exc.detail, + ) + except Exception as exc: + fallback = _maybe_fallback_openpyxl( + request, + resolved_input, + output_path, + warnings, + reason=f"COM patch failed; falling back to openpyxl. ({exc!r})", + ) + if fallback is not None: + return fallback + raise RuntimeError(f"COM patch failed: {exc}") from exc + + if com.reason: + warnings.append(f"COM unavailable: {com.reason}") + return _apply_with_openpyxl( + request, + resolved_input, + output_path, + warnings, + ) + + +def _apply_with_openpyxl( + request: PatchRequest, + input_path: Path, + output_path: Path, + warnings: list[str], +) -> PatchResult: + """Apply patch operations using openpyxl.""" + try: + diff, inverse_ops, formula_issues = _apply_ops_openpyxl( + request, + input_path, + output_path, + ) + except PatchOpError as exc: + return PatchResult( + out_path=str(output_path), + patch_diff=[], + inverse_ops=[], + formula_issues=[], + warnings=warnings, + error=exc.detail, + ) + except ValueError: + raise + except FileNotFoundError: + raise + except OSError: + raise + except Exception as exc: + raise RuntimeError(f"openpyxl patch failed: {exc}") from exc + + if not request.dry_run: + warnings.append( + "openpyxl editing may drop shapes/charts or unsupported elements." + ) + _append_skip_warnings(warnings, diff) + if ( + not request.dry_run + and request.preflight_formula_check + and any(issue.level == "error" for issue in formula_issues) + ): + issue = formula_issues[0] + op_index, op_name = _find_preflight_issue_origin(issue, request.ops) + error = PatchErrorDetail( + op_index=op_index, + op=op_name, + sheet=issue.sheet, + cell=issue.cell, + message=f"Formula health check failed: {issue.message}", + ) + return PatchResult( + out_path=str(output_path), + patch_diff=[], + inverse_ops=[], + formula_issues=formula_issues, + warnings=warnings, + error=error, + ) + return PatchResult( + out_path=str(output_path), + patch_diff=diff, + inverse_ops=inverse_ops, + formula_issues=formula_issues, + warnings=warnings, + ) + + +def _append_skip_warnings(warnings: list[str], diff: list[PatchDiffItem]) -> None: + """Append warning messages for skipped conditional operations.""" + for item in diff: + if item.status != "skipped": + continue + warnings.append( + f"Skipped op[{item.op_index}] {item.op} at {item.sheet}!{item.cell} due to condition mismatch." + ) + + +def _find_preflight_issue_origin( + issue: FormulaIssue, ops: list[PatchOp] +) -> tuple[int, PatchOpType]: + """Find the most likely op index/op name for a preflight formula issue.""" + for index, op in enumerate(ops): + if _op_targets_issue_cell(op, issue.sheet, issue.cell): + return index, op.op + return -1, "set_value" + + +def _op_targets_issue_cell(op: PatchOp, sheet: str, cell: str) -> bool: + """Return True when an op can affect the specified sheet/cell.""" + if op.sheet != sheet: + return False + if op.cell is not None: + return op.cell == cell + if op.range is None: + return False + for row in _expand_range_coordinates(op.range): + if cell in row: + return True + return False + + +def _maybe_fallback_openpyxl( + request: PatchRequest, + input_path: Path, + output_path: Path, + warnings: list[str], + *, + reason: str, +) -> PatchResult | None: + """Attempt openpyxl fallback after COM failure.""" + if input_path.suffix.lower() == ".xls": + warnings.append(reason) + return None + warnings.append(reason) + return _apply_with_openpyxl( + request, + input_path, + output_path, + warnings, + ) + + +def _requires_openpyxl_backend(request: PatchRequest) -> bool: + """Return True if request requires openpyxl backend for extended features.""" + if request.dry_run or request.return_inverse_ops or request.preflight_formula_check: + return True + return any( + op.op in {"set_range_values", "fill_formula", "set_value_if", "set_formula_if"} + for op in request.ops + ) + + +def _resolve_input_path(path: Path, *, policy: PathPolicy | None) -> Path: + """Resolve and validate the input path.""" + resolved = policy.ensure_allowed(path) if policy else path.resolve() + if not resolved.exists(): + raise FileNotFoundError(f"Input file not found: {resolved}") + if not resolved.is_file(): + raise ValueError(f"Input path is not a file: {resolved}") + return resolved + + +def _ensure_supported_extension(path: Path) -> None: + """Validate that the input file extension is supported.""" + if path.suffix.lower() not in _ALLOWED_EXTENSIONS: + raise ValueError(f"Unsupported file extension: {path.suffix}") + + +def _resolve_output_path( + input_path: Path, + *, + out_dir: Path | None, + out_name: str | None, + policy: PathPolicy | None, +) -> Path: + """Build and validate the output path.""" + target_dir = out_dir or input_path.parent + target_dir = policy.ensure_allowed(target_dir) if policy else target_dir.resolve() + name = _normalize_output_name(input_path, out_name) + output_path = (target_dir / name).resolve() + if policy is not None: + output_path = policy.ensure_allowed(output_path) + return output_path + + +def _normalize_output_name(input_path: Path, out_name: str | None) -> str: + """Normalize output filename with a safe suffix.""" + if out_name: + candidate = Path(out_name) + return ( + candidate.name + if candidate.suffix + else f"{candidate.name}{input_path.suffix}" + ) + return f"{input_path.stem}_patched{input_path.suffix}" + + +def _ensure_output_dir(path: Path) -> None: + """Ensure the output directory exists before writing.""" + path.parent.mkdir(parents=True, exist_ok=True) + + +def _apply_conflict_policy( + output_path: Path, on_conflict: OnConflictPolicy +) -> tuple[Path, str | None, bool]: + """Apply output conflict policy to a resolved output path.""" + if not output_path.exists(): + return output_path, None, False + if on_conflict == "skip": + return ( + output_path, + f"Output exists; skipping write: {output_path.name}", + True, + ) + if on_conflict == "rename": + renamed = _next_available_path(output_path) + return ( + renamed, + f"Output exists; renamed to: {renamed.name}", + False, + ) + return output_path, None, False + + +def _next_available_path(path: Path) -> Path: + """Return the next available path by appending a numeric suffix.""" + if not path.exists(): + return path + stem = path.stem + suffix = path.suffix + for idx in range(1, 10_000): + candidate = path.with_name(f"{stem}_{idx}{suffix}") + if not candidate.exists(): + return candidate + raise RuntimeError(f"Failed to resolve unique path for {path}") + + +def _apply_ops_openpyxl( + request: PatchRequest, + input_path: Path, + output_path: Path, +) -> tuple[list[PatchDiffItem], list[PatchOp], list[FormulaIssue]]: + """Apply operations using openpyxl.""" + try: + from openpyxl import load_workbook + except ImportError as exc: + raise RuntimeError(f"openpyxl is not available: {exc}") from exc + + if input_path.suffix.lower() == ".xls": + raise ValueError("openpyxl cannot edit .xls files.") + + if input_path.suffix.lower() == ".xlsm": + workbook = load_workbook(input_path, keep_vba=True) + else: + workbook = load_workbook(input_path) + try: + diff, inverse_ops = _apply_ops_to_openpyxl_workbook( + workbook, + request.ops, + request.auto_formula, + return_inverse_ops=request.return_inverse_ops, + ) + formula_issues = ( + _collect_formula_issues_openpyxl(workbook) + if request.preflight_formula_check + else [] + ) + if not request.dry_run and not ( + request.preflight_formula_check + and any(issue.level == "error" for issue in formula_issues) + ): + workbook.save(output_path) + finally: + workbook.close() + return diff, inverse_ops, formula_issues + + +def _apply_ops_to_openpyxl_workbook( + workbook: OpenpyxlWorkbookProtocol, + ops: list[PatchOp], + auto_formula: bool, + *, + return_inverse_ops: bool, +) -> tuple[list[PatchDiffItem], list[PatchOp]]: + """Apply ops to an openpyxl workbook instance.""" + sheets = _openpyxl_sheet_map(workbook) + diff: list[PatchDiffItem] = [] + inverse_ops: list[PatchOp] = [] + for index, op in enumerate(ops): + try: + item, inverse = _apply_openpyxl_op( + workbook, sheets, op, index, auto_formula + ) + diff.append(item) + if return_inverse_ops and item.status == "applied" and inverse is not None: + inverse_ops.append(inverse) + except ValueError as exc: + raise PatchOpError.from_op(index, op, exc) from exc + if return_inverse_ops: + inverse_ops.reverse() + return diff, inverse_ops + + +def _openpyxl_sheet_map( + workbook: OpenpyxlWorkbookProtocol, +) -> dict[str, OpenpyxlWorksheetProtocol]: + """Build a sheet map for openpyxl workbooks.""" + sheet_names = getattr(workbook, "sheetnames", None) + if not isinstance(sheet_names, list): + raise ValueError("Invalid workbook: sheetnames missing.") + return {name: workbook[name] for name in sheet_names} + + +def _apply_openpyxl_op( + workbook: OpenpyxlWorkbookProtocol, + sheets: dict[str, OpenpyxlWorksheetProtocol], + op: PatchOp, + index: int, + auto_formula: bool, +) -> tuple[PatchDiffItem, PatchOp | None]: + """Apply a single op to openpyxl workbook.""" + if op.op == "add_sheet": + return _apply_openpyxl_add_sheet(workbook, sheets, op, index) + + existing_sheet = sheets.get(op.sheet) + if existing_sheet is None: + raise ValueError(f"Sheet not found: {op.sheet}") + + if op.op == "set_range_values": + return _apply_openpyxl_set_range_values(existing_sheet, op, index) + + if op.op == "fill_formula": + return _apply_openpyxl_fill_formula(existing_sheet, op, index) + + if op.op in {"set_value", "set_formula", "set_value_if", "set_formula_if"}: + return _apply_openpyxl_cell_op(existing_sheet, op, index, auto_formula) + raise ValueError(f"Unsupported op: {op.op}") + + +def _apply_openpyxl_add_sheet( + workbook: OpenpyxlWorkbookProtocol, + sheets: dict[str, OpenpyxlWorksheetProtocol], + op: PatchOp, + index: int, +) -> tuple[PatchDiffItem, PatchOp | None]: + """Apply add_sheet op.""" + if op.sheet in sheets: + raise ValueError(f"Sheet already exists: {op.sheet}") + sheet = workbook.create_sheet(title=op.sheet) + sheets[op.sheet] = sheet + return ( + PatchDiffItem( + op_index=index, + op=op.op, + sheet=op.sheet, + cell=None, + before=None, + after=PatchValue(kind="sheet", value=op.sheet), + ), + None, + ) + + +def _apply_openpyxl_set_range_values( + sheet: OpenpyxlWorksheetProtocol, + op: PatchOp, + index: int, +) -> tuple[PatchDiffItem, PatchOp | None]: + """Apply set_range_values op.""" + if op.range is None or op.values is None: + raise ValueError("set_range_values requires range and values.") + coordinates = _expand_range_coordinates(op.range) + rows, cols = _shape_of_coordinates(coordinates) + if len(op.values) != rows: + raise ValueError("set_range_values values height does not match range.") + if any(len(row) != cols for row in op.values): + raise ValueError("set_range_values values width does not match range.") + for r_idx, row in enumerate(coordinates): + for c_idx, coord in enumerate(row): + sheet[coord].value = op.values[r_idx][c_idx] + return ( + PatchDiffItem( + op_index=index, + op=op.op, + sheet=op.sheet, + cell=op.range, + before=None, + after=PatchValue(kind="value", value=f"{rows}x{cols}"), + ), + None, + ) + + +def _apply_openpyxl_fill_formula( + sheet: OpenpyxlWorksheetProtocol, + op: PatchOp, + index: int, +) -> tuple[PatchDiffItem, PatchOp | None]: + """Apply fill_formula op.""" + if op.range is None or op.formula is None or op.base_cell is None: + raise ValueError("fill_formula requires range, base_cell and formula.") + coordinates = _expand_range_coordinates(op.range) + rows, cols = _shape_of_coordinates(coordinates) + if rows != 1 and cols != 1: + raise ValueError("fill_formula range must be a single row or a single column.") + for row in coordinates: + for coord in row: + sheet[coord].value = _translate_formula(op.formula, op.base_cell, coord) + return ( + PatchDiffItem( + op_index=index, + op=op.op, + sheet=op.sheet, + cell=op.range, + before=None, + after=PatchValue(kind="formula", value=op.formula), + ), + None, + ) + + +def _apply_openpyxl_cell_op( + sheet: OpenpyxlWorksheetProtocol, + op: PatchOp, + index: int, + auto_formula: bool, +) -> tuple[PatchDiffItem, PatchOp | None]: + """Apply single-cell operations.""" + cell_ref = op.cell + if cell_ref is None: + raise ValueError(f"{op.op} requires cell.") + cell = sheet[cell_ref] + before = _openpyxl_cell_value(cell) + + if op.op == "set_value": + after = _set_cell_value(cell, op.value, auto_formula, op_name="set_value") + return _build_cell_result( + op, index, cell_ref, before, after + ), _build_inverse_cell_op(op, cell_ref, before) + if op.op == "set_formula": + formula = _require_formula(op.formula, "set_formula") + cell.value = formula + after = PatchValue(kind="formula", value=formula) + return _build_cell_result( + op, index, cell_ref, before, after + ), _build_inverse_cell_op(op, cell_ref, before) + if op.op == "set_value_if": + if not _values_equal_for_condition( + _patch_value_to_primitive(before), op.expected + ): + return _build_skipped_result(op, index, cell_ref, before), None + after = _set_cell_value(cell, op.value, auto_formula, op_name="set_value_if") + return _build_cell_result( + op, index, cell_ref, before, after + ), _build_inverse_cell_op(op, cell_ref, before) + formula_if = _require_formula(op.formula, "set_formula_if") + if not _values_equal_for_condition(_patch_value_to_primitive(before), op.expected): + return _build_skipped_result(op, index, cell_ref, before), None + cell.value = formula_if + after = PatchValue(kind="formula", value=formula_if) + return _build_cell_result( + op, index, cell_ref, before, after + ), _build_inverse_cell_op(op, cell_ref, before) + + +def _set_cell_value( + cell: OpenpyxlCellProtocol, + value: str | int | float | None, + auto_formula: bool, + *, + op_name: str, +) -> PatchValue: + """Set cell value with auto_formula handling.""" + if isinstance(value, str) and value.startswith("="): + if not auto_formula: + raise ValueError(f"{op_name} rejects values starting with '='.") + cell.value = value + return PatchValue(kind="formula", value=value) + cell.value = value + return PatchValue(kind="value", value=value) + + +def _build_cell_result( + op: PatchOp, + index: int, + cell_ref: str, + before: PatchValue | None, + after: PatchValue | None, +) -> PatchDiffItem: + """Build applied diff item for single-cell op.""" + return PatchDiffItem( + op_index=index, + op=op.op, + sheet=op.sheet, + cell=cell_ref, + before=before, + after=after, + ) + + +def _build_skipped_result( + op: PatchOp, + index: int, + cell_ref: str, + before: PatchValue | None, +) -> PatchDiffItem: + """Build skipped diff item.""" + return PatchDiffItem( + op_index=index, + op=op.op, + sheet=op.sheet, + cell=cell_ref, + before=before, + after=before, + status="skipped", + ) + + +def _require_formula(formula: str | None, op_name: str) -> str: + """Require a non-null formula string.""" + if formula is None: + raise ValueError(f"{op_name} requires formula.") + return formula + + +def _openpyxl_cell_value(cell: OpenpyxlCellProtocol) -> PatchValue | None: + """Normalize an openpyxl cell value into PatchValue.""" + value = getattr(cell, "value", None) + if value is None: + return None + data_type = getattr(cell, "data_type", None) + if data_type == "f": + text = _normalize_formula(value) + return PatchValue(kind="formula", value=text) + return PatchValue(kind="value", value=value) + + +def _normalize_formula(value: object) -> str: + """Ensure formula string starts with '='.""" + text = str(value) + return text if text.startswith("=") else f"={text}" + + +def _expand_range_coordinates(range_ref: str) -> list[list[str]]: + """Expand A1 range string into a 2D list of coordinates.""" + try: + from openpyxl.utils.cell import get_column_letter, range_boundaries + except ImportError as exc: + raise RuntimeError(f"openpyxl is not available: {exc}") from exc + min_col, min_row, max_col, max_row = range_boundaries(range_ref) + if min_col > max_col or min_row > max_row: + raise ValueError(f"Invalid range reference: {range_ref}") + rows: list[list[str]] = [] + for row_idx in range(min_row, max_row + 1): + row: list[str] = [] + for col_idx in range(min_col, max_col + 1): + row.append(f"{get_column_letter(col_idx)}{row_idx}") + rows.append(row) + return rows + + +def _shape_of_coordinates(coordinates: list[list[str]]) -> tuple[int, int]: + """Return rows/cols for expanded coordinates.""" + if not coordinates or not coordinates[0]: + raise ValueError("Range expansion resulted in an empty coordinate set.") + return len(coordinates), len(coordinates[0]) + + +def _translate_formula(formula: str, origin: str, target: str) -> str: + """Translate formula with relative references from origin to target.""" + try: + from openpyxl.formula.translate import Translator + except ImportError as exc: + raise RuntimeError(f"openpyxl is not available: {exc}") from exc + translated = Translator(formula, origin=origin).translate_formula(target) + return str(translated) + + +def _patch_value_to_primitive(value: PatchValue | None) -> str | int | float | None: + """Convert PatchValue into primitive value for condition checks.""" + if value is None: + return None + return value.value + + +def _values_equal_for_condition( + current: str | int | float | None, + expected: str | int | float | None, +) -> bool: + """Compare values for conditional update checks.""" + return current == expected + + +def _build_inverse_cell_op( + op: PatchOp, + cell_ref: str, + before: PatchValue | None, +) -> PatchOp | None: + """Build inverse operation for single-cell updates.""" + if op.op not in {"set_value", "set_formula", "set_value_if", "set_formula_if"}: + return None + if before is None: + return PatchOp(op="set_value", sheet=op.sheet, cell=cell_ref, value=None) + if before.kind == "formula": + return PatchOp( + op="set_formula", + sheet=op.sheet, + cell=cell_ref, + formula=str(before.value), + ) + return PatchOp(op="set_value", sheet=op.sheet, cell=cell_ref, value=before.value) + + +def _collect_formula_issues_openpyxl( + workbook: OpenpyxlWorkbookProtocol, +) -> list[FormulaIssue]: + """Collect simple formula issues by scanning formula text.""" + token_map: dict[str, tuple[FormulaIssueCode, FormulaIssueLevel]] = { + "#REF!": ("ref_error", "error"), + "#NAME?": ("name_error", "error"), + "#DIV/0!": ("div0_error", "error"), + "#VALUE!": ("value_error", "error"), + "#N/A": ("na_error", "warning"), + } + issues: list[FormulaIssue] = [] + for sheet_name in workbook.sheetnames: + sheet = workbook[sheet_name] + iter_rows = getattr(sheet, "iter_rows", None) + if iter_rows is None: + continue + for row in iter_rows(): + for cell in row: + raw = getattr(cell, "value", None) + if not isinstance(raw, str) or not raw.startswith("="): + continue + normalized = raw.upper() + if "==" in normalized: + issues.append( + FormulaIssue( + sheet=sheet_name, + cell=str(getattr(cell, "coordinate", "")), + level="warning", + code="invalid_token", + message="Formula contains duplicated '=' token.", + ) + ) + for token, (code, level) in token_map.items(): + if token in normalized: + issues.append( + FormulaIssue( + sheet=sheet_name, + cell=str(getattr(cell, "coordinate", "")), + level=level, + code=code, + message=f"Formula contains error token {token}.", + ) + ) + return issues + + +def _apply_ops_xlwings( + input_path: Path, + output_path: Path, + ops: list[PatchOp], + auto_formula: bool, +) -> list[PatchDiffItem]: + """Apply operations using Excel COM via xlwings.""" + diff: list[PatchDiffItem] = [] + try: + with _xlwings_workbook(input_path) as workbook: + sheets = {sheet.name: sheet for sheet in workbook.sheets} + for index, op in enumerate(ops): + try: + diff.append( + _apply_xlwings_op(workbook, sheets, op, index, auto_formula) + ) + except ValueError as exc: + raise PatchOpError.from_op(index, op, exc) from exc + workbook.save(str(output_path)) + except ValueError: + raise + except Exception as exc: + raise RuntimeError(f"COM patch failed: {exc}") from exc + return diff + + +def _apply_xlwings_op( + workbook: XlwingsWorkbookProtocol, + sheets: dict[str, XlwingsSheetProtocol], + op: PatchOp, + index: int, + auto_formula: bool, +) -> PatchDiffItem: + """Apply a single op to an xlwings workbook.""" + # Extended ops are routed to openpyxl by _requires_openpyxl_backend. + # Keep this explicit guard to prevent accidental path regressions. + if op.op not in {"add_sheet", "set_value", "set_formula"}: + raise ValueError(f"Unsupported op: {op.op}") + if op.op == "add_sheet": + if op.sheet in sheets: + raise ValueError(f"Sheet already exists: {op.sheet}") + last = workbook.sheets[-1] if workbook.sheets else None + sheet = workbook.sheets.add(name=op.sheet, after=last) + sheets[op.sheet] = sheet + return PatchDiffItem( + op_index=index, + op=op.op, + sheet=op.sheet, + cell=None, + before=None, + after=PatchValue(kind="sheet", value=op.sheet), + ) + + existing_sheet = sheets.get(op.sheet) + if existing_sheet is None: + raise ValueError(f"Sheet not found: {op.sheet}") + cell_ref = op.cell + if cell_ref is None: + raise ValueError(f"{op.op} requires cell.") + rng = existing_sheet.range(cell_ref) + before = _xlwings_cell_value(rng) + if op.op == "set_value": + if isinstance(op.value, str) and op.value.startswith("="): + if not auto_formula: + raise ValueError("set_value rejects values starting with '='.") + rng.formula = op.value + after = PatchValue(kind="formula", value=op.value) + else: + rng.value = op.value + after = PatchValue(kind="value", value=op.value) + return PatchDiffItem( + op_index=index, + op=op.op, + sheet=op.sheet, + cell=cell_ref, + before=before, + after=after, + ) + if op.op == "set_formula": + formula = op.formula + if formula is None: + raise ValueError("set_formula requires formula.") + rng.formula = formula + after = PatchValue(kind="formula", value=formula) + return PatchDiffItem( + op_index=index, + op=op.op, + sheet=op.sheet, + cell=cell_ref, + before=before, + after=after, + ) + raise ValueError(f"Unsupported op: {op.op}") + + +def _xlwings_cell_value(cell: XlwingsRangeProtocol) -> PatchValue | None: + """Normalize an xlwings cell value into PatchValue.""" + formula = getattr(cell, "formula", None) + if isinstance(formula, str) and formula.startswith("="): + return PatchValue(kind="formula", value=formula) + value = getattr(cell, "value", None) + if value is None: + return None + return PatchValue(kind="value", value=value) + + +@contextmanager +def _xlwings_workbook(file_path: Path) -> Iterator[XlwingsWorkbookProtocol]: + """Open an Excel workbook with a dedicated COM app.""" + app = xw.App(add_book=False, visible=False) + app.display_alerts = False + app.screen_updating = False + workbook = app.books.open(str(file_path)) + try: + yield workbook + finally: + try: + workbook.close() + except Exception: + pass + try: + app.quit() + except Exception: + try: + app.kill() + except Exception: + pass + + +class PatchOpError(ValueError): + """Patch operation error with structured detail.""" + + def __init__(self, detail: PatchErrorDetail) -> None: + super().__init__(detail.message) + self.detail = detail + + @classmethod + def from_op(cls, index: int, op: PatchOp, exc: Exception) -> PatchOpError: + """Build a PatchOpError from an op and exception.""" + detail = PatchErrorDetail( + op_index=index, + op=op.op, + sheet=op.sheet, + cell=op.cell, + message=str(exc), + ) + return cls(detail) diff --git a/src/exstruct/mcp/server.py b/src/exstruct/mcp/server.py index 5cede48..2051057 100644 --- a/src/exstruct/mcp/server.py +++ b/src/exstruct/mcp/server.py @@ -3,6 +3,7 @@ import argparse import functools import importlib +import json import logging import os from pathlib import Path @@ -19,12 +20,24 @@ from .tools import ( ExtractToolInput, ExtractToolOutput, + PatchToolInput, + PatchToolOutput, + ReadCellsToolInput, + ReadCellsToolOutput, + ReadFormulasToolInput, + ReadFormulasToolOutput, ReadJsonChunkToolInput, ReadJsonChunkToolOutput, + ReadRangeToolInput, + ReadRangeToolOutput, ValidateInputToolInput, ValidateInputToolOutput, run_extract_tool, + run_patch_tool, + run_read_cells_tool, + run_read_formulas_tool, run_read_json_chunk_tool, + run_read_range_tool, run_validate_input_tool, ) @@ -208,11 +221,16 @@ async def _extract_tool( # pylint: disable=redefined-builtin Args: xlsx_path: Path to the Excel workbook. - mode: Extraction mode. + mode: Extraction detail level. Allowed values are: + "light" (cells + table candidates + print areas), + "standard" (recommended default), + "verbose" (adds richer metadata such as links/maps). format: Output format. out_dir: Optional output directory. out_name: Optional output filename. - options: Additional options (reserved for future use). + options: Additional options. Supports: pretty (bool), indent (int), + sheets_dir (str), print_areas_dir (str), auto_page_breaks_dir (str), + alpha_col (bool - convert column keys to Excel-style ABC names). Returns: Extraction result payload. @@ -250,10 +268,13 @@ async def _read_json_chunk_tool( # pylint: disable=redefined-builtin Args: out_path: Path to the JSON output file. - sheet: Optional sheet name. - max_bytes: Maximum chunk size in bytes. - filter: Optional filter payload. - cursor: Optional cursor for pagination. + sheet: Optional sheet name. Required when multiple sheets exist and + no cursor/filter can disambiguate the target. + max_bytes: Maximum chunk size in bytes. Start around 50_000 and + increase (for example 120_000) when chunks are too small. + filter: Optional filter payload with + rows=[start,end], cols=[start,end] (1-based inclusive). + cursor: Optional cursor for pagination (non-negative integer string). Returns: JSON chunk result payload. @@ -276,6 +297,114 @@ async def _read_json_chunk_tool( # pylint: disable=redefined-builtin chunk_tool = app.tool(name="exstruct_read_json_chunk") chunk_tool(_read_json_chunk_tool) + async def _read_range_tool( # pylint: disable=redefined-builtin + out_path: str, + sheet: str | None = None, + range: str = "A1", # noqa: A002 + include_formulas: bool = False, + include_empty: bool = True, + max_cells: int = 10_000, + ) -> ReadRangeToolOutput: + """Read a rectangular cell range from extracted JSON. + + Args: + out_path: Path to the extracted JSON file. + sheet: Optional sheet name. Required when workbook has multiple sheets. + range: A1 range (for example, \"A1:G10\"). + include_formulas: Include formula text for each returned cell. + include_empty: Include empty cells in the range. + max_cells: Safety limit for expanded cells. + + Returns: + Range read result payload. + """ + payload = ReadRangeToolInput( + out_path=out_path, + sheet=sheet, + range=range, + include_formulas=include_formulas, + include_empty=include_empty, + max_cells=max_cells, + ) + work = functools.partial( + run_read_range_tool, + payload, + policy=policy, + ) + result = cast(ReadRangeToolOutput, await anyio.to_thread.run_sync(work)) + return result + + read_range_tool = app.tool(name="exstruct_read_range") + read_range_tool(_read_range_tool) + + async def _read_cells_tool( + out_path: str, + addresses: list[str], + sheet: str | None = None, + include_formulas: bool = True, + ) -> ReadCellsToolOutput: + """Read specific cells from extracted JSON. + + Args: + out_path: Path to the extracted JSON file. + addresses: Target A1 addresses (for example, [\"J98\", \"J124\"]). + sheet: Optional sheet name. Required when workbook has multiple sheets. + include_formulas: Include formula text for each requested cell. + + Returns: + Cell read result payload. + """ + payload = ReadCellsToolInput( + out_path=out_path, + sheet=sheet, + addresses=addresses, + include_formulas=include_formulas, + ) + work = functools.partial( + run_read_cells_tool, + payload, + policy=policy, + ) + result = cast(ReadCellsToolOutput, await anyio.to_thread.run_sync(work)) + return result + + read_cells_tool = app.tool(name="exstruct_read_cells") + read_cells_tool(_read_cells_tool) + + async def _read_formulas_tool( # pylint: disable=redefined-builtin + out_path: str, + sheet: str | None = None, + range: str | None = None, # noqa: A002 + include_values: bool = False, + ) -> ReadFormulasToolOutput: + """Read formulas from extracted JSON. + + Args: + out_path: Path to the extracted JSON file. + sheet: Optional sheet name. Required when workbook has multiple sheets. + range: Optional A1 range to limit formula results (for example, \"J2:J201\"). + include_values: Include stored cell values with formulas. + + Returns: + Formula read result payload. + """ + payload = ReadFormulasToolInput( + out_path=out_path, + sheet=sheet, + range=range, + include_values=include_values, + ) + work = functools.partial( + run_read_formulas_tool, + payload, + policy=policy, + ) + result = cast(ReadFormulasToolOutput, await anyio.to_thread.run_sync(work)) + return result + + read_formulas_tool = app.tool(name="exstruct_read_formulas") + read_formulas_tool(_read_formulas_tool) + async def _validate_input_tool(xlsx_path: str) -> ValidateInputToolOutput: """Handle input validation tool call. @@ -297,6 +426,74 @@ async def _validate_input_tool(xlsx_path: str) -> ValidateInputToolOutput: validate_tool = app.tool(name="exstruct_validate_input") validate_tool(_validate_input_tool) + async def _patch_tool( + xlsx_path: str, + ops: list[dict[str, Any] | str], + out_dir: str | None = None, + out_name: str | None = None, + on_conflict: OnConflictPolicy | None = None, + auto_formula: bool = False, + dry_run: bool = False, + return_inverse_ops: bool = False, + preflight_formula_check: bool = False, + ) -> PatchToolOutput: + """Edit an Excel workbook by applying patch operations. + + Supports cell value updates, formula updates, and adding new sheets. + Operations are applied atomically: all succeed or none are saved. + + Args: + xlsx_path: Path to the Excel workbook to edit. + ops: Patch operations to apply in order. Preferred format is an + object list (one object per operation). For compatibility with + clients that cannot send object arrays, JSON object strings are + also accepted and normalized before validation. Each operation + has an 'op' field specifying the type: 'set_value' (set cell + value), 'set_formula' (set cell formula starting with '='), + 'add_sheet' (create new sheet), 'set_range_values' (bulk set + rectangular range), 'fill_formula' (fill formula across a + row/column), 'set_value_if' (conditional value update), + 'set_formula_if' (conditional formula update). + out_dir: Output directory. Defaults to same directory as input. + out_name: Output filename. Defaults to '{stem}_patched{ext}'. + on_conflict: Conflict policy when output file exists: + 'overwrite' (replace), 'skip' (do nothing), 'rename' (auto-rename). + Defaults to server --on-conflict setting. + auto_formula: When true, values starting with '=' in set_value ops + are treated as formulas instead of being rejected. + dry_run: When true, compute diff without saving changes. + return_inverse_ops: When true, return inverse (undo) operations. + preflight_formula_check: When true, scan formulas for errors + like #REF!, #NAME?, #DIV/0! before saving. + + Returns: + Patch result with output path, applied diffs, and any warnings. + """ + normalized_ops = _coerce_patch_ops(ops) + payload = PatchToolInput( + xlsx_path=xlsx_path, + ops=normalized_ops, + out_dir=out_dir, + out_name=out_name, + on_conflict=on_conflict, + auto_formula=auto_formula, + dry_run=dry_run, + return_inverse_ops=return_inverse_ops, + preflight_formula_check=preflight_formula_check, + ) + effective_on_conflict = on_conflict or default_on_conflict + work = functools.partial( + run_patch_tool, + payload, + policy=policy, + on_conflict=effective_on_conflict, + ) + result = cast(PatchToolOutput, await anyio.to_thread.run_sync(work)) + return result + + patch_tool = app.tool(name="exstruct_patch") + patch_tool(_patch_tool) + def _coerce_filter(filter_data: dict[str, Any] | None) -> dict[str, Any] | None: """Normalize filter input for chunk reading. @@ -310,3 +507,68 @@ def _coerce_filter(filter_data: dict[str, Any] | None) -> dict[str, Any] | None: if not filter_data: return None return dict(filter_data) + + +def _coerce_patch_ops(ops_data: list[dict[str, Any] | str]) -> list[dict[str, Any]]: + """Normalize patch operations payload for MCP clients. + + Args: + ops_data: Raw operations from MCP tool input. + + Returns: + Patch operations as object list. + + Raises: + ValueError: If a string op is not valid JSON object. + """ + normalized_ops: list[dict[str, Any]] = [] + for index, raw_op in enumerate(ops_data): + if isinstance(raw_op, dict): + normalized_ops.append(dict(raw_op)) + continue + normalized_ops.append(_parse_patch_op_json(raw_op, index)) + return normalized_ops + + +def _parse_patch_op_json(raw_op: str, index: int) -> dict[str, Any]: + """Parse a JSON string patch operation into object form. + + Args: + raw_op: Raw JSON string for one patch operation. + index: Source index in the ops list. + + Returns: + Parsed patch operation object. + + Raises: + ValueError: If the string is not valid JSON object. + """ + text = raw_op.strip() + if not text: + raise ValueError(_build_patch_op_error_message(index, "empty string")) + try: + parsed = json.loads(text) + except json.JSONDecodeError as exc: + raise ValueError(_build_patch_op_error_message(index, "invalid JSON")) from exc + if not isinstance(parsed, dict): + raise ValueError( + _build_patch_op_error_message(index, "JSON value must be an object") + ) + return cast(dict[str, Any], parsed) + + +def _build_patch_op_error_message(index: int, reason: str) -> str: + """Build a consistent validation message for invalid patch ops. + + Args: + index: Source index in the ops list. + reason: Validation failure reason. + + Returns: + Human-readable error message. + """ + example = '{"op":"set_value","sheet":"Sheet1","cell":"A1","value":"sample"}' + return ( + f"Invalid patch operation at ops[{index}]: {reason}. " + f"Use object form like {example}." + ) diff --git a/src/exstruct/mcp/sheet_reader.py b/src/exstruct/mcp/sheet_reader.py new file mode 100644 index 0000000..64b154d --- /dev/null +++ b/src/exstruct/mcp/sheet_reader.py @@ -0,0 +1,472 @@ +from __future__ import annotations + +import json +from pathlib import Path +import re +from typing import Any, TypeAlias, cast + +from pydantic import BaseModel, Field + +from .io import PathPolicy + +JsonScalar: TypeAlias = str | int | float | bool | None +CellCoordinate: TypeAlias = tuple[int, int] +RangeCoordinate: TypeAlias = tuple[int, int, int, int] + +_CELL_RE = re.compile(r"^([A-Za-z]+)([1-9][0-9]*)$") + + +class CellReadItem(BaseModel): + """Cell read result item.""" + + cell: str + value: JsonScalar = None + formula: str | None = None + + +class FormulaReadItem(BaseModel): + """Formula read result item.""" + + cell: str + formula: str + value: JsonScalar = None + + +class ReadRangeRequest(BaseModel): + """Input model for range reading.""" + + out_path: Path + sheet: str | None = None + range: str + include_formulas: bool = False + include_empty: bool = True + max_cells: int = Field(default=10_000, ge=1) + + +class ReadRangeResult(BaseModel): + """Output model for range reading.""" + + book_name: str | None = None + sheet_name: str + range: str + cells: list[CellReadItem] = Field(default_factory=list) + warnings: list[str] = Field(default_factory=list) + + +class ReadCellsRequest(BaseModel): + """Input model for cell list reading.""" + + out_path: Path + sheet: str | None = None + addresses: list[str] = Field(min_length=1) + include_formulas: bool = True + + +class ReadCellsResult(BaseModel): + """Output model for cell list reading.""" + + book_name: str | None = None + sheet_name: str + cells: list[CellReadItem] = Field(default_factory=list) + missing_cells: list[str] = Field(default_factory=list) + warnings: list[str] = Field(default_factory=list) + + +class ReadFormulasRequest(BaseModel): + """Input model for formula reading.""" + + out_path: Path + sheet: str | None = None + range: str | None = None + include_values: bool = False + + +class ReadFormulasResult(BaseModel): + """Output model for formula reading.""" + + book_name: str | None = None + sheet_name: str + range: str | None = None + formulas: list[FormulaReadItem] = Field(default_factory=list) + warnings: list[str] = Field(default_factory=list) + + +def read_range( + request: ReadRangeRequest, *, policy: PathPolicy | None = None +) -> ReadRangeResult: + """Read a rectangular range from extracted JSON. + + Args: + request: Range read request. + policy: Optional path policy for access control. + + Returns: + Range read result. + """ + resolved = _resolve_output_path(request.out_path, policy=policy) + data = _parse_json(_read_text(resolved)) + sheet_name, sheet_data = _select_sheet(data, request.sheet) + start_row, start_col, end_row, end_col = _parse_range(request.range) + normalized_range = _format_range(start_row, start_col, end_row, end_col) + cell_count = (end_row - start_row + 1) * (end_col - start_col + 1) + if cell_count > request.max_cells: + raise ValueError( + "Requested range exceeds max_cells. " + f"range={normalized_range}, cells={cell_count}, max_cells={request.max_cells}" + ) + + value_map = _build_value_map(sheet_data) + formula_map, has_formula_map = _build_formula_map(sheet_data) + warnings: list[str] = [] + if request.include_formulas and not has_formula_map: + warnings.append( + "formulas_map is not available. Re-run exstruct_extract with mode='verbose' " + "to inspect formulas." + ) + + items: list[CellReadItem] = [] + for row in range(start_row, end_row + 1): + for col in range(start_col, end_col + 1): + coord = (row, col) + value = value_map.get(coord) + formula = formula_map.get(coord) if request.include_formulas else None + if not request.include_empty and value is None and formula is None: + continue + items.append( + CellReadItem( + cell=_format_cell(row, col), + value=value, + formula=formula, + ) + ) + + return ReadRangeResult( + book_name=_as_optional_str(data.get("book_name")), + sheet_name=sheet_name, + range=normalized_range, + cells=items, + warnings=warnings, + ) + + +def read_cells( + request: ReadCellsRequest, *, policy: PathPolicy | None = None +) -> ReadCellsResult: + """Read specific cells from extracted JSON. + + Args: + request: Cell list read request. + policy: Optional path policy for access control. + + Returns: + Cell list read result. + """ + resolved = _resolve_output_path(request.out_path, policy=policy) + data = _parse_json(_read_text(resolved)) + sheet_name, sheet_data = _select_sheet(data, request.sheet) + value_map = _build_value_map(sheet_data) + formula_map, has_formula_map = _build_formula_map(sheet_data) + + warnings: list[str] = [] + if request.include_formulas and not has_formula_map: + warnings.append( + "formulas_map is not available. Re-run exstruct_extract with mode='verbose' " + "to inspect formulas." + ) + + items: list[CellReadItem] = [] + missing_cells: list[str] = [] + for address in request.addresses: + row, col = _parse_cell(address) + normalized = _format_cell(row, col) + coord = (row, col) + value = value_map.get(coord) + formula = formula_map.get(coord) if request.include_formulas else None + if value is None and formula is None: + missing_cells.append(normalized) + items.append(CellReadItem(cell=normalized, value=value, formula=formula)) + + return ReadCellsResult( + book_name=_as_optional_str(data.get("book_name")), + sheet_name=sheet_name, + cells=items, + missing_cells=missing_cells, + warnings=warnings, + ) + + +def read_formulas( + request: ReadFormulasRequest, *, policy: PathPolicy | None = None +) -> ReadFormulasResult: + """Read formulas from extracted JSON. + + Args: + request: Formula read request. + policy: Optional path policy for access control. + + Returns: + Formula read result. + """ + resolved = _resolve_output_path(request.out_path, policy=policy) + data = _parse_json(_read_text(resolved)) + sheet_name, sheet_data = _select_sheet(data, request.sheet) + value_map = _build_value_map(sheet_data) + formula_map, has_formula_map = _build_formula_map(sheet_data) + + range_filter: RangeCoordinate | None = None + normalized_range: str | None = None + if request.range is not None: + range_filter = _parse_range(request.range) + normalized_range = _format_range(*range_filter) + + warnings: list[str] = [] + if not has_formula_map: + warnings.append( + "formulas_map is not available. Re-run exstruct_extract with mode='verbose' " + "to inspect formulas." + ) + return ReadFormulasResult( + book_name=_as_optional_str(data.get("book_name")), + sheet_name=sheet_name, + range=normalized_range, + formulas=[], + warnings=warnings, + ) + + items: list[FormulaReadItem] = [] + for row, col in sorted(formula_map): + if range_filter is not None and not _in_range((row, col), range_filter): + continue + coord = (row, col) + items.append( + FormulaReadItem( + cell=_format_cell(row, col), + formula=formula_map[coord], + value=value_map.get(coord) if request.include_values else None, + ) + ) + + return ReadFormulasResult( + book_name=_as_optional_str(data.get("book_name")), + sheet_name=sheet_name, + range=normalized_range, + formulas=items, + warnings=warnings, + ) + + +def _resolve_output_path(path: Path, *, policy: PathPolicy | None) -> Path: + """Resolve and validate output path.""" + resolved = policy.ensure_allowed(path) if policy else path.resolve() + if not resolved.exists(): + raise FileNotFoundError(f"Output file not found: {resolved}") + if not resolved.is_file(): + raise ValueError(f"Output path is not a file: {resolved}") + return resolved + + +def _read_text(path: Path) -> str: + """Read UTF-8 text from file.""" + return path.read_text(encoding="utf-8") + + +def _parse_json(text: str) -> dict[str, Any]: + """Parse JSON text into object root.""" + parsed = json.loads(text) + if not isinstance(parsed, dict): + raise ValueError("Invalid workbook JSON: expected object at root.") + return cast(dict[str, Any], parsed) + + +def _select_sheet( + data: dict[str, Any], sheet: str | None +) -> tuple[str, dict[str, Any]]: + """Select sheet payload from workbook object.""" + sheets = data.get("sheets") + if not isinstance(sheets, dict): + raise ValueError("Invalid workbook JSON: sheets is not a mapping.") + if sheet is not None: + selected = sheets.get(sheet) + if not isinstance(selected, dict): + raise ValueError(f"Sheet not found: {sheet}") + return sheet, selected + if len(sheets) == 1: + only_name = next(iter(sheets.keys())) + only_payload = sheets[only_name] + if not isinstance(only_payload, dict): + raise ValueError("Invalid workbook JSON: sheet payload is not an object.") + return str(only_name), only_payload + names = ", ".join(str(name) for name in sheets.keys()) + raise ValueError( + "Sheet is required when multiple sheets exist. " + f"Available sheets: {names}. " + "Retry with `sheet=''`." + ) + + +def _parse_cell(address: str) -> CellCoordinate: + """Parse A1 cell address into row/column tuple (1-based).""" + match = _CELL_RE.fullmatch(address.strip()) + if match is None: + raise ValueError(f"Invalid A1 cell address: {address}") + col_label, row_text = match.groups() + row = int(row_text) + col = _alpha_to_col(col_label) + return row, col + + +def _parse_range(range_text: str) -> RangeCoordinate: + """Parse A1 range text into start/end tuple.""" + cleaned = range_text.strip() + if ":" in cleaned: + left, right = cleaned.split(":", maxsplit=1) + start_row, start_col = _parse_cell(left) + end_row, end_col = _parse_cell(right) + else: + start_row, start_col = _parse_cell(cleaned) + end_row, end_col = start_row, start_col + if start_row > end_row or start_col > end_col: + raise ValueError(f"Invalid A1 range: {range_text}") + return start_row, start_col, end_row, end_col + + +def _build_value_map(sheet_data: dict[str, Any]) -> dict[CellCoordinate, JsonScalar]: + """Build coordinate->value map from sheet rows.""" + rows = sheet_data.get("rows") + if not isinstance(rows, list): + return {} + value_map: dict[CellCoordinate, JsonScalar] = {} + for row_payload in rows: + if not isinstance(row_payload, dict): + continue + row_index = row_payload.get("r") + if not isinstance(row_index, int) or row_index <= 0: + continue + cols = row_payload.get("c") + if not isinstance(cols, dict): + continue + for key, raw_value in cols.items(): + if not isinstance(key, str): + continue + col_index = _parse_col_key(key) + if col_index is None: + continue + value = _normalize_scalar(raw_value) + value_map[(row_index, col_index)] = value + return value_map + + +def _build_formula_map( + sheet_data: dict[str, Any], +) -> tuple[dict[CellCoordinate, str], bool]: + """Build coordinate->formula map from formulas_map.""" + formulas_raw = sheet_data.get("formulas_map") + if formulas_raw is None: + return {}, False + if not isinstance(formulas_raw, dict): + return {}, False + formula_map: dict[CellCoordinate, str] = {} + for formula, positions in formulas_raw.items(): + if not isinstance(formula, str): + continue + if not isinstance(positions, list): + continue + for position in positions: + parsed = _parse_formula_position(position) + if parsed is None: + continue + formula_map[parsed] = formula + return formula_map, True + + +def _parse_formula_position(position: object) -> CellCoordinate | None: + """Parse formulas_map position into row/column tuple.""" + if not isinstance(position, list | tuple): + return None + if len(position) != 2: + return None + row = position[0] + col = position[1] + if not isinstance(row, int) or not isinstance(col, int): + return None + if row <= 0 or col < 0: + return None + return row, col + 1 + + +def _parse_col_key(key: str) -> int | None: + """Parse cell column key into 1-based column index.""" + stripped = key.strip() + try: + zero_based = int(stripped) + except (TypeError, ValueError): + pass + else: + if zero_based < 0: + return None + return zero_based + 1 + normalized = stripped.upper() + if not normalized.isalpha(): + return None + return _alpha_to_col(normalized) + + +def _alpha_to_col(label: str) -> int: + """Convert alphabetic column label to 1-based index.""" + acc = 0 + for char in label.strip().upper(): + code = ord(char) + if code < ord("A") or code > ord("Z"): + raise ValueError(f"Invalid column label: {label}") + acc = acc * 26 + (code - ord("A") + 1) + return acc + + +def _col_to_alpha(col_index: int) -> str: + """Convert 1-based column index to alphabetic column label.""" + if col_index <= 0: + raise ValueError(f"Invalid column index: {col_index}") + current = col_index + chars: list[str] = [] + while current > 0: + current -= 1 + chars.append(chr(ord("A") + (current % 26))) + current //= 26 + return "".join(reversed(chars)) + + +def _format_cell(row: int, col: int) -> str: + """Format row/column as A1 cell address.""" + return f"{_col_to_alpha(col)}{row}" + + +def _format_range(start_row: int, start_col: int, end_row: int, end_col: int) -> str: + """Format start/end coordinates as A1 range.""" + start = _format_cell(start_row, start_col) + end = _format_cell(end_row, end_col) + if start == end: + return start + return f"{start}:{end}" + + +def _in_range(cell: CellCoordinate, bounds: RangeCoordinate) -> bool: + """Check whether cell is included in range bounds.""" + row, col = cell + start_row, start_col, end_row, end_col = bounds + return start_row <= row <= end_row and start_col <= col <= end_col + + +def _normalize_scalar(value: object) -> JsonScalar: + """Normalize arbitrary JSON value to scalar.""" + if isinstance(value, str | int | float | bool) or value is None: + return value + return str(value) + + +def _as_optional_str(value: object) -> str | None: + """Convert object to optional string.""" + if value is None: + return None + if isinstance(value, str): + return value + return str(value) diff --git a/src/exstruct/mcp/tools.py b/src/exstruct/mcp/tools.py index c857b22..cd2377e 100644 --- a/src/exstruct/mcp/tools.py +++ b/src/exstruct/mcp/tools.py @@ -22,6 +22,28 @@ run_extract, ) from .io import PathPolicy +from .patch_runner import ( + FormulaIssue, + PatchDiffItem, + PatchErrorDetail, + PatchOp, + PatchRequest, + PatchResult, + run_patch, +) +from .sheet_reader import ( + CellReadItem, + FormulaReadItem, + ReadCellsRequest, + ReadCellsResult, + ReadFormulasRequest, + ReadFormulasResult, + ReadRangeRequest, + ReadRangeResult, + read_cells, + read_formulas, + read_range, +) from .validate_input import ( ValidateInputRequest, ValidateInputResult, @@ -68,6 +90,65 @@ class ReadJsonChunkToolOutput(BaseModel): warnings: list[str] = Field(default_factory=list) +class ReadRangeToolInput(BaseModel): + """MCP tool input for range reading.""" + + out_path: str + sheet: str | None = None + range: str = Field(...) # noqa: A003 + include_formulas: bool = False + include_empty: bool = True + max_cells: int = Field(default=10_000, ge=1) + + +class ReadRangeToolOutput(BaseModel): + """MCP tool output for range reading.""" + + book_name: str | None = None + sheet_name: str + range: str # noqa: A003 + cells: list[CellReadItem] = Field(default_factory=list) + warnings: list[str] = Field(default_factory=list) + + +class ReadCellsToolInput(BaseModel): + """MCP tool input for cell list reading.""" + + out_path: str + sheet: str | None = None + addresses: list[str] = Field(min_length=1) + include_formulas: bool = True + + +class ReadCellsToolOutput(BaseModel): + """MCP tool output for cell list reading.""" + + book_name: str | None = None + sheet_name: str + cells: list[CellReadItem] = Field(default_factory=list) + missing_cells: list[str] = Field(default_factory=list) + warnings: list[str] = Field(default_factory=list) + + +class ReadFormulasToolInput(BaseModel): + """MCP tool input for formula reading.""" + + out_path: str + sheet: str | None = None + range: str | None = None # noqa: A003 + include_values: bool = False + + +class ReadFormulasToolOutput(BaseModel): + """MCP tool output for formula reading.""" + + book_name: str | None = None + sheet_name: str + range: str | None = None # noqa: A003 + formulas: list[FormulaReadItem] = Field(default_factory=list) + warnings: list[str] = Field(default_factory=list) + + class ValidateInputToolInput(BaseModel): """MCP tool input for validating Excel files.""" @@ -82,6 +163,31 @@ class ValidateInputToolOutput(BaseModel): errors: list[str] = Field(default_factory=list) +class PatchToolInput(BaseModel): + """MCP tool input for patching Excel files.""" + + xlsx_path: str + ops: list[PatchOp] + out_dir: str | None = None + out_name: str | None = None + on_conflict: OnConflictPolicy | None = None + auto_formula: bool = False + dry_run: bool = False + return_inverse_ops: bool = False + preflight_formula_check: bool = False + + +class PatchToolOutput(BaseModel): + """MCP tool output for patching Excel files.""" + + out_path: str + patch_diff: list[PatchDiffItem] = Field(default_factory=list) + inverse_ops: list[PatchOp] = Field(default_factory=list) + formula_issues: list[FormulaIssue] = Field(default_factory=list) + warnings: list[str] = Field(default_factory=list) + error: PatchErrorDetail | None = None + + def run_extract_tool( payload: ExtractToolInput, *, @@ -133,6 +239,74 @@ def run_read_json_chunk_tool( return _to_read_json_chunk_output(result) +def run_read_range_tool( + payload: ReadRangeToolInput, *, policy: PathPolicy | None = None +) -> ReadRangeToolOutput: + """Run the range read tool handler. + + Args: + payload: Tool input payload. + policy: Optional path policy for access control. + + Returns: + Tool output payload. + """ + request = ReadRangeRequest( + out_path=Path(payload.out_path), + sheet=payload.sheet, + range=payload.range, + include_formulas=payload.include_formulas, + include_empty=payload.include_empty, + max_cells=payload.max_cells, + ) + result = read_range(request, policy=policy) + return _to_read_range_tool_output(result) + + +def run_read_cells_tool( + payload: ReadCellsToolInput, *, policy: PathPolicy | None = None +) -> ReadCellsToolOutput: + """Run the cell list read tool handler. + + Args: + payload: Tool input payload. + policy: Optional path policy for access control. + + Returns: + Tool output payload. + """ + request = ReadCellsRequest( + out_path=Path(payload.out_path), + sheet=payload.sheet, + addresses=payload.addresses, + include_formulas=payload.include_formulas, + ) + result = read_cells(request, policy=policy) + return _to_read_cells_tool_output(result) + + +def run_read_formulas_tool( + payload: ReadFormulasToolInput, *, policy: PathPolicy | None = None +) -> ReadFormulasToolOutput: + """Run the formulas read tool handler. + + Args: + payload: Tool input payload. + policy: Optional path policy for access control. + + Returns: + Tool output payload. + """ + request = ReadFormulasRequest( + out_path=Path(payload.out_path), + sheet=payload.sheet, + range=payload.range, + include_values=payload.include_values, + ) + result = read_formulas(request, policy=policy) + return _to_read_formulas_tool_output(result) + + def run_validate_input_tool( payload: ValidateInputToolInput, *, policy: PathPolicy | None = None ) -> ValidateInputToolOutput: @@ -150,6 +324,37 @@ def run_validate_input_tool( return _to_validate_input_output(result) +def run_patch_tool( + payload: PatchToolInput, + *, + policy: PathPolicy | None = None, + on_conflict: OnConflictPolicy | None = None, +) -> PatchToolOutput: + """Run the patch tool handler. + + Args: + payload: Tool input payload. + policy: Optional path policy for access control. + on_conflict: Optional conflict policy override. + + Returns: + Tool output payload. + """ + request = PatchRequest( + xlsx_path=Path(payload.xlsx_path), + ops=payload.ops, + out_dir=Path(payload.out_dir) if payload.out_dir else None, + out_name=payload.out_name, + on_conflict=payload.on_conflict or on_conflict or "overwrite", + auto_formula=payload.auto_formula, + dry_run=payload.dry_run, + return_inverse_ops=payload.return_inverse_ops, + preflight_formula_check=payload.preflight_formula_check, + ) + result = run_patch(request, policy=policy) + return _to_patch_tool_output(result) + + def _to_tool_output(result: ExtractResult) -> ExtractToolOutput: """Convert internal result to tool output model. @@ -185,6 +390,62 @@ def _to_read_json_chunk_output( ) +def _to_read_range_tool_output(result: ReadRangeResult) -> ReadRangeToolOutput: + """Convert internal result to range tool output. + + Args: + result: Internal range read result. + + Returns: + Tool output payload. + """ + return ReadRangeToolOutput( + book_name=result.book_name, + sheet_name=result.sheet_name, + range=result.range, + cells=result.cells, + warnings=result.warnings, + ) + + +def _to_read_cells_tool_output(result: ReadCellsResult) -> ReadCellsToolOutput: + """Convert internal result to cell list tool output. + + Args: + result: Internal cell list read result. + + Returns: + Tool output payload. + """ + return ReadCellsToolOutput( + book_name=result.book_name, + sheet_name=result.sheet_name, + cells=result.cells, + missing_cells=result.missing_cells, + warnings=result.warnings, + ) + + +def _to_read_formulas_tool_output( + result: ReadFormulasResult, +) -> ReadFormulasToolOutput: + """Convert internal result to formulas tool output. + + Args: + result: Internal formula read result. + + Returns: + Tool output payload. + """ + return ReadFormulasToolOutput( + book_name=result.book_name, + sheet_name=result.sheet_name, + range=result.range, + formulas=result.formulas, + warnings=result.warnings, + ) + + def _to_validate_input_output( result: ValidateInputResult, ) -> ValidateInputToolOutput: @@ -201,3 +462,22 @@ def _to_validate_input_output( warnings=result.warnings, errors=result.errors, ) + + +def _to_patch_tool_output(result: PatchResult) -> PatchToolOutput: + """Convert internal result to patch tool output. + + Args: + result: Internal patch result. + + Returns: + Tool output payload. + """ + return PatchToolOutput( + out_path=result.out_path, + patch_diff=result.patch_diff, + inverse_ops=result.inverse_ops, + formula_issues=result.formula_issues, + warnings=result.warnings, + error=result.error, + ) diff --git a/src/exstruct/models/__init__.py b/src/exstruct/models/__init__.py index b25e7a5..e9e8d3f 100644 --- a/src/exstruct/models/__init__.py +++ b/src/exstruct/models/__init__.py @@ -3,7 +3,7 @@ from collections.abc import Generator import json from pathlib import Path -from typing import Literal +from typing import Literal, TypeVar from pydantic import BaseModel, ConfigDict, Field @@ -194,6 +194,13 @@ class SheetData(BaseModel): merged_cells: MergedCells | None = Field( default=None, description="Merged cell ranges on the sheet." ) + merged_ranges: list[str] = Field( + default_factory=list, + description=( + "Merged ranges in A1 notation (e.g., 'A1:C3'). " + "Used in alpha_col-oriented output." + ), + ) def _as_payload(self) -> dict[str, object]: from ..io import dict_without_empty_values @@ -408,3 +415,151 @@ def save( case _: raise ValueError(f"Unsupported export format: {fmt}") return dest + + +# --------------------------------------------------------------------------- +# Column index ↔ Excel alpha-style column name conversion +# --------------------------------------------------------------------------- + + +def col_index_to_alpha(index: int) -> str: + """Convert a 0-based column index to an Excel-style column name. + + Args: + index: 0-based column index (0 → A, 25 → Z, 26 → AA, …). + + Returns: + Excel column name string. + + Raises: + ValueError: If index is negative. + + Examples: + >>> col_index_to_alpha(0) + 'A' + >>> col_index_to_alpha(25) + 'Z' + >>> col_index_to_alpha(26) + 'AA' + """ + if index < 0: + raise ValueError(f"Column index must be non-negative, got {index}") + result: list[str] = [] + num = index + while True: + num, remainder = divmod(num, 26) + result.append(chr(65 + remainder)) + if num == 0: + break + num -= 1 + result.reverse() + return "".join(result) + + +def convert_row_keys_to_alpha(row: CellRow) -> CellRow: + """Return a new CellRow with numeric column keys converted to ABC-style. + + Non-numeric keys are preserved unchanged. + + Args: + row: Original CellRow with 0-based numeric string keys. + + Returns: + New CellRow with alpha column keys. + """ + new_c = _convert_mapping_keys_to_alpha(row.c, row_index=row.r, field_name="c") + + new_links: dict[str, str] | None = None + if row.links: + new_links = _convert_mapping_keys_to_alpha( + row.links, row_index=row.r, field_name="links" + ) + + return CellRow(r=row.r, c=new_c, links=new_links) + + +def convert_sheet_keys_to_alpha(sheet: SheetData) -> SheetData: + """Return a new SheetData with all CellRow column keys converted to ABC-style. + + Args: + sheet: Original SheetData. + + Returns: + New SheetData with alpha column keys in every row. + """ + new_rows = [convert_row_keys_to_alpha(row) for row in sheet.rows] + updated_fields: dict[str, object] = {"rows": new_rows} + if sheet.merged_cells is not None: + updated_fields["merged_ranges"] = _merged_items_to_a1_ranges( + sheet.merged_cells.items + ) + updated_fields["merged_cells"] = None + return sheet.model_copy(update=updated_fields) + + +def convert_workbook_keys_to_alpha(workbook: WorkbookData) -> WorkbookData: + """Return a new WorkbookData with all CellRow column keys converted to ABC-style. + + Args: + workbook: Original WorkbookData. + + Returns: + New WorkbookData with alpha column keys in every sheet. + """ + new_sheets = { + name: convert_sheet_keys_to_alpha(sheet) + for name, sheet in workbook.sheets.items() + } + return workbook.model_copy(update={"sheets": new_sheets}) + + +def _alpha_key(key: str) -> str: + """Convert a numeric string key to alpha, passing non-numeric keys through.""" + try: + return col_index_to_alpha(int(key)) + except ValueError: + return key + + +MapValue = TypeVar("MapValue") + + +def _convert_mapping_keys_to_alpha( + source: dict[str, MapValue], *, row_index: int, field_name: str +) -> dict[str, MapValue]: + """Convert mapping keys to alpha-style and reject collisions. + + Args: + source: Original key-value mapping. + row_index: 1-based row index for error context. + field_name: Field name ("c" or "links") for error context. + + Returns: + Converted mapping with alpha-style keys. + + Raises: + ValueError: If multiple source keys map to the same alpha key. + """ + converted: dict[str, MapValue] = {} + for original_key, value in source.items(): + alpha_key = _alpha_key(original_key) + if alpha_key in converted: + raise ValueError( + "Column key collision after alpha conversion in " + f"row {row_index} ({field_name}): " + f"{original_key!r} -> {alpha_key!r}" + ) + converted[alpha_key] = value + return converted + + +def _merged_items_to_a1_ranges( + items: list[tuple[int, int, int, int, str]], +) -> list[str]: + """Convert merged cell tuples to A1 range strings.""" + ranges: list[str] = [] + for r1, c1, r2, c2, _value in items: + start = f"{col_index_to_alpha(c1)}{r1}" + end = f"{col_index_to_alpha(c2)}{r2}" + ranges.append(f"{start}:{end}") + return ranges diff --git a/tests/cli/test_cli_alpha_col.py b/tests/cli/test_cli_alpha_col.py new file mode 100644 index 0000000..24f9c40 --- /dev/null +++ b/tests/cli/test_cli_alpha_col.py @@ -0,0 +1,78 @@ +"""Tests for the --alpha-col CLI flag.""" + +from __future__ import annotations + +import json +from pathlib import Path + +from openpyxl import Workbook + +from exstruct.cli.main import main as cli_main + + +def _create_workbook(tmp_path: Path) -> Path: + """Create a minimal Excel workbook for CLI tests. + + Args: + tmp_path: Temporary directory provided by pytest. + + Returns: + Path to the generated workbook. + """ + wb = Workbook() + ws = wb.active + ws.title = "Sheet1" + ws.append(["Hello", "World"]) + ws.append([1, 2]) + dest = tmp_path / "alpha_test.xlsx" + wb.save(dest) + wb.close() + return dest + + +def test_cli_alpha_col_flag(tmp_path: Path) -> None: + """--alpha-col should produce ABC-style column keys in output.""" + xlsx = _create_workbook(tmp_path) + out = tmp_path / "out.json" + code = cli_main([str(xlsx), "-o", str(out), "--alpha-col", "--pretty"]) + assert code == 0 + data = json.loads(out.read_text(encoding="utf-8")) + rows = data["sheets"]["Sheet1"]["rows"] + # With alpha_col, column keys should be A, B, ... not 0, 1, ... + for row in rows: + cell_keys = set(row["c"].keys()) + assert "A" in cell_keys + assert "B" in cell_keys + assert "0" not in cell_keys + + +def test_cli_without_alpha_col_flag(tmp_path: Path) -> None: + """Without --alpha-col, column keys should remain 0-based numeric.""" + xlsx = _create_workbook(tmp_path) + out = tmp_path / "out.json" + code = cli_main([str(xlsx), "-o", str(out), "--pretty"]) + assert code == 0 + data = json.loads(out.read_text(encoding="utf-8")) + rows = data["sheets"]["Sheet1"]["rows"] + for row in rows: + cell_keys = set(row["c"].keys()) + assert "0" in cell_keys + assert "A" not in cell_keys + + +def test_cli_alpha_col_parser_present() -> None: + """The --alpha-col flag should be accepted by the parser.""" + from exstruct.cli.main import build_parser + + parser = build_parser() + args = parser.parse_args(["dummy.xlsx", "--alpha-col"]) + assert args.alpha_col is True + + +def test_cli_alpha_col_default_false() -> None: + """Without --alpha-col, default should be False.""" + from exstruct.cli.main import build_parser + + parser = build_parser() + args = parser.parse_args(["dummy.xlsx"]) + assert args.alpha_col is False diff --git a/tests/com/test_charts_extraction.py b/tests/com/test_charts_extraction.py index f83431c..88f53f3 100644 --- a/tests/com/test_charts_extraction.py +++ b/tests/com/test_charts_extraction.py @@ -1,3 +1,7 @@ +from __future__ import annotations + +from collections.abc import Iterator +from contextlib import contextmanager from pathlib import Path import pytest @@ -8,52 +12,54 @@ pytestmark = pytest.mark.com -def _ensure_excel() -> None: - try: - app = xw.App(add_book=False, visible=False) - app.quit() - except Exception: - pytest.skip("Excel COM is unavailable; skipping chart extraction tests.") - - -def _make_workbook_with_chart(path: Path) -> None: +@contextmanager +def _excel_app() -> Iterator[xw.App]: app = xw.App(add_book=False, visible=False) try: - wb = app.books.add() - sht = wb.sheets[0] - sht.name = "Sheet1" - sht.range("A1").value = ["Month", "Sales"] - sht.range("A2").value = [ - ["Jan", 100], - ["Feb", 120], - ["Mar", 150], - ["Apr", 180], - ] - - chart = sht.charts.add(left=200, top=50, width=300, height=200) - chart.chart_type = "column_clustered" - chart.set_source_data(sht.range("A1:B5")) - chart_name = chart.name - chart_com = sht.api.ChartObjects(chart_name).Chart - chart_com.HasTitle = True - chart_com.ChartTitle.Text = "Sales Chart" - y_axis = chart_com.Axes(2, 1) - y_axis.HasTitle = True - y_axis.AxisTitle.Text = "Amount" - y_axis.MinimumScale = 0 - y_axis.MaximumScale = 200 - - wb.save(str(path)) - wb.close() + yield app finally: try: app.quit() except Exception: - pass + try: + app.kill() + except Exception: + pass -def test_チャートの基本メタ情報が抽出される(tmp_path: Path) -> None: - _ensure_excel() +def _make_workbook_with_chart(path: Path) -> None: + with _excel_app() as app: + wb = app.books.add() + try: + sht = wb.sheets[0] + sht.name = "Sheet1" + sht.range("A1").value = ["Month", "Sales"] + sht.range("A2").value = [ + ["Jan", 100], + ["Feb", 120], + ["Mar", 150], + ["Apr", 180], + ] + + chart = sht.charts.add(left=200, top=50, width=300, height=200) + chart.chart_type = "column_clustered" + chart.set_source_data(sht.range("A1:B5")) + chart_name = chart.name + chart_com = sht.api.ChartObjects(chart_name).Chart + chart_com.HasTitle = True + chart_com.ChartTitle.Text = "Sales Chart" + y_axis = chart_com.Axes(2, 1) + y_axis.HasTitle = True + y_axis.AxisTitle.Text = "Amount" + y_axis.MinimumScale = 0 + y_axis.MaximumScale = 200 + + wb.save(str(path)) + finally: + wb.close() + + +def test_chart_basic_metadata(tmp_path: Path) -> None: path = tmp_path / "chart.xlsx" _make_workbook_with_chart(path) @@ -69,8 +75,7 @@ def test_チャートの基本メタ情報が抽出される(tmp_path: Path) -> assert ch.error is None -def test_系列情報が参照式として抽出される(tmp_path: Path) -> None: - _ensure_excel() +def test_chart_series_ranges(tmp_path: Path) -> None: path = tmp_path / "chart_series.xlsx" _make_workbook_with_chart(path) @@ -83,8 +88,7 @@ def test_系列情報が参照式として抽出される(tmp_path: Path) -> Non assert s.y_range is None or s.y_range.endswith("Sheet1!$B$2:$B$5") -def test_verboseでチャートのサイズが取得される(tmp_path: Path) -> None: - _ensure_excel() +def test_chart_verbose_size(tmp_path: Path) -> None: path = tmp_path / "chart_verbose.xlsx" _make_workbook_with_chart(path) diff --git a/tests/com/test_shapes_extraction.py b/tests/com/test_shapes_extraction.py index a9b7a98..6d9199c 100644 --- a/tests/com/test_shapes_extraction.py +++ b/tests/com/test_shapes_extraction.py @@ -1,3 +1,7 @@ +from __future__ import annotations + +from collections.abc import Iterator +from contextlib import contextmanager from pathlib import Path import pytest @@ -9,61 +13,64 @@ pytestmark = pytest.mark.com -def _ensure_excel() -> None: - try: - app = xw.App(add_book=False, visible=False) - app.quit() - except Exception: - pytest.skip("Excel COM is unavailable; skipping shapes extraction tests.") - - -def _make_workbook_with_shapes(path: Path) -> None: +@contextmanager +def _excel_app() -> Iterator[xw.App]: app = xw.App(add_book=False, visible=False) try: - wb = app.books.add() - sht = wb.sheets[0] - sht.name = "Sheet1" - - rect = sht.api.Shapes.AddShape(1, 50, 50, 120, 60) # msoShapeRectangle - rect.TextFrame2.TextRange.Text = "rect" - - _ = sht.api.Shapes.AddShape(5, 300, 50, 80, 40) # msoShapeOval (no text) - - line = sht.api.Shapes.AddLine(10, 10, 110, 10) - line.Line.EndArrowheadStyle = 3 # msoArrowheadTriangle - - outer = sht.api.Shapes.AddShape(1, 200, 200, 150, 100) - inner = sht.api.Shapes.AddShape(1, 230, 230, 80, 40) - inner.TextFrame2.TextRange.Text = "inner" - sht.api.Shapes.Range([outer.Name, inner.Name]).Group() - - # Add two rectangles and a connector that explicitly connects them - # so that ConnectorFormat.BeginConnectedShape / EndConnectedShape - # are populated by Excel. - src_shape = sht.api.Shapes.AddShape(1, 50, 150, 80, 40) - src_shape.TextFrame2.TextRange.Text = "src" - dst_shape = sht.api.Shapes.AddShape(1, 200, 150, 80, 40) - dst_shape.TextFrame2.TextRange.Text = "dst" - connector = sht.api.Shapes.AddConnector(1, 90, 170, 200, 170) - connector.Line.EndArrowheadStyle = 3 - try: - connector.ConnectorFormat.BeginConnect(src_shape, 1) - connector.ConnectorFormat.EndConnect(dst_shape, 1) - except Exception: - # In some environments connector wiring may fail; tests will - # simply not find connected shapes in that case. - pass - - wb.save(str(path)) - wb.close() + yield app finally: try: app.quit() except Exception: - pass + try: + app.kill() + except Exception: + app = None -def test_図形の種別とテキストが抽出される(tmp_path: Path) -> None: +def _make_workbook_with_shapes(path: Path) -> None: + with _excel_app() as app: + wb = app.books.add() + try: + sht = wb.sheets[0] + sht.name = "Sheet1" + + rect = sht.api.Shapes.AddShape(1, 50, 50, 120, 60) # msoShapeRectangle + rect.TextFrame2.TextRange.Text = "rect" + + _ = sht.api.Shapes.AddShape(5, 300, 50, 80, 40) # msoShapeOval (no text) + + line = sht.api.Shapes.AddLine(10, 10, 110, 10) + line.Line.EndArrowheadStyle = 3 # msoArrowheadTriangle + + outer = sht.api.Shapes.AddShape(1, 200, 200, 150, 100) + inner = sht.api.Shapes.AddShape(1, 230, 230, 80, 40) + inner.TextFrame2.TextRange.Text = "inner" + sht.api.Shapes.Range([outer.Name, inner.Name]).Group() + + # Add two rectangles and a connector that explicitly connects them + # so that ConnectorFormat.BeginConnectedShape / EndConnectedShape + # are populated by Excel. + src_shape = sht.api.Shapes.AddShape(1, 50, 150, 80, 40) + src_shape.TextFrame2.TextRange.Text = "src" + dst_shape = sht.api.Shapes.AddShape(1, 200, 150, 80, 40) + dst_shape.TextFrame2.TextRange.Text = "dst" + connector = sht.api.Shapes.AddConnector(1, 90, 170, 200, 170) + connector.Line.EndArrowheadStyle = 3 + try: + connector.ConnectorFormat.BeginConnect(src_shape, 1) + connector.ConnectorFormat.EndConnect(dst_shape, 1) + except Exception: + # In some environments connector wiring may fail; tests will + # simply not find connected shapes in that case. + connector = None + + wb.save(str(path)) + finally: + wb.close() + + +def test_shapes_basic(tmp_path: Path) -> None: """ Verifies extraction of shape types, texts, IDs, and uniqueness from a workbook containing various shapes. @@ -73,7 +80,6 @@ def test_図形の種別とテキストが抽出される(tmp_path: Path) -> Non - all emitted shape ids are unique; - no AutoShape without text is emitted in standard mode. """ - _ensure_excel() path = tmp_path / "shapes.xlsx" _make_workbook_with_shapes(path) @@ -100,13 +106,12 @@ def test_図形の種別とテキストが抽出される(tmp_path: Path) -> Non ) -def test_線図形の方向と矢印情報が抽出される(tmp_path: Path) -> None: +def test_line_direction(tmp_path: Path) -> None: """ Verifies that a line shape's direction and arrow style information is extracted correctly from a workbook. Creates a workbook containing shapes, extracts shapes from "Sheet1", finds an Arrow with a begin or end arrow style, and asserts its direction is "E". """ - _ensure_excel() path = tmp_path / "lines.xlsx" _make_workbook_with_shapes(path) @@ -122,8 +127,8 @@ def test_線図形の方向と矢印情報が抽出される(tmp_path: Path) -> assert line.direction == "E" -def test_コネクターの接続元と接続先が抽出される(tmp_path: Path) -> None: - _ensure_excel() +def test_connector_connections(tmp_path: Path) -> None: + """Verify connector begin/end IDs match emitted shape IDs.""" path = tmp_path / "connectors.xlsx" _make_workbook_with_shapes(path) diff --git a/tests/conftest.py b/tests/conftest.py index eab10f2..e48c556 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,6 +3,7 @@ from functools import lru_cache import importlib.util import os +import re import sys import pytest @@ -13,8 +14,23 @@ RUN_RENDER_SMOKE = os.getenv("RUN_RENDER_SMOKE") == "1" +def _markexpr_requests_com(markexpr: str) -> bool: + """Return True when markexpr explicitly requests the ``com`` marker.""" + tokens = re.findall(r"[A-Za-z_][A-Za-z0-9_]*", markexpr.lower()) + for index, token in enumerate(tokens): + if token != "com": + continue + prev = tokens[index - 1] if index > 0 else "" + if prev != "not": + return True + return False + + def pytest_configure(config: pytest.Config) -> None: """Register custom markers to avoid pytest warnings.""" + markexpr = getattr(config.option, "markexpr", "") or "" + if _markexpr_requests_com(markexpr): + os.environ.pop("SKIP_COM_TESTS", None) config.addinivalue_line("markers", "com: requires Excel COM (Windows + Excel).") config.addinivalue_line( "markers", @@ -41,6 +57,12 @@ def _has_pdfium() -> bool: return importlib.util.find_spec("pypdfium2") is not None +@lru_cache(maxsize=1) +def _has_pillow() -> bool: + """Return True if Pillow (PIL) is importable.""" + return importlib.util.find_spec("PIL") is not None + + def _com_skip_reason() -> str | None: """ Return a skip reason for COM-marked tests, or None when they should run. @@ -71,6 +93,8 @@ def _render_skip_reason() -> str | None: return reason if not _has_pdfium(): return "pypdfium2 is unavailable." + if not _has_pillow(): + return "Pillow (PIL) is unavailable." return None @@ -85,3 +109,23 @@ def pytest_runtest_setup(item: pytest.Item) -> None: reason = _com_skip_reason() if reason: pytest.skip(reason) + + +@pytest.fixture(autouse=True) # type: ignore[misc] +def _skip_com_for_non_com_tests( + request: pytest.FixtureRequest, monkeypatch: pytest.MonkeyPatch +) -> None: + """Disable COM usage for tests that are not marked as COM/render.""" + node_path = str(request.node.path) + markexpr = getattr(request.config.option, "markexpr", "") or "" + if _markexpr_requests_com(markexpr): + monkeypatch.delenv("SKIP_COM_TESTS", raising=False) + return + if "tests\\com\\" in node_path or "tests/com/" in node_path: + monkeypatch.delenv("SKIP_COM_TESTS", raising=False) + return + if request.node.get_closest_marker("render") is not None: + return + if request.node.get_closest_marker("com") is not None: + return + monkeypatch.setenv("SKIP_COM_TESTS", "1") diff --git a/tests/engine/test_engine_alpha_col.py b/tests/engine/test_engine_alpha_col.py new file mode 100644 index 0000000..46b2126 --- /dev/null +++ b/tests/engine/test_engine_alpha_col.py @@ -0,0 +1,71 @@ +"""Tests for alpha_col option through the engine and public API layers.""" + +from __future__ import annotations + +import json +from pathlib import Path + +import pytest + +from exstruct.engine import ExStructEngine, StructOptions +from exstruct.models import CellRow, MergedCells, SheetData, WorkbookData + + +def _fake_workbook(path: Path, **kwargs: object) -> WorkbookData: + """Return a minimal WorkbookData with numeric column keys. + + Args: + path: Input path (unused but required by signature). + **kwargs: Extra keyword arguments accepted for forward-compatibility. + + Returns: + A WorkbookData with one sheet and one row. + """ + return WorkbookData( + book_name=path.name, + sheets={ + "Sheet1": SheetData( + rows=[ + CellRow(r=1, c={"0": "A1", "1": "B1", "25": "Z1"}, links=None), + ], + merged_cells=MergedCells(items=[(1, 0, 3, 2, "merged")]), + ) + }, + ) + + +def test_engine_alpha_col_false( + monkeypatch: pytest.MonkeyPatch, tmp_path: Path +) -> None: + """With alpha_col=False, column keys remain numeric.""" + monkeypatch.setattr("exstruct.engine.extract_workbook", _fake_workbook) + engine = ExStructEngine(options=StructOptions(mode="light", alpha_col=False)) + result = engine.extract(tmp_path / "book.xlsx", mode="light") + row = result.sheets["Sheet1"].rows[0] + assert "0" in row.c + assert "1" in row.c + assert result.sheets["Sheet1"].merged_ranges == [] + + +def test_engine_alpha_col_true(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: + """With alpha_col=True, column keys are converted to ABC-style.""" + monkeypatch.setattr("exstruct.engine.extract_workbook", _fake_workbook) + engine = ExStructEngine(options=StructOptions(mode="light", alpha_col=True)) + result = engine.extract(tmp_path / "book.xlsx", mode="light") + row = result.sheets["Sheet1"].rows[0] + assert row.c == {"A": "A1", "B": "B1", "Z": "Z1"} + assert result.sheets["Sheet1"].merged_ranges == ["A1:C3"] + assert result.sheets["Sheet1"].merged_cells is None + + +def test_engine_serialize_alpha_col_includes_merged_ranges( + monkeypatch: pytest.MonkeyPatch, tmp_path: Path +) -> None: + """Serialized output should include merged_ranges when alpha_col=True.""" + monkeypatch.setattr("exstruct.engine.extract_workbook", _fake_workbook) + engine = ExStructEngine(options=StructOptions(mode="light", alpha_col=True)) + result = engine.extract(tmp_path / "book.xlsx", mode="light") + payload = json.loads(engine.serialize(result, fmt="json")) + sheet = payload["sheets"]["Sheet1"] + assert sheet["merged_ranges"] == ["A1:C3"] + assert "merged_cells" not in sheet diff --git a/tests/mcp/test_chunk_reader.py b/tests/mcp/test_chunk_reader.py index d1113cb..4741cd0 100644 --- a/tests/mcp/test_chunk_reader.py +++ b/tests/mcp/test_chunk_reader.py @@ -37,6 +37,15 @@ def test_read_json_chunk_raw_too_large(tmp_path: Path) -> None: read_json_chunk(request) +def test_read_json_chunk_raw_too_large_has_retry_hint(tmp_path: Path) -> None: + data = {"book_name": "book", "sheets": {"Sheet1": {"rows": []}}} + out = tmp_path / "out.json" + _write_json(out, data) + request = ReadJsonChunkRequest(out_path=out, max_bytes=10) + with pytest.raises(ValueError, match=r"Retry with `sheet` .* or `filter`"): + read_json_chunk(request) + + def test_read_json_chunk_with_filters(tmp_path: Path) -> None: data = { "book_name": "book", @@ -78,6 +87,19 @@ def test_read_json_chunk_requires_sheet(tmp_path: Path) -> None: read_json_chunk(request) +def test_read_json_chunk_requires_sheet_lists_available_names(tmp_path: Path) -> None: + data = {"book_name": "book", "sheets": {"A": {"rows": []}, "B": {"rows": []}}} + out = tmp_path / "out.json" + _write_json(out, data) + request = ReadJsonChunkRequest( + out_path=out, + max_bytes=10_000, + filter=ReadJsonChunkFilter(rows=(1, 1)), + ) + with pytest.raises(ValueError, match=r"Available sheets: A, B"): + read_json_chunk(request) + + def test_read_json_chunk_invalid_cursor(tmp_path: Path) -> None: data = {"book_name": "book", "sheets": {"Sheet1": {"rows": []}}} out = tmp_path / "out.json" @@ -188,6 +210,40 @@ def test_read_json_chunk_warns_on_col_filter_inversion(tmp_path: Path) -> None: assert any("Column filter ignored" in warning for warning in result.warnings) +def test_read_json_chunk_with_alpha_col_keys(tmp_path: Path) -> None: + data = { + "book_name": "book", + "sheets": { + "Sheet1": { + "rows": [ + {"r": 1, "c": {"A": "A1", "B": "B1", "AA": "AA1", "ab": "AB1"}}, + ] + } + }, + } + out = tmp_path / "out.json" + _write_json(out, data) + request = ReadJsonChunkRequest( + out_path=out, + sheet="Sheet1", + max_bytes=10_000, + filter=ReadJsonChunkFilter(cols=(1, 2)), + ) + result = read_json_chunk(request) + payload = json.loads(result.chunk) + assert payload["sheet"]["rows"][0]["c"] == {"A": "A1", "B": "B1"} + + request_aa = ReadJsonChunkRequest( + out_path=out, + sheet="Sheet1", + max_bytes=10_000, + filter=ReadJsonChunkFilter(cols=(27, 28)), + ) + result_aa = read_json_chunk(request_aa) + payload_aa = json.loads(result_aa.chunk) + assert payload_aa["sheet"]["rows"][0]["c"] == {"AA": "AA1", "ab": "AB1"} + + def test_read_json_chunk_warns_on_base_payload_exceeds_max_bytes( tmp_path: Path, ) -> None: diff --git a/tests/mcp/test_extract_alpha_col.py b/tests/mcp/test_extract_alpha_col.py new file mode 100644 index 0000000..45916c4 --- /dev/null +++ b/tests/mcp/test_extract_alpha_col.py @@ -0,0 +1,30 @@ +"""Tests for alpha_col support in MCP extract_runner.""" + +from __future__ import annotations + +from exstruct.mcp.extract_runner import ExtractOptions + + +def test_extract_options_alpha_col_default() -> None: + """alpha_col should default to True in MCP.""" + opts = ExtractOptions() + assert opts.alpha_col is True + + +def test_extract_options_alpha_col_true() -> None: + """alpha_col should be settable to True.""" + opts = ExtractOptions(alpha_col=True) + assert opts.alpha_col is True + + +def test_extract_options_alpha_col_false() -> None: + """alpha_col should be settable to False to opt out.""" + opts = ExtractOptions(alpha_col=False) + assert opts.alpha_col is False + + +def test_extract_options_from_dict() -> None: + """alpha_col should be parseable from a plain dict (MCP JSON payload).""" + opts = ExtractOptions.model_validate({"alpha_col": True, "pretty": True}) + assert opts.alpha_col is True + assert opts.pretty is True diff --git a/tests/mcp/test_patch_runner.py b/tests/mcp/test_patch_runner.py new file mode 100644 index 0000000..860ec8c --- /dev/null +++ b/tests/mcp/test_patch_runner.py @@ -0,0 +1,532 @@ +from __future__ import annotations + +from pathlib import Path + +from openpyxl import Workbook, load_workbook +from pydantic import ValidationError +import pytest + +from exstruct.cli.availability import ComAvailability +from exstruct.mcp import patch_runner +from exstruct.mcp.io import PathPolicy +from exstruct.mcp.patch_runner import PatchOp, PatchRequest, run_patch + + +def _create_workbook(path: Path) -> None: + workbook = Workbook() + sheet = workbook.active + sheet.title = "Sheet1" + sheet["A1"] = "old" + sheet["B1"] = 1 + workbook.save(path) + workbook.close() + + +def _disable_com(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setattr( + patch_runner, + "get_com_availability", + lambda: ComAvailability(available=False, reason="test"), + ) + + +def test_run_patch_set_value_and_formula( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + _disable_com(monkeypatch) + input_path = tmp_path / "book.xlsx" + _create_workbook(input_path) + ops = [ + PatchOp(op="set_value", sheet="Sheet1", cell="A1", value="new"), + PatchOp(op="set_formula", sheet="Sheet1", cell="B1", formula="=SUM(1,1)"), + ] + request = PatchRequest(xlsx_path=input_path, ops=ops, on_conflict="rename") + result = run_patch(request, policy=PathPolicy(root=tmp_path)) + assert Path(result.out_path).exists() + workbook = load_workbook(result.out_path) + try: + sheet = workbook["Sheet1"] + assert sheet["A1"].value == "new" + formula_value = sheet["B1"].value + if isinstance(formula_value, str) and not formula_value.startswith("="): + formula_value = f"={formula_value}" + assert formula_value == "=SUM(1,1)" + finally: + workbook.close() + assert len(result.patch_diff) == 2 + assert result.patch_diff[0].after is not None + + +def test_run_patch_add_sheet_and_set_value( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + _disable_com(monkeypatch) + input_path = tmp_path / "book.xlsx" + _create_workbook(input_path) + ops = [ + PatchOp(op="add_sheet", sheet="NewSheet"), + PatchOp(op="set_value", sheet="NewSheet", cell="A1", value="ok"), + ] + request = PatchRequest(xlsx_path=input_path, ops=ops, on_conflict="rename") + result = run_patch(request, policy=PathPolicy(root=tmp_path)) + workbook = load_workbook(result.out_path) + try: + assert "NewSheet" in workbook.sheetnames + assert workbook["NewSheet"]["A1"].value == "ok" + finally: + workbook.close() + + +def test_run_patch_add_sheet_rejects_duplicate( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + _disable_com(monkeypatch) + input_path = tmp_path / "book.xlsx" + _create_workbook(input_path) + ops = [PatchOp(op="add_sheet", sheet="Sheet1")] + request = PatchRequest(xlsx_path=input_path, ops=ops, on_conflict="rename") + result = run_patch(request, policy=PathPolicy(root=tmp_path)) + assert result.error is not None + assert result.error.op_index == 0 + + +def test_patch_op_allows_value_starting_with_equal() -> None: + op = PatchOp(op="set_value", sheet="Sheet1", cell="A1", value="=SUM(1,1)") + assert op.value == "=SUM(1,1)" + + +def test_run_patch_rejects_formula_without_equal() -> None: + with pytest.raises(ValidationError): + PatchOp(op="set_formula", sheet="Sheet1", cell="A1", formula="SUM(1,1)") + + +def test_run_patch_set_value_with_equal_requires_auto_formula( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + _disable_com(monkeypatch) + input_path = tmp_path / "book.xlsx" + _create_workbook(input_path) + ops = [PatchOp(op="set_value", sheet="Sheet1", cell="A1", value="=SUM(1,1)")] + request = PatchRequest(xlsx_path=input_path, ops=ops, on_conflict="rename") + result = run_patch(request, policy=PathPolicy(root=tmp_path)) + assert result.error is not None + assert "rejects values starting with" in result.error.message + + +def test_run_patch_set_value_with_equal_auto_formula( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + _disable_com(monkeypatch) + input_path = tmp_path / "book.xlsx" + _create_workbook(input_path) + ops = [PatchOp(op="set_value", sheet="Sheet1", cell="A1", value="=SUM(1,1)")] + request = PatchRequest( + xlsx_path=input_path, ops=ops, on_conflict="rename", auto_formula=True + ) + result = run_patch(request, policy=PathPolicy(root=tmp_path)) + workbook = load_workbook(result.out_path) + try: + formula_value = workbook["Sheet1"]["A1"].value + if isinstance(formula_value, str) and not formula_value.startswith("="): + formula_value = f"={formula_value}" + assert formula_value == "=SUM(1,1)" + finally: + workbook.close() + + +def test_run_patch_rejects_path_outside_root( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + _disable_com(monkeypatch) + root = tmp_path / "root" + root.mkdir() + input_path = tmp_path / "book.xlsx" + _create_workbook(input_path) + ops = [PatchOp(op="set_value", sheet="Sheet1", cell="A1", value="x")] + request = PatchRequest(xlsx_path=input_path, ops=ops, on_conflict="rename") + with pytest.raises(ValueError): + run_patch(request, policy=PathPolicy(root=root)) + + +def test_run_patch_conflict_rename( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + _disable_com(monkeypatch) + input_path = tmp_path / "book.xlsx" + _create_workbook(input_path) + default_out = tmp_path / "book_patched.xlsx" + default_out.write_text("dummy", encoding="utf-8") + ops = [PatchOp(op="set_value", sheet="Sheet1", cell="A1", value="x")] + request = PatchRequest(xlsx_path=input_path, ops=ops, on_conflict="rename") + result = run_patch(request, policy=PathPolicy(root=tmp_path)) + assert result.out_path != str(default_out) + assert Path(result.out_path).exists() + + +def test_run_patch_conflict_skip( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + _disable_com(monkeypatch) + input_path = tmp_path / "book.xlsx" + _create_workbook(input_path) + default_out = tmp_path / "book_patched.xlsx" + default_out.write_text("dummy", encoding="utf-8") + ops = [PatchOp(op="set_value", sheet="Sheet1", cell="A1", value="x")] + request = PatchRequest(xlsx_path=input_path, ops=ops, on_conflict="skip") + result = run_patch(request, policy=PathPolicy(root=tmp_path)) + assert result.out_path == str(default_out) + assert result.patch_diff == [] + assert any("skipping" in warning for warning in result.warnings) + + +def test_run_patch_conflict_skip_dry_run_still_simulates( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + _disable_com(monkeypatch) + input_path = tmp_path / "book.xlsx" + _create_workbook(input_path) + default_out = tmp_path / "book_patched.xlsx" + default_out.write_text("dummy", encoding="utf-8") + ops = [PatchOp(op="set_value", sheet="Sheet1", cell="A1", value="x")] + request = PatchRequest( + xlsx_path=input_path, + ops=ops, + on_conflict="skip", + dry_run=True, + ) + result = run_patch(request, policy=PathPolicy(root=tmp_path)) + assert result.error is None + assert len(result.patch_diff) == 1 + assert any("ignores on_conflict=skip" in warning for warning in result.warnings) + assert not any("may drop shapes/charts" in warning for warning in result.warnings) + assert default_out.read_text(encoding="utf-8") == "dummy" + + +def test_run_patch_conflict_overwrite( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + _disable_com(monkeypatch) + input_path = tmp_path / "book.xlsx" + _create_workbook(input_path) + default_out = tmp_path / "book_patched.xlsx" + _create_workbook(default_out) + ops = [PatchOp(op="set_value", sheet="Sheet1", cell="A1", value="new")] + request = PatchRequest(xlsx_path=input_path, ops=ops, on_conflict="overwrite") + result = run_patch(request, policy=PathPolicy(root=tmp_path)) + assert result.out_path == str(default_out) + workbook = load_workbook(result.out_path) + try: + assert workbook["Sheet1"]["A1"].value == "new" + finally: + workbook.close() + + +def test_run_patch_atomicity(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + _disable_com(monkeypatch) + input_path = tmp_path / "book.xlsx" + _create_workbook(input_path) + ops = [ + PatchOp(op="set_value", sheet="Sheet1", cell="A1", value="x"), + PatchOp(op="set_value", sheet="Missing", cell="A1", value="y"), + ] + request = PatchRequest(xlsx_path=input_path, ops=ops, on_conflict="rename") + output_path = tmp_path / "book_patched.xlsx" + result = run_patch(request, policy=PathPolicy(root=tmp_path)) + assert result.error is not None + assert not output_path.exists() + + +def test_run_patch_creates_out_dir( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + _disable_com(monkeypatch) + input_path = tmp_path / "book.xlsx" + _create_workbook(input_path) + out_dir = tmp_path / "nested" / "output" + ops = [PatchOp(op="set_value", sheet="Sheet1", cell="A1", value="x")] + request = PatchRequest( + xlsx_path=input_path, + ops=ops, + out_dir=out_dir, + on_conflict="rename", + ) + result = run_patch(request, policy=PathPolicy(root=tmp_path)) + out_path = Path(result.out_path) + assert out_path.exists() + assert out_path.parent == out_dir + + +def test_run_patch_xls_requires_com( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + _disable_com(monkeypatch) + input_path = tmp_path / "book.xls" + input_path.write_text("dummy", encoding="utf-8") + ops = [PatchOp(op="add_sheet", sheet="Sheet2")] + request = PatchRequest(xlsx_path=input_path, ops=ops, on_conflict="rename") + with pytest.raises(ValueError, match=r"requires Windows Excel COM"): + run_patch(request, policy=PathPolicy(root=tmp_path)) + + +def test_run_patch_xlsm_openpyxl_uses_keep_vba( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + _disable_com(monkeypatch) + input_path = tmp_path / "book.xlsm" + input_path.write_bytes(b"dummy") + calls: dict[str, object] = {} + + class FakeCell: + def __init__(self, value: str | int | float | None) -> None: + self.value = value + self.data_type: str | None = None + + class FakeSheet: + def __init__(self) -> None: + self._cells: dict[str, FakeCell] = {"A1": FakeCell("old")} + + def __getitem__(self, key: str) -> FakeCell: + if key not in self._cells: + self._cells[key] = FakeCell(None) + return self._cells[key] + + class FakeWorkbook: + def __init__(self) -> None: + self._sheets: dict[str, FakeSheet] = {"Sheet1": FakeSheet()} + self.sheetnames = ["Sheet1"] + + def __getitem__(self, key: str) -> FakeSheet: + return self._sheets[key] + + def create_sheet(self, title: str) -> FakeSheet: + sheet = FakeSheet() + self._sheets[title] = sheet + self.sheetnames.append(title) + return sheet + + def save(self, filename: str | Path) -> None: + calls["saved"] = str(filename) + + def close(self) -> None: + calls["closed"] = True + + fake_workbook = FakeWorkbook() + + def _fake_load_workbook(path: Path, **kwargs: object) -> FakeWorkbook: + calls["path"] = str(path) + calls["keep_vba"] = kwargs.get("keep_vba", False) + return fake_workbook + + monkeypatch.setattr("openpyxl.load_workbook", _fake_load_workbook) + + request = PatchRequest( + xlsx_path=input_path, + ops=[PatchOp(op="set_value", sheet="Sheet1", cell="A1", value="new")], + on_conflict="rename", + ) + result = run_patch(request, policy=PathPolicy(root=tmp_path)) + assert result.error is None + assert calls["keep_vba"] is True + assert calls["closed"] is True + + +def test_run_patch_dry_run_does_not_write( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + _disable_com(monkeypatch) + input_path = tmp_path / "book.xlsx" + _create_workbook(input_path) + output_path = tmp_path / "book_patched.xlsx" + assert not output_path.exists() + ops = [PatchOp(op="set_value", sheet="Sheet1", cell="A1", value="x")] + request = PatchRequest( + xlsx_path=input_path, + ops=ops, + on_conflict="rename", + dry_run=True, + ) + result = run_patch(request, policy=PathPolicy(root=tmp_path)) + assert result.error is None + assert not output_path.exists() + assert len(result.patch_diff) == 1 + + +def test_run_patch_return_inverse_ops( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + _disable_com(monkeypatch) + input_path = tmp_path / "book.xlsx" + _create_workbook(input_path) + ops = [PatchOp(op="set_value", sheet="Sheet1", cell="A1", value="new")] + request = PatchRequest( + xlsx_path=input_path, + ops=ops, + on_conflict="rename", + return_inverse_ops=True, + ) + result = run_patch(request, policy=PathPolicy(root=tmp_path)) + assert result.error is None + assert len(result.inverse_ops) == 1 + inverse = result.inverse_ops[0] + assert inverse.op == "set_value" + assert inverse.cell == "A1" + assert inverse.value == "old" + + +def test_run_patch_set_range_values( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + _disable_com(monkeypatch) + input_path = tmp_path / "book.xlsx" + _create_workbook(input_path) + ops = [ + PatchOp( + op="set_range_values", + sheet="Sheet1", + range="A2:B3", + values=[["r1c1", "r1c2"], ["r2c1", "r2c2"]], + ) + ] + request = PatchRequest(xlsx_path=input_path, ops=ops, on_conflict="rename") + result = run_patch(request, policy=PathPolicy(root=tmp_path)) + assert result.error is None + workbook = load_workbook(result.out_path) + try: + sheet = workbook["Sheet1"] + assert sheet["A2"].value == "r1c1" + assert sheet["B3"].value == "r2c2" + finally: + workbook.close() + + +def test_run_patch_set_range_values_size_mismatch( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + _disable_com(monkeypatch) + input_path = tmp_path / "book.xlsx" + _create_workbook(input_path) + ops = [ + PatchOp( + op="set_range_values", + sheet="Sheet1", + range="A2:B3", + values=[["only_one_column"], ["still_one_column"]], + ) + ] + request = PatchRequest(xlsx_path=input_path, ops=ops, on_conflict="rename") + result = run_patch(request, policy=PathPolicy(root=tmp_path)) + assert result.error is not None + assert "width does not match range" in result.error.message + + +def test_run_patch_set_value_if_skipped( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + _disable_com(monkeypatch) + input_path = tmp_path / "book.xlsx" + _create_workbook(input_path) + ops = [ + PatchOp( + op="set_value_if", + sheet="Sheet1", + cell="A1", + expected="not_old", + value="new", + ) + ] + request = PatchRequest(xlsx_path=input_path, ops=ops, on_conflict="rename") + result = run_patch(request, policy=PathPolicy(root=tmp_path)) + assert result.error is None + assert len(result.patch_diff) == 1 + assert result.patch_diff[0].status == "skipped" + + +def test_run_patch_fill_formula( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + _disable_com(monkeypatch) + input_path = tmp_path / "book.xlsx" + _create_workbook(input_path) + workbook = load_workbook(input_path) + try: + sheet = workbook["Sheet1"] + sheet["A2"] = 1 + sheet["A3"] = 2 + sheet["A4"] = 3 + sheet["B2"] = 10 + sheet["B3"] = 20 + sheet["B4"] = 30 + workbook.save(input_path) + finally: + workbook.close() + ops = [ + PatchOp( + op="fill_formula", + sheet="Sheet1", + range="C2:C4", + base_cell="C2", + formula="=A2+B2", + ) + ] + request = PatchRequest(xlsx_path=input_path, ops=ops, on_conflict="rename") + result = run_patch(request, policy=PathPolicy(root=tmp_path)) + assert result.error is None + workbook = load_workbook(result.out_path) + try: + sheet = workbook["Sheet1"] + assert sheet["C2"].value == "=A2+B2" + assert sheet["C3"].value == "=A3+B3" + assert sheet["C4"].value == "=A4+B4" + finally: + workbook.close() + + +def test_run_patch_formula_health_check( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + _disable_com(monkeypatch) + input_path = tmp_path / "book.xlsx" + _create_workbook(input_path) + ops = [PatchOp(op="set_formula", sheet="Sheet1", cell="A1", formula="=#REF!+1")] + request = PatchRequest( + xlsx_path=input_path, + ops=ops, + on_conflict="rename", + preflight_formula_check=True, + ) + result = run_patch(request, policy=PathPolicy(root=tmp_path)) + assert result.error is not None + assert result.formula_issues + assert result.formula_issues[0].code == "ref_error" + + +def test_run_patch_formula_health_check_reports_matching_op( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + _disable_com(monkeypatch) + input_path = tmp_path / "book.xlsx" + _create_workbook(input_path) + ops = [ + PatchOp(op="set_formula", sheet="Sheet1", cell="B1", formula="=SUM(1,1)"), + PatchOp(op="set_formula", sheet="Sheet1", cell="A1", formula="=#REF!+1"), + ] + request = PatchRequest( + xlsx_path=input_path, + ops=ops, + on_conflict="rename", + preflight_formula_check=True, + ) + result = run_patch(request, policy=PathPolicy(root=tmp_path)) + assert result.error is not None + assert result.error.op_index == 1 + assert result.error.op == "set_formula" + + +def test_patch_op_add_sheet_rejects_unrelated_fields() -> None: + with pytest.raises(ValidationError, match="add_sheet does not accept range"): + PatchOp(op="add_sheet", sheet="NewSheet", range="A1:A1") + + +def test_patch_op_set_value_rejects_expected() -> None: + with pytest.raises(ValidationError, match="set_value does not accept expected"): + PatchOp(op="set_value", sheet="Sheet1", cell="A1", value="x", expected="old") diff --git a/tests/mcp/test_server.py b/tests/mcp/test_server.py index b90131f..6064f4a 100644 --- a/tests/mcp/test_server.py +++ b/tests/mcp/test_server.py @@ -14,8 +14,16 @@ from exstruct.mcp.tools import ( ExtractToolInput, ExtractToolOutput, + PatchToolInput, + PatchToolOutput, + ReadCellsToolInput, + ReadCellsToolOutput, + ReadFormulasToolInput, + ReadFormulasToolOutput, ReadJsonChunkToolInput, ReadJsonChunkToolOutput, + ReadRangeToolInput, + ReadRangeToolOutput, ValidateInputToolInput, ValidateInputToolOutput, ) @@ -94,6 +102,34 @@ def test_coerce_filter() -> None: assert server._coerce_filter({"a": 1}) == {"a": 1} +def test_coerce_patch_ops_accepts_object_and_json_string() -> None: + result = server._coerce_patch_ops( + [ + {"op": "add_sheet", "sheet": "New"}, + '{"op":"set_value","sheet":"New","cell":"A1","value":"x"}', + ] + ) + assert result == [ + {"op": "add_sheet", "sheet": "New"}, + {"op": "set_value", "sheet": "New", "cell": "A1", "value": "x"}, + ] + + +def test_coerce_patch_ops_rejects_invalid_json_string() -> None: + with pytest.raises( + ValueError, match=r"Invalid patch operation at ops\[0\]: invalid JSON" + ): + server._coerce_patch_ops(["{invalid json}"]) + + +def test_coerce_patch_ops_rejects_non_object_json_value() -> None: + with pytest.raises( + ValueError, + match=r"Invalid patch operation at ops\[0\]: JSON value must be an object", + ): + server._coerce_patch_ops(['["not","object"]']) + + def test_register_tools_uses_default_on_conflict( monkeypatch: pytest.MonkeyPatch, tmp_path: Path ) -> None: @@ -126,6 +162,15 @@ def fake_run_validate_input_tool( calls["validate"] = (payload, policy) return ValidateInputToolOutput(is_readable=True) + def fake_run_patch_tool( + payload: PatchToolInput, + *, + policy: PathPolicy, + on_conflict: OnConflictPolicy, + ) -> PatchToolOutput: + calls["patch"] = (payload, policy, on_conflict) + return PatchToolOutput(out_path="out.xlsx", patch_diff=[]) + async def fake_run_sync(func: Callable[[], object]) -> object: return func() @@ -134,6 +179,7 @@ async def fake_run_sync(func: Callable[[], object]) -> object: server, "run_read_json_chunk_tool", fake_run_read_json_chunk_tool ) monkeypatch.setattr(server, "run_validate_input_tool", fake_run_validate_input_tool) + monkeypatch.setattr(server, "run_patch_tool", fake_run_patch_tool) monkeypatch.setattr(anyio.to_thread, "run_sync", fake_run_sync) server._register_tools(app, policy, default_on_conflict="rename") @@ -152,10 +198,398 @@ async def fake_run_sync(func: Callable[[], object]) -> object: Callable[..., Awaitable[object]], app.tools["exstruct_validate_input"] ) anyio.run(_call_async, validate_tool, {"xlsx_path": "in.xlsx"}) + patch_tool = cast(Callable[..., Awaitable[object]], app.tools["exstruct_patch"]) + anyio.run( + _call_async, + patch_tool, + {"xlsx_path": "in.xlsx", "ops": [{"op": "add_sheet", "sheet": "New"}]}, + ) assert calls["extract"][2] == "rename" chunk_call = cast(tuple[ReadJsonChunkToolInput, PathPolicy], calls["chunk"]) assert chunk_call[0].filter is not None + assert calls["patch"][2] == "rename" + patch_call = cast( + tuple[PatchToolInput, PathPolicy, OnConflictPolicy], calls["patch"] + ) + assert patch_call[0].dry_run is False + assert patch_call[0].return_inverse_ops is False + assert patch_call[0].preflight_formula_check is False + + +def test_register_tools_passes_read_tool_arguments( + monkeypatch: pytest.MonkeyPatch, tmp_path: Path +) -> None: + app = DummyApp() + policy = PathPolicy(root=tmp_path) + calls: dict[str, tuple[object, ...]] = {} + + def fake_run_extract_tool( + payload: ExtractToolInput, + *, + policy: PathPolicy, + on_conflict: OnConflictPolicy, + ) -> ExtractToolOutput: + return ExtractToolOutput(out_path="out.json") + + def fake_run_read_json_chunk_tool( + payload: ReadJsonChunkToolInput, + *, + policy: PathPolicy, + ) -> ReadJsonChunkToolOutput: + return ReadJsonChunkToolOutput(chunk="{}") + + def fake_run_read_range_tool( + payload: ReadRangeToolInput, + *, + policy: PathPolicy, + ) -> ReadRangeToolOutput: + calls["range"] = (payload, policy) + return ReadRangeToolOutput( + book_name="book", + sheet_name="Sheet1", + range="A1:B2", + cells=[], + ) + + def fake_run_read_cells_tool( + payload: ReadCellsToolInput, + *, + policy: PathPolicy, + ) -> ReadCellsToolOutput: + calls["cells"] = (payload, policy) + return ReadCellsToolOutput(book_name="book", sheet_name="Sheet1", cells=[]) + + def fake_run_read_formulas_tool( + payload: ReadFormulasToolInput, + *, + policy: PathPolicy, + ) -> ReadFormulasToolOutput: + calls["formulas"] = (payload, policy) + return ReadFormulasToolOutput( + book_name="book", sheet_name="Sheet1", formulas=[] + ) + + def fake_run_validate_input_tool( + payload: ValidateInputToolInput, + *, + policy: PathPolicy, + ) -> ValidateInputToolOutput: + return ValidateInputToolOutput(is_readable=True) + + def fake_run_patch_tool( + payload: PatchToolInput, + *, + policy: PathPolicy, + on_conflict: OnConflictPolicy, + ) -> PatchToolOutput: + return PatchToolOutput(out_path="out.xlsx", patch_diff=[]) + + async def fake_run_sync(func: Callable[[], object]) -> object: + return func() + + monkeypatch.setattr(server, "run_extract_tool", fake_run_extract_tool) + monkeypatch.setattr( + server, "run_read_json_chunk_tool", fake_run_read_json_chunk_tool + ) + monkeypatch.setattr(server, "run_read_range_tool", fake_run_read_range_tool) + monkeypatch.setattr(server, "run_read_cells_tool", fake_run_read_cells_tool) + monkeypatch.setattr(server, "run_read_formulas_tool", fake_run_read_formulas_tool) + monkeypatch.setattr(server, "run_validate_input_tool", fake_run_validate_input_tool) + monkeypatch.setattr(server, "run_patch_tool", fake_run_patch_tool) + monkeypatch.setattr(anyio.to_thread, "run_sync", fake_run_sync) + + server._register_tools(app, policy, default_on_conflict="overwrite") + + read_range_tool = cast( + Callable[..., Awaitable[object]], app.tools["exstruct_read_range"] + ) + anyio.run( + _call_async, + read_range_tool, + {"out_path": "out.json", "range": "A1:B2", "include_formulas": True}, + ) + read_cells_tool = cast( + Callable[..., Awaitable[object]], app.tools["exstruct_read_cells"] + ) + anyio.run( + _call_async, + read_cells_tool, + {"out_path": "out.json", "addresses": ["J98", "J124"]}, + ) + read_formulas_tool = cast( + Callable[..., Awaitable[object]], app.tools["exstruct_read_formulas"] + ) + anyio.run( + _call_async, + read_formulas_tool, + {"out_path": "out.json", "range": "J2:J201", "include_values": True}, + ) + + range_call = cast(tuple[ReadRangeToolInput, PathPolicy], calls["range"]) + assert range_call[0].range == "A1:B2" + assert range_call[0].include_formulas is True + cells_call = cast(tuple[ReadCellsToolInput, PathPolicy], calls["cells"]) + assert cells_call[0].addresses == ["J98", "J124"] + formulas_call = cast(tuple[ReadFormulasToolInput, PathPolicy], calls["formulas"]) + assert formulas_call[0].range == "J2:J201" + assert formulas_call[0].include_values is True + + +def test_register_tools_accepts_patch_ops_json_strings( + monkeypatch: pytest.MonkeyPatch, tmp_path: Path +) -> None: + app = DummyApp() + policy = PathPolicy(root=tmp_path) + calls: dict[str, tuple[object, ...]] = {} + + def fake_run_extract_tool( + payload: ExtractToolInput, + *, + policy: PathPolicy, + on_conflict: OnConflictPolicy, + ) -> ExtractToolOutput: + return ExtractToolOutput(out_path="out.json") + + def fake_run_read_json_chunk_tool( + payload: ReadJsonChunkToolInput, + *, + policy: PathPolicy, + ) -> ReadJsonChunkToolOutput: + return ReadJsonChunkToolOutput(chunk="{}") + + def fake_run_validate_input_tool( + payload: ValidateInputToolInput, + *, + policy: PathPolicy, + ) -> ValidateInputToolOutput: + return ValidateInputToolOutput(is_readable=True) + + def fake_run_patch_tool( + payload: PatchToolInput, + *, + policy: PathPolicy, + on_conflict: OnConflictPolicy, + ) -> PatchToolOutput: + calls["patch"] = (payload, policy, on_conflict) + return PatchToolOutput(out_path="out.xlsx", patch_diff=[]) + + async def fake_run_sync(func: Callable[[], object]) -> object: + return func() + + monkeypatch.setattr(server, "run_extract_tool", fake_run_extract_tool) + monkeypatch.setattr( + server, "run_read_json_chunk_tool", fake_run_read_json_chunk_tool + ) + monkeypatch.setattr(server, "run_validate_input_tool", fake_run_validate_input_tool) + monkeypatch.setattr(server, "run_patch_tool", fake_run_patch_tool) + monkeypatch.setattr(anyio.to_thread, "run_sync", fake_run_sync) + + server._register_tools(app, policy, default_on_conflict="overwrite") + patch_tool = cast(Callable[..., Awaitable[object]], app.tools["exstruct_patch"]) + anyio.run( + _call_async, + patch_tool, + { + "xlsx_path": "in.xlsx", + "ops": ['{"op":"add_sheet","sheet":"New"}'], + }, + ) + patch_call = cast( + tuple[PatchToolInput, PathPolicy, OnConflictPolicy], calls["patch"] + ) + assert patch_call[0].ops[0].op == "add_sheet" + assert patch_call[0].ops[0].sheet == "New" + + +def test_register_tools_rejects_invalid_patch_ops_json_strings( + monkeypatch: pytest.MonkeyPatch, tmp_path: Path +) -> None: + app = DummyApp() + policy = PathPolicy(root=tmp_path) + + def fake_run_extract_tool( + payload: ExtractToolInput, + *, + policy: PathPolicy, + on_conflict: OnConflictPolicy, + ) -> ExtractToolOutput: + return ExtractToolOutput(out_path="out.json") + + def fake_run_read_json_chunk_tool( + payload: ReadJsonChunkToolInput, + *, + policy: PathPolicy, + ) -> ReadJsonChunkToolOutput: + return ReadJsonChunkToolOutput(chunk="{}") + + def fake_run_validate_input_tool( + payload: ValidateInputToolInput, + *, + policy: PathPolicy, + ) -> ValidateInputToolOutput: + return ValidateInputToolOutput(is_readable=True) + + def fake_run_patch_tool( + payload: PatchToolInput, + *, + policy: PathPolicy, + on_conflict: OnConflictPolicy, + ) -> PatchToolOutput: + return PatchToolOutput(out_path="out.xlsx", patch_diff=[]) + + async def fake_run_sync(func: Callable[[], object]) -> object: + return func() + + monkeypatch.setattr(server, "run_extract_tool", fake_run_extract_tool) + monkeypatch.setattr( + server, "run_read_json_chunk_tool", fake_run_read_json_chunk_tool + ) + monkeypatch.setattr(server, "run_validate_input_tool", fake_run_validate_input_tool) + monkeypatch.setattr(server, "run_patch_tool", fake_run_patch_tool) + monkeypatch.setattr(anyio.to_thread, "run_sync", fake_run_sync) + + server._register_tools(app, policy, default_on_conflict="overwrite") + patch_tool = cast(Callable[..., Awaitable[object]], app.tools["exstruct_patch"]) + + with pytest.raises( + ValueError, match=r"Invalid patch operation at ops\[0\]: invalid JSON" + ): + anyio.run( + _call_async, + patch_tool, + {"xlsx_path": "in.xlsx", "ops": ['{"op":"set_value"']}, + ) + + +def test_register_tools_passes_patch_default_on_conflict( + monkeypatch: pytest.MonkeyPatch, tmp_path: Path +) -> None: + app = DummyApp() + policy = PathPolicy(root=tmp_path) + calls: dict[str, tuple[object, ...]] = {} + + def fake_run_extract_tool( + payload: ExtractToolInput, + *, + policy: PathPolicy, + on_conflict: OnConflictPolicy, + ) -> ExtractToolOutput: + return ExtractToolOutput(out_path="out.json") + + def fake_run_read_json_chunk_tool( + payload: ReadJsonChunkToolInput, + *, + policy: PathPolicy, + ) -> ReadJsonChunkToolOutput: + return ReadJsonChunkToolOutput(chunk="{}") + + def fake_run_validate_input_tool( + payload: ValidateInputToolInput, + *, + policy: PathPolicy, + ) -> ValidateInputToolOutput: + return ValidateInputToolOutput(is_readable=True) + + def fake_run_patch_tool( + payload: PatchToolInput, + *, + policy: PathPolicy, + on_conflict: OnConflictPolicy, + ) -> PatchToolOutput: + calls["patch"] = (payload, policy, on_conflict) + return PatchToolOutput(out_path="out.xlsx", patch_diff=[]) + + async def fake_run_sync(func: Callable[[], object]) -> object: + return func() + + monkeypatch.setattr(server, "run_extract_tool", fake_run_extract_tool) + monkeypatch.setattr( + server, "run_read_json_chunk_tool", fake_run_read_json_chunk_tool + ) + monkeypatch.setattr(server, "run_validate_input_tool", fake_run_validate_input_tool) + monkeypatch.setattr(server, "run_patch_tool", fake_run_patch_tool) + monkeypatch.setattr(anyio.to_thread, "run_sync", fake_run_sync) + + server._register_tools(app, policy, default_on_conflict="overwrite") + patch_tool = cast(Callable[..., Awaitable[object]], app.tools["exstruct_patch"]) + anyio.run( + _call_async, + patch_tool, + {"xlsx_path": "in.xlsx", "ops": [{"op": "add_sheet", "sheet": "New"}]}, + ) + + assert calls["patch"][2] == "overwrite" + + +def test_register_tools_passes_patch_extended_flags( + monkeypatch: pytest.MonkeyPatch, tmp_path: Path +) -> None: + app = DummyApp() + policy = PathPolicy(root=tmp_path) + calls: dict[str, tuple[object, ...]] = {} + + def fake_run_extract_tool( + payload: ExtractToolInput, + *, + policy: PathPolicy, + on_conflict: OnConflictPolicy, + ) -> ExtractToolOutput: + return ExtractToolOutput(out_path="out.json") + + def fake_run_read_json_chunk_tool( + payload: ReadJsonChunkToolInput, + *, + policy: PathPolicy, + ) -> ReadJsonChunkToolOutput: + return ReadJsonChunkToolOutput(chunk="{}") + + def fake_run_validate_input_tool( + payload: ValidateInputToolInput, + *, + policy: PathPolicy, + ) -> ValidateInputToolOutput: + return ValidateInputToolOutput(is_readable=True) + + def fake_run_patch_tool( + payload: PatchToolInput, + *, + policy: PathPolicy, + on_conflict: OnConflictPolicy, + ) -> PatchToolOutput: + calls["patch"] = (payload, policy, on_conflict) + return PatchToolOutput(out_path="out.xlsx", patch_diff=[]) + + async def fake_run_sync(func: Callable[[], object]) -> object: + return func() + + monkeypatch.setattr(server, "run_extract_tool", fake_run_extract_tool) + monkeypatch.setattr( + server, "run_read_json_chunk_tool", fake_run_read_json_chunk_tool + ) + monkeypatch.setattr(server, "run_validate_input_tool", fake_run_validate_input_tool) + monkeypatch.setattr(server, "run_patch_tool", fake_run_patch_tool) + monkeypatch.setattr(anyio.to_thread, "run_sync", fake_run_sync) + + server._register_tools(app, policy, default_on_conflict="overwrite") + patch_tool = cast(Callable[..., Awaitable[object]], app.tools["exstruct_patch"]) + anyio.run( + _call_async, + patch_tool, + { + "xlsx_path": "in.xlsx", + "ops": [{"op": "add_sheet", "sheet": "New"}], + "dry_run": True, + "return_inverse_ops": True, + "preflight_formula_check": True, + }, + ) + patch_call = cast( + tuple[PatchToolInput, PathPolicy, OnConflictPolicy], calls["patch"] + ) + assert patch_call[0].dry_run is True + assert patch_call[0].return_inverse_ops is True + assert patch_call[0].preflight_formula_check is True def test_run_server_sets_env(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: diff --git a/tests/mcp/test_sheet_reader.py b/tests/mcp/test_sheet_reader.py new file mode 100644 index 0000000..f176ac9 --- /dev/null +++ b/tests/mcp/test_sheet_reader.py @@ -0,0 +1,161 @@ +from __future__ import annotations + +from collections.abc import Mapping +import json +from pathlib import Path + +import pytest + +from exstruct.mcp.sheet_reader import ( + ReadCellsRequest, + ReadFormulasRequest, + ReadRangeRequest, + read_cells, + read_formulas, + read_range, +) + + +def _write_json(path: Path, data: Mapping[str, object]) -> None: + path.write_text(json.dumps(data, ensure_ascii=False), encoding="utf-8") + + +def test_read_range_with_a1_range(tmp_path: Path) -> None: + data = { + "book_name": "book", + "sheets": { + "Data": { + "rows": [ + {"r": 1, "c": {"0": "A1", "1": "B1"}}, + {"r": 2, "c": {"0": "A2"}}, + ] + } + }, + } + out = tmp_path / "out.json" + _write_json(out, data) + + request = ReadRangeRequest(out_path=out, sheet="Data", range="A1:B2") + result = read_range(request) + + assert result.range == "A1:B2" + assert [item.cell for item in result.cells] == ["A1", "B1", "A2", "B2"] + assert [item.value for item in result.cells] == ["A1", "B1", "A2", None] + + +def test_read_range_rejects_invalid_range(tmp_path: Path) -> None: + out = tmp_path / "out.json" + _write_json(out, {"book_name": "book", "sheets": {"Data": {"rows": []}}}) + request = ReadRangeRequest(out_path=out, sheet="Data", range="1A:B2") + with pytest.raises(ValueError, match="Invalid A1 cell address"): + read_range(request) + + +def test_read_range_rejects_too_large_range(tmp_path: Path) -> None: + out = tmp_path / "out.json" + _write_json(out, {"book_name": "book", "sheets": {"Data": {"rows": []}}}) + request = ReadRangeRequest(out_path=out, sheet="Data", range="A1:C3", max_cells=8) + with pytest.raises(ValueError, match="Requested range exceeds max_cells"): + read_range(request) + + +def test_read_cells_keeps_input_order_and_reports_missing(tmp_path: Path) -> None: + data = { + "book_name": "book", + "sheets": { + "Data": { + "rows": [ + {"r": 1, "c": {"0": "head"}}, + {"r": 98, "c": {"9": 12345}}, + ], + "formulas_map": {"=SUM(D124:F124)*G124*(1-H124)": [[124, 9]]}, + } + }, + } + out = tmp_path / "out.json" + _write_json(out, data) + + request = ReadCellsRequest( + out_path=out, + sheet="Data", + addresses=["J124", "B2", "J98", "A1"], + include_formulas=True, + ) + result = read_cells(request) + + assert [item.cell for item in result.cells] == ["J124", "B2", "J98", "A1"] + assert result.missing_cells == ["B2"] + assert result.cells[0].formula == "=SUM(D124:F124)*G124*(1-H124)" + assert result.cells[2].value == 12345 + assert result.cells[3].value == "head" + + +def test_read_formulas_with_range_and_values_alpha_col(tmp_path: Path) -> None: + data = { + "book_name": "book", + "sheets": { + "Data": { + "rows": [ + {"r": 1, "c": {"A": "header"}}, + {"r": 98, "c": {"J": 98765}}, + ], + "formulas_map": { + "=SUM(D98:F98)*G98*(1-H98)": [[98, 9]], + "=A1": [[1, 0]], + }, + } + }, + } + out = tmp_path / "out.json" + _write_json(out, data) + + request = ReadFormulasRequest( + out_path=out, + sheet="Data", + range="J2:J200", + include_values=True, + ) + result = read_formulas(request) + + assert result.range == "J2:J200" + assert len(result.formulas) == 1 + assert result.formulas[0].cell == "J98" + assert result.formulas[0].formula == "=SUM(D98:F98)*G98*(1-H98)" + assert result.formulas[0].value == 98765 + + +def test_read_formulas_warns_when_formulas_map_missing(tmp_path: Path) -> None: + data = {"book_name": "book", "sheets": {"Data": {"rows": []}}} + out = tmp_path / "out.json" + _write_json(out, data) + + request = ReadFormulasRequest(out_path=out, sheet="Data") + result = read_formulas(request) + + assert result.formulas == [] + assert any("mode='verbose'" in warning for warning in result.warnings) + + +def test_read_range_warns_when_include_formulas_without_map(tmp_path: Path) -> None: + data = {"book_name": "book", "sheets": {"Data": {"rows": []}}} + out = tmp_path / "out.json" + _write_json(out, data) + + request = ReadRangeRequest( + out_path=out, sheet="Data", range="A1:A1", include_formulas=True + ) + result = read_range(request) + + assert len(result.cells) == 1 + assert result.cells[0].cell == "A1" + assert any("mode='verbose'" in warning for warning in result.warnings) + + +def test_read_cells_requires_sheet_for_multi_sheet_payload(tmp_path: Path) -> None: + data = {"book_name": "book", "sheets": {"A": {"rows": []}, "B": {"rows": []}}} + out = tmp_path / "out.json" + _write_json(out, data) + + request = ReadCellsRequest(out_path=out, addresses=["A1"]) + with pytest.raises(ValueError, match=r"Available sheets: A, B"): + read_cells(request) diff --git a/tests/mcp/test_tool_models.py b/tests/mcp/test_tool_models.py index 71637cf..9606961 100644 --- a/tests/mcp/test_tool_models.py +++ b/tests/mcp/test_tool_models.py @@ -3,7 +3,14 @@ from pydantic import ValidationError import pytest -from exstruct.mcp.tools import ExtractToolInput, ReadJsonChunkToolInput +from exstruct.mcp.tools import ( + ExtractToolInput, + PatchToolInput, + ReadCellsToolInput, + ReadFormulasToolInput, + ReadJsonChunkToolInput, + ReadRangeToolInput, +) def test_extract_tool_input_defaults() -> None: @@ -17,3 +24,41 @@ def test_extract_tool_input_defaults() -> None: def test_read_json_chunk_rejects_invalid_max_bytes() -> None: with pytest.raises(ValidationError): ReadJsonChunkToolInput(out_path="out.json", max_bytes=0) + + +def test_patch_tool_input_defaults() -> None: + payload = PatchToolInput( + xlsx_path="input.xlsx", + ops=[{"op": "add_sheet", "sheet": "New"}], + ) + assert payload.out_dir is None + assert payload.out_name is None + assert payload.on_conflict is None + assert payload.dry_run is False + assert payload.return_inverse_ops is False + assert payload.preflight_formula_check is False + + +def test_read_range_tool_input_defaults() -> None: + payload = ReadRangeToolInput(out_path="out.json", range="A1:B2") + assert payload.sheet is None + assert payload.include_formulas is False + assert payload.include_empty is True + assert payload.max_cells == 10_000 + + +def test_read_range_tool_input_rejects_invalid_max_cells() -> None: + with pytest.raises(ValidationError): + ReadRangeToolInput(out_path="out.json", range="A1:B2", max_cells=0) + + +def test_read_cells_tool_input_rejects_empty_addresses() -> None: + with pytest.raises(ValidationError): + ReadCellsToolInput(out_path="out.json", addresses=[]) + + +def test_read_formulas_tool_input_defaults() -> None: + payload = ReadFormulasToolInput(out_path="out.json") + assert payload.sheet is None + assert payload.range is None + assert payload.include_values is False diff --git a/tests/mcp/test_tools_handlers.py b/tests/mcp/test_tools_handlers.py index c77ca1b..d06effd 100644 --- a/tests/mcp/test_tools_handlers.py +++ b/tests/mcp/test_tools_handlers.py @@ -11,6 +11,15 @@ ReadJsonChunkResult, ) from exstruct.mcp.extract_runner import ExtractRequest, ExtractResult +from exstruct.mcp.patch_runner import PatchRequest, PatchResult +from exstruct.mcp.sheet_reader import ( + ReadCellsRequest, + ReadCellsResult, + ReadFormulasRequest, + ReadFormulasResult, + ReadRangeRequest, + ReadRangeResult, +) from exstruct.mcp.validate_input import ValidateInputRequest, ValidateInputResult @@ -74,6 +83,71 @@ def _fake_read_json_chunk( assert request.filter is not None +def test_run_read_range_tool_builds_request( + monkeypatch: pytest.MonkeyPatch, +) -> None: + captured: dict[str, object] = {} + + def _fake_read_range( + request: ReadRangeRequest, *, policy: object | None = None + ) -> ReadRangeResult: + captured["request"] = request + return ReadRangeResult( + book_name="book", + sheet_name="Sheet1", + range="A1:B2", + cells=[], + ) + + monkeypatch.setattr(tools, "read_range", _fake_read_range) + payload = tools.ReadRangeToolInput(out_path="out.json", range="A1:B2") + tools.run_read_range_tool(payload) + request = captured["request"] + assert isinstance(request, ReadRangeRequest) + assert request.out_path == Path("out.json") + assert request.range == "A1:B2" + + +def test_run_read_cells_tool_builds_request( + monkeypatch: pytest.MonkeyPatch, +) -> None: + captured: dict[str, object] = {} + + def _fake_read_cells( + request: ReadCellsRequest, *, policy: object | None = None + ) -> ReadCellsResult: + captured["request"] = request + return ReadCellsResult(book_name="book", sheet_name="Sheet1", cells=[]) + + monkeypatch.setattr(tools, "read_cells", _fake_read_cells) + payload = tools.ReadCellsToolInput(out_path="out.json", addresses=["A1", "B2"]) + tools.run_read_cells_tool(payload) + request = captured["request"] + assert isinstance(request, ReadCellsRequest) + assert request.out_path == Path("out.json") + assert request.addresses == ["A1", "B2"] + + +def test_run_read_formulas_tool_builds_request( + monkeypatch: pytest.MonkeyPatch, +) -> None: + captured: dict[str, object] = {} + + def _fake_read_formulas( + request: ReadFormulasRequest, *, policy: object | None = None + ) -> ReadFormulasResult: + captured["request"] = request + return ReadFormulasResult(book_name="book", sheet_name="Sheet1", formulas=[]) + + monkeypatch.setattr(tools, "read_formulas", _fake_read_formulas) + payload = tools.ReadFormulasToolInput(out_path="out.json", range="J2:J20") + tools.run_read_formulas_tool(payload) + request = captured["request"] + assert isinstance(request, ReadFormulasRequest) + assert request.out_path == Path("out.json") + assert request.range == "J2:J20" + + def test_run_validate_input_tool_builds_request( monkeypatch: pytest.MonkeyPatch, ) -> None: @@ -91,3 +165,32 @@ def _fake_validate_input( request = captured["request"] assert isinstance(request, ValidateInputRequest) assert request.xlsx_path == Path("input.xlsx") + + +def test_run_patch_tool_builds_request( + monkeypatch: pytest.MonkeyPatch, +) -> None: + captured: dict[str, object] = {} + + def _fake_run_patch( + request: PatchRequest, *, policy: object | None = None + ) -> PatchResult: + captured["request"] = request + return PatchResult(out_path="out.xlsx", patch_diff=[]) + + monkeypatch.setattr(tools, "run_patch", _fake_run_patch) + payload = tools.PatchToolInput( + xlsx_path="input.xlsx", + ops=[{"op": "add_sheet", "sheet": "New"}], + dry_run=True, + return_inverse_ops=True, + preflight_formula_check=True, + ) + tools.run_patch_tool(payload, on_conflict="rename") + request = captured["request"] + assert isinstance(request, PatchRequest) + assert request.xlsx_path == Path("input.xlsx") + assert request.on_conflict == "rename" + assert request.dry_run is True + assert request.return_inverse_ops is True + assert request.preflight_formula_check is True diff --git a/tests/models/test_alpha_col.py b/tests/models/test_alpha_col.py new file mode 100644 index 0000000..93f1ed9 --- /dev/null +++ b/tests/models/test_alpha_col.py @@ -0,0 +1,154 @@ +"""Tests for alpha column key conversion utilities.""" + +from __future__ import annotations + +import pytest + +from exstruct.models import ( + CellRow, + MergedCells, + SheetData, + WorkbookData, + col_index_to_alpha, + convert_row_keys_to_alpha, + convert_sheet_keys_to_alpha, + convert_workbook_keys_to_alpha, +) + + +class TestColIndexToAlpha: + """Tests for col_index_to_alpha().""" + + def test_known_values(self) -> None: + cases: list[tuple[int, str]] = [ + (0, "A"), + (1, "B"), + (25, "Z"), + (26, "AA"), + (27, "AB"), + (51, "AZ"), + (52, "BA"), + (701, "ZZ"), + (702, "AAA"), + ] + for index, expected in cases: + assert col_index_to_alpha(index) == expected + + def test_negative_raises(self) -> None: + with pytest.raises(ValueError, match="non-negative"): + col_index_to_alpha(-1) + + +class TestConvertRowKeysToAlpha: + """Tests for convert_row_keys_to_alpha().""" + + def test_basic_numeric_keys(self) -> None: + row = CellRow(r=1, c={"0": "val_A", "1": "val_B", "2": "val_C"}, links=None) + result = convert_row_keys_to_alpha(row) + assert result.c == {"A": "val_A", "B": "val_B", "C": "val_C"} + assert result.r == 1 + + def test_links_converted(self) -> None: + row = CellRow( + r=2, + c={"0": "text"}, + links={"0": "http://example.com"}, + ) + result = convert_row_keys_to_alpha(row) + assert result.links == {"A": "http://example.com"} + + def test_no_links(self) -> None: + row = CellRow(r=1, c={"0": 1}, links=None) + result = convert_row_keys_to_alpha(row) + assert result.links is None + + def test_non_numeric_keys_pass_through(self) -> None: + row = CellRow(r=1, c={"X": "val"}, links=None) + result = convert_row_keys_to_alpha(row) + assert result.c == {"X": "val"} + + def test_high_columns(self) -> None: + row = CellRow(r=1, c={"26": "AA_col"}, links=None) + result = convert_row_keys_to_alpha(row) + assert result.c == {"AA": "AA_col"} + + def test_raises_on_collision_in_cells(self) -> None: + row = CellRow(r=3, c={"0": "num_zero", "A": "alpha_a"}, links=None) + with pytest.raises(ValueError, match="collision"): + convert_row_keys_to_alpha(row) + + def test_raises_on_collision_in_links(self) -> None: + row = CellRow( + r=4, + c={"0": "value"}, + links={"0": "https://numeric.example", "A": "https://alpha.example"}, + ) + with pytest.raises(ValueError, match="collision"): + convert_row_keys_to_alpha(row) + + +class TestConvertSheetKeysToAlpha: + """Tests for convert_sheet_keys_to_alpha().""" + + def test_converts_all_rows(self) -> None: + rows = [ + CellRow(r=1, c={"0": "A1", "1": "B1"}, links=None), + CellRow(r=2, c={"0": "A2", "2": "C2"}, links=None), + ] + sheet = SheetData(rows=rows) + result = convert_sheet_keys_to_alpha(sheet) + assert result.rows[0].c == {"A": "A1", "B": "B1"} + assert result.rows[1].c == {"A": "A2", "C": "C2"} + + def test_preserves_other_fields(self) -> None: + sheet = SheetData( + rows=[CellRow(r=1, c={"0": "v"}, links=None)], + shapes=[], + charts=[], + ) + result = convert_sheet_keys_to_alpha(sheet) + assert result.shapes == [] + assert result.charts == [] + + def test_moves_merged_cells_to_merged_ranges(self) -> None: + sheet = SheetData( + rows=[CellRow(r=1, c={"0": "v"}, links=None)], + merged_cells=MergedCells( + items=[(1, 0, 3, 2, "merged"), (10, 25, 10, 26, "x")] + ), + ) + result = convert_sheet_keys_to_alpha(sheet) + assert result.merged_ranges == ["A1:C3", "Z10:AA10"] + assert result.merged_cells is None + + def test_no_merged_cells_keeps_none(self) -> None: + sheet = SheetData( + rows=[CellRow(r=1, c={"0": "v"}, links=None)], merged_cells=None + ) + result = convert_sheet_keys_to_alpha(sheet) + assert result.merged_cells is None + assert result.merged_ranges == [] + + +class TestConvertWorkbookKeysToAlpha: + """Tests for convert_workbook_keys_to_alpha().""" + + def test_converts_all_sheets(self) -> None: + wb = WorkbookData( + book_name="test.xlsx", + sheets={ + "Sheet1": SheetData( + rows=[CellRow(r=1, c={"0": "a", "1": "b"}, links=None)] + ), + "Sheet2": SheetData(rows=[CellRow(r=1, c={"2": "c"}, links=None)]), + }, + ) + result = convert_workbook_keys_to_alpha(wb) + assert result.sheets["Sheet1"].rows[0].c == {"A": "a", "B": "b"} + assert result.sheets["Sheet2"].rows[0].c == {"C": "c"} + assert result.book_name == "test.xlsx" + + def test_empty_workbook(self) -> None: + wb = WorkbookData(book_name="empty.xlsx", sheets={}) + result = convert_workbook_keys_to_alpha(wb) + assert result.sheets == {} diff --git a/uv.lock b/uv.lock index 4d99c7a..02a67b9 100644 --- a/uv.lock +++ b/uv.lock @@ -2,8 +2,12 @@ version = 1 revision = 3 requires-python = ">=3.11" resolution-markers = [ - "python_full_version >= '3.12'", - "python_full_version < '3.12'", + "python_full_version >= '3.14' and sys_platform == 'win32'", + "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", + "python_full_version < '3.14' and sys_platform == 'win32'", + "python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version < '3.14' and sys_platform != 'emscripten' and sys_platform != 'win32'", ] [manifest] @@ -12,6 +16,15 @@ members = [ "exstruct", ] +[[package]] +name = "annotated-doc" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" }, +] + [[package]] name = "annotated-types" version = "0.7.0" @@ -39,7 +52,7 @@ name = "appscript" version = "1.4.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "lxml" }, + { name = "lxml", marker = "sys_platform != 'emscripten' and sys_platform != 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ca/52/2fa70edfd98f0058219ecc2e365a3ba7aabd42db14ff9d7f44bbdcc5400d/appscript-1.4.0.tar.gz", hash = "sha256:b2c6fc770bf822ea45529c7084bc0ee340e67ab260016b01d28e0449ec8723be", size = 295279, upload-time = "2025-10-08T07:56:39.126Z" } wheels = [ @@ -64,25 +77,25 @@ wheels = [ [[package]] name = "babel" -version = "2.17.0" +version = "2.18.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852, upload-time = "2025-02-01T15:17:41.026Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554, upload-time = "2026-02-01T12:30:56.078Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537, upload-time = "2025-02-01T15:17:37.39Z" }, + { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845, upload-time = "2026-02-01T12:30:53.445Z" }, ] [[package]] name = "backrefs" -version = "6.1" +version = "6.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/86/e3/bb3a439d5cb255c4774724810ad8073830fac9c9dee123555820c1bcc806/backrefs-6.1.tar.gz", hash = "sha256:3bba1749aafe1db9b915f00e0dd166cba613b6f788ffd63060ac3485dc9be231", size = 7011962, upload-time = "2025-11-15T14:52:08.323Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/a6/e325ec73b638d3ede4421b5445d4a0b8b219481826cc079d510100af356c/backrefs-6.2.tar.gz", hash = "sha256:f44ff4d48808b243b6c0cdc6231e22195c32f77046018141556c66f8bab72a49", size = 7012303, upload-time = "2026-02-16T19:10:15.828Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/ee/c216d52f58ea75b5e1841022bbae24438b19834a29b163cb32aa3a2a7c6e/backrefs-6.1-py310-none-any.whl", hash = "sha256:2a2ccb96302337ce61ee4717ceacfbf26ba4efb1d55af86564b8bbaeda39cac1", size = 381059, upload-time = "2025-11-15T14:51:59.758Z" }, - { url = "https://files.pythonhosted.org/packages/e6/9a/8da246d988ded941da96c7ed945d63e94a445637eaad985a0ed88787cb89/backrefs-6.1-py311-none-any.whl", hash = "sha256:e82bba3875ee4430f4de4b6db19429a27275d95a5f3773c57e9e18abc23fd2b7", size = 392854, upload-time = "2025-11-15T14:52:01.194Z" }, - { url = "https://files.pythonhosted.org/packages/37/c9/fd117a6f9300c62bbc33bc337fd2b3c6bfe28b6e9701de336b52d7a797ad/backrefs-6.1-py312-none-any.whl", hash = "sha256:c64698c8d2269343d88947c0735cb4b78745bd3ba590e10313fbf3f78c34da5a", size = 398770, upload-time = "2025-11-15T14:52:02.584Z" }, - { url = "https://files.pythonhosted.org/packages/eb/95/7118e935b0b0bd3f94dfec2d852fd4e4f4f9757bdb49850519acd245cd3a/backrefs-6.1-py313-none-any.whl", hash = "sha256:4c9d3dc1e2e558965202c012304f33d4e0e477e1c103663fd2c3cc9bb18b0d05", size = 400726, upload-time = "2025-11-15T14:52:04.093Z" }, - { url = "https://files.pythonhosted.org/packages/1d/72/6296bad135bfafd3254ae3648cd152980a424bd6fed64a101af00cc7ba31/backrefs-6.1-py314-none-any.whl", hash = "sha256:13eafbc9ccd5222e9c1f0bec563e6d2a6d21514962f11e7fc79872fd56cbc853", size = 412584, upload-time = "2025-11-15T14:52:05.233Z" }, - { url = "https://files.pythonhosted.org/packages/02/e3/a4fa1946722c4c7b063cc25043a12d9ce9b4323777f89643be74cef2993c/backrefs-6.1-py39-none-any.whl", hash = "sha256:a9e99b8a4867852cad177a6430e31b0f6e495d65f8c6c134b68c14c3c95bf4b0", size = 381058, upload-time = "2025-11-15T14:52:06.698Z" }, + { url = "https://files.pythonhosted.org/packages/1b/39/3765df263e08a4df37f4f43cb5aa3c6c17a4bdd42ecfe841e04c26037171/backrefs-6.2-py310-none-any.whl", hash = "sha256:0fdc7b012420b6b144410342caeb8adc54c6866cf12064abc9bb211302e496f8", size = 381075, upload-time = "2026-02-16T19:10:04.322Z" }, + { url = "https://files.pythonhosted.org/packages/0f/f0/35240571e1b67ffb19dafb29ab34150b6f59f93f717b041082cdb1bfceb1/backrefs-6.2-py311-none-any.whl", hash = "sha256:08aa7fae530c6b2361d7bdcbda1a7c454e330cc9dbcd03f5c23205e430e5c3be", size = 392874, upload-time = "2026-02-16T19:10:06.314Z" }, + { url = "https://files.pythonhosted.org/packages/e3/63/77e8c9745b4d227cce9f5e0a6f68041278c5f9b18588b35905f5f19c1beb/backrefs-6.2-py312-none-any.whl", hash = "sha256:c3f4b9cb2af8cda0d87ab4f57800b57b95428488477be164dd2b47be54db0c90", size = 398787, upload-time = "2026-02-16T19:10:08.274Z" }, + { url = "https://files.pythonhosted.org/packages/c5/71/c754b1737ad99102e03fa3235acb6cb6d3ac9d6f596cbc3e5f236705abd8/backrefs-6.2-py313-none-any.whl", hash = "sha256:12df81596ab511f783b7d87c043ce26bc5b0288cf3bb03610fe76b8189282b2b", size = 400747, upload-time = "2026-02-16T19:10:09.791Z" }, + { url = "https://files.pythonhosted.org/packages/af/75/be12ba31a6eb20dccef2320cd8ccb3f7d9013b68ba4c70156259fee9e409/backrefs-6.2-py314-none-any.whl", hash = "sha256:e5f805ae09819caa1aa0623b4a83790e7028604aa2b8c73ba602c4454e665de7", size = 412602, upload-time = "2026-02-16T19:10:12.317Z" }, + { url = "https://files.pythonhosted.org/packages/21/f8/d02f650c47d05034dcd6f9c8cf94f39598b7a89c00ecda0ecb2911bc27e9/backrefs-6.2-py39-none-any.whl", hash = "sha256:664e33cd88c6840b7625b826ecf2555f32d491800900f5a541f772c485f7cda7", size = 381077, upload-time = "2026-02-16T19:10:13.74Z" }, ] [[package]] @@ -149,11 +162,11 @@ dev = [ [[package]] name = "certifi" -version = "2025.11.12" +version = "2026.1.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538, upload-time = "2025-11-12T02:54:51.517Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/70/7d/9bc192684cea499815ff478dfcdc13835ddf401365057044fb721ec6bddb/certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b", size = 159438, upload-time = "2025-11-12T02:54:49.735Z" }, + { url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" }, ] [[package]] @@ -430,89 +443,101 @@ wheels = [ [[package]] name = "coverage" -version = "7.12.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/89/26/4a96807b193b011588099c3b5c89fbb05294e5b90e71018e065465f34eb6/coverage-7.12.0.tar.gz", hash = "sha256:fc11e0a4e372cb5f282f16ef90d4a585034050ccda536451901abfb19a57f40c", size = 819341, upload-time = "2025-11-18T13:34:20.766Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/0c/0dfe7f0487477d96432e4815537263363fb6dd7289743a796e8e51eabdf2/coverage-7.12.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aa124a3683d2af98bd9d9c2bfa7a5076ca7e5ab09fdb96b81fa7d89376ae928f", size = 217535, upload-time = "2025-11-18T13:32:08.812Z" }, - { url = "https://files.pythonhosted.org/packages/9b/f5/f9a4a053a5bbff023d3bec259faac8f11a1e5a6479c2ccf586f910d8dac7/coverage-7.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d93fbf446c31c0140208dcd07c5d882029832e8ed7891a39d6d44bd65f2316c3", size = 218044, upload-time = "2025-11-18T13:32:10.329Z" }, - { url = "https://files.pythonhosted.org/packages/95/c5/84fc3697c1fa10cd8571919bf9693f693b7373278daaf3b73e328d502bc8/coverage-7.12.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:52ca620260bd8cd6027317bdd8b8ba929be1d741764ee765b42c4d79a408601e", size = 248440, upload-time = "2025-11-18T13:32:12.536Z" }, - { url = "https://files.pythonhosted.org/packages/f4/36/2d93fbf6a04670f3874aed397d5a5371948a076e3249244a9e84fb0e02d6/coverage-7.12.0-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:f3433ffd541380f3a0e423cff0f4926d55b0cc8c1d160fdc3be24a4c03aa65f7", size = 250361, upload-time = "2025-11-18T13:32:13.852Z" }, - { url = "https://files.pythonhosted.org/packages/5d/49/66dc65cc456a6bfc41ea3d0758c4afeaa4068a2b2931bf83be6894cf1058/coverage-7.12.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f7bbb321d4adc9f65e402c677cd1c8e4c2d0105d3ce285b51b4d87f1d5db5245", size = 252472, upload-time = "2025-11-18T13:32:15.068Z" }, - { url = "https://files.pythonhosted.org/packages/35/1f/ebb8a18dffd406db9fcd4b3ae42254aedcaf612470e8712f12041325930f/coverage-7.12.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:22a7aade354a72dff3b59c577bfd18d6945c61f97393bc5fb7bd293a4237024b", size = 248592, upload-time = "2025-11-18T13:32:16.328Z" }, - { url = "https://files.pythonhosted.org/packages/da/a8/67f213c06e5ea3b3d4980df7dc344d7fea88240b5fe878a5dcbdfe0e2315/coverage-7.12.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:3ff651dcd36d2fea66877cd4a82de478004c59b849945446acb5baf9379a1b64", size = 250167, upload-time = "2025-11-18T13:32:17.687Z" }, - { url = "https://files.pythonhosted.org/packages/f0/00/e52aef68154164ea40cc8389c120c314c747fe63a04b013a5782e989b77f/coverage-7.12.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:31b8b2e38391a56e3cea39d22a23faaa7c3fc911751756ef6d2621d2a9daf742", size = 248238, upload-time = "2025-11-18T13:32:19.2Z" }, - { url = "https://files.pythonhosted.org/packages/1f/a4/4d88750bcf9d6d66f77865e5a05a20e14db44074c25fd22519777cb69025/coverage-7.12.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:297bc2da28440f5ae51c845a47c8175a4db0553a53827886e4fb25c66633000c", size = 247964, upload-time = "2025-11-18T13:32:21.027Z" }, - { url = "https://files.pythonhosted.org/packages/a7/6b/b74693158899d5b47b0bf6238d2c6722e20ba749f86b74454fac0696bb00/coverage-7.12.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6ff7651cc01a246908eac162a6a86fc0dbab6de1ad165dfb9a1e2ec660b44984", size = 248862, upload-time = "2025-11-18T13:32:22.304Z" }, - { url = "https://files.pythonhosted.org/packages/18/de/6af6730227ce0e8ade307b1cc4a08e7f51b419a78d02083a86c04ccceb29/coverage-7.12.0-cp311-cp311-win32.whl", hash = "sha256:313672140638b6ddb2c6455ddeda41c6a0b208298034544cfca138978c6baed6", size = 220033, upload-time = "2025-11-18T13:32:23.714Z" }, - { url = "https://files.pythonhosted.org/packages/e2/a1/e7f63021a7c4fe20994359fcdeae43cbef4a4d0ca36a5a1639feeea5d9e1/coverage-7.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:a1783ed5bd0d5938d4435014626568dc7f93e3cb99bc59188cc18857c47aa3c4", size = 220966, upload-time = "2025-11-18T13:32:25.599Z" }, - { url = "https://files.pythonhosted.org/packages/77/e8/deae26453f37c20c3aa0c4433a1e32cdc169bf415cce223a693117aa3ddd/coverage-7.12.0-cp311-cp311-win_arm64.whl", hash = "sha256:4648158fd8dd9381b5847622df1c90ff314efbfc1df4550092ab6013c238a5fc", size = 219637, upload-time = "2025-11-18T13:32:27.265Z" }, - { url = "https://files.pythonhosted.org/packages/02/bf/638c0427c0f0d47638242e2438127f3c8ee3cfc06c7fdeb16778ed47f836/coverage-7.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:29644c928772c78512b48e14156b81255000dcfd4817574ff69def189bcb3647", size = 217704, upload-time = "2025-11-18T13:32:28.906Z" }, - { url = "https://files.pythonhosted.org/packages/08/e1/706fae6692a66c2d6b871a608bbde0da6281903fa0e9f53a39ed441da36a/coverage-7.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8638cbb002eaa5d7c8d04da667813ce1067080b9a91099801a0053086e52b736", size = 218064, upload-time = "2025-11-18T13:32:30.161Z" }, - { url = "https://files.pythonhosted.org/packages/a9/8b/eb0231d0540f8af3ffda39720ff43cb91926489d01524e68f60e961366e4/coverage-7.12.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:083631eeff5eb9992c923e14b810a179798bb598e6a0dd60586819fc23be6e60", size = 249560, upload-time = "2025-11-18T13:32:31.835Z" }, - { url = "https://files.pythonhosted.org/packages/e9/a1/67fb52af642e974d159b5b379e4d4c59d0ebe1288677fbd04bbffe665a82/coverage-7.12.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:99d5415c73ca12d558e07776bd957c4222c687b9f1d26fa0e1b57e3598bdcde8", size = 252318, upload-time = "2025-11-18T13:32:33.178Z" }, - { url = "https://files.pythonhosted.org/packages/41/e5/38228f31b2c7665ebf9bdfdddd7a184d56450755c7e43ac721c11a4b8dab/coverage-7.12.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e949ebf60c717c3df63adb4a1a366c096c8d7fd8472608cd09359e1bd48ef59f", size = 253403, upload-time = "2025-11-18T13:32:34.45Z" }, - { url = "https://files.pythonhosted.org/packages/ec/4b/df78e4c8188f9960684267c5a4897836f3f0f20a20c51606ee778a1d9749/coverage-7.12.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:6d907ddccbca819afa2cd014bc69983b146cca2735a0b1e6259b2a6c10be1e70", size = 249984, upload-time = "2025-11-18T13:32:35.747Z" }, - { url = "https://files.pythonhosted.org/packages/ba/51/bb163933d195a345c6f63eab9e55743413d064c291b6220df754075c2769/coverage-7.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b1518ecbad4e6173f4c6e6c4a46e49555ea5679bf3feda5edb1b935c7c44e8a0", size = 251339, upload-time = "2025-11-18T13:32:37.352Z" }, - { url = "https://files.pythonhosted.org/packages/15/40/c9b29cdb8412c837cdcbc2cfa054547dd83affe6cbbd4ce4fdb92b6ba7d1/coverage-7.12.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:51777647a749abdf6f6fd8c7cffab12de68ab93aab15efc72fbbb83036c2a068", size = 249489, upload-time = "2025-11-18T13:32:39.212Z" }, - { url = "https://files.pythonhosted.org/packages/c8/da/b3131e20ba07a0de4437a50ef3b47840dfabf9293675b0cd5c2c7f66dd61/coverage-7.12.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:42435d46d6461a3b305cdfcad7cdd3248787771f53fe18305548cba474e6523b", size = 249070, upload-time = "2025-11-18T13:32:40.598Z" }, - { url = "https://files.pythonhosted.org/packages/70/81/b653329b5f6302c08d683ceff6785bc60a34be9ae92a5c7b63ee7ee7acec/coverage-7.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:5bcead88c8423e1855e64b8057d0544e33e4080b95b240c2a355334bb7ced937", size = 250929, upload-time = "2025-11-18T13:32:42.915Z" }, - { url = "https://files.pythonhosted.org/packages/a3/00/250ac3bca9f252a5fb1338b5ad01331ebb7b40223f72bef5b1b2cb03aa64/coverage-7.12.0-cp312-cp312-win32.whl", hash = "sha256:dcbb630ab034e86d2a0f79aefd2be07e583202f41e037602d438c80044957baa", size = 220241, upload-time = "2025-11-18T13:32:44.665Z" }, - { url = "https://files.pythonhosted.org/packages/64/1c/77e79e76d37ce83302f6c21980b45e09f8aa4551965213a10e62d71ce0ab/coverage-7.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:2fd8354ed5d69775ac42986a691fbf68b4084278710cee9d7c3eaa0c28fa982a", size = 221051, upload-time = "2025-11-18T13:32:46.008Z" }, - { url = "https://files.pythonhosted.org/packages/31/f5/641b8a25baae564f9e52cac0e2667b123de961985709a004e287ee7663cc/coverage-7.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:737c3814903be30695b2de20d22bcc5428fdae305c61ba44cdc8b3252984c49c", size = 219692, upload-time = "2025-11-18T13:32:47.372Z" }, - { url = "https://files.pythonhosted.org/packages/b8/14/771700b4048774e48d2c54ed0c674273702713c9ee7acdfede40c2666747/coverage-7.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:47324fffca8d8eae7e185b5bb20c14645f23350f870c1649003618ea91a78941", size = 217725, upload-time = "2025-11-18T13:32:49.22Z" }, - { url = "https://files.pythonhosted.org/packages/17/a7/3aa4144d3bcb719bf67b22d2d51c2d577bf801498c13cb08f64173e80497/coverage-7.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ccf3b2ede91decd2fb53ec73c1f949c3e034129d1e0b07798ff1d02ea0c8fa4a", size = 218098, upload-time = "2025-11-18T13:32:50.78Z" }, - { url = "https://files.pythonhosted.org/packages/fc/9c/b846bbc774ff81091a12a10203e70562c91ae71badda00c5ae5b613527b1/coverage-7.12.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:b365adc70a6936c6b0582dc38746b33b2454148c02349345412c6e743efb646d", size = 249093, upload-time = "2025-11-18T13:32:52.554Z" }, - { url = "https://files.pythonhosted.org/packages/76/b6/67d7c0e1f400b32c883e9342de4a8c2ae7c1a0b57c5de87622b7262e2309/coverage-7.12.0-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:bc13baf85cd8a4cfcf4a35c7bc9d795837ad809775f782f697bf630b7e200211", size = 251686, upload-time = "2025-11-18T13:32:54.862Z" }, - { url = "https://files.pythonhosted.org/packages/cc/75/b095bd4b39d49c3be4bffbb3135fea18a99a431c52dd7513637c0762fecb/coverage-7.12.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:099d11698385d572ceafb3288a5b80fe1fc58bf665b3f9d362389de488361d3d", size = 252930, upload-time = "2025-11-18T13:32:56.417Z" }, - { url = "https://files.pythonhosted.org/packages/6e/f3/466f63015c7c80550bead3093aacabf5380c1220a2a93c35d374cae8f762/coverage-7.12.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:473dc45d69694069adb7680c405fb1e81f60b2aff42c81e2f2c3feaf544d878c", size = 249296, upload-time = "2025-11-18T13:32:58.074Z" }, - { url = "https://files.pythonhosted.org/packages/27/86/eba2209bf2b7e28c68698fc13437519a295b2d228ba9e0ec91673e09fa92/coverage-7.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:583f9adbefd278e9de33c33d6846aa8f5d164fa49b47144180a0e037f0688bb9", size = 251068, upload-time = "2025-11-18T13:32:59.646Z" }, - { url = "https://files.pythonhosted.org/packages/ec/55/ca8ae7dbba962a3351f18940b359b94c6bafdd7757945fdc79ec9e452dc7/coverage-7.12.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b2089cc445f2dc0af6f801f0d1355c025b76c24481935303cf1af28f636688f0", size = 249034, upload-time = "2025-11-18T13:33:01.481Z" }, - { url = "https://files.pythonhosted.org/packages/7a/d7/39136149325cad92d420b023b5fd900dabdd1c3a0d1d5f148ef4a8cedef5/coverage-7.12.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:950411f1eb5d579999c5f66c62a40961f126fc71e5e14419f004471957b51508", size = 248853, upload-time = "2025-11-18T13:33:02.935Z" }, - { url = "https://files.pythonhosted.org/packages/fe/b6/76e1add8b87ef60e00643b0b7f8f7bb73d4bf5249a3be19ebefc5793dd25/coverage-7.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b1aab7302a87bafebfe76b12af681b56ff446dc6f32ed178ff9c092ca776e6bc", size = 250619, upload-time = "2025-11-18T13:33:04.336Z" }, - { url = "https://files.pythonhosted.org/packages/95/87/924c6dc64f9203f7a3c1832a6a0eee5a8335dbe5f1bdadcc278d6f1b4d74/coverage-7.12.0-cp313-cp313-win32.whl", hash = "sha256:d7e0d0303c13b54db495eb636bc2465b2fb8475d4c8bcec8fe4b5ca454dfbae8", size = 220261, upload-time = "2025-11-18T13:33:06.493Z" }, - { url = "https://files.pythonhosted.org/packages/91/77/dd4aff9af16ff776bf355a24d87eeb48fc6acde54c907cc1ea89b14a8804/coverage-7.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:ce61969812d6a98a981d147d9ac583a36ac7db7766f2e64a9d4d059c2fe29d07", size = 221072, upload-time = "2025-11-18T13:33:07.926Z" }, - { url = "https://files.pythonhosted.org/packages/70/49/5c9dc46205fef31b1b226a6e16513193715290584317fd4df91cdaf28b22/coverage-7.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:bcec6f47e4cb8a4c2dc91ce507f6eefc6a1b10f58df32cdc61dff65455031dfc", size = 219702, upload-time = "2025-11-18T13:33:09.631Z" }, - { url = "https://files.pythonhosted.org/packages/9b/62/f87922641c7198667994dd472a91e1d9b829c95d6c29529ceb52132436ad/coverage-7.12.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:459443346509476170d553035e4a3eed7b860f4fe5242f02de1010501956ce87", size = 218420, upload-time = "2025-11-18T13:33:11.153Z" }, - { url = "https://files.pythonhosted.org/packages/85/dd/1cc13b2395ef15dbb27d7370a2509b4aee77890a464fb35d72d428f84871/coverage-7.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:04a79245ab2b7a61688958f7a855275997134bc84f4a03bc240cf64ff132abf6", size = 218773, upload-time = "2025-11-18T13:33:12.569Z" }, - { url = "https://files.pythonhosted.org/packages/74/40/35773cc4bb1e9d4658d4fb669eb4195b3151bef3bbd6f866aba5cd5dac82/coverage-7.12.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:09a86acaaa8455f13d6a99221d9654df249b33937b4e212b4e5a822065f12aa7", size = 260078, upload-time = "2025-11-18T13:33:14.037Z" }, - { url = "https://files.pythonhosted.org/packages/ec/ee/231bb1a6ffc2905e396557585ebc6bdc559e7c66708376d245a1f1d330fc/coverage-7.12.0-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:907e0df1b71ba77463687a74149c6122c3f6aac56c2510a5d906b2f368208560", size = 262144, upload-time = "2025-11-18T13:33:15.601Z" }, - { url = "https://files.pythonhosted.org/packages/28/be/32f4aa9f3bf0b56f3971001b56508352c7753915345d45fab4296a986f01/coverage-7.12.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9b57e2d0ddd5f0582bae5437c04ee71c46cd908e7bc5d4d0391f9a41e812dd12", size = 264574, upload-time = "2025-11-18T13:33:17.354Z" }, - { url = "https://files.pythonhosted.org/packages/68/7c/00489fcbc2245d13ab12189b977e0cf06ff3351cb98bc6beba8bd68c5902/coverage-7.12.0-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:58c1c6aa677f3a1411fe6fb28ec3a942e4f665df036a3608816e0847fad23296", size = 259298, upload-time = "2025-11-18T13:33:18.958Z" }, - { url = "https://files.pythonhosted.org/packages/96/b4/f0760d65d56c3bea95b449e02570d4abd2549dc784bf39a2d4721a2d8ceb/coverage-7.12.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4c589361263ab2953e3c4cd2a94db94c4ad4a8e572776ecfbad2389c626e4507", size = 262150, upload-time = "2025-11-18T13:33:20.644Z" }, - { url = "https://files.pythonhosted.org/packages/c5/71/9a9314df00f9326d78c1e5a910f520d599205907432d90d1c1b7a97aa4b1/coverage-7.12.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:91b810a163ccad2e43b1faa11d70d3cf4b6f3d83f9fd5f2df82a32d47b648e0d", size = 259763, upload-time = "2025-11-18T13:33:22.189Z" }, - { url = "https://files.pythonhosted.org/packages/10/34/01a0aceed13fbdf925876b9a15d50862eb8845454301fe3cdd1df08b2182/coverage-7.12.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:40c867af715f22592e0d0fb533a33a71ec9e0f73a6945f722a0c85c8c1cbe3a2", size = 258653, upload-time = "2025-11-18T13:33:24.239Z" }, - { url = "https://files.pythonhosted.org/packages/8d/04/81d8fd64928acf1574bbb0181f66901c6c1c6279c8ccf5f84259d2c68ae9/coverage-7.12.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:68b0d0a2d84f333de875666259dadf28cc67858bc8fd8b3f1eae84d3c2bec455", size = 260856, upload-time = "2025-11-18T13:33:26.365Z" }, - { url = "https://files.pythonhosted.org/packages/f2/76/fa2a37bfaeaf1f766a2d2360a25a5297d4fb567098112f6517475eee120b/coverage-7.12.0-cp313-cp313t-win32.whl", hash = "sha256:73f9e7fbd51a221818fd11b7090eaa835a353ddd59c236c57b2199486b116c6d", size = 220936, upload-time = "2025-11-18T13:33:28.165Z" }, - { url = "https://files.pythonhosted.org/packages/f9/52/60f64d932d555102611c366afb0eb434b34266b1d9266fc2fe18ab641c47/coverage-7.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:24cff9d1f5743f67db7ba46ff284018a6e9aeb649b67aa1e70c396aa1b7cb23c", size = 222001, upload-time = "2025-11-18T13:33:29.656Z" }, - { url = "https://files.pythonhosted.org/packages/77/df/c303164154a5a3aea7472bf323b7c857fed93b26618ed9fc5c2955566bb0/coverage-7.12.0-cp313-cp313t-win_arm64.whl", hash = "sha256:c87395744f5c77c866d0f5a43d97cc39e17c7f1cb0115e54a2fe67ca75c5d14d", size = 220273, upload-time = "2025-11-18T13:33:31.415Z" }, - { url = "https://files.pythonhosted.org/packages/bf/2e/fc12db0883478d6e12bbd62d481210f0c8daf036102aa11434a0c5755825/coverage-7.12.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a1c59b7dc169809a88b21a936eccf71c3895a78f5592051b1af8f4d59c2b4f92", size = 217777, upload-time = "2025-11-18T13:33:32.86Z" }, - { url = "https://files.pythonhosted.org/packages/1f/c1/ce3e525d223350c6ec16b9be8a057623f54226ef7f4c2fee361ebb6a02b8/coverage-7.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8787b0f982e020adb732b9f051f3e49dd5054cebbc3f3432061278512a2b1360", size = 218100, upload-time = "2025-11-18T13:33:34.532Z" }, - { url = "https://files.pythonhosted.org/packages/15/87/113757441504aee3808cb422990ed7c8bcc2d53a6779c66c5adef0942939/coverage-7.12.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5ea5a9f7dc8877455b13dd1effd3202e0bca72f6f3ab09f9036b1bcf728f69ac", size = 249151, upload-time = "2025-11-18T13:33:36.135Z" }, - { url = "https://files.pythonhosted.org/packages/d9/1d/9529d9bd44049b6b05bb319c03a3a7e4b0a8a802d28fa348ad407e10706d/coverage-7.12.0-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:fdba9f15849534594f60b47c9a30bc70409b54947319a7c4fd0e8e3d8d2f355d", size = 251667, upload-time = "2025-11-18T13:33:37.996Z" }, - { url = "https://files.pythonhosted.org/packages/11/bb/567e751c41e9c03dc29d3ce74b8c89a1e3396313e34f255a2a2e8b9ebb56/coverage-7.12.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a00594770eb715854fb1c57e0dea08cce6720cfbc531accdb9850d7c7770396c", size = 253003, upload-time = "2025-11-18T13:33:39.553Z" }, - { url = "https://files.pythonhosted.org/packages/e4/b3/c2cce2d8526a02fb9e9ca14a263ca6fc074449b33a6afa4892838c903528/coverage-7.12.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:5560c7e0d82b42eb1951e4f68f071f8017c824ebfd5a6ebe42c60ac16c6c2434", size = 249185, upload-time = "2025-11-18T13:33:42.086Z" }, - { url = "https://files.pythonhosted.org/packages/0e/a7/967f93bb66e82c9113c66a8d0b65ecf72fc865adfba5a145f50c7af7e58d/coverage-7.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d6c2e26b481c9159c2773a37947a9718cfdc58893029cdfb177531793e375cfc", size = 251025, upload-time = "2025-11-18T13:33:43.634Z" }, - { url = "https://files.pythonhosted.org/packages/b9/b2/f2f6f56337bc1af465d5b2dc1ee7ee2141b8b9272f3bf6213fcbc309a836/coverage-7.12.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:6e1a8c066dabcde56d5d9fed6a66bc19a2883a3fe051f0c397a41fc42aedd4cc", size = 248979, upload-time = "2025-11-18T13:33:46.04Z" }, - { url = "https://files.pythonhosted.org/packages/f4/7a/bf4209f45a4aec09d10a01a57313a46c0e0e8f4c55ff2965467d41a92036/coverage-7.12.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:f7ba9da4726e446d8dd8aae5a6cd872511184a5d861de80a86ef970b5dacce3e", size = 248800, upload-time = "2025-11-18T13:33:47.546Z" }, - { url = "https://files.pythonhosted.org/packages/b8/b7/1e01b8696fb0521810f60c5bbebf699100d6754183e6cc0679bf2ed76531/coverage-7.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e0f483ab4f749039894abaf80c2f9e7ed77bbf3c737517fb88c8e8e305896a17", size = 250460, upload-time = "2025-11-18T13:33:49.537Z" }, - { url = "https://files.pythonhosted.org/packages/71/ae/84324fb9cb46c024760e706353d9b771a81b398d117d8c1fe010391c186f/coverage-7.12.0-cp314-cp314-win32.whl", hash = "sha256:76336c19a9ef4a94b2f8dc79f8ac2da3f193f625bb5d6f51a328cd19bfc19933", size = 220533, upload-time = "2025-11-18T13:33:51.16Z" }, - { url = "https://files.pythonhosted.org/packages/e2/71/1033629deb8460a8f97f83e6ac4ca3b93952e2b6f826056684df8275e015/coverage-7.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:7c1059b600aec6ef090721f8f633f60ed70afaffe8ecab85b59df748f24b31fe", size = 221348, upload-time = "2025-11-18T13:33:52.776Z" }, - { url = "https://files.pythonhosted.org/packages/0a/5f/ac8107a902f623b0c251abdb749be282dc2ab61854a8a4fcf49e276fce2f/coverage-7.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:172cf3a34bfef42611963e2b661302a8931f44df31629e5b1050567d6b90287d", size = 219922, upload-time = "2025-11-18T13:33:54.316Z" }, - { url = "https://files.pythonhosted.org/packages/79/6e/f27af2d4da367f16077d21ef6fe796c874408219fa6dd3f3efe7751bd910/coverage-7.12.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:aa7d48520a32cb21c7a9b31f81799e8eaec7239db36c3b670be0fa2403828d1d", size = 218511, upload-time = "2025-11-18T13:33:56.343Z" }, - { url = "https://files.pythonhosted.org/packages/67/dd/65fd874aa460c30da78f9d259400d8e6a4ef457d61ab052fd248f0050558/coverage-7.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:90d58ac63bc85e0fb919f14d09d6caa63f35a5512a2205284b7816cafd21bb03", size = 218771, upload-time = "2025-11-18T13:33:57.966Z" }, - { url = "https://files.pythonhosted.org/packages/55/e0/7c6b71d327d8068cb79c05f8f45bf1b6145f7a0de23bbebe63578fe5240a/coverage-7.12.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:ca8ecfa283764fdda3eae1bdb6afe58bf78c2c3ec2b2edcb05a671f0bba7b3f9", size = 260151, upload-time = "2025-11-18T13:33:59.597Z" }, - { url = "https://files.pythonhosted.org/packages/49/ce/4697457d58285b7200de6b46d606ea71066c6e674571a946a6ea908fb588/coverage-7.12.0-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:874fe69a0785d96bd066059cd4368022cebbec1a8958f224f0016979183916e6", size = 262257, upload-time = "2025-11-18T13:34:01.166Z" }, - { url = "https://files.pythonhosted.org/packages/2f/33/acbc6e447aee4ceba88c15528dbe04a35fb4d67b59d393d2e0d6f1e242c1/coverage-7.12.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5b3c889c0b8b283a24d721a9eabc8ccafcfc3aebf167e4cd0d0e23bf8ec4e339", size = 264671, upload-time = "2025-11-18T13:34:02.795Z" }, - { url = "https://files.pythonhosted.org/packages/87/ec/e2822a795c1ed44d569980097be839c5e734d4c0c1119ef8e0a073496a30/coverage-7.12.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8bb5b894b3ec09dcd6d3743229dc7f2c42ef7787dc40596ae04c0edda487371e", size = 259231, upload-time = "2025-11-18T13:34:04.397Z" }, - { url = "https://files.pythonhosted.org/packages/72/c5/a7ec5395bb4a49c9b7ad97e63f0c92f6bf4a9e006b1393555a02dae75f16/coverage-7.12.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:79a44421cd5fba96aa57b5e3b5a4d3274c449d4c622e8f76882d76635501fd13", size = 262137, upload-time = "2025-11-18T13:34:06.068Z" }, - { url = "https://files.pythonhosted.org/packages/67/0c/02c08858b764129f4ecb8e316684272972e60777ae986f3865b10940bdd6/coverage-7.12.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:33baadc0efd5c7294f436a632566ccc1f72c867f82833eb59820ee37dc811c6f", size = 259745, upload-time = "2025-11-18T13:34:08.04Z" }, - { url = "https://files.pythonhosted.org/packages/5a/04/4fd32b7084505f3829a8fe45c1a74a7a728cb251aaadbe3bec04abcef06d/coverage-7.12.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:c406a71f544800ef7e9e0000af706b88465f3573ae8b8de37e5f96c59f689ad1", size = 258570, upload-time = "2025-11-18T13:34:09.676Z" }, - { url = "https://files.pythonhosted.org/packages/48/35/2365e37c90df4f5342c4fa202223744119fe31264ee2924f09f074ea9b6d/coverage-7.12.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e71bba6a40883b00c6d571599b4627f50c360b3d0d02bfc658168936be74027b", size = 260899, upload-time = "2025-11-18T13:34:11.259Z" }, - { url = "https://files.pythonhosted.org/packages/05/56/26ab0464ca733fa325e8e71455c58c1c374ce30f7c04cebb88eabb037b18/coverage-7.12.0-cp314-cp314t-win32.whl", hash = "sha256:9157a5e233c40ce6613dead4c131a006adfda70e557b6856b97aceed01b0e27a", size = 221313, upload-time = "2025-11-18T13:34:12.863Z" }, - { url = "https://files.pythonhosted.org/packages/da/1c/017a3e1113ed34d998b27d2c6dba08a9e7cb97d362f0ec988fcd873dcf81/coverage-7.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:e84da3a0fd233aeec797b981c51af1cabac74f9bd67be42458365b30d11b5291", size = 222423, upload-time = "2025-11-18T13:34:15.14Z" }, - { url = "https://files.pythonhosted.org/packages/4c/36/bcc504fdd5169301b52568802bb1b9cdde2e27a01d39fbb3b4b508ab7c2c/coverage-7.12.0-cp314-cp314t-win_arm64.whl", hash = "sha256:01d24af36fedda51c2b1aca56e4330a3710f83b02a5ff3743a6b015ffa7c9384", size = 220459, upload-time = "2025-11-18T13:34:17.222Z" }, - { url = "https://files.pythonhosted.org/packages/ce/a3/43b749004e3c09452e39bb56347a008f0a0668aad37324a99b5c8ca91d9e/coverage-7.12.0-py3-none-any.whl", hash = "sha256:159d50c0b12e060b15ed3d39f87ed43d4f7f7ad40b8a534f4dd331adbb51104a", size = 209503, upload-time = "2025-11-18T13:34:18.892Z" }, +version = "7.13.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/56/95b7e30fa389756cb56630faa728da46a27b8c6eb46f9d557c68fff12b65/coverage-7.13.4.tar.gz", hash = "sha256:e5c8f6ed1e61a8b2dcdf31eb0b9bbf0130750ca79c1c49eb898e2ad86f5ccc91", size = 827239, upload-time = "2026-02-09T12:59:03.86Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b4/ad/b59e5b451cf7172b8d1043dc0fa718f23aab379bc1521ee13d4bd9bfa960/coverage-7.13.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d490ba50c3f35dd7c17953c68f3270e7ccd1c6642e2d2afe2d8e720b98f5a053", size = 219278, upload-time = "2026-02-09T12:56:31.673Z" }, + { url = "https://files.pythonhosted.org/packages/f1/17/0cb7ca3de72e5f4ef2ec2fa0089beafbcaaaead1844e8b8a63d35173d77d/coverage-7.13.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:19bc3c88078789f8ef36acb014d7241961dbf883fd2533d18cb1e7a5b4e28b11", size = 219783, upload-time = "2026-02-09T12:56:33.104Z" }, + { url = "https://files.pythonhosted.org/packages/ab/63/325d8e5b11e0eaf6d0f6a44fad444ae58820929a9b0de943fa377fe73e85/coverage-7.13.4-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:3998e5a32e62fdf410c0dbd3115df86297995d6e3429af80b8798aad894ca7aa", size = 250200, upload-time = "2026-02-09T12:56:34.474Z" }, + { url = "https://files.pythonhosted.org/packages/76/53/c16972708cbb79f2942922571a687c52bd109a7bd51175aeb7558dff2236/coverage-7.13.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8e264226ec98e01a8e1054314af91ee6cde0eacac4f465cc93b03dbe0bce2fd7", size = 252114, upload-time = "2026-02-09T12:56:35.749Z" }, + { url = "https://files.pythonhosted.org/packages/eb/c2/7ab36d8b8cc412bec9ea2d07c83c48930eb4ba649634ba00cb7e4e0f9017/coverage-7.13.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a3aa4e7b9e416774b21797365b358a6e827ffadaaca81b69ee02946852449f00", size = 254220, upload-time = "2026-02-09T12:56:37.796Z" }, + { url = "https://files.pythonhosted.org/packages/d6/4d/cf52c9a3322c89a0e6febdfbc83bb45c0ed3c64ad14081b9503adee702e7/coverage-7.13.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:71ca20079dd8f27fcf808817e281e90220475cd75115162218d0e27549f95fef", size = 256164, upload-time = "2026-02-09T12:56:39.016Z" }, + { url = "https://files.pythonhosted.org/packages/78/e9/eb1dd17bd6de8289df3580e967e78294f352a5df8a57ff4671ee5fc3dcd0/coverage-7.13.4-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e2f25215f1a359ab17320b47bcdaca3e6e6356652e8256f2441e4ef972052903", size = 250325, upload-time = "2026-02-09T12:56:40.668Z" }, + { url = "https://files.pythonhosted.org/packages/71/07/8c1542aa873728f72267c07278c5cc0ec91356daf974df21335ccdb46368/coverage-7.13.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d65b2d373032411e86960604dc4edac91fdfb5dca539461cf2cbe78327d1e64f", size = 251913, upload-time = "2026-02-09T12:56:41.97Z" }, + { url = "https://files.pythonhosted.org/packages/74/d7/c62e2c5e4483a748e27868e4c32ad3daa9bdddbba58e1bc7a15e252baa74/coverage-7.13.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:94eb63f9b363180aff17de3e7c8760c3ba94664ea2695c52f10111244d16a299", size = 249974, upload-time = "2026-02-09T12:56:43.323Z" }, + { url = "https://files.pythonhosted.org/packages/98/9f/4c5c015a6e98ced54efd0f5cf8d31b88e5504ecb6857585fc0161bb1e600/coverage-7.13.4-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e856bf6616714c3a9fbc270ab54103f4e685ba236fa98c054e8f87f266c93505", size = 253741, upload-time = "2026-02-09T12:56:45.155Z" }, + { url = "https://files.pythonhosted.org/packages/bd/59/0f4eef89b9f0fcd9633b5d350016f54126ab49426a70ff4c4e87446cabdc/coverage-7.13.4-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:65dfcbe305c3dfe658492df2d85259e0d79ead4177f9ae724b6fb245198f55d6", size = 249695, upload-time = "2026-02-09T12:56:46.636Z" }, + { url = "https://files.pythonhosted.org/packages/b5/2c/b7476f938deb07166f3eb281a385c262675d688ff4659ad56c6c6b8e2e70/coverage-7.13.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b507778ae8a4c915436ed5c2e05b4a6cecfa70f734e19c22a005152a11c7b6a9", size = 250599, upload-time = "2026-02-09T12:56:48.13Z" }, + { url = "https://files.pythonhosted.org/packages/b8/34/c3420709d9846ee3785b9f2831b4d94f276f38884032dca1457fa83f7476/coverage-7.13.4-cp311-cp311-win32.whl", hash = "sha256:784fc3cf8be001197b652d51d3fd259b1e2262888693a4636e18879f613a62a9", size = 221780, upload-time = "2026-02-09T12:56:50.479Z" }, + { url = "https://files.pythonhosted.org/packages/61/08/3d9c8613079d2b11c185b865de9a4c1a68850cfda2b357fae365cf609f29/coverage-7.13.4-cp311-cp311-win_amd64.whl", hash = "sha256:2421d591f8ca05b308cf0092807308b2facbefe54af7c02ac22548b88b95c98f", size = 222715, upload-time = "2026-02-09T12:56:51.815Z" }, + { url = "https://files.pythonhosted.org/packages/18/1a/54c3c80b2f056164cc0a6cdcb040733760c7c4be9d780fe655f356f433e4/coverage-7.13.4-cp311-cp311-win_arm64.whl", hash = "sha256:79e73a76b854d9c6088fe5d8b2ebe745f8681c55f7397c3c0a016192d681045f", size = 221385, upload-time = "2026-02-09T12:56:53.194Z" }, + { url = "https://files.pythonhosted.org/packages/d1/81/4ce2fdd909c5a0ed1f6dedb88aa57ab79b6d1fbd9b588c1ac7ef45659566/coverage-7.13.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:02231499b08dabbe2b96612993e5fc34217cdae907a51b906ac7fca8027a4459", size = 219449, upload-time = "2026-02-09T12:56:54.889Z" }, + { url = "https://files.pythonhosted.org/packages/5d/96/5238b1efc5922ddbdc9b0db9243152c09777804fb7c02ad1741eb18a11c0/coverage-7.13.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40aa8808140e55dc022b15d8aa7f651b6b3d68b365ea0398f1441e0b04d859c3", size = 219810, upload-time = "2026-02-09T12:56:56.33Z" }, + { url = "https://files.pythonhosted.org/packages/78/72/2f372b726d433c9c35e56377cf1d513b4c16fe51841060d826b95caacec1/coverage-7.13.4-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5b856a8ccf749480024ff3bd7310adaef57bf31fd17e1bfc404b7940b6986634", size = 251308, upload-time = "2026-02-09T12:56:57.858Z" }, + { url = "https://files.pythonhosted.org/packages/5d/a0/2ea570925524ef4e00bb6c82649f5682a77fac5ab910a65c9284de422600/coverage-7.13.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c048ea43875fbf8b45d476ad79f179809c590ec7b79e2035c662e7afa3192e3", size = 254052, upload-time = "2026-02-09T12:56:59.754Z" }, + { url = "https://files.pythonhosted.org/packages/e8/ac/45dc2e19a1939098d783c846e130b8f862fbb50d09e0af663988f2f21973/coverage-7.13.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b7b38448866e83176e28086674fe7368ab8590e4610fb662b44e345b86d63ffa", size = 255165, upload-time = "2026-02-09T12:57:01.287Z" }, + { url = "https://files.pythonhosted.org/packages/2d/4d/26d236ff35abc3b5e63540d3386e4c3b192168c1d96da5cb2f43c640970f/coverage-7.13.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:de6defc1c9badbf8b9e67ae90fd00519186d6ab64e5cc5f3d21359c2a9b2c1d3", size = 257432, upload-time = "2026-02-09T12:57:02.637Z" }, + { url = "https://files.pythonhosted.org/packages/ec/55/14a966c757d1348b2e19caf699415a2a4c4f7feaa4bbc6326a51f5c7dd1b/coverage-7.13.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:7eda778067ad7ffccd23ecffce537dface96212576a07924cbf0d8799d2ded5a", size = 251716, upload-time = "2026-02-09T12:57:04.056Z" }, + { url = "https://files.pythonhosted.org/packages/77/33/50116647905837c66d28b2af1321b845d5f5d19be9655cb84d4a0ea806b4/coverage-7.13.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e87f6c587c3f34356c3759f0420693e35e7eb0e2e41e4c011cb6ec6ecbbf1db7", size = 253089, upload-time = "2026-02-09T12:57:05.503Z" }, + { url = "https://files.pythonhosted.org/packages/c2/b4/8efb11a46e3665d92635a56e4f2d4529de6d33f2cb38afd47d779d15fc99/coverage-7.13.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:8248977c2e33aecb2ced42fef99f2d319e9904a36e55a8a68b69207fb7e43edc", size = 251232, upload-time = "2026-02-09T12:57:06.879Z" }, + { url = "https://files.pythonhosted.org/packages/51/24/8cd73dd399b812cc76bb0ac260e671c4163093441847ffe058ac9fda1e32/coverage-7.13.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:25381386e80ae727608e662474db537d4df1ecd42379b5ba33c84633a2b36d47", size = 255299, upload-time = "2026-02-09T12:57:08.245Z" }, + { url = "https://files.pythonhosted.org/packages/03/94/0a4b12f1d0e029ce1ccc1c800944a9984cbe7d678e470bb6d3c6bc38a0da/coverage-7.13.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:ee756f00726693e5ba94d6df2bdfd64d4852d23b09bb0bc700e3b30e6f333985", size = 250796, upload-time = "2026-02-09T12:57:10.142Z" }, + { url = "https://files.pythonhosted.org/packages/73/44/6002fbf88f6698ca034360ce474c406be6d5a985b3fdb3401128031eef6b/coverage-7.13.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fdfc1e28e7c7cdce44985b3043bc13bbd9c747520f94a4d7164af8260b3d91f0", size = 252673, upload-time = "2026-02-09T12:57:12.197Z" }, + { url = "https://files.pythonhosted.org/packages/de/c6/a0279f7c00e786be75a749a5674e6fa267bcbd8209cd10c9a450c655dfa7/coverage-7.13.4-cp312-cp312-win32.whl", hash = "sha256:01d4cbc3c283a17fc1e42d614a119f7f438eabb593391283adca8dc86eff1246", size = 221990, upload-time = "2026-02-09T12:57:14.085Z" }, + { url = "https://files.pythonhosted.org/packages/77/4e/c0a25a425fcf5557d9abd18419c95b63922e897bc86c1f327f155ef234a9/coverage-7.13.4-cp312-cp312-win_amd64.whl", hash = "sha256:9401ebc7ef522f01d01d45532c68c5ac40fb27113019b6b7d8b208f6e9baa126", size = 222800, upload-time = "2026-02-09T12:57:15.944Z" }, + { url = "https://files.pythonhosted.org/packages/47/ac/92da44ad9a6f4e3a7debd178949d6f3769bedca33830ce9b1dcdab589a37/coverage-7.13.4-cp312-cp312-win_arm64.whl", hash = "sha256:b1ec7b6b6e93255f952e27ab58fbc68dcc468844b16ecbee881aeb29b6ab4d8d", size = 221415, upload-time = "2026-02-09T12:57:17.497Z" }, + { url = "https://files.pythonhosted.org/packages/db/23/aad45061a31677d68e47499197a131eea55da4875d16c1f42021ab963503/coverage-7.13.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b66a2da594b6068b48b2692f043f35d4d3693fb639d5ea8b39533c2ad9ac3ab9", size = 219474, upload-time = "2026-02-09T12:57:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/a5/70/9b8b67a0945f3dfec1fd896c5cefb7c19d5a3a6d74630b99a895170999ae/coverage-7.13.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3599eb3992d814d23b35c536c28df1a882caa950f8f507cef23d1cbf334995ac", size = 219844, upload-time = "2026-02-09T12:57:20.66Z" }, + { url = "https://files.pythonhosted.org/packages/97/fd/7e859f8fab324cef6c4ad7cff156ca7c489fef9179d5749b0c8d321281c2/coverage-7.13.4-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:93550784d9281e374fb5a12bf1324cc8a963fd63b2d2f223503ef0fd4aa339ea", size = 250832, upload-time = "2026-02-09T12:57:22.007Z" }, + { url = "https://files.pythonhosted.org/packages/e4/dc/b2442d10020c2f52617828862d8b6ee337859cd8f3a1f13d607dddda9cf7/coverage-7.13.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b720ce6a88a2755f7c697c23268ddc47a571b88052e6b155224347389fdf6a3b", size = 253434, upload-time = "2026-02-09T12:57:23.339Z" }, + { url = "https://files.pythonhosted.org/packages/5a/88/6728a7ad17428b18d836540630487231f5470fb82454871149502f5e5aa2/coverage-7.13.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7b322db1284a2ed3aa28ffd8ebe3db91c929b7a333c0820abec3d838ef5b3525", size = 254676, upload-time = "2026-02-09T12:57:24.774Z" }, + { url = "https://files.pythonhosted.org/packages/7c/bc/21244b1b8cedf0dff0a2b53b208015fe798d5f2a8d5348dbfece04224fff/coverage-7.13.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f4594c67d8a7c89cf922d9df0438c7c7bb022ad506eddb0fdb2863359ff78242", size = 256807, upload-time = "2026-02-09T12:57:26.125Z" }, + { url = "https://files.pythonhosted.org/packages/97/a0/ddba7ed3251cff51006737a727d84e05b61517d1784a9988a846ba508877/coverage-7.13.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:53d133df809c743eb8bce33b24bcababb371f4441340578cd406e084d94a6148", size = 251058, upload-time = "2026-02-09T12:57:27.614Z" }, + { url = "https://files.pythonhosted.org/packages/9b/55/e289addf7ff54d3a540526f33751951bf0878f3809b47f6dfb3def69c6f7/coverage-7.13.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:76451d1978b95ba6507a039090ba076105c87cc76fc3efd5d35d72093964d49a", size = 252805, upload-time = "2026-02-09T12:57:29.066Z" }, + { url = "https://files.pythonhosted.org/packages/13/4e/cc276b1fa4a59be56d96f1dabddbdc30f4ba22e3b1cd42504c37b3313255/coverage-7.13.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7f57b33491e281e962021de110b451ab8a24182589be17e12a22c79047935e23", size = 250766, upload-time = "2026-02-09T12:57:30.522Z" }, + { url = "https://files.pythonhosted.org/packages/94/44/1093b8f93018f8b41a8cf29636c9292502f05e4a113d4d107d14a3acd044/coverage-7.13.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1731dc33dc276dafc410a885cbf5992f1ff171393e48a21453b78727d090de80", size = 254923, upload-time = "2026-02-09T12:57:31.946Z" }, + { url = "https://files.pythonhosted.org/packages/8b/55/ea2796da2d42257f37dbea1aab239ba9263b31bd91d5527cdd6db5efe174/coverage-7.13.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:bd60d4fe2f6fa7dff9223ca1bbc9f05d2b6697bc5961072e5d3b952d46e1b1ea", size = 250591, upload-time = "2026-02-09T12:57:33.842Z" }, + { url = "https://files.pythonhosted.org/packages/d4/fa/7c4bb72aacf8af5020675aa633e59c1fbe296d22aed191b6a5b711eb2bc7/coverage-7.13.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9181a3ccead280b828fae232df12b16652702b49d41e99d657f46cc7b1f6ec7a", size = 252364, upload-time = "2026-02-09T12:57:35.743Z" }, + { url = "https://files.pythonhosted.org/packages/5c/38/a8d2ec0146479c20bbaa7181b5b455a0c41101eed57f10dd19a78ab44c80/coverage-7.13.4-cp313-cp313-win32.whl", hash = "sha256:f53d492307962561ac7de4cd1de3e363589b000ab69617c6156a16ba7237998d", size = 222010, upload-time = "2026-02-09T12:57:37.25Z" }, + { url = "https://files.pythonhosted.org/packages/e2/0c/dbfafbe90a185943dcfbc766fe0e1909f658811492d79b741523a414a6cc/coverage-7.13.4-cp313-cp313-win_amd64.whl", hash = "sha256:e6f70dec1cc557e52df5306d051ef56003f74d56e9c4dd7ddb07e07ef32a84dd", size = 222818, upload-time = "2026-02-09T12:57:38.734Z" }, + { url = "https://files.pythonhosted.org/packages/04/d1/934918a138c932c90d78301f45f677fb05c39a3112b96fd2c8e60503cdc7/coverage-7.13.4-cp313-cp313-win_arm64.whl", hash = "sha256:fb07dc5da7e849e2ad31a5d74e9bece81f30ecf5a42909d0a695f8bd1874d6af", size = 221438, upload-time = "2026-02-09T12:57:40.223Z" }, + { url = "https://files.pythonhosted.org/packages/52/57/ee93ced533bcb3e6df961c0c6e42da2fc6addae53fb95b94a89b1e33ebd7/coverage-7.13.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:40d74da8e6c4b9ac18b15331c4b5ebc35a17069410cad462ad4f40dcd2d50c0d", size = 220165, upload-time = "2026-02-09T12:57:41.639Z" }, + { url = "https://files.pythonhosted.org/packages/c5/e0/969fc285a6fbdda49d91af278488d904dcd7651b2693872f0ff94e40e84a/coverage-7.13.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4223b4230a376138939a9173f1bdd6521994f2aff8047fae100d6d94d50c5a12", size = 220516, upload-time = "2026-02-09T12:57:44.215Z" }, + { url = "https://files.pythonhosted.org/packages/b1/b8/9531944e16267e2735a30a9641ff49671f07e8138ecf1ca13db9fd2560c7/coverage-7.13.4-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:1d4be36a5114c499f9f1f9195e95ebf979460dbe2d88e6816ea202010ba1c34b", size = 261804, upload-time = "2026-02-09T12:57:45.989Z" }, + { url = "https://files.pythonhosted.org/packages/8a/f3/e63df6d500314a2a60390d1989240d5f27318a7a68fa30ad3806e2a9323e/coverage-7.13.4-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:200dea7d1e8095cc6e98cdabe3fd1d21ab17d3cee6dab00cadbb2fe35d9c15b9", size = 263885, upload-time = "2026-02-09T12:57:47.42Z" }, + { url = "https://files.pythonhosted.org/packages/f3/67/7654810de580e14b37670b60a09c599fa348e48312db5b216d730857ffe6/coverage-7.13.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b8eb931ee8e6d8243e253e5ed7336deea6904369d2fd8ae6e43f68abbf167092", size = 266308, upload-time = "2026-02-09T12:57:49.345Z" }, + { url = "https://files.pythonhosted.org/packages/37/6f/39d41eca0eab3cc82115953ad41c4e77935286c930e8fad15eaed1389d83/coverage-7.13.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:75eab1ebe4f2f64d9509b984f9314d4aa788540368218b858dad56dc8f3e5eb9", size = 267452, upload-time = "2026-02-09T12:57:50.811Z" }, + { url = "https://files.pythonhosted.org/packages/50/6d/39c0fbb8fc5cd4d2090811e553c2108cf5112e882f82505ee7495349a6bf/coverage-7.13.4-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c35eb28c1d085eb7d8c9b3296567a1bebe03ce72962e932431b9a61f28facf26", size = 261057, upload-time = "2026-02-09T12:57:52.447Z" }, + { url = "https://files.pythonhosted.org/packages/a4/a2/60010c669df5fa603bb5a97fb75407e191a846510da70ac657eb696b7fce/coverage-7.13.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:eb88b316ec33760714a4720feb2816a3a59180fd58c1985012054fa7aebee4c2", size = 263875, upload-time = "2026-02-09T12:57:53.938Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d9/63b22a6bdbd17f1f96e9ed58604c2a6b0e72a9133e37d663bef185877cf6/coverage-7.13.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7d41eead3cc673cbd38a4417deb7fd0b4ca26954ff7dc6078e33f6ff97bed940", size = 261500, upload-time = "2026-02-09T12:57:56.012Z" }, + { url = "https://files.pythonhosted.org/packages/70/bf/69f86ba1ad85bc3ad240e4c0e57a2e620fbc0e1645a47b5c62f0e941ad7f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:fb26a934946a6afe0e326aebe0730cdff393a8bc0bbb65a2f41e30feddca399c", size = 265212, upload-time = "2026-02-09T12:57:57.5Z" }, + { url = "https://files.pythonhosted.org/packages/ae/f2/5f65a278a8c2148731831574c73e42f57204243d33bedaaf18fa79c5958f/coverage-7.13.4-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:dae88bc0fc77edaa65c14be099bd57ee140cf507e6bfdeea7938457ab387efb0", size = 260398, upload-time = "2026-02-09T12:57:59.027Z" }, + { url = "https://files.pythonhosted.org/packages/ef/80/6e8280a350ee9fea92f14b8357448a242dcaa243cb2c72ab0ca591f66c8c/coverage-7.13.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:845f352911777a8e722bfce168958214951e07e47e5d5d9744109fa5fe77f79b", size = 262584, upload-time = "2026-02-09T12:58:01.129Z" }, + { url = "https://files.pythonhosted.org/packages/22/63/01ff182fc95f260b539590fb12c11ad3e21332c15f9799cb5e2386f71d9f/coverage-7.13.4-cp313-cp313t-win32.whl", hash = "sha256:2fa8d5f8de70688a28240de9e139fa16b153cc3cbb01c5f16d88d6505ebdadf9", size = 222688, upload-time = "2026-02-09T12:58:02.736Z" }, + { url = "https://files.pythonhosted.org/packages/a9/43/89de4ef5d3cd53b886afa114065f7e9d3707bdb3e5efae13535b46ae483d/coverage-7.13.4-cp313-cp313t-win_amd64.whl", hash = "sha256:9351229c8c8407645840edcc277f4a2d44814d1bc34a2128c11c2a031d45a5dd", size = 223746, upload-time = "2026-02-09T12:58:05.362Z" }, + { url = "https://files.pythonhosted.org/packages/35/39/7cf0aa9a10d470a5309b38b289b9bb07ddeac5d61af9b664fe9775a4cb3e/coverage-7.13.4-cp313-cp313t-win_arm64.whl", hash = "sha256:30b8d0512f2dc8c8747557e8fb459d6176a2c9e5731e2b74d311c03b78451997", size = 222003, upload-time = "2026-02-09T12:58:06.952Z" }, + { url = "https://files.pythonhosted.org/packages/92/11/a9cf762bb83386467737d32187756a42094927150c3e107df4cb078e8590/coverage-7.13.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:300deaee342f90696ed186e3a00c71b5b3d27bffe9e827677954f4ee56969601", size = 219522, upload-time = "2026-02-09T12:58:08.623Z" }, + { url = "https://files.pythonhosted.org/packages/d3/28/56e6d892b7b052236d67c95f1936b6a7cf7c3e2634bf27610b8cbd7f9c60/coverage-7.13.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:29e3220258d682b6226a9b0925bc563ed9a1ebcff3cad30f043eceea7eaf2689", size = 219855, upload-time = "2026-02-09T12:58:10.176Z" }, + { url = "https://files.pythonhosted.org/packages/e5/69/233459ee9eb0c0d10fcc2fe425a029b3fa5ce0f040c966ebce851d030c70/coverage-7.13.4-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:391ee8f19bef69210978363ca930f7328081c6a0152f1166c91f0b5fdd2a773c", size = 250887, upload-time = "2026-02-09T12:58:12.503Z" }, + { url = "https://files.pythonhosted.org/packages/06/90/2cdab0974b9b5bbc1623f7876b73603aecac11b8d95b85b5b86b32de5eab/coverage-7.13.4-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0dd7ab8278f0d58a0128ba2fca25824321f05d059c1441800e934ff2efa52129", size = 253396, upload-time = "2026-02-09T12:58:14.615Z" }, + { url = "https://files.pythonhosted.org/packages/ac/15/ea4da0f85bf7d7b27635039e649e99deb8173fe551096ea15017f7053537/coverage-7.13.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78cdf0d578b15148b009ccf18c686aa4f719d887e76e6b40c38ffb61d264a552", size = 254745, upload-time = "2026-02-09T12:58:16.162Z" }, + { url = "https://files.pythonhosted.org/packages/99/11/bb356e86920c655ca4d61daee4e2bbc7258f0a37de0be32d233b561134ff/coverage-7.13.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:48685fee12c2eb3b27c62f2658e7ea21e9c3239cba5a8a242801a0a3f6a8c62a", size = 257055, upload-time = "2026-02-09T12:58:17.892Z" }, + { url = "https://files.pythonhosted.org/packages/c9/0f/9ae1f8cb17029e09da06ca4e28c9e1d5c1c0a511c7074592e37e0836c915/coverage-7.13.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4e83efc079eb39480e6346a15a1bcb3e9b04759c5202d157e1dd4303cd619356", size = 250911, upload-time = "2026-02-09T12:58:19.495Z" }, + { url = "https://files.pythonhosted.org/packages/89/3a/adfb68558fa815cbc29747b553bc833d2150228f251b127f1ce97e48547c/coverage-7.13.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ecae9737b72408d6a950f7e525f30aca12d4bd8dd95e37342e5beb3a2a8c4f71", size = 252754, upload-time = "2026-02-09T12:58:21.064Z" }, + { url = "https://files.pythonhosted.org/packages/32/b1/540d0c27c4e748bd3cd0bd001076ee416eda993c2bae47a73b7cc9357931/coverage-7.13.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ae4578f8528569d3cf303fef2ea569c7f4c4059a38c8667ccef15c6e1f118aa5", size = 250720, upload-time = "2026-02-09T12:58:22.622Z" }, + { url = "https://files.pythonhosted.org/packages/c7/95/383609462b3ffb1fe133014a7c84fc0dd01ed55ac6140fa1093b5af7ebb1/coverage-7.13.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:6fdef321fdfbb30a197efa02d48fcd9981f0d8ad2ae8903ac318adc653f5df98", size = 254994, upload-time = "2026-02-09T12:58:24.548Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ba/1761138e86c81680bfc3c49579d66312865457f9fe405b033184e5793cb3/coverage-7.13.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b0f6ccf3dbe577170bebfce1318707d0e8c3650003cb4b3a9dd744575daa8b5", size = 250531, upload-time = "2026-02-09T12:58:26.271Z" }, + { url = "https://files.pythonhosted.org/packages/f8/8e/05900df797a9c11837ab59c4d6fe94094e029582aab75c3309a93e6fb4e3/coverage-7.13.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75fcd519f2a5765db3f0e391eb3b7d150cce1a771bf4c9f861aeab86c767a3c0", size = 252189, upload-time = "2026-02-09T12:58:27.807Z" }, + { url = "https://files.pythonhosted.org/packages/00/bd/29c9f2db9ea4ed2738b8a9508c35626eb205d51af4ab7bf56a21a2e49926/coverage-7.13.4-cp314-cp314-win32.whl", hash = "sha256:8e798c266c378da2bd819b0677df41ab46d78065fb2a399558f3f6cae78b2fbb", size = 222258, upload-time = "2026-02-09T12:58:29.441Z" }, + { url = "https://files.pythonhosted.org/packages/a7/4d/1f8e723f6829977410efeb88f73673d794075091c8c7c18848d273dc9d73/coverage-7.13.4-cp314-cp314-win_amd64.whl", hash = "sha256:245e37f664d89861cf2329c9afa2c1fe9e6d4e1a09d872c947e70718aeeac505", size = 223073, upload-time = "2026-02-09T12:58:31.026Z" }, + { url = "https://files.pythonhosted.org/packages/51/5b/84100025be913b44e082ea32abcf1afbf4e872f5120b7a1cab1d331b1e13/coverage-7.13.4-cp314-cp314-win_arm64.whl", hash = "sha256:ad27098a189e5838900ce4c2a99f2fe42a0bf0c2093c17c69b45a71579e8d4a2", size = 221638, upload-time = "2026-02-09T12:58:32.599Z" }, + { url = "https://files.pythonhosted.org/packages/a7/e4/c884a405d6ead1370433dad1e3720216b4f9fd8ef5b64bfd984a2a60a11a/coverage-7.13.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:85480adfb35ffc32d40918aad81b89c69c9cc5661a9b8a81476d3e645321a056", size = 220246, upload-time = "2026-02-09T12:58:34.181Z" }, + { url = "https://files.pythonhosted.org/packages/81/5c/4d7ed8b23b233b0fffbc9dfec53c232be2e695468523242ea9fd30f97ad2/coverage-7.13.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:79be69cf7f3bf9b0deeeb062eab7ac7f36cd4cc4c4dd694bd28921ba4d8596cc", size = 220514, upload-time = "2026-02-09T12:58:35.704Z" }, + { url = "https://files.pythonhosted.org/packages/2f/6f/3284d4203fd2f28edd73034968398cd2d4cb04ab192abc8cff007ea35679/coverage-7.13.4-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:caa421e2684e382c5d8973ac55e4f36bed6821a9bad5c953494de960c74595c9", size = 261877, upload-time = "2026-02-09T12:58:37.864Z" }, + { url = "https://files.pythonhosted.org/packages/09/aa/b672a647bbe1556a85337dc95bfd40d146e9965ead9cc2fe81bde1e5cbce/coverage-7.13.4-cp314-cp314t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:14375934243ee05f56c45393fe2ce81fe5cc503c07cee2bdf1725fb8bef3ffaf", size = 264004, upload-time = "2026-02-09T12:58:39.492Z" }, + { url = "https://files.pythonhosted.org/packages/79/a1/aa384dbe9181f98bba87dd23dda436f0c6cf2e148aecbb4e50fc51c1a656/coverage-7.13.4-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:25a41c3104d08edb094d9db0d905ca54d0cd41c928bb6be3c4c799a54753af55", size = 266408, upload-time = "2026-02-09T12:58:41.852Z" }, + { url = "https://files.pythonhosted.org/packages/53/5e/5150bf17b4019bc600799f376bb9606941e55bd5a775dc1e096b6ffea952/coverage-7.13.4-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6f01afcff62bf9a08fb32b2c1d6e924236c0383c02c790732b6537269e466a72", size = 267544, upload-time = "2026-02-09T12:58:44.093Z" }, + { url = "https://files.pythonhosted.org/packages/e0/ed/f1de5c675987a4a7a672250d2c5c9d73d289dbf13410f00ed7181d8017dd/coverage-7.13.4-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:eb9078108fbf0bcdde37c3f4779303673c2fa1fe8f7956e68d447d0dd426d38a", size = 260980, upload-time = "2026-02-09T12:58:45.721Z" }, + { url = "https://files.pythonhosted.org/packages/b3/e3/fe758d01850aa172419a6743fe76ba8b92c29d181d4f676ffe2dae2ba631/coverage-7.13.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0e086334e8537ddd17e5f16a344777c1ab8194986ec533711cbe6c41cde841b6", size = 263871, upload-time = "2026-02-09T12:58:47.334Z" }, + { url = "https://files.pythonhosted.org/packages/b6/76/b829869d464115e22499541def9796b25312b8cf235d3bb00b39f1675395/coverage-7.13.4-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:725d985c5ab621268b2edb8e50dfe57633dc69bda071abc470fed55a14935fd3", size = 261472, upload-time = "2026-02-09T12:58:48.995Z" }, + { url = "https://files.pythonhosted.org/packages/14/9e/caedb1679e73e2f6ad240173f55218488bfe043e38da577c4ec977489915/coverage-7.13.4-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:3c06f0f1337c667b971ca2f975523347e63ec5e500b9aa5882d91931cd3ef750", size = 265210, upload-time = "2026-02-09T12:58:51.178Z" }, + { url = "https://files.pythonhosted.org/packages/3a/10/0dd02cb009b16ede425b49ec344aba13a6ae1dc39600840ea6abcb085ac4/coverage-7.13.4-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:590c0ed4bf8e85f745e6b805b2e1c457b2e33d5255dd9729743165253bc9ad39", size = 260319, upload-time = "2026-02-09T12:58:53.081Z" }, + { url = "https://files.pythonhosted.org/packages/92/8e/234d2c927af27c6d7a5ffad5bd2cf31634c46a477b4c7adfbfa66baf7ebb/coverage-7.13.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:eb30bf180de3f632cd043322dad5751390e5385108b2807368997d1a92a509d0", size = 262638, upload-time = "2026-02-09T12:58:55.258Z" }, + { url = "https://files.pythonhosted.org/packages/2f/64/e5547c8ff6964e5965c35a480855911b61509cce544f4d442caa759a0702/coverage-7.13.4-cp314-cp314t-win32.whl", hash = "sha256:c4240e7eded42d131a2d2c4dec70374b781b043ddc79a9de4d55ca71f8e98aea", size = 223040, upload-time = "2026-02-09T12:58:56.936Z" }, + { url = "https://files.pythonhosted.org/packages/c7/96/38086d58a181aac86d503dfa9c47eb20715a79c3e3acbdf786e92e5c09a8/coverage-7.13.4-cp314-cp314t-win_amd64.whl", hash = "sha256:4c7d3cc01e7350f2f0f6f7036caaf5673fb56b6998889ccfe9e1c1fe75a9c932", size = 224148, upload-time = "2026-02-09T12:58:58.645Z" }, + { url = "https://files.pythonhosted.org/packages/ce/72/8d10abd3740a0beb98c305e0c3faf454366221c0f37a8bcf8f60020bb65a/coverage-7.13.4-cp314-cp314t-win_arm64.whl", hash = "sha256:23e3f687cf945070d1c90f85db66d11e3025665d8dafa831301a0e0038f3db9b", size = 222172, upload-time = "2026-02-09T12:59:00.396Z" }, + { url = "https://files.pythonhosted.org/packages/0d/4a/331fe2caf6799d591109bb9c08083080f6de90a823695d412a935622abb2/coverage-7.13.4-py3-none-any.whl", hash = "sha256:1af1641e57cf7ba1bd67d677c9abdbcd6cc2ab7da3bca7fa1e2b7e50e65f2ad0", size = 211242, upload-time = "2026-02-09T12:59:02.032Z" }, ] [package.optional-dependencies] @@ -617,7 +642,7 @@ wheels = [ [[package]] name = "exstruct" -version = "0.4.2" +version = "0.4.4" source = { editable = "." } dependencies = [ { name = "numpy" }, @@ -705,11 +730,11 @@ dev = [ [[package]] name = "filelock" -version = "3.20.3" +version = "3.24.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1d/65/ce7f1b70157833bf3cb851b556a37d4547ceafc158aa9b34b36782f23696/filelock-3.20.3.tar.gz", hash = "sha256:18c57ee915c7ec61cff0ecf7f0f869936c7c30191bb0cf406f1341778d0834e1", size = 19485, upload-time = "2026-01-09T17:55:05.421Z" } +sdist = { url = "https://files.pythonhosted.org/packages/02/a8/dae62680be63cbb3ff87cfa2f51cf766269514ea5488479d42fec5aa6f3a/filelock-3.24.2.tar.gz", hash = "sha256:c22803117490f156e59fafce621f0550a7a853e2bbf4f87f112b11d469b6c81b", size = 37601, upload-time = "2026-02-16T02:50:45.614Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/36/7fb70f04bf00bc646cd5bb45aa9eddb15e19437a28b8fb2b4a5249fac770/filelock-3.20.3-py3-none-any.whl", hash = "sha256:4b0dda527ee31078689fc205ec4f1c1bf7d56cf88b6dc9426c4f230e46c2dce1", size = 16701, upload-time = "2026-01-09T17:55:04.334Z" }, + { url = "https://files.pythonhosted.org/packages/e7/04/a94ebfb4eaaa08db56725a40de2887e95de4e8641b9e902c311bfa00aa39/filelock-3.24.2-py3-none-any.whl", hash = "sha256:667d7dc0b7d1e1064dd5f8f8e80bdac157a6482e8d2e02cd16fd3b6b33bd6556", size = 24152, upload-time = "2026-02-16T02:50:44Z" }, ] [[package]] @@ -775,14 +800,34 @@ wheels = [ [[package]] name = "griffe" -version = "1.15.0" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "griffecli" }, + { name = "griffelib" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/8b/94/ee21d41e7eb4f823b94603b9d40f86d3c7fde80eacc2c3c71845476dddaa/griffe-2.0.0-py3-none-any.whl", hash = "sha256:5418081135a391c3e6e757a7f3f156f1a1a746cc7b4023868ff7d5e2f9a980aa", size = 5214, upload-time = "2026-02-09T19:09:44.105Z" }, +] + +[[package]] +name = "griffecli" +version = "2.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama" }, + { name = "griffelib" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/e6/ed/d93f7a447bbf7a935d8868e9617cbe1cadf9ee9ee6bd275d3040fbf93d60/griffecli-2.0.0-py3-none-any.whl", hash = "sha256:9f7cd9ee9b21d55e91689358978d2385ae65c22f307a63fb3269acf3f21e643d", size = 9345, upload-time = "2026-02-09T19:09:42.554Z" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0d/0c/3a471b6e31951dce2360477420d0a8d1e00dea6cf33b70f3e8c3ab6e28e1/griffe-1.15.0.tar.gz", hash = "sha256:7726e3afd6f298fbc3696e67958803e7ac843c1cfe59734b6251a40cdbfb5eea", size = 424112, upload-time = "2025-11-10T15:03:15.52Z" } + +[[package]] +name = "griffelib" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9c/83/3b1d03d36f224edded98e9affd0467630fc09d766c0e56fb1498cbb04a9b/griffe-1.15.0-py3-none-any.whl", hash = "sha256:6f6762661949411031f5fcda9593f586e6ce8340f0ba88921a0f2ef7a81eb9a3", size = 150705, upload-time = "2025-11-10T15:03:13.549Z" }, + { url = "https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl", hash = "sha256:01284878c966508b6d6f1dbff9b6fa607bc062d8261c5c7253cb285b06422a7f", size = 142004, upload-time = "2026-02-09T19:09:40.561Z" }, ] [[package]] @@ -833,11 +878,11 @@ wheels = [ [[package]] name = "identify" -version = "2.6.15" +version = "2.6.16" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ff/e7/685de97986c916a6d93b3876139e00eef26ad5bbbd61925d670ae8013449/identify-2.6.15.tar.gz", hash = "sha256:e4f4864b96c6557ef2a1e1c951771838f4edc9df3a72ec7118b338801b11c7bf", size = 99311, upload-time = "2025-10-02T17:43:40.631Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/8d/e8b97e6bd3fb6fb271346f7981362f1e04d6a7463abd0de79e1fda17c067/identify-2.6.16.tar.gz", hash = "sha256:846857203b5511bbe94d5a352a48ef2359532bc8f6727b5544077a0dcfb24980", size = 99360, upload-time = "2026-01-12T18:58:58.201Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/1c/e5fd8f973d4f375adb21565739498e2e9a1e54c858a97b9a8ccfdc81da9b/identify-2.6.15-py2.py3-none-any.whl", hash = "sha256:1181ef7608e00704db228516541eb83a88a9f94433a8c80bb9b5bd54b1d81757", size = 99183, upload-time = "2025-10-02T17:43:39.137Z" }, + { url = "https://files.pythonhosted.org/packages/b8/58/40fbbcefeda82364720eba5cf2270f98496bdfa19ea75b4cccae79c698e6/identify-2.6.16-py2.py3-none-any.whl", hash = "sha256:391ee4d77741d994189522896270b787aed8670389bfd60f326d677d64a6dfb0", size = 99202, upload-time = "2026-01-12T18:58:56.627Z" }, ] [[package]] @@ -952,87 +997,87 @@ wheels = [ [[package]] name = "jiter" -version = "0.12.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/45/9d/e0660989c1370e25848bb4c52d061c71837239738ad937e83edca174c273/jiter-0.12.0.tar.gz", hash = "sha256:64dfcd7d5c168b38d3f9f8bba7fc639edb3418abcc74f22fdbe6b8938293f30b", size = 168294, upload-time = "2025-11-09T20:49:23.302Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/32/f9/eaca4633486b527ebe7e681c431f529b63fe2709e7c5242fc0f43f77ce63/jiter-0.12.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d8f8a7e317190b2c2d60eb2e8aa835270b008139562d70fe732e1c0020ec53c9", size = 316435, upload-time = "2025-11-09T20:47:02.087Z" }, - { url = "https://files.pythonhosted.org/packages/10/c1/40c9f7c22f5e6ff715f28113ebaba27ab85f9af2660ad6e1dd6425d14c19/jiter-0.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2218228a077e784c6c8f1a8e5d6b8cb1dea62ce25811c356364848554b2056cd", size = 320548, upload-time = "2025-11-09T20:47:03.409Z" }, - { url = "https://files.pythonhosted.org/packages/6b/1b/efbb68fe87e7711b00d2cfd1f26bb4bfc25a10539aefeaa7727329ffb9cb/jiter-0.12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9354ccaa2982bf2188fd5f57f79f800ef622ec67beb8329903abf6b10da7d423", size = 351915, upload-time = "2025-11-09T20:47:05.171Z" }, - { url = "https://files.pythonhosted.org/packages/15/2d/c06e659888c128ad1e838123d0638f0efad90cc30860cb5f74dd3f2fc0b3/jiter-0.12.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8f2607185ea89b4af9a604d4c7ec40e45d3ad03ee66998b031134bc510232bb7", size = 368966, upload-time = "2025-11-09T20:47:06.508Z" }, - { url = "https://files.pythonhosted.org/packages/6b/20/058db4ae5fb07cf6a4ab2e9b9294416f606d8e467fb74c2184b2a1eeacba/jiter-0.12.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3a585a5e42d25f2e71db5f10b171f5e5ea641d3aa44f7df745aa965606111cc2", size = 482047, upload-time = "2025-11-09T20:47:08.382Z" }, - { url = "https://files.pythonhosted.org/packages/49/bb/dc2b1c122275e1de2eb12905015d61e8316b2f888bdaac34221c301495d6/jiter-0.12.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd9e21d34edff5a663c631f850edcb786719c960ce887a5661e9c828a53a95d9", size = 380835, upload-time = "2025-11-09T20:47:09.81Z" }, - { url = "https://files.pythonhosted.org/packages/23/7d/38f9cd337575349de16da575ee57ddb2d5a64d425c9367f5ef9e4612e32e/jiter-0.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a612534770470686cd5431478dc5a1b660eceb410abade6b1b74e320ca98de6", size = 364587, upload-time = "2025-11-09T20:47:11.529Z" }, - { url = "https://files.pythonhosted.org/packages/f0/a3/b13e8e61e70f0bb06085099c4e2462647f53cc2ca97614f7fedcaa2bb9f3/jiter-0.12.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3985aea37d40a908f887b34d05111e0aae822943796ebf8338877fee2ab67725", size = 390492, upload-time = "2025-11-09T20:47:12.993Z" }, - { url = "https://files.pythonhosted.org/packages/07/71/e0d11422ed027e21422f7bc1883c61deba2d9752b720538430c1deadfbca/jiter-0.12.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b1207af186495f48f72529f8d86671903c8c10127cac6381b11dddc4aaa52df6", size = 522046, upload-time = "2025-11-09T20:47:14.6Z" }, - { url = "https://files.pythonhosted.org/packages/9f/59/b968a9aa7102a8375dbbdfbd2aeebe563c7e5dddf0f47c9ef1588a97e224/jiter-0.12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ef2fb241de583934c9915a33120ecc06d94aa3381a134570f59eed784e87001e", size = 513392, upload-time = "2025-11-09T20:47:16.011Z" }, - { url = "https://files.pythonhosted.org/packages/ca/e4/7df62002499080dbd61b505c5cb351aa09e9959d176cac2aa8da6f93b13b/jiter-0.12.0-cp311-cp311-win32.whl", hash = "sha256:453b6035672fecce8007465896a25b28a6b59cfe8fbc974b2563a92f5a92a67c", size = 206096, upload-time = "2025-11-09T20:47:17.344Z" }, - { url = "https://files.pythonhosted.org/packages/bb/60/1032b30ae0572196b0de0e87dce3b6c26a1eff71aad5fe43dee3082d32e0/jiter-0.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:ca264b9603973c2ad9435c71a8ec8b49f8f715ab5ba421c85a51cde9887e421f", size = 204899, upload-time = "2025-11-09T20:47:19.365Z" }, - { url = "https://files.pythonhosted.org/packages/49/d5/c145e526fccdb834063fb45c071df78b0cc426bbaf6de38b0781f45d956f/jiter-0.12.0-cp311-cp311-win_arm64.whl", hash = "sha256:cb00ef392e7d684f2754598c02c409f376ddcef857aae796d559e6cacc2d78a5", size = 188070, upload-time = "2025-11-09T20:47:20.75Z" }, - { url = "https://files.pythonhosted.org/packages/92/c9/5b9f7b4983f1b542c64e84165075335e8a236fa9e2ea03a0c79780062be8/jiter-0.12.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:305e061fa82f4680607a775b2e8e0bcb071cd2205ac38e6ef48c8dd5ebe1cf37", size = 314449, upload-time = "2025-11-09T20:47:22.999Z" }, - { url = "https://files.pythonhosted.org/packages/98/6e/e8efa0e78de00db0aee82c0cf9e8b3f2027efd7f8a71f859d8f4be8e98ef/jiter-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5c1860627048e302a528333c9307c818c547f214d8659b0705d2195e1a94b274", size = 319855, upload-time = "2025-11-09T20:47:24.779Z" }, - { url = "https://files.pythonhosted.org/packages/20/26/894cd88e60b5d58af53bec5c6759d1292bd0b37a8b5f60f07abf7a63ae5f/jiter-0.12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df37577a4f8408f7e0ec3205d2a8f87672af8f17008358063a4d6425b6081ce3", size = 350171, upload-time = "2025-11-09T20:47:26.469Z" }, - { url = "https://files.pythonhosted.org/packages/f5/27/a7b818b9979ac31b3763d25f3653ec3a954044d5e9f5d87f2f247d679fd1/jiter-0.12.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:75fdd787356c1c13a4f40b43c2156276ef7a71eb487d98472476476d803fb2cf", size = 365590, upload-time = "2025-11-09T20:47:27.918Z" }, - { url = "https://files.pythonhosted.org/packages/ba/7e/e46195801a97673a83746170b17984aa8ac4a455746354516d02ca5541b4/jiter-0.12.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1eb5db8d9c65b112aacf14fcd0faae9913d07a8afea5ed06ccdd12b724e966a1", size = 479462, upload-time = "2025-11-09T20:47:29.654Z" }, - { url = "https://files.pythonhosted.org/packages/ca/75/f833bfb009ab4bd11b1c9406d333e3b4357709ed0570bb48c7c06d78c7dd/jiter-0.12.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:73c568cc27c473f82480abc15d1301adf333a7ea4f2e813d6a2c7d8b6ba8d0df", size = 378983, upload-time = "2025-11-09T20:47:31.026Z" }, - { url = "https://files.pythonhosted.org/packages/71/b3/7a69d77943cc837d30165643db753471aff5df39692d598da880a6e51c24/jiter-0.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4321e8a3d868919bcb1abb1db550d41f2b5b326f72df29e53b2df8b006eb9403", size = 361328, upload-time = "2025-11-09T20:47:33.286Z" }, - { url = "https://files.pythonhosted.org/packages/b0/ac/a78f90caf48d65ba70d8c6efc6f23150bc39dc3389d65bbec2a95c7bc628/jiter-0.12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0a51bad79f8cc9cac2b4b705039f814049142e0050f30d91695a2d9a6611f126", size = 386740, upload-time = "2025-11-09T20:47:34.703Z" }, - { url = "https://files.pythonhosted.org/packages/39/b6/5d31c2cc8e1b6a6bcf3c5721e4ca0a3633d1ab4754b09bc7084f6c4f5327/jiter-0.12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:2a67b678f6a5f1dd6c36d642d7db83e456bc8b104788262aaefc11a22339f5a9", size = 520875, upload-time = "2025-11-09T20:47:36.058Z" }, - { url = "https://files.pythonhosted.org/packages/30/b5/4df540fae4e9f68c54b8dab004bd8c943a752f0b00efd6e7d64aa3850339/jiter-0.12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efe1a211fe1fd14762adea941e3cfd6c611a136e28da6c39272dbb7a1bbe6a86", size = 511457, upload-time = "2025-11-09T20:47:37.932Z" }, - { url = "https://files.pythonhosted.org/packages/07/65/86b74010e450a1a77b2c1aabb91d4a91dd3cd5afce99f34d75fd1ac64b19/jiter-0.12.0-cp312-cp312-win32.whl", hash = "sha256:d779d97c834b4278276ec703dc3fc1735fca50af63eb7262f05bdb4e62203d44", size = 204546, upload-time = "2025-11-09T20:47:40.47Z" }, - { url = "https://files.pythonhosted.org/packages/1c/c7/6659f537f9562d963488e3e55573498a442503ced01f7e169e96a6110383/jiter-0.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:e8269062060212b373316fe69236096aaf4c49022d267c6736eebd66bbbc60bb", size = 205196, upload-time = "2025-11-09T20:47:41.794Z" }, - { url = "https://files.pythonhosted.org/packages/21/f4/935304f5169edadfec7f9c01eacbce4c90bb9a82035ac1de1f3bd2d40be6/jiter-0.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:06cb970936c65de926d648af0ed3d21857f026b1cf5525cb2947aa5e01e05789", size = 186100, upload-time = "2025-11-09T20:47:43.007Z" }, - { url = "https://files.pythonhosted.org/packages/3d/a6/97209693b177716e22576ee1161674d1d58029eb178e01866a0422b69224/jiter-0.12.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:6cc49d5130a14b732e0612bc76ae8db3b49898732223ef8b7599aa8d9810683e", size = 313658, upload-time = "2025-11-09T20:47:44.424Z" }, - { url = "https://files.pythonhosted.org/packages/06/4d/125c5c1537c7d8ee73ad3d530a442d6c619714b95027143f1b61c0b4dfe0/jiter-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:37f27a32ce36364d2fa4f7fdc507279db604d27d239ea2e044c8f148410defe1", size = 318605, upload-time = "2025-11-09T20:47:45.973Z" }, - { url = "https://files.pythonhosted.org/packages/99/bf/a840b89847885064c41a5f52de6e312e91fa84a520848ee56c97e4fa0205/jiter-0.12.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbc0944aa3d4b4773e348cda635252824a78f4ba44328e042ef1ff3f6080d1cf", size = 349803, upload-time = "2025-11-09T20:47:47.535Z" }, - { url = "https://files.pythonhosted.org/packages/8a/88/e63441c28e0db50e305ae23e19c1d8fae012d78ed55365da392c1f34b09c/jiter-0.12.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:da25c62d4ee1ffbacb97fac6dfe4dcd6759ebdc9015991e92a6eae5816287f44", size = 365120, upload-time = "2025-11-09T20:47:49.284Z" }, - { url = "https://files.pythonhosted.org/packages/0a/7c/49b02714af4343970eb8aca63396bc1c82fa01197dbb1e9b0d274b550d4e/jiter-0.12.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:048485c654b838140b007390b8182ba9774621103bd4d77c9c3f6f117474ba45", size = 479918, upload-time = "2025-11-09T20:47:50.807Z" }, - { url = "https://files.pythonhosted.org/packages/69/ba/0a809817fdd5a1db80490b9150645f3aae16afad166960bcd562be194f3b/jiter-0.12.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:635e737fbb7315bef0037c19b88b799143d2d7d3507e61a76751025226b3ac87", size = 379008, upload-time = "2025-11-09T20:47:52.211Z" }, - { url = "https://files.pythonhosted.org/packages/5f/c3/c9fc0232e736c8877d9e6d83d6eeb0ba4e90c6c073835cc2e8f73fdeef51/jiter-0.12.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e017c417b1ebda911bd13b1e40612704b1f5420e30695112efdbed8a4b389ed", size = 361785, upload-time = "2025-11-09T20:47:53.512Z" }, - { url = "https://files.pythonhosted.org/packages/96/61/61f69b7e442e97ca6cd53086ddc1cf59fb830549bc72c0a293713a60c525/jiter-0.12.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:89b0bfb8b2bf2351fba36bb211ef8bfceba73ef58e7f0c68fb67b5a2795ca2f9", size = 386108, upload-time = "2025-11-09T20:47:54.893Z" }, - { url = "https://files.pythonhosted.org/packages/e9/2e/76bb3332f28550c8f1eba3bf6e5efe211efda0ddbbaf24976bc7078d42a5/jiter-0.12.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:f5aa5427a629a824a543672778c9ce0c5e556550d1569bb6ea28a85015287626", size = 519937, upload-time = "2025-11-09T20:47:56.253Z" }, - { url = "https://files.pythonhosted.org/packages/84/d6/fa96efa87dc8bff2094fb947f51f66368fa56d8d4fc9e77b25d7fbb23375/jiter-0.12.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed53b3d6acbcb0fd0b90f20c7cb3b24c357fe82a3518934d4edfa8c6898e498c", size = 510853, upload-time = "2025-11-09T20:47:58.32Z" }, - { url = "https://files.pythonhosted.org/packages/8a/28/93f67fdb4d5904a708119a6ab58a8f1ec226ff10a94a282e0215402a8462/jiter-0.12.0-cp313-cp313-win32.whl", hash = "sha256:4747de73d6b8c78f2e253a2787930f4fffc68da7fa319739f57437f95963c4de", size = 204699, upload-time = "2025-11-09T20:47:59.686Z" }, - { url = "https://files.pythonhosted.org/packages/c4/1f/30b0eb087045a0abe2a5c9c0c0c8da110875a1d3be83afd4a9a4e548be3c/jiter-0.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:e25012eb0c456fcc13354255d0338cd5397cce26c77b2832b3c4e2e255ea5d9a", size = 204258, upload-time = "2025-11-09T20:48:01.01Z" }, - { url = "https://files.pythonhosted.org/packages/2c/f4/2b4daf99b96bce6fc47971890b14b2a36aef88d7beb9f057fafa032c6141/jiter-0.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:c97b92c54fe6110138c872add030a1f99aea2401ddcdaa21edf74705a646dd60", size = 185503, upload-time = "2025-11-09T20:48:02.35Z" }, - { url = "https://files.pythonhosted.org/packages/39/ca/67bb15a7061d6fe20b9b2a2fd783e296a1e0f93468252c093481a2f00efa/jiter-0.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:53839b35a38f56b8be26a7851a48b89bc47e5d88e900929df10ed93b95fea3d6", size = 317965, upload-time = "2025-11-09T20:48:03.783Z" }, - { url = "https://files.pythonhosted.org/packages/18/af/1788031cd22e29c3b14bc6ca80b16a39a0b10e611367ffd480c06a259831/jiter-0.12.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94f669548e55c91ab47fef8bddd9c954dab1938644e715ea49d7e117015110a4", size = 345831, upload-time = "2025-11-09T20:48:05.55Z" }, - { url = "https://files.pythonhosted.org/packages/05/17/710bf8472d1dff0d3caf4ced6031060091c1320f84ee7d5dcbed1f352417/jiter-0.12.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:351d54f2b09a41600ffea43d081522d792e81dcfb915f6d2d242744c1cc48beb", size = 361272, upload-time = "2025-11-09T20:48:06.951Z" }, - { url = "https://files.pythonhosted.org/packages/fb/f1/1dcc4618b59761fef92d10bcbb0b038b5160be653b003651566a185f1a5c/jiter-0.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2a5e90604620f94bf62264e7c2c038704d38217b7465b863896c6d7c902b06c7", size = 204604, upload-time = "2025-11-09T20:48:08.328Z" }, - { url = "https://files.pythonhosted.org/packages/d9/32/63cb1d9f1c5c6632a783c0052cde9ef7ba82688f7065e2f0d5f10a7e3edb/jiter-0.12.0-cp313-cp313t-win_arm64.whl", hash = "sha256:88ef757017e78d2860f96250f9393b7b577b06a956ad102c29c8237554380db3", size = 185628, upload-time = "2025-11-09T20:48:09.572Z" }, - { url = "https://files.pythonhosted.org/packages/a8/99/45c9f0dbe4a1416b2b9a8a6d1236459540f43d7fb8883cff769a8db0612d/jiter-0.12.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:c46d927acd09c67a9fb1416df45c5a04c27e83aae969267e98fba35b74e99525", size = 312478, upload-time = "2025-11-09T20:48:10.898Z" }, - { url = "https://files.pythonhosted.org/packages/4c/a7/54ae75613ba9e0f55fcb0bc5d1f807823b5167cc944e9333ff322e9f07dd/jiter-0.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:774ff60b27a84a85b27b88cd5583899c59940bcc126caca97eb2a9df6aa00c49", size = 318706, upload-time = "2025-11-09T20:48:12.266Z" }, - { url = "https://files.pythonhosted.org/packages/59/31/2aa241ad2c10774baf6c37f8b8e1f39c07db358f1329f4eb40eba179c2a2/jiter-0.12.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5433fab222fb072237df3f637d01b81f040a07dcac1cb4a5c75c7aa9ed0bef1", size = 351894, upload-time = "2025-11-09T20:48:13.673Z" }, - { url = "https://files.pythonhosted.org/packages/54/4f/0f2759522719133a9042781b18cc94e335b6d290f5e2d3e6899d6af933e3/jiter-0.12.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f8c593c6e71c07866ec6bfb790e202a833eeec885022296aff6b9e0b92d6a70e", size = 365714, upload-time = "2025-11-09T20:48:15.083Z" }, - { url = "https://files.pythonhosted.org/packages/dc/6f/806b895f476582c62a2f52c453151edd8a0fde5411b0497baaa41018e878/jiter-0.12.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:90d32894d4c6877a87ae00c6b915b609406819dce8bc0d4e962e4de2784e567e", size = 478989, upload-time = "2025-11-09T20:48:16.706Z" }, - { url = "https://files.pythonhosted.org/packages/86/6c/012d894dc6e1033acd8db2b8346add33e413ec1c7c002598915278a37f79/jiter-0.12.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:798e46eed9eb10c3adbbacbd3bdb5ecd4cf7064e453d00dbef08802dae6937ff", size = 378615, upload-time = "2025-11-09T20:48:18.614Z" }, - { url = "https://files.pythonhosted.org/packages/87/30/d718d599f6700163e28e2c71c0bbaf6dace692e7df2592fd793ac9276717/jiter-0.12.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3f1368f0a6719ea80013a4eb90ba72e75d7ea67cfc7846db2ca504f3df0169a", size = 364745, upload-time = "2025-11-09T20:48:20.117Z" }, - { url = "https://files.pythonhosted.org/packages/8f/85/315b45ce4b6ddc7d7fceca24068543b02bdc8782942f4ee49d652e2cc89f/jiter-0.12.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:65f04a9d0b4406f7e51279710b27484af411896246200e461d80d3ba0caa901a", size = 386502, upload-time = "2025-11-09T20:48:21.543Z" }, - { url = "https://files.pythonhosted.org/packages/74/0b/ce0434fb40c5b24b368fe81b17074d2840748b4952256bab451b72290a49/jiter-0.12.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:fd990541982a24281d12b67a335e44f117e4c6cbad3c3b75c7dea68bf4ce3a67", size = 519845, upload-time = "2025-11-09T20:48:22.964Z" }, - { url = "https://files.pythonhosted.org/packages/e8/a3/7a7a4488ba052767846b9c916d208b3ed114e3eb670ee984e4c565b9cf0d/jiter-0.12.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:b111b0e9152fa7df870ecaebb0bd30240d9f7fff1f2003bcb4ed0f519941820b", size = 510701, upload-time = "2025-11-09T20:48:24.483Z" }, - { url = "https://files.pythonhosted.org/packages/c3/16/052ffbf9d0467b70af24e30f91e0579e13ded0c17bb4a8eb2aed3cb60131/jiter-0.12.0-cp314-cp314-win32.whl", hash = "sha256:a78befb9cc0a45b5a5a0d537b06f8544c2ebb60d19d02c41ff15da28a9e22d42", size = 205029, upload-time = "2025-11-09T20:48:25.749Z" }, - { url = "https://files.pythonhosted.org/packages/e4/18/3cf1f3f0ccc789f76b9a754bdb7a6977e5d1d671ee97a9e14f7eb728d80e/jiter-0.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:e1fe01c082f6aafbe5c8faf0ff074f38dfb911d53f07ec333ca03f8f6226debf", size = 204960, upload-time = "2025-11-09T20:48:27.415Z" }, - { url = "https://files.pythonhosted.org/packages/02/68/736821e52ecfdeeb0f024b8ab01b5a229f6b9293bbdb444c27efade50b0f/jiter-0.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:d72f3b5a432a4c546ea4bedc84cce0c3404874f1d1676260b9c7f048a9855451", size = 185529, upload-time = "2025-11-09T20:48:29.125Z" }, - { url = "https://files.pythonhosted.org/packages/30/61/12ed8ee7a643cce29ac97c2281f9ce3956eb76b037e88d290f4ed0d41480/jiter-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e6ded41aeba3603f9728ed2b6196e4df875348ab97b28fc8afff115ed42ba7a7", size = 318974, upload-time = "2025-11-09T20:48:30.87Z" }, - { url = "https://files.pythonhosted.org/packages/2d/c6/f3041ede6d0ed5e0e79ff0de4c8f14f401bbf196f2ef3971cdbe5fd08d1d/jiter-0.12.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a947920902420a6ada6ad51892082521978e9dd44a802663b001436e4b771684", size = 345932, upload-time = "2025-11-09T20:48:32.658Z" }, - { url = "https://files.pythonhosted.org/packages/d5/5d/4d94835889edd01ad0e2dbfc05f7bdfaed46292e7b504a6ac7839aa00edb/jiter-0.12.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:add5e227e0554d3a52cf390a7635edaffdf4f8fce4fdbcef3cc2055bb396a30c", size = 367243, upload-time = "2025-11-09T20:48:34.093Z" }, - { url = "https://files.pythonhosted.org/packages/fd/76/0051b0ac2816253a99d27baf3dda198663aff882fa6ea7deeb94046da24e/jiter-0.12.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f9b1cda8fcb736250d7e8711d4580ebf004a46771432be0ae4796944b5dfa5d", size = 479315, upload-time = "2025-11-09T20:48:35.507Z" }, - { url = "https://files.pythonhosted.org/packages/70/ae/83f793acd68e5cb24e483f44f482a1a15601848b9b6f199dacb970098f77/jiter-0.12.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:deeb12a2223fe0135c7ff1356a143d57f95bbf1f4a66584f1fc74df21d86b993", size = 380714, upload-time = "2025-11-09T20:48:40.014Z" }, - { url = "https://files.pythonhosted.org/packages/b1/5e/4808a88338ad2c228b1126b93fcd8ba145e919e886fe910d578230dabe3b/jiter-0.12.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c596cc0f4cb574877550ce4ecd51f8037469146addd676d7c1a30ebe6391923f", size = 365168, upload-time = "2025-11-09T20:48:41.462Z" }, - { url = "https://files.pythonhosted.org/packages/0c/d4/04619a9e8095b42aef436b5aeb4c0282b4ff1b27d1db1508df9f5dc82750/jiter-0.12.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ab4c823b216a4aeab3fdbf579c5843165756bd9ad87cc6b1c65919c4715f783", size = 387893, upload-time = "2025-11-09T20:48:42.921Z" }, - { url = "https://files.pythonhosted.org/packages/17/ea/d3c7e62e4546fdc39197fa4a4315a563a89b95b6d54c0d25373842a59cbe/jiter-0.12.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:e427eee51149edf962203ff8db75a7514ab89be5cb623fb9cea1f20b54f1107b", size = 520828, upload-time = "2025-11-09T20:48:44.278Z" }, - { url = "https://files.pythonhosted.org/packages/cc/0b/c6d3562a03fd767e31cb119d9041ea7958c3c80cb3d753eafb19b3b18349/jiter-0.12.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:edb868841f84c111255ba5e80339d386d937ec1fdce419518ce1bd9370fac5b6", size = 511009, upload-time = "2025-11-09T20:48:45.726Z" }, - { url = "https://files.pythonhosted.org/packages/aa/51/2cb4468b3448a8385ebcd15059d325c9ce67df4e2758d133ab9442b19834/jiter-0.12.0-cp314-cp314t-win32.whl", hash = "sha256:8bbcfe2791dfdb7c5e48baf646d37a6a3dcb5a97a032017741dea9f817dca183", size = 205110, upload-time = "2025-11-09T20:48:47.033Z" }, - { url = "https://files.pythonhosted.org/packages/b2/c5/ae5ec83dec9c2d1af805fd5fe8f74ebded9c8670c5210ec7820ce0dbeb1e/jiter-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2fa940963bf02e1d8226027ef461e36af472dea85d36054ff835aeed944dd873", size = 205223, upload-time = "2025-11-09T20:48:49.076Z" }, - { url = "https://files.pythonhosted.org/packages/97/9a/3c5391907277f0e55195550cf3fa8e293ae9ee0c00fb402fec1e38c0c82f/jiter-0.12.0-cp314-cp314t-win_arm64.whl", hash = "sha256:506c9708dd29b27288f9f8f1140c3cb0e3d8ddb045956d7757b1fa0e0f39a473", size = 185564, upload-time = "2025-11-09T20:48:50.376Z" }, - { url = "https://files.pythonhosted.org/packages/fe/54/5339ef1ecaa881c6948669956567a64d2670941925f245c434f494ffb0e5/jiter-0.12.0-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:4739a4657179ebf08f85914ce50332495811004cc1747852e8b2041ed2aab9b8", size = 311144, upload-time = "2025-11-09T20:49:10.503Z" }, - { url = "https://files.pythonhosted.org/packages/27/74/3446c652bffbd5e81ab354e388b1b5fc1d20daac34ee0ed11ff096b1b01a/jiter-0.12.0-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:41da8def934bf7bec16cb24bd33c0ca62126d2d45d81d17b864bd5ad721393c3", size = 305877, upload-time = "2025-11-09T20:49:12.269Z" }, - { url = "https://files.pythonhosted.org/packages/a1/f4/ed76ef9043450f57aac2d4fbeb27175aa0eb9c38f833be6ef6379b3b9a86/jiter-0.12.0-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c44ee814f499c082e69872d426b624987dbc5943ab06e9bbaa4f81989fdb79e", size = 340419, upload-time = "2025-11-09T20:49:13.803Z" }, - { url = "https://files.pythonhosted.org/packages/21/01/857d4608f5edb0664aa791a3d45702e1a5bcfff9934da74035e7b9803846/jiter-0.12.0-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd2097de91cf03eaa27b3cbdb969addf83f0179c6afc41bbc4513705e013c65d", size = 347212, upload-time = "2025-11-09T20:49:15.643Z" }, - { url = "https://files.pythonhosted.org/packages/cb/f5/12efb8ada5f5c9edc1d4555fe383c1fb2eac05ac5859258a72d61981d999/jiter-0.12.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:e8547883d7b96ef2e5fe22b88f8a4c8725a56e7f4abafff20fd5272d634c7ecb", size = 309974, upload-time = "2025-11-09T20:49:17.187Z" }, - { url = "https://files.pythonhosted.org/packages/85/15/d6eb3b770f6a0d332675141ab3962fd4a7c270ede3515d9f3583e1d28276/jiter-0.12.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:89163163c0934854a668ed783a2546a0617f71706a2551a4a0666d91ab365d6b", size = 304233, upload-time = "2025-11-09T20:49:18.734Z" }, - { url = "https://files.pythonhosted.org/packages/8c/3e/e7e06743294eea2cf02ced6aa0ff2ad237367394e37a0e2b4a1108c67a36/jiter-0.12.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d96b264ab7d34bbb2312dedc47ce07cd53f06835eacbc16dde3761f47c3a9e7f", size = 338537, upload-time = "2025-11-09T20:49:20.317Z" }, - { url = "https://files.pythonhosted.org/packages/2f/9c/6753e6522b8d0ef07d3a3d239426669e984fb0eba15a315cdbc1253904e4/jiter-0.12.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c24e864cb30ab82311c6425655b0cdab0a98c5d973b065c66a3f020740c2324c", size = 346110, upload-time = "2025-11-09T20:49:21.817Z" }, +version = "0.13.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/5e/4ec91646aee381d01cdb9974e30882c9cd3b8c5d1079d6b5ff4af522439a/jiter-0.13.0.tar.gz", hash = "sha256:f2839f9c2c7e2dffc1bc5929a510e14ce0a946be9365fd1219e7ef342dae14f4", size = 164847, upload-time = "2026-02-02T12:37:56.441Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/29/499f8c9eaa8a16751b1c0e45e6f5f1761d180da873d417996cc7bddc8eef/jiter-0.13.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ea026e70a9a28ebbdddcbcf0f1323128a8db66898a06eaad3a4e62d2f554d096", size = 311157, upload-time = "2026-02-02T12:35:37.758Z" }, + { url = "https://files.pythonhosted.org/packages/50/f6/566364c777d2ab450b92100bea11333c64c38d32caf8dc378b48e5b20c46/jiter-0.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:66aa3e663840152d18cc8ff1e4faad3dd181373491b9cfdc6004b92198d67911", size = 319729, upload-time = "2026-02-02T12:35:39.246Z" }, + { url = "https://files.pythonhosted.org/packages/73/dd/560f13ec5e4f116d8ad2658781646cca91b617ae3b8758d4a5076b278f70/jiter-0.13.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3524798e70655ff19aec58c7d05adb1f074fecff62da857ea9be2b908b6d701", size = 354766, upload-time = "2026-02-02T12:35:40.662Z" }, + { url = "https://files.pythonhosted.org/packages/7c/0d/061faffcfe94608cbc28a0d42a77a74222bdf5055ccdbe5fd2292b94f510/jiter-0.13.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ec7e287d7fbd02cb6e22f9a00dd9c9cd504c40a61f2c61e7e1f9690a82726b4c", size = 362587, upload-time = "2026-02-02T12:35:42.025Z" }, + { url = "https://files.pythonhosted.org/packages/92/c9/c66a7864982fd38a9773ec6e932e0398d1262677b8c60faecd02ffb67bf3/jiter-0.13.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:47455245307e4debf2ce6c6e65a717550a0244231240dcf3b8f7d64e4c2f22f4", size = 487537, upload-time = "2026-02-02T12:35:43.459Z" }, + { url = "https://files.pythonhosted.org/packages/6c/86/84eb4352cd3668f16d1a88929b5888a3fe0418ea8c1dfc2ad4e7bf6e069a/jiter-0.13.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ee9da221dca6e0429c2704c1b3655fe7b025204a71d4d9b73390c759d776d165", size = 373717, upload-time = "2026-02-02T12:35:44.928Z" }, + { url = "https://files.pythonhosted.org/packages/6e/09/9fe4c159358176f82d4390407a03f506a8659ed13ca3ac93a843402acecf/jiter-0.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24ab43126d5e05f3d53a36a8e11eb2f23304c6c1117844aaaf9a0aa5e40b5018", size = 362683, upload-time = "2026-02-02T12:35:46.636Z" }, + { url = "https://files.pythonhosted.org/packages/c9/5e/85f3ab9caca0c1d0897937d378b4a515cae9e119730563572361ea0c48ae/jiter-0.13.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9da38b4fedde4fb528c740c2564628fbab737166a0e73d6d46cb4bb5463ff411", size = 392345, upload-time = "2026-02-02T12:35:48.088Z" }, + { url = "https://files.pythonhosted.org/packages/12/4c/05b8629ad546191939e6f0c2f17e29f542a398f4a52fb987bc70b6d1eb8b/jiter-0.13.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0b34c519e17658ed88d5047999a93547f8889f3c1824120c26ad6be5f27b6cf5", size = 517775, upload-time = "2026-02-02T12:35:49.482Z" }, + { url = "https://files.pythonhosted.org/packages/4d/88/367ea2eb6bc582c7052e4baf5ddf57ebe5ab924a88e0e09830dfb585c02d/jiter-0.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d2a6394e6af690d462310a86b53c47ad75ac8c21dc79f120714ea449979cb1d3", size = 551325, upload-time = "2026-02-02T12:35:51.104Z" }, + { url = "https://files.pythonhosted.org/packages/f3/12/fa377ffb94a2f28c41afaed093e0d70cfe512035d5ecb0cad0ae4792d35e/jiter-0.13.0-cp311-cp311-win32.whl", hash = "sha256:0f0c065695f616a27c920a56ad0d4fc46415ef8b806bf8fc1cacf25002bd24e1", size = 204709, upload-time = "2026-02-02T12:35:52.467Z" }, + { url = "https://files.pythonhosted.org/packages/cb/16/8e8203ce92f844dfcd3d9d6a5a7322c77077248dbb12da52d23193a839cd/jiter-0.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:0733312953b909688ae3c2d58d043aa040f9f1a6a75693defed7bc2cc4bf2654", size = 204560, upload-time = "2026-02-02T12:35:53.925Z" }, + { url = "https://files.pythonhosted.org/packages/44/26/97cc40663deb17b9e13c3a5cf29251788c271b18ee4d262c8f94798b8336/jiter-0.13.0-cp311-cp311-win_arm64.whl", hash = "sha256:5d9b34ad56761b3bf0fbe8f7e55468704107608512350962d3317ffd7a4382d5", size = 189608, upload-time = "2026-02-02T12:35:55.304Z" }, + { url = "https://files.pythonhosted.org/packages/2e/30/7687e4f87086829955013ca12a9233523349767f69653ebc27036313def9/jiter-0.13.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0a2bd69fc1d902e89925fc34d1da51b2128019423d7b339a45d9e99c894e0663", size = 307958, upload-time = "2026-02-02T12:35:57.165Z" }, + { url = "https://files.pythonhosted.org/packages/c3/27/e57f9a783246ed95481e6749cc5002a8a767a73177a83c63ea71f0528b90/jiter-0.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f917a04240ef31898182f76a332f508f2cc4b57d2b4d7ad2dbfebbfe167eb505", size = 318597, upload-time = "2026-02-02T12:35:58.591Z" }, + { url = "https://files.pythonhosted.org/packages/cf/52/e5719a60ac5d4d7c5995461a94ad5ef962a37c8bf5b088390e6fad59b2ff/jiter-0.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1e2b199f446d3e82246b4fd9236d7cb502dc2222b18698ba0d986d2fecc6152", size = 348821, upload-time = "2026-02-02T12:36:00.093Z" }, + { url = "https://files.pythonhosted.org/packages/61/db/c1efc32b8ba4c740ab3fc2d037d8753f67685f475e26b9d6536a4322bcdd/jiter-0.13.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04670992b576fa65bd056dbac0c39fe8bd67681c380cb2b48efa885711d9d726", size = 364163, upload-time = "2026-02-02T12:36:01.937Z" }, + { url = "https://files.pythonhosted.org/packages/55/8a/fb75556236047c8806995671a18e4a0ad646ed255276f51a20f32dceaeec/jiter-0.13.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a1aff1fbdb803a376d4d22a8f63f8e7ccbce0b4890c26cc7af9e501ab339ef0", size = 483709, upload-time = "2026-02-02T12:36:03.41Z" }, + { url = "https://files.pythonhosted.org/packages/7e/16/43512e6ee863875693a8e6f6d532e19d650779d6ba9a81593ae40a9088ff/jiter-0.13.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b3fb8c2053acaef8580809ac1d1f7481a0a0bdc012fd7f5d8b18fb696a5a089", size = 370480, upload-time = "2026-02-02T12:36:04.791Z" }, + { url = "https://files.pythonhosted.org/packages/f8/4c/09b93e30e984a187bc8aaa3510e1ec8dcbdcd71ca05d2f56aac0492453aa/jiter-0.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdaba7d87e66f26a2c45d8cbadcbfc4bf7884182317907baf39cfe9775bb4d93", size = 360735, upload-time = "2026-02-02T12:36:06.994Z" }, + { url = "https://files.pythonhosted.org/packages/1a/1b/46c5e349019874ec5dfa508c14c37e29864ea108d376ae26d90bee238cd7/jiter-0.13.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7b88d649135aca526da172e48083da915ec086b54e8e73a425ba50999468cc08", size = 391814, upload-time = "2026-02-02T12:36:08.368Z" }, + { url = "https://files.pythonhosted.org/packages/15/9e/26184760e85baee7162ad37b7912797d2077718476bf91517641c92b3639/jiter-0.13.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e404ea551d35438013c64b4f357b0474c7abf9f781c06d44fcaf7a14c69ff9e2", size = 513990, upload-time = "2026-02-02T12:36:09.993Z" }, + { url = "https://files.pythonhosted.org/packages/e9/34/2c9355247d6debad57a0a15e76ab1566ab799388042743656e566b3b7de1/jiter-0.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1f4748aad1b4a93c8bdd70f604d0f748cdc0e8744c5547798acfa52f10e79228", size = 548021, upload-time = "2026-02-02T12:36:11.376Z" }, + { url = "https://files.pythonhosted.org/packages/ac/4a/9f2c23255d04a834398b9c2e0e665382116911dc4d06b795710503cdad25/jiter-0.13.0-cp312-cp312-win32.whl", hash = "sha256:0bf670e3b1445fc4d31612199f1744f67f889ee1bbae703c4b54dc097e5dd394", size = 203024, upload-time = "2026-02-02T12:36:12.682Z" }, + { url = "https://files.pythonhosted.org/packages/09/ee/f0ae675a957ae5a8f160be3e87acea6b11dc7b89f6b7ab057e77b2d2b13a/jiter-0.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:15db60e121e11fe186c0b15236bd5d18381b9ddacdcf4e659feb96fc6c969c92", size = 205424, upload-time = "2026-02-02T12:36:13.93Z" }, + { url = "https://files.pythonhosted.org/packages/1b/02/ae611edf913d3cbf02c97cdb90374af2082c48d7190d74c1111dde08bcdd/jiter-0.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:41f92313d17989102f3cb5dd533a02787cdb99454d494344b0361355da52fcb9", size = 186818, upload-time = "2026-02-02T12:36:15.308Z" }, + { url = "https://files.pythonhosted.org/packages/91/9c/7ee5a6ff4b9991e1a45263bfc46731634c4a2bde27dfda6c8251df2d958c/jiter-0.13.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1f8a55b848cbabf97d861495cd65f1e5c590246fabca8b48e1747c4dfc8f85bf", size = 306897, upload-time = "2026-02-02T12:36:16.748Z" }, + { url = "https://files.pythonhosted.org/packages/7c/02/be5b870d1d2be5dd6a91bdfb90f248fbb7dcbd21338f092c6b89817c3dbf/jiter-0.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f556aa591c00f2c45eb1b89f68f52441a016034d18b65da60e2d2875bbbf344a", size = 317507, upload-time = "2026-02-02T12:36:18.351Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/b25d2ec333615f5f284f3a4024f7ce68cfa0604c322c6808b2344c7f5d2b/jiter-0.13.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7e1d61da332ec412350463891923f960c3073cf1aae93b538f0bb4c8cd46efb", size = 350560, upload-time = "2026-02-02T12:36:19.746Z" }, + { url = "https://files.pythonhosted.org/packages/be/ec/74dcb99fef0aca9fbe56b303bf79f6bd839010cb18ad41000bf6cc71eec0/jiter-0.13.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3097d665a27bc96fd9bbf7f86178037db139f319f785e4757ce7ccbf390db6c2", size = 363232, upload-time = "2026-02-02T12:36:21.243Z" }, + { url = "https://files.pythonhosted.org/packages/1b/37/f17375e0bb2f6a812d4dd92d7616e41917f740f3e71343627da9db2824ce/jiter-0.13.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d01ecc3a8cbdb6f25a37bd500510550b64ddf9f7d64a107d92f3ccb25035d0f", size = 483727, upload-time = "2026-02-02T12:36:22.688Z" }, + { url = "https://files.pythonhosted.org/packages/77/d2/a71160a5ae1a1e66c1395b37ef77da67513b0adba73b993a27fbe47eb048/jiter-0.13.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed9bbc30f5d60a3bdf63ae76beb3f9db280d7f195dfcfa61af792d6ce912d159", size = 370799, upload-time = "2026-02-02T12:36:24.106Z" }, + { url = "https://files.pythonhosted.org/packages/01/99/ed5e478ff0eb4e8aa5fd998f9d69603c9fd3f32de3bd16c2b1194f68361c/jiter-0.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98fbafb6e88256f4454de33c1f40203d09fc33ed19162a68b3b257b29ca7f663", size = 359120, upload-time = "2026-02-02T12:36:25.519Z" }, + { url = "https://files.pythonhosted.org/packages/16/be/7ffd08203277a813f732ba897352797fa9493faf8dc7995b31f3d9cb9488/jiter-0.13.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5467696f6b827f1116556cb0db620440380434591e93ecee7fd14d1a491b6daa", size = 390664, upload-time = "2026-02-02T12:36:26.866Z" }, + { url = "https://files.pythonhosted.org/packages/d1/84/e0787856196d6d346264d6dcccb01f741e5f0bd014c1d9a2ebe149caf4f3/jiter-0.13.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:2d08c9475d48b92892583df9da592a0e2ac49bcd41fae1fec4f39ba6cf107820", size = 513543, upload-time = "2026-02-02T12:36:28.217Z" }, + { url = "https://files.pythonhosted.org/packages/65/50/ecbd258181c4313cf79bca6c88fb63207d04d5bf5e4f65174114d072aa55/jiter-0.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:aed40e099404721d7fcaf5b89bd3b4568a4666358bcac7b6b15c09fb6252ab68", size = 547262, upload-time = "2026-02-02T12:36:29.678Z" }, + { url = "https://files.pythonhosted.org/packages/27/da/68f38d12e7111d2016cd198161b36e1f042bd115c169255bcb7ec823a3bf/jiter-0.13.0-cp313-cp313-win32.whl", hash = "sha256:36ebfbcffafb146d0e6ffb3e74d51e03d9c35ce7c625c8066cdbfc7b953bdc72", size = 200630, upload-time = "2026-02-02T12:36:31.808Z" }, + { url = "https://files.pythonhosted.org/packages/25/65/3bd1a972c9a08ecd22eb3b08a95d1941ebe6938aea620c246cf426ae09c2/jiter-0.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:8d76029f077379374cf0dbc78dbe45b38dec4a2eb78b08b5194ce836b2517afc", size = 202602, upload-time = "2026-02-02T12:36:33.679Z" }, + { url = "https://files.pythonhosted.org/packages/15/fe/13bd3678a311aa67686bb303654792c48206a112068f8b0b21426eb6851e/jiter-0.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:bb7613e1a427cfcb6ea4544f9ac566b93d5bf67e0d48c787eca673ff9c9dff2b", size = 185939, upload-time = "2026-02-02T12:36:35.065Z" }, + { url = "https://files.pythonhosted.org/packages/49/19/a929ec002ad3228bc97ca01dbb14f7632fffdc84a95ec92ceaf4145688ae/jiter-0.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fa476ab5dd49f3bf3a168e05f89358c75a17608dbabb080ef65f96b27c19ab10", size = 316616, upload-time = "2026-02-02T12:36:36.579Z" }, + { url = "https://files.pythonhosted.org/packages/52/56/d19a9a194afa37c1728831e5fb81b7722c3de18a3109e8f282bfc23e587a/jiter-0.13.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ade8cb6ff5632a62b7dbd4757d8c5573f7a2e9ae285d6b5b841707d8363205ef", size = 346850, upload-time = "2026-02-02T12:36:38.058Z" }, + { url = "https://files.pythonhosted.org/packages/36/4a/94e831c6bf287754a8a019cb966ed39ff8be6ab78cadecf08df3bb02d505/jiter-0.13.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9950290340acc1adaded363edd94baebcee7dabdfa8bee4790794cd5cfad2af6", size = 358551, upload-time = "2026-02-02T12:36:39.417Z" }, + { url = "https://files.pythonhosted.org/packages/a2/ec/a4c72c822695fa80e55d2b4142b73f0012035d9fcf90eccc56bc060db37c/jiter-0.13.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2b4972c6df33731aac0742b64fd0d18e0a69bc7d6e03108ce7d40c85fd9e3e6d", size = 201950, upload-time = "2026-02-02T12:36:40.791Z" }, + { url = "https://files.pythonhosted.org/packages/b6/00/393553ec27b824fbc29047e9c7cd4a3951d7fbe4a76743f17e44034fa4e4/jiter-0.13.0-cp313-cp313t-win_arm64.whl", hash = "sha256:701a1e77d1e593c1b435315ff625fd071f0998c5f02792038a5ca98899261b7d", size = 185852, upload-time = "2026-02-02T12:36:42.077Z" }, + { url = "https://files.pythonhosted.org/packages/6e/f5/f1997e987211f6f9bd71b8083047b316208b4aca0b529bb5f8c96c89ef3e/jiter-0.13.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:cc5223ab19fe25e2f0bf2643204ad7318896fe3729bf12fde41b77bfc4fafff0", size = 308804, upload-time = "2026-02-02T12:36:43.496Z" }, + { url = "https://files.pythonhosted.org/packages/cd/8f/5482a7677731fd44881f0204981ce2d7175db271f82cba2085dd2212e095/jiter-0.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9776ebe51713acf438fd9b4405fcd86893ae5d03487546dae7f34993217f8a91", size = 318787, upload-time = "2026-02-02T12:36:45.071Z" }, + { url = "https://files.pythonhosted.org/packages/f3/b9/7257ac59778f1cd025b26a23c5520a36a424f7f1b068f2442a5b499b7464/jiter-0.13.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:879e768938e7b49b5e90b7e3fecc0dbec01b8cb89595861fb39a8967c5220d09", size = 353880, upload-time = "2026-02-02T12:36:47.365Z" }, + { url = "https://files.pythonhosted.org/packages/c3/87/719eec4a3f0841dad99e3d3604ee4cba36af4419a76f3cb0b8e2e691ad67/jiter-0.13.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:682161a67adea11e3aae9038c06c8b4a9a71023228767477d683f69903ebc607", size = 366702, upload-time = "2026-02-02T12:36:48.871Z" }, + { url = "https://files.pythonhosted.org/packages/d2/65/415f0a75cf6921e43365a1bc227c565cb949caca8b7532776e430cbaa530/jiter-0.13.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a13b68cd1cd8cc9de8f244ebae18ccb3e4067ad205220ef324c39181e23bbf66", size = 486319, upload-time = "2026-02-02T12:36:53.006Z" }, + { url = "https://files.pythonhosted.org/packages/54/a2/9e12b48e82c6bbc6081fd81abf915e1443add1b13d8fc586e1d90bb02bb8/jiter-0.13.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87ce0f14c6c08892b610686ae8be350bf368467b6acd5085a5b65441e2bf36d2", size = 372289, upload-time = "2026-02-02T12:36:54.593Z" }, + { url = "https://files.pythonhosted.org/packages/4e/c1/e4693f107a1789a239c759a432e9afc592366f04e901470c2af89cfd28e1/jiter-0.13.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c365005b05505a90d1c47856420980d0237adf82f70c4aff7aebd3c1cc143ad", size = 360165, upload-time = "2026-02-02T12:36:56.112Z" }, + { url = "https://files.pythonhosted.org/packages/17/08/91b9ea976c1c758240614bd88442681a87672eebc3d9a6dde476874e706b/jiter-0.13.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1317fdffd16f5873e46ce27d0e0f7f4f90f0cdf1d86bf6abeaea9f63ca2c401d", size = 389634, upload-time = "2026-02-02T12:36:57.495Z" }, + { url = "https://files.pythonhosted.org/packages/18/23/58325ef99390d6d40427ed6005bf1ad54f2577866594bcf13ce55675f87d/jiter-0.13.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c05b450d37ba0c9e21c77fef1f205f56bcee2330bddca68d344baebfc55ae0df", size = 514933, upload-time = "2026-02-02T12:36:58.909Z" }, + { url = "https://files.pythonhosted.org/packages/5b/25/69f1120c7c395fd276c3996bb8adefa9c6b84c12bb7111e5c6ccdcd8526d/jiter-0.13.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:775e10de3849d0631a97c603f996f518159272db00fdda0a780f81752255ee9d", size = 548842, upload-time = "2026-02-02T12:37:00.433Z" }, + { url = "https://files.pythonhosted.org/packages/18/05/981c9669d86850c5fbb0d9e62bba144787f9fba84546ba43d624ee27ef29/jiter-0.13.0-cp314-cp314-win32.whl", hash = "sha256:632bf7c1d28421c00dd8bbb8a3bac5663e1f57d5cd5ed962bce3c73bf62608e6", size = 202108, upload-time = "2026-02-02T12:37:01.718Z" }, + { url = "https://files.pythonhosted.org/packages/8d/96/cdcf54dd0b0341db7d25413229888a346c7130bd20820530905fdb65727b/jiter-0.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:f22ef501c3f87ede88f23f9b11e608581c14f04db59b6a801f354397ae13739f", size = 204027, upload-time = "2026-02-02T12:37:03.075Z" }, + { url = "https://files.pythonhosted.org/packages/fb/f9/724bcaaab7a3cd727031fe4f6995cb86c4bd344909177c186699c8dec51a/jiter-0.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:07b75fe09a4ee8e0c606200622e571e44943f47254f95e2436c8bdcaceb36d7d", size = 187199, upload-time = "2026-02-02T12:37:04.414Z" }, + { url = "https://files.pythonhosted.org/packages/62/92/1661d8b9fd6a3d7a2d89831db26fe3c1509a287d83ad7838831c7b7a5c7e/jiter-0.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:964538479359059a35fb400e769295d4b315ae61e4105396d355a12f7fef09f0", size = 318423, upload-time = "2026-02-02T12:37:05.806Z" }, + { url = "https://files.pythonhosted.org/packages/4f/3b/f77d342a54d4ebcd128e520fc58ec2f5b30a423b0fd26acdfc0c6fef8e26/jiter-0.13.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e104da1db1c0991b3eaed391ccd650ae8d947eab1480c733e5a3fb28d4313e40", size = 351438, upload-time = "2026-02-02T12:37:07.189Z" }, + { url = "https://files.pythonhosted.org/packages/76/b3/ba9a69f0e4209bd3331470c723c2f5509e6f0482e416b612431a5061ed71/jiter-0.13.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e3a5f0cde8ff433b8e88e41aa40131455420fb3649a3c7abdda6145f8cb7202", size = 364774, upload-time = "2026-02-02T12:37:08.579Z" }, + { url = "https://files.pythonhosted.org/packages/b3/16/6cdb31fa342932602458dbb631bfbd47f601e03d2e4950740e0b2100b570/jiter-0.13.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:57aab48f40be1db920a582b30b116fe2435d184f77f0e4226f546794cedd9cf0", size = 487238, upload-time = "2026-02-02T12:37:10.066Z" }, + { url = "https://files.pythonhosted.org/packages/ed/b1/956cc7abaca8d95c13aa8d6c9b3f3797241c246cd6e792934cc4c8b250d2/jiter-0.13.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7772115877c53f62beeb8fd853cab692dbc04374ef623b30f997959a4c0e7e95", size = 372892, upload-time = "2026-02-02T12:37:11.656Z" }, + { url = "https://files.pythonhosted.org/packages/26/c4/97ecde8b1e74f67b8598c57c6fccf6df86ea7861ed29da84629cdbba76c4/jiter-0.13.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1211427574b17b633cfceba5040de8081e5abf114f7a7602f73d2e16f9fdaa59", size = 360309, upload-time = "2026-02-02T12:37:13.244Z" }, + { url = "https://files.pythonhosted.org/packages/4b/d7/eabe3cf46715854ccc80be2cd78dd4c36aedeb30751dbf85a1d08c14373c/jiter-0.13.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7beae3a3d3b5212d3a55d2961db3c292e02e302feb43fce6a3f7a31b90ea6dfe", size = 389607, upload-time = "2026-02-02T12:37:14.881Z" }, + { url = "https://files.pythonhosted.org/packages/df/2d/03963fc0804e6109b82decfb9974eb92df3797fe7222428cae12f8ccaa0c/jiter-0.13.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:e5562a0f0e90a6223b704163ea28e831bd3a9faa3512a711f031611e6b06c939", size = 514986, upload-time = "2026-02-02T12:37:16.326Z" }, + { url = "https://files.pythonhosted.org/packages/f6/6c/8c83b45eb3eb1c1e18d841fe30b4b5bc5619d781267ca9bc03e005d8fd0a/jiter-0.13.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:6c26a424569a59140fb51160a56df13f438a2b0967365e987889186d5fc2f6f9", size = 548756, upload-time = "2026-02-02T12:37:17.736Z" }, + { url = "https://files.pythonhosted.org/packages/47/66/eea81dfff765ed66c68fd2ed8c96245109e13c896c2a5015c7839c92367e/jiter-0.13.0-cp314-cp314t-win32.whl", hash = "sha256:24dc96eca9f84da4131cdf87a95e6ce36765c3b156fc9ae33280873b1c32d5f6", size = 201196, upload-time = "2026-02-02T12:37:19.101Z" }, + { url = "https://files.pythonhosted.org/packages/ff/32/4ac9c7a76402f8f00d00842a7f6b83b284d0cf7c1e9d4227bc95aa6d17fa/jiter-0.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0a8d76c7524087272c8ae913f5d9d608bd839154b62c4322ef65723d2e5bb0b8", size = 204215, upload-time = "2026-02-02T12:37:20.495Z" }, + { url = "https://files.pythonhosted.org/packages/f9/8e/7def204fea9f9be8b3c21a6f2dd6c020cf56c7d5ff753e0e23ed7f9ea57e/jiter-0.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2c26cf47e2cad140fa23b6d58d435a7c0161f5c514284802f25e87fddfe11024", size = 187152, upload-time = "2026-02-02T12:37:22.124Z" }, + { url = "https://files.pythonhosted.org/packages/79/b3/3c29819a27178d0e461a8571fb63c6ae38be6dc36b78b3ec2876bbd6a910/jiter-0.13.0-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b1cbfa133241d0e6bdab48dcdc2604e8ba81512f6bbd68ec3e8e1357dd3c316c", size = 307016, upload-time = "2026-02-02T12:37:42.755Z" }, + { url = "https://files.pythonhosted.org/packages/eb/ae/60993e4b07b1ac5ebe46da7aa99fdbb802eb986c38d26e3883ac0125c4e0/jiter-0.13.0-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:db367d8be9fad6e8ebbac4a7578b7af562e506211036cba2c06c3b998603c3d2", size = 305024, upload-time = "2026-02-02T12:37:44.774Z" }, + { url = "https://files.pythonhosted.org/packages/77/fa/2227e590e9cf98803db2811f172b2d6460a21539ab73006f251c66f44b14/jiter-0.13.0-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45f6f8efb2f3b0603092401dc2df79fa89ccbc027aaba4174d2d4133ed661434", size = 339337, upload-time = "2026-02-02T12:37:46.668Z" }, + { url = "https://files.pythonhosted.org/packages/2d/92/015173281f7eb96c0ef580c997da8ef50870d4f7f4c9e03c845a1d62ae04/jiter-0.13.0-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:597245258e6ad085d064780abfb23a284d418d3e61c57362d9449c6c7317ee2d", size = 346395, upload-time = "2026-02-02T12:37:48.09Z" }, + { url = "https://files.pythonhosted.org/packages/80/60/e50fa45dd7e2eae049f0ce964663849e897300433921198aef94b6ffa23a/jiter-0.13.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:3d744a6061afba08dd7ae375dcde870cffb14429b7477e10f67e9e6d68772a0a", size = 305169, upload-time = "2026-02-02T12:37:50.376Z" }, + { url = "https://files.pythonhosted.org/packages/d2/73/a009f41c5eed71c49bec53036c4b33555afcdee70682a18c6f66e396c039/jiter-0.13.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:ff732bd0a0e778f43d5009840f20b935e79087b4dc65bd36f1cd0f9b04b8ff7f", size = 303808, upload-time = "2026-02-02T12:37:52.092Z" }, + { url = "https://files.pythonhosted.org/packages/c4/10/528b439290763bff3d939268085d03382471b442f212dca4ff5f12802d43/jiter-0.13.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab44b178f7981fcaea7e0a5df20e773c663d06ffda0198f1a524e91b2fde7e59", size = 337384, upload-time = "2026-02-02T12:37:53.582Z" }, + { url = "https://files.pythonhosted.org/packages/67/8a/a342b2f0251f3dac4ca17618265d93bf244a2a4d089126e81e4c1056ac50/jiter-0.13.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bb00b6d26db67a05fe3e12c76edc75f32077fb51deed13822dc648fa373bc19", size = 343768, upload-time = "2026-02-02T12:37:55.055Z" }, ] [[package]] @@ -1154,65 +1199,75 @@ wheels = [ [[package]] name = "librt" -version = "0.7.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b3/d9/6f3d3fcf5e5543ed8a60cc70fa7d50508ed60b8a10e9af6d2058159ab54e/librt-0.7.3.tar.gz", hash = "sha256:3ec50cf65235ff5c02c5b747748d9222e564ad48597122a361269dd3aa808798", size = 144549, upload-time = "2025-12-06T19:04:45.553Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/21/e6/f6391f5c6f158d31ed9af6bd1b1bcd3ffafdea1d816bc4219d0d90175a7f/librt-0.7.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:687403cced6a29590e6be6964463835315905221d797bc5c934a98750fe1a9af", size = 54711, upload-time = "2025-12-06T19:03:24.6Z" }, - { url = "https://files.pythonhosted.org/packages/ab/1b/53c208188c178987c081560a0fcf36f5ca500d5e21769596c845ef2f40d4/librt-0.7.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:24d70810f6e2ea853ff79338001533716b373cc0f63e2a0be5bc96129edb5fb5", size = 56664, upload-time = "2025-12-06T19:03:25.969Z" }, - { url = "https://files.pythonhosted.org/packages/cb/5c/d9da832b9a1e5f8366e8a044ec80217945385b26cb89fd6f94bfdc7d80b0/librt-0.7.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bf8c7735fbfc0754111f00edda35cf9e98a8d478de6c47b04eaa9cef4300eaa7", size = 161701, upload-time = "2025-12-06T19:03:27.035Z" }, - { url = "https://files.pythonhosted.org/packages/20/aa/1e0a7aba15e78529dd21f233076b876ee58c8b8711b1793315bdd3b263b0/librt-0.7.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32d43610dff472eab939f4d7fbdd240d1667794192690433672ae22d7af8445", size = 171040, upload-time = "2025-12-06T19:03:28.482Z" }, - { url = "https://files.pythonhosted.org/packages/69/46/3cfa325c1c2bc25775ec6ec1718cfbec9cff4ac767d37d2d3a2d1cc6f02c/librt-0.7.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:adeaa886d607fb02563c1f625cf2ee58778a2567c0c109378da8f17ec3076ad7", size = 184720, upload-time = "2025-12-06T19:03:29.599Z" }, - { url = "https://files.pythonhosted.org/packages/99/bb/e4553433d7ac47f4c75d0a7e59b13aee0e08e88ceadbee356527a9629b0a/librt-0.7.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:572a24fc5958c61431da456a0ef1eeea6b4989d81eeb18b8e5f1f3077592200b", size = 180731, upload-time = "2025-12-06T19:03:31.201Z" }, - { url = "https://files.pythonhosted.org/packages/35/89/51cd73006232981a3106d4081fbaa584ac4e27b49bc02266468d3919db03/librt-0.7.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6488e69d408b492e08bfb68f20c4a899a354b4386a446ecd490baff8d0862720", size = 174565, upload-time = "2025-12-06T19:03:32.818Z" }, - { url = "https://files.pythonhosted.org/packages/42/54/0578a78b587e5aa22486af34239a052c6366835b55fc307bc64380229e3f/librt-0.7.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ed028fc3d41adda916320712838aec289956c89b4f0a361ceadf83a53b4c047a", size = 195247, upload-time = "2025-12-06T19:03:34.434Z" }, - { url = "https://files.pythonhosted.org/packages/b5/0a/ee747cd999753dd9447e50b98fc36ee433b6c841a42dbf6d47b64b32a56e/librt-0.7.3-cp311-cp311-win32.whl", hash = "sha256:2cf9d73499486ce39eebbff5f42452518cc1f88d8b7ea4a711ab32962b176ee2", size = 47514, upload-time = "2025-12-06T19:03:35.959Z" }, - { url = "https://files.pythonhosted.org/packages/ec/af/8b13845178dec488e752878f8e290f8f89e7e34ae1528b70277aa1a6dd1e/librt-0.7.3-cp311-cp311-win_amd64.whl", hash = "sha256:35f1609e3484a649bb80431310ddbec81114cd86648f1d9482bc72a3b86ded2e", size = 54695, upload-time = "2025-12-06T19:03:36.956Z" }, - { url = "https://files.pythonhosted.org/packages/02/7a/ae59578501b1a25850266778f59279f4f3e726acc5c44255bfcb07b4bc57/librt-0.7.3-cp311-cp311-win_arm64.whl", hash = "sha256:550fdbfbf5bba6a2960b27376ca76d6aaa2bd4b1a06c4255edd8520c306fcfc0", size = 48142, upload-time = "2025-12-06T19:03:38.263Z" }, - { url = "https://files.pythonhosted.org/packages/29/90/ed8595fa4e35b6020317b5ea8d226a782dcbac7a997c19ae89fb07a41c66/librt-0.7.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0fa9ac2e49a6bee56e47573a6786cb635e128a7b12a0dc7851090037c0d397a3", size = 55687, upload-time = "2025-12-06T19:03:39.245Z" }, - { url = "https://files.pythonhosted.org/packages/dd/f6/6a20702a07b41006cb001a759440cb6b5362530920978f64a2b2ae2bf729/librt-0.7.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e980cf1ed1a2420a6424e2ed884629cdead291686f1048810a817de07b5eb18", size = 57127, upload-time = "2025-12-06T19:03:40.3Z" }, - { url = "https://files.pythonhosted.org/packages/79/f3/b0c4703d5ffe9359b67bb2ccb86c42d4e930a363cfc72262ac3ba53cff3e/librt-0.7.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:e094e445c37c57e9ec612847812c301840239d34ccc5d153a982fa9814478c60", size = 165336, upload-time = "2025-12-06T19:03:41.369Z" }, - { url = "https://files.pythonhosted.org/packages/02/69/3ba05b73ab29ccbe003856232cea4049769be5942d799e628d1470ed1694/librt-0.7.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aca73d70c3f553552ba9133d4a09e767dcfeee352d8d8d3eb3f77e38a3beb3ed", size = 174237, upload-time = "2025-12-06T19:03:42.44Z" }, - { url = "https://files.pythonhosted.org/packages/22/ad/d7c2671e7bf6c285ef408aa435e9cd3fdc06fd994601e1f2b242df12034f/librt-0.7.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c634a0a6db395fdaba0361aa78395597ee72c3aad651b9a307a3a7eaf5efd67e", size = 189017, upload-time = "2025-12-06T19:03:44.01Z" }, - { url = "https://files.pythonhosted.org/packages/f4/94/d13f57193148004592b618555f296b41d2d79b1dc814ff8b3273a0bf1546/librt-0.7.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a59a69deeb458c858b8fea6acf9e2acd5d755d76cd81a655256bc65c20dfff5b", size = 183983, upload-time = "2025-12-06T19:03:45.834Z" }, - { url = "https://files.pythonhosted.org/packages/02/10/b612a9944ebd39fa143c7e2e2d33f2cb790205e025ddd903fb509a3a3bb3/librt-0.7.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d91e60ac44bbe3a77a67af4a4c13114cbe9f6d540337ce22f2c9eaf7454ca71f", size = 177602, upload-time = "2025-12-06T19:03:46.944Z" }, - { url = "https://files.pythonhosted.org/packages/1f/48/77bc05c4cc232efae6c5592c0095034390992edbd5bae8d6cf1263bb7157/librt-0.7.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:703456146dc2bf430f7832fd1341adac5c893ec3c1430194fdcefba00012555c", size = 199282, upload-time = "2025-12-06T19:03:48.069Z" }, - { url = "https://files.pythonhosted.org/packages/12/aa/05916ccd864227db1ffec2a303ae34f385c6b22d4e7ce9f07054dbcf083c/librt-0.7.3-cp312-cp312-win32.whl", hash = "sha256:b7c1239b64b70be7759554ad1a86288220bbb04d68518b527783c4ad3fb4f80b", size = 47879, upload-time = "2025-12-06T19:03:49.289Z" }, - { url = "https://files.pythonhosted.org/packages/50/92/7f41c42d31ea818b3c4b9cc1562e9714bac3c676dd18f6d5dd3d0f2aa179/librt-0.7.3-cp312-cp312-win_amd64.whl", hash = "sha256:ef59c938f72bdbc6ab52dc50f81d0637fde0f194b02d636987cea2ab30f8f55a", size = 54972, upload-time = "2025-12-06T19:03:50.335Z" }, - { url = "https://files.pythonhosted.org/packages/3f/dc/53582bbfb422311afcbc92adb75711f04e989cec052f08ec0152fbc36c9c/librt-0.7.3-cp312-cp312-win_arm64.whl", hash = "sha256:ff21c554304e8226bf80c3a7754be27c6c3549a9fec563a03c06ee8f494da8fc", size = 48338, upload-time = "2025-12-06T19:03:51.431Z" }, - { url = "https://files.pythonhosted.org/packages/93/7d/e0ce1837dfb452427db556e6d4c5301ba3b22fe8de318379fbd0593759b9/librt-0.7.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56f2a47beda8409061bc1c865bef2d4bd9ff9255219402c0817e68ab5ad89aed", size = 55742, upload-time = "2025-12-06T19:03:52.459Z" }, - { url = "https://files.pythonhosted.org/packages/be/c0/3564262301e507e1d5cf31c7d84cb12addf0d35e05ba53312494a2eba9a4/librt-0.7.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:14569ac5dd38cfccf0a14597a88038fb16811a6fede25c67b79c6d50fc2c8fdc", size = 57163, upload-time = "2025-12-06T19:03:53.516Z" }, - { url = "https://files.pythonhosted.org/packages/be/ac/245e72b7e443d24a562f6047563c7f59833384053073ef9410476f68505b/librt-0.7.3-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6038ccbd5968325a5d6fd393cf6e00b622a8de545f0994b89dd0f748dcf3e19e", size = 165840, upload-time = "2025-12-06T19:03:54.918Z" }, - { url = "https://files.pythonhosted.org/packages/98/af/587e4491f40adba066ba39a450c66bad794c8d92094f936a201bfc7c2b5f/librt-0.7.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d39079379a9a28e74f4d57dc6357fa310a1977b51ff12239d7271ec7e71d67f5", size = 174827, upload-time = "2025-12-06T19:03:56.082Z" }, - { url = "https://files.pythonhosted.org/packages/78/21/5b8c60ea208bc83dd00421022a3874330685d7e856404128dc3728d5d1af/librt-0.7.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8837d5a52a2d7aa9f4c3220a8484013aed1d8ad75240d9a75ede63709ef89055", size = 189612, upload-time = "2025-12-06T19:03:57.507Z" }, - { url = "https://files.pythonhosted.org/packages/da/2f/8b819169ef696421fb81cd04c6cdf225f6e96f197366001e9d45180d7e9e/librt-0.7.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:399bbd7bcc1633c3e356ae274a1deb8781c7bf84d9c7962cc1ae0c6e87837292", size = 184584, upload-time = "2025-12-06T19:03:58.686Z" }, - { url = "https://files.pythonhosted.org/packages/6c/fc/af9d225a9395b77bd7678362cb055d0b8139c2018c37665de110ca388022/librt-0.7.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8d8cf653e798ee4c4e654062b633db36984a1572f68c3aa25e364a0ddfbbb910", size = 178269, upload-time = "2025-12-06T19:03:59.769Z" }, - { url = "https://files.pythonhosted.org/packages/6c/d8/7b4fa1683b772966749d5683aa3fd605813defffe157833a8fa69cc89207/librt-0.7.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:2f03484b54bf4ae80ab2e504a8d99d20d551bfe64a7ec91e218010b467d77093", size = 199852, upload-time = "2025-12-06T19:04:00.901Z" }, - { url = "https://files.pythonhosted.org/packages/77/e8/4598413aece46ca38d9260ef6c51534bd5f34b5c21474fcf210ce3a02123/librt-0.7.3-cp313-cp313-win32.whl", hash = "sha256:44b3689b040df57f492e02cd4f0bacd1b42c5400e4b8048160c9d5e866de8abe", size = 47936, upload-time = "2025-12-06T19:04:02.054Z" }, - { url = "https://files.pythonhosted.org/packages/af/80/ac0e92d5ef8c6791b3e2c62373863827a279265e0935acdf807901353b0e/librt-0.7.3-cp313-cp313-win_amd64.whl", hash = "sha256:6b407c23f16ccc36614c136251d6b32bf30de7a57f8e782378f1107be008ddb0", size = 54965, upload-time = "2025-12-06T19:04:03.224Z" }, - { url = "https://files.pythonhosted.org/packages/f1/fd/042f823fcbff25c1449bb4203a29919891ca74141b68d3a5f6612c4ce283/librt-0.7.3-cp313-cp313-win_arm64.whl", hash = "sha256:abfc57cab3c53c4546aee31859ef06753bfc136c9d208129bad23e2eca39155a", size = 48350, upload-time = "2025-12-06T19:04:04.234Z" }, - { url = "https://files.pythonhosted.org/packages/3e/ae/c6ecc7bb97134a71b5241e8855d39964c0e5f4d96558f0d60593892806d2/librt-0.7.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:120dd21d46ff875e849f1aae19346223cf15656be489242fe884036b23d39e93", size = 55175, upload-time = "2025-12-06T19:04:05.308Z" }, - { url = "https://files.pythonhosted.org/packages/cf/bc/2cc0cb0ab787b39aa5c7645cd792433c875982bdf12dccca558b89624594/librt-0.7.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1617bea5ab31266e152871208502ee943cb349c224846928a1173c864261375e", size = 56881, upload-time = "2025-12-06T19:04:06.674Z" }, - { url = "https://files.pythonhosted.org/packages/8e/87/397417a386190b70f5bf26fcedbaa1515f19dce33366e2684c6b7ee83086/librt-0.7.3-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:93b2a1f325fefa1482516ced160c8c7b4b8d53226763fa6c93d151fa25164207", size = 163710, upload-time = "2025-12-06T19:04:08.437Z" }, - { url = "https://files.pythonhosted.org/packages/c9/37/7338f85b80e8a17525d941211451199845093ca242b32efbf01df8531e72/librt-0.7.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3d4801db8354436fd3936531e7f0e4feb411f62433a6b6cb32bb416e20b529f", size = 172471, upload-time = "2025-12-06T19:04:10.124Z" }, - { url = "https://files.pythonhosted.org/packages/3b/e0/741704edabbfae2c852fedc1b40d9ed5a783c70ed3ed8e4fe98f84b25d13/librt-0.7.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11ad45122bbed42cfc8b0597450660126ef28fd2d9ae1a219bc5af8406f95678", size = 186804, upload-time = "2025-12-06T19:04:11.586Z" }, - { url = "https://files.pythonhosted.org/packages/f4/d1/0a82129d6ba242f3be9af34815be089f35051bc79619f5c27d2c449ecef6/librt-0.7.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:6b4e7bff1d76dd2b46443078519dc75df1b5e01562345f0bb740cea5266d8218", size = 181817, upload-time = "2025-12-06T19:04:12.802Z" }, - { url = "https://files.pythonhosted.org/packages/4f/32/704f80bcf9979c68d4357c46f2af788fbf9d5edda9e7de5786ed2255e911/librt-0.7.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:d86f94743a11873317094326456b23f8a5788bad9161fd2f0e52088c33564620", size = 175602, upload-time = "2025-12-06T19:04:14.004Z" }, - { url = "https://files.pythonhosted.org/packages/f7/6d/4355cfa0fae0c062ba72f541d13db5bc575770125a7ad3d4f46f4109d305/librt-0.7.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:754a0d09997095ad764ccef050dd5bf26cbf457aab9effcba5890dad081d879e", size = 196497, upload-time = "2025-12-06T19:04:15.487Z" }, - { url = "https://files.pythonhosted.org/packages/2e/eb/ac6d8517d44209e5a712fde46f26d0055e3e8969f24d715f70bd36056230/librt-0.7.3-cp314-cp314-win32.whl", hash = "sha256:fbd7351d43b80d9c64c3cfcb50008f786cc82cba0450e8599fdd64f264320bd3", size = 44678, upload-time = "2025-12-06T19:04:16.688Z" }, - { url = "https://files.pythonhosted.org/packages/e9/93/238f026d141faf9958da588c761a0812a1a21c98cc54a76f3608454e4e59/librt-0.7.3-cp314-cp314-win_amd64.whl", hash = "sha256:d376a35c6561e81d2590506804b428fc1075fcc6298fc5bb49b771534c0ba010", size = 51689, upload-time = "2025-12-06T19:04:17.726Z" }, - { url = "https://files.pythonhosted.org/packages/52/44/43f462ad9dcf9ed7d3172fe2e30d77b980956250bd90e9889a9cca93df2a/librt-0.7.3-cp314-cp314-win_arm64.whl", hash = "sha256:cbdb3f337c88b43c3b49ca377731912c101178be91cb5071aac48faa898e6f8e", size = 44662, upload-time = "2025-12-06T19:04:18.771Z" }, - { url = "https://files.pythonhosted.org/packages/1d/35/fed6348915f96b7323241de97f26e2af481e95183b34991df12fd5ce31b1/librt-0.7.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9f0e0927efe87cd42ad600628e595a1a0aa1c64f6d0b55f7e6059079a428641a", size = 57347, upload-time = "2025-12-06T19:04:19.812Z" }, - { url = "https://files.pythonhosted.org/packages/9a/f2/045383ccc83e3fea4fba1b761796584bc26817b6b2efb6b8a6731431d16f/librt-0.7.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:020c6db391268bcc8ce75105cb572df8cb659a43fd347366aaa407c366e5117a", size = 59223, upload-time = "2025-12-06T19:04:20.862Z" }, - { url = "https://files.pythonhosted.org/packages/77/3f/c081f8455ab1d7f4a10dbe58463ff97119272ff32494f21839c3b9029c2c/librt-0.7.3-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:7af7785f5edd1f418da09a8cdb9ec84b0213e23d597413e06525340bcce1ea4f", size = 183861, upload-time = "2025-12-06T19:04:21.963Z" }, - { url = "https://files.pythonhosted.org/packages/1d/f5/73c5093c22c31fbeaebc25168837f05ebfd8bf26ce00855ef97a5308f36f/librt-0.7.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8ccadf260bb46a61b9c7e89e2218f6efea9f3eeaaab4e3d1f58571890e54858e", size = 194594, upload-time = "2025-12-06T19:04:23.14Z" }, - { url = "https://files.pythonhosted.org/packages/78/b8/d5f17d4afe16612a4a94abfded94c16c5a033f183074fb130dfe56fc1a42/librt-0.7.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9883b2d819ce83f87ba82a746c81d14ada78784db431e57cc9719179847376e", size = 206759, upload-time = "2025-12-06T19:04:24.328Z" }, - { url = "https://files.pythonhosted.org/packages/36/2e/021765c1be85ee23ffd5b5b968bb4cba7526a4db2a0fc27dcafbdfc32da7/librt-0.7.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:59cb0470612d21fa1efddfa0dd710756b50d9c7fb6c1236bbf8ef8529331dc70", size = 203210, upload-time = "2025-12-06T19:04:25.544Z" }, - { url = "https://files.pythonhosted.org/packages/77/f0/9923656e42da4fd18c594bd08cf6d7e152d4158f8b808e210d967f0dcceb/librt-0.7.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:1fe603877e1865b5fd047a5e40379509a4a60204aa7aa0f72b16f7a41c3f0712", size = 196708, upload-time = "2025-12-06T19:04:26.725Z" }, - { url = "https://files.pythonhosted.org/packages/fc/0b/0708b886ac760e64d6fbe7e16024e4be3ad1a3629d19489a97e9cf4c3431/librt-0.7.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5460d99ed30f043595bbdc888f542bad2caeb6226b01c33cda3ae444e8f82d42", size = 217212, upload-time = "2025-12-06T19:04:27.892Z" }, - { url = "https://files.pythonhosted.org/packages/5d/7f/12a73ff17bca4351e73d585dd9ebf46723c4a8622c4af7fe11a2e2d011ff/librt-0.7.3-cp314-cp314t-win32.whl", hash = "sha256:d09f677693328503c9e492e33e9601464297c01f9ebd966ea8fc5308f3069bfd", size = 45586, upload-time = "2025-12-06T19:04:29.116Z" }, - { url = "https://files.pythonhosted.org/packages/e2/df/8decd032ac9b995e4f5606cde783711a71094128d88d97a52e397daf2c89/librt-0.7.3-cp314-cp314t-win_amd64.whl", hash = "sha256:25711f364c64cab2c910a0247e90b51421e45dbc8910ceeb4eac97a9e132fc6f", size = 53002, upload-time = "2025-12-06T19:04:30.173Z" }, - { url = "https://files.pythonhosted.org/packages/de/0c/6605b6199de8178afe7efc77ca1d8e6db00453bc1d3349d27605c0f42104/librt-0.7.3-cp314-cp314t-win_arm64.whl", hash = "sha256:a9f9b661f82693eb56beb0605156c7fca57f535704ab91837405913417d6990b", size = 45647, upload-time = "2025-12-06T19:04:31.302Z" }, +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8a/3f/4ca7dd7819bf8ff303aca39c3c60e5320e46e766ab7f7dd627d3b9c11bdf/librt-0.8.0.tar.gz", hash = "sha256:cb74cdcbc0103fc988e04e5c58b0b31e8e5dd2babb9182b6f9490488eb36324b", size = 177306, upload-time = "2026-02-12T14:53:54.743Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/e9/42af181c89b65abfd557c1b017cba5b82098eef7bf26d1649d82ce93ccc7/librt-0.8.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0ce33a9778e294507f3a0e3468eccb6a698b5166df7db85661543eca1cfc5369", size = 65314, upload-time = "2026-02-12T14:52:14.778Z" }, + { url = "https://files.pythonhosted.org/packages/9d/4a/15a847fca119dc0334a4b8012b1e15fdc5fc19d505b71e227eaf1bcdba09/librt-0.8.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8070aa3368559de81061ef752770d03ca1f5fc9467d4d512d405bd0483bfffe6", size = 68015, upload-time = "2026-02-12T14:52:15.797Z" }, + { url = "https://files.pythonhosted.org/packages/e1/87/ffc8dbd6ab68dd91b736c88529411a6729649d2b74b887f91f3aaff8d992/librt-0.8.0-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:20f73d4fecba969efc15cdefd030e382502d56bb6f1fc66b580cce582836c9fa", size = 194508, upload-time = "2026-02-12T14:52:16.835Z" }, + { url = "https://files.pythonhosted.org/packages/89/92/a7355cea28d6c48ff6ff5083ac4a2a866fb9b07b786aa70d1f1116680cd5/librt-0.8.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a512c88900bdb1d448882f5623a0b1ad27ba81a9bd75dacfe17080b72272ca1f", size = 205630, upload-time = "2026-02-12T14:52:18.58Z" }, + { url = "https://files.pythonhosted.org/packages/ac/5e/54509038d7ac527828db95b8ba1c8f5d2649bc32fd8f39b1718ec9957dce/librt-0.8.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:015e2dde6e096d27c10238bf9f6492ba6c65822dfb69d2bf74c41a8e88b7ddef", size = 218289, upload-time = "2026-02-12T14:52:20.134Z" }, + { url = "https://files.pythonhosted.org/packages/6d/17/0ee0d13685cefee6d6f2d47bb643ddad3c62387e2882139794e6a5f1288a/librt-0.8.0-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1c25a131013eadd3c600686a0c0333eb2896483cbc7f65baa6a7ee761017aef9", size = 211508, upload-time = "2026-02-12T14:52:21.413Z" }, + { url = "https://files.pythonhosted.org/packages/4b/a8/1714ef6e9325582e3727de3be27e4c1b2f428ea411d09f1396374180f130/librt-0.8.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:21b14464bee0b604d80a638cf1ee3148d84ca4cc163dcdcecb46060c1b3605e4", size = 219129, upload-time = "2026-02-12T14:52:22.61Z" }, + { url = "https://files.pythonhosted.org/packages/89/d3/2d9fe353edff91cdc0ece179348054a6fa61f3de992c44b9477cb973509b/librt-0.8.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:05a3dd3f116747f7e1a2b475ccdc6fb637fd4987126d109e03013a79d40bf9e6", size = 213126, upload-time = "2026-02-12T14:52:23.819Z" }, + { url = "https://files.pythonhosted.org/packages/ad/8e/9f5c60444880f6ad50e3ff7475e5529e787797e7f3ad5432241633733b92/librt-0.8.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:fa37f99bff354ff191c6bcdffbc9d7cdd4fc37faccfc9be0ef3a4fd5613977da", size = 212279, upload-time = "2026-02-12T14:52:25.034Z" }, + { url = "https://files.pythonhosted.org/packages/fe/eb/d4a2cfa647da3022ae977f50d7eda1d91f70d7d1883cf958a4b6ef689eab/librt-0.8.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1566dbb9d1eb0987264c9b9460d212e809ba908d2f4a3999383a84d765f2f3f1", size = 234654, upload-time = "2026-02-12T14:52:26.204Z" }, + { url = "https://files.pythonhosted.org/packages/6a/31/26b978861c7983b036a3aea08bdbb2ec32bbaab1ad1d57c5e022be59afc1/librt-0.8.0-cp311-cp311-win32.whl", hash = "sha256:70defb797c4d5402166787a6b3c66dfb3fa7f93d118c0509ffafa35a392f4258", size = 54603, upload-time = "2026-02-12T14:52:27.342Z" }, + { url = "https://files.pythonhosted.org/packages/d0/78/f194ed7c48dacf875677e749c5d0d1d69a9daa7c994314a39466237fb1be/librt-0.8.0-cp311-cp311-win_amd64.whl", hash = "sha256:db953b675079884ffda33d1dca7189fb961b6d372153750beb81880384300817", size = 61730, upload-time = "2026-02-12T14:52:28.31Z" }, + { url = "https://files.pythonhosted.org/packages/97/ee/ad71095478d02137b6f49469dc808c595cfe89b50985f6b39c5345f0faab/librt-0.8.0-cp311-cp311-win_arm64.whl", hash = "sha256:75d1a8cab20b2043f03f7aab730551e9e440adc034d776f15f6f8d582b0a5ad4", size = 52274, upload-time = "2026-02-12T14:52:29.345Z" }, + { url = "https://files.pythonhosted.org/packages/fb/53/f3bc0c4921adb0d4a5afa0656f2c0fbe20e18e3e0295e12985b9a5dc3f55/librt-0.8.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:17269dd2745dbe8e42475acb28e419ad92dfa38214224b1b01020b8cac70b645", size = 66511, upload-time = "2026-02-12T14:52:30.34Z" }, + { url = "https://files.pythonhosted.org/packages/89/4b/4c96357432007c25a1b5e363045373a6c39481e49f6ba05234bb59a839c1/librt-0.8.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f4617cef654fca552f00ce5ffdf4f4b68770f18950e4246ce94629b789b92467", size = 68628, upload-time = "2026-02-12T14:52:31.491Z" }, + { url = "https://files.pythonhosted.org/packages/47/16/52d75374d1012e8fc709216b5eaa25f471370e2a2331b8be00f18670a6c7/librt-0.8.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:5cb11061a736a9db45e3c1293cfcb1e3caf205912dfa085734ba750f2197ff9a", size = 198941, upload-time = "2026-02-12T14:52:32.489Z" }, + { url = "https://files.pythonhosted.org/packages/fc/11/d5dd89e5a2228567b1228d8602d896736247424484db086eea6b8010bcba/librt-0.8.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4bb00bd71b448f16749909b08a0ff16f58b079e2261c2e1000f2bbb2a4f0a45", size = 210009, upload-time = "2026-02-12T14:52:33.634Z" }, + { url = "https://files.pythonhosted.org/packages/49/d8/fc1a92a77c3020ee08ce2dc48aed4b42ab7c30fb43ce488d388673b0f164/librt-0.8.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95a719a049f0eefaf1952673223cf00d442952273cbd20cf2ed7ec423a0ef58d", size = 224461, upload-time = "2026-02-12T14:52:34.868Z" }, + { url = "https://files.pythonhosted.org/packages/7f/98/eb923e8b028cece924c246104aa800cf72e02d023a8ad4ca87135b05a2fe/librt-0.8.0-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bd32add59b58fba3439d48d6f36ac695830388e3da3e92e4fc26d2d02670d19c", size = 217538, upload-time = "2026-02-12T14:52:36.078Z" }, + { url = "https://files.pythonhosted.org/packages/fd/67/24e80ab170674a1d8ee9f9a83081dca4635519dbd0473b8321deecddb5be/librt-0.8.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4f764b2424cb04524ff7a486b9c391e93f93dc1bd8305b2136d25e582e99aa2f", size = 225110, upload-time = "2026-02-12T14:52:37.301Z" }, + { url = "https://files.pythonhosted.org/packages/d8/c7/6fbdcbd1a6e5243c7989c21d68ab967c153b391351174b4729e359d9977f/librt-0.8.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:f04ca50e847abc486fa8f4107250566441e693779a5374ba211e96e238f298b9", size = 217758, upload-time = "2026-02-12T14:52:38.89Z" }, + { url = "https://files.pythonhosted.org/packages/4b/bd/4d6b36669db086e3d747434430073e14def032dd58ad97959bf7e2d06c67/librt-0.8.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9ab3a3475a55b89b87ffd7e6665838e8458e0b596c22e0177e0f961434ec474a", size = 218384, upload-time = "2026-02-12T14:52:40.637Z" }, + { url = "https://files.pythonhosted.org/packages/50/2d/afe966beb0a8f179b132f3e95c8dd90738a23e9ebdba10f89a3f192f9366/librt-0.8.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3e36a8da17134ffc29373775d88c04832f9ecfab1880470661813e6c7991ef79", size = 241187, upload-time = "2026-02-12T14:52:43.55Z" }, + { url = "https://files.pythonhosted.org/packages/02/d0/6172ea4af2b538462785ab1a68e52d5c99cfb9866a7caf00fdf388299734/librt-0.8.0-cp312-cp312-win32.whl", hash = "sha256:4eb5e06ebcc668677ed6389164f52f13f71737fc8be471101fa8b4ce77baeb0c", size = 54914, upload-time = "2026-02-12T14:52:44.676Z" }, + { url = "https://files.pythonhosted.org/packages/d4/cb/ceb6ed6175612a4337ad49fb01ef594712b934b4bc88ce8a63554832eb44/librt-0.8.0-cp312-cp312-win_amd64.whl", hash = "sha256:0a33335eb59921e77c9acc05d0e654e4e32e45b014a4d61517897c11591094f8", size = 62020, upload-time = "2026-02-12T14:52:45.676Z" }, + { url = "https://files.pythonhosted.org/packages/f1/7e/61701acbc67da74ce06ddc7ba9483e81c70f44236b2d00f6a4bfee1aacbf/librt-0.8.0-cp312-cp312-win_arm64.whl", hash = "sha256:24a01c13a2a9bdad20997a4443ebe6e329df063d1978bbe2ebbf637878a46d1e", size = 52443, upload-time = "2026-02-12T14:52:47.218Z" }, + { url = "https://files.pythonhosted.org/packages/6d/32/3edb0bcb4113a9c8bdcd1750663a54565d255027657a5df9d90f13ee07fa/librt-0.8.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7f820210e21e3a8bf8fde2ae3c3d10106d4de9ead28cbfdf6d0f0f41f5b12fa1", size = 66522, upload-time = "2026-02-12T14:52:48.219Z" }, + { url = "https://files.pythonhosted.org/packages/30/ab/e8c3d05e281f5d405ebdcc5bc8ab36df23e1a4b40ac9da8c3eb9928b72b9/librt-0.8.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4831c44b8919e75ca0dfb52052897c1ef59fdae19d3589893fbd068f1e41afbf", size = 68658, upload-time = "2026-02-12T14:52:50.351Z" }, + { url = "https://files.pythonhosted.org/packages/7c/d3/74a206c47b7748bbc8c43942de3ed67de4c231156e148b4f9250869593df/librt-0.8.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:88c6e75540f1f10f5e0fc5e87b4b6c290f0e90d1db8c6734f670840494764af8", size = 199287, upload-time = "2026-02-12T14:52:51.938Z" }, + { url = "https://files.pythonhosted.org/packages/fa/29/ef98a9131cf12cb95771d24e4c411fda96c89dc78b09c2de4704877ebee4/librt-0.8.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9646178cd794704d722306c2c920c221abbf080fede3ba539d5afdec16c46dad", size = 210293, upload-time = "2026-02-12T14:52:53.128Z" }, + { url = "https://files.pythonhosted.org/packages/5b/3e/89b4968cb08c53d4c2d8b02517081dfe4b9e07a959ec143d333d76899f6c/librt-0.8.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e1af31a710e17891d9adf0dbd9a5fcd94901a3922a96499abdbf7ce658f4e01", size = 224801, upload-time = "2026-02-12T14:52:54.367Z" }, + { url = "https://files.pythonhosted.org/packages/6d/28/f38526d501f9513f8b48d78e6be4a241e15dd4b000056dc8b3f06ee9ce5d/librt-0.8.0-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:507e94f4bec00b2f590fbe55f48cd518a208e2474a3b90a60aa8f29136ddbada", size = 218090, upload-time = "2026-02-12T14:52:55.758Z" }, + { url = "https://files.pythonhosted.org/packages/02/ec/64e29887c5009c24dc9c397116c680caffc50286f62bd99c39e3875a2854/librt-0.8.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f1178e0de0c271231a660fbef9be6acdfa1d596803464706862bef6644cc1cae", size = 225483, upload-time = "2026-02-12T14:52:57.375Z" }, + { url = "https://files.pythonhosted.org/packages/ee/16/7850bdbc9f1a32d3feff2708d90c56fc0490b13f1012e438532781aa598c/librt-0.8.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:71fc517efc14f75c2f74b1f0a5d5eb4a8e06aa135c34d18eaf3522f4a53cd62d", size = 218226, upload-time = "2026-02-12T14:52:58.534Z" }, + { url = "https://files.pythonhosted.org/packages/1c/4a/166bffc992d65ddefa7c47052010a87c059b44a458ebaf8f5eba384b0533/librt-0.8.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:0583aef7e9a720dd40f26a2ad5a1bf2ccbb90059dac2b32ac516df232c701db3", size = 218755, upload-time = "2026-02-12T14:52:59.701Z" }, + { url = "https://files.pythonhosted.org/packages/da/5d/9aeee038bcc72a9cfaaee934463fe9280a73c5440d36bd3175069d2cb97b/librt-0.8.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5d0f76fc73480d42285c609c0ea74d79856c160fa828ff9aceab574ea4ecfd7b", size = 241617, upload-time = "2026-02-12T14:53:00.966Z" }, + { url = "https://files.pythonhosted.org/packages/64/ff/2bec6b0296b9d0402aa6ec8540aa19ebcb875d669c37800cb43d10d9c3a3/librt-0.8.0-cp313-cp313-win32.whl", hash = "sha256:e79dbc8f57de360f0ed987dc7de7be814b4803ef0e8fc6d3ff86e16798c99935", size = 54966, upload-time = "2026-02-12T14:53:02.042Z" }, + { url = "https://files.pythonhosted.org/packages/08/8d/bf44633b0182996b2c7ea69a03a5c529683fa1f6b8e45c03fe874ff40d56/librt-0.8.0-cp313-cp313-win_amd64.whl", hash = "sha256:25b3e667cbfc9000c4740b282df599ebd91dbdcc1aa6785050e4c1d6be5329ab", size = 62000, upload-time = "2026-02-12T14:53:03.822Z" }, + { url = "https://files.pythonhosted.org/packages/5c/fd/c6472b8e0eac0925001f75e366cf5500bcb975357a65ef1f6b5749389d3a/librt-0.8.0-cp313-cp313-win_arm64.whl", hash = "sha256:e9a3a38eb4134ad33122a6d575e6324831f930a771d951a15ce232e0237412c2", size = 52496, upload-time = "2026-02-12T14:53:04.889Z" }, + { url = "https://files.pythonhosted.org/packages/e0/13/79ebfe30cd273d7c0ce37a5f14dc489c5fb8b722a008983db2cfd57270bb/librt-0.8.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:421765e8c6b18e64d21c8ead315708a56fc24f44075059702e421d164575fdda", size = 66078, upload-time = "2026-02-12T14:53:06.085Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8f/d11eca40b62a8d5e759239a80636386ef88adecb10d1a050b38cc0da9f9e/librt-0.8.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:48f84830a8f8ad7918afd743fd7c4eb558728bceab7b0e38fd5a5cf78206a556", size = 68309, upload-time = "2026-02-12T14:53:07.121Z" }, + { url = "https://files.pythonhosted.org/packages/9c/b4/f12ee70a3596db40ff3c88ec9eaa4e323f3b92f77505b4d900746706ec6a/librt-0.8.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:9f09d4884f882baa39a7e36bbf3eae124c4ca2a223efb91e567381d1c55c6b06", size = 196804, upload-time = "2026-02-12T14:53:08.164Z" }, + { url = "https://files.pythonhosted.org/packages/8b/7e/70dbbdc0271fd626abe1671ad117bcd61a9a88cdc6a10ccfbfc703db1873/librt-0.8.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:693697133c3b32aa9b27f040e3691be210e9ac4d905061859a9ed519b1d5a376", size = 206915, upload-time = "2026-02-12T14:53:09.333Z" }, + { url = "https://files.pythonhosted.org/packages/79/13/6b9e05a635d4327608d06b3c1702166e3b3e78315846373446cf90d7b0bf/librt-0.8.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5512aae4648152abaf4d48b59890503fcbe86e85abc12fb9b096fe948bdd816", size = 221200, upload-time = "2026-02-12T14:53:10.68Z" }, + { url = "https://files.pythonhosted.org/packages/35/6c/e19a3ac53e9414de43a73d7507d2d766cd22d8ca763d29a4e072d628db42/librt-0.8.0-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:995d24caa6bbb34bcdd4a41df98ac6d1af637cfa8975cb0790e47d6623e70e3e", size = 214640, upload-time = "2026-02-12T14:53:12.342Z" }, + { url = "https://files.pythonhosted.org/packages/30/f0/23a78464788619e8c70f090cfd099cce4973eed142c4dccb99fc322283fd/librt-0.8.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b9aef96d7593584e31ef6ac1eb9775355b0099fee7651fae3a15bc8657b67b52", size = 221980, upload-time = "2026-02-12T14:53:13.603Z" }, + { url = "https://files.pythonhosted.org/packages/03/32/38e21420c5d7aa8a8bd2c7a7d5252ab174a5a8aaec8b5551968979b747bf/librt-0.8.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:4f6e975377fbc4c9567cb33ea9ab826031b6c7ec0515bfae66a4fb110d40d6da", size = 215146, upload-time = "2026-02-12T14:53:14.8Z" }, + { url = "https://files.pythonhosted.org/packages/bb/00/bd9ecf38b1824c25240b3ad982fb62c80f0a969e6679091ba2b3afb2b510/librt-0.8.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:daae5e955764be8fd70a93e9e5133c75297f8bce1e802e1d3683b98f77e1c5ab", size = 215203, upload-time = "2026-02-12T14:53:16.087Z" }, + { url = "https://files.pythonhosted.org/packages/b9/60/7559bcc5279d37810b98d4a52616febd7b8eef04391714fd6bdf629598b1/librt-0.8.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7bd68cebf3131bb920d5984f75fe302d758db33264e44b45ad139385662d7bc3", size = 237937, upload-time = "2026-02-12T14:53:17.236Z" }, + { url = "https://files.pythonhosted.org/packages/41/cc/be3e7da88f1abbe2642672af1dc00a0bccece11ca60241b1883f3018d8d5/librt-0.8.0-cp314-cp314-win32.whl", hash = "sha256:1e6811cac1dcb27ca4c74e0ca4a5917a8e06db0d8408d30daee3a41724bfde7a", size = 50685, upload-time = "2026-02-12T14:53:18.888Z" }, + { url = "https://files.pythonhosted.org/packages/38/27/e381d0df182a8f61ef1f6025d8b138b3318cc9d18ad4d5f47c3bf7492523/librt-0.8.0-cp314-cp314-win_amd64.whl", hash = "sha256:178707cda89d910c3b28bf5aa5f69d3d4734e0f6ae102f753ad79edef83a83c7", size = 57872, upload-time = "2026-02-12T14:53:19.942Z" }, + { url = "https://files.pythonhosted.org/packages/c5/0c/ca9dfdf00554a44dea7d555001248269a4bab569e1590a91391feb863fa4/librt-0.8.0-cp314-cp314-win_arm64.whl", hash = "sha256:3e8b77b5f54d0937b26512774916041756c9eb3e66f1031971e626eea49d0bf4", size = 48056, upload-time = "2026-02-12T14:53:21.473Z" }, + { url = "https://files.pythonhosted.org/packages/f2/ed/6cc9c4ad24f90c8e782193c7b4a857408fd49540800613d1356c63567d7b/librt-0.8.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:789911e8fa40a2e82f41120c936b1965f3213c67f5a483fc5a41f5839a05dcbb", size = 68307, upload-time = "2026-02-12T14:53:22.498Z" }, + { url = "https://files.pythonhosted.org/packages/84/d8/0e94292c6b3e00b6eeea39dd44d5703d1ec29b6dafce7eea19dc8f1aedbd/librt-0.8.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2b37437e7e4ef5e15a297b36ba9e577f73e29564131d86dd75875705e97402b5", size = 70999, upload-time = "2026-02-12T14:53:23.603Z" }, + { url = "https://files.pythonhosted.org/packages/0e/f4/6be1afcbdeedbdbbf54a7c9d73ad43e1bf36897cebf3978308cd64922e02/librt-0.8.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:671a6152edf3b924d98a5ed5e6982ec9cb30894085482acadce0975f031d4c5c", size = 220782, upload-time = "2026-02-12T14:53:25.133Z" }, + { url = "https://files.pythonhosted.org/packages/f0/8d/f306e8caa93cfaf5c6c9e0d940908d75dc6af4fd856baa5535c922ee02b1/librt-0.8.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8992ca186a1678107b0af3d0c9303d8c7305981b9914989b9788319ed4d89546", size = 235420, upload-time = "2026-02-12T14:53:27.047Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f2/65d86bd462e9c351326564ca805e8457442149f348496e25ccd94583ffa2/librt-0.8.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:001e5330093d887b8b9165823eca6c5c4db183fe4edea4fdc0680bbac5f46944", size = 246452, upload-time = "2026-02-12T14:53:28.341Z" }, + { url = "https://files.pythonhosted.org/packages/03/94/39c88b503b4cb3fcbdeb3caa29672b6b44ebee8dcc8a54d49839ac280f3f/librt-0.8.0-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d920789eca7ef71df7f31fd547ec0d3002e04d77f30ba6881e08a630e7b2c30e", size = 238891, upload-time = "2026-02-12T14:53:29.625Z" }, + { url = "https://files.pythonhosted.org/packages/e3/c6/6c0d68190893d01b71b9569b07a1c811e280c0065a791249921c83dc0290/librt-0.8.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:82fb4602d1b3e303a58bfe6165992b5a78d823ec646445356c332cd5f5bbaa61", size = 250249, upload-time = "2026-02-12T14:53:30.93Z" }, + { url = "https://files.pythonhosted.org/packages/52/7a/f715ed9e039035d0ea637579c3c0155ab3709a7046bc408c0fb05d337121/librt-0.8.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:4d3e38797eb482485b486898f89415a6ab163bc291476bd95712e42cf4383c05", size = 240642, upload-time = "2026-02-12T14:53:32.174Z" }, + { url = "https://files.pythonhosted.org/packages/c2/3c/609000a333debf5992efe087edc6467c1fdbdddca5b610355569bbea9589/librt-0.8.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:a905091a13e0884701226860836d0386b88c72ce5c2fdfba6618e14c72be9f25", size = 239621, upload-time = "2026-02-12T14:53:33.39Z" }, + { url = "https://files.pythonhosted.org/packages/b9/df/87b0673d5c395a8f34f38569c116c93142d4dc7e04af2510620772d6bd4f/librt-0.8.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:375eda7acfce1f15f5ed56cfc960669eefa1ec8732e3e9087c3c4c3f2066759c", size = 262986, upload-time = "2026-02-12T14:53:34.617Z" }, + { url = "https://files.pythonhosted.org/packages/09/7f/6bbbe9dcda649684773aaea78b87fff4d7e59550fbc2877faa83612087a3/librt-0.8.0-cp314-cp314t-win32.whl", hash = "sha256:2ccdd20d9a72c562ffb73098ac411de351b53a6fbb3390903b2d33078ef90447", size = 51328, upload-time = "2026-02-12T14:53:36.15Z" }, + { url = "https://files.pythonhosted.org/packages/bb/f3/e1981ab6fa9b41be0396648b5850267888a752d025313a9e929c4856208e/librt-0.8.0-cp314-cp314t-win_amd64.whl", hash = "sha256:25e82d920d4d62ad741592fcf8d0f3bda0e3fc388a184cb7d2f566c681c5f7b9", size = 58719, upload-time = "2026-02-12T14:53:37.183Z" }, + { url = "https://files.pythonhosted.org/packages/94/d1/433b3c06e78f23486fe4fdd19bc134657eb30997d2054b0dbf52bbf3382e/librt-0.8.0-cp314-cp314t-win_arm64.whl", hash = "sha256:92249938ab744a5890580d3cb2b22042f0dce71cdaa7c1369823df62bedf7cbc", size = 48753, upload-time = "2026-02-12T14:53:38.539Z" }, ] [[package]] @@ -1319,11 +1374,11 @@ wheels = [ [[package]] name = "markdown" -version = "3.10" +version = "3.10.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7d/ab/7dd27d9d863b3376fcf23a5a13cb5d024aed1db46f963f1b5735ae43b3be/markdown-3.10.tar.gz", hash = "sha256:37062d4f2aa4b2b6b32aefb80faa300f82cc790cb949a35b8caede34f2b68c0e", size = 364931, upload-time = "2025-11-03T19:51:15.007Z" } +sdist = { url = "https://files.pythonhosted.org/packages/2b/f4/69fa6ed85ae003c2378ffa8f6d2e3234662abd02c10d216c0ba96081a238/markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950", size = 368805, upload-time = "2026-02-09T14:57:26.942Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/70/81/54e3ce63502cd085a0c556652a4e1b919c45a446bd1e5300e10c44c8c521/markdown-3.10-py3-none-any.whl", hash = "sha256:b5b99d6951e2e4948d939255596523444c0e677c669700b1d17aa4a8a464cb7c", size = 107678, upload-time = "2025-11-03T19:51:13.887Z" }, + { url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180, upload-time = "2026-02-09T14:57:25.787Z" }, ] [[package]] @@ -1478,7 +1533,7 @@ wheels = [ [[package]] name = "mcp" -version = "1.25.0" +version = "1.26.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -1496,9 +1551,9 @@ dependencies = [ { name = "typing-inspection" }, { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d5/2d/649d80a0ecf6a1f82632ca44bec21c0461a9d9fc8934d38cb5b319f2db5e/mcp-1.25.0.tar.gz", hash = "sha256:56310361ebf0364e2d438e5b45f7668cbb124e158bb358333cd06e49e83a6802", size = 605387, upload-time = "2025-12-19T10:19:56.985Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/6d/62e76bbb8144d6ed86e202b5edd8a4cb631e7c8130f3f4893c3f90262b10/mcp-1.26.0.tar.gz", hash = "sha256:db6e2ef491eecc1a0d93711a76f28dec2e05999f93afd48795da1c1137142c66", size = 608005, upload-time = "2026-01-24T19:40:32.468Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/fc/6dc7659c2ae5ddf280477011f4213a74f806862856b796ef08f028e664bf/mcp-1.25.0-py3-none-any.whl", hash = "sha256:b37c38144a666add0862614cc79ec276e97d72aa8ca26d622818d4e278b9721a", size = 233076, upload-time = "2025-12-19T10:19:55.416Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d9/eaa1f80170d2b7c5ba23f3b59f766f3a0bb41155fbc32a69adfa1adaaef9/mcp-1.26.0-py3-none-any.whl", hash = "sha256:904a21c33c25aa98ddbeb47273033c435e595bbacfdb177f4bd87f6dceebe1ca", size = 233615, upload-time = "2026-01-24T19:40:30.652Z" }, ] [[package]] @@ -1545,16 +1600,16 @@ wheels = [ [[package]] name = "mkdocs-autorefs" -version = "1.4.3" +version = "1.4.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown" }, { name = "markupsafe" }, { name = "mkdocs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/51/fa/9124cd63d822e2bcbea1450ae68cdc3faf3655c69b455f3a7ed36ce6c628/mkdocs_autorefs-1.4.3.tar.gz", hash = "sha256:beee715b254455c4aa93b6ef3c67579c399ca092259cc41b7d9342573ff1fc75", size = 55425, upload-time = "2025-08-26T14:23:17.223Z" } +sdist = { url = "https://files.pythonhosted.org/packages/52/c0/f641843de3f612a6b48253f39244165acff36657a91cc903633d456ae1ac/mkdocs_autorefs-1.4.4.tar.gz", hash = "sha256:d54a284f27a7346b9c38f1f852177940c222da508e66edc816a0fa55fc6da197", size = 56588, upload-time = "2026-02-10T15:23:55.105Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/9f/4d/7123b6fa2278000688ebd338e2a06d16870aaf9eceae6ba047ea05f92df1/mkdocs_autorefs-1.4.3-py3-none-any.whl", hash = "sha256:469d85eb3114801d08e9cc55d102b3ba65917a869b893403b8987b601cf55dc9", size = 25034, upload-time = "2025-08-26T14:23:15.906Z" }, + { url = "https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl", hash = "sha256:834ef5408d827071ad1bc69e0f39704fa34c7fc05bc8e1c72b227dfdc5c76089", size = 25530, upload-time = "2026-02-10T15:23:53.817Z" }, ] [[package]] @@ -1573,7 +1628,7 @@ wheels = [ [[package]] name = "mkdocs-material" -version = "9.7.0" +version = "9.7.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "babel" }, @@ -1588,9 +1643,9 @@ dependencies = [ { name = "pymdown-extensions" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9c/3b/111b84cd6ff28d9e955b5f799ef217a17bc1684ac346af333e6100e413cb/mkdocs_material-9.7.0.tar.gz", hash = "sha256:602b359844e906ee402b7ed9640340cf8a474420d02d8891451733b6b02314ec", size = 4094546, upload-time = "2025-11-11T08:49:09.73Z" } +sdist = { url = "https://files.pythonhosted.org/packages/27/e2/2ffc356cd72f1473d07c7719d82a8f2cbd261666828614ecb95b12169f41/mkdocs_material-9.7.1.tar.gz", hash = "sha256:89601b8f2c3e6c6ee0a918cc3566cb201d40bf37c3cd3c2067e26fadb8cce2b8", size = 4094392, upload-time = "2025-12-18T09:49:00.308Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/87/eefe8d5e764f4cf50ed91b943f8e8f96b5efd65489d8303b7a36e2e79834/mkdocs_material-9.7.0-py3-none-any.whl", hash = "sha256:da2866ea53601125ff5baa8aa06404c6e07af3c5ce3d5de95e3b52b80b442887", size = 9283770, upload-time = "2025-11-11T08:49:06.26Z" }, + { url = "https://files.pythonhosted.org/packages/3e/32/ed071cb721aca8c227718cffcf7bd539620e9799bbf2619e90c757bfd030/mkdocs_material-9.7.1-py3-none-any.whl", hash = "sha256:3f6100937d7d731f87f1e3e3b021c97f7239666b9ba1151ab476cabb96c60d5c", size = 9297166, upload-time = "2025-12-18T09:48:56.664Z" }, ] [[package]] @@ -1604,7 +1659,7 @@ wheels = [ [[package]] name = "mkdocstrings" -version = "1.0.0" +version = "1.0.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jinja2" }, @@ -1614,23 +1669,23 @@ dependencies = [ { name = "mkdocs-autorefs" }, { name = "pymdown-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e5/13/10bbf9d56565fd91b91e6f5a8cd9b9d8a2b101c4e8ad6eeafa35a706301d/mkdocstrings-1.0.0.tar.gz", hash = "sha256:351a006dbb27aefce241ade110d3cd040c1145b7a3eb5fd5ac23f03ed67f401a", size = 101086, upload-time = "2025-11-27T15:39:40.534Z" } +sdist = { url = "https://files.pythonhosted.org/packages/46/62/0dfc5719514115bf1781f44b1d7f2a0923fcc01e9c5d7990e48a05c9ae5d/mkdocstrings-1.0.3.tar.gz", hash = "sha256:ab670f55040722b49bb45865b2e93b824450fb4aef638b00d7acb493a9020434", size = 100946, upload-time = "2026-02-07T14:31:40.973Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/fc/80aa31b79133634721cf7855d37b76ea49773599214896f2ff10be03de2a/mkdocstrings-1.0.0-py3-none-any.whl", hash = "sha256:4c50eb960bff6e05dfc631f6bc00dfabffbcb29c5ff25f676d64daae05ed82fa", size = 35135, upload-time = "2025-11-27T15:39:39.301Z" }, + { url = "https://files.pythonhosted.org/packages/04/41/1cf02e3df279d2dd846a1bf235a928254eba9006dd22b4a14caa71aed0f7/mkdocstrings-1.0.3-py3-none-any.whl", hash = "sha256:0d66d18430c2201dc7fe85134277382baaa15e6b30979f3f3bdbabd6dbdb6046", size = 35523, upload-time = "2026-02-07T14:31:39.27Z" }, ] [[package]] name = "mkdocstrings-python" -version = "2.0.1" +version = "2.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "griffe" }, { name = "mkdocs-autorefs" }, { name = "mkdocstrings" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/24/75/d30af27a2906f00eb90143470272376d728521997800f5dce5b340ba35bc/mkdocstrings_python-2.0.1.tar.gz", hash = "sha256:843a562221e6a471fefdd4b45cc6c22d2607ccbad632879234fa9692e9cf7732", size = 199345, upload-time = "2025-12-03T14:26:11.755Z" } +sdist = { url = "https://files.pythonhosted.org/packages/25/84/78243847ad9d5c21d30a2842720425b17e880d99dfe824dee11d6b2149b4/mkdocstrings_python-2.0.2.tar.gz", hash = "sha256:4a32ccfc4b8d29639864698e81cfeb04137bce76bb9f3c251040f55d4b6e1ad8", size = 199124, upload-time = "2026-02-09T15:12:01.543Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/81/06/c5f8deba7d2cbdfa7967a716ae801aa9ca5f734b8f54fd473ef77a088dbe/mkdocstrings_python-2.0.1-py3-none-any.whl", hash = "sha256:66ecff45c5f8b71bf174e11d49afc845c2dfc7fc0ab17a86b6b337e0f24d8d90", size = 105055, upload-time = "2025-12-03T14:26:10.184Z" }, + { url = "https://files.pythonhosted.org/packages/f3/31/7ee938abbde2322e553a2cb5f604cdd1e4728e08bba39c7ee6fae9af840b/mkdocstrings_python-2.0.2-py3-none-any.whl", hash = "sha256:31241c0f43d85a69306d704d5725786015510ea3f3c4bdfdb5a5731d83cdc2b0", size = 104900, upload-time = "2026-02-09T15:12:00.166Z" }, ] [[package]] @@ -1644,41 +1699,41 @@ wheels = [ [[package]] name = "mypy" -version = "1.19.0" +version = "1.19.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "librt" }, + { name = "librt", marker = "platform_python_implementation != 'PyPy'" }, { name = "mypy-extensions" }, { name = "pathspec" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f9/b5/b58cdc25fadd424552804bf410855d52324183112aa004f0732c5f6324cf/mypy-1.19.0.tar.gz", hash = "sha256:f6b874ca77f733222641e5c46e4711648c4037ea13646fd0cdc814c2eaec2528", size = 3579025, upload-time = "2025-11-28T15:49:01.26Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0f/d2/010fb171ae5ac4a01cc34fbacd7544531e5ace95c35ca166dd8fd1b901d0/mypy-1.19.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a31e4c28e8ddb042c84c5e977e28a21195d086aaffaf08b016b78e19c9ef8106", size = 13010563, upload-time = "2025-11-28T15:48:23.975Z" }, - { url = "https://files.pythonhosted.org/packages/41/6b/63f095c9f1ce584fdeb595d663d49e0980c735a1d2004720ccec252c5d47/mypy-1.19.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:34ec1ac66d31644f194b7c163d7f8b8434f1b49719d403a5d26c87fff7e913f7", size = 12077037, upload-time = "2025-11-28T15:47:51.582Z" }, - { url = "https://files.pythonhosted.org/packages/d7/83/6cb93d289038d809023ec20eb0b48bbb1d80af40511fa077da78af6ff7c7/mypy-1.19.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cb64b0ba5980466a0f3f9990d1c582bcab8db12e29815ecb57f1408d99b4bff7", size = 12680255, upload-time = "2025-11-28T15:46:57.628Z" }, - { url = "https://files.pythonhosted.org/packages/99/db/d217815705987d2cbace2edd9100926196d6f85bcb9b5af05058d6e3c8ad/mypy-1.19.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:120cffe120cca5c23c03c77f84abc0c14c5d2e03736f6c312480020082f1994b", size = 13421472, upload-time = "2025-11-28T15:47:59.655Z" }, - { url = "https://files.pythonhosted.org/packages/4e/51/d2beaca7c497944b07594f3f8aad8d2f0e8fc53677059848ae5d6f4d193e/mypy-1.19.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7a500ab5c444268a70565e374fc803972bfd1f09545b13418a5174e29883dab7", size = 13651823, upload-time = "2025-11-28T15:45:29.318Z" }, - { url = "https://files.pythonhosted.org/packages/aa/d1/7883dcf7644db3b69490f37b51029e0870aac4a7ad34d09ceae709a3df44/mypy-1.19.0-cp311-cp311-win_amd64.whl", hash = "sha256:c14a98bc63fd867530e8ec82f217dae29d0550c86e70debc9667fff1ec83284e", size = 10049077, upload-time = "2025-11-28T15:45:39.818Z" }, - { url = "https://files.pythonhosted.org/packages/11/7e/1afa8fb188b876abeaa14460dc4983f909aaacaa4bf5718c00b2c7e0b3d5/mypy-1.19.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0fb3115cb8fa7c5f887c8a8d81ccdcb94cff334684980d847e5a62e926910e1d", size = 13207728, upload-time = "2025-11-28T15:46:26.463Z" }, - { url = "https://files.pythonhosted.org/packages/b2/13/f103d04962bcbefb1644f5ccb235998b32c337d6c13145ea390b9da47f3e/mypy-1.19.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3e19e3b897562276bb331074d64c076dbdd3e79213f36eed4e592272dabd760", size = 12202945, upload-time = "2025-11-28T15:48:49.143Z" }, - { url = "https://files.pythonhosted.org/packages/e4/93/a86a5608f74a22284a8ccea8592f6e270b61f95b8588951110ad797c2ddd/mypy-1.19.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b9d491295825182fba01b6ffe2c6fe4e5a49dbf4e2bb4d1217b6ced3b4797bc6", size = 12718673, upload-time = "2025-11-28T15:47:37.193Z" }, - { url = "https://files.pythonhosted.org/packages/3d/58/cf08fff9ced0423b858f2a7495001fda28dc058136818ee9dffc31534ea9/mypy-1.19.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6016c52ab209919b46169651b362068f632efcd5eb8ef9d1735f6f86da7853b2", size = 13608336, upload-time = "2025-11-28T15:48:32.625Z" }, - { url = "https://files.pythonhosted.org/packages/64/ed/9c509105c5a6d4b73bb08733102a3ea62c25bc02c51bca85e3134bf912d3/mypy-1.19.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f188dcf16483b3e59f9278c4ed939ec0254aa8a60e8fc100648d9ab5ee95a431", size = 13833174, upload-time = "2025-11-28T15:45:48.091Z" }, - { url = "https://files.pythonhosted.org/packages/cd/71/01939b66e35c6f8cb3e6fdf0b657f0fd24de2f8ba5e523625c8e72328208/mypy-1.19.0-cp312-cp312-win_amd64.whl", hash = "sha256:0e3c3d1e1d62e678c339e7ade72746a9e0325de42cd2cccc51616c7b2ed1a018", size = 10112208, upload-time = "2025-11-28T15:46:41.702Z" }, - { url = "https://files.pythonhosted.org/packages/cb/0d/a1357e6bb49e37ce26fcf7e3cc55679ce9f4ebee0cd8b6ee3a0e301a9210/mypy-1.19.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:7686ed65dbabd24d20066f3115018d2dce030d8fa9db01aa9f0a59b6813e9f9e", size = 13191993, upload-time = "2025-11-28T15:47:22.336Z" }, - { url = "https://files.pythonhosted.org/packages/5d/75/8e5d492a879ec4490e6ba664b5154e48c46c85b5ac9785792a5ec6a4d58f/mypy-1.19.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:fd4a985b2e32f23bead72e2fb4bbe5d6aceee176be471243bd831d5b2644672d", size = 12174411, upload-time = "2025-11-28T15:44:55.492Z" }, - { url = "https://files.pythonhosted.org/packages/71/31/ad5dcee9bfe226e8eaba777e9d9d251c292650130f0450a280aec3485370/mypy-1.19.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fc51a5b864f73a3a182584b1ac75c404396a17eced54341629d8bdcb644a5bba", size = 12727751, upload-time = "2025-11-28T15:44:14.169Z" }, - { url = "https://files.pythonhosted.org/packages/77/06/b6b8994ce07405f6039701f4b66e9d23f499d0b41c6dd46ec28f96d57ec3/mypy-1.19.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:37af5166f9475872034b56c5efdcf65ee25394e9e1d172907b84577120714364", size = 13593323, upload-time = "2025-11-28T15:46:34.699Z" }, - { url = "https://files.pythonhosted.org/packages/68/b1/126e274484cccdf099a8e328d4fda1c7bdb98a5e888fa6010b00e1bbf330/mypy-1.19.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:510c014b722308c9bd377993bcbf9a07d7e0692e5fa8fc70e639c1eb19fc6bee", size = 13818032, upload-time = "2025-11-28T15:46:18.286Z" }, - { url = "https://files.pythonhosted.org/packages/f8/56/53a8f70f562dfc466c766469133a8a4909f6c0012d83993143f2a9d48d2d/mypy-1.19.0-cp313-cp313-win_amd64.whl", hash = "sha256:cabbee74f29aa9cd3b444ec2f1e4fa5a9d0d746ce7567a6a609e224429781f53", size = 10120644, upload-time = "2025-11-28T15:47:43.99Z" }, - { url = "https://files.pythonhosted.org/packages/b0/f4/7751f32f56916f7f8c229fe902cbdba3e4dd3f3ea9e8b872be97e7fc546d/mypy-1.19.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f2e36bed3c6d9b5f35d28b63ca4b727cb0228e480826ffc8953d1892ddc8999d", size = 13185236, upload-time = "2025-11-28T15:45:20.696Z" }, - { url = "https://files.pythonhosted.org/packages/35/31/871a9531f09e78e8d145032355890384f8a5b38c95a2c7732d226b93242e/mypy-1.19.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a18d8abdda14035c5718acb748faec09571432811af129bf0d9e7b2d6699bf18", size = 12213902, upload-time = "2025-11-28T15:46:10.117Z" }, - { url = "https://files.pythonhosted.org/packages/58/b8/af221910dd40eeefa2077a59107e611550167b9994693fc5926a0b0f87c0/mypy-1.19.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f75e60aca3723a23511948539b0d7ed514dda194bc3755eae0bfc7a6b4887aa7", size = 12738600, upload-time = "2025-11-28T15:44:22.521Z" }, - { url = "https://files.pythonhosted.org/packages/11/9f/c39e89a3e319c1d9c734dedec1183b2cc3aefbab066ec611619002abb932/mypy-1.19.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f44f2ae3c58421ee05fe609160343c25f70e3967f6e32792b5a78006a9d850f", size = 13592639, upload-time = "2025-11-28T15:48:08.55Z" }, - { url = "https://files.pythonhosted.org/packages/97/6d/ffaf5f01f5e284d9033de1267e6c1b8f3783f2cf784465378a86122e884b/mypy-1.19.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:63ea6a00e4bd6822adbfc75b02ab3653a17c02c4347f5bb0cf1d5b9df3a05835", size = 13799132, upload-time = "2025-11-28T15:47:06.032Z" }, - { url = "https://files.pythonhosted.org/packages/fe/b0/c33921e73aaa0106224e5a34822411bea38046188eb781637f5a5b07e269/mypy-1.19.0-cp314-cp314-win_amd64.whl", hash = "sha256:3ad925b14a0bb99821ff6f734553294aa6a3440a8cb082fe1f5b84dfb662afb1", size = 10269832, upload-time = "2025-11-28T15:47:29.392Z" }, - { url = "https://files.pythonhosted.org/packages/09/0e/fe228ed5aeab470c6f4eb82481837fadb642a5aa95cc8215fd2214822c10/mypy-1.19.0-py3-none-any.whl", hash = "sha256:0c01c99d626380752e527d5ce8e69ffbba2046eb8a060db0329690849cf9b6f9", size = 2469714, upload-time = "2025-11-28T15:45:33.22Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/f5/db/4efed9504bc01309ab9c2da7e352cc223569f05478012b5d9ece38fd44d2/mypy-1.19.1.tar.gz", hash = "sha256:19d88bb05303fe63f71dd2c6270daca27cb9401c4ca8255fe50d1d920e0eb9ba", size = 3582404, upload-time = "2025-12-15T05:03:48.42Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ef/47/6b3ebabd5474d9cdc170d1342fbf9dddc1b0ec13ec90bf9004ee6f391c31/mypy-1.19.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d8dfc6ab58ca7dda47d9237349157500468e404b17213d44fc1cb77bce532288", size = 13028539, upload-time = "2025-12-15T05:03:44.129Z" }, + { url = "https://files.pythonhosted.org/packages/5c/a6/ac7c7a88a3c9c54334f53a941b765e6ec6c4ebd65d3fe8cdcfbe0d0fd7db/mypy-1.19.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e3f276d8493c3c97930e354b2595a44a21348b320d859fb4a2b9f66da9ed27ab", size = 12083163, upload-time = "2025-12-15T05:03:37.679Z" }, + { url = "https://files.pythonhosted.org/packages/67/af/3afa9cf880aa4a2c803798ac24f1d11ef72a0c8079689fac5cfd815e2830/mypy-1.19.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2abb24cf3f17864770d18d673c85235ba52456b36a06b6afc1e07c1fdcd3d0e6", size = 12687629, upload-time = "2025-12-15T05:02:31.526Z" }, + { url = "https://files.pythonhosted.org/packages/2d/46/20f8a7114a56484ab268b0ab372461cb3a8f7deed31ea96b83a4e4cfcfca/mypy-1.19.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a009ffa5a621762d0c926a078c2d639104becab69e79538a494bcccb62cc0331", size = 13436933, upload-time = "2025-12-15T05:03:15.606Z" }, + { url = "https://files.pythonhosted.org/packages/5b/f8/33b291ea85050a21f15da910002460f1f445f8007adb29230f0adea279cb/mypy-1.19.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f7cee03c9a2e2ee26ec07479f38ea9c884e301d42c6d43a19d20fb014e3ba925", size = 13661754, upload-time = "2025-12-15T05:02:26.731Z" }, + { url = "https://files.pythonhosted.org/packages/fd/a3/47cbd4e85bec4335a9cd80cf67dbc02be21b5d4c9c23ad6b95d6c5196bac/mypy-1.19.1-cp311-cp311-win_amd64.whl", hash = "sha256:4b84a7a18f41e167f7995200a1d07a4a6810e89d29859df936f1c3923d263042", size = 10055772, upload-time = "2025-12-15T05:03:26.179Z" }, + { url = "https://files.pythonhosted.org/packages/06/8a/19bfae96f6615aa8a0604915512e0289b1fad33d5909bf7244f02935d33a/mypy-1.19.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a8174a03289288c1f6c46d55cef02379b478bfbc8e358e02047487cad44c6ca1", size = 13206053, upload-time = "2025-12-15T05:03:46.622Z" }, + { url = "https://files.pythonhosted.org/packages/a5/34/3e63879ab041602154ba2a9f99817bb0c85c4df19a23a1443c8986e4d565/mypy-1.19.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ffcebe56eb09ff0c0885e750036a095e23793ba6c2e894e7e63f6d89ad51f22e", size = 12219134, upload-time = "2025-12-15T05:03:24.367Z" }, + { url = "https://files.pythonhosted.org/packages/89/cc/2db6f0e95366b630364e09845672dbee0cbf0bbe753a204b29a944967cd9/mypy-1.19.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b64d987153888790bcdb03a6473d321820597ab8dd9243b27a92153c4fa50fd2", size = 12731616, upload-time = "2025-12-15T05:02:44.725Z" }, + { url = "https://files.pythonhosted.org/packages/00/be/dd56c1fd4807bc1eba1cf18b2a850d0de7bacb55e158755eb79f77c41f8e/mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8", size = 13620847, upload-time = "2025-12-15T05:03:39.633Z" }, + { url = "https://files.pythonhosted.org/packages/6d/42/332951aae42b79329f743bf1da088cd75d8d4d9acc18fbcbd84f26c1af4e/mypy-1.19.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:34c81968774648ab5ac09c29a375fdede03ba253f8f8287847bd480782f73a6a", size = 13834976, upload-time = "2025-12-15T05:03:08.786Z" }, + { url = "https://files.pythonhosted.org/packages/6f/63/e7493e5f90e1e085c562bb06e2eb32cae27c5057b9653348d38b47daaecc/mypy-1.19.1-cp312-cp312-win_amd64.whl", hash = "sha256:b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13", size = 10118104, upload-time = "2025-12-15T05:03:10.834Z" }, + { url = "https://files.pythonhosted.org/packages/de/9f/a6abae693f7a0c697dbb435aac52e958dc8da44e92e08ba88d2e42326176/mypy-1.19.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e3157c7594ff2ef1634ee058aafc56a82db665c9438fd41b390f3bde1ab12250", size = 13201927, upload-time = "2025-12-15T05:02:29.138Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a4/45c35ccf6e1c65afc23a069f50e2c66f46bd3798cbe0d680c12d12935caa/mypy-1.19.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdb12f69bcc02700c2b47e070238f42cb87f18c0bc1fc4cdb4fb2bc5fd7a3b8b", size = 12206730, upload-time = "2025-12-15T05:03:01.325Z" }, + { url = "https://files.pythonhosted.org/packages/05/bb/cdcf89678e26b187650512620eec8368fded4cfd99cfcb431e4cdfd19dec/mypy-1.19.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f859fb09d9583a985be9a493d5cfc5515b56b08f7447759a0c5deaf68d80506e", size = 12724581, upload-time = "2025-12-15T05:03:20.087Z" }, + { url = "https://files.pythonhosted.org/packages/d1/32/dd260d52babf67bad8e6770f8e1102021877ce0edea106e72df5626bb0ec/mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef", size = 13616252, upload-time = "2025-12-15T05:02:49.036Z" }, + { url = "https://files.pythonhosted.org/packages/71/d0/5e60a9d2e3bd48432ae2b454b7ef2b62a960ab51292b1eda2a95edd78198/mypy-1.19.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:da4869fc5e7f62a88f3fe0b5c919d1d9f7ea3cef92d3689de2823fd27e40aa75", size = 13840848, upload-time = "2025-12-15T05:02:55.95Z" }, + { url = "https://files.pythonhosted.org/packages/98/76/d32051fa65ecf6cc8c6610956473abdc9b4c43301107476ac03559507843/mypy-1.19.1-cp313-cp313-win_amd64.whl", hash = "sha256:016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd", size = 10135510, upload-time = "2025-12-15T05:02:58.438Z" }, + { url = "https://files.pythonhosted.org/packages/de/eb/b83e75f4c820c4247a58580ef86fcd35165028f191e7e1ba57128c52782d/mypy-1.19.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:06e6170bd5836770e8104c8fdd58e5e725cfeb309f0a6c681a811f557e97eac1", size = 13199744, upload-time = "2025-12-15T05:03:30.823Z" }, + { url = "https://files.pythonhosted.org/packages/94/28/52785ab7bfa165f87fcbb61547a93f98bb20e7f82f90f165a1f69bce7b3d/mypy-1.19.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:804bd67b8054a85447c8954215a906d6eff9cabeabe493fb6334b24f4bfff718", size = 12215815, upload-time = "2025-12-15T05:02:42.323Z" }, + { url = "https://files.pythonhosted.org/packages/0a/c6/bdd60774a0dbfb05122e3e925f2e9e846c009e479dcec4821dad881f5b52/mypy-1.19.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:21761006a7f497cb0d4de3d8ef4ca70532256688b0523eee02baf9eec895e27b", size = 12740047, upload-time = "2025-12-15T05:03:33.168Z" }, + { url = "https://files.pythonhosted.org/packages/32/2a/66ba933fe6c76bd40d1fe916a83f04fed253152f451a877520b3c4a5e41e/mypy-1.19.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:28902ee51f12e0f19e1e16fbe2f8f06b6637f482c459dd393efddd0ec7f82045", size = 13601998, upload-time = "2025-12-15T05:03:13.056Z" }, + { url = "https://files.pythonhosted.org/packages/e3/da/5055c63e377c5c2418760411fd6a63ee2b96cf95397259038756c042574f/mypy-1.19.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:481daf36a4c443332e2ae9c137dfee878fcea781a2e3f895d54bd3002a900957", size = 13807476, upload-time = "2025-12-15T05:03:17.977Z" }, + { url = "https://files.pythonhosted.org/packages/cd/09/4ebd873390a063176f06b0dbf1f7783dd87bd120eae7727fa4ae4179b685/mypy-1.19.1-cp314-cp314-win_amd64.whl", hash = "sha256:8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f", size = 10281872, upload-time = "2025-12-15T05:03:05.549Z" }, + { url = "https://files.pythonhosted.org/packages/8d/f4/4ce9a05ce5ded1de3ec1c1d96cf9f9504a04e54ce0ed55cfa38619a32b8d/mypy-1.19.1-py3-none-any.whl", hash = "sha256:f1235f5ea01b7db5468d53ece6aaddf1ad0b88d9e7462b86ef96fe04995d7247", size = 2471239, upload-time = "2025-12-15T05:03:07.248Z" }, ] [[package]] @@ -1692,97 +1747,95 @@ wheels = [ [[package]] name = "nodeenv" -version = "1.9.1" +version = "1.10.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/43/16/fc88b08840de0e0a72a2f9d8c6bae36be573e475a6326ae854bcc549fc45/nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f", size = 47437, upload-time = "2024-06-04T18:44:11.171Z" } +sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/1d/1b658dbd2b9fa9c4c9f32accbfc0205d532c8c6194dc0f2a4c0428e7128a/nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9", size = 22314, upload-time = "2024-06-04T18:44:08.352Z" }, + { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, ] [[package]] name = "numpy" -version = "2.3.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/76/65/21b3bc86aac7b8f2862db1e808f1ea22b028e30a225a34a5ede9bf8678f2/numpy-2.3.5.tar.gz", hash = "sha256:784db1dcdab56bf0517743e746dfb0f885fc68d948aba86eeec2cba234bdf1c0", size = 20584950, upload-time = "2025-11-16T22:52:42.067Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/43/77/84dd1d2e34d7e2792a236ba180b5e8fcc1e3e414e761ce0253f63d7f572e/numpy-2.3.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:de5672f4a7b200c15a4127042170a694d4df43c992948f5e1af57f0174beed10", size = 17034641, upload-time = "2025-11-16T22:49:19.336Z" }, - { url = "https://files.pythonhosted.org/packages/2a/ea/25e26fa5837106cde46ae7d0b667e20f69cbbc0efd64cba8221411ab26ae/numpy-2.3.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:acfd89508504a19ed06ef963ad544ec6664518c863436306153e13e94605c218", size = 12528324, upload-time = "2025-11-16T22:49:22.582Z" }, - { url = "https://files.pythonhosted.org/packages/4d/1a/e85f0eea4cf03d6a0228f5c0256b53f2df4bc794706e7df019fc622e47f1/numpy-2.3.5-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:ffe22d2b05504f786c867c8395de703937f934272eb67586817b46188b4ded6d", size = 5356872, upload-time = "2025-11-16T22:49:25.408Z" }, - { url = "https://files.pythonhosted.org/packages/5c/bb/35ef04afd567f4c989c2060cde39211e4ac5357155c1833bcd1166055c61/numpy-2.3.5-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:872a5cf366aec6bb1147336480fef14c9164b154aeb6542327de4970282cd2f5", size = 6893148, upload-time = "2025-11-16T22:49:27.549Z" }, - { url = "https://files.pythonhosted.org/packages/f2/2b/05bbeb06e2dff5eab512dfc678b1cc5ee94d8ac5956a0885c64b6b26252b/numpy-2.3.5-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3095bdb8dd297e5920b010e96134ed91d852d81d490e787beca7e35ae1d89cf7", size = 14557282, upload-time = "2025-11-16T22:49:30.964Z" }, - { url = "https://files.pythonhosted.org/packages/65/fb/2b23769462b34398d9326081fad5655198fcf18966fcb1f1e49db44fbf31/numpy-2.3.5-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8cba086a43d54ca804ce711b2a940b16e452807acebe7852ff327f1ecd49b0d4", size = 16897903, upload-time = "2025-11-16T22:49:34.191Z" }, - { url = "https://files.pythonhosted.org/packages/ac/14/085f4cf05fc3f1e8aa95e85404e984ffca9b2275a5dc2b1aae18a67538b8/numpy-2.3.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6cf9b429b21df6b99f4dee7a1218b8b7ffbbe7df8764dc0bd60ce8a0708fed1e", size = 16341672, upload-time = "2025-11-16T22:49:37.2Z" }, - { url = "https://files.pythonhosted.org/packages/6f/3b/1f73994904142b2aa290449b3bb99772477b5fd94d787093e4f24f5af763/numpy-2.3.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:396084a36abdb603546b119d96528c2f6263921c50df3c8fd7cb28873a237748", size = 18838896, upload-time = "2025-11-16T22:49:39.727Z" }, - { url = "https://files.pythonhosted.org/packages/cd/b9/cf6649b2124f288309ffc353070792caf42ad69047dcc60da85ee85fea58/numpy-2.3.5-cp311-cp311-win32.whl", hash = "sha256:b0c7088a73aef3d687c4deef8452a3ac7c1be4e29ed8bf3b366c8111128ac60c", size = 6563608, upload-time = "2025-11-16T22:49:42.079Z" }, - { url = "https://files.pythonhosted.org/packages/aa/44/9fe81ae1dcc29c531843852e2874080dc441338574ccc4306b39e2ff6e59/numpy-2.3.5-cp311-cp311-win_amd64.whl", hash = "sha256:a414504bef8945eae5f2d7cb7be2d4af77c5d1cb5e20b296c2c25b61dff2900c", size = 13078442, upload-time = "2025-11-16T22:49:43.99Z" }, - { url = "https://files.pythonhosted.org/packages/6d/a7/f99a41553d2da82a20a2f22e93c94f928e4490bb447c9ff3c4ff230581d3/numpy-2.3.5-cp311-cp311-win_arm64.whl", hash = "sha256:0cd00b7b36e35398fa2d16af7b907b65304ef8bb4817a550e06e5012929830fa", size = 10458555, upload-time = "2025-11-16T22:49:47.092Z" }, - { url = "https://files.pythonhosted.org/packages/44/37/e669fe6cbb2b96c62f6bbedc6a81c0f3b7362f6a59230b23caa673a85721/numpy-2.3.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:74ae7b798248fe62021dbf3c914245ad45d1a6b0cb4a29ecb4b31d0bfbc4cc3e", size = 16733873, upload-time = "2025-11-16T22:49:49.84Z" }, - { url = "https://files.pythonhosted.org/packages/c5/65/df0db6c097892c9380851ab9e44b52d4f7ba576b833996e0080181c0c439/numpy-2.3.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee3888d9ff7c14604052b2ca5535a30216aa0a58e948cdd3eeb8d3415f638769", size = 12259838, upload-time = "2025-11-16T22:49:52.863Z" }, - { url = "https://files.pythonhosted.org/packages/5b/e1/1ee06e70eb2136797abe847d386e7c0e830b67ad1d43f364dd04fa50d338/numpy-2.3.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:612a95a17655e213502f60cfb9bf9408efdc9eb1d5f50535cc6eb365d11b42b5", size = 5088378, upload-time = "2025-11-16T22:49:55.055Z" }, - { url = "https://files.pythonhosted.org/packages/6d/9c/1ca85fb86708724275103b81ec4cf1ac1d08f465368acfc8da7ab545bdae/numpy-2.3.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3101e5177d114a593d79dd79658650fe28b5a0d8abeb8ce6f437c0e6df5be1a4", size = 6628559, upload-time = "2025-11-16T22:49:57.371Z" }, - { url = "https://files.pythonhosted.org/packages/74/78/fcd41e5a0ce4f3f7b003da85825acddae6d7ecb60cf25194741b036ca7d6/numpy-2.3.5-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b973c57ff8e184109db042c842423ff4f60446239bd585a5131cc47f06f789d", size = 14250702, upload-time = "2025-11-16T22:49:59.632Z" }, - { url = "https://files.pythonhosted.org/packages/b6/23/2a1b231b8ff672b4c450dac27164a8b2ca7d9b7144f9c02d2396518352eb/numpy-2.3.5-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d8163f43acde9a73c2a33605353a4f1bc4798745a8b1d73183b28e5b435ae28", size = 16606086, upload-time = "2025-11-16T22:50:02.127Z" }, - { url = "https://files.pythonhosted.org/packages/a0/c5/5ad26fbfbe2012e190cc7d5003e4d874b88bb18861d0829edc140a713021/numpy-2.3.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:51c1e14eb1e154ebd80e860722f9e6ed6ec89714ad2db2d3aa33c31d7c12179b", size = 16025985, upload-time = "2025-11-16T22:50:04.536Z" }, - { url = "https://files.pythonhosted.org/packages/d2/fa/dd48e225c46c819288148d9d060b047fd2a6fb1eb37eae25112ee4cb4453/numpy-2.3.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b46b4ec24f7293f23adcd2d146960559aaf8020213de8ad1909dba6c013bf89c", size = 18542976, upload-time = "2025-11-16T22:50:07.557Z" }, - { url = "https://files.pythonhosted.org/packages/05/79/ccbd23a75862d95af03d28b5c6901a1b7da4803181513d52f3b86ed9446e/numpy-2.3.5-cp312-cp312-win32.whl", hash = "sha256:3997b5b3c9a771e157f9aae01dd579ee35ad7109be18db0e85dbdbe1de06e952", size = 6285274, upload-time = "2025-11-16T22:50:10.746Z" }, - { url = "https://files.pythonhosted.org/packages/2d/57/8aeaf160312f7f489dea47ab61e430b5cb051f59a98ae68b7133ce8fa06a/numpy-2.3.5-cp312-cp312-win_amd64.whl", hash = "sha256:86945f2ee6d10cdfd67bcb4069c1662dd711f7e2a4343db5cecec06b87cf31aa", size = 12782922, upload-time = "2025-11-16T22:50:12.811Z" }, - { url = "https://files.pythonhosted.org/packages/78/a6/aae5cc2ca78c45e64b9ef22f089141d661516856cf7c8a54ba434576900d/numpy-2.3.5-cp312-cp312-win_arm64.whl", hash = "sha256:f28620fe26bee16243be2b7b874da327312240a7cdc38b769a697578d2100013", size = 10194667, upload-time = "2025-11-16T22:50:16.16Z" }, - { url = "https://files.pythonhosted.org/packages/db/69/9cde09f36da4b5a505341180a3f2e6fadc352fd4d2b7096ce9778db83f1a/numpy-2.3.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d0f23b44f57077c1ede8c5f26b30f706498b4862d3ff0a7298b8411dd2f043ff", size = 16728251, upload-time = "2025-11-16T22:50:19.013Z" }, - { url = "https://files.pythonhosted.org/packages/79/fb/f505c95ceddd7027347b067689db71ca80bd5ecc926f913f1a23e65cf09b/numpy-2.3.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa5bc7c5d59d831d9773d1170acac7893ce3a5e130540605770ade83280e7188", size = 12254652, upload-time = "2025-11-16T22:50:21.487Z" }, - { url = "https://files.pythonhosted.org/packages/78/da/8c7738060ca9c31b30e9301ee0cf6c5ffdbf889d9593285a1cead337f9a5/numpy-2.3.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:ccc933afd4d20aad3c00bcef049cb40049f7f196e0397f1109dba6fed63267b0", size = 5083172, upload-time = "2025-11-16T22:50:24.562Z" }, - { url = "https://files.pythonhosted.org/packages/a4/b4/ee5bb2537fb9430fd2ef30a616c3672b991a4129bb1c7dcc42aa0abbe5d7/numpy-2.3.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:afaffc4393205524af9dfa400fa250143a6c3bc646c08c9f5e25a9f4b4d6a903", size = 6622990, upload-time = "2025-11-16T22:50:26.47Z" }, - { url = "https://files.pythonhosted.org/packages/95/03/dc0723a013c7d7c19de5ef29e932c3081df1c14ba582b8b86b5de9db7f0f/numpy-2.3.5-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c75442b2209b8470d6d5d8b1c25714270686f14c749028d2199c54e29f20b4d", size = 14248902, upload-time = "2025-11-16T22:50:28.861Z" }, - { url = "https://files.pythonhosted.org/packages/f5/10/ca162f45a102738958dcec8023062dad0cbc17d1ab99d68c4e4a6c45fb2b/numpy-2.3.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11e06aa0af8c0f05104d56450d6093ee639e15f24ecf62d417329d06e522e017", size = 16597430, upload-time = "2025-11-16T22:50:31.56Z" }, - { url = "https://files.pythonhosted.org/packages/2a/51/c1e29be863588db58175175f057286900b4b3327a1351e706d5e0f8dd679/numpy-2.3.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ed89927b86296067b4f81f108a2271d8926467a8868e554eaf370fc27fa3ccaf", size = 16024551, upload-time = "2025-11-16T22:50:34.242Z" }, - { url = "https://files.pythonhosted.org/packages/83/68/8236589d4dbb87253d28259d04d9b814ec0ecce7cb1c7fed29729f4c3a78/numpy-2.3.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51c55fe3451421f3a6ef9a9c1439e82101c57a2c9eab9feb196a62b1a10b58ce", size = 18533275, upload-time = "2025-11-16T22:50:37.651Z" }, - { url = "https://files.pythonhosted.org/packages/40/56/2932d75b6f13465239e3b7b7e511be27f1b8161ca2510854f0b6e521c395/numpy-2.3.5-cp313-cp313-win32.whl", hash = "sha256:1978155dd49972084bd6ef388d66ab70f0c323ddee6f693d539376498720fb7e", size = 6277637, upload-time = "2025-11-16T22:50:40.11Z" }, - { url = "https://files.pythonhosted.org/packages/0c/88/e2eaa6cffb115b85ed7c7c87775cb8bcf0816816bc98ca8dbfa2ee33fe6e/numpy-2.3.5-cp313-cp313-win_amd64.whl", hash = "sha256:00dc4e846108a382c5869e77c6ed514394bdeb3403461d25a829711041217d5b", size = 12779090, upload-time = "2025-11-16T22:50:42.503Z" }, - { url = "https://files.pythonhosted.org/packages/8f/88/3f41e13a44ebd4034ee17baa384acac29ba6a4fcc2aca95f6f08ca0447d1/numpy-2.3.5-cp313-cp313-win_arm64.whl", hash = "sha256:0472f11f6ec23a74a906a00b48a4dcf3849209696dff7c189714511268d103ae", size = 10194710, upload-time = "2025-11-16T22:50:44.971Z" }, - { url = "https://files.pythonhosted.org/packages/13/cb/71744144e13389d577f867f745b7df2d8489463654a918eea2eeb166dfc9/numpy-2.3.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:414802f3b97f3c1eef41e530aaba3b3c1620649871d8cb38c6eaff034c2e16bd", size = 16827292, upload-time = "2025-11-16T22:50:47.715Z" }, - { url = "https://files.pythonhosted.org/packages/71/80/ba9dc6f2a4398e7f42b708a7fdc841bb638d353be255655498edbf9a15a8/numpy-2.3.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5ee6609ac3604fa7780e30a03e5e241a7956f8e2fcfe547d51e3afa5247ac47f", size = 12378897, upload-time = "2025-11-16T22:50:51.327Z" }, - { url = "https://files.pythonhosted.org/packages/2e/6d/db2151b9f64264bcceccd51741aa39b50150de9b602d98ecfe7e0c4bff39/numpy-2.3.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:86d835afea1eaa143012a2d7a3f45a3adce2d7adc8b4961f0b362214d800846a", size = 5207391, upload-time = "2025-11-16T22:50:54.542Z" }, - { url = "https://files.pythonhosted.org/packages/80/ae/429bacace5ccad48a14c4ae5332f6aa8ab9f69524193511d60ccdfdc65fa/numpy-2.3.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:30bc11310e8153ca664b14c5f1b73e94bd0503681fcf136a163de856f3a50139", size = 6721275, upload-time = "2025-11-16T22:50:56.794Z" }, - { url = "https://files.pythonhosted.org/packages/74/5b/1919abf32d8722646a38cd527bc3771eb229a32724ee6ba340ead9b92249/numpy-2.3.5-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1062fde1dcf469571705945b0f221b73928f34a20c904ffb45db101907c3454e", size = 14306855, upload-time = "2025-11-16T22:50:59.208Z" }, - { url = "https://files.pythonhosted.org/packages/a5/87/6831980559434973bebc30cd9c1f21e541a0f2b0c280d43d3afd909b66d0/numpy-2.3.5-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce581db493ea1a96c0556360ede6607496e8bf9b3a8efa66e06477267bc831e9", size = 16657359, upload-time = "2025-11-16T22:51:01.991Z" }, - { url = "https://files.pythonhosted.org/packages/dd/91/c797f544491ee99fd00495f12ebb7802c440c1915811d72ac5b4479a3356/numpy-2.3.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:cc8920d2ec5fa99875b670bb86ddeb21e295cb07aa331810d9e486e0b969d946", size = 16093374, upload-time = "2025-11-16T22:51:05.291Z" }, - { url = "https://files.pythonhosted.org/packages/74/a6/54da03253afcbe7a72785ec4da9c69fb7a17710141ff9ac5fcb2e32dbe64/numpy-2.3.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9ee2197ef8c4f0dfe405d835f3b6a14f5fee7782b5de51ba06fb65fc9b36e9f1", size = 18594587, upload-time = "2025-11-16T22:51:08.585Z" }, - { url = "https://files.pythonhosted.org/packages/80/e9/aff53abbdd41b0ecca94285f325aff42357c6b5abc482a3fcb4994290b18/numpy-2.3.5-cp313-cp313t-win32.whl", hash = "sha256:70b37199913c1bd300ff6e2693316c6f869c7ee16378faf10e4f5e3275b299c3", size = 6405940, upload-time = "2025-11-16T22:51:11.541Z" }, - { url = "https://files.pythonhosted.org/packages/d5/81/50613fec9d4de5480de18d4f8ef59ad7e344d497edbef3cfd80f24f98461/numpy-2.3.5-cp313-cp313t-win_amd64.whl", hash = "sha256:b501b5fa195cc9e24fe102f21ec0a44dffc231d2af79950b451e0d99cea02234", size = 12920341, upload-time = "2025-11-16T22:51:14.312Z" }, - { url = "https://files.pythonhosted.org/packages/bb/ab/08fd63b9a74303947f34f0bd7c5903b9c5532c2d287bead5bdf4c556c486/numpy-2.3.5-cp313-cp313t-win_arm64.whl", hash = "sha256:a80afd79f45f3c4a7d341f13acbe058d1ca8ac017c165d3fa0d3de6bc1a079d7", size = 10262507, upload-time = "2025-11-16T22:51:16.846Z" }, - { url = "https://files.pythonhosted.org/packages/ba/97/1a914559c19e32d6b2e233cf9a6a114e67c856d35b1d6babca571a3e880f/numpy-2.3.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:bf06bc2af43fa8d32d30fae16ad965663e966b1a3202ed407b84c989c3221e82", size = 16735706, upload-time = "2025-11-16T22:51:19.558Z" }, - { url = "https://files.pythonhosted.org/packages/57/d4/51233b1c1b13ecd796311216ae417796b88b0616cfd8a33ae4536330748a/numpy-2.3.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:052e8c42e0c49d2575621c158934920524f6c5da05a1d3b9bab5d8e259e045f0", size = 12264507, upload-time = "2025-11-16T22:51:22.492Z" }, - { url = "https://files.pythonhosted.org/packages/45/98/2fe46c5c2675b8306d0b4a3ec3494273e93e1226a490f766e84298576956/numpy-2.3.5-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:1ed1ec893cff7040a02c8aa1c8611b94d395590d553f6b53629a4461dc7f7b63", size = 5093049, upload-time = "2025-11-16T22:51:25.171Z" }, - { url = "https://files.pythonhosted.org/packages/ce/0e/0698378989bb0ac5f1660c81c78ab1fe5476c1a521ca9ee9d0710ce54099/numpy-2.3.5-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:2dcd0808a421a482a080f89859a18beb0b3d1e905b81e617a188bd80422d62e9", size = 6626603, upload-time = "2025-11-16T22:51:27Z" }, - { url = "https://files.pythonhosted.org/packages/5e/a6/9ca0eecc489640615642a6cbc0ca9e10df70df38c4d43f5a928ff18d8827/numpy-2.3.5-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:727fd05b57df37dc0bcf1a27767a3d9a78cbbc92822445f32cc3436ba797337b", size = 14262696, upload-time = "2025-11-16T22:51:29.402Z" }, - { url = "https://files.pythonhosted.org/packages/c8/f6/07ec185b90ec9d7217a00eeeed7383b73d7e709dae2a9a021b051542a708/numpy-2.3.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fffe29a1ef00883599d1dc2c51aa2e5d80afe49523c261a74933df395c15c520", size = 16597350, upload-time = "2025-11-16T22:51:32.167Z" }, - { url = "https://files.pythonhosted.org/packages/75/37/164071d1dde6a1a84c9b8e5b414fa127981bad47adf3a6b7e23917e52190/numpy-2.3.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8f7f0e05112916223d3f438f293abf0727e1181b5983f413dfa2fefc4098245c", size = 16040190, upload-time = "2025-11-16T22:51:35.403Z" }, - { url = "https://files.pythonhosted.org/packages/08/3c/f18b82a406b04859eb026d204e4e1773eb41c5be58410f41ffa511d114ae/numpy-2.3.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2e2eb32ddb9ccb817d620ac1d8dae7c3f641c1e5f55f531a33e8ab97960a75b8", size = 18536749, upload-time = "2025-11-16T22:51:39.698Z" }, - { url = "https://files.pythonhosted.org/packages/40/79/f82f572bf44cf0023a2fe8588768e23e1592585020d638999f15158609e1/numpy-2.3.5-cp314-cp314-win32.whl", hash = "sha256:66f85ce62c70b843bab1fb14a05d5737741e74e28c7b8b5a064de10142fad248", size = 6335432, upload-time = "2025-11-16T22:51:42.476Z" }, - { url = "https://files.pythonhosted.org/packages/a3/2e/235b4d96619931192c91660805e5e49242389742a7a82c27665021db690c/numpy-2.3.5-cp314-cp314-win_amd64.whl", hash = "sha256:e6a0bc88393d65807d751a614207b7129a310ca4fe76a74e5c7da5fa5671417e", size = 12919388, upload-time = "2025-11-16T22:51:45.275Z" }, - { url = "https://files.pythonhosted.org/packages/07/2b/29fd75ce45d22a39c61aad74f3d718e7ab67ccf839ca8b60866054eb15f8/numpy-2.3.5-cp314-cp314-win_arm64.whl", hash = "sha256:aeffcab3d4b43712bb7a60b65f6044d444e75e563ff6180af8f98dd4b905dfd2", size = 10476651, upload-time = "2025-11-16T22:51:47.749Z" }, - { url = "https://files.pythonhosted.org/packages/17/e1/f6a721234ebd4d87084cfa68d081bcba2f5cfe1974f7de4e0e8b9b2a2ba1/numpy-2.3.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:17531366a2e3a9e30762c000f2c43a9aaa05728712e25c11ce1dbe700c53ad41", size = 16834503, upload-time = "2025-11-16T22:51:50.443Z" }, - { url = "https://files.pythonhosted.org/packages/5c/1c/baf7ffdc3af9c356e1c135e57ab7cf8d247931b9554f55c467efe2c69eff/numpy-2.3.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d21644de1b609825ede2f48be98dfde4656aefc713654eeee280e37cadc4e0ad", size = 12381612, upload-time = "2025-11-16T22:51:53.609Z" }, - { url = "https://files.pythonhosted.org/packages/74/91/f7f0295151407ddc9ba34e699013c32c3c91944f9b35fcf9281163dc1468/numpy-2.3.5-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:c804e3a5aba5460c73955c955bdbd5c08c354954e9270a2c1565f62e866bdc39", size = 5210042, upload-time = "2025-11-16T22:51:56.213Z" }, - { url = "https://files.pythonhosted.org/packages/2e/3b/78aebf345104ec50dd50a4d06ddeb46a9ff5261c33bcc58b1c4f12f85ec2/numpy-2.3.5-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:cc0a57f895b96ec78969c34f682c602bf8da1a0270b09bc65673df2e7638ec20", size = 6724502, upload-time = "2025-11-16T22:51:58.584Z" }, - { url = "https://files.pythonhosted.org/packages/02/c6/7c34b528740512e57ef1b7c8337ab0b4f0bddf34c723b8996c675bc2bc91/numpy-2.3.5-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:900218e456384ea676e24ea6a0417f030a3b07306d29d7ad843957b40a9d8d52", size = 14308962, upload-time = "2025-11-16T22:52:01.698Z" }, - { url = "https://files.pythonhosted.org/packages/80/35/09d433c5262bc32d725bafc619e095b6a6651caf94027a03da624146f655/numpy-2.3.5-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09a1bea522b25109bf8e6f3027bd810f7c1085c64a0c7ce050c1676ad0ba010b", size = 16655054, upload-time = "2025-11-16T22:52:04.267Z" }, - { url = "https://files.pythonhosted.org/packages/7a/ab/6a7b259703c09a88804fa2430b43d6457b692378f6b74b356155283566ac/numpy-2.3.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:04822c00b5fd0323c8166d66c701dc31b7fbd252c100acd708c48f763968d6a3", size = 16091613, upload-time = "2025-11-16T22:52:08.651Z" }, - { url = "https://files.pythonhosted.org/packages/c2/88/330da2071e8771e60d1038166ff9d73f29da37b01ec3eb43cb1427464e10/numpy-2.3.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d6889ec4ec662a1a37eb4b4fb26b6100841804dac55bd9df579e326cdc146227", size = 18591147, upload-time = "2025-11-16T22:52:11.453Z" }, - { url = "https://files.pythonhosted.org/packages/51/41/851c4b4082402d9ea860c3626db5d5df47164a712cb23b54be028b184c1c/numpy-2.3.5-cp314-cp314t-win32.whl", hash = "sha256:93eebbcf1aafdf7e2ddd44c2923e2672e1010bddc014138b229e49725b4d6be5", size = 6479806, upload-time = "2025-11-16T22:52:14.641Z" }, - { url = "https://files.pythonhosted.org/packages/90/30/d48bde1dfd93332fa557cff1972fbc039e055a52021fbef4c2c4b1eefd17/numpy-2.3.5-cp314-cp314t-win_amd64.whl", hash = "sha256:c8a9958e88b65c3b27e22ca2a076311636850b612d6bbfb76e8d156aacde2aaf", size = 13105760, upload-time = "2025-11-16T22:52:17.975Z" }, - { url = "https://files.pythonhosted.org/packages/2d/fd/4b5eb0b3e888d86aee4d198c23acec7d214baaf17ea93c1adec94c9518b9/numpy-2.3.5-cp314-cp314t-win_arm64.whl", hash = "sha256:6203fdf9f3dc5bdaed7319ad8698e685c7a3be10819f41d32a0723e611733b42", size = 10545459, upload-time = "2025-11-16T22:52:20.55Z" }, - { url = "https://files.pythonhosted.org/packages/c6/65/f9dea8e109371ade9c782b4e4756a82edf9d3366bca495d84d79859a0b79/numpy-2.3.5-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f0963b55cdd70fad460fa4c1341f12f976bb26cb66021a5580329bd498988310", size = 16910689, upload-time = "2025-11-16T22:52:23.247Z" }, - { url = "https://files.pythonhosted.org/packages/00/4f/edb00032a8fb92ec0a679d3830368355da91a69cab6f3e9c21b64d0bb986/numpy-2.3.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f4255143f5160d0de972d28c8f9665d882b5f61309d8362fdd3e103cf7bf010c", size = 12457053, upload-time = "2025-11-16T22:52:26.367Z" }, - { url = "https://files.pythonhosted.org/packages/16/a4/e8a53b5abd500a63836a29ebe145fc1ab1f2eefe1cfe59276020373ae0aa/numpy-2.3.5-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:a4b9159734b326535f4dd01d947f919c6eefd2d9827466a696c44ced82dfbc18", size = 5285635, upload-time = "2025-11-16T22:52:29.266Z" }, - { url = "https://files.pythonhosted.org/packages/a3/2f/37eeb9014d9c8b3e9c55bc599c68263ca44fdbc12a93e45a21d1d56df737/numpy-2.3.5-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2feae0d2c91d46e59fcd62784a3a83b3fb677fead592ce51b5a6fbb4f95965ff", size = 6801770, upload-time = "2025-11-16T22:52:31.421Z" }, - { url = "https://files.pythonhosted.org/packages/7d/e4/68d2f474df2cb671b2b6c2986a02e520671295647dad82484cde80ca427b/numpy-2.3.5-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ffac52f28a7849ad7576293c0cb7b9f08304e8f7d738a8cb8a90ec4c55a998eb", size = 14391768, upload-time = "2025-11-16T22:52:33.593Z" }, - { url = "https://files.pythonhosted.org/packages/b8/50/94ccd8a2b141cb50651fddd4f6a48874acb3c91c8f0842b08a6afc4b0b21/numpy-2.3.5-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63c0e9e7eea69588479ebf4a8a270d5ac22763cc5854e9a7eae952a3908103f7", size = 16729263, upload-time = "2025-11-16T22:52:36.369Z" }, - { url = "https://files.pythonhosted.org/packages/2d/ee/346fa473e666fe14c52fcdd19ec2424157290a032d4c41f98127bfb31ac7/numpy-2.3.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f16417ec91f12f814b10bafe79ef77e70113a2f5f7018640e7425ff979253425", size = 12967213, upload-time = "2025-11-16T22:52:39.38Z" }, +version = "2.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/fd/0005efbd0af48e55eb3c7208af93f2862d4b1a56cd78e84309a2d959208d/numpy-2.4.2.tar.gz", hash = "sha256:659a6107e31a83c4e33f763942275fd278b21d095094044eb35569e86a21ddae", size = 20723651, upload-time = "2026-01-31T23:13:10.135Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/44/71852273146957899753e69986246d6a176061ea183407e95418c2aa4d9a/numpy-2.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e7e88598032542bd49af7c4747541422884219056c268823ef6e5e89851c8825", size = 16955478, upload-time = "2026-01-31T23:10:25.623Z" }, + { url = "https://files.pythonhosted.org/packages/74/41/5d17d4058bd0cd96bcbd4d9ff0fb2e21f52702aab9a72e4a594efa18692f/numpy-2.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7edc794af8b36ca37ef5fcb5e0d128c7e0595c7b96a2318d1badb6fcd8ee86b1", size = 14965467, upload-time = "2026-01-31T23:10:28.186Z" }, + { url = "https://files.pythonhosted.org/packages/49/48/fb1ce8136c19452ed15f033f8aee91d5defe515094e330ce368a0647846f/numpy-2.4.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:6e9f61981ace1360e42737e2bae58b27bf28a1b27e781721047d84bd754d32e7", size = 5475172, upload-time = "2026-01-31T23:10:30.848Z" }, + { url = "https://files.pythonhosted.org/packages/40/a9/3feb49f17bbd1300dd2570432961f5c8a4ffeff1db6f02c7273bd020a4c9/numpy-2.4.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:cb7bbb88aa74908950d979eeaa24dbdf1a865e3c7e45ff0121d8f70387b55f73", size = 6805145, upload-time = "2026-01-31T23:10:32.352Z" }, + { url = "https://files.pythonhosted.org/packages/3f/39/fdf35cbd6d6e2fcad42fcf85ac04a85a0d0fbfbf34b30721c98d602fd70a/numpy-2.4.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f069069931240b3fc703f1e23df63443dbd6390614c8c44a87d96cd0ec81eb1", size = 15966084, upload-time = "2026-01-31T23:10:34.502Z" }, + { url = "https://files.pythonhosted.org/packages/1b/46/6fa4ea94f1ddf969b2ee941290cca6f1bfac92b53c76ae5f44afe17ceb69/numpy-2.4.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c02ef4401a506fb60b411467ad501e1429a3487abca4664871d9ae0b46c8ba32", size = 16899477, upload-time = "2026-01-31T23:10:37.075Z" }, + { url = "https://files.pythonhosted.org/packages/09/a1/2a424e162b1a14a5bd860a464ab4e07513916a64ab1683fae262f735ccd2/numpy-2.4.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2653de5c24910e49c2b106499803124dde62a5a1fe0eedeaecf4309a5f639390", size = 17323429, upload-time = "2026-01-31T23:10:39.704Z" }, + { url = "https://files.pythonhosted.org/packages/ce/a2/73014149ff250628df72c58204822ac01d768697913881aacf839ff78680/numpy-2.4.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1ae241bbfc6ae276f94a170b14785e561cb5e7f626b6688cf076af4110887413", size = 18635109, upload-time = "2026-01-31T23:10:41.924Z" }, + { url = "https://files.pythonhosted.org/packages/6c/0c/73e8be2f1accd56df74abc1c5e18527822067dced5ec0861b5bb882c2ce0/numpy-2.4.2-cp311-cp311-win32.whl", hash = "sha256:df1b10187212b198dd45fa943d8985a3c8cf854aed4923796e0e019e113a1bda", size = 6237915, upload-time = "2026-01-31T23:10:45.26Z" }, + { url = "https://files.pythonhosted.org/packages/76/ae/e0265e0163cf127c24c3969d29f1c4c64551a1e375d95a13d32eab25d364/numpy-2.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:b9c618d56a29c9cb1c4da979e9899be7578d2e0b3c24d52079c166324c9e8695", size = 12607972, upload-time = "2026-01-31T23:10:47.021Z" }, + { url = "https://files.pythonhosted.org/packages/29/a5/c43029af9b8014d6ea157f192652c50042e8911f4300f8f6ed3336bf437f/numpy-2.4.2-cp311-cp311-win_arm64.whl", hash = "sha256:47c5a6ed21d9452b10227e5e8a0e1c22979811cad7dcc19d8e3e2fb8fa03f1a3", size = 10485763, upload-time = "2026-01-31T23:10:50.087Z" }, + { url = "https://files.pythonhosted.org/packages/51/6e/6f394c9c77668153e14d4da83bcc247beb5952f6ead7699a1a2992613bea/numpy-2.4.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:21982668592194c609de53ba4933a7471880ccbaadcc52352694a59ecc860b3a", size = 16667963, upload-time = "2026-01-31T23:10:52.147Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f8/55483431f2b2fd015ae6ed4fe62288823ce908437ed49db5a03d15151678/numpy-2.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40397bda92382fcec844066efb11f13e1c9a3e2a8e8f318fb72ed8b6db9f60f1", size = 14693571, upload-time = "2026-01-31T23:10:54.789Z" }, + { url = "https://files.pythonhosted.org/packages/2f/20/18026832b1845cdc82248208dd929ca14c9d8f2bac391f67440707fff27c/numpy-2.4.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:b3a24467af63c67829bfaa61eecf18d5432d4f11992688537be59ecd6ad32f5e", size = 5203469, upload-time = "2026-01-31T23:10:57.343Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/2eb97c8a77daaba34eaa3fa7241a14ac5f51c46a6bd5911361b644c4a1e2/numpy-2.4.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:805cc8de9fd6e7a22da5aed858e0ab16be5a4db6c873dde1d7451c541553aa27", size = 6550820, upload-time = "2026-01-31T23:10:59.429Z" }, + { url = "https://files.pythonhosted.org/packages/b1/91/b97fdfd12dc75b02c44e26c6638241cc004d4079a0321a69c62f51470c4c/numpy-2.4.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d82351358ffbcdcd7b686b90742a9b86632d6c1c051016484fa0b326a0a1548", size = 15663067, upload-time = "2026-01-31T23:11:01.291Z" }, + { url = "https://files.pythonhosted.org/packages/f5/c6/a18e59f3f0b8071cc85cbc8d80cd02d68aa9710170b2553a117203d46936/numpy-2.4.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e35d3e0144137d9fdae62912e869136164534d64a169f86438bc9561b6ad49f", size = 16619782, upload-time = "2026-01-31T23:11:03.669Z" }, + { url = "https://files.pythonhosted.org/packages/b7/83/9751502164601a79e18847309f5ceec0b1446d7b6aa12305759b72cf98b2/numpy-2.4.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:adb6ed2ad29b9e15321d167d152ee909ec73395901b70936f029c3bc6d7f4460", size = 17013128, upload-time = "2026-01-31T23:11:05.913Z" }, + { url = "https://files.pythonhosted.org/packages/61/c4/c4066322256ec740acc1c8923a10047818691d2f8aec254798f3dd90f5f2/numpy-2.4.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8906e71fd8afcb76580404e2a950caef2685df3d2a57fe82a86ac8d33cc007ba", size = 18345324, upload-time = "2026-01-31T23:11:08.248Z" }, + { url = "https://files.pythonhosted.org/packages/ab/af/6157aa6da728fa4525a755bfad486ae7e3f76d4c1864138003eb84328497/numpy-2.4.2-cp312-cp312-win32.whl", hash = "sha256:ec055f6dae239a6299cace477b479cca2fc125c5675482daf1dd886933a1076f", size = 5960282, upload-time = "2026-01-31T23:11:10.497Z" }, + { url = "https://files.pythonhosted.org/packages/92/0f/7ceaaeaacb40567071e94dbf2c9480c0ae453d5bb4f52bea3892c39dc83c/numpy-2.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:209fae046e62d0ce6435fcfe3b1a10537e858249b3d9b05829e2a05218296a85", size = 12314210, upload-time = "2026-01-31T23:11:12.176Z" }, + { url = "https://files.pythonhosted.org/packages/2f/a3/56c5c604fae6dd40fa2ed3040d005fca97e91bd320d232ac9931d77ba13c/numpy-2.4.2-cp312-cp312-win_arm64.whl", hash = "sha256:fbde1b0c6e81d56f5dccd95dd4a711d9b95df1ae4009a60887e56b27e8d903fa", size = 10220171, upload-time = "2026-01-31T23:11:14.684Z" }, + { url = "https://files.pythonhosted.org/packages/a1/22/815b9fe25d1d7ae7d492152adbc7226d3eff731dffc38fe970589fcaaa38/numpy-2.4.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:25f2059807faea4b077a2b6837391b5d830864b3543627f381821c646f31a63c", size = 16663696, upload-time = "2026-01-31T23:11:17.516Z" }, + { url = "https://files.pythonhosted.org/packages/09/f0/817d03a03f93ba9c6c8993de509277d84e69f9453601915e4a69554102a1/numpy-2.4.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bd3a7a9f5847d2fb8c2c6d1c862fa109c31a9abeca1a3c2bd5a64572955b2979", size = 14688322, upload-time = "2026-01-31T23:11:19.883Z" }, + { url = "https://files.pythonhosted.org/packages/da/b4/f805ab79293c728b9a99438775ce51885fd4f31b76178767cfc718701a39/numpy-2.4.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:8e4549f8a3c6d13d55041925e912bfd834285ef1dd64d6bc7d542583355e2e98", size = 5198157, upload-time = "2026-01-31T23:11:22.375Z" }, + { url = "https://files.pythonhosted.org/packages/74/09/826e4289844eccdcd64aac27d13b0fd3f32039915dd5b9ba01baae1f436c/numpy-2.4.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:aea4f66ff44dfddf8c2cffd66ba6538c5ec67d389285292fe428cb2c738c8aef", size = 6546330, upload-time = "2026-01-31T23:11:23.958Z" }, + { url = "https://files.pythonhosted.org/packages/19/fb/cbfdbfa3057a10aea5422c558ac57538e6acc87ec1669e666d32ac198da7/numpy-2.4.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3cd545784805de05aafe1dde61752ea49a359ccba9760c1e5d1c88a93bbf2b7", size = 15660968, upload-time = "2026-01-31T23:11:25.713Z" }, + { url = "https://files.pythonhosted.org/packages/04/dc/46066ce18d01645541f0186877377b9371b8fa8017fa8262002b4ef22612/numpy-2.4.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0d9b7c93578baafcbc5f0b83eaf17b79d345c6f36917ba0c67f45226911d499", size = 16607311, upload-time = "2026-01-31T23:11:28.117Z" }, + { url = "https://files.pythonhosted.org/packages/14/d9/4b5adfc39a43fa6bf918c6d544bc60c05236cc2f6339847fc5b35e6cb5b0/numpy-2.4.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f74f0f7779cc7ae07d1810aab8ac6b1464c3eafb9e283a40da7309d5e6e48fbb", size = 17012850, upload-time = "2026-01-31T23:11:30.888Z" }, + { url = "https://files.pythonhosted.org/packages/b7/20/adb6e6adde6d0130046e6fdfb7675cc62bc2f6b7b02239a09eb58435753d/numpy-2.4.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c7ac672d699bf36275c035e16b65539931347d68b70667d28984c9fb34e07fa7", size = 18334210, upload-time = "2026-01-31T23:11:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/78/0e/0a73b3dff26803a8c02baa76398015ea2a5434d9b8265a7898a6028c1591/numpy-2.4.2-cp313-cp313-win32.whl", hash = "sha256:8e9afaeb0beff068b4d9cd20d322ba0ee1cecfb0b08db145e4ab4dd44a6b5110", size = 5958199, upload-time = "2026-01-31T23:11:35.385Z" }, + { url = "https://files.pythonhosted.org/packages/43/bc/6352f343522fcb2c04dbaf94cb30cca6fd32c1a750c06ad6231b4293708c/numpy-2.4.2-cp313-cp313-win_amd64.whl", hash = "sha256:7df2de1e4fba69a51c06c28f5a3de36731eb9639feb8e1cf7e4a7b0daf4cf622", size = 12310848, upload-time = "2026-01-31T23:11:38.001Z" }, + { url = "https://files.pythonhosted.org/packages/6e/8d/6da186483e308da5da1cc6918ce913dcfe14ffde98e710bfeff2a6158d4e/numpy-2.4.2-cp313-cp313-win_arm64.whl", hash = "sha256:0fece1d1f0a89c16b03442eae5c56dc0be0c7883b5d388e0c03f53019a4bfd71", size = 10221082, upload-time = "2026-01-31T23:11:40.392Z" }, + { url = "https://files.pythonhosted.org/packages/25/a1/9510aa43555b44781968935c7548a8926274f815de42ad3997e9e83680dd/numpy-2.4.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5633c0da313330fd20c484c78cdd3f9b175b55e1a766c4a174230c6b70ad8262", size = 14815866, upload-time = "2026-01-31T23:11:42.495Z" }, + { url = "https://files.pythonhosted.org/packages/36/30/6bbb5e76631a5ae46e7923dd16ca9d3f1c93cfa8d4ed79a129814a9d8db3/numpy-2.4.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d9f64d786b3b1dd742c946c42d15b07497ed14af1a1f3ce840cce27daa0ce913", size = 5325631, upload-time = "2026-01-31T23:11:44.7Z" }, + { url = "https://files.pythonhosted.org/packages/46/00/3a490938800c1923b567b3a15cd17896e68052e2145d8662aaf3e1ffc58f/numpy-2.4.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:b21041e8cb6a1eb5312dd1d2f80a94d91efffb7a06b70597d44f1bd2dfc315ab", size = 6646254, upload-time = "2026-01-31T23:11:46.341Z" }, + { url = "https://files.pythonhosted.org/packages/d3/e9/fac0890149898a9b609caa5af7455a948b544746e4b8fe7c212c8edd71f8/numpy-2.4.2-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:00ab83c56211a1d7c07c25e3217ea6695e50a3e2f255053686b081dc0b091a82", size = 15720138, upload-time = "2026-01-31T23:11:48.082Z" }, + { url = "https://files.pythonhosted.org/packages/ea/5c/08887c54e68e1e28df53709f1893ce92932cc6f01f7c3d4dc952f61ffd4e/numpy-2.4.2-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2fb882da679409066b4603579619341c6d6898fc83a8995199d5249f986e8e8f", size = 16655398, upload-time = "2026-01-31T23:11:50.293Z" }, + { url = "https://files.pythonhosted.org/packages/4d/89/253db0fa0e66e9129c745e4ef25631dc37d5f1314dad2b53e907b8538e6d/numpy-2.4.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:66cb9422236317f9d44b67b4d18f44efe6e9c7f8794ac0462978513359461554", size = 17079064, upload-time = "2026-01-31T23:11:52.927Z" }, + { url = "https://files.pythonhosted.org/packages/2a/d5/cbade46ce97c59c6c3da525e8d95b7abe8a42974a1dc5c1d489c10433e88/numpy-2.4.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0f01dcf33e73d80bd8dc0f20a71303abbafa26a19e23f6b68d1aa9990af90257", size = 18379680, upload-time = "2026-01-31T23:11:55.22Z" }, + { url = "https://files.pythonhosted.org/packages/40/62/48f99ae172a4b63d981babe683685030e8a3df4f246c893ea5c6ef99f018/numpy-2.4.2-cp313-cp313t-win32.whl", hash = "sha256:52b913ec40ff7ae845687b0b34d8d93b60cb66dcee06996dd5c99f2fc9328657", size = 6082433, upload-time = "2026-01-31T23:11:58.096Z" }, + { url = "https://files.pythonhosted.org/packages/07/38/e054a61cfe48ad9f1ed0d188e78b7e26859d0b60ef21cd9de4897cdb5326/numpy-2.4.2-cp313-cp313t-win_amd64.whl", hash = "sha256:5eea80d908b2c1f91486eb95b3fb6fab187e569ec9752ab7d9333d2e66bf2d6b", size = 12451181, upload-time = "2026-01-31T23:11:59.782Z" }, + { url = "https://files.pythonhosted.org/packages/6e/a4/a05c3a6418575e185dd84d0b9680b6bb2e2dc3e4202f036b7b4e22d6e9dc/numpy-2.4.2-cp313-cp313t-win_arm64.whl", hash = "sha256:fd49860271d52127d61197bb50b64f58454e9f578cb4b2c001a6de8b1f50b0b1", size = 10290756, upload-time = "2026-01-31T23:12:02.438Z" }, + { url = "https://files.pythonhosted.org/packages/18/88/b7df6050bf18fdcfb7046286c6535cabbdd2064a3440fca3f069d319c16e/numpy-2.4.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:444be170853f1f9d528428eceb55f12918e4fda5d8805480f36a002f1415e09b", size = 16663092, upload-time = "2026-01-31T23:12:04.521Z" }, + { url = "https://files.pythonhosted.org/packages/25/7a/1fee4329abc705a469a4afe6e69b1ef7e915117747886327104a8493a955/numpy-2.4.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d1240d50adff70c2a88217698ca844723068533f3f5c5fa6ee2e3220e3bdb000", size = 14698770, upload-time = "2026-01-31T23:12:06.96Z" }, + { url = "https://files.pythonhosted.org/packages/fb/0b/f9e49ba6c923678ad5bc38181c08ac5e53b7a5754dbca8e581aa1a56b1ff/numpy-2.4.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:7cdde6de52fb6664b00b056341265441192d1291c130e99183ec0d4b110ff8b1", size = 5208562, upload-time = "2026-01-31T23:12:09.632Z" }, + { url = "https://files.pythonhosted.org/packages/7d/12/d7de8f6f53f9bb76997e5e4c069eda2051e3fe134e9181671c4391677bb2/numpy-2.4.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:cda077c2e5b780200b6b3e09d0b42205a3d1c68f30c6dceb90401c13bff8fe74", size = 6543710, upload-time = "2026-01-31T23:12:11.969Z" }, + { url = "https://files.pythonhosted.org/packages/09/63/c66418c2e0268a31a4cf8a8b512685748200f8e8e8ec6c507ce14e773529/numpy-2.4.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d30291931c915b2ab5717c2974bb95ee891a1cf22ebc16a8006bd59cd210d40a", size = 15677205, upload-time = "2026-01-31T23:12:14.33Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6c/7f237821c9642fb2a04d2f1e88b4295677144ca93285fd76eff3bcba858d/numpy-2.4.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bba37bc29d4d85761deed3954a1bc62be7cf462b9510b51d367b769a8c8df325", size = 16611738, upload-time = "2026-01-31T23:12:16.525Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a7/39c4cdda9f019b609b5c473899d87abff092fc908cfe4d1ecb2fcff453b0/numpy-2.4.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b2f0073ed0868db1dcd86e052d37279eef185b9c8db5bf61f30f46adac63c909", size = 17028888, upload-time = "2026-01-31T23:12:19.306Z" }, + { url = "https://files.pythonhosted.org/packages/da/b3/e84bb64bdfea967cc10950d71090ec2d84b49bc691df0025dddb7c26e8e3/numpy-2.4.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7f54844851cdb630ceb623dcec4db3240d1ac13d4990532446761baede94996a", size = 18339556, upload-time = "2026-01-31T23:12:21.816Z" }, + { url = "https://files.pythonhosted.org/packages/88/f5/954a291bc1192a27081706862ac62bb5920fbecfbaa302f64682aa90beed/numpy-2.4.2-cp314-cp314-win32.whl", hash = "sha256:12e26134a0331d8dbd9351620f037ec470b7c75929cb8a1537f6bfe411152a1a", size = 6006899, upload-time = "2026-01-31T23:12:24.14Z" }, + { url = "https://files.pythonhosted.org/packages/05/cb/eff72a91b2efdd1bc98b3b8759f6a1654aa87612fc86e3d87d6fe4f948c4/numpy-2.4.2-cp314-cp314-win_amd64.whl", hash = "sha256:068cdb2d0d644cdb45670810894f6a0600797a69c05f1ac478e8d31670b8ee75", size = 12443072, upload-time = "2026-01-31T23:12:26.33Z" }, + { url = "https://files.pythonhosted.org/packages/37/75/62726948db36a56428fce4ba80a115716dc4fad6a3a4352487f8bb950966/numpy-2.4.2-cp314-cp314-win_arm64.whl", hash = "sha256:6ed0be1ee58eef41231a5c943d7d1375f093142702d5723ca2eb07db9b934b05", size = 10494886, upload-time = "2026-01-31T23:12:28.488Z" }, + { url = "https://files.pythonhosted.org/packages/36/2f/ee93744f1e0661dc267e4b21940870cabfae187c092e1433b77b09b50ac4/numpy-2.4.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:98f16a80e917003a12c0580f97b5f875853ebc33e2eaa4bccfc8201ac6869308", size = 14818567, upload-time = "2026-01-31T23:12:30.709Z" }, + { url = "https://files.pythonhosted.org/packages/a7/24/6535212add7d76ff938d8bdc654f53f88d35cddedf807a599e180dcb8e66/numpy-2.4.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:20abd069b9cda45874498b245c8015b18ace6de8546bf50dfa8cea1696ed06ef", size = 5328372, upload-time = "2026-01-31T23:12:32.962Z" }, + { url = "https://files.pythonhosted.org/packages/5e/9d/c48f0a035725f925634bf6b8994253b43f2047f6778a54147d7e213bc5a7/numpy-2.4.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:e98c97502435b53741540a5717a6749ac2ada901056c7db951d33e11c885cc7d", size = 6649306, upload-time = "2026-01-31T23:12:34.797Z" }, + { url = "https://files.pythonhosted.org/packages/81/05/7c73a9574cd4a53a25907bad38b59ac83919c0ddc8234ec157f344d57d9a/numpy-2.4.2-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:da6cad4e82cb893db4b69105c604d805e0c3ce11501a55b5e9f9083b47d2ffe8", size = 15722394, upload-time = "2026-01-31T23:12:36.565Z" }, + { url = "https://files.pythonhosted.org/packages/35/fa/4de10089f21fc7d18442c4a767ab156b25c2a6eaf187c0db6d9ecdaeb43f/numpy-2.4.2-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e4424677ce4b47fe73c8b5556d876571f7c6945d264201180db2dc34f676ab5", size = 16653343, upload-time = "2026-01-31T23:12:39.188Z" }, + { url = "https://files.pythonhosted.org/packages/b8/f9/d33e4ffc857f3763a57aa85650f2e82486832d7492280ac21ba9efda80da/numpy-2.4.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:2b8f157c8a6f20eb657e240f8985cc135598b2b46985c5bccbde7616dc9c6b1e", size = 17078045, upload-time = "2026-01-31T23:12:42.041Z" }, + { url = "https://files.pythonhosted.org/packages/c8/b8/54bdb43b6225badbea6389fa038c4ef868c44f5890f95dd530a218706da3/numpy-2.4.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5daf6f3914a733336dab21a05cdec343144600e964d2fcdabaac0c0269874b2a", size = 18380024, upload-time = "2026-01-31T23:12:44.331Z" }, + { url = "https://files.pythonhosted.org/packages/a5/55/6e1a61ded7af8df04016d81b5b02daa59f2ea9252ee0397cb9f631efe9e5/numpy-2.4.2-cp314-cp314t-win32.whl", hash = "sha256:8c50dd1fc8826f5b26a5ee4d77ca55d88a895f4e4819c7ecc2a9f5905047a443", size = 6153937, upload-time = "2026-01-31T23:12:47.229Z" }, + { url = "https://files.pythonhosted.org/packages/45/aa/fa6118d1ed6d776b0983f3ceac9b1a5558e80df9365b1c3aa6d42bf9eee4/numpy-2.4.2-cp314-cp314t-win_amd64.whl", hash = "sha256:fcf92bee92742edd401ba41135185866f7026c502617f422eb432cfeca4fe236", size = 12631844, upload-time = "2026-01-31T23:12:48.997Z" }, + { url = "https://files.pythonhosted.org/packages/32/0a/2ec5deea6dcd158f254a7b372fb09cfba5719419c8d66343bab35237b3fb/numpy-2.4.2-cp314-cp314t-win_arm64.whl", hash = "sha256:1f92f53998a17265194018d1cc321b2e96e900ca52d54c7c77837b71b9465181", size = 10565379, upload-time = "2026-01-31T23:12:51.345Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f8/50e14d36d915ef64d8f8bc4a087fc8264d82c785eda6711f80ab7e620335/numpy-2.4.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:89f7268c009bc492f506abd6f5265defa7cb3f7487dc21d357c3d290add45082", size = 16833179, upload-time = "2026-01-31T23:12:53.5Z" }, + { url = "https://files.pythonhosted.org/packages/17/17/809b5cad63812058a8189e91a1e2d55a5a18fd04611dbad244e8aeae465c/numpy-2.4.2-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:e6dee3bb76aa4009d5a912180bf5b2de012532998d094acee25d9cb8dee3e44a", size = 14889755, upload-time = "2026-01-31T23:12:55.933Z" }, + { url = "https://files.pythonhosted.org/packages/3e/ea/181b9bcf7627fc8371720316c24db888dcb9829b1c0270abf3d288b2e29b/numpy-2.4.2-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:cd2bd2bbed13e213d6b55dc1d035a4f91748a7d3edc9480c13898b0353708920", size = 5399500, upload-time = "2026-01-31T23:12:58.671Z" }, + { url = "https://files.pythonhosted.org/packages/33/9f/413adf3fc955541ff5536b78fcf0754680b3c6d95103230252a2c9408d23/numpy-2.4.2-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:cf28c0c1d4c4bf00f509fa7eb02c58d7caf221b50b467bcb0d9bbf1584d5c821", size = 6714252, upload-time = "2026-01-31T23:13:00.518Z" }, + { url = "https://files.pythonhosted.org/packages/91/da/643aad274e29ccbdf42ecd94dafe524b81c87bcb56b83872d54827f10543/numpy-2.4.2-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e04ae107ac591763a47398bb45b568fc38f02dbc4aa44c063f67a131f99346cb", size = 15797142, upload-time = "2026-01-31T23:13:02.219Z" }, + { url = "https://files.pythonhosted.org/packages/66/27/965b8525e9cb5dc16481b30a1b3c21e50c7ebf6e9dbd48d0c4d0d5089c7e/numpy-2.4.2-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:602f65afdef699cda27ec0b9224ae5dc43e328f4c24c689deaf77133dbee74d0", size = 16727979, upload-time = "2026-01-31T23:13:04.62Z" }, + { url = "https://files.pythonhosted.org/packages/de/e5/b7d20451657664b07986c2f6e3be564433f5dcaf3482d68eaecd79afaf03/numpy-2.4.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:be71bf1edb48ebbbf7f6337b5bfd2f895d1902f6335a5830b20141fc126ffba0", size = 12502577, upload-time = "2026-01-31T23:13:07.08Z" }, ] [[package]] name = "openai" -version = "2.15.0" +version = "2.21.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, @@ -1794,9 +1847,9 @@ dependencies = [ { name = "tqdm" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/94/f4/4690ecb5d70023ce6bfcfeabfe717020f654bde59a775058ec6ac4692463/openai-2.15.0.tar.gz", hash = "sha256:42eb8cbb407d84770633f31bf727d4ffb4138711c670565a41663d9439174fba", size = 627383, upload-time = "2026-01-09T22:10:08.603Z" } +sdist = { url = "https://files.pythonhosted.org/packages/92/e5/3d197a0947a166649f566706d7a4c8f7fe38f1fa7b24c9bcffe4c7591d44/openai-2.21.0.tar.gz", hash = "sha256:81b48ce4b8bbb2cc3af02047ceb19561f7b1dc0d4e52d1de7f02abfd15aa59b7", size = 644374, upload-time = "2026-02-14T00:12:01.577Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/df/c306f7375d42bafb379934c2df4c2fa3964656c8c782bac75ee10c102818/openai-2.15.0-py3-none-any.whl", hash = "sha256:6ae23b932cd7230f7244e52954daa6602716d6b9bf235401a107af731baea6c3", size = 1067879, upload-time = "2026-01-09T22:10:06.446Z" }, + { url = "https://files.pythonhosted.org/packages/cc/56/0a89092a453bb2c676d66abee44f863e742b2110d4dbb1dbcca3f7e5fc33/openai-2.21.0-py3-none-any.whl", hash = "sha256:0bc1c775e5b1536c294eded39ee08f8407656537ccc71b1004104fe1602e267c", size = 1103065, upload-time = "2026-02-14T00:11:59.603Z" }, ] [[package]] @@ -1813,11 +1866,11 @@ wheels = [ [[package]] name = "packaging" -version = "25.0" +version = "26.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, ] [[package]] @@ -1831,65 +1884,71 @@ wheels = [ [[package]] name = "pandas" -version = "2.3.3" +version = "3.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, { name = "python-dateutil" }, - { name = "pytz" }, - { name = "tzdata" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/33/01/d40b85317f86cf08d853a4f495195c73815fdf205eef3993821720274518/pandas-2.3.3.tar.gz", hash = "sha256:e05e1af93b977f7eafa636d043f9f94c7ee3ac81af99c13508215942e64c993b", size = 4495223, upload-time = "2025-09-29T23:34:51.853Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/fa/7ac648108144a095b4fb6aa3de1954689f7af60a14cf25583f4960ecb878/pandas-2.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:602b8615ebcc4a0c1751e71840428ddebeb142ec02c786e8ad6b1ce3c8dec523", size = 11578790, upload-time = "2025-09-29T23:18:30.065Z" }, - { url = "https://files.pythonhosted.org/packages/9b/35/74442388c6cf008882d4d4bdfc4109be87e9b8b7ccd097ad1e7f006e2e95/pandas-2.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8fe25fc7b623b0ef6b5009149627e34d2a4657e880948ec3c840e9402e5c1b45", size = 10833831, upload-time = "2025-09-29T23:38:56.071Z" }, - { url = "https://files.pythonhosted.org/packages/fe/e4/de154cbfeee13383ad58d23017da99390b91d73f8c11856f2095e813201b/pandas-2.3.3-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b468d3dad6ff947df92dcb32ede5b7bd41a9b3cceef0a30ed925f6d01fb8fa66", size = 12199267, upload-time = "2025-09-29T23:18:41.627Z" }, - { url = "https://files.pythonhosted.org/packages/bf/c9/63f8d545568d9ab91476b1818b4741f521646cbdd151c6efebf40d6de6f7/pandas-2.3.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b98560e98cb334799c0b07ca7967ac361a47326e9b4e5a7dfb5ab2b1c9d35a1b", size = 12789281, upload-time = "2025-09-29T23:18:56.834Z" }, - { url = "https://files.pythonhosted.org/packages/f2/00/a5ac8c7a0e67fd1a6059e40aa08fa1c52cc00709077d2300e210c3ce0322/pandas-2.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37b5848ba49824e5c30bedb9c830ab9b7751fd049bc7914533e01c65f79791", size = 13240453, upload-time = "2025-09-29T23:19:09.247Z" }, - { url = "https://files.pythonhosted.org/packages/27/4d/5c23a5bc7bd209231618dd9e606ce076272c9bc4f12023a70e03a86b4067/pandas-2.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:db4301b2d1f926ae677a751eb2bd0e8c5f5319c9cb3f88b0becbbb0b07b34151", size = 13890361, upload-time = "2025-09-29T23:19:25.342Z" }, - { url = "https://files.pythonhosted.org/packages/8e/59/712db1d7040520de7a4965df15b774348980e6df45c129b8c64d0dbe74ef/pandas-2.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:f086f6fe114e19d92014a1966f43a3e62285109afe874f067f5abbdcbb10e59c", size = 11348702, upload-time = "2025-09-29T23:19:38.296Z" }, - { url = "https://files.pythonhosted.org/packages/9c/fb/231d89e8637c808b997d172b18e9d4a4bc7bf31296196c260526055d1ea0/pandas-2.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d21f6d74eb1725c2efaa71a2bfc661a0689579b58e9c0ca58a739ff0b002b53", size = 11597846, upload-time = "2025-09-29T23:19:48.856Z" }, - { url = "https://files.pythonhosted.org/packages/5c/bd/bf8064d9cfa214294356c2d6702b716d3cf3bb24be59287a6a21e24cae6b/pandas-2.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3fd2f887589c7aa868e02632612ba39acb0b8948faf5cc58f0850e165bd46f35", size = 10729618, upload-time = "2025-09-29T23:39:08.659Z" }, - { url = "https://files.pythonhosted.org/packages/57/56/cf2dbe1a3f5271370669475ead12ce77c61726ffd19a35546e31aa8edf4e/pandas-2.3.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ecaf1e12bdc03c86ad4a7ea848d66c685cb6851d807a26aa245ca3d2017a1908", size = 11737212, upload-time = "2025-09-29T23:19:59.765Z" }, - { url = "https://files.pythonhosted.org/packages/e5/63/cd7d615331b328e287d8233ba9fdf191a9c2d11b6af0c7a59cfcec23de68/pandas-2.3.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b3d11d2fda7eb164ef27ffc14b4fcab16a80e1ce67e9f57e19ec0afaf715ba89", size = 12362693, upload-time = "2025-09-29T23:20:14.098Z" }, - { url = "https://files.pythonhosted.org/packages/a6/de/8b1895b107277d52f2b42d3a6806e69cfef0d5cf1d0ba343470b9d8e0a04/pandas-2.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a68e15f780eddf2b07d242e17a04aa187a7ee12b40b930bfdd78070556550e98", size = 12771002, upload-time = "2025-09-29T23:20:26.76Z" }, - { url = "https://files.pythonhosted.org/packages/87/21/84072af3187a677c5893b170ba2c8fbe450a6ff911234916da889b698220/pandas-2.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:371a4ab48e950033bcf52b6527eccb564f52dc826c02afd9a1bc0ab731bba084", size = 13450971, upload-time = "2025-09-29T23:20:41.344Z" }, - { url = "https://files.pythonhosted.org/packages/86/41/585a168330ff063014880a80d744219dbf1dd7a1c706e75ab3425a987384/pandas-2.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:a16dcec078a01eeef8ee61bf64074b4e524a2a3f4b3be9326420cabe59c4778b", size = 10992722, upload-time = "2025-09-29T23:20:54.139Z" }, - { url = "https://files.pythonhosted.org/packages/cd/4b/18b035ee18f97c1040d94debd8f2e737000ad70ccc8f5513f4eefad75f4b/pandas-2.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:56851a737e3470de7fa88e6131f41281ed440d29a9268dcbf0002da5ac366713", size = 11544671, upload-time = "2025-09-29T23:21:05.024Z" }, - { url = "https://files.pythonhosted.org/packages/31/94/72fac03573102779920099bcac1c3b05975c2cb5f01eac609faf34bed1ca/pandas-2.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bdcd9d1167f4885211e401b3036c0c8d9e274eee67ea8d0758a256d60704cfe8", size = 10680807, upload-time = "2025-09-29T23:21:15.979Z" }, - { url = "https://files.pythonhosted.org/packages/16/87/9472cf4a487d848476865321de18cc8c920b8cab98453ab79dbbc98db63a/pandas-2.3.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e32e7cc9af0f1cc15548288a51a3b681cc2a219faa838e995f7dc53dbab1062d", size = 11709872, upload-time = "2025-09-29T23:21:27.165Z" }, - { url = "https://files.pythonhosted.org/packages/15/07/284f757f63f8a8d69ed4472bfd85122bd086e637bf4ed09de572d575a693/pandas-2.3.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:318d77e0e42a628c04dc56bcef4b40de67918f7041c2b061af1da41dcff670ac", size = 12306371, upload-time = "2025-09-29T23:21:40.532Z" }, - { url = "https://files.pythonhosted.org/packages/33/81/a3afc88fca4aa925804a27d2676d22dcd2031c2ebe08aabd0ae55b9ff282/pandas-2.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e0a175408804d566144e170d0476b15d78458795bb18f1304fb94160cabf40c", size = 12765333, upload-time = "2025-09-29T23:21:55.77Z" }, - { url = "https://files.pythonhosted.org/packages/8d/0f/b4d4ae743a83742f1153464cf1a8ecfafc3ac59722a0b5c8602310cb7158/pandas-2.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:93c2d9ab0fc11822b5eece72ec9587e172f63cff87c00b062f6e37448ced4493", size = 13418120, upload-time = "2025-09-29T23:22:10.109Z" }, - { url = "https://files.pythonhosted.org/packages/4f/c7/e54682c96a895d0c808453269e0b5928a07a127a15704fedb643e9b0a4c8/pandas-2.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:f8bfc0e12dc78f777f323f55c58649591b2cd0c43534e8355c51d3fede5f4dee", size = 10993991, upload-time = "2025-09-29T23:25:04.889Z" }, - { url = "https://files.pythonhosted.org/packages/f9/ca/3f8d4f49740799189e1395812f3bf23b5e8fc7c190827d55a610da72ce55/pandas-2.3.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:75ea25f9529fdec2d2e93a42c523962261e567d250b0013b16210e1d40d7c2e5", size = 12048227, upload-time = "2025-09-29T23:22:24.343Z" }, - { url = "https://files.pythonhosted.org/packages/0e/5a/f43efec3e8c0cc92c4663ccad372dbdff72b60bdb56b2749f04aa1d07d7e/pandas-2.3.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:74ecdf1d301e812db96a465a525952f4dde225fdb6d8e5a521d47e1f42041e21", size = 11411056, upload-time = "2025-09-29T23:22:37.762Z" }, - { url = "https://files.pythonhosted.org/packages/46/b1/85331edfc591208c9d1a63a06baa67b21d332e63b7a591a5ba42a10bb507/pandas-2.3.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6435cb949cb34ec11cc9860246ccb2fdc9ecd742c12d3304989017d53f039a78", size = 11645189, upload-time = "2025-09-29T23:22:51.688Z" }, - { url = "https://files.pythonhosted.org/packages/44/23/78d645adc35d94d1ac4f2a3c4112ab6f5b8999f4898b8cdf01252f8df4a9/pandas-2.3.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:900f47d8f20860de523a1ac881c4c36d65efcb2eb850e6948140fa781736e110", size = 12121912, upload-time = "2025-09-29T23:23:05.042Z" }, - { url = "https://files.pythonhosted.org/packages/53/da/d10013df5e6aaef6b425aa0c32e1fc1f3e431e4bcabd420517dceadce354/pandas-2.3.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a45c765238e2ed7d7c608fc5bc4a6f88b642f2f01e70c0c23d2224dd21829d86", size = 12712160, upload-time = "2025-09-29T23:23:28.57Z" }, - { url = "https://files.pythonhosted.org/packages/bd/17/e756653095a083d8a37cbd816cb87148debcfcd920129b25f99dd8d04271/pandas-2.3.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c4fc4c21971a1a9f4bdb4c73978c7f7256caa3e62b323f70d6cb80db583350bc", size = 13199233, upload-time = "2025-09-29T23:24:24.876Z" }, - { url = "https://files.pythonhosted.org/packages/04/fd/74903979833db8390b73b3a8a7d30d146d710bd32703724dd9083950386f/pandas-2.3.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:ee15f284898e7b246df8087fc82b87b01686f98ee67d85a17b7ab44143a3a9a0", size = 11540635, upload-time = "2025-09-29T23:25:52.486Z" }, - { url = "https://files.pythonhosted.org/packages/21/00/266d6b357ad5e6d3ad55093a7e8efc7dd245f5a842b584db9f30b0f0a287/pandas-2.3.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1611aedd912e1ff81ff41c745822980c49ce4a7907537be8692c8dbc31924593", size = 10759079, upload-time = "2025-09-29T23:26:33.204Z" }, - { url = "https://files.pythonhosted.org/packages/ca/05/d01ef80a7a3a12b2f8bbf16daba1e17c98a2f039cbc8e2f77a2c5a63d382/pandas-2.3.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6d2cefc361461662ac48810cb14365a365ce864afe85ef1f447ff5a1e99ea81c", size = 11814049, upload-time = "2025-09-29T23:27:15.384Z" }, - { url = "https://files.pythonhosted.org/packages/15/b2/0e62f78c0c5ba7e3d2c5945a82456f4fac76c480940f805e0b97fcbc2f65/pandas-2.3.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ee67acbbf05014ea6c763beb097e03cd629961c8a632075eeb34247120abcb4b", size = 12332638, upload-time = "2025-09-29T23:27:51.625Z" }, - { url = "https://files.pythonhosted.org/packages/c5/33/dd70400631b62b9b29c3c93d2feee1d0964dc2bae2e5ad7a6c73a7f25325/pandas-2.3.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c46467899aaa4da076d5abc11084634e2d197e9460643dd455ac3db5856b24d6", size = 12886834, upload-time = "2025-09-29T23:28:21.289Z" }, - { url = "https://files.pythonhosted.org/packages/d3/18/b5d48f55821228d0d2692b34fd5034bb185e854bdb592e9c640f6290e012/pandas-2.3.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6253c72c6a1d990a410bc7de641d34053364ef8bcd3126f7e7450125887dffe3", size = 13409925, upload-time = "2025-09-29T23:28:58.261Z" }, - { url = "https://files.pythonhosted.org/packages/a6/3d/124ac75fcd0ecc09b8fdccb0246ef65e35b012030defb0e0eba2cbbbe948/pandas-2.3.3-cp314-cp314-win_amd64.whl", hash = "sha256:1b07204a219b3b7350abaae088f451860223a52cfb8a6c53358e7948735158e5", size = 11109071, upload-time = "2025-09-29T23:32:27.484Z" }, - { url = "https://files.pythonhosted.org/packages/89/9c/0e21c895c38a157e0faa1fb64587a9226d6dd46452cac4532d80c3c4a244/pandas-2.3.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2462b1a365b6109d275250baaae7b760fd25c726aaca0054649286bcfbb3e8ec", size = 12048504, upload-time = "2025-09-29T23:29:31.47Z" }, - { url = "https://files.pythonhosted.org/packages/d7/82/b69a1c95df796858777b68fbe6a81d37443a33319761d7c652ce77797475/pandas-2.3.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:0242fe9a49aa8b4d78a4fa03acb397a58833ef6199e9aa40a95f027bb3a1b6e7", size = 11410702, upload-time = "2025-09-29T23:29:54.591Z" }, - { url = "https://files.pythonhosted.org/packages/f9/88/702bde3ba0a94b8c73a0181e05144b10f13f29ebfc2150c3a79062a8195d/pandas-2.3.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a21d830e78df0a515db2b3d2f5570610f5e6bd2e27749770e8bb7b524b89b450", size = 11634535, upload-time = "2025-09-29T23:30:21.003Z" }, - { url = "https://files.pythonhosted.org/packages/a4/1e/1bac1a839d12e6a82ec6cb40cda2edde64a2013a66963293696bbf31fbbb/pandas-2.3.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2e3ebdb170b5ef78f19bfb71b0dc5dc58775032361fa188e814959b74d726dd5", size = 12121582, upload-time = "2025-09-29T23:30:43.391Z" }, - { url = "https://files.pythonhosted.org/packages/44/91/483de934193e12a3b1d6ae7c8645d083ff88dec75f46e827562f1e4b4da6/pandas-2.3.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d051c0e065b94b7a3cea50eb1ec32e912cd96dba41647eb24104b6c6c14c5788", size = 12699963, upload-time = "2025-09-29T23:31:10.009Z" }, - { url = "https://files.pythonhosted.org/packages/70/44/5191d2e4026f86a2a109053e194d3ba7a31a2d10a9c2348368c63ed4e85a/pandas-2.3.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:3869faf4bd07b3b66a9f462417d0ca3a9df29a9f6abd5d0d0dbab15dac7abe87", size = 13202175, upload-time = "2025-09-29T23:31:59.173Z" }, + { name = "tzdata", marker = "sys_platform == 'emscripten' or sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/de/da/b1dc0481ab8d55d0f46e343cfe67d4551a0e14fcee52bd38ca1bd73258d8/pandas-3.0.0.tar.gz", hash = "sha256:0facf7e87d38f721f0af46fe70d97373a37701b1c09f7ed7aeeb292ade5c050f", size = 4633005, upload-time = "2026-01-21T15:52:04.726Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/1e/b184654a856e75e975a6ee95d6577b51c271cd92cb2b020c9378f53e0032/pandas-3.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d64ce01eb9cdca96a15266aa679ae50212ec52757c79204dbc7701a222401850", size = 10313247, upload-time = "2026-01-21T15:50:15.775Z" }, + { url = "https://files.pythonhosted.org/packages/dd/5e/e04a547ad0f0183bf151fd7c7a477468e3b85ff2ad231c566389e6cc9587/pandas-3.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:613e13426069793aa1ec53bdcc3b86e8d32071daea138bbcf4fa959c9cdaa2e2", size = 9913131, upload-time = "2026-01-21T15:50:18.611Z" }, + { url = "https://files.pythonhosted.org/packages/a2/93/bb77bfa9fc2aba9f7204db807d5d3fb69832ed2854c60ba91b4c65ba9219/pandas-3.0.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0192fee1f1a8e743b464a6607858ee4b071deb0b118eb143d71c2a1d170996d5", size = 10741925, upload-time = "2026-01-21T15:50:21.058Z" }, + { url = "https://files.pythonhosted.org/packages/62/fb/89319812eb1d714bfc04b7f177895caeba8ab4a37ef6712db75ed786e2e0/pandas-3.0.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0b853319dec8d5e0c8b875374c078ef17f2269986a78168d9bd57e49bf650ae", size = 11245979, upload-time = "2026-01-21T15:50:23.413Z" }, + { url = "https://files.pythonhosted.org/packages/a9/63/684120486f541fc88da3862ed31165b3b3e12b6a1c7b93be4597bc84e26c/pandas-3.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:707a9a877a876c326ae2cb640fbdc4ef63b0a7b9e2ef55c6df9942dcee8e2af9", size = 11756337, upload-time = "2026-01-21T15:50:25.932Z" }, + { url = "https://files.pythonhosted.org/packages/39/92/7eb0ad232312b59aec61550c3c81ad0743898d10af5df7f80bc5e5065416/pandas-3.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:afd0aa3d0b5cda6e0b8ffc10dbcca3b09ef3cbcd3fe2b27364f85fdc04e1989d", size = 12325517, upload-time = "2026-01-21T15:50:27.952Z" }, + { url = "https://files.pythonhosted.org/packages/51/27/bf9436dd0a4fc3130acec0828951c7ef96a0631969613a9a35744baf27f6/pandas-3.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:113b4cca2614ff7e5b9fee9b6f066618fe73c5a83e99d721ffc41217b2bf57dd", size = 9881576, upload-time = "2026-01-21T15:50:30.149Z" }, + { url = "https://files.pythonhosted.org/packages/e7/2b/c618b871fce0159fd107516336e82891b404e3f340821853c2fc28c7830f/pandas-3.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c14837eba8e99a8da1527c0280bba29b0eb842f64aa94982c5e21227966e164b", size = 9140807, upload-time = "2026-01-21T15:50:32.308Z" }, + { url = "https://files.pythonhosted.org/packages/0b/38/db33686f4b5fa64d7af40d96361f6a4615b8c6c8f1b3d334eee46ae6160e/pandas-3.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9803b31f5039b3c3b10cc858c5e40054adb4b29b4d81cb2fd789f4121c8efbcd", size = 10334013, upload-time = "2026-01-21T15:50:34.771Z" }, + { url = "https://files.pythonhosted.org/packages/a5/7b/9254310594e9774906bacdd4e732415e1f86ab7dbb4b377ef9ede58cd8ec/pandas-3.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:14c2a4099cd38a1d18ff108168ea417909b2dea3bd1ebff2ccf28ddb6a74d740", size = 9874154, upload-time = "2026-01-21T15:50:36.67Z" }, + { url = "https://files.pythonhosted.org/packages/63/d4/726c5a67a13bc66643e66d2e9ff115cead482a44fc56991d0c4014f15aaf/pandas-3.0.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d257699b9a9960e6125686098d5714ac59d05222bef7a5e6af7a7fd87c650801", size = 10384433, upload-time = "2026-01-21T15:50:39.132Z" }, + { url = "https://files.pythonhosted.org/packages/bf/2e/9211f09bedb04f9832122942de8b051804b31a39cfbad199a819bb88d9f3/pandas-3.0.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:69780c98f286076dcafca38d8b8eee1676adf220199c0a39f0ecbf976b68151a", size = 10864519, upload-time = "2026-01-21T15:50:41.043Z" }, + { url = "https://files.pythonhosted.org/packages/00/8d/50858522cdc46ac88b9afdc3015e298959a70a08cd21e008a44e9520180c/pandas-3.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4a66384f017240f3858a4c8a7cf21b0591c3ac885cddb7758a589f0f71e87ebb", size = 11394124, upload-time = "2026-01-21T15:50:43.377Z" }, + { url = "https://files.pythonhosted.org/packages/86/3f/83b2577db02503cd93d8e95b0f794ad9d4be0ba7cb6c8bcdcac964a34a42/pandas-3.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:be8c515c9bc33989d97b89db66ea0cececb0f6e3c2a87fcc8b69443a6923e95f", size = 11920444, upload-time = "2026-01-21T15:50:45.932Z" }, + { url = "https://files.pythonhosted.org/packages/64/2d/4f8a2f192ed12c90a0aab47f5557ece0e56b0370c49de9454a09de7381b2/pandas-3.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:a453aad8c4f4e9f166436994a33884442ea62aa8b27d007311e87521b97246e1", size = 9730970, upload-time = "2026-01-21T15:50:47.962Z" }, + { url = "https://files.pythonhosted.org/packages/d4/64/ff571be435cf1e643ca98d0945d76732c0b4e9c37191a89c8550b105eed1/pandas-3.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:da768007b5a33057f6d9053563d6b74dd6d029c337d93c6d0d22a763a5c2ecc0", size = 9041950, upload-time = "2026-01-21T15:50:50.422Z" }, + { url = "https://files.pythonhosted.org/packages/6f/fa/7f0ac4ca8877c57537aaff2a842f8760e630d8e824b730eb2e859ffe96ca/pandas-3.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b78d646249b9a2bc191040988c7bb524c92fa8534fb0898a0741d7e6f2ffafa6", size = 10307129, upload-time = "2026-01-21T15:50:52.877Z" }, + { url = "https://files.pythonhosted.org/packages/6f/11/28a221815dcea4c0c9414dfc845e34a84a6a7dabc6da3194498ed5ba4361/pandas-3.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bc9cba7b355cb4162442a88ce495e01cb605f17ac1e27d6596ac963504e0305f", size = 9850201, upload-time = "2026-01-21T15:50:54.807Z" }, + { url = "https://files.pythonhosted.org/packages/ba/da/53bbc8c5363b7e5bd10f9ae59ab250fc7a382ea6ba08e4d06d8694370354/pandas-3.0.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c9a1a149aed3b6c9bf246033ff91e1b02d529546c5d6fb6b74a28fea0cf4c70", size = 10354031, upload-time = "2026-01-21T15:50:57.463Z" }, + { url = "https://files.pythonhosted.org/packages/f7/a3/51e02ebc2a14974170d51e2410dfdab58870ea9bcd37cda15bd553d24dc4/pandas-3.0.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95683af6175d884ee89471842acfca29172a85031fccdabc35e50c0984470a0e", size = 10861165, upload-time = "2026-01-21T15:50:59.32Z" }, + { url = "https://files.pythonhosted.org/packages/a5/fe/05a51e3cac11d161472b8297bd41723ea98013384dd6d76d115ce3482f9b/pandas-3.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1fbbb5a7288719e36b76b4f18d46ede46e7f916b6c8d9915b756b0a6c3f792b3", size = 11359359, upload-time = "2026-01-21T15:51:02.014Z" }, + { url = "https://files.pythonhosted.org/packages/ee/56/ba620583225f9b85a4d3e69c01df3e3870659cc525f67929b60e9f21dcd1/pandas-3.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8e8b9808590fa364416b49b2a35c1f4cf2785a6c156935879e57f826df22038e", size = 11912907, upload-time = "2026-01-21T15:51:05.175Z" }, + { url = "https://files.pythonhosted.org/packages/c9/8c/c6638d9f67e45e07656b3826405c5cc5f57f6fd07c8b2572ade328c86e22/pandas-3.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:98212a38a709feb90ae658cb6227ea3657c22ba8157d4b8f913cd4c950de5e7e", size = 9732138, upload-time = "2026-01-21T15:51:07.569Z" }, + { url = "https://files.pythonhosted.org/packages/7b/bf/bd1335c3bf1770b6d8fed2799993b11c4971af93bb1b729b9ebbc02ca2ec/pandas-3.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:177d9df10b3f43b70307a149d7ec49a1229a653f907aa60a48f1877d0e6be3be", size = 9033568, upload-time = "2026-01-21T15:51:09.484Z" }, + { url = "https://files.pythonhosted.org/packages/8e/c6/f5e2171914d5e29b9171d495344097d54e3ffe41d2d85d8115baba4dc483/pandas-3.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2713810ad3806767b89ad3b7b69ba153e1c6ff6d9c20f9c2140379b2a98b6c98", size = 10741936, upload-time = "2026-01-21T15:51:11.693Z" }, + { url = "https://files.pythonhosted.org/packages/51/88/9a0164f99510a1acb9f548691f022c756c2314aad0d8330a24616c14c462/pandas-3.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:15d59f885ee5011daf8335dff47dcb8a912a27b4ad7826dc6cbe809fd145d327", size = 10393884, upload-time = "2026-01-21T15:51:14.197Z" }, + { url = "https://files.pythonhosted.org/packages/e0/53/b34d78084d88d8ae2b848591229da8826d1e65aacf00b3abe34023467648/pandas-3.0.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24e6547fb64d2c92665dd2adbfa4e85fa4fd70a9c070e7cfb03b629a0bbab5eb", size = 10310740, upload-time = "2026-01-21T15:51:16.093Z" }, + { url = "https://files.pythonhosted.org/packages/5b/d3/bee792e7c3d6930b74468d990604325701412e55d7aaf47460a22311d1a5/pandas-3.0.0-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:48ee04b90e2505c693d3f8e8f524dab8cb8aaf7ddcab52c92afa535e717c4812", size = 10700014, upload-time = "2026-01-21T15:51:18.818Z" }, + { url = "https://files.pythonhosted.org/packages/55/db/2570bc40fb13aaed1cbc3fbd725c3a60ee162477982123c3adc8971e7ac1/pandas-3.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:66f72fb172959af42a459e27a8d8d2c7e311ff4c1f7db6deb3b643dbc382ae08", size = 11323737, upload-time = "2026-01-21T15:51:20.784Z" }, + { url = "https://files.pythonhosted.org/packages/bc/2e/297ac7f21c8181b62a4cccebad0a70caf679adf3ae5e83cb676194c8acc3/pandas-3.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4a4a400ca18230976724a5066f20878af785f36c6756e498e94c2a5e5d57779c", size = 11771558, upload-time = "2026-01-21T15:51:22.977Z" }, + { url = "https://files.pythonhosted.org/packages/0a/46/e1c6876d71c14332be70239acce9ad435975a80541086e5ffba2f249bcf6/pandas-3.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:940eebffe55528074341a5a36515f3e4c5e25e958ebbc764c9502cfc35ba3faa", size = 10473771, upload-time = "2026-01-21T15:51:25.285Z" }, + { url = "https://files.pythonhosted.org/packages/c0/db/0270ad9d13c344b7a36fa77f5f8344a46501abf413803e885d22864d10bf/pandas-3.0.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:597c08fb9fef0edf1e4fa2f9828dd27f3d78f9b8c9b4a748d435ffc55732310b", size = 10312075, upload-time = "2026-01-21T15:51:28.5Z" }, + { url = "https://files.pythonhosted.org/packages/09/9f/c176f5e9717f7c91becfe0f55a52ae445d3f7326b4a2cf355978c51b7913/pandas-3.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:447b2d68ac5edcbf94655fe909113a6dba6ef09ad7f9f60c80477825b6c489fe", size = 9900213, upload-time = "2026-01-21T15:51:30.955Z" }, + { url = "https://files.pythonhosted.org/packages/d9/e7/63ad4cc10b257b143e0a5ebb04304ad806b4e1a61c5da25f55896d2ca0f4/pandas-3.0.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:debb95c77ff3ed3ba0d9aa20c3a2f19165cc7956362f9873fce1ba0a53819d70", size = 10428768, upload-time = "2026-01-21T15:51:33.018Z" }, + { url = "https://files.pythonhosted.org/packages/9e/0e/4e4c2d8210f20149fd2248ef3fff26623604922bd564d915f935a06dd63d/pandas-3.0.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fedabf175e7cd82b69b74c30adbaa616de301291a5231138d7242596fc296a8d", size = 10882954, upload-time = "2026-01-21T15:51:35.287Z" }, + { url = "https://files.pythonhosted.org/packages/c6/60/c9de8ac906ba1f4d2250f8a951abe5135b404227a55858a75ad26f84db47/pandas-3.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:412d1a89aab46889f3033a386912efcdfa0f1131c5705ff5b668dda88305e986", size = 11430293, upload-time = "2026-01-21T15:51:37.57Z" }, + { url = "https://files.pythonhosted.org/packages/a1/69/806e6637c70920e5787a6d6896fd707f8134c2c55cd761e7249a97b7dc5a/pandas-3.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e979d22316f9350c516479dd3a92252be2937a9531ed3a26ec324198a99cdd49", size = 11952452, upload-time = "2026-01-21T15:51:39.618Z" }, + { url = "https://files.pythonhosted.org/packages/cb/de/918621e46af55164c400ab0ef389c9d969ab85a43d59ad1207d4ddbe30a5/pandas-3.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:083b11415b9970b6e7888800c43c82e81a06cd6b06755d84804444f0007d6bb7", size = 9851081, upload-time = "2026-01-21T15:51:41.758Z" }, + { url = "https://files.pythonhosted.org/packages/91/a1/3562a18dd0bd8c73344bfa26ff90c53c72f827df119d6d6b1dacc84d13e3/pandas-3.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:5db1e62cb99e739fa78a28047e861b256d17f88463c76b8dafc7c1338086dca8", size = 9174610, upload-time = "2026-01-21T15:51:44.312Z" }, + { url = "https://files.pythonhosted.org/packages/ce/26/430d91257eaf366f1737d7a1c158677caaf6267f338ec74e3a1ec444111c/pandas-3.0.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:697b8f7d346c68274b1b93a170a70974cdc7d7354429894d5927c1effdcccd73", size = 10761999, upload-time = "2026-01-21T15:51:46.899Z" }, + { url = "https://files.pythonhosted.org/packages/ec/1a/954eb47736c2b7f7fe6a9d56b0cb6987773c00faa3c6451a43db4beb3254/pandas-3.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8cb3120f0d9467ed95e77f67a75e030b67545bcfa08964e349252d674171def2", size = 10410279, upload-time = "2026-01-21T15:51:48.89Z" }, + { url = "https://files.pythonhosted.org/packages/20/fc/b96f3a5a28b250cd1b366eb0108df2501c0f38314a00847242abab71bb3a/pandas-3.0.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:33fd3e6baa72899746b820c31e4b9688c8e1b7864d7aec2de7ab5035c285277a", size = 10330198, upload-time = "2026-01-21T15:51:51.015Z" }, + { url = "https://files.pythonhosted.org/packages/90/b3/d0e2952f103b4fbef1ef22d0c2e314e74fc9064b51cee30890b5e3286ee6/pandas-3.0.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8942e333dc67ceda1095227ad0febb05a3b36535e520154085db632c40ad084", size = 10728513, upload-time = "2026-01-21T15:51:53.387Z" }, + { url = "https://files.pythonhosted.org/packages/76/81/832894f286df828993dc5fd61c63b231b0fb73377e99f6c6c369174cf97e/pandas-3.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:783ac35c4d0fe0effdb0d67161859078618b1b6587a1af15928137525217a721", size = 11345550, upload-time = "2026-01-21T15:51:55.329Z" }, + { url = "https://files.pythonhosted.org/packages/34/a0/ed160a00fb4f37d806406bc0a79a8b62fe67f29d00950f8d16203ff3409b/pandas-3.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:125eb901e233f155b268bbef9abd9afb5819db74f0e677e89a61b246228c71ac", size = 11799386, upload-time = "2026-01-21T15:51:57.457Z" }, + { url = "https://files.pythonhosted.org/packages/36/c8/2ac00d7255252c5e3cf61b35ca92ca25704b0188f7454ca4aec08a33cece/pandas-3.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b86d113b6c109df3ce0ad5abbc259fe86a1bd4adfd4a31a89da42f84f65509bb", size = 10873041, upload-time = "2026-01-21T15:52:00.034Z" }, + { url = "https://files.pythonhosted.org/packages/e6/3f/a80ac00acbc6b35166b42850e98a4f466e2c0d9c64054161ba9620f95680/pandas-3.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:1c39eab3ad38f2d7a249095f0a3d8f8c22cc0f847e98ccf5bbe732b272e2d9fa", size = 9441003, upload-time = "2026-01-21T15:52:02.281Z" }, ] [[package]] name = "pathspec" -version = "0.12.1" +version = "1.0.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043, upload-time = "2023-12-10T22:30:45Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/36/e27608899f9b8d4dff0617b2d9ab17ca5608956ca44461ac14ac48b44015/pathspec-1.0.4.tar.gz", hash = "sha256:0210e2ae8a21a9137c0d470578cb0e595af87edaa6ebf12ff176f14a02e0e645", size = 131200, upload-time = "2026-01-27T03:59:46.938Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" }, + { url = "https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl", hash = "sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723", size = 55206, upload-time = "2026-01-27T03:59:45.137Z" }, ] [[package]] @@ -1981,11 +2040,11 @@ wheels = [ [[package]] name = "platformdirs" -version = "4.5.0" +version = "4.9.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/61/33/9611380c2bdb1225fdef633e2a9610622310fed35ab11dac9620972ee088/platformdirs-4.5.0.tar.gz", hash = "sha256:70ddccdd7c99fc5942e9fc25636a8b34d04c24b335100223152c2803e4063312", size = 21632, upload-time = "2025-10-08T17:44:48.791Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/04/fea538adf7dbbd6d186f551d595961e564a3b6715bdf276b477460858672/platformdirs-4.9.2.tar.gz", hash = "sha256:9a33809944b9db043ad67ca0db94b14bf452cc6aeaac46a88ea55b26e2e9d291", size = 28394, upload-time = "2026-02-16T03:56:10.574Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3", size = 18651, upload-time = "2025-10-08T17:44:47.223Z" }, + { url = "https://files.pythonhosted.org/packages/48/31/05e764397056194206169869b50cf2fee4dbbbc71b344705b9c0d878d4d8/platformdirs-4.9.2-py3-none-any.whl", hash = "sha256:9170634f126f8efdae22fb58ae8a0eaa86f38365bc57897a6c4f781d1f5875bd", size = 21168, upload-time = "2026-02-16T03:56:08.891Z" }, ] [[package]] @@ -1999,7 +2058,7 @@ wheels = [ [[package]] name = "pre-commit" -version = "4.5.0" +version = "4.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cfgv" }, @@ -2008,9 +2067,9 @@ dependencies = [ { name = "pyyaml" }, { name = "virtualenv" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f4/9b/6a4ffb4ed980519da959e1cf3122fc6cb41211daa58dbae1c73c0e519a37/pre_commit-4.5.0.tar.gz", hash = "sha256:dc5a065e932b19fc1d4c653c6939068fe54325af8e741e74e88db4d28a4dd66b", size = 198428, upload-time = "2025-11-22T21:02:42.304Z" } +sdist = { url = "https://files.pythonhosted.org/packages/40/f1/6d86a29246dfd2e9b6237f0b5823717f60cad94d47ddc26afa916d21f525/pre_commit-4.5.1.tar.gz", hash = "sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61", size = 198232, upload-time = "2025-12-16T21:14:33.552Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5d/c4/b2d28e9d2edf4f1713eb3c29307f1a63f3d67cf09bdda29715a36a68921a/pre_commit-4.5.0-py2.py3-none-any.whl", hash = "sha256:25e2ce09595174d9c97860a95609f9f852c0614ba602de3561e267547f2335e1", size = 226429, upload-time = "2025-11-22T21:02:40.836Z" }, + { url = "https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77", size = 226437, upload-time = "2025-12-16T21:14:32.409Z" }, ] [[package]] @@ -2151,16 +2210,16 @@ wheels = [ [[package]] name = "pydantic-settings" -version = "2.12.0" +version = "2.13.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pydantic" }, { name = "python-dotenv" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/43/4b/ac7e0aae12027748076d72a8764ff1c9d82ca75a7a52622e67ed3f765c54/pydantic_settings-2.12.0.tar.gz", hash = "sha256:005538ef951e3c2a68e1c08b292b5f2e71490def8589d4221b95dab00dafcfd0", size = 194184, upload-time = "2025-11-10T14:25:47.013Z" } +sdist = { url = "https://files.pythonhosted.org/packages/96/a1/ae859ffac5a3338a66b74c5e29e244fd3a3cc483c89feaf9f56c39898d75/pydantic_settings-2.13.0.tar.gz", hash = "sha256:95d875514610e8595672800a5c40b073e99e4aae467fa7c8f9c263061ea2e1fe", size = 222450, upload-time = "2026-02-15T12:11:23.476Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/60/5d4751ba3f4a40a6891f24eec885f51afd78d208498268c734e256fb13c4/pydantic_settings-2.12.0-py3-none-any.whl", hash = "sha256:fddb9fd99a5b18da837b29710391e945b1e30c135477f484084ee513adb93809", size = 51880, upload-time = "2025-11-10T14:25:45.546Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1a/dd1b9d7e627486cf8e7523d09b70010e05a4bc41414f4ae6ce184cf0afb6/pydantic_settings-2.13.0-py3-none-any.whl", hash = "sha256:d67b576fff39cd086b595441bf9c75d4193ca9c0ed643b90360694d0f1240246", size = 58429, upload-time = "2026-02-15T12:11:22.133Z" }, ] [[package]] @@ -2174,11 +2233,11 @@ wheels = [ [[package]] name = "pyjwt" -version = "2.10.1" +version = "2.11.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785, upload-time = "2024-11-28T03:43:29.933Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/5a/b46fa56bf322901eee5b0454a34343cdbdae202cd421775a8ee4e42fd519/pyjwt-2.11.0.tar.gz", hash = "sha256:35f95c1f0fbe5d5ba6e43f00271c275f7a1a4db1dab27bf708073b75318ea623", size = 98019, upload-time = "2026-01-30T19:59:55.694Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997, upload-time = "2024-11-28T03:43:27.893Z" }, + { url = "https://files.pythonhosted.org/packages/6f/01/c26ce75ba460d5cd503da9e13b21a33804d38c2165dec7b716d06b13010c/pyjwt-2.11.0-py3-none-any.whl", hash = "sha256:94a6bde30eb5c8e04fee991062b534071fd1439ef58d2adc9ccb823e7bcd0469", size = 28224, upload-time = "2026-01-30T19:59:54.539Z" }, ] [package.optional-dependencies] @@ -2188,30 +2247,30 @@ crypto = [ [[package]] name = "pymdown-extensions" -version = "10.17.2" +version = "10.21" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown" }, { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/25/6d/af5378dbdb379fddd9a277f8b9888c027db480cde70028669ebd009d642a/pymdown_extensions-10.17.2.tar.gz", hash = "sha256:26bb3d7688e651606260c90fb46409fbda70bf9fdc3623c7868643a1aeee4713", size = 847344, upload-time = "2025-11-26T15:43:57.004Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/63/06673d1eb6d8f83c0ea1f677d770e12565fb516928b4109c9e2055656a9e/pymdown_extensions-10.21.tar.gz", hash = "sha256:39f4a020f40773f6b2ff31d2cd2546c2c04d0a6498c31d9c688d2be07e1767d5", size = 853363, upload-time = "2026-02-15T20:44:06.748Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/93/78/b93cb80bd673bdc9f6ede63d8eb5b4646366953df15667eb3603be57a2b1/pymdown_extensions-10.17.2-py3-none-any.whl", hash = "sha256:bffae79a2e8b9e44aef0d813583a8fea63457b7a23643a43988055b7b79b4992", size = 266556, upload-time = "2025-11-26T15:43:55.162Z" }, + { url = "https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl", hash = "sha256:91b879f9f864d49794c2d9534372b10150e6141096c3908a455e45ca72ad9d3f", size = 268877, upload-time = "2026-02-15T20:44:05.464Z" }, ] [[package]] name = "pymupdf" -version = "1.26.7" +version = "1.27.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/48/d6/09b28f027b510838559f7748807192149c419b30cb90e6d5f0cf916dc9dc/pymupdf-1.26.7.tar.gz", hash = "sha256:71add8bdc8eb1aaa207c69a13400693f06ad9b927bea976f5d5ab9df0bb489c3", size = 84327033, upload-time = "2025-12-11T21:48:50.694Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/0c/40dda0cc4bd2220a2ef75f8c53dd7d8ed1e29681fcb3df75db6ee9677a7e/pymupdf-1.27.1.tar.gz", hash = "sha256:4afbde0769c336717a149ab0de3330dcb75378f795c1a8c5af55c1a628b17d55", size = 85303479, upload-time = "2026-02-12T08:29:17.682Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/94/35/cd74cea1787b2247702ef8522186bdef32e9cb30a099e6bb864627ef6045/pymupdf-1.26.7-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:07085718dfdae5ab83b05eb5eb397f863bcc538fe05135318a01ea353e7a1353", size = 23179369, upload-time = "2025-12-11T21:47:21.587Z" }, - { url = "https://files.pythonhosted.org/packages/72/74/448b6172927c829c6a3fba80078d7b0a016ebbe2c9ee528821f5ea21677a/pymupdf-1.26.7-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:31aa9c8377ea1eea02934b92f4dcf79fb2abba0bf41f8a46d64c3e31546a3c02", size = 22470101, upload-time = "2025-12-11T21:47:37.105Z" }, - { url = "https://files.pythonhosted.org/packages/65/e7/47af26f3ac76be7ac3dd4d6cc7ee105948a8355d774e5ca39857bf91c11c/pymupdf-1.26.7-cp310-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e419b609996434a14a80fa060adec72c434a1cca6a511ec54db9841bc5d51b3c", size = 23502486, upload-time = "2025-12-12T09:51:25.824Z" }, - { url = "https://files.pythonhosted.org/packages/2a/6b/3de1714d734ff949be1e90a22375d0598d3540b22ae73eb85c2d7d1f36a9/pymupdf-1.26.7-cp310-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:69dfc78f206a96e5b3ac22741263ebab945fdf51f0dbe7c5757c3511b23d9d72", size = 24115727, upload-time = "2025-12-11T21:47:51.274Z" }, - { url = "https://files.pythonhosted.org/packages/62/9b/f86224847949577a523be2207315ae0fd3155b5d909cd66c274d095349a3/pymupdf-1.26.7-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1d5106f46e1ca0d64d46bd51892372a4f82076bdc14a9678d33d630702abca36", size = 24324386, upload-time = "2025-12-12T14:58:45.483Z" }, - { url = "https://files.pythonhosted.org/packages/85/8e/a117d39092ca645fde8b903f4a941d9aa75b370a67b4f1f435f56393dc5a/pymupdf-1.26.7-cp310-abi3-win32.whl", hash = "sha256:7c9645b6f5452629c747690190350213d3e5bbdb6b2eca227d82702b327f6eee", size = 17203888, upload-time = "2025-12-12T13:59:57.613Z" }, - { url = "https://files.pythonhosted.org/packages/dd/c3/d0047678146c294469c33bae167c8ace337deafb736b0bf97b9bc481aa65/pymupdf-1.26.7-cp310-abi3-win_amd64.whl", hash = "sha256:425b1befe40d41b72eb0fe211711c7ae334db5eb60307e9dd09066ed060cceba", size = 18405952, upload-time = "2025-12-11T21:48:02.947Z" }, + { url = "https://files.pythonhosted.org/packages/13/19/fde6ea4712a904b65e8f41124a0e4233879b87a770fe6a8ce857964de6d5/pymupdf-1.27.1-cp310-abi3-macosx_10_9_x86_64.whl", hash = "sha256:bee9f95512f9556dbf2cacfd1413c61b29a55baa07fa7f8fc83d221d8419888a", size = 23986707, upload-time = "2026-02-11T15:03:24.025Z" }, + { url = "https://files.pythonhosted.org/packages/75/c2/070dff91ad3f1bc16fd6c6ceff23495601fcce4c92d28be534417596418a/pymupdf-1.27.1-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:3de95a0889395b0966fafd11b94980b7543a816e89dd1c218597a08543ac3415", size = 23263493, upload-time = "2026-02-11T15:03:45.528Z" }, + { url = "https://files.pythonhosted.org/packages/8e/db/937377f4b3e0fbf6273c17436a49f7db17df1a46b1be9e26653b6fafc0e1/pymupdf-1.27.1-cp310-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:2c9d9353b840040cbc724341f4095fb7e2cc1a12a9147d0ec1a0a79f5d773147", size = 24317651, upload-time = "2026-02-11T22:33:38.967Z" }, + { url = "https://files.pythonhosted.org/packages/72/d5/c701cf2d0cdd6e5d6bca3ca9188d7f5d7ce3ae67dd1368d658cd4bae2707/pymupdf-1.27.1-cp310-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:aeaed76e72cbc061149a825ab0811c5f4752970c56591c2938c5042ec06b26e1", size = 24945742, upload-time = "2026-02-11T15:04:06.21Z" }, + { url = "https://files.pythonhosted.org/packages/2b/29/690202b38b93cf77b73a29c25a63a2b6f3fcb36b1f75006e50b8dee7c108/pymupdf-1.27.1-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4f1837554134fb45d390a44de8844b2ca9b6c901c82ccc90b340e3b7f3b126ca", size = 25167965, upload-time = "2026-02-11T22:36:35.478Z" }, + { url = "https://files.pythonhosted.org/packages/8a/81/f937e6aa606fd263c3a45d0ff0f0bbdbf3fb779933091fc0f6179513cc93/pymupdf-1.27.1-cp310-abi3-win32.whl", hash = "sha256:fa33b512d82c6c4852edadf57f22d5f27d16243bb33dac0fbe4eb0f281c5b17e", size = 18006253, upload-time = "2026-02-12T13:48:07.129Z" }, + { url = "https://files.pythonhosted.org/packages/3e/99/fe4a7752990bf65277718fffbead4478de9afd1c7288d7a6d643f79a6fa7/pymupdf-1.27.1-cp310-abi3-win_amd64.whl", hash = "sha256:4b6268dff3a9d713034eba5c2ffce0da37c62443578941ac5df433adcde57b2f", size = 19236703, upload-time = "2026-02-11T15:04:19.607Z" }, ] [[package]] @@ -2225,27 +2284,36 @@ wheels = [ [[package]] name = "pypdfium2" -version = "5.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1c/87/56782107fa242137b77ccddc30519bbb33e7a9eed9da9649d9db45db2c64/pypdfium2-5.1.0.tar.gz", hash = "sha256:46335ca30a1584b804a6824da84d2e846b4b954bdfc342d035b7bf15ed9a14e5", size = 270104, upload-time = "2025-11-23T13:36:52.589Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/d7/46ce255322cd29f0db3772667a0da3db8ed137e1e9b9aa306ac5691765b3/pypdfium2-5.1.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f3dde94d320d582d3c20255b600f1e7e03261bfdea139b7064b54126fc3db4e2", size = 2817789, upload-time = "2025-11-23T13:36:31.423Z" }, - { url = "https://files.pythonhosted.org/packages/19/a5/4ad3c1b336fdc2b7a88d835c56bcd64ce60d4a95d1a9eaafc44f853da582/pypdfium2-5.1.0-py3-none-macosx_11_0_x86_64.whl", hash = "sha256:dee09b7a3ab1860a17decc97c179a5aaba5a74b2780d53c91daa18d742945892", size = 2940861, upload-time = "2025-11-23T13:36:33.519Z" }, - { url = "https://files.pythonhosted.org/packages/19/93/d13ca66d5e075d7e27736c51c15955cdd3266ac0a8327613c3c520d43693/pypdfium2-5.1.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1757d6470cbf5b8d1c825350df2ccd79fd0bfcf5753ff566fd02153a486014b1", size = 2980933, upload-time = "2025-11-23T13:36:35.283Z" }, - { url = "https://files.pythonhosted.org/packages/a2/7c/02744ef9e0363af08f9ed47c0e603ef8713e02d4a48492c76d5bf36f65c3/pypdfium2-5.1.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ad18e95497423f88b33f2976cb78c27f0bd6ef4b4bf340c901f5f28a234c4f06", size = 2762960, upload-time = "2025-11-23T13:36:37.033Z" }, - { url = "https://files.pythonhosted.org/packages/89/26/f0abcfccb99b0a5c4451b70b0e72ccb7c27387931af01eae982870272202/pypdfium2-5.1.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2faee2f4fbd5bd33dd77c07d15ccaa6687562d883a54c4beb8329ebaee615b7d", size = 3060522, upload-time = "2025-11-23T13:36:38.835Z" }, - { url = "https://files.pythonhosted.org/packages/2f/74/92f508e71178aa85de32454762f84d6f9cef35c468caab3e0f1041dae464/pypdfium2-5.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d688372df169a9dad606c1e5ad34b6e0e6b820f1e0d540b4780711600a7bf8dd", size = 2995178, upload-time = "2025-11-23T13:36:40.319Z" }, - { url = "https://files.pythonhosted.org/packages/94/9f/91ca099ea64b24e19ef05da72e33d0ef0840e104d89cbdcb618da12629b5/pypdfium2-5.1.0-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:cfecd2b20f1c05027aaa2af6bfbcc2835b4c8f6455155b0dc2800ec6a2051965", size = 6321704, upload-time = "2025-11-23T13:36:42.177Z" }, - { url = "https://files.pythonhosted.org/packages/e0/4b/5628cfda9f534b3acc1e2cf50f9e9582cd9cfd86cf2ce718da229de6e709/pypdfium2-5.1.0-py3-none-musllinux_1_1_i686.whl", hash = "sha256:5698de8e6d662f1b2cdff5cb62e6f0ee79ffaaa13e282251854cbc64cf712449", size = 6329892, upload-time = "2025-11-23T13:36:43.757Z" }, - { url = "https://files.pythonhosted.org/packages/c5/25/5d2db765f8f82129d75ea2883ed26af3d1a64d8daaa20a11005ac681e2c3/pypdfium2-5.1.0-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:2cbd73093fbb1710ea1164cdf27583363e1b663b8cc22d555c84af0ee1af50c7", size = 6409889, upload-time = "2025-11-23T13:36:45.387Z" }, - { url = "https://files.pythonhosted.org/packages/89/d3/135ed8ca46044cd5005cd104ead13bea417777afa65d7af5a710eb68d340/pypdfium2-5.1.0-py3-none-win32.whl", hash = "sha256:11d319cd2e5f71cdc3d68e8a79142b559a0edbcc16fe31d4036fcfc45f0e9ed8", size = 2991546, upload-time = "2025-11-23T13:36:47.373Z" }, - { url = "https://files.pythonhosted.org/packages/52/8f/884a1b2fd7c747a98e9b4c95097c08b39d042a88837ac72f2945a7f6162c/pypdfium2-5.1.0-py3-none-win_amd64.whl", hash = "sha256:4725f347a8c9ff011a7035d8267ee25912ab1b946034ba0b57f3cca89de8847a", size = 3100176, upload-time = "2025-11-23T13:36:49.234Z" }, - { url = "https://files.pythonhosted.org/packages/d7/5c/72448636ea0ccd44878f77bb5d59a2c967a54eec806ee2e0d894ef0d2434/pypdfium2-5.1.0-py3-none-win_arm64.whl", hash = "sha256:47c5593f7eb6ae0f1e5a940d712d733ede580f09ca91de6c3f89611848695c0f", size = 2941500, upload-time = "2025-11-23T13:36:50.69Z" }, +version = "5.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/99/23/b3979a1d4f536fabce02e3d9f332e8aeeed064d9df9391f2a77160f4ab36/pypdfium2-5.4.0.tar.gz", hash = "sha256:7219e55048fb3999fc8adcaea467088507207df4676ff9e521a3ae15a67d99c4", size = 269136, upload-time = "2026-02-08T16:54:08.383Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/c0/3d707bff5e973272b5412556d19e8c6889ce859a235465f0049cc8d35bc3/pypdfium2-5.4.0-py3-none-android_23_arm64_v8a.whl", hash = "sha256:8bc51a12a8c8eabbdbd7499d3e5ec47bcf56ba18e07b52bdd07d321cc1252c90", size = 2759769, upload-time = "2026-02-08T16:53:32.985Z" }, + { url = "https://files.pythonhosted.org/packages/1b/6b/306cafcb0b18d5fab41687d9ed76eabea86a9ff78bc568bee1bfa34e526d/pypdfium2-5.4.0-py3-none-android_23_armeabi_v7a.whl", hash = "sha256:a414ef5b685824cc6c7acbe19b7dbc735de2023cf473321a8ebfe8d7f5d8a41f", size = 2301913, upload-time = "2026-02-08T16:53:35.026Z" }, + { url = "https://files.pythonhosted.org/packages/7a/37/3d737c7eb84fb22939ab0a643aa0183dbc0745c309e962b4d61eeff8211b/pypdfium2-5.4.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:0e83657db8da5971434ff5683bf3faa007ee1f3a56b61f245b8aa5b60442c23a", size = 2814181, upload-time = "2026-02-08T16:53:36.481Z" }, + { url = "https://files.pythonhosted.org/packages/96/d7/0895737ec3d95ad607ade42e98fa8868b91e35b1170ec39b8c1b5fdb124c/pypdfium2-5.4.0-py3-none-macosx_11_0_x86_64.whl", hash = "sha256:e42b1d14db642e96bb3a57167f620b4247e9c843d22b9fb569b16a7c35a18f47", size = 2943476, upload-time = "2026-02-08T16:53:37.992Z" }, + { url = "https://files.pythonhosted.org/packages/9a/53/f8ab449997d3efa52737b8e6c494f1c3f09dc0642161fadc934f16a57cf0/pypdfium2-5.4.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0698c9a002f839127e74ec0185147e08b64e47a1e6caeaee95df434c05b26e8c", size = 2976675, upload-time = "2026-02-08T16:53:39.923Z" }, + { url = "https://files.pythonhosted.org/packages/c6/28/b8a4d4c1557019101bb722c88ba532ec9c14640117ab1c272c80774d83d7/pypdfium2-5.4.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:22e9d4c73fc48b18b022977ea6fe78df43adf95440e1135020ed35fea9595017", size = 2762396, upload-time = "2026-02-08T16:53:41.958Z" }, + { url = "https://files.pythonhosted.org/packages/0b/4a/6c765f6e0b69d792e2d4c7ef2359301896c82df265d60f9a56e87618ec50/pypdfium2-5.4.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f0619f8a8ae3eb71b2cdc1fbd2a8f5d43f0fc6bee66d1b3aac2c9c23e44a3bf", size = 3068559, upload-time = "2026-02-08T16:53:43.974Z" }, + { url = "https://files.pythonhosted.org/packages/1c/17/4464e4ab6dd98ac3783c10eb799d8da49cb551a769c987eb9c6ba72a5ccf/pypdfium2-5.4.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50124415d815c41de8ce7e21cee5450f74f6f1240a140573bb71ccac804d5e5f", size = 3419384, upload-time = "2026-02-08T16:53:46.041Z" }, + { url = "https://files.pythonhosted.org/packages/92/08/fa315a2ab353b41501b7088be72dc6cf8ad2bd4f1ebdfdb90c41b7f29155/pypdfium2-5.4.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce482d76e5447e745d761307401eaa366616ca44032b86cf7fbe6be918ade64e", size = 2998123, upload-time = "2026-02-08T16:53:47.705Z" }, + { url = "https://files.pythonhosted.org/packages/02/7a/a171d313d54a028d9437dea2c5d07fc9e1592f4daf5c39cbf514fca75242/pypdfium2-5.4.0-py3-none-manylinux_2_27_s390x.manylinux_2_28_s390x.whl", hash = "sha256:16b9c6b07f3dbe7eda209bf7aaf131ca9614e1dae527e9764180dd58bcbaf411", size = 3673594, upload-time = "2026-02-08T16:53:49.139Z" }, + { url = "https://files.pythonhosted.org/packages/0b/c0/60416f011f7e5a4ca29f40ae94907f34975239f3c6dd7fcb51f99e110f3b/pypdfium2-5.4.0-py3-none-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:3b08d48b7cca3b51aefaad7855bc0e9e251432a6eef1356d532ff438be84855e", size = 2965025, upload-time = "2026-02-08T16:53:50.553Z" }, + { url = "https://files.pythonhosted.org/packages/75/e2/8e36144b5e933c707b6aeab7dc6638eee8208697925b48b5b78ef68fb52a/pypdfium2-5.4.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0a1526e2a2bde7f2f13bec0f471d9fd475f7bbac2c0c860d48c35af8394d5931", size = 4130551, upload-time = "2026-02-08T16:53:52.71Z" }, + { url = "https://files.pythonhosted.org/packages/a0/64/8cda96259a8fdecd457f5d14a9d650315d7bdf496f96055d1d55900b3881/pypdfium2-5.4.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:40cea0bceb1e60a71b3855e2b04d175d2199b7da06212bb80f0c78067d065810", size = 3746587, upload-time = "2026-02-08T16:53:54.219Z" }, + { url = "https://files.pythonhosted.org/packages/33/6b/7764491269f188a922bd6b254359d718899fc3092c90f0f68c2f6e451921/pypdfium2-5.4.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:7a116f8fbeae7aa3a18ff2d1fa331ac647831cc16b589d4fbbbb66d64ecc8793", size = 4336703, upload-time = "2026-02-08T16:53:56.18Z" }, + { url = "https://files.pythonhosted.org/packages/87/b0/2484bd3c20ead51ecea2082deaf94a3e91bad709fa14f049ca7fb598dc9a/pypdfium2-5.4.0-py3-none-musllinux_1_2_ppc64le.whl", hash = "sha256:55c7fc894718db5fa2981d46dee45fe3a4fcd60d26f5095ad8f7779600fa8b6f", size = 4375051, upload-time = "2026-02-08T16:53:57.804Z" }, + { url = "https://files.pythonhosted.org/packages/c0/ac/5f0536be885c3cadc09422de0324a193a21c165488a574029d9d2db92ecb/pypdfium2-5.4.0-py3-none-musllinux_1_2_riscv64.whl", hash = "sha256:dfc1c0c7e6e7ba258ebb338aaf664eb933bff1854cda76e4ee530886ea39b31a", size = 3928935, upload-time = "2026-02-08T16:53:59.265Z" }, + { url = "https://files.pythonhosted.org/packages/13/b9/693b665df0939555491bece0777cafda1270e208734e925006de313abb5b/pypdfium2-5.4.0-py3-none-musllinux_1_2_s390x.whl", hash = "sha256:4c0a48ede7180f804c029c509c2b6ea0c66813a3fde9eb9afc390183f947164d", size = 4997642, upload-time = "2026-02-08T16:54:00.809Z" }, + { url = "https://files.pythonhosted.org/packages/fb/ea/ba585acdfbefe309ee2fe5ebfeb097e36abe1d33c2a5108828c493c070bb/pypdfium2-5.4.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:dea22d15c44a275702fd95ad664ba6eaa3c493d53d58b4d69272a04bdfb0df70", size = 4179914, upload-time = "2026-02-08T16:54:02.264Z" }, + { url = "https://files.pythonhosted.org/packages/97/47/238383e89081a0ed1ca2bf4ef44f7e512fa0c72ffc51adc7df83bfcfd9b9/pypdfium2-5.4.0-py3-none-win32.whl", hash = "sha256:35c643827ed0f4dae9cedf3caf836f94cba5b31bd2c115b80a7c85f004636de9", size = 2995844, upload-time = "2026-02-08T16:54:03.692Z" }, + { url = "https://files.pythonhosted.org/packages/08/37/f1338a0600c6c6e31759f8f80d7ab20aa0bc43b11594da67091300e051d4/pypdfium2-5.4.0-py3-none-win_amd64.whl", hash = "sha256:f9d9ce3c6901294d6984004d4a797dea110f8248b1bde33a823d25b45d3c2685", size = 3104198, upload-time = "2026-02-08T16:54:05.304Z" }, + { url = "https://files.pythonhosted.org/packages/65/17/18ad82f070da18ab970928f730fbd44d9b05aafcb52a2ebb6470eaae53f9/pypdfium2-5.4.0-py3-none-win_arm64.whl", hash = "sha256:2b78ea216fb92e7709b61c46241ebf2cc0c60cf18ad2fb4633af665d7b4e21e6", size = 2938727, upload-time = "2026-02-08T16:54:06.814Z" }, ] [[package]] name = "pytest" -version = "9.0.1" +version = "9.0.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, @@ -2254,9 +2322,9 @@ dependencies = [ { name = "pluggy" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/07/56/f013048ac4bc4c1d9be45afd4ab209ea62822fb1598f40687e6bf45dcea4/pytest-9.0.1.tar.gz", hash = "sha256:3e9c069ea73583e255c3b21cf46b8d3c56f6e3a1a8f6da94ccb0fcf57b9d73c8", size = 1564125, upload-time = "2025-11-12T13:05:09.333Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/8b/6300fb80f858cda1c51ffa17075df5d846757081d11ab4aa35cef9e6258b/pytest-9.0.1-py3-none-any.whl", hash = "sha256:67be0030d194df2dfa7b556f2e56fb3c3315bd5c8822c6951162b92b32ce7dad", size = 373668, upload-time = "2025-11-12T13:05:07.379Z" }, + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, ] [[package]] @@ -2324,15 +2392,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/26/a4/2f2def0378b44f913d2d6cb3bc5b1a15267b363937ab1cb9afb07ce2313c/python_toon-0.1.3-py3-none-any.whl", hash = "sha256:a27b0ee4a729e730d1037d0a63eb8b344b3e5a26e3dc9a173067b6c31a868ee6", size = 21797, upload-time = "2025-11-04T09:12:19.443Z" }, ] -[[package]] -name = "pytz" -version = "2025.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, -] - [[package]] name = "pywin32" version = "311" @@ -2463,15 +2522,15 @@ wheels = [ [[package]] name = "rich" -version = "14.2.0" +version = "14.3.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown-it-py" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fb/d2/8920e102050a0de7bfabeb4c4614a49248cf8d5d7a8d01885fbb24dc767a/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4", size = 219990, upload-time = "2025-10-09T14:16:53.064Z" } +sdist = { url = "https://files.pythonhosted.org/packages/74/99/a4cab2acbb884f80e558b0771e97e21e939c5dfb460f488d19df485e8298/rich-14.3.2.tar.gz", hash = "sha256:e712f11c1a562a11843306f5ed999475f09ac31ffb64281f73ab29ffdda8b3b8", size = 230143, upload-time = "2026-02-01T16:20:47.908Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd", size = 243393, upload-time = "2025-10-09T14:16:51.245Z" }, + { url = "https://files.pythonhosted.org/packages/ef/45/615f5babd880b4bd7d405cc0dc348234c5ffb6ed1ea33e152ede08b2072d/rich-14.3.2-py3-none-any.whl", hash = "sha256:08e67c3e90884651da3239ea668222d19bea7b589149d8014a21c633420dbb69", size = 309963, upload-time = "2026-02-01T16:20:46.078Z" }, ] [[package]] @@ -2584,112 +2643,111 @@ wheels = [ [[package]] name = "ruff" -version = "0.14.8" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ed/d9/f7a0c4b3a2bf2556cd5d99b05372c29980249ef71e8e32669ba77428c82c/ruff-0.14.8.tar.gz", hash = "sha256:774ed0dd87d6ce925e3b8496feb3a00ac564bea52b9feb551ecd17e0a23d1eed", size = 5765385, upload-time = "2025-12-04T15:06:17.669Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/48/b8/9537b52010134b1d2b72870cc3f92d5fb759394094741b09ceccae183fbe/ruff-0.14.8-py3-none-linux_armv6l.whl", hash = "sha256:ec071e9c82eca417f6111fd39f7043acb53cd3fde9b1f95bbed745962e345afb", size = 13441540, upload-time = "2025-12-04T15:06:14.896Z" }, - { url = "https://files.pythonhosted.org/packages/24/00/99031684efb025829713682012b6dd37279b1f695ed1b01725f85fd94b38/ruff-0.14.8-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:8cdb162a7159f4ca36ce980a18c43d8f036966e7f73f866ac8f493b75e0c27e9", size = 13669384, upload-time = "2025-12-04T15:06:51.809Z" }, - { url = "https://files.pythonhosted.org/packages/72/64/3eb5949169fc19c50c04f28ece2c189d3b6edd57e5b533649dae6ca484fe/ruff-0.14.8-py3-none-macosx_11_0_arm64.whl", hash = "sha256:2e2fcbefe91f9fad0916850edf0854530c15bd1926b6b779de47e9ab619ea38f", size = 12806917, upload-time = "2025-12-04T15:06:08.925Z" }, - { url = "https://files.pythonhosted.org/packages/c4/08/5250babb0b1b11910f470370ec0cbc67470231f7cdc033cee57d4976f941/ruff-0.14.8-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9d70721066a296f45786ec31916dc287b44040f553da21564de0ab4d45a869b", size = 13256112, upload-time = "2025-12-04T15:06:23.498Z" }, - { url = "https://files.pythonhosted.org/packages/78/4c/6c588e97a8e8c2d4b522c31a579e1df2b4d003eddfbe23d1f262b1a431ff/ruff-0.14.8-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2c87e09b3cd9d126fc67a9ecd3b5b1d3ded2b9c7fce3f16e315346b9d05cfb52", size = 13227559, upload-time = "2025-12-04T15:06:33.432Z" }, - { url = "https://files.pythonhosted.org/packages/23/ce/5f78cea13eda8eceac71b5f6fa6e9223df9b87bb2c1891c166d1f0dce9f1/ruff-0.14.8-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d62cb310c4fbcb9ee4ac023fe17f984ae1e12b8a4a02e3d21489f9a2a5f730c", size = 13896379, upload-time = "2025-12-04T15:06:02.687Z" }, - { url = "https://files.pythonhosted.org/packages/cf/79/13de4517c4dadce9218a20035b21212a4c180e009507731f0d3b3f5df85a/ruff-0.14.8-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:1af35c2d62633d4da0521178e8a2641c636d2a7153da0bac1b30cfd4ccd91344", size = 15372786, upload-time = "2025-12-04T15:06:29.828Z" }, - { url = "https://files.pythonhosted.org/packages/00/06/33df72b3bb42be8a1c3815fd4fae83fa2945fc725a25d87ba3e42d1cc108/ruff-0.14.8-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:25add4575ffecc53d60eed3f24b1e934493631b48ebbc6ebaf9d8517924aca4b", size = 14990029, upload-time = "2025-12-04T15:06:36.812Z" }, - { url = "https://files.pythonhosted.org/packages/64/61/0f34927bd90925880394de0e081ce1afab66d7b3525336f5771dcf0cb46c/ruff-0.14.8-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4c943d847b7f02f7db4201a0600ea7d244d8a404fbb639b439e987edcf2baf9a", size = 14407037, upload-time = "2025-12-04T15:06:39.979Z" }, - { url = "https://files.pythonhosted.org/packages/96/bc/058fe0aefc0fbf0d19614cb6d1a3e2c048f7dc77ca64957f33b12cfdc5ef/ruff-0.14.8-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cb6e8bf7b4f627548daa1b69283dac5a296bfe9ce856703b03130732e20ddfe2", size = 14102390, upload-time = "2025-12-04T15:06:46.372Z" }, - { url = "https://files.pythonhosted.org/packages/af/a4/e4f77b02b804546f4c17e8b37a524c27012dd6ff05855d2243b49a7d3cb9/ruff-0.14.8-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:7aaf2974f378e6b01d1e257c6948207aec6a9b5ba53fab23d0182efb887a0e4a", size = 14230793, upload-time = "2025-12-04T15:06:20.497Z" }, - { url = "https://files.pythonhosted.org/packages/3f/52/bb8c02373f79552e8d087cedaffad76b8892033d2876c2498a2582f09dcf/ruff-0.14.8-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e5758ca513c43ad8a4ef13f0f081f80f08008f410790f3611a21a92421ab045b", size = 13160039, upload-time = "2025-12-04T15:06:49.06Z" }, - { url = "https://files.pythonhosted.org/packages/1f/ad/b69d6962e477842e25c0b11622548df746290cc6d76f9e0f4ed7456c2c31/ruff-0.14.8-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f74f7ba163b6e85a8d81a590363bf71618847e5078d90827749bfda1d88c9cdf", size = 13205158, upload-time = "2025-12-04T15:06:54.574Z" }, - { url = "https://files.pythonhosted.org/packages/06/63/54f23da1315c0b3dfc1bc03fbc34e10378918a20c0b0f086418734e57e74/ruff-0.14.8-py3-none-musllinux_1_2_i686.whl", hash = "sha256:eed28f6fafcc9591994c42254f5a5c5ca40e69a30721d2ab18bb0bb3baac3ab6", size = 13469550, upload-time = "2025-12-04T15:05:59.209Z" }, - { url = "https://files.pythonhosted.org/packages/70/7d/a4d7b1961e4903bc37fffb7ddcfaa7beb250f67d97cfd1ee1d5cddb1ec90/ruff-0.14.8-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:21d48fa744c9d1cb8d71eb0a740c4dd02751a5de9db9a730a8ef75ca34cf138e", size = 14211332, upload-time = "2025-12-04T15:06:06.027Z" }, - { url = "https://files.pythonhosted.org/packages/5d/93/2a5063341fa17054e5c86582136e9895db773e3c2ffb770dde50a09f35f0/ruff-0.14.8-py3-none-win32.whl", hash = "sha256:15f04cb45c051159baebb0f0037f404f1dc2f15a927418f29730f411a79bc4e7", size = 13151890, upload-time = "2025-12-04T15:06:11.668Z" }, - { url = "https://files.pythonhosted.org/packages/02/1c/65c61a0859c0add13a3e1cbb6024b42de587456a43006ca2d4fd3d1618fe/ruff-0.14.8-py3-none-win_amd64.whl", hash = "sha256:9eeb0b24242b5bbff3011409a739929f497f3fb5fe3b5698aba5e77e8c833097", size = 14537826, upload-time = "2025-12-04T15:06:26.409Z" }, - { url = "https://files.pythonhosted.org/packages/6d/63/8b41cea3afd7f58eb64ac9251668ee0073789a3bc9ac6f816c8c6fef986d/ruff-0.14.8-py3-none-win_arm64.whl", hash = "sha256:965a582c93c63fe715fd3e3f8aa37c4b776777203d8e1d8aa3cc0c14424a4b99", size = 13634522, upload-time = "2025-12-04T15:06:43.212Z" }, +version = "0.15.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/04/dc/4e6ac71b511b141cf626357a3946679abeba4cf67bc7cc5a17920f31e10d/ruff-0.15.1.tar.gz", hash = "sha256:c590fe13fb57c97141ae975c03a1aedb3d3156030cabd740d6ff0b0d601e203f", size = 4540855, upload-time = "2026-02-12T23:09:09.998Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/23/bf/e6e4324238c17f9d9120a9d60aa99a7daaa21204c07fcd84e2ef03bb5fd1/ruff-0.15.1-py3-none-linux_armv6l.whl", hash = "sha256:b101ed7cf4615bda6ffe65bdb59f964e9f4a0d3f85cbf0e54f0ab76d7b90228a", size = 10367819, upload-time = "2026-02-12T23:09:03.598Z" }, + { url = "https://files.pythonhosted.org/packages/b3/ea/c8f89d32e7912269d38c58f3649e453ac32c528f93bb7f4219258be2e7ed/ruff-0.15.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:939c995e9277e63ea632cc8d3fae17aa758526f49a9a850d2e7e758bfef46602", size = 10798618, upload-time = "2026-02-12T23:09:22.928Z" }, + { url = "https://files.pythonhosted.org/packages/5e/0f/1d0d88bc862624247d82c20c10d4c0f6bb2f346559d8af281674cf327f15/ruff-0.15.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:1d83466455fdefe60b8d9c8df81d3c1bbb2115cede53549d3b522ce2bc703899", size = 10148518, upload-time = "2026-02-12T23:08:58.339Z" }, + { url = "https://files.pythonhosted.org/packages/f5/c8/291c49cefaa4a9248e986256df2ade7add79388fe179e0691be06fae6f37/ruff-0.15.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9457e3c3291024866222b96108ab2d8265b477e5b1534c7ddb1810904858d16", size = 10518811, upload-time = "2026-02-12T23:09:31.865Z" }, + { url = "https://files.pythonhosted.org/packages/c3/1a/f5707440e5ae43ffa5365cac8bbb91e9665f4a883f560893829cf16a606b/ruff-0.15.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:92c92b003e9d4f7fbd33b1867bb15a1b785b1735069108dfc23821ba045b29bc", size = 10196169, upload-time = "2026-02-12T23:09:17.306Z" }, + { url = "https://files.pythonhosted.org/packages/2a/ff/26ddc8c4da04c8fd3ee65a89c9fb99eaa5c30394269d424461467be2271f/ruff-0.15.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fe5c41ab43e3a06778844c586251eb5a510f67125427625f9eb2b9526535779", size = 10990491, upload-time = "2026-02-12T23:09:25.503Z" }, + { url = "https://files.pythonhosted.org/packages/fc/00/50920cb385b89413f7cdb4bb9bc8fc59c1b0f30028d8bccc294189a54955/ruff-0.15.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:66a6dd6df4d80dc382c6484f8ce1bcceb55c32e9f27a8b94c32f6c7331bf14fb", size = 11843280, upload-time = "2026-02-12T23:09:19.88Z" }, + { url = "https://files.pythonhosted.org/packages/5d/6d/2f5cad8380caf5632a15460c323ae326f1e1a2b5b90a6ee7519017a017ca/ruff-0.15.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a4a42cbb8af0bda9bcd7606b064d7c0bc311a88d141d02f78920be6acb5aa83", size = 11274336, upload-time = "2026-02-12T23:09:14.907Z" }, + { url = "https://files.pythonhosted.org/packages/a3/1d/5f56cae1d6c40b8a318513599b35ea4b075d7dc1cd1d04449578c29d1d75/ruff-0.15.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4ab064052c31dddada35079901592dfba2e05f5b1e43af3954aafcbc1096a5b2", size = 11137288, upload-time = "2026-02-12T23:09:07.475Z" }, + { url = "https://files.pythonhosted.org/packages/cd/20/6f8d7d8f768c93b0382b33b9306b3b999918816da46537d5a61635514635/ruff-0.15.1-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:5631c940fe9fe91f817a4c2ea4e81f47bee3ca4aa646134a24374f3c19ad9454", size = 11070681, upload-time = "2026-02-12T23:08:55.43Z" }, + { url = "https://files.pythonhosted.org/packages/9a/67/d640ac76069f64cdea59dba02af2e00b1fa30e2103c7f8d049c0cff4cafd/ruff-0.15.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:68138a4ba184b4691ccdc39f7795c66b3c68160c586519e7e8444cf5a53e1b4c", size = 10486401, upload-time = "2026-02-12T23:09:27.927Z" }, + { url = "https://files.pythonhosted.org/packages/65/3d/e1429f64a3ff89297497916b88c32a5cc88eeca7e9c787072d0e7f1d3e1e/ruff-0.15.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:518f9af03bfc33c03bdb4cb63fabc935341bb7f54af500f92ac309ecfbba6330", size = 10197452, upload-time = "2026-02-12T23:09:12.147Z" }, + { url = "https://files.pythonhosted.org/packages/78/83/e2c3bade17dad63bf1e1c2ffaf11490603b760be149e1419b07049b36ef2/ruff-0.15.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:da79f4d6a826caaea95de0237a67e33b81e6ec2e25fc7e1993a4015dffca7c61", size = 10693900, upload-time = "2026-02-12T23:09:34.418Z" }, + { url = "https://files.pythonhosted.org/packages/a1/27/fdc0e11a813e6338e0706e8b39bb7a1d61ea5b36873b351acee7e524a72a/ruff-0.15.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:3dd86dccb83cd7d4dcfac303ffc277e6048600dfc22e38158afa208e8bf94a1f", size = 11227302, upload-time = "2026-02-12T23:09:36.536Z" }, + { url = "https://files.pythonhosted.org/packages/f6/58/ac864a75067dcbd3b95be5ab4eb2b601d7fbc3d3d736a27e391a4f92a5c1/ruff-0.15.1-py3-none-win32.whl", hash = "sha256:660975d9cb49b5d5278b12b03bb9951d554543a90b74ed5d366b20e2c57c2098", size = 10462555, upload-time = "2026-02-12T23:09:29.899Z" }, + { url = "https://files.pythonhosted.org/packages/e0/5e/d4ccc8a27ecdb78116feac4935dfc39d1304536f4296168f91ed3ec00cd2/ruff-0.15.1-py3-none-win_amd64.whl", hash = "sha256:c820fef9dd5d4172a6570e5721704a96c6679b80cf7be41659ed439653f62336", size = 11599956, upload-time = "2026-02-12T23:09:01.157Z" }, + { url = "https://files.pythonhosted.org/packages/2a/07/5bda6a85b220c64c65686bc85bd0bbb23b29c62b3a9f9433fa55f17cda93/ruff-0.15.1-py3-none-win_arm64.whl", hash = "sha256:5ff7d5f0f88567850f45081fac8f4ec212be8d0b963e385c3f7d0d2eb4899416", size = 10874604, upload-time = "2026-02-12T23:09:05.515Z" }, ] [[package]] name = "scipy" -version = "1.16.3" +version = "1.17.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0a/ca/d8ace4f98322d01abcd52d381134344bf7b431eba7ed8b42bdea5a3c2ac9/scipy-1.16.3.tar.gz", hash = "sha256:01e87659402762f43bd2fee13370553a17ada367d42e7487800bf2916535aecb", size = 30597883, upload-time = "2025-10-28T17:38:54.068Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/5f/6f37d7439de1455ce9c5a556b8d1db0979f03a796c030bafdf08d35b7bf9/scipy-1.16.3-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:40be6cf99e68b6c4321e9f8782e7d5ff8265af28ef2cd56e9c9b2638fa08ad97", size = 36630881, upload-time = "2025-10-28T17:31:47.104Z" }, - { url = "https://files.pythonhosted.org/packages/7c/89/d70e9f628749b7e4db2aa4cd89735502ff3f08f7b9b27d2e799485987cd9/scipy-1.16.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:8be1ca9170fcb6223cc7c27f4305d680ded114a1567c0bd2bfcbf947d1b17511", size = 28941012, upload-time = "2025-10-28T17:31:53.411Z" }, - { url = "https://files.pythonhosted.org/packages/a8/a8/0e7a9a6872a923505dbdf6bb93451edcac120363131c19013044a1e7cb0c/scipy-1.16.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:bea0a62734d20d67608660f69dcda23e7f90fb4ca20974ab80b6ed40df87a005", size = 20931935, upload-time = "2025-10-28T17:31:57.361Z" }, - { url = "https://files.pythonhosted.org/packages/bd/c7/020fb72bd79ad798e4dbe53938543ecb96b3a9ac3fe274b7189e23e27353/scipy-1.16.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:2a207a6ce9c24f1951241f4693ede2d393f59c07abc159b2cb2be980820e01fb", size = 23534466, upload-time = "2025-10-28T17:32:01.875Z" }, - { url = "https://files.pythonhosted.org/packages/be/a0/668c4609ce6dbf2f948e167836ccaf897f95fb63fa231c87da7558a374cd/scipy-1.16.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:532fb5ad6a87e9e9cd9c959b106b73145a03f04c7d57ea3e6f6bb60b86ab0876", size = 33593618, upload-time = "2025-10-28T17:32:06.902Z" }, - { url = "https://files.pythonhosted.org/packages/ca/6e/8942461cf2636cdae083e3eb72622a7fbbfa5cf559c7d13ab250a5dbdc01/scipy-1.16.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0151a0749efeaaab78711c78422d413c583b8cdd2011a3c1d6c794938ee9fdb2", size = 35899798, upload-time = "2025-10-28T17:32:12.665Z" }, - { url = "https://files.pythonhosted.org/packages/79/e8/d0f33590364cdbd67f28ce79368b373889faa4ee959588beddf6daef9abe/scipy-1.16.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b7180967113560cca57418a7bc719e30366b47959dd845a93206fbed693c867e", size = 36226154, upload-time = "2025-10-28T17:32:17.961Z" }, - { url = "https://files.pythonhosted.org/packages/39/c1/1903de608c0c924a1749c590064e65810f8046e437aba6be365abc4f7557/scipy-1.16.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:deb3841c925eeddb6afc1e4e4a45e418d19ec7b87c5df177695224078e8ec733", size = 38878540, upload-time = "2025-10-28T17:32:23.907Z" }, - { url = "https://files.pythonhosted.org/packages/f1/d0/22ec7036ba0b0a35bccb7f25ab407382ed34af0b111475eb301c16f8a2e5/scipy-1.16.3-cp311-cp311-win_amd64.whl", hash = "sha256:53c3844d527213631e886621df5695d35e4f6a75f620dca412bcd292f6b87d78", size = 38722107, upload-time = "2025-10-28T17:32:29.921Z" }, - { url = "https://files.pythonhosted.org/packages/7b/60/8a00e5a524bb3bf8898db1650d350f50e6cffb9d7a491c561dc9826c7515/scipy-1.16.3-cp311-cp311-win_arm64.whl", hash = "sha256:9452781bd879b14b6f055b26643703551320aa8d79ae064a71df55c00286a184", size = 25506272, upload-time = "2025-10-28T17:32:34.577Z" }, - { url = "https://files.pythonhosted.org/packages/40/41/5bf55c3f386b1643812f3a5674edf74b26184378ef0f3e7c7a09a7e2ca7f/scipy-1.16.3-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:81fc5827606858cf71446a5e98715ba0e11f0dbc83d71c7409d05486592a45d6", size = 36659043, upload-time = "2025-10-28T17:32:40.285Z" }, - { url = "https://files.pythonhosted.org/packages/1e/0f/65582071948cfc45d43e9870bf7ca5f0e0684e165d7c9ef4e50d783073eb/scipy-1.16.3-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:c97176013d404c7346bf57874eaac5187d969293bf40497140b0a2b2b7482e07", size = 28898986, upload-time = "2025-10-28T17:32:45.325Z" }, - { url = "https://files.pythonhosted.org/packages/96/5e/36bf3f0ac298187d1ceadde9051177d6a4fe4d507e8f59067dc9dd39e650/scipy-1.16.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2b71d93c8a9936046866acebc915e2af2e292b883ed6e2cbe5c34beb094b82d9", size = 20889814, upload-time = "2025-10-28T17:32:49.277Z" }, - { url = "https://files.pythonhosted.org/packages/80/35/178d9d0c35394d5d5211bbff7ac4f2986c5488b59506fef9e1de13ea28d3/scipy-1.16.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3d4a07a8e785d80289dfe66b7c27d8634a773020742ec7187b85ccc4b0e7b686", size = 23565795, upload-time = "2025-10-28T17:32:53.337Z" }, - { url = "https://files.pythonhosted.org/packages/fa/46/d1146ff536d034d02f83c8afc3c4bab2eddb634624d6529a8512f3afc9da/scipy-1.16.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0553371015692a898e1aa858fed67a3576c34edefa6b7ebdb4e9dde49ce5c203", size = 33349476, upload-time = "2025-10-28T17:32:58.353Z" }, - { url = "https://files.pythonhosted.org/packages/79/2e/415119c9ab3e62249e18c2b082c07aff907a273741b3f8160414b0e9193c/scipy-1.16.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:72d1717fd3b5e6ec747327ce9bda32d5463f472c9dce9f54499e81fbd50245a1", size = 35676692, upload-time = "2025-10-28T17:33:03.88Z" }, - { url = "https://files.pythonhosted.org/packages/27/82/df26e44da78bf8d2aeaf7566082260cfa15955a5a6e96e6a29935b64132f/scipy-1.16.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1fb2472e72e24d1530debe6ae078db70fb1605350c88a3d14bc401d6306dbffe", size = 36019345, upload-time = "2025-10-28T17:33:09.773Z" }, - { url = "https://files.pythonhosted.org/packages/82/31/006cbb4b648ba379a95c87262c2855cd0d09453e500937f78b30f02fa1cd/scipy-1.16.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c5192722cffe15f9329a3948c4b1db789fbb1f05c97899187dcf009b283aea70", size = 38678975, upload-time = "2025-10-28T17:33:15.809Z" }, - { url = "https://files.pythonhosted.org/packages/c2/7f/acbd28c97e990b421af7d6d6cd416358c9c293fc958b8529e0bd5d2a2a19/scipy-1.16.3-cp312-cp312-win_amd64.whl", hash = "sha256:56edc65510d1331dae01ef9b658d428e33ed48b4f77b1d51caf479a0253f96dc", size = 38555926, upload-time = "2025-10-28T17:33:21.388Z" }, - { url = "https://files.pythonhosted.org/packages/ce/69/c5c7807fd007dad4f48e0a5f2153038dc96e8725d3345b9ee31b2b7bed46/scipy-1.16.3-cp312-cp312-win_arm64.whl", hash = "sha256:a8a26c78ef223d3e30920ef759e25625a0ecdd0d60e5a8818b7513c3e5384cf2", size = 25463014, upload-time = "2025-10-28T17:33:25.975Z" }, - { url = "https://files.pythonhosted.org/packages/72/f1/57e8327ab1508272029e27eeef34f2302ffc156b69e7e233e906c2a5c379/scipy-1.16.3-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:d2ec56337675e61b312179a1ad124f5f570c00f920cc75e1000025451b88241c", size = 36617856, upload-time = "2025-10-28T17:33:31.375Z" }, - { url = "https://files.pythonhosted.org/packages/44/13/7e63cfba8a7452eb756306aa2fd9b37a29a323b672b964b4fdeded9a3f21/scipy-1.16.3-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:16b8bc35a4cc24db80a0ec836a9286d0e31b2503cb2fd7ff7fb0e0374a97081d", size = 28874306, upload-time = "2025-10-28T17:33:36.516Z" }, - { url = "https://files.pythonhosted.org/packages/15/65/3a9400efd0228a176e6ec3454b1fa998fbbb5a8defa1672c3f65706987db/scipy-1.16.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:5803c5fadd29de0cf27fa08ccbfe7a9e5d741bf63e4ab1085437266f12460ff9", size = 20865371, upload-time = "2025-10-28T17:33:42.094Z" }, - { url = "https://files.pythonhosted.org/packages/33/d7/eda09adf009a9fb81827194d4dd02d2e4bc752cef16737cc4ef065234031/scipy-1.16.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:b81c27fc41954319a943d43b20e07c40bdcd3ff7cf013f4fb86286faefe546c4", size = 23524877, upload-time = "2025-10-28T17:33:48.483Z" }, - { url = "https://files.pythonhosted.org/packages/7d/6b/3f911e1ebc364cb81320223a3422aab7d26c9c7973109a9cd0f27c64c6c0/scipy-1.16.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0c3b4dd3d9b08dbce0f3440032c52e9e2ab9f96ade2d3943313dfe51a7056959", size = 33342103, upload-time = "2025-10-28T17:33:56.495Z" }, - { url = "https://files.pythonhosted.org/packages/21/f6/4bfb5695d8941e5c570a04d9fcd0d36bce7511b7d78e6e75c8f9791f82d0/scipy-1.16.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7dc1360c06535ea6116a2220f760ae572db9f661aba2d88074fe30ec2aa1ff88", size = 35697297, upload-time = "2025-10-28T17:34:04.722Z" }, - { url = "https://files.pythonhosted.org/packages/04/e1/6496dadbc80d8d896ff72511ecfe2316b50313bfc3ebf07a3f580f08bd8c/scipy-1.16.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:663b8d66a8748051c3ee9c96465fb417509315b99c71550fda2591d7dd634234", size = 36021756, upload-time = "2025-10-28T17:34:13.482Z" }, - { url = "https://files.pythonhosted.org/packages/fe/bd/a8c7799e0136b987bda3e1b23d155bcb31aec68a4a472554df5f0937eef7/scipy-1.16.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eab43fae33a0c39006a88096cd7b4f4ef545ea0447d250d5ac18202d40b6611d", size = 38696566, upload-time = "2025-10-28T17:34:22.384Z" }, - { url = "https://files.pythonhosted.org/packages/cd/01/1204382461fcbfeb05b6161b594f4007e78b6eba9b375382f79153172b4d/scipy-1.16.3-cp313-cp313-win_amd64.whl", hash = "sha256:062246acacbe9f8210de8e751b16fc37458213f124bef161a5a02c7a39284304", size = 38529877, upload-time = "2025-10-28T17:35:51.076Z" }, - { url = "https://files.pythonhosted.org/packages/7f/14/9d9fbcaa1260a94f4bb5b64ba9213ceb5d03cd88841fe9fd1ffd47a45b73/scipy-1.16.3-cp313-cp313-win_arm64.whl", hash = "sha256:50a3dbf286dbc7d84f176f9a1574c705f277cb6565069f88f60db9eafdbe3ee2", size = 25455366, upload-time = "2025-10-28T17:35:59.014Z" }, - { url = "https://files.pythonhosted.org/packages/e2/a3/9ec205bd49f42d45d77f1730dbad9ccf146244c1647605cf834b3a8c4f36/scipy-1.16.3-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:fb4b29f4cf8cc5a8d628bc8d8e26d12d7278cd1f219f22698a378c3d67db5e4b", size = 37027931, upload-time = "2025-10-28T17:34:31.451Z" }, - { url = "https://files.pythonhosted.org/packages/25/06/ca9fd1f3a4589cbd825b1447e5db3a8ebb969c1eaf22c8579bd286f51b6d/scipy-1.16.3-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:8d09d72dc92742988b0e7750bddb8060b0c7079606c0d24a8cc8e9c9c11f9079", size = 29400081, upload-time = "2025-10-28T17:34:39.087Z" }, - { url = "https://files.pythonhosted.org/packages/6a/56/933e68210d92657d93fb0e381683bc0e53a965048d7358ff5fbf9e6a1b17/scipy-1.16.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:03192a35e661470197556de24e7cb1330d84b35b94ead65c46ad6f16f6b28f2a", size = 21391244, upload-time = "2025-10-28T17:34:45.234Z" }, - { url = "https://files.pythonhosted.org/packages/a8/7e/779845db03dc1418e215726329674b40576879b91814568757ff0014ad65/scipy-1.16.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:57d01cb6f85e34f0946b33caa66e892aae072b64b034183f3d87c4025802a119", size = 23929753, upload-time = "2025-10-28T17:34:51.793Z" }, - { url = "https://files.pythonhosted.org/packages/4c/4b/f756cf8161d5365dcdef9e5f460ab226c068211030a175d2fc7f3f41ca64/scipy-1.16.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:96491a6a54e995f00a28a3c3badfff58fd093bf26cd5fb34a2188c8c756a3a2c", size = 33496912, upload-time = "2025-10-28T17:34:59.8Z" }, - { url = "https://files.pythonhosted.org/packages/09/b5/222b1e49a58668f23839ca1542a6322bb095ab8d6590d4f71723869a6c2c/scipy-1.16.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cd13e354df9938598af2be05822c323e97132d5e6306b83a3b4ee6724c6e522e", size = 35802371, upload-time = "2025-10-28T17:35:08.173Z" }, - { url = "https://files.pythonhosted.org/packages/c1/8d/5964ef68bb31829bde27611f8c9deeac13764589fe74a75390242b64ca44/scipy-1.16.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:63d3cdacb8a824a295191a723ee5e4ea7768ca5ca5f2838532d9f2e2b3ce2135", size = 36190477, upload-time = "2025-10-28T17:35:16.7Z" }, - { url = "https://files.pythonhosted.org/packages/ab/f2/b31d75cb9b5fa4dd39a0a931ee9b33e7f6f36f23be5ef560bf72e0f92f32/scipy-1.16.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:e7efa2681ea410b10dde31a52b18b0154d66f2485328830e45fdf183af5aefc6", size = 38796678, upload-time = "2025-10-28T17:35:26.354Z" }, - { url = "https://files.pythonhosted.org/packages/b4/1e/b3723d8ff64ab548c38d87055483714fefe6ee20e0189b62352b5e015bb1/scipy-1.16.3-cp313-cp313t-win_amd64.whl", hash = "sha256:2d1ae2cf0c350e7705168ff2429962a89ad90c2d49d1dd300686d8b2a5af22fc", size = 38640178, upload-time = "2025-10-28T17:35:35.304Z" }, - { url = "https://files.pythonhosted.org/packages/8e/f3/d854ff38789aca9b0cc23008d607ced9de4f7ab14fa1ca4329f86b3758ca/scipy-1.16.3-cp313-cp313t-win_arm64.whl", hash = "sha256:0c623a54f7b79dd88ef56da19bc2873afec9673a48f3b85b18e4d402bdd29a5a", size = 25803246, upload-time = "2025-10-28T17:35:42.155Z" }, - { url = "https://files.pythonhosted.org/packages/99/f6/99b10fd70f2d864c1e29a28bbcaa0c6340f9d8518396542d9ea3b4aaae15/scipy-1.16.3-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:875555ce62743e1d54f06cdf22c1e0bc47b91130ac40fe5d783b6dfa114beeb6", size = 36606469, upload-time = "2025-10-28T17:36:08.741Z" }, - { url = "https://files.pythonhosted.org/packages/4d/74/043b54f2319f48ea940dd025779fa28ee360e6b95acb7cd188fad4391c6b/scipy-1.16.3-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:bb61878c18a470021fb515a843dc7a76961a8daceaaaa8bad1332f1bf4b54657", size = 28872043, upload-time = "2025-10-28T17:36:16.599Z" }, - { url = "https://files.pythonhosted.org/packages/4d/e1/24b7e50cc1c4ee6ffbcb1f27fe9f4c8b40e7911675f6d2d20955f41c6348/scipy-1.16.3-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:f2622206f5559784fa5c4b53a950c3c7c1cf3e84ca1b9c4b6c03f062f289ca26", size = 20862952, upload-time = "2025-10-28T17:36:22.966Z" }, - { url = "https://files.pythonhosted.org/packages/dd/3a/3e8c01a4d742b730df368e063787c6808597ccb38636ed821d10b39ca51b/scipy-1.16.3-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7f68154688c515cdb541a31ef8eb66d8cd1050605be9dcd74199cbd22ac739bc", size = 23508512, upload-time = "2025-10-28T17:36:29.731Z" }, - { url = "https://files.pythonhosted.org/packages/1f/60/c45a12b98ad591536bfe5330cb3cfe1850d7570259303563b1721564d458/scipy-1.16.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8b3c820ddb80029fe9f43d61b81d8b488d3ef8ca010d15122b152db77dc94c22", size = 33413639, upload-time = "2025-10-28T17:36:37.982Z" }, - { url = "https://files.pythonhosted.org/packages/71/bc/35957d88645476307e4839712642896689df442f3e53b0fa016ecf8a3357/scipy-1.16.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d3837938ae715fc0fe3c39c0202de3a8853aff22ca66781ddc2ade7554b7e2cc", size = 35704729, upload-time = "2025-10-28T17:36:46.547Z" }, - { url = "https://files.pythonhosted.org/packages/3b/15/89105e659041b1ca11c386e9995aefacd513a78493656e57789f9d9eab61/scipy-1.16.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:aadd23f98f9cb069b3bd64ddc900c4d277778242e961751f77a8cb5c4b946fb0", size = 36086251, upload-time = "2025-10-28T17:36:55.161Z" }, - { url = "https://files.pythonhosted.org/packages/1a/87/c0ea673ac9c6cc50b3da2196d860273bc7389aa69b64efa8493bdd25b093/scipy-1.16.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b7c5f1bda1354d6a19bc6af73a649f8285ca63ac6b52e64e658a5a11d4d69800", size = 38716681, upload-time = "2025-10-28T17:37:04.1Z" }, - { url = "https://files.pythonhosted.org/packages/91/06/837893227b043fb9b0d13e4bd7586982d8136cb249ffb3492930dab905b8/scipy-1.16.3-cp314-cp314-win_amd64.whl", hash = "sha256:e5d42a9472e7579e473879a1990327830493a7047506d58d73fc429b84c1d49d", size = 39358423, upload-time = "2025-10-28T17:38:20.005Z" }, - { url = "https://files.pythonhosted.org/packages/95/03/28bce0355e4d34a7c034727505a02d19548549e190bedd13a721e35380b7/scipy-1.16.3-cp314-cp314-win_arm64.whl", hash = "sha256:6020470b9d00245926f2d5bb93b119ca0340f0d564eb6fbaad843eaebf9d690f", size = 26135027, upload-time = "2025-10-28T17:38:24.966Z" }, - { url = "https://files.pythonhosted.org/packages/b2/6f/69f1e2b682efe9de8fe9f91040f0cd32f13cfccba690512ba4c582b0bc29/scipy-1.16.3-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:e1d27cbcb4602680a49d787d90664fa4974063ac9d4134813332a8c53dbe667c", size = 37028379, upload-time = "2025-10-28T17:37:14.061Z" }, - { url = "https://files.pythonhosted.org/packages/7c/2d/e826f31624a5ebbab1cd93d30fd74349914753076ed0593e1d56a98c4fb4/scipy-1.16.3-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:9b9c9c07b6d56a35777a1b4cc8966118fb16cfd8daf6743867d17d36cfad2d40", size = 29400052, upload-time = "2025-10-28T17:37:21.709Z" }, - { url = "https://files.pythonhosted.org/packages/69/27/d24feb80155f41fd1f156bf144e7e049b4e2b9dd06261a242905e3bc7a03/scipy-1.16.3-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:3a4c460301fb2cffb7f88528f30b3127742cff583603aa7dc964a52c463b385d", size = 21391183, upload-time = "2025-10-28T17:37:29.559Z" }, - { url = "https://files.pythonhosted.org/packages/f8/d3/1b229e433074c5738a24277eca520a2319aac7465eea7310ea6ae0e98ae2/scipy-1.16.3-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:f667a4542cc8917af1db06366d3f78a5c8e83badd56409f94d1eac8d8d9133fa", size = 23930174, upload-time = "2025-10-28T17:37:36.306Z" }, - { url = "https://files.pythonhosted.org/packages/16/9d/d9e148b0ec680c0f042581a2be79a28a7ab66c0c4946697f9e7553ead337/scipy-1.16.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f379b54b77a597aa7ee5e697df0d66903e41b9c85a6dd7946159e356319158e8", size = 33497852, upload-time = "2025-10-28T17:37:42.228Z" }, - { url = "https://files.pythonhosted.org/packages/2f/22/4e5f7561e4f98b7bea63cf3fd7934bff1e3182e9f1626b089a679914d5c8/scipy-1.16.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4aff59800a3b7f786b70bfd6ab551001cb553244988d7d6b8299cb1ea653b353", size = 35798595, upload-time = "2025-10-28T17:37:48.102Z" }, - { url = "https://files.pythonhosted.org/packages/83/42/6644d714c179429fc7196857866f219fef25238319b650bb32dde7bf7a48/scipy-1.16.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:da7763f55885045036fabcebd80144b757d3db06ab0861415d1c3b7c69042146", size = 36186269, upload-time = "2025-10-28T17:37:53.72Z" }, - { url = "https://files.pythonhosted.org/packages/ac/70/64b4d7ca92f9cf2e6fc6aaa2eecf80bb9b6b985043a9583f32f8177ea122/scipy-1.16.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ffa6eea95283b2b8079b821dc11f50a17d0571c92b43e2b5b12764dc5f9b285d", size = 38802779, upload-time = "2025-10-28T17:37:59.393Z" }, - { url = "https://files.pythonhosted.org/packages/61/82/8d0e39f62764cce5ffd5284131e109f07cf8955aef9ab8ed4e3aa5e30539/scipy-1.16.3-cp314-cp314t-win_amd64.whl", hash = "sha256:d9f48cafc7ce94cf9b15c6bffdc443a81a27bf7075cf2dcd5c8b40f85d10c4e7", size = 39471128, upload-time = "2025-10-28T17:38:05.259Z" }, - { url = "https://files.pythonhosted.org/packages/64/47/a494741db7280eae6dc033510c319e34d42dd41b7ac0c7ead39354d1a2b5/scipy-1.16.3-cp314-cp314t-win_arm64.whl", hash = "sha256:21d9d6b197227a12dcbf9633320a4e34c6b0e51c57268df255a0942983bac562", size = 26464127, upload-time = "2025-10-28T17:38:11.34Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/56/3e/9cca699f3486ce6bc12ff46dc2031f1ec8eb9ccc9a320fdaf925f1417426/scipy-1.17.0.tar.gz", hash = "sha256:2591060c8e648d8b96439e111ac41fd8342fdeff1876be2e19dea3fe8930454e", size = 30396830, upload-time = "2026-01-10T21:34:23.009Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/4b/c89c131aa87cad2b77a54eb0fb94d633a842420fa7e919dc2f922037c3d8/scipy-1.17.0-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:2abd71643797bd8a106dff97894ff7869eeeb0af0f7a5ce02e4227c6a2e9d6fd", size = 31381316, upload-time = "2026-01-10T21:24:33.42Z" }, + { url = "https://files.pythonhosted.org/packages/5e/5f/a6b38f79a07d74989224d5f11b55267714707582908a5f1ae854cf9a9b84/scipy-1.17.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:ef28d815f4d2686503e5f4f00edc387ae58dfd7a2f42e348bb53359538f01558", size = 27966760, upload-time = "2026-01-10T21:24:38.911Z" }, + { url = "https://files.pythonhosted.org/packages/c1/20/095ad24e031ee8ed3c5975954d816b8e7e2abd731e04f8be573de8740885/scipy-1.17.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:272a9f16d6bb4667e8b50d25d71eddcc2158a214df1b566319298de0939d2ab7", size = 20138701, upload-time = "2026-01-10T21:24:43.249Z" }, + { url = "https://files.pythonhosted.org/packages/89/11/4aad2b3858d0337756f3323f8960755704e530b27eb2a94386c970c32cbe/scipy-1.17.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:7204fddcbec2fe6598f1c5fdf027e9f259106d05202a959a9f1aecf036adc9f6", size = 22480574, upload-time = "2026-01-10T21:24:47.266Z" }, + { url = "https://files.pythonhosted.org/packages/85/bd/f5af70c28c6da2227e510875cadf64879855193a687fb19951f0f44cfd6b/scipy-1.17.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fc02c37a5639ee67d8fb646ffded6d793c06c5622d36b35cfa8fe5ececb8f042", size = 32862414, upload-time = "2026-01-10T21:24:52.566Z" }, + { url = "https://files.pythonhosted.org/packages/ef/df/df1457c4df3826e908879fe3d76bc5b6e60aae45f4ee42539512438cfd5d/scipy-1.17.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dac97a27520d66c12a34fd90a4fe65f43766c18c0d6e1c0a80f114d2260080e4", size = 35112380, upload-time = "2026-01-10T21:24:58.433Z" }, + { url = "https://files.pythonhosted.org/packages/5f/bb/88e2c16bd1dd4de19d80d7c5e238387182993c2fb13b4b8111e3927ad422/scipy-1.17.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ebb7446a39b3ae0fe8f416a9a3fdc6fba3f11c634f680f16a239c5187bc487c0", size = 34922676, upload-time = "2026-01-10T21:25:04.287Z" }, + { url = "https://files.pythonhosted.org/packages/02/ba/5120242cc735f71fc002cff0303d536af4405eb265f7c60742851e7ccfe9/scipy-1.17.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:474da16199f6af66601a01546144922ce402cb17362e07d82f5a6cf8f963e449", size = 37507599, upload-time = "2026-01-10T21:25:09.851Z" }, + { url = "https://files.pythonhosted.org/packages/52/c8/08629657ac6c0da198487ce8cd3de78e02cfde42b7f34117d56a3fe249dc/scipy-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:255c0da161bd7b32a6c898e7891509e8a9289f0b1c6c7d96142ee0d2b114c2ea", size = 36380284, upload-time = "2026-01-10T21:25:15.632Z" }, + { url = "https://files.pythonhosted.org/packages/6c/4a/465f96d42c6f33ad324a40049dfd63269891db9324aa66c4a1c108c6f994/scipy-1.17.0-cp311-cp311-win_arm64.whl", hash = "sha256:85b0ac3ad17fa3be50abd7e69d583d98792d7edc08367e01445a1e2076005379", size = 24370427, upload-time = "2026-01-10T21:25:20.514Z" }, + { url = "https://files.pythonhosted.org/packages/0b/11/7241a63e73ba5a516f1930ac8d5b44cbbfabd35ac73a2d08ca206df007c4/scipy-1.17.0-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:0d5018a57c24cb1dd828bcf51d7b10e65986d549f52ef5adb6b4d1ded3e32a57", size = 31364580, upload-time = "2026-01-10T21:25:25.717Z" }, + { url = "https://files.pythonhosted.org/packages/ed/1d/5057f812d4f6adc91a20a2d6f2ebcdb517fdbc87ae3acc5633c9b97c8ba5/scipy-1.17.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:88c22af9e5d5a4f9e027e26772cc7b5922fab8bcc839edb3ae33de404feebd9e", size = 27969012, upload-time = "2026-01-10T21:25:30.921Z" }, + { url = "https://files.pythonhosted.org/packages/e3/21/f6ec556c1e3b6ec4e088da667d9987bb77cc3ab3026511f427dc8451187d/scipy-1.17.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:f3cd947f20fe17013d401b64e857c6b2da83cae567adbb75b9dcba865abc66d8", size = 20140691, upload-time = "2026-01-10T21:25:34.802Z" }, + { url = "https://files.pythonhosted.org/packages/7a/fe/5e5ad04784964ba964a96f16c8d4676aa1b51357199014dce58ab7ec5670/scipy-1.17.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:e8c0b331c2c1f531eb51f1b4fc9ba709521a712cce58f1aa627bc007421a5306", size = 22463015, upload-time = "2026-01-10T21:25:39.277Z" }, + { url = "https://files.pythonhosted.org/packages/4a/69/7c347e857224fcaf32a34a05183b9d8a7aca25f8f2d10b8a698b8388561a/scipy-1.17.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5194c445d0a1c7a6c1a4a4681b6b7c71baad98ff66d96b949097e7513c9d6742", size = 32724197, upload-time = "2026-01-10T21:25:44.084Z" }, + { url = "https://files.pythonhosted.org/packages/d1/fe/66d73b76d378ba8cc2fe605920c0c75092e3a65ae746e1e767d9d020a75a/scipy-1.17.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9eeb9b5f5997f75507814ed9d298ab23f62cf79f5a3ef90031b1ee2506abdb5b", size = 35009148, upload-time = "2026-01-10T21:25:50.591Z" }, + { url = "https://files.pythonhosted.org/packages/af/07/07dec27d9dc41c18d8c43c69e9e413431d20c53a0339c388bcf72f353c4b/scipy-1.17.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:40052543f7bbe921df4408f46003d6f01c6af109b9e2c8a66dd1cf6cf57f7d5d", size = 34798766, upload-time = "2026-01-10T21:25:59.41Z" }, + { url = "https://files.pythonhosted.org/packages/81/61/0470810c8a093cdacd4ba7504b8a218fd49ca070d79eca23a615f5d9a0b0/scipy-1.17.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0cf46c8013fec9d3694dc572f0b54100c28405d55d3e2cb15e2895b25057996e", size = 37405953, upload-time = "2026-01-10T21:26:07.75Z" }, + { url = "https://files.pythonhosted.org/packages/92/ce/672ed546f96d5d41ae78c4b9b02006cedd0b3d6f2bf5bb76ea455c320c28/scipy-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:0937a0b0d8d593a198cededd4c439a0ea216a3f36653901ea1f3e4be949056f8", size = 36328121, upload-time = "2026-01-10T21:26:16.509Z" }, + { url = "https://files.pythonhosted.org/packages/9d/21/38165845392cae67b61843a52c6455d47d0cc2a40dd495c89f4362944654/scipy-1.17.0-cp312-cp312-win_arm64.whl", hash = "sha256:f603d8a5518c7426414d1d8f82e253e454471de682ce5e39c29adb0df1efb86b", size = 24314368, upload-time = "2026-01-10T21:26:23.087Z" }, + { url = "https://files.pythonhosted.org/packages/0c/51/3468fdfd49387ddefee1636f5cf6d03ce603b75205bf439bbf0e62069bfd/scipy-1.17.0-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:65ec32f3d32dfc48c72df4291345dae4f048749bc8d5203ee0a3f347f96c5ce6", size = 31344101, upload-time = "2026-01-10T21:26:30.25Z" }, + { url = "https://files.pythonhosted.org/packages/b2/9a/9406aec58268d437636069419e6977af953d1e246df941d42d3720b7277b/scipy-1.17.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:1f9586a58039d7229ce77b52f8472c972448cded5736eaf102d5658bbac4c269", size = 27950385, upload-time = "2026-01-10T21:26:36.801Z" }, + { url = "https://files.pythonhosted.org/packages/4f/98/e7342709e17afdfd1b26b56ae499ef4939b45a23a00e471dfb5375eea205/scipy-1.17.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:9fad7d3578c877d606b1150135c2639e9de9cecd3705caa37b66862977cc3e72", size = 20122115, upload-time = "2026-01-10T21:26:42.107Z" }, + { url = "https://files.pythonhosted.org/packages/fd/0e/9eeeb5357a64fd157cbe0302c213517c541cc16b8486d82de251f3c68ede/scipy-1.17.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:423ca1f6584fc03936972b5f7c06961670dbba9f234e71676a7c7ccf938a0d61", size = 22442402, upload-time = "2026-01-10T21:26:48.029Z" }, + { url = "https://files.pythonhosted.org/packages/c9/10/be13397a0e434f98e0c79552b2b584ae5bb1c8b2be95db421533bbca5369/scipy-1.17.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fe508b5690e9eaaa9467fc047f833af58f1152ae51a0d0aed67aa5801f4dd7d6", size = 32696338, upload-time = "2026-01-10T21:26:55.521Z" }, + { url = "https://files.pythonhosted.org/packages/63/1e/12fbf2a3bb240161651c94bb5cdd0eae5d4e8cc6eaeceb74ab07b12a753d/scipy-1.17.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6680f2dfd4f6182e7d6db161344537da644d1cf85cf293f015c60a17ecf08752", size = 34977201, upload-time = "2026-01-10T21:27:03.501Z" }, + { url = "https://files.pythonhosted.org/packages/19/5b/1a63923e23ccd20bd32156d7dd708af5bbde410daa993aa2500c847ab2d2/scipy-1.17.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eec3842ec9ac9de5917899b277428886042a93db0b227ebbe3a333b64ec7643d", size = 34777384, upload-time = "2026-01-10T21:27:11.423Z" }, + { url = "https://files.pythonhosted.org/packages/39/22/b5da95d74edcf81e540e467202a988c50fef41bd2011f46e05f72ba07df6/scipy-1.17.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d7425fcafbc09a03731e1bc05581f5fad988e48c6a861f441b7ab729a49a55ea", size = 37379586, upload-time = "2026-01-10T21:27:20.171Z" }, + { url = "https://files.pythonhosted.org/packages/b9/b6/8ac583d6da79e7b9e520579f03007cb006f063642afd6b2eeb16b890bf93/scipy-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:87b411e42b425b84777718cc41516b8a7e0795abfa8e8e1d573bf0ef014f0812", size = 36287211, upload-time = "2026-01-10T21:28:43.122Z" }, + { url = "https://files.pythonhosted.org/packages/55/fb/7db19e0b3e52f882b420417644ec81dd57eeef1bd1705b6f689d8ff93541/scipy-1.17.0-cp313-cp313-win_arm64.whl", hash = "sha256:357ca001c6e37601066092e7c89cca2f1ce74e2a520ca78d063a6d2201101df2", size = 24312646, upload-time = "2026-01-10T21:28:49.893Z" }, + { url = "https://files.pythonhosted.org/packages/20/b6/7feaa252c21cc7aff335c6c55e1b90ab3e3306da3f048109b8b639b94648/scipy-1.17.0-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:ec0827aa4d36cb79ff1b81de898e948a51ac0b9b1c43e4a372c0508c38c0f9a3", size = 31693194, upload-time = "2026-01-10T21:27:27.454Z" }, + { url = "https://files.pythonhosted.org/packages/76/bb/bbb392005abce039fb7e672cb78ac7d158700e826b0515cab6b5b60c26fb/scipy-1.17.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:819fc26862b4b3c73a60d486dbb919202f3d6d98c87cf20c223511429f2d1a97", size = 28365415, upload-time = "2026-01-10T21:27:34.26Z" }, + { url = "https://files.pythonhosted.org/packages/37/da/9d33196ecc99fba16a409c691ed464a3a283ac454a34a13a3a57c0d66f3a/scipy-1.17.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:363ad4ae2853d88ebcde3ae6ec46ccca903ea9835ee8ba543f12f575e7b07e4e", size = 20537232, upload-time = "2026-01-10T21:27:40.306Z" }, + { url = "https://files.pythonhosted.org/packages/56/9d/f4b184f6ddb28e9a5caea36a6f98e8ecd2a524f9127354087ce780885d83/scipy-1.17.0-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:979c3a0ff8e5ba254d45d59ebd38cde48fce4f10b5125c680c7a4bfe177aab07", size = 22791051, upload-time = "2026-01-10T21:27:46.539Z" }, + { url = "https://files.pythonhosted.org/packages/9b/9d/025cccdd738a72140efc582b1641d0dd4caf2e86c3fb127568dc80444e6e/scipy-1.17.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:130d12926ae34399d157de777472bf82e9061c60cc081372b3118edacafe1d00", size = 32815098, upload-time = "2026-01-10T21:27:54.389Z" }, + { url = "https://files.pythonhosted.org/packages/48/5f/09b879619f8bca15ce392bfc1894bd9c54377e01d1b3f2f3b595a1b4d945/scipy-1.17.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e886000eb4919eae3a44f035e63f0fd8b651234117e8f6f29bad1cd26e7bc45", size = 35031342, upload-time = "2026-01-10T21:28:03.012Z" }, + { url = "https://files.pythonhosted.org/packages/f2/9a/f0f0a9f0aa079d2f106555b984ff0fbb11a837df280f04f71f056ea9c6e4/scipy-1.17.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:13c4096ac6bc31d706018f06a49abe0485f96499deb82066b94d19b02f664209", size = 34893199, upload-time = "2026-01-10T21:28:10.832Z" }, + { url = "https://files.pythonhosted.org/packages/90/b8/4f0f5cf0c5ea4d7548424e6533e6b17d164f34a6e2fb2e43ffebb6697b06/scipy-1.17.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cacbaddd91fcffde703934897c5cd2c7cb0371fac195d383f4e1f1c5d3f3bd04", size = 37438061, upload-time = "2026-01-10T21:28:19.684Z" }, + { url = "https://files.pythonhosted.org/packages/f9/cc/2bd59140ed3b2fa2882fb15da0a9cb1b5a6443d67cfd0d98d4cec83a57ec/scipy-1.17.0-cp313-cp313t-win_amd64.whl", hash = "sha256:edce1a1cf66298cccdc48a1bdf8fb10a3bf58e8b58d6c3883dd1530e103f87c0", size = 36328593, upload-time = "2026-01-10T21:28:28.007Z" }, + { url = "https://files.pythonhosted.org/packages/13/1b/c87cc44a0d2c7aaf0f003aef2904c3d097b422a96c7e7c07f5efd9073c1b/scipy-1.17.0-cp313-cp313t-win_arm64.whl", hash = "sha256:30509da9dbec1c2ed8f168b8d8aa853bc6723fede1dbc23c7d43a56f5ab72a67", size = 24625083, upload-time = "2026-01-10T21:28:35.188Z" }, + { url = "https://files.pythonhosted.org/packages/1a/2d/51006cd369b8e7879e1c630999a19d1fbf6f8b5ed3e33374f29dc87e53b3/scipy-1.17.0-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:c17514d11b78be8f7e6331b983a65a7f5ca1fd037b95e27b280921fe5606286a", size = 31346803, upload-time = "2026-01-10T21:28:57.24Z" }, + { url = "https://files.pythonhosted.org/packages/d6/2e/2349458c3ce445f53a6c93d4386b1c4c5c0c540917304c01222ff95ff317/scipy-1.17.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:4e00562e519c09da34c31685f6acc3aa384d4d50604db0f245c14e1b4488bfa2", size = 27967182, upload-time = "2026-01-10T21:29:04.107Z" }, + { url = "https://files.pythonhosted.org/packages/5e/7c/df525fbfa77b878d1cfe625249529514dc02f4fd5f45f0f6295676a76528/scipy-1.17.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:f7df7941d71314e60a481e02d5ebcb3f0185b8d799c70d03d8258f6c80f3d467", size = 20139125, upload-time = "2026-01-10T21:29:10.179Z" }, + { url = "https://files.pythonhosted.org/packages/33/11/fcf9d43a7ed1234d31765ec643b0515a85a30b58eddccc5d5a4d12b5f194/scipy-1.17.0-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:aabf057c632798832f071a8dde013c2e26284043934f53b00489f1773b33527e", size = 22443554, upload-time = "2026-01-10T21:29:15.888Z" }, + { url = "https://files.pythonhosted.org/packages/80/5c/ea5d239cda2dd3d31399424967a24d556cf409fbea7b5b21412b0fd0a44f/scipy-1.17.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a38c3337e00be6fd8a95b4ed66b5d988bac4ec888fd922c2ea9fe5fb1603dd67", size = 32757834, upload-time = "2026-01-10T21:29:23.406Z" }, + { url = "https://files.pythonhosted.org/packages/b8/7e/8c917cc573310e5dc91cbeead76f1b600d3fb17cf0969db02c9cf92e3cfa/scipy-1.17.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00fb5f8ec8398ad90215008d8b6009c9db9fa924fd4c7d6be307c6f945f9cd73", size = 34995775, upload-time = "2026-01-10T21:29:31.915Z" }, + { url = "https://files.pythonhosted.org/packages/c5/43/176c0c3c07b3f7df324e7cdd933d3e2c4898ca202b090bd5ba122f9fe270/scipy-1.17.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f2a4942b0f5f7c23c7cd641a0ca1955e2ae83dedcff537e3a0259096635e186b", size = 34841240, upload-time = "2026-01-10T21:29:39.995Z" }, + { url = "https://files.pythonhosted.org/packages/44/8c/d1f5f4b491160592e7f084d997de53a8e896a3ac01cd07e59f43ca222744/scipy-1.17.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:dbf133ced83889583156566d2bdf7a07ff89228fe0c0cb727f777de92092ec6b", size = 37394463, upload-time = "2026-01-10T21:29:48.723Z" }, + { url = "https://files.pythonhosted.org/packages/9f/ec/42a6657f8d2d087e750e9a5dde0b481fd135657f09eaf1cf5688bb23c338/scipy-1.17.0-cp314-cp314-win_amd64.whl", hash = "sha256:3625c631a7acd7cfd929e4e31d2582cf00f42fcf06011f59281271746d77e061", size = 37053015, upload-time = "2026-01-10T21:30:51.418Z" }, + { url = "https://files.pythonhosted.org/packages/27/58/6b89a6afd132787d89a362d443a7bddd511b8f41336a1ae47f9e4f000dc4/scipy-1.17.0-cp314-cp314-win_arm64.whl", hash = "sha256:9244608d27eafe02b20558523ba57f15c689357c85bdcfe920b1828750aa26eb", size = 24951312, upload-time = "2026-01-10T21:30:56.771Z" }, + { url = "https://files.pythonhosted.org/packages/e9/01/f58916b9d9ae0112b86d7c3b10b9e685625ce6e8248df139d0fcb17f7397/scipy-1.17.0-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:2b531f57e09c946f56ad0b4a3b2abee778789097871fc541e267d2eca081cff1", size = 31706502, upload-time = "2026-01-10T21:29:56.326Z" }, + { url = "https://files.pythonhosted.org/packages/59/8e/2912a87f94a7d1f8b38aabc0faf74b82d3b6c9e22be991c49979f0eceed8/scipy-1.17.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:13e861634a2c480bd237deb69333ac79ea1941b94568d4b0efa5db5e263d4fd1", size = 28380854, upload-time = "2026-01-10T21:30:01.554Z" }, + { url = "https://files.pythonhosted.org/packages/bd/1c/874137a52dddab7d5d595c1887089a2125d27d0601fce8c0026a24a92a0b/scipy-1.17.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:eb2651271135154aa24f6481cbae5cc8af1f0dd46e6533fb7b56aa9727b6a232", size = 20552752, upload-time = "2026-01-10T21:30:05.93Z" }, + { url = "https://files.pythonhosted.org/packages/3f/f0/7518d171cb735f6400f4576cf70f756d5b419a07fe1867da34e2c2c9c11b/scipy-1.17.0-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:c5e8647f60679790c2f5c76be17e2e9247dc6b98ad0d3b065861e082c56e078d", size = 22803972, upload-time = "2026-01-10T21:30:10.651Z" }, + { url = "https://files.pythonhosted.org/packages/7c/74/3498563a2c619e8a3ebb4d75457486c249b19b5b04a30600dfd9af06bea5/scipy-1.17.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5fb10d17e649e1446410895639f3385fd2bf4c3c7dfc9bea937bddcbc3d7b9ba", size = 32829770, upload-time = "2026-01-10T21:30:16.359Z" }, + { url = "https://files.pythonhosted.org/packages/48/d1/7b50cedd8c6c9d6f706b4b36fa8544d829c712a75e370f763b318e9638c1/scipy-1.17.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8547e7c57f932e7354a2319fab613981cde910631979f74c9b542bb167a8b9db", size = 35051093, upload-time = "2026-01-10T21:30:22.987Z" }, + { url = "https://files.pythonhosted.org/packages/e2/82/a2d684dfddb87ba1b3ea325df7c3293496ee9accb3a19abe9429bce94755/scipy-1.17.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:33af70d040e8af9d5e7a38b5ed3b772adddd281e3062ff23fec49e49681c38cf", size = 34909905, upload-time = "2026-01-10T21:30:28.704Z" }, + { url = "https://files.pythonhosted.org/packages/ef/5e/e565bd73991d42023eb82bb99e51c5b3d9e2c588ca9d4b3e2cc1d3ca62a6/scipy-1.17.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f9eb55bb97d00f8b7ab95cb64f873eb0bf54d9446264d9f3609130381233483f", size = 37457743, upload-time = "2026-01-10T21:30:34.819Z" }, + { url = "https://files.pythonhosted.org/packages/58/a8/a66a75c3d8f1fb2b83f66007d6455a06a6f6cf5618c3dc35bc9b69dd096e/scipy-1.17.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1ff269abf702f6c7e67a4b7aad981d42871a11b9dd83c58d2d2ea624efbd1088", size = 37098574, upload-time = "2026-01-10T21:30:40.782Z" }, + { url = "https://files.pythonhosted.org/packages/56/a5/df8f46ef7da168f1bc52cd86e09a9de5c6f19cc1da04454d51b7d4f43408/scipy-1.17.0-cp314-cp314t-win_arm64.whl", hash = "sha256:031121914e295d9791319a1875444d55079885bbae5bdc9c5e0f2ee5f09d34ff", size = 25246266, upload-time = "2026-01-10T21:30:45.923Z" }, ] [[package]] name = "sentry-sdk" -version = "2.48.0" +version = "2.53.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "certifi" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/40/f0/0e9dc590513d5e742d7799e2038df3a05167cba084c6ca4f3cdd75b55164/sentry_sdk-2.48.0.tar.gz", hash = "sha256:5213190977ff7fdff8a58b722fb807f8d5524a80488626ebeda1b5676c0c1473", size = 384828, upload-time = "2025-12-16T14:55:41.722Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d3/06/66c8b705179bc54087845f28fd1b72f83751b6e9a195628e2e9af9926505/sentry_sdk-2.53.0.tar.gz", hash = "sha256:6520ef2c4acd823f28efc55e43eb6ce2e6d9f954a95a3aa96b6fd14871e92b77", size = 412369, upload-time = "2026-02-16T11:11:14.743Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/4d/19/8d77f9992e5cbfcaa9133c3bf63b4fbbb051248802e1e803fed5c552fbb2/sentry_sdk-2.48.0-py2.py3-none-any.whl", hash = "sha256:6b12ac256769d41825d9b7518444e57fa35b5642df4c7c5e322af4d2c8721172", size = 414555, upload-time = "2025-12-16T14:55:40.152Z" }, + { url = "https://files.pythonhosted.org/packages/47/d4/2fdf854bc3b9c7f55219678f812600a20a138af2dd847d99004994eada8f/sentry_sdk-2.53.0-py2.py3-none-any.whl", hash = "sha256:46e1ed8d84355ae54406c924f6b290c3d61f4048625989a723fd622aab838899", size = 437908, upload-time = "2026-02-16T11:11:13.227Z" }, ] [[package]] @@ -2840,78 +2898,83 @@ wheels = [ [[package]] name = "tomli" -version = "2.3.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/52/ed/3f73f72945444548f33eba9a87fc7a6e969915e7b1acc8260b30e1f76a2f/tomli-2.3.0.tar.gz", hash = "sha256:64be704a875d2a59753d80ee8a533c3fe183e3f06807ff7dc2232938ccb01549", size = 17392, upload-time = "2025-10-08T22:01:47.119Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/2e/299f62b401438d5fe1624119c723f5d877acc86a4c2492da405626665f12/tomli-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:88bd15eb972f3664f5ed4b57c1634a97153b4bac4479dcb6a495f41921eb7f45", size = 153236, upload-time = "2025-10-08T22:01:00.137Z" }, - { url = "https://files.pythonhosted.org/packages/86/7f/d8fffe6a7aefdb61bced88fcb5e280cfd71e08939da5894161bd71bea022/tomli-2.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:883b1c0d6398a6a9d29b508c331fa56adbcdff647f6ace4dfca0f50e90dfd0ba", size = 148084, upload-time = "2025-10-08T22:01:01.63Z" }, - { url = "https://files.pythonhosted.org/packages/47/5c/24935fb6a2ee63e86d80e4d3b58b222dafaf438c416752c8b58537c8b89a/tomli-2.3.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1381caf13ab9f300e30dd8feadb3de072aeb86f1d34a8569453ff32a7dea4bf", size = 234832, upload-time = "2025-10-08T22:01:02.543Z" }, - { url = "https://files.pythonhosted.org/packages/89/da/75dfd804fc11e6612846758a23f13271b76d577e299592b4371a4ca4cd09/tomli-2.3.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a0e285d2649b78c0d9027570d4da3425bdb49830a6156121360b3f8511ea3441", size = 242052, upload-time = "2025-10-08T22:01:03.836Z" }, - { url = "https://files.pythonhosted.org/packages/70/8c/f48ac899f7b3ca7eb13af73bacbc93aec37f9c954df3c08ad96991c8c373/tomli-2.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0a154a9ae14bfcf5d8917a59b51ffd5a3ac1fd149b71b47a3a104ca4edcfa845", size = 239555, upload-time = "2025-10-08T22:01:04.834Z" }, - { url = "https://files.pythonhosted.org/packages/ba/28/72f8afd73f1d0e7829bfc093f4cb98ce0a40ffc0cc997009ee1ed94ba705/tomli-2.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:74bf8464ff93e413514fefd2be591c3b0b23231a77f901db1eb30d6f712fc42c", size = 245128, upload-time = "2025-10-08T22:01:05.84Z" }, - { url = "https://files.pythonhosted.org/packages/b6/eb/a7679c8ac85208706d27436e8d421dfa39d4c914dcf5fa8083a9305f58d9/tomli-2.3.0-cp311-cp311-win32.whl", hash = "sha256:00b5f5d95bbfc7d12f91ad8c593a1659b6387b43f054104cda404be6bda62456", size = 96445, upload-time = "2025-10-08T22:01:06.896Z" }, - { url = "https://files.pythonhosted.org/packages/0a/fe/3d3420c4cb1ad9cb462fb52967080575f15898da97e21cb6f1361d505383/tomli-2.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:4dc4ce8483a5d429ab602f111a93a6ab1ed425eae3122032db7e9acf449451be", size = 107165, upload-time = "2025-10-08T22:01:08.107Z" }, - { url = "https://files.pythonhosted.org/packages/ff/b7/40f36368fcabc518bb11c8f06379a0fd631985046c038aca08c6d6a43c6e/tomli-2.3.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d7d86942e56ded512a594786a5ba0a5e521d02529b3826e7761a05138341a2ac", size = 154891, upload-time = "2025-10-08T22:01:09.082Z" }, - { url = "https://files.pythonhosted.org/packages/f9/3f/d9dd692199e3b3aab2e4e4dd948abd0f790d9ded8cd10cbaae276a898434/tomli-2.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:73ee0b47d4dad1c5e996e3cd33b8a76a50167ae5f96a2607cbe8cc773506ab22", size = 148796, upload-time = "2025-10-08T22:01:10.266Z" }, - { url = "https://files.pythonhosted.org/packages/60/83/59bff4996c2cf9f9387a0f5a3394629c7efa5ef16142076a23a90f1955fa/tomli-2.3.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:792262b94d5d0a466afb5bc63c7daa9d75520110971ee269152083270998316f", size = 242121, upload-time = "2025-10-08T22:01:11.332Z" }, - { url = "https://files.pythonhosted.org/packages/45/e5/7c5119ff39de8693d6baab6c0b6dcb556d192c165596e9fc231ea1052041/tomli-2.3.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f195fe57ecceac95a66a75ac24d9d5fbc98ef0962e09b2eddec5d39375aae52", size = 250070, upload-time = "2025-10-08T22:01:12.498Z" }, - { url = "https://files.pythonhosted.org/packages/45/12/ad5126d3a278f27e6701abde51d342aa78d06e27ce2bb596a01f7709a5a2/tomli-2.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e31d432427dcbf4d86958c184b9bfd1e96b5b71f8eb17e6d02531f434fd335b8", size = 245859, upload-time = "2025-10-08T22:01:13.551Z" }, - { url = "https://files.pythonhosted.org/packages/fb/a1/4d6865da6a71c603cfe6ad0e6556c73c76548557a8d658f9e3b142df245f/tomli-2.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b0882799624980785240ab732537fcfc372601015c00f7fc367c55308c186f6", size = 250296, upload-time = "2025-10-08T22:01:14.614Z" }, - { url = "https://files.pythonhosted.org/packages/a0/b7/a7a7042715d55c9ba6e8b196d65d2cb662578b4d8cd17d882d45322b0d78/tomli-2.3.0-cp312-cp312-win32.whl", hash = "sha256:ff72b71b5d10d22ecb084d345fc26f42b5143c5533db5e2eaba7d2d335358876", size = 97124, upload-time = "2025-10-08T22:01:15.629Z" }, - { url = "https://files.pythonhosted.org/packages/06/1e/f22f100db15a68b520664eb3328fb0ae4e90530887928558112c8d1f4515/tomli-2.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:1cb4ed918939151a03f33d4242ccd0aa5f11b3547d0cf30f7c74a408a5b99878", size = 107698, upload-time = "2025-10-08T22:01:16.51Z" }, - { url = "https://files.pythonhosted.org/packages/89/48/06ee6eabe4fdd9ecd48bf488f4ac783844fd777f547b8d1b61c11939974e/tomli-2.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5192f562738228945d7b13d4930baffda67b69425a7f0da96d360b0a3888136b", size = 154819, upload-time = "2025-10-08T22:01:17.964Z" }, - { url = "https://files.pythonhosted.org/packages/f1/01/88793757d54d8937015c75dcdfb673c65471945f6be98e6a0410fba167ed/tomli-2.3.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:be71c93a63d738597996be9528f4abe628d1adf5e6eb11607bc8fe1a510b5dae", size = 148766, upload-time = "2025-10-08T22:01:18.959Z" }, - { url = "https://files.pythonhosted.org/packages/42/17/5e2c956f0144b812e7e107f94f1cc54af734eb17b5191c0bbfb72de5e93e/tomli-2.3.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c4665508bcbac83a31ff8ab08f424b665200c0e1e645d2bd9ab3d3e557b6185b", size = 240771, upload-time = "2025-10-08T22:01:20.106Z" }, - { url = "https://files.pythonhosted.org/packages/d5/f4/0fbd014909748706c01d16824eadb0307115f9562a15cbb012cd9b3512c5/tomli-2.3.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4021923f97266babc6ccab9f5068642a0095faa0a51a246a6a02fccbb3514eaf", size = 248586, upload-time = "2025-10-08T22:01:21.164Z" }, - { url = "https://files.pythonhosted.org/packages/30/77/fed85e114bde5e81ecf9bc5da0cc69f2914b38f4708c80ae67d0c10180c5/tomli-2.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4ea38c40145a357d513bffad0ed869f13c1773716cf71ccaa83b0fa0cc4e42f", size = 244792, upload-time = "2025-10-08T22:01:22.417Z" }, - { url = "https://files.pythonhosted.org/packages/55/92/afed3d497f7c186dc71e6ee6d4fcb0acfa5f7d0a1a2878f8beae379ae0cc/tomli-2.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ad805ea85eda330dbad64c7ea7a4556259665bdf9d2672f5dccc740eb9d3ca05", size = 248909, upload-time = "2025-10-08T22:01:23.859Z" }, - { url = "https://files.pythonhosted.org/packages/f8/84/ef50c51b5a9472e7265ce1ffc7f24cd4023d289e109f669bdb1553f6a7c2/tomli-2.3.0-cp313-cp313-win32.whl", hash = "sha256:97d5eec30149fd3294270e889b4234023f2c69747e555a27bd708828353ab606", size = 96946, upload-time = "2025-10-08T22:01:24.893Z" }, - { url = "https://files.pythonhosted.org/packages/b2/b7/718cd1da0884f281f95ccfa3a6cc572d30053cba64603f79d431d3c9b61b/tomli-2.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:0c95ca56fbe89e065c6ead5b593ee64b84a26fca063b5d71a1122bf26e533999", size = 107705, upload-time = "2025-10-08T22:01:26.153Z" }, - { url = "https://files.pythonhosted.org/packages/19/94/aeafa14a52e16163008060506fcb6aa1949d13548d13752171a755c65611/tomli-2.3.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:cebc6fe843e0733ee827a282aca4999b596241195f43b4cc371d64fc6639da9e", size = 154244, upload-time = "2025-10-08T22:01:27.06Z" }, - { url = "https://files.pythonhosted.org/packages/db/e4/1e58409aa78eefa47ccd19779fc6f36787edbe7d4cd330eeeedb33a4515b/tomli-2.3.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:4c2ef0244c75aba9355561272009d934953817c49f47d768070c3c94355c2aa3", size = 148637, upload-time = "2025-10-08T22:01:28.059Z" }, - { url = "https://files.pythonhosted.org/packages/26/b6/d1eccb62f665e44359226811064596dd6a366ea1f985839c566cd61525ae/tomli-2.3.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c22a8bf253bacc0cf11f35ad9808b6cb75ada2631c2d97c971122583b129afbc", size = 241925, upload-time = "2025-10-08T22:01:29.066Z" }, - { url = "https://files.pythonhosted.org/packages/70/91/7cdab9a03e6d3d2bb11beae108da5bdc1c34bdeb06e21163482544ddcc90/tomli-2.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0eea8cc5c5e9f89c9b90c4896a8deefc74f518db5927d0e0e8d4a80953d774d0", size = 249045, upload-time = "2025-10-08T22:01:31.98Z" }, - { url = "https://files.pythonhosted.org/packages/15/1b/8c26874ed1f6e4f1fcfeb868db8a794cbe9f227299402db58cfcc858766c/tomli-2.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b74a0e59ec5d15127acdabd75ea17726ac4c5178ae51b85bfe39c4f8a278e879", size = 245835, upload-time = "2025-10-08T22:01:32.989Z" }, - { url = "https://files.pythonhosted.org/packages/fd/42/8e3c6a9a4b1a1360c1a2a39f0b972cef2cc9ebd56025168c4137192a9321/tomli-2.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:b5870b50c9db823c595983571d1296a6ff3e1b88f734a4c8f6fc6188397de005", size = 253109, upload-time = "2025-10-08T22:01:34.052Z" }, - { url = "https://files.pythonhosted.org/packages/22/0c/b4da635000a71b5f80130937eeac12e686eefb376b8dee113b4a582bba42/tomli-2.3.0-cp314-cp314-win32.whl", hash = "sha256:feb0dacc61170ed7ab602d3d972a58f14ee3ee60494292d384649a3dc38ef463", size = 97930, upload-time = "2025-10-08T22:01:35.082Z" }, - { url = "https://files.pythonhosted.org/packages/b9/74/cb1abc870a418ae99cd5c9547d6bce30701a954e0e721821df483ef7223c/tomli-2.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:b273fcbd7fc64dc3600c098e39136522650c49bca95df2d11cf3b626422392c8", size = 107964, upload-time = "2025-10-08T22:01:36.057Z" }, - { url = "https://files.pythonhosted.org/packages/54/78/5c46fff6432a712af9f792944f4fcd7067d8823157949f4e40c56b8b3c83/tomli-2.3.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:940d56ee0410fa17ee1f12b817b37a4d4e4dc4d27340863cc67236c74f582e77", size = 163065, upload-time = "2025-10-08T22:01:37.27Z" }, - { url = "https://files.pythonhosted.org/packages/39/67/f85d9bd23182f45eca8939cd2bc7050e1f90c41f4a2ecbbd5963a1d1c486/tomli-2.3.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:f85209946d1fe94416debbb88d00eb92ce9cd5266775424ff81bc959e001acaf", size = 159088, upload-time = "2025-10-08T22:01:38.235Z" }, - { url = "https://files.pythonhosted.org/packages/26/5a/4b546a0405b9cc0659b399f12b6adb750757baf04250b148d3c5059fc4eb/tomli-2.3.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a56212bdcce682e56b0aaf79e869ba5d15a6163f88d5451cbde388d48b13f530", size = 268193, upload-time = "2025-10-08T22:01:39.712Z" }, - { url = "https://files.pythonhosted.org/packages/42/4f/2c12a72ae22cf7b59a7fe75b3465b7aba40ea9145d026ba41cb382075b0e/tomli-2.3.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c5f3ffd1e098dfc032d4d3af5c0ac64f6d286d98bc148698356847b80fa4de1b", size = 275488, upload-time = "2025-10-08T22:01:40.773Z" }, - { url = "https://files.pythonhosted.org/packages/92/04/a038d65dbe160c3aa5a624e93ad98111090f6804027d474ba9c37c8ae186/tomli-2.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5e01decd096b1530d97d5d85cb4dff4af2d8347bd35686654a004f8dea20fc67", size = 272669, upload-time = "2025-10-08T22:01:41.824Z" }, - { url = "https://files.pythonhosted.org/packages/be/2f/8b7c60a9d1612a7cbc39ffcca4f21a73bf368a80fc25bccf8253e2563267/tomli-2.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8a35dd0e643bb2610f156cca8db95d213a90015c11fee76c946aa62b7ae7e02f", size = 279709, upload-time = "2025-10-08T22:01:43.177Z" }, - { url = "https://files.pythonhosted.org/packages/7e/46/cc36c679f09f27ded940281c38607716c86cf8ba4a518d524e349c8b4874/tomli-2.3.0-cp314-cp314t-win32.whl", hash = "sha256:a1f7f282fe248311650081faafa5f4732bdbfef5d45fe3f2e702fbc6f2d496e0", size = 107563, upload-time = "2025-10-08T22:01:44.233Z" }, - { url = "https://files.pythonhosted.org/packages/84/ff/426ca8683cf7b753614480484f6437f568fd2fda2edbdf57a2d3d8b27a0b/tomli-2.3.0-cp314-cp314t-win_amd64.whl", hash = "sha256:70a251f8d4ba2d9ac2542eecf008b3c8a9fc5c3f9f02c56a9d7952612be2fdba", size = 119756, upload-time = "2025-10-08T22:01:45.234Z" }, - { url = "https://files.pythonhosted.org/packages/77/b8/0135fadc89e73be292b473cb820b4f5a08197779206b33191e801feeae40/tomli-2.3.0-py3-none-any.whl", hash = "sha256:e95b1af3c5b07d9e643909b5abbec77cd9f1217e6d0bca72b0234736b9fb1f1b", size = 14408, upload-time = "2025-10-08T22:01:46.04Z" }, +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/30/31573e9457673ab10aa432461bee537ce6cef177667deca369efb79df071/tomli-2.4.0.tar.gz", hash = "sha256:aa89c3f6c277dd275d8e243ad24f3b5e701491a860d5121f2cdd399fbb31fc9c", size = 17477, upload-time = "2026-01-11T11:22:38.165Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3c/d9/3dc2289e1f3b32eb19b9785b6a006b28ee99acb37d1d47f78d4c10e28bf8/tomli-2.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b5ef256a3fd497d4973c11bf142e9ed78b150d36f5773f1ca6088c230ffc5867", size = 153663, upload-time = "2026-01-11T11:21:45.27Z" }, + { url = "https://files.pythonhosted.org/packages/51/32/ef9f6845e6b9ca392cd3f64f9ec185cc6f09f0a2df3db08cbe8809d1d435/tomli-2.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5572e41282d5268eb09a697c89a7bee84fae66511f87533a6f88bd2f7b652da9", size = 148469, upload-time = "2026-01-11T11:21:46.873Z" }, + { url = "https://files.pythonhosted.org/packages/d6/c2/506e44cce89a8b1b1e047d64bd495c22c9f71f21e05f380f1a950dd9c217/tomli-2.4.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:551e321c6ba03b55676970b47cb1b73f14a0a4dce6a3e1a9458fd6d921d72e95", size = 236039, upload-time = "2026-01-11T11:21:48.503Z" }, + { url = "https://files.pythonhosted.org/packages/b3/40/e1b65986dbc861b7e986e8ec394598187fa8aee85b1650b01dd925ca0be8/tomli-2.4.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5e3f639a7a8f10069d0e15408c0b96a2a828cfdec6fca05296ebcdcc28ca7c76", size = 243007, upload-time = "2026-01-11T11:21:49.456Z" }, + { url = "https://files.pythonhosted.org/packages/9c/6f/6e39ce66b58a5b7ae572a0f4352ff40c71e8573633deda43f6a379d56b3e/tomli-2.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1b168f2731796b045128c45982d3a4874057626da0e2ef1fdd722848b741361d", size = 240875, upload-time = "2026-01-11T11:21:50.755Z" }, + { url = "https://files.pythonhosted.org/packages/aa/ad/cb089cb190487caa80204d503c7fd0f4d443f90b95cf4ef5cf5aa0f439b0/tomli-2.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:133e93646ec4300d651839d382d63edff11d8978be23da4cc106f5a18b7d0576", size = 246271, upload-time = "2026-01-11T11:21:51.81Z" }, + { url = "https://files.pythonhosted.org/packages/0b/63/69125220e47fd7a3a27fd0de0c6398c89432fec41bc739823bcc66506af6/tomli-2.4.0-cp311-cp311-win32.whl", hash = "sha256:b6c78bdf37764092d369722d9946cb65b8767bfa4110f902a1b2542d8d173c8a", size = 96770, upload-time = "2026-01-11T11:21:52.647Z" }, + { url = "https://files.pythonhosted.org/packages/1e/0d/a22bb6c83f83386b0008425a6cd1fa1c14b5f3dd4bad05e98cf3dbbf4a64/tomli-2.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa", size = 107626, upload-time = "2026-01-11T11:21:53.459Z" }, + { url = "https://files.pythonhosted.org/packages/2f/6d/77be674a3485e75cacbf2ddba2b146911477bd887dda9d8c9dfb2f15e871/tomli-2.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:cae9c19ed12d4e8f3ebf46d1a75090e4c0dc16271c5bce1c833ac168f08fb614", size = 94842, upload-time = "2026-01-11T11:21:54.831Z" }, + { url = "https://files.pythonhosted.org/packages/3c/43/7389a1869f2f26dba52404e1ef13b4784b6b37dac93bac53457e3ff24ca3/tomli-2.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:920b1de295e72887bafa3ad9f7a792f811847d57ea6b1215154030cf131f16b1", size = 154894, upload-time = "2026-01-11T11:21:56.07Z" }, + { url = "https://files.pythonhosted.org/packages/e9/05/2f9bf110b5294132b2edf13fe6ca6ae456204f3d749f623307cbb7a946f2/tomli-2.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7d6d9a4aee98fac3eab4952ad1d73aee87359452d1c086b5ceb43ed02ddb16b8", size = 149053, upload-time = "2026-01-11T11:21:57.467Z" }, + { url = "https://files.pythonhosted.org/packages/e8/41/1eda3ca1abc6f6154a8db4d714a4d35c4ad90adc0bcf700657291593fbf3/tomli-2.4.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36b9d05b51e65b254ea6c2585b59d2c4cb91c8a3d91d0ed0f17591a29aaea54a", size = 243481, upload-time = "2026-01-11T11:21:58.661Z" }, + { url = "https://files.pythonhosted.org/packages/d2/6d/02ff5ab6c8868b41e7d4b987ce2b5f6a51d3335a70aa144edd999e055a01/tomli-2.4.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c8a885b370751837c029ef9bc014f27d80840e48bac415f3412e6593bbc18c1", size = 251720, upload-time = "2026-01-11T11:22:00.178Z" }, + { url = "https://files.pythonhosted.org/packages/7b/57/0405c59a909c45d5b6f146107c6d997825aa87568b042042f7a9c0afed34/tomli-2.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8768715ffc41f0008abe25d808c20c3d990f42b6e2e58305d5da280ae7d1fa3b", size = 247014, upload-time = "2026-01-11T11:22:01.238Z" }, + { url = "https://files.pythonhosted.org/packages/2c/0e/2e37568edd944b4165735687cbaf2fe3648129e440c26d02223672ee0630/tomli-2.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7b438885858efd5be02a9a133caf5812b8776ee0c969fea02c45e8e3f296ba51", size = 251820, upload-time = "2026-01-11T11:22:02.727Z" }, + { url = "https://files.pythonhosted.org/packages/5a/1c/ee3b707fdac82aeeb92d1a113f803cf6d0f37bdca0849cb489553e1f417a/tomli-2.4.0-cp312-cp312-win32.whl", hash = "sha256:0408e3de5ec77cc7f81960c362543cbbd91ef883e3138e81b729fc3eea5b9729", size = 97712, upload-time = "2026-01-11T11:22:03.777Z" }, + { url = "https://files.pythonhosted.org/packages/69/13/c07a9177d0b3bab7913299b9278845fc6eaaca14a02667c6be0b0a2270c8/tomli-2.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:685306e2cc7da35be4ee914fd34ab801a6acacb061b6a7abca922aaf9ad368da", size = 108296, upload-time = "2026-01-11T11:22:04.86Z" }, + { url = "https://files.pythonhosted.org/packages/18/27/e267a60bbeeee343bcc279bb9e8fbed0cbe224bc7b2a3dc2975f22809a09/tomli-2.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:5aa48d7c2356055feef06a43611fc401a07337d5b006be13a30f6c58f869e3c3", size = 94553, upload-time = "2026-01-11T11:22:05.854Z" }, + { url = "https://files.pythonhosted.org/packages/34/91/7f65f9809f2936e1f4ce6268ae1903074563603b2a2bd969ebbda802744f/tomli-2.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:84d081fbc252d1b6a982e1870660e7330fb8f90f676f6e78b052ad4e64714bf0", size = 154915, upload-time = "2026-01-11T11:22:06.703Z" }, + { url = "https://files.pythonhosted.org/packages/20/aa/64dd73a5a849c2e8f216b755599c511badde80e91e9bc2271baa7b2cdbb1/tomli-2.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9a08144fa4cba33db5255f9b74f0b89888622109bd2776148f2597447f92a94e", size = 149038, upload-time = "2026-01-11T11:22:07.56Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8a/6d38870bd3d52c8d1505ce054469a73f73a0fe62c0eaf5dddf61447e32fa/tomli-2.4.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c73add4bb52a206fd0c0723432db123c0c75c280cbd67174dd9d2db228ebb1b4", size = 242245, upload-time = "2026-01-11T11:22:08.344Z" }, + { url = "https://files.pythonhosted.org/packages/59/bb/8002fadefb64ab2669e5b977df3f5e444febea60e717e755b38bb7c41029/tomli-2.4.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fb2945cbe303b1419e2706e711b7113da57b7db31ee378d08712d678a34e51e", size = 250335, upload-time = "2026-01-11T11:22:09.951Z" }, + { url = "https://files.pythonhosted.org/packages/a5/3d/4cdb6f791682b2ea916af2de96121b3cb1284d7c203d97d92d6003e91c8d/tomli-2.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bbb1b10aa643d973366dc2cb1ad94f99c1726a02343d43cbc011edbfac579e7c", size = 245962, upload-time = "2026-01-11T11:22:11.27Z" }, + { url = "https://files.pythonhosted.org/packages/f2/4a/5f25789f9a460bd858ba9756ff52d0830d825b458e13f754952dd15fb7bb/tomli-2.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4cbcb367d44a1f0c2be408758b43e1ffb5308abe0ea222897d6bfc8e8281ef2f", size = 250396, upload-time = "2026-01-11T11:22:12.325Z" }, + { url = "https://files.pythonhosted.org/packages/aa/2f/b73a36fea58dfa08e8b3a268750e6853a6aac2a349241a905ebd86f3047a/tomli-2.4.0-cp313-cp313-win32.whl", hash = "sha256:7d49c66a7d5e56ac959cb6fc583aff0651094ec071ba9ad43df785abc2320d86", size = 97530, upload-time = "2026-01-11T11:22:13.865Z" }, + { url = "https://files.pythonhosted.org/packages/3b/af/ca18c134b5d75de7e8dc551c5234eaba2e8e951f6b30139599b53de9c187/tomli-2.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:3cf226acb51d8f1c394c1b310e0e0e61fecdd7adcb78d01e294ac297dd2e7f87", size = 108227, upload-time = "2026-01-11T11:22:15.224Z" }, + { url = "https://files.pythonhosted.org/packages/22/c3/b386b832f209fee8073c8138ec50f27b4460db2fdae9ffe022df89a57f9b/tomli-2.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:d20b797a5c1ad80c516e41bc1fb0443ddb5006e9aaa7bda2d71978346aeb9132", size = 94748, upload-time = "2026-01-11T11:22:16.009Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c4/84047a97eb1004418bc10bdbcfebda209fca6338002eba2dc27cc6d13563/tomli-2.4.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:26ab906a1eb794cd4e103691daa23d95c6919cc2fa9160000ac02370cc9dd3f6", size = 154725, upload-time = "2026-01-11T11:22:17.269Z" }, + { url = "https://files.pythonhosted.org/packages/a8/5d/d39038e646060b9d76274078cddf146ced86dc2b9e8bbf737ad5983609a0/tomli-2.4.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:20cedb4ee43278bc4f2fee6cb50daec836959aadaf948db5172e776dd3d993fc", size = 148901, upload-time = "2026-01-11T11:22:18.287Z" }, + { url = "https://files.pythonhosted.org/packages/73/e5/383be1724cb30f4ce44983d249645684a48c435e1cd4f8b5cded8a816d3c/tomli-2.4.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:39b0b5d1b6dd03684b3fb276407ebed7090bbec989fa55838c98560c01113b66", size = 243375, upload-time = "2026-01-11T11:22:19.154Z" }, + { url = "https://files.pythonhosted.org/packages/31/f0/bea80c17971c8d16d3cc109dc3585b0f2ce1036b5f4a8a183789023574f2/tomli-2.4.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a26d7ff68dfdb9f87a016ecfd1e1c2bacbe3108f4e0f8bcd2228ef9a766c787d", size = 250639, upload-time = "2026-01-11T11:22:20.168Z" }, + { url = "https://files.pythonhosted.org/packages/2c/8f/2853c36abbb7608e3f945d8a74e32ed3a74ee3a1f468f1ffc7d1cb3abba6/tomli-2.4.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:20ffd184fb1df76a66e34bd1b36b4a4641bd2b82954befa32fe8163e79f1a702", size = 246897, upload-time = "2026-01-11T11:22:21.544Z" }, + { url = "https://files.pythonhosted.org/packages/49/f0/6c05e3196ed5337b9fe7ea003e95fd3819a840b7a0f2bf5a408ef1dad8ed/tomli-2.4.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:75c2f8bbddf170e8effc98f5e9084a8751f8174ea6ccf4fca5398436e0320bc8", size = 254697, upload-time = "2026-01-11T11:22:23.058Z" }, + { url = "https://files.pythonhosted.org/packages/f3/f5/2922ef29c9f2951883525def7429967fc4d8208494e5ab524234f06b688b/tomli-2.4.0-cp314-cp314-win32.whl", hash = "sha256:31d556d079d72db7c584c0627ff3a24c5d3fb4f730221d3444f3efb1b2514776", size = 98567, upload-time = "2026-01-11T11:22:24.033Z" }, + { url = "https://files.pythonhosted.org/packages/7b/31/22b52e2e06dd2a5fdbc3ee73226d763b184ff21fc24e20316a44ccc4d96b/tomli-2.4.0-cp314-cp314-win_amd64.whl", hash = "sha256:43e685b9b2341681907759cf3a04e14d7104b3580f808cfde1dfdb60ada85475", size = 108556, upload-time = "2026-01-11T11:22:25.378Z" }, + { url = "https://files.pythonhosted.org/packages/48/3d/5058dff3255a3d01b705413f64f4306a141a8fd7a251e5a495e3f192a998/tomli-2.4.0-cp314-cp314-win_arm64.whl", hash = "sha256:3d895d56bd3f82ddd6faaff993c275efc2ff38e52322ea264122d72729dca2b2", size = 96014, upload-time = "2026-01-11T11:22:26.138Z" }, + { url = "https://files.pythonhosted.org/packages/b8/4e/75dab8586e268424202d3a1997ef6014919c941b50642a1682df43204c22/tomli-2.4.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:5b5807f3999fb66776dbce568cc9a828544244a8eb84b84b9bafc080c99597b9", size = 163339, upload-time = "2026-01-11T11:22:27.143Z" }, + { url = "https://files.pythonhosted.org/packages/06/e3/b904d9ab1016829a776d97f163f183a48be6a4deb87304d1e0116a349519/tomli-2.4.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c084ad935abe686bd9c898e62a02a19abfc9760b5a79bc29644463eaf2840cb0", size = 159490, upload-time = "2026-01-11T11:22:28.399Z" }, + { url = "https://files.pythonhosted.org/packages/e3/5a/fc3622c8b1ad823e8ea98a35e3c632ee316d48f66f80f9708ceb4f2a0322/tomli-2.4.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0f2e3955efea4d1cfbcb87bc321e00dc08d2bcb737fd1d5e398af111d86db5df", size = 269398, upload-time = "2026-01-11T11:22:29.345Z" }, + { url = "https://files.pythonhosted.org/packages/fd/33/62bd6152c8bdd4c305ad9faca48f51d3acb2df1f8791b1477d46ff86e7f8/tomli-2.4.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e0fe8a0b8312acf3a88077a0802565cb09ee34107813bba1c7cd591fa6cfc8d", size = 276515, upload-time = "2026-01-11T11:22:30.327Z" }, + { url = "https://files.pythonhosted.org/packages/4b/ff/ae53619499f5235ee4211e62a8d7982ba9e439a0fb4f2f351a93d67c1dd2/tomli-2.4.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:413540dce94673591859c4c6f794dfeaa845e98bf35d72ed59636f869ef9f86f", size = 273806, upload-time = "2026-01-11T11:22:32.56Z" }, + { url = "https://files.pythonhosted.org/packages/47/71/cbca7787fa68d4d0a9f7072821980b39fbb1b6faeb5f5cf02f4a5559fa28/tomli-2.4.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:0dc56fef0e2c1c470aeac5b6ca8cc7b640bb93e92d9803ddaf9ea03e198f5b0b", size = 281340, upload-time = "2026-01-11T11:22:33.505Z" }, + { url = "https://files.pythonhosted.org/packages/f5/00/d595c120963ad42474cf6ee7771ad0d0e8a49d0f01e29576ee9195d9ecdf/tomli-2.4.0-cp314-cp314t-win32.whl", hash = "sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087", size = 108106, upload-time = "2026-01-11T11:22:34.451Z" }, + { url = "https://files.pythonhosted.org/packages/de/69/9aa0c6a505c2f80e519b43764f8b4ba93b5a0bbd2d9a9de6e2b24271b9a5/tomli-2.4.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2add28aacc7425117ff6364fe9e06a183bb0251b03f986df0e78e974047571fd", size = 120504, upload-time = "2026-01-11T11:22:35.764Z" }, + { url = "https://files.pythonhosted.org/packages/b3/9f/f1668c281c58cfae01482f7114a4b88d345e4c140386241a1a24dcc9e7bc/tomli-2.4.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2b1e3b80e1d5e52e40e9b924ec43d81570f0e7d09d11081b797bc4692765a3d4", size = 99561, upload-time = "2026-01-11T11:22:36.624Z" }, + { url = "https://files.pythonhosted.org/packages/23/d1/136eb2cb77520a31e1f64cbae9d33ec6df0d78bdf4160398e86eec8a8754/tomli-2.4.0-py3-none-any.whl", hash = "sha256:1f776e7d669ebceb01dee46484485f43a4048746235e683bcdffacdf1fb4785a", size = 14477, upload-time = "2026-01-11T11:22:37.446Z" }, ] [[package]] name = "tqdm" -version = "4.67.1" +version = "4.67.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" } +sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, + { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" }, ] [[package]] name = "typer" -version = "0.21.1" +version = "0.24.0" source = { registry = "https://pypi.org/simple" } dependencies = [ + { name = "annotated-doc" }, { name = "click" }, { name = "rich" }, { name = "shellingham" }, - { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/36/bf/8825b5929afd84d0dabd606c67cd57b8388cb3ec385f7ef19c5cc2202069/typer-0.21.1.tar.gz", hash = "sha256:ea835607cd752343b6b2b7ce676893e5a0324082268b48f27aa058bdb7d2145d", size = 110371, upload-time = "2026-01-06T11:21:10.989Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b6/3e681d3b6bb22647509bdbfdd18055d5adc0dce5c5585359fa46ff805fdc/typer-0.24.0.tar.gz", hash = "sha256:f9373dc4eff901350694f519f783c29b6d7a110fc0dcc11b1d7e353b85ca6504", size = 118380, upload-time = "2026-02-16T22:08:48.496Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/1d/d9257dd49ff2ca23ea5f132edf1281a0c4f9de8a762b9ae399b670a59235/typer-0.21.1-py3-none-any.whl", hash = "sha256:7985e89081c636b88d172c2ee0cfe33c253160994d47bdfdc302defd7d1f1d01", size = 47381, upload-time = "2026-01-06T11:21:09.824Z" }, + { url = "https://files.pythonhosted.org/packages/85/d0/4da85c2a45054bb661993c93524138ace4956cb075a7ae0c9d1deadc331b/typer-0.24.0-py3-none-any.whl", hash = "sha256:5fc435a9c8356f6160ed6e85a6301fdd6e3d8b2851da502050d1f92c5e9eddc8", size = 56441, upload-time = "2026-02-16T22:08:47.535Z" }, ] [[package]] @@ -2937,11 +3000,11 @@ wheels = [ [[package]] name = "tzdata" -version = "2025.2" +version = "2025.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380, upload-time = "2025-03-23T13:54:43.652Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839, upload-time = "2025-03-23T13:54:41.845Z" }, + { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" }, ] [[package]] @@ -2955,29 +3018,29 @@ wheels = [ [[package]] name = "uvicorn" -version = "0.40.0" +version = "0.41.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "click" }, - { name = "h11" }, + { name = "click", marker = "sys_platform != 'emscripten'" }, + { name = "h11", marker = "sys_platform != 'emscripten'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c3/d1/8f3c683c9561a4e6689dd3b1d345c815f10f86acd044ee1fb9a4dcd0b8c5/uvicorn-0.40.0.tar.gz", hash = "sha256:839676675e87e73694518b5574fd0f24c9d97b46bea16df7b8c05ea1a51071ea", size = 81761, upload-time = "2025-12-21T14:16:22.45Z" } +sdist = { url = "https://files.pythonhosted.org/packages/32/ce/eeb58ae4ac36fe09e3842eb02e0eb676bf2c53ae062b98f1b2531673efdd/uvicorn-0.41.0.tar.gz", hash = "sha256:09d11cf7008da33113824ee5a1c6422d89fbc2ff476540d69a34c87fab8b571a", size = 82633, upload-time = "2026-02-16T23:07:24.1Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/d8/2083a1daa7439a66f3a48589a57d576aa117726762618f6bb09fe3798796/uvicorn-0.40.0-py3-none-any.whl", hash = "sha256:c6c8f55bc8bf13eb6fa9ff87ad62308bbbc33d0b67f84293151efe87e0d5f2ee", size = 68502, upload-time = "2025-12-21T14:16:21.041Z" }, + { url = "https://files.pythonhosted.org/packages/83/e4/d04a086285c20886c0daad0e026f250869201013d18f81d9ff5eada73a88/uvicorn-0.41.0-py3-none-any.whl", hash = "sha256:29e35b1d2c36a04b9e180d4007ede3bcb32a85fbdfd6c6aeb3f26839de088187", size = 68783, upload-time = "2026-02-16T23:07:22.357Z" }, ] [[package]] name = "virtualenv" -version = "20.36.1" +version = "20.37.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "distlib" }, { name = "filelock" }, { name = "platformdirs" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/aa/a3/4d310fa5f00863544e1d0f4de93bddec248499ccf97d4791bc3122c9d4f3/virtualenv-20.36.1.tar.gz", hash = "sha256:8befb5c81842c641f8ee658481e42641c68b5eab3521d8e092d18320902466ba", size = 6032239, upload-time = "2026-01-09T18:21:01.296Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c1/ef/d9d4ce633df789bf3430bd81fb0d8b9d9465dfc1d1f0deb3fb62cd80f5c2/virtualenv-20.37.0.tar.gz", hash = "sha256:6f7e2064ed470aa7418874e70b6369d53b66bcd9e9fd5389763e96b6c94ccb7c", size = 5864710, upload-time = "2026-02-16T16:17:59.42Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/2a/dc2228b2888f51192c7dc766106cd475f1b768c10caaf9727659726f7391/virtualenv-20.36.1-py3-none-any.whl", hash = "sha256:575a8d6b124ef88f6f51d56d656132389f961062a9177016a50e4f507bbcc19f", size = 6008258, upload-time = "2026-01-09T18:20:59.425Z" }, + { url = "https://files.pythonhosted.org/packages/42/4b/6cf85b485be7ec29db837ec2a1d8cd68bc1147b1abf23d8636c5bd65b3cc/virtualenv-20.37.0-py3-none-any.whl", hash = "sha256:5d3951c32d57232ae3569d4de4cc256c439e045135ebf43518131175d9be435d", size = 5837480, upload-time = "2026-02-16T16:17:57.341Z" }, ] [[package]] @@ -3009,46 +3072,46 @@ wheels = [ [[package]] name = "xlwings" -version = "0.33.16" +version = "0.33.20" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "appscript", marker = "sys_platform == 'darwin'" }, { name = "psutil", marker = "sys_platform == 'darwin'" }, { name = "pywin32", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ae/e3/4b4ff2248476b4a9cc71c7ebafdba642dafdf6c0559cf86fe44f73a5c6d0/xlwings-0.33.16.tar.gz", hash = "sha256:3ed1b93199d2cb4509ad9a3d4bab38ce483a8eaa39d38ffd034b7edac3241b51", size = 15705501, upload-time = "2025-10-10T16:13:34.197Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6f/59/d9f9ae712acd273396f6e58ccfbdc116f9deba269fbead95bf5760221384/xlwings-0.33.16-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d20e4a32b0dd76571ace70d03093ab933704f488c6db2f888550c9c435844817", size = 1418140, upload-time = "2025-10-10T16:13:48.709Z" }, - { url = "https://files.pythonhosted.org/packages/23/c5/a3c564294057b39de260e4b03f25d44f3c033ddcb78a0ecef576a3d98720/xlwings-0.33.16-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b2e145a06fd44755963c9ec3fd2a9d75c144f9024789264fb8a39bc125dd0af8", size = 1403121, upload-time = "2025-10-10T16:13:49.87Z" }, - { url = "https://files.pythonhosted.org/packages/31/cd/3ca7b20c96e8af552349e803aa620b4ffdeb0d4eee560b3f14c3e2b8a90d/xlwings-0.33.16-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65ac0f404f076a780b346239dc1a9efd4a2a6e45ca62f15d9a08b220a6de8d53", size = 1460325, upload-time = "2025-10-10T16:13:51.498Z" }, - { url = "https://files.pythonhosted.org/packages/4c/df/3a60d9ffe47f3178ca538678485e2ea69cbd442914f7a6340673c4bfeb7f/xlwings-0.33.16-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cb8146b24798198497c96df99c70d00c8d7b9c845d747162c438b91e192aa01d", size = 1464812, upload-time = "2025-10-10T16:13:53.045Z" }, - { url = "https://files.pythonhosted.org/packages/23/0a/5b4f66c26a212f6cfd715fa2658d3ce7902499214ea638300e0984add9ce/xlwings-0.33.16-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fbddd19a5fc80021ca6ff9cc8139b29164372457179f566e1b73b2b2bad4388b", size = 1465855, upload-time = "2025-10-10T16:13:54.465Z" }, - { url = "https://files.pythonhosted.org/packages/78/65/e00386d26091bce82ca85f03954d9c5250e9d9b8cb5738cf61468a428d99/xlwings-0.33.16-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:933624904fda923dfd6368d207c3b2c8668788d9a24264b3a608147b97463df9", size = 1641694, upload-time = "2025-10-10T16:13:55.892Z" }, - { url = "https://files.pythonhosted.org/packages/44/91/4b45453407bffa15ed8b6111f9122076663df7d579d048f0e1b396cfc772/xlwings-0.33.16-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:970ce94624a6e75a66ed57c8fb1ba2e50cbf4309e56563d0d3b96c03f40a5fcb", size = 1636945, upload-time = "2025-10-10T16:13:57.391Z" }, - { url = "https://files.pythonhosted.org/packages/6e/35/e2190c93bd615e059da270cefabdeecd2e417bc601e59d761e7adfb00d40/xlwings-0.33.16-cp311-cp311-win_amd64.whl", hash = "sha256:4459a39586d8001bf1e6d14d502c19eac52af1362cd3ac12d6c9d988d370f804", size = 1631025, upload-time = "2025-10-10T16:13:58.467Z" }, - { url = "https://files.pythonhosted.org/packages/16/bc/1d024c1f5b0487da5ee4fa3f71fc9e8140ff4ca6ed95f24d310b0bc428c1/xlwings-0.33.16-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:3332ea06de1f993c72101251948afd12e44f68d9738c85934a0d7f899743ce60", size = 1415246, upload-time = "2025-10-10T16:14:00.037Z" }, - { url = "https://files.pythonhosted.org/packages/28/90/4652e69dec171a0570e0ebf0dab1bcf637c7f74f4c81c3b5b9339344aacc/xlwings-0.33.16-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2c4bb59522fe6b2e6106b460d4dea8d5ab06e7d3599e0dbe7582c10c257c5ddb", size = 1399303, upload-time = "2025-10-10T16:14:01.704Z" }, - { url = "https://files.pythonhosted.org/packages/a6/af/02e48e007a6a2213cc775bf699feac46cd4a5f8abbef5ca9c52fac0b01f1/xlwings-0.33.16-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97aaa937438a58fb166192b09338a6b58bb9612167fd25b3afcde7ba3edf1664", size = 1459522, upload-time = "2025-10-10T16:14:02.835Z" }, - { url = "https://files.pythonhosted.org/packages/0d/e1/273d99eb6cf6da026ac58aff3feaa288591a949b6bb5563d908e204722cf/xlwings-0.33.16-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d705df6a731d17436bf2fe419e85b0eccb74877de977fe320cb31ad18097b4cc", size = 1463864, upload-time = "2025-10-10T16:14:04.568Z" }, - { url = "https://files.pythonhosted.org/packages/6b/7e/2aa1a2daba72512bc13483f424070db0649b6720d30f8527db8efb33c94e/xlwings-0.33.16-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4602f0a5fc2416f954c3b389ed732f1f5f3e4f6a7f35f1a5032b618323c85ba", size = 1465310, upload-time = "2025-10-10T16:14:05.716Z" }, - { url = "https://files.pythonhosted.org/packages/55/6c/3579de03d723378854228a79d94c31a43656bc2d7c4b205fd86a4f7505d1/xlwings-0.33.16-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f2ecb6b820ae9657e017bffd57c46676bb2afd98377931d2deb526e758e83eaa", size = 1641107, upload-time = "2025-10-10T16:14:07.318Z" }, - { url = "https://files.pythonhosted.org/packages/e5/f3/46f752a363b23454d6d285ce6132d22bbe125199979e9aaedbc744897cae/xlwings-0.33.16-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5992255711b11ce2130571c8c77babf5b99bdc01b88aba967e3c3983fdd0ec67", size = 1636267, upload-time = "2025-10-10T16:14:08.646Z" }, - { url = "https://files.pythonhosted.org/packages/bf/e2/5b944e96312490ccb03450ca19d8f3337b8e8c6b7a1b8947e9fff7fe87a4/xlwings-0.33.16-cp312-cp312-win_amd64.whl", hash = "sha256:28bbbd2bda7e9dc71d8a566a6306708676758973d00c4d0bccd79c33ab74a914", size = 1630930, upload-time = "2025-10-10T16:14:10.253Z" }, - { url = "https://files.pythonhosted.org/packages/86/08/e8cd25beae629dfe201bc2f52fba3128d6ad2ad05ca10e6f0a675d5ea353/xlwings-0.33.16-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:a1fa32063ea8e567409242ba11e483759210af6de93d025ecdbed45695b241fe", size = 1415631, upload-time = "2025-10-10T16:14:11.815Z" }, - { url = "https://files.pythonhosted.org/packages/8f/d3/10e0f425bc695941d95c7f47e1707dd56f99ce79f6d29e7b27fda1178dca/xlwings-0.33.16-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6a79f54916c42514ba9904154b217d8bfa98c25f0c5969353af23623f7c5d4c4", size = 1400140, upload-time = "2025-10-10T16:14:13.531Z" }, - { url = "https://files.pythonhosted.org/packages/92/15/6ae6328e64a0b181a07a78423d8c3942fc5cc9261cf27f1f37449ce203a4/xlwings-0.33.16-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ee2c3c0ad5c4277a1b5709c3f4ca2e2d1c06df1415adc231ec1854ba9d87a93", size = 1459909, upload-time = "2025-10-10T16:14:15.674Z" }, - { url = "https://files.pythonhosted.org/packages/d7/f4/e6ae7ea7ddfc53a9ff90d7f050d5236f091292200db988a7f6fdff0977ad/xlwings-0.33.16-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1fb07f710b04538a9207a882a32d0151c4f3a8e7bf1218aeed76abda2a8d499", size = 1463285, upload-time = "2025-10-10T16:14:17.284Z" }, - { url = "https://files.pythonhosted.org/packages/07/38/4d46d1e5a7a0d60d84360e9bfbd62b28f6ca913009262aac9b59c3c18705/xlwings-0.33.16-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0c6bc520d726f6eabfbbaceeb4dc8412fc906742377ab0f77b326ec8b95b39a", size = 1465546, upload-time = "2025-10-10T16:14:18.473Z" }, - { url = "https://files.pythonhosted.org/packages/66/58/ca06a6fd9e13e72a7ec05bd6b56f39dd2843c57594133f42363e31850d6f/xlwings-0.33.16-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:8ff6940a9f2d2b9b6876b4f4780403bc9c9bb1614ed6e3e29c913058a1b9b05e", size = 1641584, upload-time = "2025-10-10T16:14:19.613Z" }, - { url = "https://files.pythonhosted.org/packages/d5/6d/ed12c1bca3bbeb2a26a918c52b376dd39a647c5c6443bbf3bf351846c90d/xlwings-0.33.16-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:3fa4768d74915df3e000737d26ad0889c8586c194996c68360909d8214743c18", size = 1636547, upload-time = "2025-10-10T16:14:21.3Z" }, - { url = "https://files.pythonhosted.org/packages/00/ee/29356034743afe63b90af1d8d6f605233454166496ff58ec6133f51921e4/xlwings-0.33.16-cp313-cp313-win_amd64.whl", hash = "sha256:fd4c6da24b4032a65917a35575126892c0319c2d607542a8816f0f9dac02c97f", size = 1630855, upload-time = "2025-10-10T16:14:22.52Z" }, - { url = "https://files.pythonhosted.org/packages/d2/31/2ff0120422f320d3840c3cff5cf6813e9507378414f2ceab3345b4686f00/xlwings-0.33.16-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:cc86d0180abb69d23531840ce51da8d15e597c5ad33aaa54c02f35f5e7991439", size = 1414773, upload-time = "2025-10-10T16:14:23.775Z" }, - { url = "https://files.pythonhosted.org/packages/fd/df/cb1e04bbef5b63adbe1e28556c90dc4faa5f5a00cc29ce2b1ad955f85a26/xlwings-0.33.16-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:160625fa6d3c54b17a5afa642a0dd0364c67919e0d82d38a2bc9df8f1019c959", size = 1399805, upload-time = "2025-10-10T16:14:24.967Z" }, - { url = "https://files.pythonhosted.org/packages/fe/36/b91f459f7a45bb3030f684abc85e970ad627acb7960a194cb874d45fc9ae/xlwings-0.33.16-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33ef84134f821b0597351961ed6c6abf3c97cf7f604beb0733478ba1a55ebb47", size = 1460195, upload-time = "2025-10-10T16:14:26.64Z" }, - { url = "https://files.pythonhosted.org/packages/c4/84/c55a6eb5459ace6668ef55e4b042e32e0dbc6c605cceff707609c9903043/xlwings-0.33.16-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4283246f8553e776cabd149088f001b5f45315ff375f2d0d3c2fcd7bd7a98195", size = 1463185, upload-time = "2025-10-10T16:14:28.333Z" }, - { url = "https://files.pythonhosted.org/packages/3f/92/128580317b29b179f0ffb3e8c12528d24d351671e55eb24d1e97d6f63cc6/xlwings-0.33.16-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4bfcecec3b20802e3e976f63054d3a019c2a7c23d640d014e51d2cf4f907edf3", size = 1464759, upload-time = "2025-10-10T16:14:29.544Z" }, - { url = "https://files.pythonhosted.org/packages/fa/f1/08d30773fbcfdff60632b01db3eee3311da9383cd8d473d4b013679d197f/xlwings-0.33.16-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:6a0fc127d4c95d8452024f2f4a4589be4d76b80a8e32b7fa86576d9a6aa735af", size = 1641645, upload-time = "2025-10-10T16:14:30.671Z" }, - { url = "https://files.pythonhosted.org/packages/b7/a9/3c41849f6199e845c2af0971202ed99c4aba24243adcec3b385a18a471a8/xlwings-0.33.16-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:da19b3becfa34eacb6e0d6786bd6bfb17da45b28c7f29cba8b7dce292ca552d8", size = 1636452, upload-time = "2025-10-10T16:14:31.998Z" }, - { url = "https://files.pythonhosted.org/packages/3c/d3/6303d45234dade44b2fbf24f73cdf33485b121bc1641aefad7976d2652da/xlwings-0.33.16-cp314-cp314-win_amd64.whl", hash = "sha256:ce41bbc78997a2214dfd3722081ccd7b345f1af0d7437f3eb8e426accb39e7e4", size = 1653329, upload-time = "2025-10-10T16:14:33.326Z" }, - { url = "https://files.pythonhosted.org/packages/f2/1f/2cfb6b8145a6594d69aae76d9213e7d709c763c251e45a03d76df43f1f7e/xlwings-0.33.16-py3-none-any.whl", hash = "sha256:2adc68b55dedcf835b461bb60dc730c59329267b5361cb2f74d0a0dafc86906c", size = 732638, upload-time = "2025-10-10T16:14:34.511Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/9d/bb/c63719509af8dff2ec20a809e87bef90f35661488d6484dac306d1d7b560/xlwings-0.33.20.tar.gz", hash = "sha256:52c85b693745cd0cc9fd847609dae976cb12ea1e9fa2065b91cf52ce2d7a559d", size = 15761897, upload-time = "2026-01-21T14:41:03.709Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d4/80/a59e72e7b3fd38bca31b107471722fc3c85415314bef70ba2ceb50c234bf/xlwings-0.33.20-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:388b124db5928ead1bac43b828b06ebb25a26959a1daaadc57cc518640a93cd1", size = 1388819, upload-time = "2026-01-21T14:41:14.419Z" }, + { url = "https://files.pythonhosted.org/packages/8d/aa/24da7578c615f95f025bbac8fc9b851399211e377f33409183bd60ef772c/xlwings-0.33.20-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2dcb228a463def04fa07107527fc1a1116a1d6e515621355977cdb77bec3f7f9", size = 1371654, upload-time = "2026-01-21T14:41:15.554Z" }, + { url = "https://files.pythonhosted.org/packages/86/77/5001f724f84e0b728fa7ac7b0256205b73394f6993d3bb2ee3a4c25e7b79/xlwings-0.33.20-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ae857e3a59cfea0eb05f8400be17a76387dd7fb3ea4fd9f9325c52f140e1e55", size = 1430557, upload-time = "2026-01-21T14:41:16.737Z" }, + { url = "https://files.pythonhosted.org/packages/88/87/39798513a6d9a44b451613ae8961b718d1c6a033884262cf77f9f59e33aa/xlwings-0.33.20-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7911b6bb5f24a2df0229390f37ea835480a508a60933de051b50585d3a725ef", size = 1433243, upload-time = "2026-01-21T14:41:17.82Z" }, + { url = "https://files.pythonhosted.org/packages/10/d1/be9425cedaa84fe9eaadca61878bf03c7127798f059545718143f8b54dce/xlwings-0.33.20-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54ec450ef6dc7704f316e44cd6149102c41ea776ce0863494a5b37b0948b42b1", size = 1440029, upload-time = "2026-01-21T14:41:19.041Z" }, + { url = "https://files.pythonhosted.org/packages/f7/c9/92193b7cf2ca4f43c6ca8640e53b796ee3db0ccc4a7bb135712736079e63/xlwings-0.33.20-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9dfd1fa445e9be73ea30d932a18ce877df3e3b339de8af59540c56ecc6872d5b", size = 1611773, upload-time = "2026-01-21T14:41:20.102Z" }, + { url = "https://files.pythonhosted.org/packages/ad/33/3e02d50e8e05f52cbf288bd261cfa36e0c43800195d9a0cda715705cea86/xlwings-0.33.20-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dac7e245776d4e47f22c43d1a5a1e9486a6ca86ae6a769540b2f5cccaa5e730a", size = 1645100, upload-time = "2026-01-21T14:41:21.463Z" }, + { url = "https://files.pythonhosted.org/packages/cc/f8/34e506fdb03a128bf94f4e6a4ef782ffd08daef0309a9fc6a8e1d3e1f743/xlwings-0.33.20-cp311-cp311-win_amd64.whl", hash = "sha256:f9c0b47fea3607ea1d18ed964953401f1e569ef1a1e8778189a86050d7a4098a", size = 1603114, upload-time = "2026-01-21T14:41:23.147Z" }, + { url = "https://files.pythonhosted.org/packages/e4/bf/ca7671307d147e4c5bb47e84d1f8ebb27d05e7dca45cb2f066f1937cb97f/xlwings-0.33.20-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:d8e7029aede59821ec5e963188fc1691e1ed55d3586b05bf91d6c49e64418b49", size = 1385489, upload-time = "2026-01-21T14:41:24.268Z" }, + { url = "https://files.pythonhosted.org/packages/60/b7/a6870daba05685f6957a23a8b33d385a18c1b4f36193d483626fe057a519/xlwings-0.33.20-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e1499bbd945bfc7e23895bdf2d400c74c5333f76f3e6fd23b6bf4bb2d8501dd3", size = 1366744, upload-time = "2026-01-21T14:41:25.427Z" }, + { url = "https://files.pythonhosted.org/packages/e8/f9/95983e3cdd1fd4e0cceedf7166fdfddf1a538867b2e8936c497b86b491ad/xlwings-0.33.20-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aea36b509b86f28c57a4208e72bca30110952c11e0cd6e11610421f85a0c5156", size = 1429398, upload-time = "2026-01-21T14:41:26.641Z" }, + { url = "https://files.pythonhosted.org/packages/02/18/ead395017c25bd9fa055a3ae1e86c835f25bb1b861031a283b0a8abd44cc/xlwings-0.33.20-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8776e701b4ef761dab1563c17b18a009267ce0e285421e099c1f36d52d6a53ff", size = 1432144, upload-time = "2026-01-21T14:41:27.857Z" }, + { url = "https://files.pythonhosted.org/packages/1b/73/89c0f44a1d1fb8d928baac616b8d881fc691170d0149ee00e8a173c0b38a/xlwings-0.33.20-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ad352cb2e1a55fdf20ce9d90433f88d727c6b362b8b452e290aee70899060448", size = 1439442, upload-time = "2026-01-21T14:41:28.938Z" }, + { url = "https://files.pythonhosted.org/packages/c5/a6/1e7197c9f78708ce53ba0ed74a19e9dbb845fa73cb5747b3ab5d152384d3/xlwings-0.33.20-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:08ef59d3217ee28ce36d3f122b5b034fd97c167dac31855bab30ebc565b51346", size = 1610359, upload-time = "2026-01-21T14:41:30.532Z" }, + { url = "https://files.pythonhosted.org/packages/7f/ca/7b3dd94712dce8f6cc7509be69ec4e0fd1bd22ca8009a3d8116ac6d1685b/xlwings-0.33.20-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:706b0e9f21ca8bf228391b78ee8d9007287391ba499205c5790ff6b051d4dbe8", size = 1642755, upload-time = "2026-01-21T14:41:32.471Z" }, + { url = "https://files.pythonhosted.org/packages/06/71/325886875c5cafac2ea15561c891a73987e9e8490282dc3f619edca58584/xlwings-0.33.20-cp312-cp312-win_amd64.whl", hash = "sha256:a08040c9bddf7a4ebc1d51fc84138307b58c7553099b50e3d2d7a3b7c6c591fc", size = 1602722, upload-time = "2026-01-21T14:41:33.565Z" }, + { url = "https://files.pythonhosted.org/packages/9e/15/d47bbe7e869f0b60a377540baad70e7874a9ecb9bc027b2616a4bab7b457/xlwings-0.33.20-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:4f77e348957f4db5dc89eaa234c7167e590b76352d499a70a9dcfcdff03c9ebf", size = 1385103, upload-time = "2026-01-21T14:41:35.199Z" }, + { url = "https://files.pythonhosted.org/packages/91/4b/8ec8de217e0d487e4cab97e02b35c6b24d3e0852f55b41d92bad966f6d71/xlwings-0.33.20-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9af3e0988ce1ab2922197c693e5047749c27e6415338a4927a24458dc94324ee", size = 1366295, upload-time = "2026-01-21T14:41:36.295Z" }, + { url = "https://files.pythonhosted.org/packages/3d/ee/afa5067e84f27e998df9fa37cda71f216a047d565500718933799f3a13a4/xlwings-0.33.20-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8fa67abf4ddfaa7a135fa534974dbf77b2f28f2528b78db67615c90ec878eaf4", size = 1428944, upload-time = "2026-01-21T14:41:37.837Z" }, + { url = "https://files.pythonhosted.org/packages/6a/50/77d80f98bd4fb7d55bea8d9a12f29954dea9bced64461e3922aa2b79be9a/xlwings-0.33.20-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:09592c09afcffe2435de2d548a3a611b48d6ab68669808e0b2b1fd37afc0c8e7", size = 1432203, upload-time = "2026-01-21T14:41:39.476Z" }, + { url = "https://files.pythonhosted.org/packages/a2/53/9caba36ba0bb924ae8d075fe6ff1b003af460b2fca4a3240a4d8b38462c7/xlwings-0.33.20-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:918f417a41a7ed09054584bc1f61c57bbaaae11636b9450a1219c0afd628e373", size = 1439025, upload-time = "2026-01-21T14:41:40.771Z" }, + { url = "https://files.pythonhosted.org/packages/d0/5e/3b7e4289d66397f61be6a0651f5c810b76a9eccfc4e93db8a32caf3bf390/xlwings-0.33.20-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:73f9cbe16405f54ed8d43eb565459f5c36a14c386dc2f8595a0e6d20ad22f447", size = 1610290, upload-time = "2026-01-21T14:41:41.86Z" }, + { url = "https://files.pythonhosted.org/packages/1e/79/cb2ee8383279a1eb80baf0432af95c2a143a3bbb6054339f0623287decc4/xlwings-0.33.20-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ab2273fa9a4e786ad0e57b3f2ecbc9e8ef3bddbdcb439708af62bdd002d33acf", size = 1644221, upload-time = "2026-01-21T14:41:42.978Z" }, + { url = "https://files.pythonhosted.org/packages/94/b5/e030e5deee30aa4e3ddbb0fdee3d47f087bbb4e647850dc0b9147f02eb8a/xlwings-0.33.20-cp313-cp313-win_amd64.whl", hash = "sha256:62dd5666ee68460416163ded2934e9439a3db1361987870ace5fb26ed8bdf614", size = 1602685, upload-time = "2026-01-21T14:41:44.232Z" }, + { url = "https://files.pythonhosted.org/packages/3e/da/c4f9ccecedef9e98642a588ac7903d9143942717dfbee0e3da2b3dee7a98/xlwings-0.33.20-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:527e820ce6c37068c53212d611b3f6fa56a9f71dfd535e16f64fd860a3615080", size = 1385244, upload-time = "2026-01-21T14:41:45.319Z" }, + { url = "https://files.pythonhosted.org/packages/81/68/c4a14e9148ef42f9383b8c6aead50d1d428200ec4b5f1eb0d0306997fc29/xlwings-0.33.20-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:af88d064bfe81d767d32312d7b81afb2e6b3565fee624c0a5e2a3454383a7d9e", size = 1365918, upload-time = "2026-01-21T14:41:46.361Z" }, + { url = "https://files.pythonhosted.org/packages/a4/e7/779aec907b0a919a71a2e59fa9b7e89e7c74694a9e359f0136b1a122b53e/xlwings-0.33.20-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdb1bb6b5478af103c6fb6471370e7e0a5d70a00f672b313ba68a609f0faff27", size = 1429005, upload-time = "2026-01-21T14:41:47.483Z" }, + { url = "https://files.pythonhosted.org/packages/02/4c/6b8d42e085c907d344e9091e0cbb891a775de190a0db288d8ca367d03332/xlwings-0.33.20-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:79f9377798c537c437e04f158f635b078becfe881bba2a538e59d01448342f9b", size = 1431915, upload-time = "2026-01-21T14:41:48.881Z" }, + { url = "https://files.pythonhosted.org/packages/b0/c2/ae98cf66427862dfe52e56668e51f872e5e7baee8ec0d8222fd747f6726f/xlwings-0.33.20-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca22286d62b3db3a4cb6a8626a024b680869bc84896a47c997c60069c51e49ef", size = 1439084, upload-time = "2026-01-21T14:41:49.988Z" }, + { url = "https://files.pythonhosted.org/packages/ac/1f/1a8d6cfc33da413fa2bec104ceed01d88c5d33edd71ca32fc726604e22ca/xlwings-0.33.20-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:15f7821bb364f86cb3db2dec34a64b6e12b3ddf1ea3323b4baadc6122b0ec978", size = 1610152, upload-time = "2026-01-21T14:41:51.371Z" }, + { url = "https://files.pythonhosted.org/packages/99/56/2bb4d8972e3e1fbc9361f29595a22afc163627fbdfba56450c523a74ea86/xlwings-0.33.20-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:1db4bd88277fd5a4b5a1effc1fa8649a2cb7dc633f4eba03d800ae60896c53df", size = 1642859, upload-time = "2026-01-21T14:41:52.45Z" }, + { url = "https://files.pythonhosted.org/packages/ab/5b/ac61220baea87a00e1a5471eab5280726bb048e92817557d714fa6627f2e/xlwings-0.33.20-cp314-cp314-win_amd64.whl", hash = "sha256:4c5443b92a3aa9c2bd6cc1b93f48a7036d50b1f673475f758bcd0ab52e83db0d", size = 1625767, upload-time = "2026-01-21T14:41:53.619Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e3/7253471b65023376819415729d5bfa85d2dc572e60ef1beeb927fe7326df/xlwings-0.33.20-py3-none-any.whl", hash = "sha256:47be279873519ee7d501a2daadca87b3a80c72b57bf6fca55b5891d4bb293957", size = 710681, upload-time = "2026-01-21T14:41:54.706Z" }, ]