diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f2044a8..65bc4a4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,10 +29,10 @@ jobs: matrix: python-version: ["3.12", "3.13"] steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Install uv - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0 + uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 with: enable-cache: true @@ -66,16 +66,23 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Install uv - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0 + uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 with: enable-cache: true + - name: Sync dev deps + # ruff is in [dependency-groups].dev, not an optional extra. + run: uv sync --group dev + - name: Ruff lint - # Use uvx so we don't need to add ruff to project deps. - run: uvx ruff check src tests + # ruff is pinned in dev group so CI and local dev use the same version. + run: uv run ruff check src tests + + - name: Ruff format check + run: uv run ruff format --check src tests secret-scan: # Scans the full git history for committed secrets. Runs the @@ -85,7 +92,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: # Full history so gitleaks scans every commit, not just HEAD. fetch-depth: 0 @@ -111,10 +118,10 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Install uv - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0 + uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 with: enable-cache: true @@ -150,10 +157,10 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Install uv - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0 + uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 with: enable-cache: true @@ -177,10 +184,10 @@ jobs: matrix: template: [csv-import, nyc-transit] steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Install uv - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0 + uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 with: enable-cache: true diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 0076aa4..b0da8e3 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -16,8 +16,8 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 20 steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 - run: uv sync --all-extras - name: Run e2e template tests env: diff --git a/.github/workflows/nightly-e2e.yml b/.github/workflows/nightly-e2e.yml index d1c02dd..bd565d2 100644 --- a/.github/workflows/nightly-e2e.yml +++ b/.github/workflows/nightly-e2e.yml @@ -28,10 +28,10 @@ jobs: contents: read issues: write steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Install uv - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0 + uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 with: enable-cache: true diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index e616ee0..ee3120c 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -20,7 +20,7 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - name: Validate version + changelog coherence run: | @@ -70,8 +70,8 @@ jobs: permissions: contents: read steps: - - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - - uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + - uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0 - run: uv build - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ec3460..969b2fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,44 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.1.11] - 2026-08-07 + +_Headline: `tycoon.yml` learns its own schema version. The ingestion rewrite's M2 lands ([#83][], PR [#190][]) — `runtimes:` and `metadata:` blocks on the project model, an integer `schema_version` with comment-preserving in-place migration via `tycoon init --upgrade`, and the config singleton replaced across the ingestion commands. Plus a pinned, fully-formatted codebase (PR [#174][]) and the first release-time dependency review (PR [#180][], replacing the Dependabot PR pile)._ + +### Added + +- **`runtimes:` and `metadata:` blocks in `tycoon.yml`** ([#90][], PR [#190][]). New `RuntimeEntry` and `MetadataConfig` models on `TycoonProject`, both defaulted — every existing `tycoon.yml` loads unchanged. This is the schema surface the rest of the ingestion rewrite ([#81][]) configures runtimes and metadata backends through. +- **`schema_version` + `migrate_project()`** ([#92][], PR [#190][]). `tycoon.yml` now carries an integer `schema_version` (currently `2`), stamped on new projects and advanced by an in-place, idempotent migration that also writes `metadata:` defaults. The migration uses ruamel.yaml so comments and blank lines survive the round-trip; `ruamel-yaml` joins the runtime dependencies. Integer rather than semver-string because string comparison ordered `"0.10.0" < "0.2.0"`. +- **`tycoon init --upgrade`** ([#96][], PR [#190][]). Runs the migration and reports whether the file changed. Loading a project with a stale `schema_version` warns and names this command as the remedy; a future `schema_version` makes gated data commands exit 1 cleanly instead of tracebacking. Enforcement is staged — it lives in `load_config()` and currently covers the ingestion commands; full-surface coverage is tracked in [#189][]. +- **Docs for the schema surface** ([#188][]). `schema_version` in the `tycoon.yml` reference — including the `version` (yours) vs `schema_version` (tycoon's) split — and an "Upgrading an existing project" section on the init page. +- **Subprocess import-safety guard** ([#187][]). A `tests/test_cli_surface.py` regression test runs the real `tycoon` binary against a hostile `schema_version: 99` project: `--help`/`--version` must exit 0 and `init --upgrade` must fail cleanly. In-process `CliRunner` tests cannot observe import-time breakage by construction. + +### Changed + +- **Ingestion commands construct config locally** ([#94][], PR [#190][]). `sources`, `run-all`, `sync`, and `explore` build `TycoonConfig` at invocation through a new `load_config()` factory instead of importing the module-level singleton, keeping `--help` and `tycoon init --upgrade` import-safe no matter what a project's `tycoon.yml` says. Remaining singleton users (`transform`, `db`, `status`) are tracked in [#175][]. +- **ruff pinned, formatting enforced in CI** (PR [#174][]). CI previously ran `uvx ruff` (always latest) against an unpinned project, so new ruff releases broke CI without a code change. ruff is now pinned in the dev group, `ruff format --check` joins the lint job, and the one-time repo-wide format pass (~75 files) clears all pre-existing violations. +- **Dependencies reviewed at release time** (PR [#180][], replacing Dependabot PRs #161–#168). `dbt-core` 1.11.8 → 1.12.0, `typer` 0.25.0 → 0.27.0; SHA-pinned actions `actions/checkout` → v7.0.1 and `astral-sh/setup-uv` → v9.0.0. dbt-core 1.12 reshapes the dependency tree (adds metricflow, drops dbt-semantic-interfaces; lockfile 109 → 113 packages). + +### Fixed + +- **Template scaffolding no longer flattens `tycoon.yml`** ([#185][]). `tycoon init --template` stamped `schema_version` through a `yaml.safe_load`/`dump` round-trip that stripped the template's blank-line separators — the first file a new user opens. Stamping now goes through `migrate_project()` (ruamel-based), which also writes the `metadata:` defaults in the same pass. +- **Dead code from the T2-4 review dropped** ([#186][]). `save_project` carried a two-line conditional that duplicated what `model_dump` already does. + +[#81]: https://github.com/Database-Tycoon/tycoon-cli/issues/81 +[#83]: https://github.com/Database-Tycoon/tycoon-cli/issues/83 +[#90]: https://github.com/Database-Tycoon/tycoon-cli/issues/90 +[#92]: https://github.com/Database-Tycoon/tycoon-cli/issues/92 +[#94]: https://github.com/Database-Tycoon/tycoon-cli/issues/94 +[#96]: https://github.com/Database-Tycoon/tycoon-cli/issues/96 +[#174]: https://github.com/Database-Tycoon/tycoon-cli/pull/174 +[#175]: https://github.com/Database-Tycoon/tycoon-cli/issues/175 +[#180]: https://github.com/Database-Tycoon/tycoon-cli/pull/180 +[#185]: https://github.com/Database-Tycoon/tycoon-cli/issues/185 +[#186]: https://github.com/Database-Tycoon/tycoon-cli/issues/186 +[#187]: https://github.com/Database-Tycoon/tycoon-cli/issues/187 +[#188]: https://github.com/Database-Tycoon/tycoon-cli/issues/188 +[#189]: https://github.com/Database-Tycoon/tycoon-cli/issues/189 +[#190]: https://github.com/Database-Tycoon/tycoon-cli/pull/190 + ## [0.1.10] - 2026-07-21 _Headline: the codebase gets dramatically smaller and more focused. The ingestion rewrite's M1 metadata backend lands ([#82][], PRs [#136][]/[#137][]), and the FastAPI server, Dagster orchestration, and Nao/ask AI-agent extras are removed outright ([#139][], PR [#147][]) — the lockfile drops from 174 to 109 packages. Cycle plan in [`docs/proposals/v0.1.10-scope.md`](docs/proposals/v0.1.10-scope.md)._ diff --git a/docs/commands/init.md b/docs/commands/init.md index cea6fdb..7aa2287 100644 --- a/docs/commands/init.md +++ b/docs/commands/init.md @@ -12,6 +12,7 @@ Options: -n, --name TEXT Project name (default: current directory name) --list-templates List available templates and exit -p, --param TEXT Template parameter in 'name=value' form (repeatable) + --upgrade Migrate tycoon.yml to the current schema version and exit -h, --help Show this message and exit ``` @@ -91,6 +92,34 @@ tycoon start --only rill # serve dashboards See [Your first project](../getting-started/first-project.md) for a 10-minute walkthrough. +## Upgrading an existing project: `--upgrade` + +When tycoon's `tycoon.yml` schema gains new fields, existing projects +keep working — but commands will warn: + +``` +tycoon.yml is at schema version 1, current is 2. Run 'tycoon init --upgrade' to migrate. +``` + +`tycoon init --upgrade` is that migration: + +```bash +cd my-project +tycoon init --upgrade +``` + +It edits `tycoon.yml` in place — adding any missing blocks with their +defaults (currently `metadata:`) and stamping +[`schema_version`](../reference/tycoon-yml.md#schema_version) to the +current value. Your comments, blank lines, and the project's own +`version:` field are left untouched. It is idempotent: run it twice and +the second run reports the file is already up to date and writes +nothing. + +If `tycoon.yml` declares a `schema_version` *newer* than the installed +tycoon supports, `--upgrade` (and any data command) exits with an error +asking you to upgrade tycoon-cli instead — it never downgrades a file. + ## Re-running `init` `tycoon init` refuses to overwrite an existing `tycoon.yml`. To reset a project: diff --git a/docs/reference/all-commands.md b/docs/reference/all-commands.md index 7107826..506df6a 100644 --- a/docs/reference/all-commands.md +++ b/docs/reference/all-commands.md @@ -24,6 +24,8 @@ Options: -n, --name TEXT Project name (default: current directory name) --list-templates List available templates and exit -p, --param TEXT Template parameter in 'name=value' form (repeatable) + --upgrade Migrate tycoon.yml to the current schema version + and exit ``` ### [`tycoon register dbt`](../commands/register.md#tycoon-register-dbt) diff --git a/docs/reference/tycoon-yml.md b/docs/reference/tycoon-yml.md index 1c25a05..6eaab6c 100644 --- a/docs/reference/tycoon-yml.md +++ b/docs/reference/tycoon-yml.md @@ -22,6 +22,7 @@ Anything not declared falls back to the defaults below. |---|---|---|---| | `name` | string | `my-project` | Used in dlt pipeline names | | `version` | string | `0.1.0` | Project's own version, free-form | +| `schema_version` | int | unset | Managed by tycoon — see [`schema_version`](#schema_version) below. Don't edit by hand. | | `database` | block | see below | Where data lives | | `sources` | map[name → source] | `{}` | Registered ingestion sources | | `dbt_project_dir` | string | `dbt_project` | Path to dbt project root | @@ -32,6 +33,34 @@ Anything not declared falls back to the defaults below. | `sync` | block | unset | `tycoon data sync` defaults | | `stack` | block | see below | Tool-by-tool stack toggle | +## `schema_version` + +```yaml +schema_version: 2 +``` + +An integer marking which revision of the `tycoon.yml` *format* the file +was written against. **Tycoon manages this field** — `tycoon init` +stamps it on new projects, and `tycoon init --upgrade` advances it when +migrating an older file. You never need to set or edit it. + +Not to be confused with `version`, which is *yours*: a free-form label +for your project that tycoon never touches. + +| | `version` | `schema_version` | +|---|---|---| +| Owned by | you | tycoon | +| Type | free-form string | integer | +| Meaning | your project's version | the `tycoon.yml` format revision | + +Files without `schema_version` (or with an older one) still load — data +commands print a warning pointing at +[`tycoon init --upgrade`](../commands/init.md#upgrading-an-existing-project-upgrade), +which migrates the file in place while preserving comments and blank +lines. A `schema_version` *newer* than the installed tycoon supports is +an error: commands exit and ask you to upgrade tycoon-cli rather than +risk misreading a future format. + ## `database` ```yaml diff --git a/docs/releases/v0.1.10.md b/docs/releases/v0.1.10.md index eb30af8..ae2ff84 100644 --- a/docs/releases/v0.1.10.md +++ b/docs/releases/v0.1.10.md @@ -1,4 +1,4 @@ -# v0.1.10 — the great slimming +# v0.1.10 — metadata backend + a slimmer install _Released: 2026-07-21_ diff --git a/docs/releases/v0.1.11.md b/docs/releases/v0.1.11.md new file mode 100644 index 0000000..0d23995 --- /dev/null +++ b/docs/releases/v0.1.11.md @@ -0,0 +1,68 @@ +# v0.1.11 + +_Released: 2026-08-07_ + +Headline: `tycoon.yml` learns its **own schema version**. The ingestion +rewrite's second milestone lands — `runtimes:` and `metadata:` blocks on +the project model, an integer `schema_version` with comment-preserving +in-place migration via the new **`tycoon init --upgrade`**, and the +config singleton replaced across the ingestion commands. Alongside it: +a pinned, fully-formatted codebase and the first release-time +dependency review. + +## Landed + +- **A versioned, migratable `tycoon.yml`** (#83, PR #190) — the project + file now carries `schema_version` (an integer, currently `2`) plus + `runtimes:` and `metadata:` blocks, both defaulted so existing files + load unchanged. `tycoon init --upgrade` migrates older files in + place — idempotent, and your comments, blank lines, and own + `version:` field survive untouched (ruamel.yaml under the hood). A + stale file warns and names the remedy; a *future* `schema_version` + exits cleanly with "upgrade tycoon-cli" instead of tracebacking. See + the [`schema_version` reference](../reference/tycoon-yml.md#schema_version). +- **Config singleton on its way out** (#94, PR #190) — the ingestion + commands (`sources`, `run-all`, `sync`, `explore`) construct their + config at invocation through a `load_config()` factory rather than an + import-time singleton, which is what keeps `--help` and + `init --upgrade` safe in front of any project file. The remaining + singleton users are tracked in #175, broader enforcement coverage + in #189. +- **CI that can't drift** (PR #174) — ruff pinned (CI previously ran + whatever `uvx ruff` resolved to, so upstream releases broke builds), + `ruff format --check` gating every PR, and a one-time repo-wide + format pass. + +## Upgrading from 0.1.10 + +- **Existing projects keep working.** The first data command prints a + one-time-per-invocation warning that `tycoon.yml` is at an older + schema version; run `tycoon init --upgrade` in the project directory + to migrate. The migration adds a `metadata:` block with defaults and + stamps `schema_version: 2` — nothing else in your file is touched. +- **No command or flag was removed.** New runtime dependency + `ruamel-yaml` installs automatically with the package. +- **Dependency floors moved** (PR #180): `dbt-core` 1.12.0 and `typer` + 0.27.0. dbt-core 1.12 pulls metricflow into the tree; if you pin + tycoon's dependencies downstream, re-lock. + +## Behind the scenes + +- The T2-4 review round produced four follow-ups (#185–#188), all + closed in this release: template scaffolding no longer flattens + `tycoon.yml` formatting, a dead conditional in `save_project` is + gone, the schema surface is documented, and a subprocess-layer test + now guards the "hostile tycoon.yml bricks the CLI at import time" + regression class that in-process tests can't see. +- Dependabot's per-bump PRs are replaced by a release-time dependency + review (PR #180 is the first) — runtime pins and SHA-pinned actions + reviewed in one pass, dev-only noise dropped. + +## What's next + +Milestone 3 of the ingestion rewrite — the verified-source catalog +(manifest, `SourceSpec` models, spec-driven config collection) — is +already in flight on the `v0.1.12` branch. Remaining schema-enforcement +coverage (#189) and the rest of the singleton removal (#175) ride with +it. See the [ingestion architecture doc](../ingestion-architecture.md) +and #81 for the sequence. diff --git a/mkdocs.yml b/mkdocs.yml index a171f2a..e390d5f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -120,6 +120,7 @@ nav: - recipes/fivetran-metadata.md - recipes/layered-architecture.md - Releases: + - "v0.1.11": releases/v0.1.11.md - "v0.1.10": releases/v0.1.10.md - "v0.1.9": releases/v0.1.9.md - "v0.1.8": releases/v0.1.8.md diff --git a/pyproject.toml b/pyproject.toml index fe75acf..02b9f4f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "database-tycoon" -version = "0.1.10" +version = "0.1.11" description = "Tycoon CLI — local-first analytics CLI that adapts to your existing data stack" readme = "README.md" license = {text = "MIT"} @@ -21,9 +21,9 @@ classifiers = [ "Topic :: Database", ] dependencies = [ - "typer==0.25.0", + "typer==0.27.0", "rich==15.0.0", - "dbt-core==1.11.8", + "dbt-core==1.12.0", "dbt-duckdb==1.10.1", "dlt[duckdb]==1.26.0", # Nothing in src/ imports pandas, but dlt's filesystem source @@ -35,6 +35,7 @@ dependencies = [ "httpx==0.28.1", "pyyaml==6.0.3", "pydantic==2.13.3", + "ruamel-yaml==0.19.1", ] [project.optional-dependencies] @@ -53,6 +54,7 @@ dev = [ "pytest==9.0.3", "pytest-cov>=7.1.0", "syrupy==5.2.0", + "ruff==0.15.20", ] [project.scripts] @@ -81,6 +83,9 @@ target-version = "py312" # cli.py intentionally registers subcommands after the Typer app is # constructed, which ruff flags as E402. The alternative is circular imports. "src/tycoon/cli.py" = ["E402"] +# init.py re-exports moved symbols at the bottom of the file for backwards +# compatibility; they are intentionally placed after the module body. +"src/tycoon/commands/init.py" = ["E402"] # Forward-reference string literals in type annotations trip F821 when the # referenced name is imported inside the method body to avoid circular imports. "tests/test_ingestion.py" = ["F821"] diff --git a/src/tycoon/__init__.py b/src/tycoon/__init__.py index 9efb1dc..36f8048 100644 --- a/src/tycoon/__init__.py +++ b/src/tycoon/__init__.py @@ -1,3 +1,3 @@ """Database Tycoon — local-first analytics CLI for exploring any dataset.""" -__version__ = "0.1.10" +__version__ = "0.1.11" diff --git a/src/tycoon/cli.py b/src/tycoon/cli.py index 62dc1d4..6964b7f 100644 --- a/src/tycoon/cli.py +++ b/src/tycoon/cli.py @@ -7,22 +7,36 @@ import tycoon -_COMMAND_ORDER = ["init", "setup", "register", "profiles", "semantics", "data", "start", "stop", "run", "notify", "schedule", "doctor", "docs"] +_COMMAND_ORDER = [ + "init", + "setup", + "register", + "profiles", + "semantics", + "data", + "start", + "stop", + "run", + "notify", + "schedule", + "doctor", + "docs", +] _SECTIONS = { - "init": "Project", + "init": "Project", "setup": "Project", "register": "Project", "profiles": "Project", "semantics": "Project", - "data": "Data Pipeline", + "data": "Data Pipeline", "start": "Services", - "stop": "Services", - "run": "Tools", + "stop": "Services", + "run": "Tools", "notify": "Utilities", "schedule": "Utilities", "doctor": "Utilities", - "docs": "Utilities", + "docs": "Utilities", } @@ -38,9 +52,7 @@ def format_commands(self, ctx: object, formatter: object) -> None: if cmd is None or getattr(cmd, "hidden", False): continue section = _SECTIONS.get(name, "Commands") - seen.setdefault(section, []).append( - (name, cmd.get_short_help_str(limit=formatter.width)) - ) + seen.setdefault(section, []).append((name, cmd.get_short_help_str(limit=formatter.width))) for section, rows in seen.items(): with formatter.section(section): formatter.write_dl(rows) @@ -79,7 +91,8 @@ def _root( pass -from tycoon.commands import data, docs as docs_cmd_mod, profiles, register, semantics +from tycoon.commands import data, profiles, register, semantics +from tycoon.commands import docs as docs_cmd_mod from tycoon.commands.doctor import doctor_cmd from tycoon.commands.init import init_cmd from tycoon.commands.notify import notify_cmd diff --git a/src/tycoon/commands/db.py b/src/tycoon/commands/db.py index 5d0d74f..09e819e 100644 --- a/src/tycoon/commands/db.py +++ b/src/tycoon/commands/db.py @@ -10,8 +10,8 @@ from rich.table import Table from tycoon.config import config -from tycoon.utils.console import console, header, info, success, error, warn, status_table -from tycoon.utils.duckdb_utils import db_file_size_mb, get_tables, get_row_count +from tycoon.utils.console import console, error, header, info, status_table, success, warn +from tycoon.utils.duckdb_utils import db_file_size_mb, get_row_count, get_tables def _resolve_source_db(source_name: str) -> Path | None: @@ -51,10 +51,7 @@ def _has_schema(db_path: Path, schema_name: str) -> bool: con = duckdb.connect(str(db_path), read_only=True) try: schemas = [ - r[0] - for r in con.execute( - "SELECT DISTINCT schema_name FROM information_schema.schemata" - ).fetchall() + r[0] for r in con.execute("SELECT DISTINCT schema_name FROM information_schema.schemata").fetchall() ] finally: con.close() @@ -82,11 +79,13 @@ def schema() -> None: rows.append((" Tables", "", f"{len(tables)}")) for s, table in tables: count = get_row_count(db_path, s, table) - rows.append(( - f" {s}.{table}", - "", - f"{count:,} rows" if count is not None else "empty", - )) + rows.append( + ( + f" {s}.{table}", + "", + f"{count:,} rows" if count is not None else "empty", + ) + ) else: rows.append((f"{label} database", "WARN", "not found")) @@ -105,11 +104,13 @@ def schema() -> None: rows.append((" Tables", "", f"{len(tables)}")) for s, table in tables: count = get_row_count(db_file, s, table) - rows.append(( - f" {s}.{table}", - "", - f"{count:,} rows" if count is not None else "empty", - )) + rows.append( + ( + f" {s}.{table}", + "", + f"{count:,} rows" if count is not None else "empty", + ) + ) console.print(status_table(rows, title="Database Schema")) diff --git a/src/tycoon/commands/docs.py b/src/tycoon/commands/docs.py index 35e7543..b4f47a6 100644 --- a/src/tycoon/commands/docs.py +++ b/src/tycoon/commands/docs.py @@ -21,7 +21,6 @@ from tycoon.utils.console import error, info, next_steps, success - app = typer.Typer(help="Local documentation site.") @@ -60,9 +59,7 @@ def docs_serve( "--host", help="Host interface to bind to. Default keeps the server local-only.", ), - no_open: bool = typer.Option( - False, "--no-open", help="Don't try to open a browser." - ), + no_open: bool = typer.Option(False, "--no-open", help="Don't try to open a browser."), ) -> None: """Build the docs and serve them locally with hot-reload. @@ -86,10 +83,7 @@ def docs_serve( ) raise typer.Exit(1) - info( - f"Serving docs from [bold]{root / 'docs'}[/bold] on " - f"[bold]http://{host}:{port}[/bold]" - ) + info(f"Serving docs from [bold]{root / 'docs'}[/bold] on [bold]http://{host}:{port}[/bold]") cmd = [mkdocs, "serve", "--dev-addr", f"{host}:{port}"] if no_open: cmd.append("--no-livereload") # mkdocs doesn't have --no-open; quietest equivalent @@ -103,9 +97,7 @@ def docs_serve( @app.command(name="build") def docs_build( - strict: bool = typer.Option( - False, "--strict", help="Fail on warnings (broken links, missing pages, ...)." - ), + strict: bool = typer.Option(False, "--strict", help="Fail on warnings (broken links, missing pages, ...)."), ) -> None: """One-shot build of the docs into ``site/`` without serving. diff --git a/src/tycoon/commands/doctor.py b/src/tycoon/commands/doctor.py index c2969ff..cc57d3a 100644 --- a/src/tycoon/commands/doctor.py +++ b/src/tycoon/commands/doctor.py @@ -59,8 +59,7 @@ def _check_python_version(version_info: tuple[int, int] | None = None) -> bool: ) return False - success(f"Python {label} is in the supported range " - f"(>={_fmt_ver(_MIN_PYTHON)},<{_fmt_ver(_MAX_PYTHON_EXCLUSIVE)}).") + success(f"Python {label} is in the supported range (>={_fmt_ver(_MIN_PYTHON)},<{_fmt_ver(_MAX_PYTHON_EXCLUSIVE)}).") return True @@ -208,6 +207,7 @@ def _check_stack_config() -> None: elif stack.ingestion == IngestionTool.dlt: try: import dlt # noqa: F401 + success("dlt is installed.") except ImportError: error("dlt not found. Run: pip install 'dlt[duckdb]'") @@ -222,6 +222,7 @@ def _check_stack_config() -> None: if stack.transformation == TransformationTool.dbt and not stack.transformation_managed: from pathlib import Path + dbt_dir = Path(project.dbt_project_dir) if not dbt_dir.is_absolute(): dbt_dir = config.root / dbt_dir @@ -240,10 +241,7 @@ def _check_fivetran_credentials(stack) -> None: """ meta = stack.ingestion_metadata if meta is None: - error( - "stack.ingestion is fivetran but stack.ingestion_metadata is " - "missing. Add api_key, api_secret, group_id." - ) + error("stack.ingestion is fivetran but stack.ingestion_metadata is missing. Add api_key, api_secret, group_id.") return if not (meta.api_key and meta.api_secret and meta.group_id): @@ -350,10 +348,7 @@ def _check_layer_coverage() -> None: uncovered.append(src.name) if not uncovered: - success( - f"Layer coverage: every source ({len(sources)}) has at least " - f"one staging model." - ) + success(f"Layer coverage: every source ({len(sources)}) has at least one staging model.") return warn( @@ -411,8 +406,7 @@ def _check_observability() -> None: con.close() success( - f"Observability: {dlt_count} dlt load(s), {dbt_count} dbt run(s) captured. " - f"View with `tycoon data history`." + f"Observability: {dlt_count} dlt load(s), {dbt_count} dbt run(s) captured. View with `tycoon data history`." ) @@ -429,8 +423,7 @@ def _check_schedules() -> None: info("Schedules: none installed.") return success( - f"Schedules: {len(names)} installed ({', '.join(names)}). " - f"Inspect a run with `tycoon schedule status `." + f"Schedules: {len(names)} installed ({', '.join(names)}). Inspect a run with `tycoon schedule status `." ) diff --git a/src/tycoon/commands/explore.py b/src/tycoon/commands/explore.py index cd1899f..4bfde8c 100644 --- a/src/tycoon/commands/explore.py +++ b/src/tycoon/commands/explore.py @@ -3,18 +3,18 @@ from __future__ import annotations from pathlib import Path -from typing import Annotated, Optional +from typing import Annotated import click import typer -from tycoon.config import config +from tycoon.config import TycoonConfig, load_config from tycoon.utils.console import error, header, info, success, warn def analyze_cmd( source_name: Annotated[ - Optional[str], + str | None, typer.Argument(help="Name of the registered source to analyze."), ] = None, no_dbt: Annotated[ @@ -75,12 +75,13 @@ def analyze_cmd( from tycoon.utils.duckdb_utils import get_tables # 1. Verify tycoon.yml exists - if not config.has_project_file: + cfg = load_config() + if not cfg.has_project_file: error("No tycoon.yml found. Run 'tycoon init' first.") raise typer.Exit(1) # 2. Resolve which source(s) we're analyzing. - sources = config.sources + sources = cfg.sources if all_sources: if source_name: error("Pass either a source name or --all, not both.") @@ -88,7 +89,7 @@ def analyze_cmd( if not sources: error("No sources registered in tycoon.yml. Run 'tycoon data sources add' first.") raise typer.Exit(1) - _analyze_all(force=force, no_dbt=no_dbt, rill=rill, build=build) + _analyze_all(cfg=cfg, force=force, no_dbt=no_dbt, rill=rill, build=build) return if not source_name: @@ -102,10 +103,7 @@ def analyze_cmd( ) if source_name not in sources: - error( - f"Source '{source_name}' not found in tycoon.yml. " - f"Available: {', '.join(sources.keys()) or '(none)'}" - ) + error(f"Source '{source_name}' not found in tycoon.yml. Available: {', '.join(sources.keys()) or '(none)'}") raise typer.Exit(1) source_cfg = sources[source_name] @@ -115,12 +113,9 @@ def analyze_cmd( info(f"Schema: {schema_name}") # 3. Verify raw database exists and has data for this schema - raw_db = config.raw_db + raw_db = cfg.raw_db if not raw_db.exists(): - error( - f"Raw database not found at {raw_db}. " - f"Run 'tycoon data sources run {source_name}' first." - ) + error(f"Raw database not found at {raw_db}. Run 'tycoon data sources run {source_name}' first.") raise typer.Exit(1) all_tables = get_tables(raw_db) @@ -139,7 +134,7 @@ def analyze_cmd( # 4. Generate dbt staging models if not no_dbt: info("Generating dbt staging models...") - staging_dir = config.dbt_project_dir / "models" / "staging" / source_name + staging_dir = cfg.dbt_project_dir / "models" / "staging" / source_name try: result = generate_staging_models( raw_db_path=raw_db, @@ -173,7 +168,7 @@ def analyze_cmd( from tycoon.scaffolding.rill_generator import generate_rill_config from tycoon.scaffolding.templates import scaffold_rill_dir - rill_dir = config.rill_dir + rill_dir = cfg.rill_dir if not rill_dir.exists(): info(f"Rill project not found; scaffolding at {rill_dir}") scaffold_rill_dir(rill_dir) @@ -203,10 +198,7 @@ def analyze_cmd( from tycoon.utils.console import console console.print() - success( - f"Explore scaffolding complete — " - f"{len(all_generated)} file(s) generated for source '{source_name}'" - ) + success(f"Explore scaffolding complete — {len(all_generated)} file(s) generated for source '{source_name}'") # 7. Optionally run dbt build — route through transform's _run_dbt so # profile resolution honors tycoon.yml + $DBT_PROFILES_DIR + ~/.dbt # exactly like a standalone `tycoon data transform build`. @@ -231,8 +223,7 @@ def analyze_cmd( success("dbt build completed successfully.") - -def _analyze_all(*, force: bool, no_dbt: bool, rill: bool, build: bool) -> None: +def _analyze_all(*, cfg: TycoonConfig, force: bool, no_dbt: bool, rill: bool, build: bool) -> None: """Iterate every registered source and analyze each. Soft-skips sources whose raw DB doesn't exist yet. ``--rill`` and @@ -240,7 +231,7 @@ def _analyze_all(*, force: bool, no_dbt: bool, rill: bool, build: bool) -> None: """ from tycoon.scaffolding.dbt_generator import generate_staging_models - sources = config.sources + sources = cfg.sources header(f"Analyzing all sources ({len(sources)})") total_generated: list[str] = [] @@ -249,7 +240,7 @@ def _analyze_all(*, force: bool, no_dbt: bool, rill: bool, build: bool) -> None: for src_name, src_cfg in sources.items(): info(f" → {src_name} (schema: {src_cfg.schema_name})") - raw_db = config.raw_db + raw_db = cfg.raw_db if not raw_db.exists(): warn( f" Skipping {src_name} — raw DB not found at {raw_db}. " @@ -261,7 +252,7 @@ def _analyze_all(*, force: bool, no_dbt: bool, rill: bool, build: bool) -> None: if no_dbt: continue - staging_dir = config.dbt_project_dir / "models" / "staging" / src_name + staging_dir = cfg.dbt_project_dir / "models" / "staging" / src_name try: result = generate_staging_models( raw_db_path=raw_db, @@ -287,10 +278,7 @@ def _analyze_all(*, force: bool, no_dbt: bool, rill: bool, build: bool) -> None: f"generated {len(total_generated)} dbt file(s) total" ) if total_skipped: - warn( - f"Skipped {len(total_skipped)} hand-edited file(s) (sentinel removed). " - f"Re-run with --force to overwrite." - ) + warn(f"Skipped {len(total_skipped)} hand-edited file(s) (sentinel removed). Re-run with --force to overwrite.") # Note: --rill and --build aren't yet wired for --all to keep the # initial surface small. File a follow-up issue if needed. diff --git a/src/tycoon/commands/fivetran.py b/src/tycoon/commands/fivetran.py index f6e4327..ea92b3b 100644 --- a/src/tycoon/commands/fivetran.py +++ b/src/tycoon/commands/fivetran.py @@ -72,19 +72,10 @@ def sync() -> None: error(f"Fivetran sync failed: {exc}") raise typer.Exit(1) from exc - success( - f"Captured {result.connectors_seen} connector(s) at " - f"{result.captured_at.isoformat(timespec='seconds')}." - ) - info( - f" Healthy: {result.healthy} · Failing: {result.failing} · " - f"Paused: {result.paused} · New: {result.new}" - ) + success(f"Captured {result.connectors_seen} connector(s) at {result.captured_at.isoformat(timespec='seconds')}.") + info(f" Healthy: {result.healthy} · Failing: {result.failing} · Paused: {result.paused} · New: {result.new}") if result.connectors_seen == 0: - warn( - "No connectors found in this group. Check `group_id` in " - "tycoon.yml's stack.ingestion_metadata block." - ) + warn("No connectors found in this group. Check `group_id` in tycoon.yml's stack.ingestion_metadata block.") @app.command(name="list") @@ -95,10 +86,7 @@ def list_cmd() -> None: rows = latest_connector_snapshot(metadata_db_path(config.root)) if not rows: - info( - "No snapshots yet. Run [bold]tycoon data fivetran sync[/bold] to " - "fetch the current state." - ) + info("No snapshots yet. Run [bold]tycoon data fivetran sync[/bold] to fetch the current state.") return table = Table(show_header=True, header_style="bold cyan") @@ -127,6 +115,5 @@ def list_cmd() -> None: console.print(table) console.print() info( - "Snapshot history accumulates in `.tycoon/metadata.duckdb` — " - "every `fivetran sync` adds one row per connector." + "Snapshot history accumulates in `.tycoon/metadata.duckdb` — every `fivetran sync` adds one row per connector." ) diff --git a/src/tycoon/commands/history.py b/src/tycoon/commands/history.py index 7232cb3..a8343ab 100644 --- a/src/tycoon/commands/history.py +++ b/src/tycoon/commands/history.py @@ -23,7 +23,6 @@ from tycoon.observability import metadata_db_path from tycoon.utils.console import console, error, header, info - app = typer.Typer( help="Show dlt + dbt run history captured in .tycoon/metadata.duckdb.", invoke_without_command=True, @@ -88,11 +87,7 @@ def _render_history_table(runs: list[RunSummary]) -> Table: for s in runs: is_dbt = s.runtime_id == "dbt" - tool_style = ( - "[bold magenta]dbt[/bold magenta]" - if is_dbt - else "[bold cyan]dlt[/bold cyan]" - ) + tool_style = "[bold magenta]dbt[/bold magenta]" if is_dbt else "[bold cyan]dlt[/bold cyan]" status_str = "[green]✓[/green]" if s.status == "success" else "[red]✗[/red]" if is_dbt: @@ -186,10 +181,7 @@ def _list_history( if not runs: if layer_models is not None: - info( - f"No dbt invocations captured for the [bold]{layer}[/bold] " - "layer yet." - ) + info(f"No dbt invocations captured for the [bold]{layer}[/bold] layer yet.") elif source is not None: info(f"No dlt runs captured for source [bold]{source}[/bold].") else: @@ -229,10 +221,7 @@ def _show_run(id_prefix: str) -> None: detail = None if detail is None: - error( - f"No run found matching prefix '{id_prefix}'. " - "Try [bold]tycoon data history[/bold] to list." - ) + error(f"No run found matching prefix '{id_prefix}'. Try [bold]tycoon data history[/bold] to list.") raise typer.Exit(1) s = detail.summary @@ -275,12 +264,8 @@ def _show_run(id_prefix: str) -> None: @app.callback() def history_default( ctx: typer.Context, - tool: str = typer.Option( - "all", "--tool", "-t", help="Filter by tool: all, dlt, or dbt." - ), - limit: int = typer.Option( - 20, "--limit", "-n", help="Max rows to display (default 20)." - ), + tool: str = typer.Option("all", "--tool", "-t", help="Filter by tool: all, dlt, or dbt."), + limit: int = typer.Option(20, "--limit", "-n", help="Max rows to display (default 20)."), source: str = typer.Option( None, "--source", @@ -318,9 +303,7 @@ def history_default( @app.command("show") def history_show( - run_id: str = typer.Argument( - ..., help="Load id (dlt) or event id (dbt). Short prefix OK." - ), + run_id: str = typer.Argument(..., help="Load id (dlt) or event id (dbt). Short prefix OK."), ) -> None: """Show per-table detail for a specific run.""" _show_run(run_id) diff --git a/src/tycoon/commands/init.py b/src/tycoon/commands/init.py index 2584ac6..c06fc78 100644 --- a/src/tycoon/commands/init.py +++ b/src/tycoon/commands/init.py @@ -4,7 +4,7 @@ from dataclasses import dataclass, field from pathlib import Path -from typing import Annotated, Optional +from typing import Annotated import typer @@ -14,6 +14,7 @@ StackConfig, TransformationTool, WarehouseType, + migrate_project, ) from tycoon.scaffolding.templates import ( list_templates, @@ -161,9 +162,13 @@ def _prompt_register_project(component: str, default_sibling: Path) -> str | Non f"Clone into {default_sibling}?", default=True, ) - dest = default_sibling if clone_here else Path( - typer.prompt(f"Where should the {component} project be cloned?", default=str(default_sibling)) - ).expanduser().resolve() + dest = ( + default_sibling + if clone_here + else Path(typer.prompt(f"Where should the {component} project be cloned?", default=str(default_sibling))) + .expanduser() + .resolve() + ) if not _clone_repo(raw, dest): return None return str(dest) @@ -178,11 +183,14 @@ def _prompt_register_project(component: str, default_sibling: Path) -> str | Non def _prompt_ingestion() -> tuple[IngestionTool, bool]: _print_section("Ingestion") console.print("How do you load data into your warehouse?") - choice = _prompt_choice("Choice", [ - "dlt — tycoon manages it (scaffolds and runs dlt pipelines)", - "External (Airbyte / Fivetran / Meltano / custom) — tycoon records only", - "Skip — no ingestion configured", - ]) + choice = _prompt_choice( + "Choice", + [ + "dlt — tycoon manages it (scaffolds and runs dlt pipelines)", + "External (Airbyte / Fivetran / Meltano / custom) — tycoon records only", + "Skip — no ingestion configured", + ], + ) if choice == 1: return IngestionTool.dlt, True if choice == 2: @@ -197,11 +205,14 @@ def _prompt_ingestion() -> tuple[IngestionTool, bool]: def _prompt_warehouse(project_name: str) -> tuple[WarehouseType, str]: _print_section("Warehouse") console.print("Where should your data live?") - choice = _prompt_choice("Choice", [ - "Local DuckDB at ./data/warehouse.duckdb [recommended]", - "Use an existing DuckDB file (provide path)", - "Cloud — MotherDuck / Snowflake / BigQuery", - ]) + choice = _prompt_choice( + "Choice", + [ + "Local DuckDB at ./data/warehouse.duckdb [recommended]", + "Use an existing DuckDB file (provide path)", + "Cloud — MotherDuck / Snowflake / BigQuery", + ], + ) if choice == 1: return WarehouseType.duckdb, "data/warehouse.duckdb" if choice == 2: @@ -297,8 +308,10 @@ def _prompt_rill( # commands and the new `tycoon profiles` namespace. We re-export the old # names here so any external importer (and the existing test suite) keeps # working. -from tycoon.dbt_profiles import ( # noqa: E402 +from tycoon.dbt_profiles import ( DbtWarehouseTarget as DbtWarehouseTarget, # re-exported for tests + register.py +) +from tycoon.dbt_profiles import ( extract_dbt_warehouse_target as _extract_dbt_warehouse_target, ) @@ -357,7 +370,6 @@ def _maybe_align_warehouse(wizard_warehouse_path: str, dbt_project_dir: Path) -> return wizard_warehouse_path - def _run_wizard(target: Path, project_name: str) -> WizardResult: """Run the interactive setup questionnaire, per-component. @@ -448,9 +460,7 @@ def _parse_param_pairs(raw: list[str]) -> dict[str, str]: out: dict[str, str] = {} for entry in raw: if "=" not in entry: - raise typer.BadParameter( - f"--param must be in 'name=value' form; got '{entry}'" - ) + raise typer.BadParameter(f"--param must be in 'name=value' form; got '{entry}'") name, _, value = entry.partition("=") name = name.strip() if not name: @@ -461,7 +471,7 @@ def _parse_param_pairs(raw: list[str]) -> dict[str, str]: def init_cmd( template: Annotated[ - Optional[str], + str | None, typer.Option( "--template", "-t", @@ -469,7 +479,7 @@ def init_cmd( ), ] = None, name: Annotated[ - Optional[str], + str | None, typer.Option( "--name", "-n", @@ -484,7 +494,7 @@ def init_cmd( ), ] = False, param: Annotated[ - Optional[list[str]], + list[str] | None, typer.Option( "--param", "-p", @@ -495,8 +505,31 @@ def init_cmd( ), ), ] = None, + upgrade: Annotated[ + bool, + typer.Option( + "--upgrade", + help="Migrate tycoon.yml to the current schema version and exit.", + ), + ] = False, ) -> None: """Initialize a new tycoon project in the current directory.""" + if upgrade: + target = Path.cwd() + if not (target / "tycoon.yml").exists(): + error("No tycoon.yml found in the current directory. Run 'tycoon init' to create one.") + raise typer.Exit(1) + try: + changed = migrate_project(target) + except ValueError as exc: + error(str(exc)) + raise typer.Exit(1) from exc + if changed: + success("tycoon.yml migrated to the current schema version.") + else: + info("tycoon.yml is already up to date.") + raise typer.Exit(0) + if list_templates_flag: templates = list_templates() if not templates: diff --git a/src/tycoon/commands/notify.py b/src/tycoon/commands/notify.py index a4c123b..5f1bc1b 100644 --- a/src/tycoon/commands/notify.py +++ b/src/tycoon/commands/notify.py @@ -49,10 +49,7 @@ def notify_cmd( --notify`` wires the same surface into pipeline runs. """ if severity not in notify_mod.SEVERITIES: - error( - f"Unknown severity '{severity}'. Choose one of: " - f"{', '.join(notify_mod.SEVERITIES)}." - ) + error(f"Unknown severity '{severity}'. Choose one of: {', '.join(notify_mod.SEVERITIES)}.") raise typer.Exit(2) fields = _parse_fields(field) diff --git a/src/tycoon/commands/observability.py b/src/tycoon/commands/observability.py index aa0d528..ce44e0c 100644 --- a/src/tycoon/commands/observability.py +++ b/src/tycoon/commands/observability.py @@ -16,7 +16,6 @@ from tycoon.config import config from tycoon.utils.console import error, info, next_steps, success, warn - app = typer.Typer(help="Tycoon's observability metadata as first-class data.") @@ -89,6 +88,7 @@ def observability_scaffold( # projects; an explicit dbt_profiles_dir overrides. if project.dbt_profiles_dir: from pathlib import Path + profiles_dir = Path(project.dbt_profiles_dir) if not profiles_dir.is_absolute(): profiles_dir = (config.root / profiles_dir).resolve() @@ -117,5 +117,5 @@ def observability_scaffold( success("Observability scaffolding complete.") next_steps( ("tycoon data transform run --select _tycoon", "build the staging views"), - ("tycoon data query \"SELECT * FROM stg_tycoon__dlt_runs LIMIT 5\"", "smoke-test"), + ('tycoon data query "SELECT * FROM stg_tycoon__dlt_runs LIMIT 5"', "smoke-test"), ) diff --git a/src/tycoon/commands/profiles.py b/src/tycoon/commands/profiles.py index 3d71ec9..e5ebf45 100644 --- a/src/tycoon/commands/profiles.py +++ b/src/tycoon/commands/profiles.py @@ -13,7 +13,6 @@ from __future__ import annotations from pathlib import Path -from typing import Optional import typer import yaml @@ -55,17 +54,14 @@ def _require_project() -> None: """Bail when there's no tycoon.yml — every subcommand needs one.""" if not config.has_project_file: - error( - "No tycoon.yml found. Run [bold]tycoon init[/bold] first, " - "or cd into an existing tycoon project." - ) + error("No tycoon.yml found. Run [bold]tycoon init[/bold] first, or cd into an existing tycoon project.") raise typer.Exit(1) def _resolve_for_cli( - profile: Optional[str], - profiles_dir: Optional[Path], - target: Optional[str], + profile: str | None, + profiles_dir: Path | None, + target: str | None, ): """Centralized resolve_profile call; returns ResolvedProfile | None.""" project = config.project @@ -85,7 +81,7 @@ def _resolve_for_cli( @app.command(name="list") def list_cmd( - profiles_dir: Optional[Path] = _PROFILES_DIR_OPTION, + profiles_dir: Path | None = _PROFILES_DIR_OPTION, ) -> None: """List every profile in the active profiles.yml + targets + adapters.""" _require_project() @@ -121,10 +117,7 @@ def list_cmd( active_target = resolved.target if is_active else "" targets_display = ", ".join(p.targets) if p.targets else "—" if active_target: - targets_display = ", ".join( - f"[bold]{t}[/bold]" if t == active_target else t - for t in p.targets - ) + targets_display = ", ".join(f"[bold]{t}[/bold]" if t == active_target else t for t in p.targets) table.add_row( f"[bold]{p.name}[/bold]" if is_active else p.name, targets_display, @@ -135,18 +128,17 @@ def list_cmd( console.print(table) console.print( - f"\n[dim]Tycoon will use:[/dim] [bold]{resolved.profile}[/bold] " - f"(target: [bold]{resolved.target}[/bold])" + f"\n[dim]Tycoon will use:[/dim] [bold]{resolved.profile}[/bold] (target: [bold]{resolved.target}[/bold])" ) @app.command() def show( - name: Optional[str] = typer.Argument( + name: str | None = typer.Argument( None, help="Profile name. Defaults to the active profile (per tycoon.yml + dbt_project.yml).", ), - profiles_dir: Optional[Path] = _PROFILES_DIR_OPTION, + profiles_dir: Path | None = _PROFILES_DIR_OPTION, ) -> None: """Pretty-print one profile, with secrets redacted.""" _require_project() @@ -175,9 +167,9 @@ def show( @app.command() def doctor( - profile: Optional[str] = _PROFILE_OPTION, - profiles_dir: Optional[Path] = _PROFILES_DIR_OPTION, - target: Optional[str] = _TARGET_OPTION, + profile: str | None = _PROFILE_OPTION, + profiles_dir: Path | None = _PROFILES_DIR_OPTION, + target: str | None = _TARGET_OPTION, ) -> None: """Validate the active profile + cross-check against tycoon.yml.""" _require_project() @@ -188,9 +180,9 @@ def doctor( def run_profile_checks( - profile: Optional[str] = None, - profiles_dir: Optional[Path] = None, - target: Optional[str] = None, + profile: str | None = None, + profiles_dir: Path | None = None, + target: str | None = None, ) -> int: """Shared check logic — also called by ``tycoon doctor``. @@ -216,9 +208,7 @@ def run_profile_checks( f"profile [bold]{resolved.profile}[/bold]'s outputs. " "Available targets: " + ", ".join( - discover_profiles(resolved.profiles_yml)[0].targets - if discover_profiles(resolved.profiles_yml) - else [] + discover_profiles(resolved.profiles_yml)[0].targets if discover_profiles(resolved.profiles_yml) else [] ) ) return 1 diff --git a/src/tycoon/commands/register.py b/src/tycoon/commands/register.py index b92db82..f669d06 100644 --- a/src/tycoon/commands/register.py +++ b/src/tycoon/commands/register.py @@ -3,7 +3,7 @@ from __future__ import annotations from pathlib import Path -from typing import Annotated, Optional +from typing import Annotated import typer import yaml @@ -15,14 +15,13 @@ ) from tycoon.config import config from tycoon.project import ( - BITool, PROJECT_FILENAME, + BITool, TransformationTool, WarehouseType, ) from tycoon.utils.console import console, error, header, info, success, warn - app = typer.Typer( help="Attach existing dbt, Rill, or warehouse components to the current tycoon.yml.", no_args_is_help=True, @@ -38,9 +37,11 @@ def _resolve_path_or_url(source: str, default_clone_dest: Path) -> Path | None: ) dest = default_clone_dest if not clone_here: - dest = Path( - typer.prompt("Where should the project be cloned?", default=str(default_clone_dest)) - ).expanduser().resolve() + dest = ( + Path(typer.prompt("Where should the project be cloned?", default=str(default_clone_dest))) + .expanduser() + .resolve() + ) if not _clone_repo(source, dest): return None return dest @@ -71,14 +72,11 @@ def _create_new_dbt_project( """ from tycoon.scaffolding.templates import _scaffold_dbt_project - target = ( - Path(source).expanduser().resolve() if source else default_target.resolve() - ) + target = Path(source).expanduser().resolve() if source else default_target.resolve() if (target / "dbt_project.yml").exists(): error( - f"{target} already contains a dbt_project.yml. " - f"Use `tycoon register dbt {target}` to register it instead." + f"{target} already contains a dbt_project.yml. Use `tycoon register dbt {target}` to register it instead." ) return None @@ -141,7 +139,7 @@ def _write_raw_tycoon_yml(path: Path, data: dict) -> None: @app.command(name="dbt") def register_dbt( source: Annotated[ - Optional[str], + str | None, typer.Argument( help=( "Local path or GitHub URL of an existing dbt project. " @@ -164,7 +162,7 @@ def register_dbt( ), ] = False, profiles_dir: Annotated[ - Optional[Path], + Path | None, typer.Option( "--profiles-dir", help=( @@ -174,14 +172,14 @@ def register_dbt( ), ] = None, profile: Annotated[ - Optional[str], + str | None, typer.Option( "--profile", help="Profile name within profiles.yml. Default: the `profile:` field in dbt_project.yml.", ), ] = None, target: Annotated[ - Optional[str], + str | None, typer.Option( "--target", help="Target within the profile (dev / prod / ...). Default: the profile's `target:` field, then 'dev'.", @@ -352,18 +350,13 @@ def _align_duckdb_warehouse(raw: dict, stack: dict, dbt_warehouse: str) -> None: return console.print() - warn( - f"Your dbt project targets {dbt_warehouse}, " - f"but tycoon.yml has warehouse = {current_warehouse!r}." - ) + warn(f"Your dbt project targets {dbt_warehouse}, but tycoon.yml has warehouse = {current_warehouse!r}.") if typer.confirm(f"Update warehouse to {dbt_warehouse}?", default=True): raw.setdefault("database", {})["warehouse"] = dbt_warehouse if raw["database"].get("raw", "") == current_warehouse: raw["database"]["raw"] = dbt_warehouse stack["warehouse"] = ( - WarehouseType.motherduck.value - if dbt_warehouse.startswith("md:") - else WarehouseType.duckdb.value + WarehouseType.motherduck.value if dbt_warehouse.startswith("md:") else WarehouseType.duckdb.value ) @@ -400,9 +393,7 @@ def _align_cloud_warehouse(raw: dict, stack: dict, dbt_target) -> None: "MotherDuck, so it won't be touched. Only stack.warehouse is " "adjusted for cloud adapters." ) - if typer.confirm( - f"Update stack.warehouse to {desired_type!r}?", default=True - ): + if typer.confirm(f"Update stack.warehouse to {desired_type!r}?", default=True): stack["warehouse"] = desired_type # Snowflake: surface an account-mismatch hint when the user previously @@ -429,7 +420,7 @@ def _align_cloud_warehouse(raw: dict, stack: dict, dbt_target) -> None: @app.command(name="warehouse") def register_warehouse( warehouse_type: Annotated[ - Optional[str], + str | None, typer.Option( "--type", help=( @@ -440,14 +431,14 @@ def register_warehouse( ), ] = None, path: Annotated[ - Optional[str], + str | None, typer.Option( "--path", help="For --type duckdb: path to the local .duckdb file. Default: data/warehouse.duckdb.", ), ] = None, catalog: Annotated[ - Optional[str], + str | None, typer.Option( "--catalog", help="For --type motherduck: catalog name. Becomes md: in tycoon.yml.", @@ -514,10 +505,14 @@ def register_warehouse( error("--type is required when --no-prompt is set.") raise typer.Exit(1) else: - choice = typer.prompt( - "Cloud (MotherDuck) or local DuckDB? [cloud/local]", - default="local", - ).strip().lower() + choice = ( + typer.prompt( + "Cloud (MotherDuck) or local DuckDB? [cloud/local]", + default="local", + ) + .strip() + .lower() + ) if choice in _CLOUD_TYPE_ALIASES: choice = "cloud" elif choice in _LOCAL_TYPE_ALIASES: @@ -609,6 +604,7 @@ def register_rill( raise typer.Exit(1) import os + try: rel = os.path.relpath(resolved, config.root) raw["rill_dir"] = rel diff --git a/src/tycoon/commands/run_all.py b/src/tycoon/commands/run_all.py index 89c2c08..bea3815 100644 --- a/src/tycoon/commands/run_all.py +++ b/src/tycoon/commands/run_all.py @@ -4,17 +4,17 @@ import shutil import time -from typing import Annotated, Optional +from typing import Annotated import typer -from tycoon.config import config +from tycoon.config import load_config from tycoon.utils.console import console, error, header, info, next_steps, success, warn def run_all_cmd( max_records: Annotated[ - Optional[int], + int | None, typer.Option( "--max-records", "-n", @@ -46,7 +46,8 @@ def run_all_cmd( ] = False, ) -> None: """Ingest all registered sources then run dbt build.""" - if not config.has_project_file: + cfg = load_config() + if not cfg.has_project_file: error("No tycoon.yml found. Run [bold]tycoon init[/bold] first.") raise typer.Exit(1) @@ -59,7 +60,7 @@ def _emit(severity: str, message: str, **fields: str) -> None: return from tycoon import notify as notify_mod - project = config.project + project = cfg.project prefs = project.notify if project is not None else None allowed = prefs.severities if prefs is not None else ["success", "error"] if severity not in allowed: @@ -75,12 +76,12 @@ def _emit(severity: str, message: str, **fields: str) -> None: if not skip_ingest: from tycoon.ingestion.runner import run_source as _run_source - sources = config.sources + sources = cfg.sources if not sources: error("No sources registered. Run [bold]tycoon data sources add[/bold] first.") raise typer.Exit(1) - config.ensure_data_dir() + cfg.ensure_data_dir() total = len(sources) info(f"Ingesting {total} source{'s' if total != 1 else ''}...") if max_records is not None: @@ -92,7 +93,7 @@ def _emit(severity: str, message: str, **fields: str) -> None: _pipeline, load_info = _run_source( name=name, source_config=source_config, - raw_db_path=config.raw_db, + raw_db_path=cfg.raw_db, max_records=max_records, ) success(f"{name}: {load_info}") @@ -111,11 +112,12 @@ def _emit(severity: str, message: str, **fields: str) -> None: _emit("error", "run-all failed: dbt not found on PATH", stage="transform") raise typer.Exit(1) - project_dir = config.dbt_project_dir + project_dir = cfg.dbt_project_dir if not project_dir.exists(): warn(f"dbt project not found at {project_dir} — skipping transform.") else: import subprocess + console.rule("[bold cyan]dbt build") cmd = [dbt, "build", "--target", target, "--profiles-dir", str(project_dir)] console.print(f"[dim]Running: {' '.join(cmd)}[/dim]") @@ -131,7 +133,7 @@ def _emit(severity: str, message: str, **fields: str) -> None: elapsed = time.time() - start console.rule("[bold green]Done") success(f"Finished in {elapsed:.1f}s") - _emit("success", "run-all complete", elapsed=f"{elapsed:.1f}s", sources=str(len(config.sources))) + _emit("success", "run-all complete", elapsed=f"{elapsed:.1f}s", sources=str(len(cfg.sources))) next_steps( ("tycoon data status", "check source freshness and row counts"), ("tycoon start --only rill", "explore results in Rill"), diff --git a/src/tycoon/commands/schedule.py b/src/tycoon/commands/schedule.py index 01813e6..2c4efb9 100644 --- a/src/tycoon/commands/schedule.py +++ b/src/tycoon/commands/schedule.py @@ -20,7 +20,7 @@ def add_schedule( "data run-all", "--command", "-c", - help="The tycoon command to run, e.g. \"data run-all --notify\".", + help='The tycoon command to run, e.g. "data run-all --notify".', ), at: str = typer.Option("04:00", "--at", help="Time of day (24h HH:MM)."), cadence: str = typer.Option( @@ -29,9 +29,7 @@ def add_schedule( help="daily (at --at), hourly (at --at's minute), or weekly (--weekday at --at).", ), weekday: int = typer.Option(1, "--weekday", help="Weekly cadence: 1=Mon .. 7=Sun."), - notify: bool = typer.Option( - False, "--notify", help="Append --notify to the scheduled command." - ), + notify: bool = typer.Option(False, "--notify", help="Append --notify to the scheduled command."), force: bool = typer.Option(False, "--force", help="Replace an existing schedule of the same name."), ) -> None: """Install a scheduled run of a tycoon command. diff --git a/src/tycoon/commands/semantics.py b/src/tycoon/commands/semantics.py index 990cb0b..ee59169 100644 --- a/src/tycoon/commands/semantics.py +++ b/src/tycoon/commands/semantics.py @@ -16,7 +16,6 @@ from __future__ import annotations from pathlib import Path -from typing import Optional import typer @@ -36,10 +35,7 @@ def _require_project() -> None: if not config.has_project_file: - error( - "No tycoon.yml found. Run [bold]tycoon init[/bold] first, " - "or cd into an existing tycoon project." - ) + error("No tycoon.yml found. Run [bold]tycoon init[/bold] first, or cd into an existing tycoon project.") raise typer.Exit(1) @@ -57,7 +53,7 @@ def _has_dbt_transformation(project) -> bool: @app.command() def scaffold( - out: Optional[Path] = typer.Option( + out: Path | None = typer.Option( None, "--out", "-o", @@ -85,7 +81,7 @@ def scaffold( _require_project() header("OSI semantic-layer scaffold") - out_path = (out.expanduser().resolve() if out else _default_out_path()) + out_path = out.expanduser().resolve() if out else _default_out_path() project = config.project project_name = project.name if project else config.root.name @@ -111,23 +107,18 @@ def scaffold( if result.datasets_emitted: success( f"Wrote {result.out_path} — " - f"{len(result.datasets_emitted)} dataset(s): " - + ", ".join(result.datasets_emitted) + f"{len(result.datasets_emitted)} dataset(s): " + ", ".join(result.datasets_emitted) ) info( - "Next: edit `metrics:` and `relationships:` by hand, then " - "[bold]tycoon semantics doctor[/bold] to validate." + "Next: edit `metrics:` and `relationships:` by hand, then [bold]tycoon semantics doctor[/bold] to validate." ) else: - info( - f"Wrote {result.out_path} (no marts found — placeholder " - "dataset emitted to keep the file schema-valid)." - ) + info(f"Wrote {result.out_path} (no marts found — placeholder dataset emitted to keep the file schema-valid).") @app.command() def doctor( - path: Optional[Path] = typer.Option( + path: Path | None = typer.Option( None, "--path", help="OSI YAML to validate. Default: /semantic/osi.yaml.", @@ -141,13 +132,10 @@ def doctor( _require_project() header("OSI semantic-layer doctor") - osi_path = (path.expanduser().resolve() if path else _default_out_path()) + osi_path = path.expanduser().resolve() if path else _default_out_path() if not osi_path.exists(): - error( - f"OSI YAML not found at {osi_path}. " - "Run [bold]tycoon semantics scaffold[/bold] first." - ) + error(f"OSI YAML not found at {osi_path}. Run [bold]tycoon semantics scaffold[/bold] first.") raise typer.Exit(1) errors = validate_osi_yaml(osi_path) @@ -161,13 +149,13 @@ def doctor( raise typer.Exit(1) -def run_osi_check(path: Optional[Path] = None) -> int: +def run_osi_check(path: Path | None = None) -> int: """Shared check used by `tycoon doctor`. Returns 0 if valid or skipped, 1 if invalid. Skips silently when no OSI file exists — OSI is opt-in per the v0.1.6 design (issue #28 Q6). Only flags when a file exists *and* fails. """ - osi_path = (path.expanduser().resolve() if path else _default_out_path()) + osi_path = path.expanduser().resolve() if path else _default_out_path() if not osi_path.exists(): info("OSI: no semantic/osi.yaml present (opt-in via `tycoon semantics scaffold`).") return 0 diff --git a/src/tycoon/commands/sources.py b/src/tycoon/commands/sources.py index c5b223c..3680a5c 100644 --- a/src/tycoon/commands/sources.py +++ b/src/tycoon/commands/sources.py @@ -3,13 +3,13 @@ from __future__ import annotations from pathlib import Path -from typing import Any, Optional +from typing import Any import click import typer from rich.table import Table -from tycoon.config import config +from tycoon.config import TycoonConfig, load_config from tycoon.ingestion.catalog import CATALOG, CatalogEntry from tycoon.project import SourceConfig, load_project, save_project from tycoon.utils.console import console, error, header, info, next_steps, success, warn @@ -32,11 +32,13 @@ app.add_typer(list_app, name="list") -def _require_project() -> None: - """Abort if no tycoon.yml exists.""" - if not config.has_project_file: +def _require_project() -> TycoonConfig: + """Return a fresh TycoonConfig, aborting if no tycoon.yml exists.""" + cfg = load_config() + if not cfg.has_project_file: error("No tycoon.yml found. Run [bold]tycoon init[/bold] first.") raise typer.Exit(1) + return cfg # --------------------------------------------------------------------------- @@ -79,9 +81,9 @@ def catalog_default(ctx: typer.Context) -> None: def _list_sources() -> None: """Print registered sources table.""" - _require_project() + cfg = _require_project() - sources = config.sources + sources = cfg.sources if not sources: info("No sources registered yet.") info("Browse available sources with [bold]tycoon data sources catalog[/bold]") @@ -110,9 +112,9 @@ def show_source( name: str = typer.Argument(help="Name of the source to show"), ) -> None: """Show detailed configuration for a specific source.""" - _require_project() + cfg = _require_project() - sources = config.sources + sources = cfg.sources if name not in sources: error(f"Source [bold]{name}[/bold] not found.") info(f"Available sources: {', '.join(sources.keys()) if sources else '(none)'}") @@ -264,12 +266,12 @@ def _parse_config_pairs(pairs: list[str]) -> dict[str, Any]: def _build_source_config_from_flags( source_type: str, *, - base_url: Optional[str], - resources: Optional[str], - connection_string: Optional[str], - path: Optional[str], + base_url: str | None, + resources: str | None, + connection_string: str | None, + path: str | None, config_pairs: list[str], - catalog_entry: Optional[CatalogEntry], + catalog_entry: CatalogEntry | None, ) -> dict[str, Any]: """Assemble a source's ``config:`` dict from non-interactive flags. @@ -304,9 +306,7 @@ def _build_source_config_from_flags( cfg["connection_string"] = connection_string elif source_type == "filesystem": if path is None: - error( - "--path is required for `filesystem` sources with --no-prompt." - ) + error("--path is required for `filesystem` sources with --no-prompt.") raise typer.Exit(1) cfg["path"] = path @@ -317,11 +317,11 @@ def _build_source_config_from_flags( @app.command("add") def add_source( - source_type: Optional[str] = typer.Argument( + source_type: str | None = typer.Argument( None, help="Source type — run `tycoon data sources catalog` to see all options.", ), - name: Optional[str] = typer.Option( + name: str | None = typer.Option( None, "--name", help=( @@ -330,27 +330,27 @@ def add_source( "for other types under --no-prompt." ), ), - schema: Optional[str] = typer.Option( + schema: str | None = typer.Option( None, "--schema", help="Raw schema name in DuckDB. Auto-derived when omitted.", ), - base_url: Optional[str] = typer.Option( + base_url: str | None = typer.Option( None, "--base-url", help="Base URL for `rest_api` sources (e.g. `https://api.example.com`).", ), - resources: Optional[str] = typer.Option( + resources: str | None = typer.Option( None, "--resources", help="Comma-separated resource list for `rest_api` (e.g. `pokemon,berry,type`).", ), - connection_string: Optional[str] = typer.Option( + connection_string: str | None = typer.Option( None, "--connection-string", help="Connection string for `sql_database`. Use ${ENV_VAR} for secrets.", ), - path: Optional[str] = typer.Option( + path: str | None = typer.Option( None, "--path", help="File path or URL for `filesystem` sources.", @@ -358,10 +358,7 @@ def add_source( config_pairs: list[str] = typer.Option( [], "--config", - help=( - "Extra `key=value` pairs to merge into the source config. " - "Repeatable. Overrides type-specific flags." - ), + help=("Extra `key=value` pairs to merge into the source config. Repeatable. Overrides type-specific flags."), ), no_prompt: bool = typer.Option( False, @@ -386,7 +383,7 @@ def add_source( credentials default to ``${ENV_VAR}`` references in both modes — set the env var separately. """ - _require_project() + cfg = _require_project() if not source_type: if no_prompt: @@ -418,17 +415,12 @@ def add_source( catalog_entry=catalog_entry, ) if source_type in _AUTO_NAMED_SOURCES and not name: - source_name, derived_schema = _derive_source_identity( - source_type, source_config - ) + source_name, derived_schema = _derive_source_identity(source_type, source_config) elif name: source_name = name derived_schema = f"raw_{source_name.replace('-', '_')}" else: - error( - f"--name is required under --no-prompt for `{source_type}` " - "(no auto-naming rule). Pass --name ." - ) + error(f"--name is required under --no-prompt for `{source_type}` (no auto-naming rule). Pass --name .") raise typer.Exit(1) schema_name = schema or derived_schema elif catalog_entry and source_type in _AUTO_NAMED_SOURCES: @@ -442,20 +434,11 @@ def add_source( info(f"Schema: [bold]{schema_name}[/bold]") else: default_name = name or (f"my-{source_type}" if catalog_entry else source_type) - source_name = ( - name - if name - else typer.prompt("Source name", default=default_name) - ) - default_schema = ( - schema - or (catalog_entry.default_schema if catalog_entry else f"raw_{source_name.replace('-', '_')}") - ) - schema_name = ( - schema - if schema - else typer.prompt("Schema name", default=default_schema) + source_name = name if name else typer.prompt("Source name", default=default_name) + default_schema = schema or ( + catalog_entry.default_schema if catalog_entry else f"raw_{source_name.replace('-', '_')}" ) + schema_name = schema if schema else typer.prompt("Schema name", default=default_schema) if catalog_entry: source_config = _prompt_catalog_config(catalog_entry) else: @@ -468,28 +451,22 @@ def add_source( config=source_config, ) - project = load_project(config.root) + project = load_project(cfg.root) assert project is not None # guarded by _require_project if source_name in project.sources: if force or no_prompt: if not force: - error( - f"Source '{source_name}' already exists. Pass --force to " - "overwrite under --no-prompt." - ) + error(f"Source '{source_name}' already exists. Pass --force to overwrite under --no-prompt.") raise typer.Exit(1) else: - overwrite = typer.confirm( - f"Source '{source_name}' already exists. Overwrite?", default=False - ) + overwrite = typer.confirm(f"Source '{source_name}' already exists. Overwrite?", default=False) if not overwrite: info("Cancelled.") raise typer.Exit(0) project.sources[source_name] = new_source - save_project(project, config.root) - config.reload() + save_project(project, cfg.root) success(f"Source [bold]{source_name}[/bold] added to tycoon.yml") @@ -543,9 +520,7 @@ def _maybe_install_dlt_extra(source_type: str) -> None: if is_dlt_extra_available(source_type): return - install = typer.confirm( - f"dlt[{source_type}] is not installed. Install it now?", default=True - ) + install = typer.confirm(f"dlt[{source_type}] is not installed. Install it now?", default=True) if install: if install_dlt_extra(source_type): success(f"dlt[{source_type}] installed successfully.") @@ -560,9 +535,9 @@ def remove_source( name: str = typer.Argument(help="Name of the source to remove"), ) -> None: """Remove a registered data source.""" - _require_project() + cfg = _require_project() - project = load_project(config.root) + project = load_project(cfg.root) assert project is not None if name not in project.sources: @@ -573,8 +548,7 @@ def remove_source( typer.confirm(f"Remove source '{name}'?", abort=True) del project.sources[name] - save_project(project, config.root) - config.reload() + save_project(project, cfg.root) success(f"Source [bold]{name}[/bold] removed from tycoon.yml") @@ -611,7 +585,7 @@ def _source_already_referenced(dbt_dir: Path, source_name: str) -> bool: return False -def _maybe_auto_scaffold(source_name: str, source_config: SourceConfig, *, scaffold: bool) -> None: +def _maybe_auto_scaffold(source_name: str, source_config: SourceConfig, *, cfg: TycoonConfig, scaffold: bool) -> None: """Auto-run the analyze flow if a dbt project exists and no staging models are present for this source yet. @@ -621,10 +595,10 @@ def _maybe_auto_scaffold(source_name: str, source_config: SourceConfig, *, scaff """ if not scaffold: return - project = config.project + project = cfg.project if project is not None and not project.transform.auto_scaffold: return - dbt_dir = config.dbt_project_dir + dbt_dir = cfg.dbt_project_dir if not dbt_dir.exists(): return # No dbt project to scaffold into. @@ -637,7 +611,7 @@ def _maybe_auto_scaffold(source_name: str, source_config: SourceConfig, *, scaff from tycoon.scaffolding.dbt_generator import generate_staging_models result = generate_staging_models( - raw_db_path=config.raw_db, + raw_db_path=cfg.raw_db, schema_name=source_config.schema_name, source_name=source_name, output_dir=staging_dir, @@ -654,8 +628,8 @@ def _maybe_auto_scaffold(source_name: str, source_config: SourceConfig, *, scaff @app.command(name="run") def run_source( - source_name: Optional[str] = typer.Argument(None, help="Name of the registered source to ingest."), - max_records: Optional[int] = _MaxRecordsOption, + source_name: str | None = typer.Argument(None, help="Name of the registered source to ingest."), + max_records: int | None = _MaxRecordsOption, no_scaffold: bool = typer.Option( False, "--no-scaffold", @@ -670,9 +644,9 @@ def run_source( """Ingest data from a registered source by name.""" from tycoon.ingestion.runner import run_source as _run_source - _require_project() + cfg = _require_project() - sources = config.sources + sources = cfg.sources if not source_name: if not sources: error("No sources registered. Run 'tycoon data sources add' first.") @@ -693,30 +667,31 @@ def run_source( if max_records is not None: info(f"Record cap: {max_records:,}") - config.ensure_data_dir() + cfg.ensure_data_dir() try: _pipeline, load_info = _run_source( name=source_name, source_config=source_config, - raw_db_path=config.raw_db, + raw_db_path=cfg.raw_db, max_records=max_records, ) success(f"{source_name} load complete. {load_info}") - _maybe_auto_scaffold(source_name, source_config, scaffold=not no_scaffold) + _maybe_auto_scaffold(source_name, source_config, cfg=cfg, scaffold=not no_scaffold) next_steps( ("tycoon data transform run", "run dbt models on the ingested data"), ("tycoon start --only rill", "open the Rill dashboard"), ) except Exception as exc: from tycoon.ingestion.runner import IngestionError + error(str(exc) if isinstance(exc, IngestionError) else f"{source_name} pipeline failed: {exc}") raise typer.Exit(1) from exc @app.command(name="run-all") def run_all( - max_records: Optional[int] = _MaxRecordsOption, + max_records: int | None = _MaxRecordsOption, no_scaffold: bool = typer.Option( False, "--no-scaffold", @@ -729,9 +704,9 @@ def run_all( """Run all registered source pipelines sequentially.""" from tycoon.ingestion.runner import run_source as _run_source - _require_project() + cfg = _require_project() - sources = config.sources + sources = cfg.sources if not sources: error("No sources registered. Run 'tycoon data sources add' first.") raise typer.Exit(1) @@ -741,7 +716,7 @@ def run_all( if max_records is not None: info(f"Record cap per resource: {max_records:,}") - config.ensure_data_dir() + cfg.ensure_data_dir() for i, (name, source_config) in enumerate(sources.items(), 1): info(f"Step {i}/{total} — {name} ({source_config.type})...") @@ -749,11 +724,11 @@ def run_all( _pipeline, load_info = _run_source( name=name, source_config=source_config, - raw_db_path=config.raw_db, + raw_db_path=cfg.raw_db, max_records=max_records, ) success(f"{name} complete. {load_info}") - _maybe_auto_scaffold(name, source_config, scaffold=not no_scaffold) + _maybe_auto_scaffold(name, source_config, cfg=cfg, scaffold=not no_scaffold) except Exception as exc: error(f"{name} pipeline failed: {exc}") raise typer.Exit(1) from exc diff --git a/src/tycoon/commands/start.py b/src/tycoon/commands/start.py index dc8d64b..296d2e5 100644 --- a/src/tycoon/commands/start.py +++ b/src/tycoon/commands/start.py @@ -35,12 +35,8 @@ def clear_pids() -> None: def start_cmd( - skip: list[str] = typer.Option( - [], "--skip", help="Server(s) to skip. Repeatable: --skip rill" - ), - only: list[str] = typer.Option( - [], "--only", help="Only start these server(s). Repeatable: --only rill" - ), + skip: list[str] = typer.Option([], "--skip", help="Server(s) to skip. Repeatable: --skip rill"), + only: list[str] = typer.Option([], "--only", help="Only start these server(s). Repeatable: --only rill"), ) -> None: """Start the Rill dashboard and Quack warehouse server. @@ -109,12 +105,14 @@ def _preflight_checks(targets: list[str]) -> None: """Warn if required binaries are missing before starting.""" if "rill" in targets: import shutil + if not shutil.which("rill"): warn("rill not found — skipping.") targets.remove("rill") if "quack" in targets: from tycoon import quack + if not quack.extension_available(): info("Quack extension unavailable (needs duckdb core_nightly) — serving the warehouse in file mode.") targets.remove("quack") @@ -133,6 +131,7 @@ def _start_server(name: str) -> subprocess.Popen | None: if name == "quack": from tycoon import quack + return quack.start_server(config.root, port=PORTS["quack"]) return None @@ -140,8 +139,9 @@ def _start_server(name: str) -> subprocess.Popen | None: def _print_urls(targets: list[str]) -> None: from tycoon.constants import PORTS + lines = { - "rill": ("Rill dashboards", f"http://localhost:{PORTS['rill']}"), + "rill": ("Rill dashboards", f"http://localhost:{PORTS['rill']}"), "quack": ("Quack warehouse", f"quack:localhost:{PORTS['quack']}"), } for name in targets: diff --git a/src/tycoon/commands/status.py b/src/tycoon/commands/status.py index 43a6edb..54738be 100644 --- a/src/tycoon/commands/status.py +++ b/src/tycoon/commands/status.py @@ -19,8 +19,8 @@ from __future__ import annotations import datetime +from collections.abc import Iterable from pathlib import Path -from typing import Iterable, Optional import typer from rich.panel import Panel @@ -41,7 +41,6 @@ from tycoon.project import TransformationTool from tycoon.utils.console import console, error, header, info, warn - # -- Freshness helpers --------------------------------------------------------- @@ -84,22 +83,20 @@ def _query_run_counts(metadata_db: Path) -> dict[str, int]: return {} try: with duckdb.connect(str(metadata_db), read_only=True) as con: - rows = con.execute( - "SELECT source_schema, COUNT(*) FROM dlt_runs GROUP BY source_schema" - ).fetchall() + rows = con.execute("SELECT source_schema, COUNT(*) FROM dlt_runs GROUP BY source_schema").fetchall() return {schema: count for schema, count in rows} except Exception: return {} -def _freshness_label(last_sync: Optional[datetime.datetime]) -> tuple[str, str]: +def _freshness_label(last_sync: datetime.datetime | None) -> tuple[str, str]: """Return (label, style) describing how fresh a sync is.""" if last_sync is None: return "never", "red" - now = datetime.datetime.now(tz=datetime.timezone.utc) + now = datetime.datetime.now(tz=datetime.UTC) if last_sync.tzinfo is None: - last_sync = last_sync.replace(tzinfo=datetime.timezone.utc) + last_sync = last_sync.replace(tzinfo=datetime.UTC) age = now - last_sync hours = age.total_seconds() / 3600 @@ -117,9 +114,7 @@ def _freshness_label(last_sync: Optional[datetime.datetime]) -> tuple[str, str]: # -- Layer-build freshness ------------------------------------------------------ -def _query_layer_last_build( - metadata_db: Path, model_names: list[str] -) -> Optional[datetime.datetime]: +def _query_layer_last_build(metadata_db: Path, model_names: list[str]) -> datetime.datetime | None: """Latest successful build start time across ``model_names``.""" import duckdb @@ -206,7 +201,7 @@ def _render_sources_panel( console.print(table) -def _live_refresh_fivetran(client, metadata_db: Path) -> tuple[bool, Optional[str]]: +def _live_refresh_fivetran(client, metadata_db: Path) -> tuple[bool, str | None]: """Pull connectors live and write them through to the metadata cache. Returns ``(refreshed, warning)``. ``refreshed`` is True when the live @@ -260,15 +255,14 @@ def _render_fivetran_detail() -> None: """Service / sync-state detail on top of the unified Sources panel.""" from tycoon.ingestion.fivetran_sync import ( freshness_label as fv_freshness_label, + ) + from tycoon.ingestion.fivetran_sync import ( latest_connector_snapshot, ) rows = latest_connector_snapshot(metadata_db_path(config.root)) if not rows: - info( - "Fivetran detail: no metadata captured yet. Run " - "[bold]tycoon data fivetran sync[/bold] to populate." - ) + info("Fivetran detail: no metadata captured yet. Run [bold]tycoon data fivetran sync[/bold] to populate.") return table = Table(show_header=True, header_style="bold cyan") @@ -318,10 +312,7 @@ def _render_layer_panel( for m in listed: table.add_row(m.name, m.schema or "—") - summary = ( - f"{len(listed)} model(s) — last build " - f"[{fresh_style}]{fresh_label}[/{fresh_style}]" - ) + summary = f"{len(listed)} model(s) — last build [{fresh_style}]{fresh_label}[/{fresh_style}]" console.print(table) console.print(summary) @@ -350,9 +341,7 @@ def status_cmd() -> None: # Live API read with write-through to the cache; falls back to the # last snapshot (with a warning) on auth/network failure. _refresh_fivetran_cache(project) - fivetran_sources = classify_fivetran_sources( - latest_connector_snapshot(metadata_db_path(config.root)) - ) + fivetran_sources = classify_fivetran_sources(latest_connector_snapshot(metadata_db_path(config.root))) all_sources = [*dlt_sources, *fivetran_sources] _meta_db = metadata_db_path(config.root) @@ -391,19 +380,13 @@ def status_cmd() -> None: "Staging", filter_by_layer(models, Layer.STAGING), metadata_db, - empty_hint=( - "No staging models. Scaffold one with " - "[bold]tycoon data analyze [/bold]." - ), + empty_hint=("No staging models. Scaffold one with [bold]tycoon data analyze [/bold]."), ) _render_layer_panel( "Intermediate", filter_by_layer(models, Layer.INTERMEDIATE), metadata_db, - empty_hint=( - "No intermediate models. Optional layer — typically used to " - "combine staging models before marts." - ), + empty_hint=("No intermediate models. Optional layer — typically used to combine staging models before marts."), ) _render_layer_panel( "Marts", diff --git a/src/tycoon/commands/sync_cmd.py b/src/tycoon/commands/sync_cmd.py index 0dcd4e4..efad64e 100644 --- a/src/tycoon/commands/sync_cmd.py +++ b/src/tycoon/commands/sync_cmd.py @@ -16,33 +16,32 @@ from __future__ import annotations from pathlib import Path -from typing import Optional import typer -from tycoon.config import config +from tycoon.config import load_config from tycoon.project import SyncSourceSpec from tycoon.sync import sync_to_local from tycoon.utils.console import console, error, info, next_steps, success, warn def sync_cmd( - from_: Optional[list[str]] = typer.Option( + from_: list[str] | None = typer.Option( None, "--from", help="Source URL — repeatable. md:, /path/to/other.duckdb, etc.", ), - to: Optional[Path] = typer.Option( + to: Path | None = typer.Option( None, "--to", help="Destination DuckDB file. Defaults to tycoon.yml's sync.to.", ), - schema: Optional[str] = typer.Option( + schema: str | None = typer.Option( None, "--schema", help="Filter to one schema (applied to every --from source).", ), - tables: Optional[str] = typer.Option( + tables: str | None = typer.Option( None, "--tables", help="Glob filter for table names within selected schemas (e.g. 'mart.*,dim_*').", @@ -60,7 +59,8 @@ def sync_cmd( the local snapshot is intentionally allowed to go stale until you re-run it. """ - sync_cfg = config.project.sync if config.project else None + cfg = load_config() + sync_cfg = cfg.project.sync if cfg.project else None # Resolve source specs. if from_: @@ -98,12 +98,9 @@ def sync_cmd( # Resolve destination. if to is None: if sync_cfg and sync_cfg.to: - to = config.root / sync_cfg.to + to = cfg.root / sync_cfg.to else: - error( - "No destination. Pass [bold]--to [/bold] or add " - "[bold]sync.to[/bold] to tycoon.yml." - ) + error("No destination. Pass [bold]--to [/bold] or add [bold]sync.to[/bold] to tycoon.yml.") raise typer.Exit(1) # Mode validation up front so we don't half-attach before failing. @@ -112,10 +109,7 @@ def sync_cmd( error(f"Unknown --mode {mode!r}. Expected one of: {', '.join(sorted(valid_modes))}.") raise typer.Exit(1) - info( - f"Syncing {len(sources)} source(s) → [bold]{to}[/bold] " - f"([dim]mode: {mode}[/dim])" - ) + info(f"Syncing {len(sources)} source(s) → [bold]{to}[/bold] ([dim]mode: {mode}[/dim])") try: result = sync_to_local(sources, to, mode=mode) @@ -129,10 +123,7 @@ def sync_cmd( # Summary table — keep it terse but show enough to spot misalignment. for t in result.tables: - console.print( - f" [cyan]{t.schema}.{t.table}[/cyan] " - f"[dim]{t.rows:,} rows from {t.source}[/dim]" - ) + console.print(f" [cyan]{t.schema}.{t.table}[/cyan] [dim]{t.rows:,} rows from {t.source}[/dim]") if result.skipped: warn( @@ -140,15 +131,10 @@ def sync_cmd( f"(usually views referencing unattached catalogs):" ) for s in result.skipped: - console.print( - f" [yellow]{s.schema}.{s.table}[/yellow] [dim]{s.reason}[/dim]" - ) + console.print(f" [yellow]{s.schema}.{s.table}[/yellow] [dim]{s.reason}[/dim]") - success( - f"Synced {result.total_rows:,} rows across {len(result.tables)} " - f"table(s) to [bold]{to}[/bold]" - ) + success(f"Synced {result.total_rows:,} rows across {len(result.tables)} table(s) to [bold]{to}[/bold]") next_steps( - ("tycoon data query \"SELECT ...\"", f"query the local snapshot ({to.name})"), + ('tycoon data query "SELECT ..."', f"query the local snapshot ({to.name})"), ("tycoon data sync --mode skip-existing", "re-run later, only fill in new tables"), ) diff --git a/src/tycoon/commands/transform.py b/src/tycoon/commands/transform.py index fe680ae..ac07802 100644 --- a/src/tycoon/commands/transform.py +++ b/src/tycoon/commands/transform.py @@ -6,7 +6,6 @@ import subprocess import time from pathlib import Path -from typing import Optional import typer @@ -95,9 +94,7 @@ def _capture_dbt_and_refresh_safe(dbt_cmd: str, *, started_at: float) -> None: target = args.get("target", "dev") or "dev" results = run_results.get("results") or [] models_run = len(results) - models_errored = sum( - 1 for r in results if r.get("status") not in ("success", "pass", "warn", "skipped") - ) + models_errored = sum(1 for r in results if r.get("status") not in ("success", "pass", "warn", "skipped")) models_passed = models_run - models_errored event = DbtRunCompleted( @@ -141,9 +138,9 @@ def _auto_osi_scaffold_safe() -> None: def _resolve_for_run( - profile: Optional[str], - profiles_dir: Optional[Path], - target: Optional[str], + profile: str | None, + profiles_dir: Path | None, + target: str | None, ) -> tuple[Path | None, str | None, str]: """Resolve profile/profiles_dir/target via the central resolver. @@ -176,20 +173,16 @@ def _resolve_for_run( # Resolver couldn't find a profile; fall back to whatever the user # passed on the CLI / had in tycoon.yml so dbt's own discovery still # has a chance. - fallback_target = ( - target - or (project.dbt_target if project else None) - or "dev" - ) + fallback_target = target or (project.dbt_target if project else None) or "dev" return profiles_dir, profile, fallback_target def _run_dbt( dbt_cmd: str, - profile: Optional[str], - profiles_dir: Optional[Path], - target: Optional[str], - select: Optional[str], + profile: str | None, + profiles_dir: Path | None, + target: str | None, + select: str | None, full_refresh: bool, extra: list[str] | None = None, ) -> int: @@ -225,10 +218,10 @@ def _run_dbt( @app.command() def run( - target: Optional[str] = _TARGET_OPTION, - profile: Optional[str] = _PROFILE_OPTION, - profiles_dir: Optional[Path] = _PROFILES_DIR_OPTION, - select: Optional[str] = _SELECT_OPTION, + target: str | None = _TARGET_OPTION, + profile: str | None = _PROFILE_OPTION, + profiles_dir: Path | None = _PROFILES_DIR_OPTION, + select: str | None = _SELECT_OPTION, full_refresh: bool = _FULL_REFRESH_FLAG, ) -> None: """Execute dbt run — build all models (or a selection) in the warehouse.""" @@ -260,10 +253,10 @@ def run( @app.command() def test( - target: Optional[str] = _TARGET_OPTION, - profile: Optional[str] = _PROFILE_OPTION, - profiles_dir: Optional[Path] = _PROFILES_DIR_OPTION, - select: Optional[str] = _SELECT_OPTION, + target: str | None = _TARGET_OPTION, + profile: str | None = _PROFILE_OPTION, + profiles_dir: Path | None = _PROFILES_DIR_OPTION, + select: str | None = _SELECT_OPTION, ) -> None: """Execute dbt test — run data quality tests against built models.""" header("dbt test") @@ -290,10 +283,10 @@ def test( @app.command() def build( - target: Optional[str] = _TARGET_OPTION, - profile: Optional[str] = _PROFILE_OPTION, - profiles_dir: Optional[Path] = _PROFILES_DIR_OPTION, - select: Optional[str] = _SELECT_OPTION, + target: str | None = _TARGET_OPTION, + profile: str | None = _PROFILE_OPTION, + profiles_dir: Path | None = _PROFILES_DIR_OPTION, + select: str | None = _SELECT_OPTION, full_refresh: bool = _FULL_REFRESH_FLAG, ) -> None: """Execute dbt build — run + test all models (or a selection).""" @@ -321,9 +314,9 @@ def build( @app.command() def docs( - target: Optional[str] = _TARGET_OPTION, - profile: Optional[str] = _PROFILE_OPTION, - profiles_dir: Optional[Path] = _PROFILES_DIR_OPTION, + target: str | None = _TARGET_OPTION, + profile: str | None = _PROFILE_OPTION, + profiles_dir: Path | None = _PROFILES_DIR_OPTION, port: int = typer.Option(8080, "--port", "-p", help="Port for dbt docs serve."), ) -> None: """Generate and serve dbt documentation in the browser.""" @@ -352,9 +345,7 @@ def docs( dbt = _dbt_executable() project_dir = config.dbt_project_dir cmd = [dbt, "docs", "serve", "--port", str(port)] - resolved_dir, resolved_profile, _ = _resolve_for_run( - profile=profile, profiles_dir=profiles_dir, target=target - ) + resolved_dir, resolved_profile, _ = _resolve_for_run(profile=profile, profiles_dir=profiles_dir, target=target) if resolved_dir is not None: cmd += ["--profiles-dir", str(resolved_dir)] if resolved_profile: diff --git a/src/tycoon/config.py b/src/tycoon/config.py index b17a43e..83b5e31 100644 --- a/src/tycoon/config.py +++ b/src/tycoon/config.py @@ -8,8 +8,8 @@ from pathlib import Path -from tycoon.project import PROJECT_FILENAME, TycoonProject, load_project - +from tycoon.project import PROJECT_FILENAME, SCHEMA_VERSION, TycoonProject, load_project +from tycoon.utils.console import error as _error_console, warn as _warn_console # v0.1 defaults (used when no tycoon.yml exists) _DEFAULT_RAW_DB = "data/raw.duckdb" @@ -121,5 +121,29 @@ def ensure_data_dir(self) -> None: self.data_dir.mkdir(parents=True, exist_ok=True) -# Singleton +def load_config() -> TycoonConfig: + """Return a TycoonConfig rooted at the nearest project directory. + + Prefer this over importing _find_project_root across module boundaries. + Emits a console warning when tycoon.yml is at an older schema version. + """ + cfg = TycoonConfig(project_root=_find_project_root()) + if cfg.project is not None: + sv = cfg.project.schema_version + if sv is not None and sv > SCHEMA_VERSION: + _error_console( + f"tycoon.yml schema_version {sv} is newer than this tycoon supports " + f"({SCHEMA_VERSION}). Upgrade tycoon-cli to use this project." + ) + raise SystemExit(1) + if sv is None or sv < SCHEMA_VERSION: + current = sv if sv is not None else "none" + _warn_console( + f"tycoon.yml is at schema version {current}, current is {SCHEMA_VERSION}. " + "Run 'tycoon init --upgrade' to migrate." + ) + return cfg + + +# Singleton (used by modules not yet migrated to load_config) config = TycoonConfig() diff --git a/src/tycoon/core/events.py b/src/tycoon/core/events.py index a4d5c02..b63c563 100644 --- a/src/tycoon/core/events.py +++ b/src/tycoon/core/events.py @@ -1,6 +1,6 @@ from __future__ import annotations -from datetime import datetime, timezone +from datetime import UTC, datetime from typing import Annotated, Literal from uuid import uuid4 @@ -12,7 +12,7 @@ class BaseEvent(BaseModel): event_type: str source_id: str runtime_id: str - timestamp: AwareDatetime = Field(default_factory=lambda: datetime.now(tz=timezone.utc)) + timestamp: AwareDatetime = Field(default_factory=lambda: datetime.now(tz=UTC)) class RunStarted(BaseEvent): diff --git a/src/tycoon/core/history.py b/src/tycoon/core/history.py index 2fac04b..135b7e5 100644 --- a/src/tycoon/core/history.py +++ b/src/tycoon/core/history.py @@ -36,63 +36,20 @@ def list_runs(self, limit: int | None = 20) -> list[RunSummary]: summaries: list[RunSummary] = [] for e in events: if isinstance(e, RunCompleted): - summaries.append(RunSummary( - run_id=e.load_id or e.event_id, - source_id=e.source_id, - runtime_id=e.runtime_id, - status="success", - started_at=e.timestamp, - duration_seconds=e.duration_seconds, - rows_total=sum((e.rows_loaded or {}).values()), - )) - elif isinstance(e, RunFailed): - summaries.append(RunSummary( - run_id=e.event_id, - source_id=e.source_id, - runtime_id=e.runtime_id, - status="failed", - started_at=e.timestamp, - duration_seconds=0.0, - rows_total=0, - )) - elif isinstance(e, DbtRunCompleted): - summaries.append(RunSummary( - run_id=e.event_id, - source_id=e.source_id, - runtime_id=e.runtime_id, - status="success" if e.models_errored == 0 else "failed", - started_at=e.timestamp, - duration_seconds=e.duration_seconds, - rows_total=e.models_run, - command=e.command, - )) - summaries.sort(key=lambda s: s.started_at, reverse=True) - if limit is not None: - return summaries[:limit] - return summaries - - def get_run(self, run_id_prefix: str) -> RunDetail | None: - if not run_id_prefix: - return None - events = self._backend.query_events() - matches: list[tuple[RunSummary, object]] = [] - - for e in events: - if isinstance(e, RunCompleted): - eid = e.load_id or e.event_id - if eid.startswith(run_id_prefix): - matches.append((RunSummary( - run_id=eid, + summaries.append( + RunSummary( + run_id=e.load_id or e.event_id, source_id=e.source_id, runtime_id=e.runtime_id, status="success", started_at=e.timestamp, duration_seconds=e.duration_seconds, rows_total=sum((e.rows_loaded or {}).values()), - ), e)) + ) + ) elif isinstance(e, RunFailed): - if e.event_id.startswith(run_id_prefix): - matches.append((RunSummary( + summaries.append( + RunSummary( run_id=e.event_id, source_id=e.source_id, runtime_id=e.runtime_id, @@ -100,10 +57,11 @@ def get_run(self, run_id_prefix: str) -> RunDetail | None: started_at=e.timestamp, duration_seconds=0.0, rows_total=0, - ), e)) + ) + ) elif isinstance(e, DbtRunCompleted): - if e.event_id.startswith(run_id_prefix): - matches.append((RunSummary( + summaries.append( + RunSummary( run_id=e.event_id, source_id=e.source_id, runtime_id=e.runtime_id, @@ -112,7 +70,70 @@ def get_run(self, run_id_prefix: str) -> RunDetail | None: duration_seconds=e.duration_seconds, rows_total=e.models_run, command=e.command, - ), e)) + ) + ) + summaries.sort(key=lambda s: s.started_at, reverse=True) + if limit is not None: + return summaries[:limit] + return summaries + + def get_run(self, run_id_prefix: str) -> RunDetail | None: + if not run_id_prefix: + return None + events = self._backend.query_events() + matches: list[tuple[RunSummary, object]] = [] + + for e in events: + if isinstance(e, RunCompleted): + eid = e.load_id or e.event_id + if eid.startswith(run_id_prefix): + matches.append( + ( + RunSummary( + run_id=eid, + source_id=e.source_id, + runtime_id=e.runtime_id, + status="success", + started_at=e.timestamp, + duration_seconds=e.duration_seconds, + rows_total=sum((e.rows_loaded or {}).values()), + ), + e, + ) + ) + elif isinstance(e, RunFailed): + if e.event_id.startswith(run_id_prefix): + matches.append( + ( + RunSummary( + run_id=e.event_id, + source_id=e.source_id, + runtime_id=e.runtime_id, + status="failed", + started_at=e.timestamp, + duration_seconds=0.0, + rows_total=0, + ), + e, + ) + ) + elif isinstance(e, DbtRunCompleted): + if e.event_id.startswith(run_id_prefix): + matches.append( + ( + RunSummary( + run_id=e.event_id, + source_id=e.source_id, + runtime_id=e.runtime_id, + status="success" if e.models_errored == 0 else "failed", + started_at=e.timestamp, + duration_seconds=e.duration_seconds, + rows_total=e.models_run, + command=e.command, + ), + e, + ) + ) if len(matches) == 0: return None diff --git a/src/tycoon/dbt_profiles.py b/src/tycoon/dbt_profiles.py index d5a9053..a2f23d5 100644 --- a/src/tycoon/dbt_profiles.py +++ b/src/tycoon/dbt_profiles.py @@ -30,7 +30,6 @@ import yaml - # -------------------------------------------------------------------------- # Data containers # -------------------------------------------------------------------------- @@ -280,11 +279,7 @@ def resolve_profile( if data is None: return None - profile_name = ( - overrides.profile - or project_dbt_profile - or _read_dbt_project_profile_name(dbt_project_dir) - ) + profile_name = overrides.profile or project_dbt_profile or _read_dbt_project_profile_name(dbt_project_dir) if not profile_name: return None @@ -345,11 +340,7 @@ def discover_profiles(profiles_yml: Path) -> list[DiscoveredProfile]: if not isinstance(outputs, dict): continue targets = list(outputs.keys()) - adapters = { - t: str((outputs[t] or {}).get("type") or "") - for t in targets - if isinstance(outputs[t], dict) - } + adapters = {t: str((outputs[t] or {}).get("type") or "") for t in targets if isinstance(outputs[t], dict)} default_target = body.get("target") or (targets[0] if targets else "") out.append( DiscoveredProfile( @@ -404,10 +395,7 @@ def extract_dbt_warehouse_target( def redact_secrets(value: Any) -> Any: """Recursively redact common secret-bearing fields. Used by ``profiles show``.""" if isinstance(value, dict): - return { - k: ("***redacted***" if k.lower() in _SECRET_KEYS else redact_secrets(v)) - for k, v in value.items() - } + return {k: ("***redacted***" if k.lower() in _SECRET_KEYS else redact_secrets(v)) for k, v in value.items()} if isinstance(value, list): return [redact_secrets(v) for v in value] return value diff --git a/src/tycoon/ingestion/catalog.py b/src/tycoon/ingestion/catalog.py index deae56a..9d68e1b 100644 --- a/src/tycoon/ingestion/catalog.py +++ b/src/tycoon/ingestion/catalog.py @@ -11,10 +11,10 @@ @dataclass class CredentialField: - key: str # Key stored in tycoon.yml config dict - label: str # Human-readable prompt label - hint: str # Where to get the credential - env_var: str # Suggested environment variable name + key: str # Key stored in tycoon.yml config dict + label: str # Human-readable prompt label + hint: str # Where to get the credential + env_var: str # Suggested environment variable name secret: bool = True @@ -140,7 +140,7 @@ class CatalogEntry: ConfigField( key="database_ids", label="Database IDs (comma-separated, leave blank to sync all)", - hint='Copy the ID from your Notion database URL: notion.so//', + hint="Copy the ID from your Notion database URL: notion.so//", required=False, default="", ), @@ -172,7 +172,7 @@ class CatalogEntry: ConfigField( key="spreadsheet_url_or_id", label="Spreadsheet URL or ID", - hint='Full share URL or just the ID from docs.google.com/spreadsheets/d//edit', + hint="Full share URL or just the ID from docs.google.com/spreadsheets/d//edit", required=True, ), ConfigField( diff --git a/src/tycoon/ingestion/ducklake_config.py b/src/tycoon/ingestion/ducklake_config.py index 9495e27..c0c679b 100644 --- a/src/tycoon/ingestion/ducklake_config.py +++ b/src/tycoon/ingestion/ducklake_config.py @@ -57,7 +57,7 @@ def _bucket_root(catalog_path: str) -> str: # -> "s3://my-bucket" """ if catalog_path.startswith("s3://"): - parts = catalog_path[len("s3://"):].split("/", 1) + parts = catalog_path[len("s3://") :].split("/", 1) return f"s3://{parts[0]}" # Local path fallback — return parent directory return str(Path(catalog_path).parent) diff --git a/src/tycoon/ingestion/fivetran_client.py b/src/tycoon/ingestion/fivetran_client.py index 58c99a3..96e7cc9 100644 --- a/src/tycoon/ingestion/fivetran_client.py +++ b/src/tycoon/ingestion/fivetran_client.py @@ -92,7 +92,7 @@ def __init__( def __enter__(self) -> FivetranClient: return self - def __exit__(self, *_args: Any) -> None: + def __exit__(self, *_args: object) -> None: self.close() def close(self) -> None: @@ -110,10 +110,7 @@ def _get(self, path: str, **params: Any) -> dict[str, Any]: raise FivetranAPIError(f"Fivetran request failed: {exc}") from exc if response.status_code >= 400: - raise FivetranAPIError( - f"Fivetran API {response.status_code} on {path}: " - f"{response.text[:200]}" - ) + raise FivetranAPIError(f"Fivetran API {response.status_code} on {path}: {response.text[:200]}") try: payload = response.json() @@ -121,9 +118,7 @@ def _get(self, path: str, **params: Any) -> dict[str, Any]: raise FivetranAPIError("Fivetran response was not JSON.") from exc if not isinstance(payload, dict) or "data" not in payload: - raise FivetranAPIError( - f"Unexpected Fivetran payload shape: {payload!r}" - ) + raise FivetranAPIError(f"Unexpected Fivetran payload shape: {payload!r}") return payload # ----- public API --------------------------------------------------- @@ -172,9 +167,7 @@ def _get_connector(self, connector_id: str) -> Connector: """Fetch one connector by id. Reserved for future use — list_connectors no longer hits this endpoint after the N+1 cleanup.""" payload = self._get(f"/connectors/{connector_id}") - return self._parse_connector_body( - payload.get("data") or {}, fallback_id=connector_id - ) + return self._parse_connector_body(payload.get("data") or {}, fallback_id=connector_id) def _parse_connector_body( self, @@ -211,7 +204,7 @@ def _parse_iso(raw: Any) -> datetime.datetime | None: except ValueError: return None if dt.tzinfo is None: - dt = dt.replace(tzinfo=datetime.timezone.utc) + dt = dt.replace(tzinfo=datetime.UTC) return dt diff --git a/src/tycoon/ingestion/fivetran_sync.py b/src/tycoon/ingestion/fivetran_sync.py index 5f9213a..6902726 100644 --- a/src/tycoon/ingestion/fivetran_sync.py +++ b/src/tycoon/ingestion/fivetran_sync.py @@ -24,9 +24,9 @@ class SyncResult: captured_at: datetime.datetime connectors_seen: int paused: int - healthy: int # had a recent succeeded_at - failing: int # last touch was a failure - new: int # connectors not seen in any prior snapshot + healthy: int # had a recent succeeded_at + failing: int # last touch was a failure + new: int # connectors not seen in any prior snapshot def sync_fivetran_metadata( @@ -40,27 +40,20 @@ def sync_fivetran_metadata( keep the call safely retryable in tight loops. """ ensure_schema(metadata_db) - captured_at = datetime.datetime.now(tz=datetime.timezone.utc) + captured_at = datetime.datetime.now(tz=datetime.UTC) connectors = client.list_connectors() healthy = paused = failing = new = 0 con = duckdb.connect(str(metadata_db)) try: - prior_ids = { - row[0] - for row in con.execute( - "SELECT DISTINCT connector_id FROM fivetran_connectors" - ).fetchall() - } + prior_ids = {row[0] for row in con.execute("SELECT DISTINCT connector_id FROM fivetran_connectors").fetchall()} for c in connectors: if c.connector_id not in prior_ids: new += 1 if c.paused: paused += 1 - if c.succeeded_at and ( - not c.failed_at or c.succeeded_at > c.failed_at - ): + if c.succeeded_at and (not c.failed_at or c.succeeded_at > c.failed_at): healthy += 1 elif c.failed_at: failing += 1 @@ -109,8 +102,7 @@ def latest_connector_snapshot(metadata_db: Path) -> list[dict]: names = { row[0] for row in con.execute( - "SELECT table_name FROM information_schema.tables " - "WHERE table_schema = 'main'" + "SELECT table_name FROM information_schema.tables WHERE table_schema = 'main'" ).fetchall() } if "fivetran_connectors" not in names: @@ -147,16 +139,12 @@ def freshness_label( if last is None: return "never", "red" - now = datetime.datetime.now(tz=datetime.timezone.utc) + now = datetime.datetime.now(tz=datetime.UTC) if last.tzinfo is None: - last = last.replace(tzinfo=datetime.timezone.utc) + last = last.replace(tzinfo=datetime.UTC) age = now - last hours = age.total_seconds() / 3600 - style = ( - "green" if hours < 24 else - "yellow" if hours < 24 * 7 else - "red" - ) + style = "green" if hours < 24 else "yellow" if hours < 24 * 7 else "red" if hours < 1: label = f"{int(age.total_seconds() / 60)}m ago" elif hours < 24: diff --git a/src/tycoon/ingestion/mta_bus_speeds_pipeline.py b/src/tycoon/ingestion/mta_bus_speeds_pipeline.py index a578d9f..ca1910e 100644 --- a/src/tycoon/ingestion/mta_bus_speeds_pipeline.py +++ b/src/tycoon/ingestion/mta_bus_speeds_pipeline.py @@ -57,8 +57,6 @@ def _socrata_pages( break - - @dlt.source(name="raw_mta_bus_speeds") def mta_bus_speeds_source( years: list[str] | None = None, @@ -72,9 +70,7 @@ def mta_bus_speeds_source( datasets_to_ingest = MTA_BUS_SPEEDS_DATASETS if years: datasets_to_ingest = { - year_range: dataset_id - for year_range, dataset_id in MTA_BUS_SPEEDS_DATASETS.items() - if year_range in years + year_range: dataset_id for year_range, dataset_id in MTA_BUS_SPEEDS_DATASETS.items() if year_range in years } for year_range, dataset_id in datasets_to_ingest.items(): diff --git a/src/tycoon/ingestion/mta_pipeline.py b/src/tycoon/ingestion/mta_pipeline.py index 94c9258..4cf6e61 100644 --- a/src/tycoon/ingestion/mta_pipeline.py +++ b/src/tycoon/ingestion/mta_pipeline.py @@ -2,9 +2,9 @@ from __future__ import annotations +import csv import io import zipfile -import csv from collections.abc import Iterator from pathlib import Path from typing import Any diff --git a/src/tycoon/ingestion/runner.py b/src/tycoon/ingestion/runner.py index b2ec140..2c5e101 100644 --- a/src/tycoon/ingestion/runner.py +++ b/src/tycoon/ingestion/runner.py @@ -17,7 +17,6 @@ import dlt from tycoon.core.events import RunCompleted, RunFailed, RunStarted - from tycoon.ingestion.catalog import CATALOG from tycoon.ingestion.source_manager import SOURCES_DIR, get_run_module_path, is_source_installed from tycoon.project import SourceConfig @@ -55,24 +54,27 @@ def _classify_error(exc: Exception, source_type: str) -> IngestionError: ) if any(x in msg for x in ("403", "forbidden", "permission denied", "insufficient scope")): return IngestionError( - f"Access denied for '{source_type}'. " - "Your token may lack the required scopes or permissions." + f"Access denied for '{source_type}'. Your token may lack the required scopes or permissions." + ) + if any( + x in msg + for x in ( + "connectionerror", + "connection refused", + "name or service not known", + "failed to establish", + "nodename nor servname provided", ) - if any(x in msg for x in ("connectionerror", "connection refused", "name or service not known", - "failed to establish", "nodename nor servname provided")): + ): return IngestionError( - f"Could not reach the '{source_type}' API. " - "Check your internet connection and verify the base URL." + f"Could not reach the '{source_type}' API. Check your internet connection and verify the base URL." ) if "timeout" in msg or "timed out" in msg: return IngestionError( - f"Request to '{source_type}' timed out. " - "The API may be slow or unreachable — try again later." + f"Request to '{source_type}' timed out. The API may be slow or unreachable — try again later." ) if "rate limit" in msg or "429" in msg: - return IngestionError( - f"Rate limited by '{source_type}' API. Wait a moment and try again." - ) + return IngestionError(f"Rate limited by '{source_type}' API. Wait a moment and try again.") return IngestionError(str(exc)) @@ -191,10 +193,7 @@ def _build_run_completed(name: str, pipeline: Any, load_info: Any, elapsed: floa rows_by_table: dict[str, int] = {} try: ni = pipeline.last_trace.last_normalize_info - rows_by_table = { - t: c for t, c in (ni.row_counts or {}).items() - if not t.startswith("_dlt") - } + rows_by_table = {t: c for t, c in (ni.row_counts or {}).items() if not t.startswith("_dlt")} except Exception: pass loads_ids = getattr(load_info, "loads_ids", []) or [] @@ -291,6 +290,7 @@ def run_source( try: from tycoon.config import config as _cfg from tycoon.observability import metadata_db_path + _metadata_db = metadata_db_path(_cfg.root) except Exception: pass @@ -300,9 +300,7 @@ def run_source( try: # 1. Legacy pipeline delegation (keyed by source name) if name in _LEGACY_PIPELINES: - pipeline, load_info = _run_legacy( - name, raw_db_path=raw_db_path, max_records=max_records, **kwargs - ) + pipeline, load_info = _run_legacy(name, raw_db_path=raw_db_path, max_records=max_records, **kwargs) load_info.raise_on_failed_jobs() _capture_and_refresh_safe(raw_db_path, pipeline=pipeline) _emit_run_completed_safe(_metadata_db, name, pipeline, load_info, time.monotonic() - _started) @@ -313,9 +311,7 @@ def run_source( source_type = source_config.type if source_type not in _NATIVE_BUILDERS and source_type in CATALOG: # 3. Catalog source dispatch — load from ~/.tycoon/sources/ - pipeline, load_info = _run_catalog( - source_type, name, source_config, raw_db_path, max_records - ) + pipeline, load_info = _run_catalog(source_type, name, source_config, raw_db_path, max_records) _capture_and_refresh_safe(raw_db_path, pipeline=pipeline) _emit_run_completed_safe(_metadata_db, name, pipeline, load_info, time.monotonic() - _started) return pipeline, load_info @@ -340,8 +336,7 @@ def run_source( dlt_source = source_fn(**source_config.config) except ImportError as exc: raise RuntimeError( - f"Unknown source type '{source_type}'. " - f"Install with: tycoon sources add {source_type}" + f"Unknown source type '{source_type}'. Install with: tycoon sources add {source_type}" ) from exc else: dlt_source = builder(source_config) @@ -382,15 +377,13 @@ def _run_catalog( import importlib if not is_source_installed(source_type): - raise IngestionError( - f"Source '{source_type}' is not installed. " - f"Run: tycoon data sources add {source_type}" - ) + raise IngestionError(f"Source '{source_type}' is not installed. Run: tycoon data sources add {source_type}") # Warn about unexpanded env vars before hitting the API bad_pairs = _check_unexpanded_env_vars(source_config) if bad_pairs: from tycoon.utils.console import warn + for key, var in bad_pairs: warn( f"Config key '{key}' contains an unexpanded env var: {var}\n" diff --git a/src/tycoon/ingestion/source_manager.py b/src/tycoon/ingestion/source_manager.py index f7d8a06..721f0f2 100644 --- a/src/tycoon/ingestion/source_manager.py +++ b/src/tycoon/ingestion/source_manager.py @@ -246,11 +246,7 @@ def is_source_installed(source_type: str) -> bool: return (SOURCES_DIR / source_type / "_run.py").exists() dlt_name = _DLT_INIT_NAME.get(source_type, source_type) source_pkg = SOURCES_DIR / dlt_name - return ( - source_pkg.is_dir() - and (source_pkg / "__init__.py").exists() - and (source_pkg / "_run.py").exists() - ) + return source_pkg.is_dir() and (source_pkg / "__init__.py").exists() and (source_pkg / "_run.py").exists() def install_source(source_type: str) -> bool: diff --git a/src/tycoon/layers.py b/src/tycoon/layers.py index e4cbaee..4f95548 100644 --- a/src/tycoon/layers.py +++ b/src/tycoon/layers.py @@ -26,10 +26,11 @@ from __future__ import annotations import json +from collections.abc import Iterable from dataclasses import dataclass from enum import Enum from pathlib import Path -from typing import Any, Iterable +from typing import Any class Layer(str, Enum): @@ -269,8 +270,6 @@ def classify_fivetran_sources( # -- Convenience accessors ----------------------------------------------------- -def filter_by_layer( - classifications: Iterable[LayerClassification], layer: Layer -) -> list[LayerClassification]: +def filter_by_layer(classifications: Iterable[LayerClassification], layer: Layer) -> list[LayerClassification]: """Return every classification matching ``layer``. Stable order.""" return [c for c in classifications if c.layer == layer] diff --git a/src/tycoon/metadata_backends/duckdb_file.py b/src/tycoon/metadata_backends/duckdb_file.py index 59c1ad5..e7005c9 100644 --- a/src/tycoon/metadata_backends/duckdb_file.py +++ b/src/tycoon/metadata_backends/duckdb_file.py @@ -1,7 +1,7 @@ from __future__ import annotations import json -from datetime import datetime, timezone +from datetime import UTC, datetime from pathlib import Path import duckdb @@ -91,7 +91,7 @@ def query_events(self, filter: EventFilter | None = None) -> list[BaseEvent]: if filter.since is not None: since = filter.since if since.tzinfo is None: - since = since.replace(tzinfo=timezone.utc) + since = since.replace(tzinfo=UTC) where_clauses.append("timestamp >= ?") params.append(since) @@ -110,7 +110,7 @@ def query_events(self, filter: EventFilter | None = None) -> list[BaseEvent]: return events def upsert_snapshot(self, kind: str, key: str, blob: dict) -> None: - now = datetime.now(tz=timezone.utc) + now = datetime.now(tz=UTC) self.connection.execute( """ INSERT INTO snapshots (kind, key, blob, updated_at) diff --git a/src/tycoon/notify.py b/src/tycoon/notify.py index 0836acd..5a529cf 100644 --- a/src/tycoon/notify.py +++ b/src/tycoon/notify.py @@ -31,8 +31,8 @@ # Slack attachment colours per severity (also surfaced in the generic payload). _COLORS: dict[str, str] = { "success": "#36a64f", # green - "error": "#d00000", # red - "info": "#2196f3", # blue + "error": "#d00000", # red + "info": "#2196f3", # blue } @@ -66,9 +66,7 @@ def build_payload( "color": _COLORS.get(severity, _COLORS["info"]), "title": f"tycoon: {severity}", "text": message, - "fields": [ - {"title": k, "value": v, "short": True} for k, v in fields.items() - ], + "fields": [{"title": k, "value": v, "short": True} for k, v in fields.items()], } if label: attachment["footer"] = label @@ -106,9 +104,7 @@ def send( if not resolved: return False - payload = build_payload( - severity, message, fields, label=label, slack=is_slack_url(resolved) - ) + payload = build_payload(severity, message, fields, label=label, slack=is_slack_url(resolved)) try: response = httpx.post(resolved, json=payload, timeout=timeout) except (httpx.HTTPError, httpx.InvalidURL): diff --git a/src/tycoon/observability.py b/src/tycoon/observability.py index 2fb9d0b..3e02732 100644 --- a/src/tycoon/observability.py +++ b/src/tycoon/observability.py @@ -39,12 +39,11 @@ from __future__ import annotations import json -from datetime import datetime, timezone +from datetime import UTC, datetime from pathlib import Path import duckdb - # --------------------------------------------------------------------------- # Paths # --------------------------------------------------------------------------- @@ -235,13 +234,7 @@ def _dlt_user_tables(con: duckdb.DuckDBPyConnection, schema: str) -> list[str]: """, [schema], ).fetchall() - return [ - r[0] - for r in rows - if r[0] not in _DLT_INTERNAL_TABLES - and "__" not in r[0] - and not r[0].startswith("_") - ] + return [r[0] for r in rows if r[0] not in _DLT_INTERNAL_TABLES and "__" not in r[0] and not r[0].startswith("_")] def capture_dlt(metadata_db: Path, raw_db: Path) -> int: @@ -279,7 +272,7 @@ def q(name: str) -> str: try: meta_con.execute(f"ATTACH '{raw_db}' AS {q(raw_alias)} (READ_ONLY)") try: - captured_at = datetime.now(tz=timezone.utc) + captured_at = datetime.now(tz=UTC) schema_rows = meta_con.execute( """ @@ -304,11 +297,7 @@ def q(name: str) -> str: [raw_alias, schema], ).fetchall() cols = {r[0] for r in col_rows} - svh_expr = ( - '"schema_version_hash"' - if "schema_version_hash" in cols - else "NULL" - ) + svh_expr = '"schema_version_hash"' if "schema_version_hash" in cols else "NULL" loads = meta_con.execute( f""" @@ -350,9 +339,7 @@ def q(name: str) -> str: user_tables = [ r[0] for r in table_rows - if r[0] not in _DLT_INTERNAL_TABLES - and "__" not in r[0] - and not r[0].startswith("_") + if r[0] not in _DLT_INTERNAL_TABLES and "__" not in r[0] and not r[0].startswith("_") ] for table in user_tables: @@ -461,9 +448,7 @@ def capture_dbt( cmd = command or args.get("which") or args.get("rpc_method") or "unknown" success = data.get("success") elapsed = data.get("elapsed_time") - started_at = _earliest_started_at(results) or _parse_run_results_timestamp( - metadata.get("generated_at") - ) + started_at = _earliest_started_at(results) or _parse_run_results_timestamp(metadata.get("generated_at")) models_ok = models_error = tests_passed = tests_failed = 0 for res in results: @@ -481,13 +466,11 @@ def capture_dbt( tests_failed += 1 ensure_schema(metadata_db) - captured_at = datetime.now(tz=timezone.utc) + captured_at = datetime.now(tz=UTC) con = duckdb.connect(str(metadata_db)) try: - pre = con.execute( - "SELECT 1 FROM dbt_runs WHERE invocation_id = ?", [invocation_id] - ).fetchone() + pre = con.execute("SELECT 1 FROM dbt_runs WHERE invocation_id = ?", [invocation_id]).fetchone() if pre is not None: return None # already captured @@ -641,7 +624,7 @@ def capture_dlt_trace_from_dict(metadata_db: Path, trace: dict) -> str | None: finished = _coerce_ts(trace.get("finished_at")) duration = _duration_s(trace.get("started_at"), trace.get("finished_at")) success, exception = _derive_success_and_exception(steps) - captured_at = datetime.now(tz=timezone.utc) + captured_at = datetime.now(tz=UTC) con.execute( """ @@ -679,8 +662,7 @@ def capture_dlt_trace_from_dict(metadata_db: Path, trace: dict) -> str | None: _coerce_ts(step.get("started_at")), _coerce_ts(step.get("finished_at")), _duration_s(step.get("started_at"), step.get("finished_at")), - (str(step.get("step_exception"))[:2000] - if step.get("step_exception") else None), + (str(step.get("step_exception"))[:2000] if step.get("step_exception") else None), ], ) @@ -931,7 +913,7 @@ def capture_dbt_manifest( fingerprint = _extract_manifest_fingerprint(manifest) ensure_schema(metadata_db) - captured_at = datetime.now(tz=timezone.utc) + captured_at = datetime.now(tz=UTC) generated_at = _parse_run_results_timestamp(metadata.get("generated_at")) dbt_schema_version = metadata.get("dbt_schema_version") @@ -944,9 +926,7 @@ def capture_dbt_manifest( if pre is not None: return None - prev_invocation_id, prev_fingerprint = _load_previous_fingerprint( - con, exclude_invocation_id=invocation_id - ) + prev_invocation_id, prev_fingerprint = _load_previous_fingerprint(con, exclude_invocation_id=invocation_id) changes = _diff_fingerprints(prev_fingerprint, fingerprint) con.execute( @@ -1027,9 +1007,7 @@ def export_to_parquet(metadata_db: Path, parquet_dir: Path) -> dict[str, Path]: try: for table in _EXPORT_TABLES: path = parquet_dir / f"{table}.parquet" - con.execute( - f"COPY (SELECT * FROM {table}) TO '{path}' (FORMAT PARQUET)" - ) + con.execute(f"COPY (SELECT * FROM {table}) TO '{path}' (FORMAT PARQUET)") out[table] = path return out finally: diff --git a/src/tycoon/project.py b/src/tycoon/project.py index 053769a..31125e3 100644 --- a/src/tycoon/project.py +++ b/src/tycoon/project.py @@ -6,7 +6,7 @@ import re from enum import Enum from pathlib import Path -from typing import Any +from typing import Any, Literal import yaml from pydantic import BaseModel, Field, SecretStr, field_validator @@ -60,8 +60,7 @@ class FivetranIngestionMetadata(BaseModel): api_key: SecretStr = Field( description=( - "Fivetran API key (Basic Auth username). Recommended: " - "${FIVETRAN_API_KEY} rather than a literal value." + "Fivetran API key (Basic Auth username). Recommended: ${FIVETRAN_API_KEY} rather than a literal value." ) ) api_secret: SecretStr = Field( @@ -95,6 +94,7 @@ class StackConfig(BaseModel): def _interpolate_env(value: str) -> str: """Replace ${ENV_VAR} and ${ENV_VAR:-default} patterns with env values.""" + def _replace(match: re.Match) -> str: var = match.group(1) if ":-" in var: @@ -131,17 +131,14 @@ def _replace(match: re.Match) -> str: def _path_matches(path: tuple[str, ...], pattern: tuple[str, ...]) -> bool: - return len(path) == len(pattern) and all( - p in ("*", segment) for segment, p in zip(path, pattern) - ) + return len(path) == len(pattern) and all(p in ("*", segment) for segment, p in zip(path, pattern)) def _is_interpolated_field(path: tuple[str, ...]) -> bool: if any(_path_matches(path, leaf) for leaf in _INTERPOLATED_LEAVES): return True return any( - len(path) > len(prefix) and _path_matches(path[: len(prefix)], prefix) - for prefix in _INTERPOLATED_SUBTREES + len(path) > len(prefix) and _path_matches(path[: len(prefix)], prefix) for prefix in _INTERPOLATED_SUBTREES ) @@ -282,6 +279,25 @@ class TransformConfig(BaseModel): ) +class RuntimeEntry(BaseModel): + """One named ingestion runtime declared under ``runtimes:``.""" + + type: Literal["dlt-managed", "dlt-project", "fivetran", "airbyte", "estuary"] + # only meaningful for dlt-project; ignored for cloud-managed runtimes + path: str | None = None + + +class MetadataConfig(BaseModel): + """Where tycoon stores its internal state (run history, source records). + + ``backend: duckdb_file`` is the only supported backend in v0.1.x. + ``path`` is relative to the project root. + """ + + backend: str = "duckdb_file" + path: str = ".tycoon/metadata.duckdb" + + class NotifyConfig(BaseModel): """Optional ``notify:`` block — non-secret notification prefs (#46). @@ -307,6 +323,7 @@ class TycoonProject(BaseModel): name: str = Field(default="my-project", description="Project name") version: str = Field(default="0.1.0", description="Project version") + schema_version: int | None = Field(default=None, description="Tycoon schema version (managed by tycoon)") database: DatabaseConfig = Field(default_factory=DatabaseConfig) sources: dict[str, SourceConfig] = Field(default_factory=dict, description="Registered data sources") dbt_project_dir: str = Field(default="dbt_project", description="Path to dbt project") @@ -320,16 +337,12 @@ class TycoonProject(BaseModel): ) dbt_profile: str | None = Field( default=None, - description=( - "Profile name within profiles.yml. Defaults to the `profile:` " - "field in dbt_project.yml." - ), + description=("Profile name within profiles.yml. Defaults to the `profile:` field in dbt_project.yml."), ) dbt_target: str | None = Field( default=None, description=( - "Target within the profile (dev / prod / ...). Defaults to the " - "profile's `target:` field, then 'dev'." + "Target within the profile (dev / prod / ...). Defaults to the profile's `target:` field, then 'dev'." ), ) rill_dir: str = Field(default="rill", description="Path to Rill dashboards") @@ -339,6 +352,21 @@ class TycoonProject(BaseModel): description="Defaults for transform-side commands (`data analyze`, `data sources run`).", ) stack: StackConfig = Field(default_factory=StackConfig) + runtimes: dict[str, RuntimeEntry] = Field( + default_factory=dict, + description=( + "Named ingestion runtimes. Keys are arbitrary labels (e.g. 'shopify', " + "'custom_pipeline'); each entry declares which ingestion tool owns that " + "pipeline and, for dlt-project runtimes, where the project lives." + ), + ) + metadata: MetadataConfig = Field( + default_factory=MetadataConfig, + description=( + "Where tycoon stores internal state (run history, source records). " + "Defaults to a local DuckDB file at .tycoon/metadata.duckdb." + ), + ) notify: NotifyConfig = Field( default_factory=lambda: NotifyConfig(), description="Notification preferences for `--notify` runs and `tycoon notify`.", @@ -365,6 +393,7 @@ def _check_path_field(cls, v: str | None) -> str | None: PROJECT_FILENAME = "tycoon.yml" +SCHEMA_VERSION = 2 def load_project(project_root: Path) -> TycoonProject | None: @@ -399,3 +428,55 @@ def save_project(project: TycoonProject, project_root: Path) -> None: if existing_meta is not None and isinstance(data.get("stack"), dict): data["stack"]["ingestion_metadata"] = existing_meta path.write_text(yaml.dump(data, default_flow_style=False, sort_keys=False)) + + +def migrate_project(project_root: Path) -> bool: + """Upgrade tycoon.yml to SCHEMA_VERSION in place. + + Adds ``metadata:`` with defaults if the key is absent, then stamps + ``schema_version`` when it is absent or older than SCHEMA_VERSION. + The user's ``version`` field is never touched. Writes back only when + a change is needed. Returns True if the file was modified, False if + already up to date (idempotent). Comments and blank lines are + preserved via ruamel.yaml. + """ + from ruamel.yaml import YAML + + path = project_root / PROJECT_FILENAME + if not path.exists(): + return False + + ryaml = YAML() + ryaml.preserve_quotes = True + + with path.open() as f: + raw = ryaml.load(f) + + if not isinstance(raw, dict): + return False + + existing = raw.get("schema_version") + if existing is not None and (isinstance(existing, bool) or not isinstance(existing, int)): + raise ValueError(f"tycoon.yml schema_version must be an integer, got {type(existing).__name__}: {existing!r}") + if existing is not None and existing > SCHEMA_VERSION: + raise ValueError( + f"tycoon.yml schema_version {existing} is newer than this tycoon supports ({SCHEMA_VERSION}). " + "Upgrade tycoon-cli to use this project." + ) + + changed = False + + if "metadata" not in raw: + defaults = MetadataConfig() + raw["metadata"] = {"backend": defaults.backend, "path": defaults.path} + changed = True + + if existing is None or existing < SCHEMA_VERSION: + raw["schema_version"] = SCHEMA_VERSION + changed = True + + if changed: + with path.open("w") as f: + ryaml.dump(raw, f) + + return changed diff --git a/src/tycoon/scaffolding/dbt_generator.py b/src/tycoon/scaffolding/dbt_generator.py index 370eb46..2e230ab 100644 --- a/src/tycoon/scaffolding/dbt_generator.py +++ b/src/tycoon/scaffolding/dbt_generator.py @@ -225,9 +225,7 @@ def generate_staging_models( raw_table_names = [ row[0] for row in table_rows - if row[0] not in _DLT_INTERNAL_TABLES - and "__" not in row[0] - and not row[0].startswith("_") + if row[0] not in _DLT_INTERNAL_TABLES and "__" not in row[0] and not row[0].startswith("_") ] if not raw_table_names: @@ -249,9 +247,7 @@ def generate_staging_models( ).fetchall() # Filter dlt internal columns filtered = [ - (col_name, data_type) - for col_name, data_type in col_rows - if col_name not in _DLT_INTERNAL_COLUMNS + (col_name, data_type) for col_name, data_type in col_rows if col_name not in _DLT_INTERNAL_COLUMNS ] if filtered: tables[table_name] = filtered diff --git a/src/tycoon/scaffolding/observability_dbt.py b/src/tycoon/scaffolding/observability_dbt.py index 1aa5aab..057425e 100644 --- a/src/tycoon/scaffolding/observability_dbt.py +++ b/src/tycoon/scaffolding/observability_dbt.py @@ -31,20 +31,35 @@ import yaml - # Each tuple: (table_name_in_metadata_db, dbt_model_filename_stem, description) # Tracks the schema documented in `docs/reference/observability.md`. Add new # tables here when observability captures new state. _METADATA_TABLES = [ ("dlt_runs", "stg_tycoon__dlt_runs", "One row per dlt load — load_id, schema, status, inserted_at."), ("dlt_rows_by_table", "stg_tycoon__dlt_rows_by_table", "Per-table row counts within each dlt load."), - ("dlt_trace_runs", "stg_tycoon__dlt_trace_runs", "Per-pipeline timing and success metadata from dlt's trace.pickle."), - ("dlt_trace_steps", "stg_tycoon__dlt_trace_steps", "Per-step (extract / normalize / load) durations within a dlt run."), + ( + "dlt_trace_runs", + "stg_tycoon__dlt_trace_runs", + "Per-pipeline timing and success metadata from dlt's trace.pickle.", + ), + ( + "dlt_trace_steps", + "stg_tycoon__dlt_trace_steps", + "Per-step (extract / normalize / load) durations within a dlt run.", + ), ("dlt_trace_jobs", "stg_tycoon__dlt_trace_jobs", "Per-job byte sizes and elapsed times from dlt's trace.pickle."), ("dbt_runs", "stg_tycoon__dbt_runs", "One row per dbt invocation — command, target, duration, success counts."), ("dbt_nodes", "stg_tycoon__dbt_nodes", "One row per (model, test, snapshot) per dbt invocation."), - ("dbt_manifest_snapshots", "stg_tycoon__dbt_manifest_snapshots", "Per-invocation fingerprint of dbt's manifest.json."), - ("dbt_schema_changes", "stg_tycoon__dbt_schema_changes", "Per-column / per-model changes detected across consecutive manifest snapshots."), + ( + "dbt_manifest_snapshots", + "stg_tycoon__dbt_manifest_snapshots", + "Per-invocation fingerprint of dbt's manifest.json.", + ), + ( + "dbt_schema_changes", + "stg_tycoon__dbt_schema_changes", + "Per-column / per-model changes detected across consecutive manifest snapshots.", + ), ] _DIM_RUNS_SQL = """\ @@ -129,9 +144,10 @@ def _schema_yml(tables: list[tuple[str, str, str]]) -> str: "config": {"materialized": "view"}, } ) - return _SCHEMA_YML_HEADER + yaml.dump( - {"models": models}, default_flow_style=False, sort_keys=False - ).split("models:\n", 1)[1] + return ( + _SCHEMA_YML_HEADER + + yaml.dump({"models": models}, default_flow_style=False, sort_keys=False).split("models:\n", 1)[1] + ) def scaffold_observability_models( @@ -216,10 +232,7 @@ def attach_metadata_to_profiles( attach_list = output_data.setdefault("attach", []) if not isinstance(attach_list, list): continue - already = any( - isinstance(item, dict) and item.get("alias") == "tycoon_meta" - for item in attach_list - ) + already = any(isinstance(item, dict) and item.get("alias") == "tycoon_meta" for item in attach_list) if already: continue attach_list.append( @@ -232,7 +245,5 @@ def attach_metadata_to_profiles( changed = True if changed: - profiles_yml_path.write_text( - yaml.dump(raw, default_flow_style=False, sort_keys=False) - ) + profiles_yml_path.write_text(yaml.dump(raw, default_flow_style=False, sort_keys=False)) return changed diff --git a/src/tycoon/scaffolding/osi_generator.py b/src/tycoon/scaffolding/osi_generator.py index e1730a1..88c8a42 100644 --- a/src/tycoon/scaffolding/osi_generator.py +++ b/src/tycoon/scaffolding/osi_generator.py @@ -114,10 +114,7 @@ def scaffold_osi( if not warehouse_db.exists(): return ScaffoldResult( out_path=out_path, - warnings=[ - f"Warehouse not found at {warehouse_db}. " - "Run `tycoon data transform run` first." - ], + warnings=[f"Warehouse not found at {warehouse_db}. Run `tycoon data transform run` first."], ) mart_set, discovery_warnings = _resolve_marts_from_manifest(dbt_project_dir) @@ -134,9 +131,7 @@ def scaffold_osi( ) else: warnings.append( - "No marts found. Looked for tables prefixed " - + ", ".join(MART_PREFIXES) - + f" in {warehouse_db}." + "No marts found. Looked for tables prefixed " + ", ".join(MART_PREFIXES) + f" in {warehouse_db}." ) doc = _build_osi_document(project_name=project_name, datasets=datasets) @@ -317,13 +312,30 @@ def _build_field_entry(col_name: str, data_type: str) -> dict[str, Any]: # These cover the long tail; everything not in NUMERIC or TIME falls # through to the dimension branch. _NUMERIC_TYPES = { - "TINYINT", "SMALLINT", "INTEGER", "BIGINT", "HUGEINT", - "UTINYINT", "USMALLINT", "UINTEGER", "UBIGINT", - "FLOAT", "DOUBLE", "DECIMAL", "REAL", "NUMERIC", + "TINYINT", + "SMALLINT", + "INTEGER", + "BIGINT", + "HUGEINT", + "UTINYINT", + "USMALLINT", + "UINTEGER", + "UBIGINT", + "FLOAT", + "DOUBLE", + "DECIMAL", + "REAL", + "NUMERIC", } _TIME_TYPES = { - "DATE", "TIME", "TIMESTAMP", "TIMESTAMP_S", "TIMESTAMP_MS", - "TIMESTAMP_NS", "TIMESTAMP WITH TIME ZONE", "TIMESTAMPTZ", + "DATE", + "TIME", + "TIMESTAMP", + "TIMESTAMP_S", + "TIMESTAMP_MS", + "TIMESTAMP_NS", + "TIMESTAMP WITH TIME ZONE", + "TIMESTAMPTZ", "TIME WITH TIME ZONE", } @@ -378,7 +390,7 @@ def _guess_primary_key( table_lower = table.lower() for prefix in MART_PREFIXES: if table_lower.startswith(prefix): - stem = table_lower[len(prefix):] + stem = table_lower[len(prefix) :] for cand in (f"{stem}_id", f"{stem.rstrip('s')}_id"): if cand in name_set: for n in names: @@ -410,11 +422,7 @@ def _build_osi_document( "fields": [ { "name": "_placeholder_field", - "expression": { - "dialects": [ - {"dialect": "ANSI_SQL", "expression": "1"} - ] - }, + "expression": {"dialects": [{"dialect": "ANSI_SQL", "expression": "1"}]}, } ], } diff --git a/src/tycoon/scaffolding/rill_generator.py b/src/tycoon/scaffolding/rill_generator.py index ef58fdc..f2bc27b 100644 --- a/src/tycoon/scaffolding/rill_generator.py +++ b/src/tycoon/scaffolding/rill_generator.py @@ -21,7 +21,6 @@ from tycoon.utils.duckdb_utils import quote_identifier - # --------------------------------------------------------------------------- # dlt internal columns / tables — excluded from all Rill output # --------------------------------------------------------------------------- @@ -251,9 +250,7 @@ def generate_rill_config( raw_table_names = [ row[0] for row in table_rows - if row[0] not in _DLT_INTERNAL_TABLES - and "__" not in row[0] - and not row[0].startswith("_") + if row[0] not in _DLT_INTERNAL_TABLES and "__" not in row[0] and not row[0].startswith("_") ] if not raw_table_names: @@ -276,8 +273,7 @@ def generate_rill_config( filtered = [ (col_name, data_type) for col_name, data_type in col_rows - if col_name not in _DLT_INTERNAL_COLUMNS - and not col_name.startswith("_dlt_") + if col_name not in _DLT_INTERNAL_COLUMNS and not col_name.startswith("_dlt_") ] if filtered: tables[table_name] = filtered @@ -344,9 +340,7 @@ def generate_rill_config( # Explore YAML explore_path = dashboards_dir / f"{model_name}.yaml" - explore_path.write_text( - _generate_explore_yaml(source_name, table_name, metrics_view_name) - ) + explore_path.write_text(_generate_explore_yaml(source_name, table_name, metrics_view_name)) generated.append(str(explore_path)) # ------------------------------------------------------------------ @@ -366,9 +360,7 @@ def generate_rill_config( try: from tycoon.config import config - generated.extend( - refresh_usage_dashboards(project_root=config.root, rill_dir=output_dir) - ) + generated.extend(refresh_usage_dashboards(project_root=config.root, rill_dir=output_dir)) except Exception: pass diff --git a/src/tycoon/scaffolding/templates.py b/src/tycoon/scaffolding/templates.py index fb64d4d..bb9a078 100644 --- a/src/tycoon/scaffolding/templates.py +++ b/src/tycoon/scaffolding/templates.py @@ -14,7 +14,7 @@ import typer import yaml -from tycoon.project import StackConfig +from tycoon.project import SCHEMA_VERSION, StackConfig, migrate_project from tycoon.utils.console import info, success, warn @@ -50,11 +50,7 @@ def list_templates(*, include_demoted: bool = False) -> list[str]: """ if not _TEMPLATES_DIR.is_dir(): return [] - names = ( - d.name - for d in _TEMPLATES_DIR.iterdir() - if d.is_dir() and (d / "tycoon.yml").exists() - ) + names = (d.name for d in _TEMPLATES_DIR.iterdir() if d.is_dir() and (d / "tycoon.yml").exists()) if not include_demoted: names = (n for n in names if n not in _DEMOTED_TEMPLATES) return sorted(names) @@ -69,10 +65,7 @@ def get_template_path(name: str) -> Path: path = _TEMPLATES_DIR / name if not path.is_dir() or not (path / "tycoon.yml").exists(): available = list_templates() - raise FileNotFoundError( - f"Template '{name}' not found. " - f"Available templates: {', '.join(available) or '(none)'}" - ) + raise FileNotFoundError(f"Template '{name}' not found. Available templates: {', '.join(available) or '(none)'}") return path @@ -166,6 +159,7 @@ def scaffold_blank_project( project_data: dict = { "name": name, "version": "0.1.0", + "schema_version": SCHEMA_VERSION, "database": { "raw": raw_db_path, "warehouse": warehouse_db_path, @@ -271,6 +265,7 @@ def _scaffold_dbt_project( metadata_rel_for_dbt = os.path.relpath(metadata_abs, start=dbt_dir) try: from tycoon.observability import ensure_schema + ensure_schema(metadata_abs) except Exception: # Best-effort — observability scaffolding shouldn't block project setup. @@ -313,6 +308,7 @@ def _scaffold_dbt_project( raw_abs.parent.mkdir(parents=True, exist_ok=True) try: import duckdb + duckdb.connect(str(raw_abs)).close() except Exception: # Best-effort — if it fails the user gets the clearer @@ -334,15 +330,14 @@ def _scaffold_dbt_project( } } - (dbt_dir / "profiles.yml").write_text( - yaml.dump(profiles_data, default_flow_style=False, sort_keys=False) - ) + (dbt_dir / "profiles.yml").write_text(yaml.dump(profiles_data, default_flow_style=False, sort_keys=False)) info(f"Created dbt project at {dbt_dir} with dbt_project.yml and profiles.yml") # Generate the _tycoon staging models so users have a working dbt # surface over the metadata from the very first run. try: from tycoon.scaffolding.observability_dbt import scaffold_observability_models + scaffold_observability_models(dbt_dir) except Exception: # Best-effort — observability scaffolding shouldn't block project setup. @@ -397,16 +392,12 @@ def load_template_parameters(template_name: str) -> list[dict]: params = data.get("parameters", []) or [] if not isinstance(params, list): - raise ValueError( - f"{meta_path}: 'parameters' must be a list, got {type(params).__name__}" - ) + raise ValueError(f"{meta_path}: 'parameters' must be a list, got {type(params).__name__}") normalized: list[dict] = [] for i, entry in enumerate(params): if not isinstance(entry, dict) or "name" not in entry: - raise ValueError( - f"{meta_path}: parameters[{i}] must be a dict with a 'name' key" - ) + raise ValueError(f"{meta_path}: parameters[{i}] must be a dict with a 'name' key") normalized.append( { "name": entry["name"], @@ -474,10 +465,7 @@ def _resolve_template_parameters( declared = load_template_parameters(template_name) if not declared: if supplied: - warn( - f"Template '{template_name}' does not declare any parameters; " - f"ignoring --param: {', '.join(supplied)}" - ) + warn(f"Template '{template_name}' does not declare any parameters; ignoring --param: {', '.join(supplied)}") return {} supplied = dict(supplied or {}) @@ -550,6 +538,10 @@ def scaffold_from_template( warn("tycoon.yml already exists, skipping") else: shutil.copy2(src_yml, dst_yml) + # Stamp schema_version (and metadata: defaults) via migrate_project, + # which round-trips through ruamel.yaml — the template's comments and + # blank-line separators survive, unlike a yaml.safe_load/dump pass. + migrate_project(target) success(f"Created tycoon.yml from template '{template_name}'") # Copy any subdirectories from the template (e.g. dbt_project/, rill/) @@ -592,6 +584,7 @@ def scaffold_from_template( # observability bookkeeping should never block project setup. try: from tycoon.observability import ensure_schema + ensure_schema(target / ".tycoon" / "metadata.duckdb") except Exception: pass @@ -628,6 +621,7 @@ def scaffold_from_template( if template_dbt.exists() and (template_dbt / "dbt_project.yml").exists(): try: from tycoon.scaffolding.observability_dbt import scaffold_observability_models + scaffold_observability_models(template_dbt) except Exception: pass diff --git a/src/tycoon/schedule.py b/src/tycoon/schedule.py index d644f13..672ae3a 100644 --- a/src/tycoon/schedule.py +++ b/src/tycoon/schedule.py @@ -39,11 +39,11 @@ class ScheduleSpec: """A scheduled tycoon run.""" name: str - args: list[str] # tycoon args, e.g. ["data", "run-all", "--notify"] + args: list[str] # tycoon args, e.g. ["data", "run-all", "--notify"] hour: int = 4 minute: int = 0 - cadence: str = "daily" # daily | hourly | weekly - weekday: int = 1 # 1=Mon .. 7=Sun (weekly only) + cadence: str = "daily" # daily | hourly | weekly + weekday: int = 1 # 1=Mon .. 7=Sun (weekly only) project_root: Path = field(default_factory=Path.cwd) @@ -55,8 +55,7 @@ class ScheduleSpec: def validate_name(name: str) -> None: if not _NAME_RE.match(name): raise ScheduleError( - f"Invalid schedule name '{name}'. Use lowercase letters, digits, and " - "hyphens (e.g. 'daily-refresh')." + f"Invalid schedule name '{name}'. Use lowercase letters, digits, and hyphens (e.g. 'daily-refresh')." ) @@ -262,9 +261,7 @@ def add(spec: ScheduleSpec, home: Path | None = None) -> str: raise ScheduleError( f"Scheduling isn't supported on this platform ({plat}). On Windows, use " - "Task Scheduler to run `tycoon " - + " ".join(spec.args) - + "` — see the docs." + "Task Scheduler to run `tycoon " + " ".join(spec.args) + "` — see the docs." ) @@ -276,19 +273,13 @@ def list_schedules(home: Path | None = None) -> list[str]: d = launch_agents_dir(home) if not d.exists(): return [] - names = [ - p.stem[len(LAUNCHD_PREFIX):] - for p in sorted(d.glob(f"{LAUNCHD_PREFIX}*.plist")) - ] + names = [p.stem[len(LAUNCHD_PREFIX) :] for p in sorted(d.glob(f"{LAUNCHD_PREFIX}*.plist"))] return names if plat == "linux": d = systemd_user_dir(home) if not d.exists(): return [] - return [ - p.stem[len(SYSTEMD_PREFIX):] - for p in sorted(d.glob(f"{SYSTEMD_PREFIX}*.timer")) - ] + return [p.stem[len(SYSTEMD_PREFIX) :] for p in sorted(d.glob(f"{SYSTEMD_PREFIX}*.timer"))] return [] @@ -343,7 +334,6 @@ def _run(cmd: list[str], check: bool = True) -> subprocess.CompletedProcess: raise ScheduleError(f"Failed to run {cmd[0]}: {exc}") from exc if check and result.returncode != 0: raise ScheduleError( - f"`{' '.join(cmd)}` failed (exit {result.returncode}):\n" - f"{result.stderr.strip() or result.stdout.strip()}" + f"`{' '.join(cmd)}` failed (exit {result.returncode}):\n{result.stderr.strip() or result.stdout.strip()}" ) return result diff --git a/src/tycoon/sync.py b/src/tycoon/sync.py index e59a828..5b193bc 100644 --- a/src/tycoon/sync.py +++ b/src/tycoon/sync.py @@ -21,15 +21,14 @@ from __future__ import annotations import fnmatch +from collections.abc import Iterable from dataclasses import dataclass, field from pathlib import Path -from typing import Iterable import duckdb from tycoon.project import SyncSourceSpec - _SYSTEM_SCHEMAS = frozenset( { "information_schema", @@ -132,9 +131,7 @@ def _list_source_tables( return [ (schema, table) for schema, table in rows - if schema not in _SYSTEM_SCHEMAS - and _matches_any(schema, schemas_glob) - and _matches_any(table, tables_glob) + if schema not in _SYSTEM_SCHEMAS and _matches_any(schema, schemas_glob) and _matches_any(table, tables_glob) ] @@ -143,9 +140,7 @@ def _quote(identifier: str) -> str: return '"' + identifier.replace('"', '""') + '"' -def _table_exists( - con: duckdb.DuckDBPyConnection, schema: str, table: str -) -> bool: +def _table_exists(con: duckdb.DuckDBPyConnection, schema: str, table: str) -> bool: """True if a base table named ``schema.table`` already exists in the dest db.""" row = con.execute( """ diff --git a/src/tycoon/utils/console.py b/src/tycoon/utils/console.py index 256a414..eb24402 100644 --- a/src/tycoon/utils/console.py +++ b/src/tycoon/utils/console.py @@ -1,8 +1,8 @@ """Rich output helpers.""" from rich.console import Console -from rich.table import Table from rich.panel import Panel +from rich.table import Table console = Console() err_console = Console(stderr=True) diff --git a/src/tycoon/utils/duckdb_utils.py b/src/tycoon/utils/duckdb_utils.py index 3018a2a..4804c25 100644 --- a/src/tycoon/utils/duckdb_utils.py +++ b/src/tycoon/utils/duckdb_utils.py @@ -24,9 +24,7 @@ def get_row_count(db_path: Path, schema: str, table: str) -> int | None: return None try: con = duckdb.connect(str(db_path), read_only=True) - result = con.execute( - f"SELECT count(*) FROM {quote_identifier(schema)}.{quote_identifier(table)}" - ).fetchone() + result = con.execute(f"SELECT count(*) FROM {quote_identifier(schema)}.{quote_identifier(table)}").fetchone() con.close() return result[0] if result else None except duckdb.Error: diff --git a/src/tycoon/utils/process.py b/src/tycoon/utils/process.py index f587646..a45b415 100644 --- a/src/tycoon/utils/process.py +++ b/src/tycoon/utils/process.py @@ -2,9 +2,9 @@ from __future__ import annotations +import shutil import socket import subprocess -import shutil def is_port_in_use(port: int, host: str = "127.0.0.1") -> bool: diff --git a/src/tycoon/venv.py b/src/tycoon/venv.py index 312e9ab..d9820ac 100644 --- a/src/tycoon/venv.py +++ b/src/tycoon/venv.py @@ -140,8 +140,7 @@ def create_venv( ok=False, venv_path=target, message=( - f"{target} already exists. Re-run with --force to recreate it " - "(this removes the existing environment)." + f"{target} already exists. Re-run with --force to recreate it (this removes the existing environment)." ), ) diff --git a/tests/conftest.py b/tests/conftest.py index c84432b..92520d1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,9 +2,10 @@ from __future__ import annotations -import pytest from pathlib import Path +import pytest + def pytest_addoption(parser: pytest.Parser) -> None: parser.addoption( diff --git a/tests/test_check.py b/tests/test_check.py index 33e6c1d..196c2fe 100644 --- a/tests/test_check.py +++ b/tests/test_check.py @@ -2,12 +2,12 @@ from __future__ import annotations +from datetime import UTC from tycoon.cli import app class TestDoctorCommand: - def test_doctor_runs_without_crash(self, cli_runner): """doctor should run and produce output (may exit 0 or 1 depending on env).""" result = cli_runner.invoke(app, ["doctor"]) @@ -158,7 +158,7 @@ def test_empty_metadata_db_reports_hint(self, monkeypatch, tmp_path, capsys): assert "no runs captured yet" in out def test_populated_metadata_db_reports_counts(self, monkeypatch, tmp_path, capsys): - from datetime import datetime, timezone + from datetime import datetime import duckdb @@ -169,7 +169,7 @@ def test_populated_metadata_db_reports_counts(self, monkeypatch, tmp_path, capsy meta = metadata_db_path(cfg.root) ensure_schema(meta) - now = datetime.now(tz=timezone.utc) + now = datetime.now(tz=UTC) con = duckdb.connect(str(meta)) try: con.execute( @@ -251,9 +251,7 @@ def test_silent_when_no_manifest(self, monkeypatch, tmp_path, capsys): doctor._check_layer_coverage() assert capsys.readouterr().out == "" - def test_reports_success_when_every_source_has_staging( - self, monkeypatch, tmp_path, capsys - ): + def test_reports_success_when_every_source_has_staging(self, monkeypatch, tmp_path, capsys): from tycoon.commands import doctor cfg = self._patch_config( diff --git a/tests/test_cli_surface.py b/tests/test_cli_surface.py index 22ba23f..ead9255 100644 --- a/tests/test_cli_surface.py +++ b/tests/test_cli_surface.py @@ -18,13 +18,15 @@ from __future__ import annotations +import os +import shutil +import subprocess from pathlib import Path import pytest from tycoon.cli import app - # -- Stale-string registry ------------------------------------------------ # # Every entry is a substring that should NOT appear anywhere in the @@ -33,25 +35,17 @@ # addition then guards against regressions across the entire codebase. _STALE_SUBSTRINGS: tuple[tuple[str, str], ...] = ( - ("ask install-model", - "removed in v0.1.5 — folded into `tycoon register llm`"), + ("ask install-model", "removed in v0.1.5 — folded into `tycoon register llm`"), # v0.1.10 dropped the entire ask/nao surface (PR #147): no `tycoon # ask`, no `tycoon register llm`, no Nao agent. Any source string # mentioning them points users at commands that no longer exist. - ("tycoon ask", - "removed in v0.1.10 — the ask/nao surface was dropped entirely"), - ("register llm", - "removed in v0.1.10 — the ask/nao surface was dropped entirely"), - ("nao", - "removed in v0.1.10 — the Nao agent integration was dropped"), - ("Nao", - "removed in v0.1.10 — the Nao agent integration was dropped"), - ("pip install tycoon[", - "wrong package name — use `pip install database-tycoon[...]`"), - ("pip install tycoon\\[", - "wrong package name — use `pip install database-tycoon[...]`"), - ("pip install 'tycoon[", - "wrong package name — use `pip install 'database-tycoon[...]'`"), + ("tycoon ask", "removed in v0.1.10 — the ask/nao surface was dropped entirely"), + ("register llm", "removed in v0.1.10 — the ask/nao surface was dropped entirely"), + ("nao", "removed in v0.1.10 — the Nao agent integration was dropped"), + ("Nao", "removed in v0.1.10 — the Nao agent integration was dropped"), + ("pip install tycoon[", "wrong package name — use `pip install database-tycoon[...]`"), + ("pip install tycoon\\[", "wrong package name — use `pip install database-tycoon[...]`"), + ("pip install 'tycoon[", "wrong package name — use `pip install 'database-tycoon[...]'`"), ) @@ -89,9 +83,7 @@ def src_files(self) -> list[Path]: return sorted(src.rglob("*.py")) @pytest.mark.parametrize("needle,reason", _STALE_SUBSTRINGS) - def test_substring_absent_from_src( - self, src_files: list[Path], needle: str, reason: str - ): + def test_substring_absent_from_src(self, src_files: list[Path], needle: str, reason: str): """Each needle must appear in zero source files.""" offenders: list[tuple[Path, int, str]] = [] for f in src_files: @@ -102,12 +94,8 @@ def test_substring_absent_from_src( for i, line in enumerate(lines, start=1): if needle in line: offenders.append((f, i, line.strip())) - assert not offenders, ( - f"Found stale substring {needle!r} ({reason}) in:\n" - + "\n".join( - f" {f.relative_to(Path(__file__).parent.parent)}:{i}: {line}" - for f, i, line in offenders - ) + assert not offenders, f"Found stale substring {needle!r} ({reason}) in:\n" + "\n".join( + f" {f.relative_to(Path(__file__).parent.parent)}:{i}: {line}" for f, i, line in offenders ) @@ -122,7 +110,7 @@ def test_substring_absent_from_src( _KEY_HELP_PATHS = [ - (), # top-level + (), # top-level ("init",), ("register",), ("register", "dbt"), @@ -147,8 +135,7 @@ class TestHelpSurface: def test_help_exits_clean(self, path: tuple[str, ...], cli_runner): result = cli_runner.invoke(app, list(path) + ["--help"]) assert result.exit_code == 0, ( - f"`tycoon {' '.join(path)} --help` exited {result.exit_code}\n" - f"--- stdout ---\n{result.stdout}" + f"`tycoon {' '.join(path)} --help` exited {result.exit_code}\n--- stdout ---\n{result.stdout}" ) @pytest.mark.parametrize("path", _KEY_HELP_PATHS, ids=lambda p: " ".join(p) or "tycoon") @@ -218,13 +205,67 @@ def test_no_unescaped_extras_remain_in_source(self): continue line_start = text.rfind("\n", 0, match.start()) + 1 line_end = text.find("\n", match.start()) - line = text[line_start:line_end if line_end != -1 else None] + line = text[line_start : line_end if line_end != -1 else None] offenders.append(f"{src_file.name}: {line.strip()}") assert not offenders, ( "Unescaped `database-tycoon[]` found — Rich will strip " - "the brackets. Use `database-tycoon\\\\[]` instead.\n" - + "\n".join(offenders) + "the brackets. Use `database-tycoon\\\\[]` instead.\n" + "\n".join(offenders) + ) + + +# -- 4. Import-time safety: a hostile tycoon.yml must never brick the CLI +# +# Regression class from the T2-4 review (#187): a future +# schema_version raised inside the module-level +# `config = TycoonConfig()` — and because Typer imports every +# command module to build the CLI, the raise landed before +# argument parsing. `--help`, `--version`, and `init --upgrade` +# (the documented remedy!) all died with a traceback. +# +# CliRunner cannot observe this failure mode by construction: +# `tycoon.config` is already imported in pytest's own process +# before any tmp_path project exists. Only a subprocess sees the +# real import sequence. + + +class TestImportTimeSafety: + @pytest.fixture + def hostile_project(self, tmp_path: Path) -> Path: + (tmp_path / "tycoon.yml").write_text("name: hostile\nversion: 0.1.0\nschema_version: 99\n") + return tmp_path + + # (args, expected_rc): --help/--version must succeed regardless of the + # project file; `init --upgrade` on a future version must fail *cleanly* + # (rc 1, no traceback) — it can't downgrade, but it must stay reachable. + @pytest.mark.parametrize( + "args,expected_rc", + [(["--help"], 0), (["--version"], 0), (["init", "--upgrade"], 1)], + ids=lambda v: " ".join(v) if isinstance(v, list) else str(v), + ) + def test_cli_survives_future_schema_version(self, hostile_project: Path, args: list[str], expected_rc: int): + binary = shutil.which("tycoon") + if not binary: + pytest.skip( + "`tycoon` binary not on PATH. Run via `uv run pytest` so the " + "venv's bin dir is active (or `pip install -e .` first)." + ) + result = subprocess.run( + [binary, *args], + cwd=hostile_project, + env=os.environ.copy(), + capture_output=True, + text=True, + timeout=60, + check=False, + ) + combined = result.stdout + result.stderr + assert "Traceback" not in combined, ( + f"`tycoon {' '.join(args)}` tracebacked with schema_version: 99 in cwd:\n{combined}" + ) + assert result.returncode == expected_rc, ( + f"`tycoon {' '.join(args)}` exited {result.returncode} (expected {expected_rc}) with " + f"schema_version: 99 in cwd\n--- stdout ---\n{result.stdout}\n--- stderr ---\n{result.stderr}" ) @@ -234,15 +275,13 @@ def test_no_unescaped_extras_remain_in_source(self): class TestVersionString: - def test_version_matches_init_module(self, cli_runner): from tycoon import __version__ result = cli_runner.invoke(app, ["--version"]) assert result.exit_code == 0 assert __version__ in result.stdout, ( - f"`tycoon --version` printed {result.stdout!r}, expected substring " - f"{__version__!r}" + f"`tycoon --version` printed {result.stdout!r}, expected substring {__version__!r}" ) def test_version_matches_pyproject(self): @@ -270,6 +309,5 @@ def test_version_matches_pyproject(self): pin = line.split("=", 1)[1].strip().strip('"').strip("'") break assert pin == __version__, ( - f"pyproject.toml [project].version={pin!r} but " - f"src/tycoon/__init__.py __version__={__version__!r}" + f"pyproject.toml [project].version={pin!r} but src/tycoon/__init__.py __version__={__version__!r}" ) diff --git a/tests/test_config.py b/tests/test_config.py index 84fa11f..051f2bc 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -4,11 +4,11 @@ from pathlib import Path -from tycoon.config import TycoonConfig +from tycoon.config import TycoonConfig, load_config +from tycoon.project import SCHEMA_VERSION class TestTycoonConfig: - def test_finds_project_root_from_pyproject(self, tmp_path: Path): (tmp_path / "pyproject.toml").write_text('[project]\nname = "test"\n') cfg = TycoonConfig(project_root=tmp_path) @@ -37,3 +37,73 @@ def test_paths_relative_to_project_root(self, tmp_config): assert tmp_config.data_dir == tmp_config.root / "data" assert tmp_config.dbt_project_dir == tmp_config.root / "dbt_project" assert tmp_config.rill_dir == tmp_config.root / "rill" + + +class TestLoadConfigSchemaWarning: + """T2-4: load_config warns via console when tycoon.yml schema_version is stale.""" + + def test_warns_when_schema_version_absent(self, tmp_path, monkeypatch): + (tmp_path / "tycoon.yml").write_text("name: old-project\n") + monkeypatch.chdir(tmp_path) + + calls = [] + monkeypatch.setattr("tycoon.config._warn_console", lambda msg: calls.append(msg)) + + load_config() + + assert len(calls) == 1 + assert "tycoon init --upgrade" in calls[0] + + def test_warns_when_schema_version_old(self, tmp_path, monkeypatch): + (tmp_path / "tycoon.yml").write_text(f"name: old\nschema_version: {SCHEMA_VERSION - 1}\n") + monkeypatch.chdir(tmp_path) + + calls = [] + monkeypatch.setattr("tycoon.config._warn_console", lambda msg: calls.append(msg)) + + load_config() + + assert len(calls) == 1 + + def test_no_warning_when_current(self, tmp_path, monkeypatch): + (tmp_path / "tycoon.yml").write_text(f"name: current\nschema_version: {SCHEMA_VERSION}\n") + monkeypatch.chdir(tmp_path) + + calls = [] + monkeypatch.setattr("tycoon.config._warn_console", lambda msg: calls.append(msg)) + + load_config() + + assert calls == [] + + def test_no_warning_without_tycoon_yml(self, tmp_path, monkeypatch): + (tmp_path / "pyproject.toml").write_text('[project]\nname = "x"\n') + monkeypatch.chdir(tmp_path) + + calls = [] + monkeypatch.setattr("tycoon.config._warn_console", lambda msg: calls.append(msg)) + + load_config() + + assert calls == [] + + def test_errors_and_exits_when_schema_version_future(self, tmp_path, monkeypatch): + """load_config must error and exit for a schema_version newer than SCHEMA_VERSION. + + The gate lives here (not in load_project) so the import-time singleton + never raises and --help / init --upgrade remain reachable. + """ + import pytest + + (tmp_path / "tycoon.yml").write_text(f"name: future\nschema_version: {SCHEMA_VERSION + 1}\n") + monkeypatch.chdir(tmp_path) + + errors = [] + monkeypatch.setattr("tycoon.config._error_console", lambda msg: errors.append(msg)) + + with pytest.raises(SystemExit) as exc_info: + load_config() + + assert exc_info.value.code == 1 + assert len(errors) == 1 + assert "newer than this tycoon supports" in errors[0] diff --git a/tests/test_constants.py b/tests/test_constants.py index c3baa32..0ce57af 100644 --- a/tests/test_constants.py +++ b/tests/test_constants.py @@ -6,7 +6,6 @@ class TestPorts: - def test_ports_are_integers(self): for name, port in constants.PORTS.items(): assert isinstance(port, int), f"Port for {name} should be int" @@ -21,7 +20,6 @@ def test_expected_keys_present(self): class TestSocrataDefaults: - def test_page_size_is_positive_int(self): assert isinstance(constants.SOCRATA_PAGE_SIZE, int) assert constants.SOCRATA_PAGE_SIZE > 0 diff --git a/tests/test_db_command.py b/tests/test_db_command.py index bd98ea0..bfd1ef9 100644 --- a/tests/test_db_command.py +++ b/tests/test_db_command.py @@ -10,7 +10,6 @@ class TestSchema: - def test_schema_runs(self, cli_runner): """data schema should run even with no databases (shows WARN status).""" result = cli_runner.invoke(app, ["data", "schema"]) @@ -24,7 +23,6 @@ def test_schema_output_contains_database_labels(self, cli_runner): class TestQuery: - def test_query_no_database_gives_error(self, cli_runner, tmp_config, monkeypatch): """query should fail gracefully when the database file does not exist.""" monkeypatch.setattr("tycoon.commands.db.config", tmp_config) @@ -72,7 +70,6 @@ def test_query_with_source_flag(self, cli_runner, tmp_path, monkeypatch): class TestDataHelp: - def test_data_help_shows_query_schema_clean(self, cli_runner): result = cli_runner.invoke(app, ["data", "--help"]) assert result.exit_code == 0 @@ -120,21 +117,15 @@ def test_clean_all_preserves_metadata_by_default(self, tmp_path, monkeypatch, cl assert not local.exists() assert meta.exists(), "metadata.duckdb must survive --all by default" - def test_clean_all_with_metadata_flag_wipes_metadata( - self, tmp_path, monkeypatch, cli_runner - ): + def test_clean_all_with_metadata_flag_wipes_metadata(self, tmp_path, monkeypatch, cli_runner): raw, local, meta = self._setup(tmp_path, monkeypatch) - result = cli_runner.invoke( - app, ["data", "clean", "--all", "--metadata"], input="y\n" - ) + result = cli_runner.invoke(app, ["data", "clean", "--all", "--metadata"], input="y\n") assert result.exit_code == 0 assert not raw.exists() assert not local.exists() assert not meta.exists() - def test_clean_metadata_alone_removes_only_metadata( - self, tmp_path, monkeypatch, cli_runner - ): + def test_clean_metadata_alone_removes_only_metadata(self, tmp_path, monkeypatch, cli_runner): raw, local, meta = self._setup(tmp_path, monkeypatch) result = cli_runner.invoke(app, ["data", "clean", "--metadata"], input="y\n") assert result.exit_code == 0 diff --git a/tests/test_dbt_profiles.py b/tests/test_dbt_profiles.py index 08347ce..c454d66 100644 --- a/tests/test_dbt_profiles.py +++ b/tests/test_dbt_profiles.py @@ -17,9 +17,7 @@ def _write_dbt_project(dir_: Path, profile_name: str = "p") -> None: dir_.mkdir(parents=True, exist_ok=True) - (dir_ / "dbt_project.yml").write_text( - yaml.dump({"name": "p", "profile": profile_name, "version": "1"}) - ) + (dir_ / "dbt_project.yml").write_text(yaml.dump({"name": "p", "profile": profile_name, "version": "1"})) def _write_profiles( @@ -109,9 +107,7 @@ def test_tycoon_yml_dbt_profiles_dir_beats_dbt_project(self, tmp_path): assert r.profiles_yml == elsewhere / "profiles.yml" assert r.source.startswith("tycoon.yml") - def test_env_var_dbt_profiles_dir_used_when_no_other( - self, tmp_path, monkeypatch - ): + def test_env_var_dbt_profiles_dir_used_when_no_other(self, tmp_path, monkeypatch): """``$DBT_PROFILES_DIR`` is honored after the co-located file.""" dbt = tmp_path / "dbt" _write_dbt_project(dbt) @@ -128,9 +124,7 @@ def test_env_var_dbt_profiles_dir_used_when_no_other( assert r.profiles_yml == env_dir / "profiles.yml" assert r.source == "$DBT_PROFILES_DIR" - def test_returns_none_when_no_profiles_yml_anywhere( - self, tmp_path, monkeypatch - ): + def test_returns_none_when_no_profiles_yml_anywhere(self, tmp_path, monkeypatch): dbt = tmp_path / "dbt" _write_dbt_project(dbt) # dbt_project.yml only monkeypatch.delenv("DBT_PROFILES_DIR", raising=False) diff --git a/tests/test_e2e_demo_arc.py b/tests/test_e2e_demo_arc.py index 1b49039..a3d3a71 100644 --- a/tests/test_e2e_demo_arc.py +++ b/tests/test_e2e_demo_arc.py @@ -15,6 +15,7 @@ [40]: https://github.com/Database-Tycoon/tycoon-cli/issues/40 """ + from __future__ import annotations import os @@ -42,10 +43,8 @@ def _tycoon_bin() -> str: return binary -def _run( - args: list[str], *, cwd: Path, env: dict[str, str], timeout: int = 90 -) -> subprocess.CompletedProcess[str]: - return subprocess.run( # noqa: S603 - intentional subprocess for CLI surface test +def _run(args: list[str], *, cwd: Path, env: dict[str, str], timeout: int = 90) -> subprocess.CompletedProcess[str]: + return subprocess.run( args, cwd=cwd, env=env, @@ -56,14 +55,10 @@ def _run( ) -def _assert_ok( - label: str, result: subprocess.CompletedProcess[str] -) -> None: +def _assert_ok(label: str, result: subprocess.CompletedProcess[str]) -> None: if result.returncode != 0: pytest.fail( - f"{label} exited {result.returncode}\n" - f"--- stdout ---\n{result.stdout}\n" - f"--- stderr ---\n{result.stderr}\n" + f"{label} exited {result.returncode}\n--- stdout ---\n{result.stdout}\n--- stderr ---\n{result.stderr}\n" ) @@ -96,12 +91,8 @@ def test_demo_arc_csv_import(tmp_path: Path) -> None: ), ) assert (project / "tycoon.yml").exists(), "tycoon.yml not scaffolded" - assert (project / "dbt_project" / "dbt_project.yml").exists(), ( - "dbt_project/ not scaffolded by csv-import template" - ) - assert (project / "data" / "input" / "widgets.csv").exists(), ( - "sample widgets.csv not seeded by csv-import template" - ) + assert (project / "dbt_project" / "dbt_project.yml").exists(), "dbt_project/ not scaffolded by csv-import template" + assert (project / "data" / "input" / "widgets.csv").exists(), "sample widgets.csv not seeded by csv-import template" # 2. ingest the bundled CSV _assert_ok( @@ -136,6 +127,4 @@ def test_demo_arc_csv_import(tmp_path: Path) -> None: ), ) warehouse_db = project / "data" / "files_warehouse.duckdb" - assert warehouse_db.exists(), ( - f"warehouse db missing after transform: {warehouse_db}" - ) + assert warehouse_db.exists(), f"warehouse db missing after transform: {warehouse_db}" diff --git a/tests/test_explore.py b/tests/test_explore.py index 99d3844..79dd4ec 100644 --- a/tests/test_explore.py +++ b/tests/test_explore.py @@ -10,7 +10,6 @@ from tycoon.cli import app - # --------------------------------------------------------------------------- # Shared fixtures # --------------------------------------------------------------------------- @@ -34,10 +33,7 @@ def test_db(tmp_path: Path) -> Path: ) """ ) - con.execute( - "INSERT INTO test_schema.my_table VALUES " - "(1, 'test', 99.5, '2024-01-01', 'x', 'y')" - ) + con.execute("INSERT INTO test_schema.my_table VALUES (1, 'test', 99.5, '2024-01-01', 'x', 'y')") con.close() return db_path @@ -687,9 +683,7 @@ def test_analyze_fails_without_tycoon_yml(self, cli_runner, tmp_path, monkeypatc def test_analyze_fails_for_unknown_source(self, cli_runner, tmp_path, monkeypatch): monkeypatch.chdir(tmp_path) - (tmp_path / "tycoon.yml").write_text( - "name: test\nversion: 0.1.0\nsources: {}\n" - ) + (tmp_path / "tycoon.yml").write_text("name: test\nversion: 0.1.0\nsources: {}\n") result = cli_runner.invoke(app, ["data", "analyze", "nonexistent-source"]) assert result.exit_code != 0 @@ -713,13 +707,7 @@ def test_analyze_fails_when_raw_db_missing(self, cli_runner, tmp_path, monkeypat def test_analyze_all_with_source_name_errors(self, cli_runner, tmp_path, monkeypatch): monkeypatch.chdir(tmp_path) - (tmp_path / "tycoon.yml").write_text( - "name: test\nversion: 0.1.0\nsources: {}\n" - ) - from tycoon.commands import explore as explore_mod - from tycoon.config import TycoonConfig - monkeypatch.setattr(explore_mod, "config", TycoonConfig(project_root=tmp_path)) - + (tmp_path / "tycoon.yml").write_text("name: test\nversion: 0.1.0\nsources: {}\n") result = cli_runner.invoke(app, ["data", "analyze", "my-source", "--all"]) assert result.exit_code != 0 combined = (result.stdout or "") + (result.stderr or "") @@ -754,20 +742,13 @@ def test_analyze_all_iterates_every_source(self, cli_runner, tmp_path, monkeypat # Need a dbt project dir so output_dir parent is valid. (tmp_path / "dbt_project" / "models").mkdir(parents=True) - from tycoon.commands import explore as explore_mod - from tycoon.config import TycoonConfig - monkeypatch.setattr(explore_mod, "config", TycoonConfig(project_root=tmp_path)) - result = cli_runner.invoke(app, ["data", "analyze", "--all"]) assert result.exit_code == 0, result.stdout # src_a generated; src_b skipped (raw DB has no schema for it but # the DB file exists, so the path is "no eligible tables" — still # a soft-skip in --all mode). - assert ( - tmp_path / "dbt_project" / "models" / "staging" / "src_a" - / "stg_src_a__items.sql" - ).exists() + assert (tmp_path / "dbt_project" / "models" / "staging" / "src_a" / "stg_src_a__items.sql").exists() assert "src_a" in result.stdout assert "src_b" in result.stdout @@ -794,11 +775,6 @@ def test_analyze_interactive_prompt_does_not_crash(self, cli_runner, tmp_path, m con.execute("CREATE TABLE raw_src_a.items (id INTEGER)") con.close() - # Reload config for the analyze command - from tycoon.commands import explore as explore_mod - from tycoon.config import TycoonConfig - monkeypatch.setattr(explore_mod, "config", TycoonConfig(project_root=tmp_path)) - # Supply "src_a" as the interactive choice result = cli_runner.invoke(app, ["data", "analyze", "--no-dbt"], input="src_a\n") # The key assertion: no AttributeError. Exit may be 0 or non-zero depending @@ -840,18 +816,12 @@ def _make_dlt_db( load_id = f"load-{i + 1}" svh_val = ", 'hash-v1'" if include_schema_version_hash else "" con.execute( - f'INSERT INTO "{schema}"."_dlt_loads" VALUES ' - f"('{load_id}', 0, '2026-04-01 10:00:00'{svh_val})" + f"INSERT INTO \"{schema}\".\"_dlt_loads\" VALUES ('{load_id}', 0, '2026-04-01 10:00:00'{svh_val})" ) for table, rows in tables: - con.execute( - f'CREATE TABLE "{schema}"."{table}" ' - f"(id INTEGER, _dlt_load_id VARCHAR)" - ) + con.execute(f'CREATE TABLE "{schema}"."{table}" (id INTEGER, _dlt_load_id VARCHAR)') for r in range(rows): - con.execute( - f'INSERT INTO "{schema}"."{table}" VALUES ({r}, \'{load_id}\')' - ) + con.execute(f'INSERT INTO "{schema}"."{table}" VALUES ({r}, \'{load_id}\')') finally: con.close() return db_path @@ -958,9 +928,7 @@ def test_mirrors_loads_and_row_counts(self, tmp_path: Path): con = duckdb.connect(str(meta), read_only=True) try: - runs = con.execute( - "SELECT source_schema, load_id, status FROM dlt_runs ORDER BY source_schema" - ).fetchall() + runs = con.execute("SELECT source_schema, load_id, status FROM dlt_runs ORDER BY source_schema").fetchall() assert runs == [ ("raw_src_a", "load-1", 0), ("raw_src_b", "load-2", 0), @@ -1009,9 +977,7 @@ def test_tolerates_missing_schema_version_hash(self, tmp_path: Path): con = duckdb.connect(str(meta), read_only=True) try: - row = con.execute( - "SELECT schema_version_hash FROM dlt_runs" - ).fetchone() + row = con.execute("SELECT schema_version_hash FROM dlt_runs").fetchone() assert row is not None and row[0] is None finally: con.close() @@ -1042,14 +1008,12 @@ def test_captures_invocation_and_nodes(self, tmp_path: Path): con = duckdb.connect(str(meta), read_only=True) try: run = con.execute( - "SELECT invocation_id, command, success, models_ok, tests_passed " - "FROM dbt_runs" + "SELECT invocation_id, command, success, models_ok, tests_passed FROM dbt_runs" ).fetchone() assert run == ("abc-123", "build", True, 1, 1) nodes = con.execute( - "SELECT node_name, resource_type, status, rows_affected " - "FROM dbt_nodes ORDER BY node_name" + "SELECT node_name, resource_type, status, rows_affected FROM dbt_nodes ORDER BY node_name" ).fetchall() assert nodes == [ ("model.demo.stg_orders", "model", "success", 1000), @@ -1135,9 +1099,7 @@ def test_no_op_when_rill_dir_missing(self, tmp_path: Path): # seed metadata capture_dlt(tmp_path / ".tycoon" / "metadata.duckdb", raw) - result = refresh_usage_dashboards( - project_root=tmp_path, rill_dir=tmp_path / "does_not_exist" - ) + result = refresh_usage_dashboards(project_root=tmp_path, rill_dir=tmp_path / "does_not_exist") assert result == [] def test_no_op_when_metadata_db_missing(self, tmp_path: Path): diff --git a/tests/test_fivetran.py b/tests/test_fivetran.py index 8805043..d948c1f 100644 --- a/tests/test_fivetran.py +++ b/tests/test_fivetran.py @@ -26,7 +26,6 @@ sync_fivetran_metadata, ) - # -------------------------------------------------------------------------- # Fixtures # -------------------------------------------------------------------------- @@ -72,10 +71,7 @@ def _list_payload( """Group-connector list response. Accepts full bodies (preferred) or bare ids as a backwards-compatible shorthand for tests that only care about pagination shape.""" - items: list[dict] = [ - c if isinstance(c, dict) else _connector_body(c) - for c in connectors - ] + items: list[dict] = [c if isinstance(c, dict) else _connector_body(c) for c in connectors] body: dict = {"data": {"items": items}} if next_cursor: body["data"]["next_cursor"] = next_cursor @@ -109,9 +105,7 @@ def handler(request: httpx.Request) -> httpx.Response: def _make_client(routes: dict, group_id: str = "g1") -> FivetranClient: transport = httpx.MockTransport(_make_handler(routes)) http = httpx.Client(transport=transport) - return FivetranClient( - api_key="k", api_secret="s", group_id=group_id, http_client=http - ) + return FivetranClient(api_key="k", api_secret="s", group_id=group_id, http_client=http) # -------------------------------------------------------------------------- @@ -146,9 +140,7 @@ def handler(request: httpx.Request) -> httpx.Response: calls.append(request.url.path) return httpx.Response( 200, - json=_list_payload( - [_connector_body("c1"), _connector_body("c2")] - ), + json=_list_payload([_connector_body("c1"), _connector_body("c2")]), ) http = httpx.Client(transport=httpx.MockTransport(handler)) @@ -161,9 +153,7 @@ def test_pagination_follows_next_cursor(self): client = _make_client( { "/v1/groups/g1/connectors": [ - _list_payload( - [_connector_body("c1")], next_cursor="abc" - ), + _list_payload([_connector_body("c1")], next_cursor="abc"), _list_payload([_connector_body("c2")]), # no cursor → done ], } @@ -174,24 +164,18 @@ def test_pagination_follows_next_cursor(self): def test_iso_timestamps_parsed_as_utc_aware(self): client = _make_client( { - "/v1/groups/g1/connectors": _list_payload( - [_connector_body("c1", succeeded_at="2026-05-08T08:30:00Z")] - ), + "/v1/groups/g1/connectors": _list_payload([_connector_body("c1", succeeded_at="2026-05-08T08:30:00Z")]), } ) c = client.list_connectors()[0] assert c.succeeded_at is not None assert c.succeeded_at.tzinfo is not None - assert c.succeeded_at == datetime.datetime( - 2026, 5, 8, 8, 30, tzinfo=datetime.timezone.utc - ) + assert c.succeeded_at == datetime.datetime(2026, 5, 8, 8, 30, tzinfo=datetime.UTC) def test_invalid_timestamp_becomes_none(self): client = _make_client( { - "/v1/groups/g1/connectors": _list_payload( - [_connector_body("c1", succeeded_at="not-a-date")] - ), + "/v1/groups/g1/connectors": _list_payload([_connector_body("c1", succeeded_at="not-a-date")]), } ) c = client.list_connectors()[0] @@ -201,7 +185,7 @@ def test_context_manager_closes_owned_client(self): """When the client creates its own httpx.Client, exiting the `with` block must close it. Reuses a real client + asserts via is_closed.""" client = FivetranClient("k", "s", "g1") - underlying = client._http # noqa: SLF001 — explicit white-box check + underlying = client._http with client: assert not underlying.is_closed assert underlying.is_closed @@ -268,9 +252,7 @@ def test_sync_writes_one_row_per_connector(self, tmp_path: Path): con = duckdb.connect(str(meta_db), read_only=True) try: - count = con.execute( - "SELECT count(*) FROM fivetran_connectors" - ).fetchone()[0] + count = con.execute("SELECT count(*) FROM fivetran_connectors").fetchone()[0] finally: con.close() assert count == 2 @@ -279,9 +261,7 @@ def test_re_sync_at_later_time_accumulates_history(self, tmp_path: Path): """Multiple syncs produce multiple snapshots per connector.""" client = _make_client( { - "/v1/groups/g1/connectors": _list_payload( - [_connector_body("c1")] - ), + "/v1/groups/g1/connectors": _list_payload([_connector_body("c1")]), } ) meta_db = tmp_path / "metadata.duckdb" @@ -294,23 +274,17 @@ def test_re_sync_at_later_time_accumulates_history(self, tmp_path: Path): con = duckdb.connect(str(meta_db), read_only=True) try: - row = con.execute( - "SELECT count(DISTINCT connector_id) FROM fivetran_connectors" - ).fetchone() + row = con.execute("SELECT count(DISTINCT connector_id) FROM fivetran_connectors").fetchone() finally: con.close() assert row[0] == 1 assert first.new == 1 # first sync flagged it as new - def test_latest_connector_snapshot_returns_most_recent_per_connector( - self, tmp_path: Path - ): + def test_latest_connector_snapshot_returns_most_recent_per_connector(self, tmp_path: Path): meta_db = tmp_path / "metadata.duckdb" client = _make_client( { - "/v1/groups/g1/connectors": _list_payload( - [_connector_body("c1")] - ), + "/v1/groups/g1/connectors": _list_payload([_connector_body("c1")]), } ) sync_fivetran_metadata(client, meta_db) @@ -342,9 +316,7 @@ def test_live_ok_writes_through_to_cache(self, tmp_path: Path): meta_db = tmp_path / "metadata.duckdb" client = _make_client( { - "/v1/groups/g1/connectors": _list_payload( - [_connector_body("c1", schema="raw_live")] - ), + "/v1/groups/g1/connectors": _list_payload([_connector_body("c1", schema="raw_live")]), } ) refreshed, warning = _live_refresh_fivetran(client, meta_db) @@ -360,9 +332,7 @@ def test_live_fail_falls_back_to_existing_cache(self, tmp_path: Path): # Seed a prior snapshot so there's a cache to fall back to. seed = _make_client( { - "/v1/groups/g1/connectors": _list_payload( - [_connector_body("c1", schema="raw_cached")] - ), + "/v1/groups/g1/connectors": _list_payload([_connector_body("c1", schema="raw_cached")]), } ) sync_fivetran_metadata(seed, meta_db) @@ -403,32 +373,22 @@ def boom(request: httpx.Request) -> httpx.Response: class TestFreshnessLabel: def test_paused_connector_labelled_paused(self): - label, _ = freshness_label( - succeeded_at=None, failed_at=None, paused=True - ) + label, _ = freshness_label(succeeded_at=None, failed_at=None, paused=True) assert label == "paused" def test_never_synced_is_red(self): - _, style = freshness_label( - succeeded_at=None, failed_at=None, paused=False - ) + _, style = freshness_label(succeeded_at=None, failed_at=None, paused=False) assert style == "red" def test_recent_success_is_green(self): - recent = datetime.datetime.now(tz=datetime.timezone.utc) - datetime.timedelta( - minutes=10 - ) - _, style = freshness_label( - succeeded_at=recent, failed_at=None, paused=False - ) + recent = datetime.datetime.now(tz=datetime.UTC) - datetime.timedelta(minutes=10) + _, style = freshness_label(succeeded_at=recent, failed_at=None, paused=False) assert style == "green" def test_failure_after_success_is_red_failed(self): - now = datetime.datetime.now(tz=datetime.timezone.utc) + now = datetime.datetime.now(tz=datetime.UTC) succ = now - datetime.timedelta(hours=2) fail = now - datetime.timedelta(minutes=5) - label, style = freshness_label( - succeeded_at=succ, failed_at=fail, paused=False - ) + label, style = freshness_label(succeeded_at=succ, failed_at=fail, paused=False) assert "failed" in label assert style == "red" diff --git a/tests/test_history.py b/tests/test_history.py index fe16e9c..35acfc0 100644 --- a/tests/test_history.py +++ b/tests/test_history.py @@ -2,7 +2,7 @@ from __future__ import annotations -from datetime import datetime, timezone +from datetime import UTC, datetime from pathlib import Path import duckdb @@ -11,27 +11,15 @@ from tycoon.cli import app from tycoon.core.events import DbtRunCompleted, RunCompleted - # --------------------------------------------------------------------------- # Fixtures and seeding helpers # --------------------------------------------------------------------------- def _write_tycoon_yml(root: Path, with_sources: bool = False) -> None: - body = ( - "name: test\n" - "version: 0.1.0\n" - "database:\n" - " raw: data/raw.duckdb\n" - " warehouse: data/warehouse.duckdb\n" - ) + body = "name: test\nversion: 0.1.0\ndatabase:\n raw: data/raw.duckdb\n warehouse: data/warehouse.duckdb\n" if with_sources: - body += ( - "sources:\n" - " src_a:\n" - " type: rest_api\n" - " schema: raw_src_a\n" - ) + body += "sources:\n src_a:\n type: rest_api\n schema: raw_src_a\n" else: body += "sources: {}\n" (root / "tycoon.yml").write_text(body) @@ -55,7 +43,7 @@ def _seed_metadata( meta = metadata_db_path(root) ensure_schema(meta) - now = datetime.now(tz=timezone.utc) + now = datetime.now(tz=UTC) con = duckdb.connect(str(meta)) try: for row in dlt_runs or []: @@ -74,9 +62,7 @@ def _seed_metadata( [*row, now], ) for row in dbt_nodes or []: - con.execute( - "INSERT INTO dbt_nodes VALUES (?, ?, ?, ?, ?, ?, ?, ?)", list(row) - ) + con.execute("INSERT INTO dbt_nodes VALUES (?, ?, ?, ?, ?, ?, ?, ?)", list(row)) finally: con.close() return meta @@ -130,7 +116,7 @@ def test_lists_dlt_runs(self, history_project, cli_runner): runtime_id="dlt-managed", load_id="load-aaaaaaaa-001", rows_loaded={"items": 100, "orders": 50}, - timestamp=datetime(2026, 4, 19, 15, 30, tzinfo=timezone.utc), + timestamp=datetime(2026, 4, 19, 15, 30, tzinfo=UTC), ), ], ) @@ -154,7 +140,7 @@ def test_lists_dbt_runs(self, history_project, cli_runner): models_passed=12, models_errored=0, duration_seconds=38.1, - timestamp=datetime(2026, 4, 19, 16, 0, tzinfo=timezone.utc), + timestamp=datetime(2026, 4, 19, 16, 0, tzinfo=UTC), ), ], ) @@ -172,7 +158,7 @@ def test_filters_by_tool_dlt(self, history_project, cli_runner): source_id="s", runtime_id="dlt-managed", load_id="load-1", - timestamp=datetime(2026, 4, 19, 10, 0, tzinfo=timezone.utc), + timestamp=datetime(2026, 4, 19, 10, 0, tzinfo=UTC), ), DbtRunCompleted( event_id="inv-1", @@ -180,7 +166,7 @@ def test_filters_by_tool_dlt(self, history_project, cli_runner): runtime_id="dbt", command="run", target="dev", - timestamp=datetime(2026, 4, 19, 11, 0, tzinfo=timezone.utc), + timestamp=datetime(2026, 4, 19, 11, 0, tzinfo=UTC), ), ], ) @@ -202,7 +188,7 @@ def test_respects_limit(self, history_project, cli_runner): source_id="s", runtime_id="dlt-managed", load_id=f"{p}-{i:03d}", - timestamp=datetime(2026, 4, 19, 10, i, tzinfo=timezone.utc), + timestamp=datetime(2026, 4, 19, 10, i, tzinfo=UTC), ) for i, p in enumerate(prefixes) ] @@ -226,19 +212,17 @@ def test_source_filters_dlt_by_schema_literal(self, history_project, cli_runner) source_id="raw_apples", runtime_id="dlt-managed", load_id="apple-load-1", - timestamp=datetime(2026, 4, 19, 10, 0, tzinfo=timezone.utc), + timestamp=datetime(2026, 4, 19, 10, 0, tzinfo=UTC), ), RunCompleted( source_id="raw_bananas", runtime_id="dlt-managed", load_id="banana-load-1", - timestamp=datetime(2026, 4, 19, 11, 0, tzinfo=timezone.utc), + timestamp=datetime(2026, 4, 19, 11, 0, tzinfo=UTC), ), ], ) - result = cli_runner.invoke( - app, ["data", "history", "--source", "raw_apples"] - ) + result = cli_runner.invoke(app, ["data", "history", "--source", "raw_apples"]) assert result.exit_code == 0 # _short() truncates load_id to 8 chars → "apple-lo" assert "apple-lo" in result.stdout @@ -271,13 +255,13 @@ def test_source_resolves_config_name_to_schema(self, history_project, cli_runner source_id="pokeapi", runtime_id="dlt-managed", load_id="poke-load-1", - timestamp=datetime(2026, 4, 19, 10, 0, tzinfo=timezone.utc), + timestamp=datetime(2026, 4, 19, 10, 0, tzinfo=UTC), ), RunCompleted( source_id="other", runtime_id="dlt-managed", load_id="other-load-1", - timestamp=datetime(2026, 4, 19, 11, 0, tzinfo=timezone.utc), + timestamp=datetime(2026, 4, 19, 11, 0, tzinfo=UTC), ), ], ) @@ -295,7 +279,7 @@ def test_source_hides_dbt_runs(self, history_project, cli_runner): source_id="raw_apples", runtime_id="dlt-managed", load_id="apple-load-1", - timestamp=datetime(2026, 4, 19, 10, 0, tzinfo=timezone.utc), + timestamp=datetime(2026, 4, 19, 10, 0, tzinfo=UTC), ), DbtRunCompleted( event_id="inv-xyz", @@ -303,13 +287,11 @@ def test_source_hides_dbt_runs(self, history_project, cli_runner): runtime_id="dbt", command="build", target="dev", - timestamp=datetime(2026, 4, 19, 11, 0, tzinfo=timezone.utc), + timestamp=datetime(2026, 4, 19, 11, 0, tzinfo=UTC), ), ], ) - result = cli_runner.invoke( - app, ["data", "history", "--source", "raw_apples"] - ) + result = cli_runner.invoke(app, ["data", "history", "--source", "raw_apples"]) assert result.exit_code == 0 assert "apple-lo" in result.stdout assert "inv-xyz" not in result.stdout @@ -322,13 +304,11 @@ def test_source_with_no_matches_informs(self, history_project, cli_runner): source_id="raw_apples", runtime_id="dlt-managed", load_id="apple-load-1", - timestamp=datetime(2026, 4, 19, 10, 0, tzinfo=timezone.utc), + timestamp=datetime(2026, 4, 19, 10, 0, tzinfo=UTC), ), ], ) - result = cli_runner.invoke( - app, ["data", "history", "--source", "raw_nonexistent"] - ) + result = cli_runner.invoke(app, ["data", "history", "--source", "raw_nonexistent"]) assert result.exit_code == 0 assert "No dlt runs captured" in result.stdout @@ -349,7 +329,7 @@ def test_show_resolves_dlt_load_by_prefix(self, history_project, cli_runner): load_id="load-xyz-123456", rows_loaded={"items": 42}, tables_created=["items"], - timestamp=datetime(2026, 4, 19, 12, 0, tzinfo=timezone.utc), + timestamp=datetime(2026, 4, 19, 12, 0, tzinfo=UTC), ), ], ) @@ -373,7 +353,7 @@ def test_show_resolves_dbt_invocation_by_prefix(self, history_project, cli_runne models_passed=1, models_errored=0, duration_seconds=5.0, - timestamp=datetime(2026, 4, 19, 12, 0, tzinfo=timezone.utc), + timestamp=datetime(2026, 4, 19, 12, 0, tzinfo=UTC), ), ], ) @@ -392,7 +372,7 @@ def test_show_dlt_surfaces_duration(self, history_project, cli_runner): load_id="load-dur-001", rows_loaded={"widgets": 42}, duration_seconds=3.0, - timestamp=datetime(2026, 4, 19, 12, 0, tzinfo=timezone.utc), + timestamp=datetime(2026, 4, 19, 12, 0, tzinfo=UTC), ), ], ) @@ -414,13 +394,11 @@ def test_show_dbt_invocation_exits_zero(self, history_project, cli_runner): models_run=2, models_errored=0, duration_seconds=3.0, - timestamp=datetime(2026, 4, 19, 12, 0, tzinfo=timezone.utc), + timestamp=datetime(2026, 4, 19, 12, 0, tzinfo=UTC), ), ], ) - result = cli_runner.invoke( - app, ["data", "history", "show", "inv-schema-change"] - ) + result = cli_runner.invoke(app, ["data", "history", "show", "inv-schema-change"]) assert result.exit_code == 0 assert "build" in result.stdout @@ -432,7 +410,7 @@ def test_show_unknown_id_errors(self, history_project, cli_runner): source_id="s", runtime_id="dlt-managed", load_id="load-1", - timestamp=datetime(2026, 4, 19, 10, 0, tzinfo=timezone.utc), + timestamp=datetime(2026, 4, 19, 10, 0, tzinfo=UTC), ), ], ) @@ -447,13 +425,13 @@ def test_show_ambiguous_prefix_errors(self, history_project, cli_runner): source_id="s1", runtime_id="dlt-managed", load_id="shared-prefix-aaa", - timestamp=datetime(2026, 4, 19, 10, 0, tzinfo=timezone.utc), + timestamp=datetime(2026, 4, 19, 10, 0, tzinfo=UTC), ), RunCompleted( source_id="s2", runtime_id="dlt-managed", load_id="shared-prefix-bbb", - timestamp=datetime(2026, 4, 19, 11, 0, tzinfo=timezone.utc), + timestamp=datetime(2026, 4, 19, 11, 0, tzinfo=UTC), ), ], ) @@ -579,9 +557,7 @@ def _setup(self, history_project: Path, monkeypatch): ) return cfg - def test_layer_filter_shows_all_dbt_runs_m1_limitation( - self, history_project, cli_runner, monkeypatch - ): + def test_layer_filter_shows_all_dbt_runs_m1_limitation(self, history_project, cli_runner, monkeypatch): self._setup(history_project, monkeypatch) _seed_events( history_project, @@ -593,7 +569,7 @@ def test_layer_filter_shows_all_dbt_runs_m1_limitation( command="run", target="dev", models_run=1, - timestamp=datetime(2026, 5, 1, 10, 0, tzinfo=timezone.utc), + timestamp=datetime(2026, 5, 1, 10, 0, tzinfo=UTC), ), DbtRunCompleted( event_id="inv-marts00", @@ -602,7 +578,7 @@ def test_layer_filter_shows_all_dbt_runs_m1_limitation( command="build", target="dev", models_run=1, - timestamp=datetime(2026, 5, 1, 11, 0, tzinfo=timezone.utc), + timestamp=datetime(2026, 5, 1, 11, 0, tzinfo=UTC), ), ], ) @@ -611,9 +587,7 @@ def test_layer_filter_shows_all_dbt_runs_m1_limitation( # M1: --layer shows all dbt runs; per-model filtering added in a later milestone. assert "build" in result.stdout - def test_layer_filter_with_no_manifest_errors_out( - self, history_project, cli_runner, monkeypatch - ): + def test_layer_filter_with_no_manifest_errors_out(self, history_project, cli_runner, monkeypatch): # No manifest written — _resolve_layer_models errors before reaching DB _write_tycoon_yml(history_project, with_sources=False) from tycoon.commands import history as history_mod @@ -626,17 +600,13 @@ def test_layer_filter_with_no_manifest_errors_out( assert result.exit_code == 1 assert "No dbt manifest" in (result.stderr or result.output) - def test_invalid_layer_errors_out( - self, history_project, cli_runner, monkeypatch - ): + def test_invalid_layer_errors_out(self, history_project, cli_runner, monkeypatch): self._setup(history_project, monkeypatch) result = cli_runner.invoke(app, ["data", "history", "--layer", "nonsense"]) assert result.exit_code == 1 assert "Invalid --layer" in (result.stderr or result.output) - def test_layer_and_source_are_mutually_exclusive( - self, history_project, cli_runner, monkeypatch - ): + def test_layer_and_source_are_mutually_exclusive(self, history_project, cli_runner, monkeypatch): self._setup(history_project, monkeypatch) result = cli_runner.invoke( app, diff --git a/tests/test_ingestion.py b/tests/test_ingestion.py index 48cff36..41e063e 100644 --- a/tests/test_ingestion.py +++ b/tests/test_ingestion.py @@ -10,7 +10,6 @@ class TestNYCDotPipeline: - def test_module_imports(self): mod = importlib.import_module("tycoon.ingestion.nyc_dot_pipeline") assert mod is not None @@ -35,7 +34,6 @@ def test_has_dlt_resources(self): class TestMTAPipeline: - def test_module_imports(self): mod = importlib.import_module("tycoon.ingestion.mta_pipeline") assert mod is not None @@ -59,7 +57,6 @@ def test_has_dlt_resources(self): class TestMTABusSpeedsPipeline: - def test_module_imports(self): mod = importlib.import_module("tycoon.ingestion.mta_bus_speeds_pipeline") assert mod is not None @@ -122,18 +119,14 @@ def test_normalize_handles_list_resources(self): """Resources arriving as a list (hand-authored) stay a list.""" from tycoon.ingestion.runner import _normalize_rest_api_config - out = _normalize_rest_api_config( - {"base_url": "https://x", "resources": ["a", "b"]} - ) + out = _normalize_rest_api_config({"base_url": "https://x", "resources": ["a", "b"]}) assert out["resources"] == ["a", "b"] def test_normalize_drops_empty_resource_entries(self): """`resources: 'a,,b, ,'` shouldn't produce empty strings.""" from tycoon.ingestion.runner import _normalize_rest_api_config - out = _normalize_rest_api_config( - {"base_url": "https://x", "resources": "a,,b, ,"} - ) + out = _normalize_rest_api_config({"base_url": "https://x", "resources": "a,,b, ,"}) assert out["resources"] == ["a", "b"] def test_build_rest_api_source_constructs_dlt_source(self): @@ -159,7 +152,7 @@ def test_build_rest_api_source_constructs_dlt_source(self): class TestBuildFilesystemSource: """Unit tests for _build_filesystem_source glob-based dispatch.""" - def _make_source_config(self, file_glob: str) -> "SourceConfig": + def _make_source_config(self, file_glob: str) -> SourceConfig: from tycoon.project import SourceConfig return SourceConfig( @@ -236,9 +229,7 @@ def test_native_types_also_in_catalog(self): from tycoon.ingestion.catalog import CATALOG for native in ("rest_api", "filesystem"): - assert native in CATALOG, ( - f"{native} should still appear in the catalog for browsing" - ) + assert native in CATALOG, f"{native} should still appear in the catalog for browsing" # --------------------------------------------------------------------------- @@ -319,9 +310,7 @@ def test_append_keeps_both_batches(self, tmp_path): assert rows == [(1, "a"), (2, "B-updated"), (2, "b"), (3, "c")], rows def test_merge_upserts_on_primary_key(self, tmp_path): - rows = self._run_two_loads( - tmp_path, write_disposition="merge", primary_key="id" - ) + rows = self._run_two_loads(tmp_path, write_disposition="merge", primary_key="id") # id=1 carries from batch 1; id=2 wins from batch 2; id=3 added. assert rows == [(1, "a"), (2, "B-updated"), (3, "c")], rows @@ -368,9 +357,7 @@ def run(self, source): fake_dlt = types.ModuleType("dlt") fake_dlt.pipeline = lambda **kw: _FakePipeline() - fake_dlt.destinations = types.SimpleNamespace( - duckdb=lambda path: ("duckdb", path) - ) + fake_dlt.destinations = types.SimpleNamespace(duckdb=lambda path: ("duckdb", path)) monkeypatch.setitem(sys.modules, "google_sheets", fake_gs) monkeypatch.setitem(sys.modules, "dlt", fake_dlt) @@ -381,9 +368,7 @@ def run(self, source): exec(_SHIMS["google_sheets"], ns) source_config = types.SimpleNamespace(config=config, schema_name="raw_google_sheets") - pipeline, load_info = ns["run_pipeline"]( - "my-sheet", source_config, "/tmp/raw.duckdb", max_records=max_records - ) + pipeline, load_info = ns["run_pipeline"]("my-sheet", source_config, "/tmp/raw.duckdb", max_records=max_records) return captured, fake_source, load_info def test_passes_spreadsheet_and_range_subset(self, monkeypatch): diff --git a/tests/test_init.py b/tests/test_init.py index 921d83e..ed7fa6c 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -2,11 +2,10 @@ from __future__ import annotations - import yaml from tycoon.cli import app -from tycoon.project import load_project +from tycoon.project import SCHEMA_VERSION, load_project from tycoon.scaffolding.templates import list_templates, scaffold_blank_project @@ -19,6 +18,7 @@ def test_init_help_exits_zero(self, cli_runner): assert "--template" in result.stdout assert "--name" in result.stdout assert "--list-templates" in result.stdout + assert "--upgrade" in result.stdout def test_init_appears_in_top_level_help(self, cli_runner): result = cli_runner.invoke(app, ["--help"]) @@ -151,6 +151,28 @@ def test_template_creates_data_dir(self, cli_runner, tmp_path, monkeypatch): cli_runner.invoke(app, ["init", "--template", "nyc-transit"]) assert (tmp_path / "data").is_dir() + def test_template_scaffold_preserves_template_formatting(self, tmp_path, monkeypatch): + """Stamping schema_version must not flatten the template's layout (#185).""" + monkeypatch.chdir(tmp_path) + from tycoon.scaffolding.templates import get_template_path, scaffold_from_template + + template_text = (get_template_path("nyc-transit") / "tycoon.yml").read_text() + scaffold_from_template(tmp_path, "nyc-transit") + scaffolded = (tmp_path / "tycoon.yml").read_text() + + # ruamel may re-indent sequence items, so compare stripped lines — + # the point is that no content, comment, or blank separator is lost. + scaffolded_lines = [line.strip() for line in scaffolded.splitlines()] + for line in template_text.splitlines(): + assert line.strip() in scaffolded_lines + + assert scaffolded.count("\n\n") >= template_text.count("\n\n") + + data = yaml.safe_load(scaffolded) + assert data["schema_version"] == SCHEMA_VERSION + assert data["version"] == "0.1.0" + assert "metadata" in data + class TestInitRefusesOverwrite: """Verify init refuses to overwrite existing tycoon.yml.""" @@ -220,14 +242,10 @@ def test_substitute_params_replaces_braces(self): def test_substitute_params_leaves_unknown_placeholders_alone(self): from tycoon.scaffolding.templates import _substitute_params - out = _substitute_params( - "{{ known }} vs {{ unknown }}", {"known": "yes"} - ) + out = _substitute_params("{{ known }} vs {{ unknown }}", {"known": "yes"}) assert out == "yes vs {{ unknown }}" - def test_scaffold_with_params_substitutes_in_tycoon_yml( - self, cli_runner, tmp_path, monkeypatch - ): + def test_scaffold_with_params_substitutes_in_tycoon_yml(self, cli_runner, tmp_path, monkeypatch): monkeypatch.chdir(tmp_path) result = cli_runner.invoke( app, @@ -265,15 +283,11 @@ def test_template_yml_not_copied_to_target(self, cli_runner, tmp_path, monkeypat ) assert not (tmp_path / "template.yml").exists() - def test_missing_required_param_errors_out_in_noninteractive( - self, cli_runner, tmp_path, monkeypatch - ): + def test_missing_required_param_errors_out_in_noninteractive(self, cli_runner, tmp_path, monkeypatch): """When no --param is supplied and stdin is empty (CliRunner default), typer.prompt fails. We just need it to not silently succeed.""" monkeypatch.chdir(tmp_path) - result = cli_runner.invoke( - app, ["init", "--template", "github-analytics"] - ) + result = cli_runner.invoke(app, ["init", "--template", "github-analytics"]) assert result.exit_code != 0 def test_param_malformed_is_rejected(self, cli_runner, tmp_path, monkeypatch): @@ -284,9 +298,7 @@ def test_param_malformed_is_rejected(self, cli_runner, tmp_path, monkeypatch): ) assert result.exit_code != 0 - def test_unknown_param_is_warned_but_not_fatal( - self, cli_runner, tmp_path, monkeypatch - ): + def test_unknown_param_is_warned_but_not_fatal(self, cli_runner, tmp_path, monkeypatch): monkeypatch.chdir(tmp_path) result = cli_runner.invoke( app, @@ -305,3 +317,44 @@ def test_unknown_param_is_warned_but_not_fatal( assert result.exit_code == 0 # The unknown-param warning goes to stdout via the console helper assert "bogus" in result.stdout.lower() or "unknown parameter" in result.stdout.lower() + + +class TestUpgrade: + """tycoon init --upgrade migrates tycoon.yml to the current schema version.""" + + def test_upgrade_no_tycoon_yml_exits_nonzero(self, cli_runner, tmp_path, monkeypatch): + monkeypatch.chdir(tmp_path) + result = cli_runner.invoke(app, ["init", "--upgrade"]) + assert result.exit_code != 0 + + def test_upgrade_migrates_outdated_yml(self, cli_runner, tmp_path, monkeypatch): + monkeypatch.chdir(tmp_path) + (tmp_path / "tycoon.yml").write_text("name: old-project\nversion: 1.0.0\n") + + result = cli_runner.invoke(app, ["init", "--upgrade"]) + + assert result.exit_code == 0 + p = load_project(tmp_path) + assert p is not None + assert p.schema_version == SCHEMA_VERSION + + def test_upgrade_already_current_reports_up_to_date(self, cli_runner, tmp_path, monkeypatch): + monkeypatch.chdir(tmp_path) + (tmp_path / "tycoon.yml").write_text( + f"name: current\nschema_version: {SCHEMA_VERSION}\n" + "metadata:\n backend: duckdb_file\n path: .tycoon/metadata.duckdb\n" + ) + + result = cli_runner.invoke(app, ["init", "--upgrade"]) + + assert result.exit_code == 0 + assert "up to date" in result.stdout + + def test_upgrade_future_schema_version_exits_nonzero_cleanly(self, cli_runner, tmp_path, monkeypatch): + monkeypatch.chdir(tmp_path) + (tmp_path / "tycoon.yml").write_text(f"name: future\nschema_version: {SCHEMA_VERSION + 1}\n") + + result = cli_runner.invoke(app, ["init", "--upgrade"]) + + assert result.exit_code != 0 + assert "newer than this tycoon supports" in result.output diff --git a/tests/test_layers.py b/tests/test_layers.py index ab34594..61e4c76 100644 --- a/tests/test_layers.py +++ b/tests/test_layers.py @@ -16,7 +16,6 @@ load_manifest, ) - # -- helpers -------------------------------------------------------------------- @@ -49,13 +48,7 @@ class TestClassifyDbtModelsFolderConvention: """Folder convention is the default — no overrides needed.""" def test_staging_folder_maps_to_staging_layer(self) -> None: - m = _manifest( - { - "model.p.stg_orders": _node( - "stg_orders", file_path="models/staging/stg_orders.sql" - ) - } - ) + m = _manifest({"model.p.stg_orders": _node("stg_orders", file_path="models/staging/stg_orders.sql")}) [c] = classify_dbt_models(m) assert c.layer is Layer.STAGING assert c.vendor is Vendor.DBT @@ -63,20 +56,12 @@ def test_staging_folder_maps_to_staging_layer(self) -> None: assert c.identifier == "dbt:model.p.stg_orders" def test_intermediate_folder(self) -> None: - m = _manifest( - { - "model.p.int_x": _node( - "int_x", file_path="models/intermediate/int_x.sql" - ) - } - ) + m = _manifest({"model.p.int_x": _node("int_x", file_path="models/intermediate/int_x.sql")}) [c] = classify_dbt_models(m) assert c.layer is Layer.INTERMEDIATE def test_marts_folder(self) -> None: - m = _manifest( - {"model.p.fct_o": _node("fct_o", file_path="models/marts/fct_o.sql")} - ) + m = _manifest({"model.p.fct_o": _node("fct_o", file_path="models/marts/fct_o.sql")}) [c] = classify_dbt_models(m) assert c.layer is Layer.MART @@ -84,12 +69,8 @@ def test_aliased_folder_names(self) -> None: """`core/` and `published/` are common aliases for marts.""" m = _manifest( { - "model.p.fct_a": _node( - "fct_a", file_path="models/core/fct_a.sql" - ), - "model.p.fct_b": _node( - "fct_b", file_path="models/published/fct_b.sql" - ), + "model.p.fct_a": _node("fct_a", file_path="models/core/fct_a.sql"), + "model.p.fct_b": _node("fct_b", file_path="models/published/fct_b.sql"), } ) layers = {c.name: c.layer for c in classify_dbt_models(m)} @@ -110,9 +91,7 @@ def test_nested_folder_classifies_by_top_level(self) -> None: def test_unknown_folder_is_unclassified(self) -> None: """A model in `models/scratch/` falls through to UNCLASSIFIED.""" - m = _manifest( - {"model.p.x": _node("x", file_path="models/scratch/x.sql")} - ) + m = _manifest({"model.p.x": _node("x", file_path="models/scratch/x.sql")}) [c] = classify_dbt_models(m) assert c.layer is Layer.UNCLASSIFIED @@ -140,13 +119,7 @@ def test_per_model_meta_overrides_folder(self) -> None: assert c.layer is Layer.MART def test_meta_is_case_insensitive(self) -> None: - m = _manifest( - { - "model.p.x": _node( - "x", file_path="", meta={"tycoon_layer": "STAGING"} - ) - } - ) + m = _manifest({"model.p.x": _node("x", file_path="", meta={"tycoon_layer": "STAGING"})}) [c] = classify_dbt_models(m) assert c.layer is Layer.STAGING @@ -169,24 +142,12 @@ class TestClassifyDbtModelsResourceTypes: """Snapshots and seeds get their own layer regardless of folder.""" def test_snapshot_classified_as_snapshot(self) -> None: - m = _manifest( - { - "snapshot.p.snap_x": _node( - "snap_x", rtype="snapshot", file_path="snapshots/snap_x.sql" - ) - } - ) + m = _manifest({"snapshot.p.snap_x": _node("snap_x", rtype="snapshot", file_path="snapshots/snap_x.sql")}) [c] = classify_dbt_models(m) assert c.layer is Layer.SNAPSHOT def test_seed_classified_as_seed(self) -> None: - m = _manifest( - { - "seed.p.zip_codes": _node( - "zip_codes", rtype="seed", file_path="seeds/zip_codes.csv" - ) - } - ) + m = _manifest({"seed.p.zip_codes": _node("zip_codes", rtype="seed", file_path="seeds/zip_codes.csv")}) [c] = classify_dbt_models(m) assert c.layer is Layer.SEED @@ -196,9 +157,7 @@ def test_non_classifiable_resource_types_skipped(self) -> None: { "test.p.t": _node("t", rtype="test"), "exposure.p.e": _node("e", rtype="exposure"), - "model.p.stg_x": _node( - "stg_x", file_path="models/staging/stg_x.sql" - ), + "model.p.stg_x": _node("stg_x", file_path="models/staging/stg_x.sql"), } ) out = classify_dbt_models(m) @@ -222,13 +181,7 @@ def test_returns_none_when_malformed(self, tmp_path: Path) -> None: def test_round_trip(self, tmp_path: Path) -> None: target = tmp_path / "target" target.mkdir() - m = _manifest( - { - "model.p.stg_x": _node( - "stg_x", file_path="models/staging/stg_x.sql" - ) - } - ) + m = _manifest({"model.p.stg_x": _node("stg_x", file_path="models/staging/stg_x.sql")}) (target / "manifest.json").write_text(json.dumps(m)) loaded = load_manifest(tmp_path) assert loaded is not None diff --git a/tests/test_metadata_contract.py b/tests/test_metadata_contract.py index c6be536..78032c0 100644 --- a/tests/test_metadata_contract.py +++ b/tests/test_metadata_contract.py @@ -1,5 +1,7 @@ from __future__ import annotations +from datetime import UTC + import pytest from tycoon.core.events import DbtRunCompleted, RunCompleted, RunStarted @@ -10,6 +12,7 @@ def backend(request, tmp_path): if request.param == "duckdb_file": from tycoon.metadata_backends.duckdb_file import DuckDBFileBackend + with DuckDBFileBackend(tmp_path / ".tycoon" / "metadata.duckdb") as b: yield b @@ -75,29 +78,29 @@ def test_read_nonexistent_snapshot_returns_none(backend): def test_since_filter_tz_aware_excludes_earlier_events(backend): - from datetime import datetime, timezone + from datetime import datetime early = RunStarted(source_id="early", runtime_id="dlt-managed") - early = early.model_copy(update={"timestamp": datetime(2024, 1, 1, 10, 0, tzinfo=timezone.utc)}) + early = early.model_copy(update={"timestamp": datetime(2024, 1, 1, 10, 0, tzinfo=UTC)}) late = RunStarted(source_id="late", runtime_id="dlt-managed") - late = late.model_copy(update={"timestamp": datetime(2024, 1, 1, 12, 0, tzinfo=timezone.utc)}) + late = late.model_copy(update={"timestamp": datetime(2024, 1, 1, 12, 0, tzinfo=UTC)}) backend.append_event(early) backend.append_event(late) - cutoff = datetime(2024, 1, 1, 11, 0, tzinfo=timezone.utc) + cutoff = datetime(2024, 1, 1, 11, 0, tzinfo=UTC) results = backend.query_events(EventFilter(since=cutoff)) assert len(results) == 1 assert results[0].source_id == "late" def test_since_filter_naive_datetime_coerced_to_utc(backend): - from datetime import datetime, timezone + from datetime import datetime early = RunStarted(source_id="early", runtime_id="dlt-managed") - early = early.model_copy(update={"timestamp": datetime(2024, 1, 1, 10, 0, tzinfo=timezone.utc)}) + early = early.model_copy(update={"timestamp": datetime(2024, 1, 1, 10, 0, tzinfo=UTC)}) late = RunStarted(source_id="late", runtime_id="dlt-managed") - late = late.model_copy(update={"timestamp": datetime(2024, 1, 1, 12, 0, tzinfo=timezone.utc)}) + late = late.model_copy(update={"timestamp": datetime(2024, 1, 1, 12, 0, tzinfo=UTC)}) backend.append_event(early) backend.append_event(late) diff --git a/tests/test_notify.py b/tests/test_notify.py index a247311..980d7ad 100644 --- a/tests/test_notify.py +++ b/tests/test_notify.py @@ -7,7 +7,6 @@ from tycoon import notify from tycoon.cli import app - # --------------------------------------------------------------------------- # Payload + URL helpers # --------------------------------------------------------------------------- @@ -125,9 +124,7 @@ def test_empty_field_key_exits_2(self, cli_runner, monkeypatch): def test_success_path_sends(self, cli_runner, monkeypatch): monkeypatch.setenv(notify.WEBHOOK_ENV_VAR, "https://example.com/h") with patch("tycoon.notify.send", return_value=True) as send: - result = cli_runner.invoke( - app, ["notify", "success", "done", "-f", "rows=10"] - ) + result = cli_runner.invoke(app, ["notify", "success", "done", "-f", "rows=10"]) assert result.exit_code == 0, result.output send.assert_called_once() # Fields parsed into a dict. @@ -153,10 +150,7 @@ def _bind(self, tmp_path, monkeypatch): "sources: {}\n" ) (tmp_path / "pyproject.toml").write_text('[project]\nname = "t"\n') - from tycoon.commands import run_all as ra_mod - from tycoon.config import TycoonConfig - - monkeypatch.setattr(ra_mod, "config", TycoonConfig(project_root=tmp_path)) + monkeypatch.chdir(tmp_path) def test_success_emits_notification(self, cli_runner, tmp_path, monkeypatch): self._bind(tmp_path, monkeypatch) @@ -174,17 +168,13 @@ def test_no_notify_flag_sends_nothing(self, cli_runner, tmp_path, monkeypatch): self._bind(tmp_path, monkeypatch) monkeypatch.setenv(notify.WEBHOOK_ENV_VAR, "https://example.com/h") with patch("tycoon.notify.send", return_value=True) as send: - cli_runner.invoke( - app, ["data", "run-all", "--skip-ingest", "--skip-transform"] - ) + cli_runner.invoke(app, ["data", "run-all", "--skip-ingest", "--skip-transform"]) send.assert_not_called() def test_notify_without_webhook_warns_not_crashes(self, cli_runner, tmp_path, monkeypatch): self._bind(tmp_path, monkeypatch) monkeypatch.delenv(notify.WEBHOOK_ENV_VAR, raising=False) - result = cli_runner.invoke( - app, ["data", "run-all", "--skip-ingest", "--skip-transform", "--notify"] - ) + result = cli_runner.invoke(app, ["data", "run-all", "--skip-ingest", "--skip-transform", "--notify"]) # Pipeline still succeeds; just warns about the missing webhook. assert result.exit_code == 0, result.output assert notify.WEBHOOK_ENV_VAR in result.output diff --git a/tests/test_observability_manifest.py b/tests/test_observability_manifest.py index cf0392f..d5bdda1 100644 --- a/tests/test_observability_manifest.py +++ b/tests/test_observability_manifest.py @@ -138,9 +138,7 @@ def test_column_added_removed_and_type_changed(self) -> None: assert types["column_type_changed"]["old_value"] == "INTEGER" assert types["column_type_changed"]["new_value"] == "BIGINT" - def test_first_snapshot_captures_but_records_no_changes( - self, tmp_path: Path - ) -> None: + def test_first_snapshot_captures_but_records_no_changes(self, tmp_path: Path) -> None: """Round-trip: writing a manifest.json with no prior snapshot should insert the snapshot row and zero change rows.""" meta = metadata_db_path(tmp_path) @@ -154,13 +152,9 @@ def test_first_snapshot_captures_but_records_no_changes( con = duckdb.connect(str(meta), read_only=True) try: - snap = con.execute( - "SELECT invocation_id FROM dbt_manifest_snapshots" - ).fetchall() + snap = con.execute("SELECT invocation_id FROM dbt_manifest_snapshots").fetchall() assert snap == [("inv-001",)] - changes = con.execute( - "SELECT count(*) FROM dbt_schema_changes" - ).fetchone() + changes = con.execute("SELECT count(*) FROM dbt_schema_changes").fetchone() assert changes == (0,) finally: con.close() @@ -209,14 +203,12 @@ def test_two_snapshots_produce_diff_rows(self, tmp_path: Path) -> None: types = [ r[0] for r in con.execute( - "SELECT change_type FROM dbt_schema_changes " - "WHERE invocation_id = 'inv-002' ORDER BY change_type" + "SELECT change_type FROM dbt_schema_changes WHERE invocation_id = 'inv-002' ORDER BY change_type" ).fetchall() ] assert types == ["column_added", "column_removed", "sql_changed"] prev = con.execute( - "SELECT DISTINCT prev_invocation_id FROM dbt_schema_changes " - "WHERE invocation_id = 'inv-002'" + "SELECT DISTINCT prev_invocation_id FROM dbt_schema_changes WHERE invocation_id = 'inv-002'" ).fetchall() assert prev == [("inv-001",)] finally: diff --git a/tests/test_observability_trace.py b/tests/test_observability_trace.py index 6eceb35..4ca2ac0 100644 --- a/tests/test_observability_trace.py +++ b/tests/test_observability_trace.py @@ -8,7 +8,7 @@ from __future__ import annotations -from datetime import datetime, timezone +from datetime import UTC, datetime from pathlib import Path import duckdb @@ -29,10 +29,10 @@ def _sample_trace( step_exception: str | None = None, ) -> dict: """Hand-built trace dict in the shape of ``PipelineTrace.asdict()``.""" - started = datetime(2026, 4, 19, 10, 0, 0, tzinfo=timezone.utc) - extract_end = datetime(2026, 4, 19, 10, 0, 5, tzinfo=timezone.utc) - normalize_end = datetime(2026, 4, 19, 10, 0, 6, tzinfo=timezone.utc) - load_end = datetime(2026, 4, 19, 10, 0, 8, tzinfo=timezone.utc) + started = datetime(2026, 4, 19, 10, 0, 0, tzinfo=UTC) + extract_end = datetime(2026, 4, 19, 10, 0, 5, tzinfo=UTC) + normalize_end = datetime(2026, 4, 19, 10, 0, 6, tzinfo=UTC) + load_end = datetime(2026, 4, 19, 10, 0, 8, tzinfo=UTC) load_packages = [ { @@ -105,14 +105,12 @@ def test_inserts_run_steps_and_jobs(self, tmp_path: Path) -> None: con = duckdb.connect(str(meta), read_only=True) try: runs = con.execute( - "SELECT transaction_id, pipeline_name, duration_s, success " - "FROM dlt_trace_runs" + "SELECT transaction_id, pipeline_name, duration_s, success FROM dlt_trace_runs" ).fetchall() assert runs == [("txn-001", "sample_pipeline", 8.0, True)] steps = con.execute( - "SELECT step FROM dlt_trace_steps " - "WHERE transaction_id = 'txn-001' ORDER BY started_at" + "SELECT step FROM dlt_trace_steps WHERE transaction_id = 'txn-001' ORDER BY started_at" ).fetchall() assert [s[0] for s in steps] == ["extract", "normalize", "load"] @@ -139,13 +137,9 @@ def test_is_idempotent_per_transaction_id(self, tmp_path: Path) -> None: con = duckdb.connect(str(meta), read_only=True) try: - count = con.execute( - "SELECT COUNT(*) FROM dlt_trace_runs" - ).fetchone() + count = con.execute("SELECT COUNT(*) FROM dlt_trace_runs").fetchone() assert count == (1,) - job_count = con.execute( - "SELECT COUNT(*) FROM dlt_trace_jobs" - ).fetchone() + job_count = con.execute("SELECT COUNT(*) FROM dlt_trace_jobs").fetchone() assert job_count == (2,) finally: con.close() @@ -175,13 +169,9 @@ def test_records_step_exception(self, tmp_path: Path) -> None: con = duckdb.connect(str(meta), read_only=True) try: - row = con.execute( - "SELECT success, exception FROM dlt_trace_runs" - ).fetchone() + row = con.execute("SELECT success, exception FROM dlt_trace_runs").fetchone() assert row == (False, "429 rate-limited") - exc_step = con.execute( - "SELECT step_exception FROM dlt_trace_steps WHERE step = 'load'" - ).fetchone() + exc_step = con.execute("SELECT step_exception FROM dlt_trace_steps WHERE step = 'load'").fetchone() assert exc_step == ("429 rate-limited",) finally: con.close() diff --git a/tests/test_osi_generator.py b/tests/test_osi_generator.py index 052e6f4..4216194 100644 --- a/tests/test_osi_generator.py +++ b/tests/test_osi_generator.py @@ -32,12 +32,7 @@ def warehouse(tmp_path: Path) -> Path: ")" ) con.execute( - "CREATE TABLE dim_customer (" - " id INTEGER, " - " name VARCHAR, " - " signup_date DATE, " - " lifetime_value DECIMAL(18, 2)" - ")" + "CREATE TABLE dim_customer ( id INTEGER, name VARCHAR, signup_date DATE, lifetime_value DECIMAL(18, 2))" ) # Should be skipped — not a mart prefix. con.execute("CREATE TABLE staging_orders (id INTEGER)") @@ -63,30 +58,20 @@ def test_first_line_is_sentinel(self, warehouse, tmp_path): first_line = out.read_text().splitlines()[0] assert first_line.startswith(SENTINEL_PREFIX) - def test_numeric_measure_columns_have_no_dimension_attribute( - self, warehouse, tmp_path - ): + def test_numeric_measure_columns_have_no_dimension_attribute(self, warehouse, tmp_path): """Conservative dial: DECIMAL → measure-eligible (no dimension key).""" out = tmp_path / "osi.yaml" scaffold_osi(warehouse_db=warehouse, out_path=out, project_name="demo") body = yaml.safe_load(out.read_text()) - mart = next( - d for d in body["semantic_model"][0]["datasets"] - if d["name"] == "mart_orders" - ) + mart = next(d for d in body["semantic_model"][0]["datasets"] if d["name"] == "mart_orders") total = next(f for f in mart["fields"] if f["name"] == "total_amount") assert "dimension" not in total - def test_fk_named_columns_become_dimensions_even_when_numeric( - self, warehouse, tmp_path - ): + def test_fk_named_columns_become_dimensions_even_when_numeric(self, warehouse, tmp_path): out = tmp_path / "osi.yaml" scaffold_osi(warehouse_db=warehouse, out_path=out, project_name="demo") body = yaml.safe_load(out.read_text()) - mart = next( - d for d in body["semantic_model"][0]["datasets"] - if d["name"] == "mart_orders" - ) + mart = next(d for d in body["semantic_model"][0]["datasets"] if d["name"] == "mart_orders") cust_id = next(f for f in mart["fields"] if f["name"] == "customer_id") # FK shape (column ends in `_id`) → dimension, despite INTEGER type. assert cust_id["dimension"] == {} @@ -98,10 +83,7 @@ def test_date_columns_become_time_dimensions(self, warehouse, tmp_path): out = tmp_path / "osi.yaml" scaffold_osi(warehouse_db=warehouse, out_path=out, project_name="demo") body = yaml.safe_load(out.read_text()) - mart = next( - d for d in body["semantic_model"][0]["datasets"] - if d["name"] == "mart_orders" - ) + mart = next(d for d in body["semantic_model"][0]["datasets"] if d["name"] == "mart_orders") order_date = next(f for f in mart["fields"] if f["name"] == "order_date") assert order_date["dimension"] == {"is_time": True} @@ -260,13 +242,7 @@ def warehouse_with_oddly_named_marts(self, tmp_path: Path) -> Path: """ db = tmp_path / "wh.duckdb" con = duckdb.connect(str(db)) - con.execute( - "CREATE TABLE orders_summary (" - " id INTEGER, " - " total DECIMAL(18, 2), " - " order_date DATE" - ")" - ) + con.execute("CREATE TABLE orders_summary ( id INTEGER, total DECIMAL(18, 2), order_date DATE)") con.execute("CREATE TABLE customer_book (id INTEGER, name VARCHAR)") # Should still NOT appear — manifest classifies as staging. con.execute("CREATE TABLE stg_raw_orders (id INTEGER)") @@ -312,9 +288,7 @@ def test_manifest_picks_up_unconventionally_named_marts( ) assert sorted(result.datasets_emitted) == ["customer_book", "orders_summary"] - def test_meta_override_promotes_table_to_mart( - self, tmp_path: Path - ) -> None: + def test_meta_override_promotes_table_to_mart(self, tmp_path: Path) -> None: """A table in models/scratch/ tagged ``meta.tycoon_layer: mart`` is included.""" db = tmp_path / "wh.duckdb" con = duckdb.connect(str(db)) @@ -343,9 +317,7 @@ def test_meta_override_promotes_table_to_mart( ) assert result.datasets_emitted == ["oddball"] - def test_missing_manifest_falls_back_to_prefix( - self, warehouse, tmp_path: Path - ) -> None: + def test_missing_manifest_falls_back_to_prefix(self, warehouse, tmp_path: Path) -> None: """No manifest at the given path → warn + fall back to prefix matching.""" dbt_dir = tmp_path / "dbt" # no manifest written out = tmp_path / "osi.yaml" @@ -359,9 +331,7 @@ def test_missing_manifest_falls_back_to_prefix( assert sorted(result.datasets_emitted) == ["dim_customer", "mart_orders"] assert any("No dbt manifest" in w for w in result.warnings) - def test_no_dbt_project_dir_uses_prefix( - self, warehouse, tmp_path: Path - ) -> None: + def test_no_dbt_project_dir_uses_prefix(self, warehouse, tmp_path: Path) -> None: """Caller passes None → existing v0.1.6 behaviour exactly.""" out = tmp_path / "osi.yaml" result = scaffold_osi( @@ -374,9 +344,7 @@ def test_no_dbt_project_dir_uses_prefix( # No manifest-related warnings when None is passed explicitly. assert not any("manifest" in w.lower() for w in result.warnings) - def test_manifest_excludes_staging_even_with_mart_prefix( - self, tmp_path: Path - ) -> None: + def test_manifest_excludes_staging_even_with_mart_prefix(self, tmp_path: Path) -> None: """A table named ``fct_x`` living in ``models/staging/`` is NOT a mart.""" db = tmp_path / "wh.duckdb" con = duckdb.connect(str(db)) diff --git a/tests/test_project.py b/tests/test_project.py index ad4354f..83d500d 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -2,12 +2,18 @@ from __future__ import annotations - -from tycoon.project import TycoonProject, SourceConfig, DatabaseConfig, load_project, save_project +from tycoon.project import ( + DatabaseConfig, + SCHEMA_VERSION, + SourceConfig, + TycoonProject, + load_project, + migrate_project, + save_project, +) class TestTycoonProject: - def test_default_project(self): p = TycoonProject() assert p.name == "my-project" @@ -36,7 +42,6 @@ def test_custom_project(self): class TestLoadSave: - def test_load_missing_file_returns_none(self, tmp_path): assert load_project(tmp_path) is None @@ -57,12 +62,7 @@ def test_round_trip(self, tmp_path): def test_env_var_interpolation(self, tmp_path, monkeypatch): monkeypatch.setenv("TEST_DB_PATH", "custom/my.duckdb") yml = tmp_path / "tycoon.yml" - yml.write_text( - "name: env-test\n" - "database:\n" - " raw: ${TEST_DB_PATH}\n" - " warehouse: data/wh.duckdb\n" - ) + yml.write_text("name: env-test\ndatabase:\n raw: ${TEST_DB_PATH}\n warehouse: data/wh.duckdb\n") loaded = load_project(tmp_path) assert loaded is not None assert loaded.database.raw == "custom/my.duckdb" @@ -79,9 +79,7 @@ def test_env_var_with_default(self, tmp_path): assert loaded is not None assert loaded.database.raw == "fallback/raw.duckdb" - def test_save_does_not_leak_fivetran_secret_or_flatten_env_ref( - self, tmp_path, monkeypatch - ): + def test_save_does_not_leak_fivetran_secret_or_flatten_env_ref(self, tmp_path, monkeypatch): """save_project must never write the expanded Fivetran secret back to disk, nor mask it to ``**********`` — the hand-authored env-ref block is preserved verbatim (regression for #60).""" @@ -100,10 +98,7 @@ def test_save_does_not_leak_fivetran_secret_or_flatten_env_ref( assert loaded is not None assert loaded.stack.ingestion_metadata is not None # In-memory the secret is expanded for use, but masked in any repr. - assert ( - loaded.stack.ingestion_metadata.api_secret.get_secret_value() - == "super-secret-value" - ) + assert loaded.stack.ingestion_metadata.api_secret.get_secret_value() == "super-secret-value" assert "super-secret-value" not in repr(loaded.stack.ingestion_metadata) # A subsequent save (e.g. triggered by `sources add`) must keep the @@ -116,17 +111,13 @@ def test_save_does_not_leak_fivetran_secret_or_flatten_env_ref( class TestConfigIntegration: - def test_config_reads_tycoon_yml(self, tmp_path): from tycoon.config import TycoonConfig (tmp_path / "pyproject.toml").write_text('[project]\nname = "test"\n') yml = tmp_path / "tycoon.yml" yml.write_text( - "name: integration-test\n" - "database:\n" - " raw: data/custom_raw.duckdb\n" - " warehouse: data/custom_wh.duckdb\n" + "name: integration-test\ndatabase:\n raw: data/custom_raw.duckdb\n warehouse: data/custom_wh.duckdb\n" ) cfg = TycoonConfig(project_root=tmp_path) assert cfg.has_project_file @@ -153,11 +144,166 @@ def test_config_sources_from_yml(self, tmp_path): (tmp_path / "pyproject.toml").write_text('[project]\nname = "test"\n') (tmp_path / "tycoon.yml").write_text( - "name: src-test\n" - "sources:\n" - " my-api:\n" - " type: rest_api\n" - " schema: raw_api\n" + "name: src-test\nsources:\n my-api:\n type: rest_api\n schema: raw_api\n" ) cfg = TycoonConfig(project_root=tmp_path) assert "my-api" in cfg.sources + + +class TestRuntimesAndMetadata: + """T2-1: runtimes: and metadata: fields on TycoonProject.""" + + def test_existing_yml_loads_without_new_fields(self, tmp_path): + """A tycoon.yml with no runtimes/metadata keys must load with defaults.""" + (tmp_path / "tycoon.yml").write_text("name: legacy-project\n") + p = load_project(tmp_path) + assert p is not None + assert p.runtimes == {} + assert p.metadata.backend == "duckdb_file" + assert p.metadata.path == ".tycoon/metadata.duckdb" + + def test_runtimes_field_parses(self, tmp_path): + """runtimes: block with mixed types should parse into RuntimeEntry objects.""" + (tmp_path / "tycoon.yml").write_text( + "name: runtimes-test\n" + "runtimes:\n" + " shopify:\n" + " type: dlt-managed\n" + " custom_pipeline:\n" + " type: dlt-project\n" + " path: pipelines/custom\n" + " fivetran_sync:\n" + " type: fivetran\n" + ) + p = load_project(tmp_path) + assert p is not None + assert set(p.runtimes) == {"shopify", "custom_pipeline", "fivetran_sync"} + assert p.runtimes["shopify"].type == "dlt-managed" + assert p.runtimes["shopify"].path is None + assert p.runtimes["custom_pipeline"].type == "dlt-project" + assert p.runtimes["custom_pipeline"].path == "pipelines/custom" + assert p.runtimes["fivetran_sync"].type == "fivetran" + + def test_metadata_field_parses(self, tmp_path): + """metadata: block with custom values should override defaults.""" + (tmp_path / "tycoon.yml").write_text( + "name: metadata-test\nmetadata:\n backend: duckdb_file\n path: .tycoon/custom_meta.duckdb\n" + ) + p = load_project(tmp_path) + assert p is not None + assert p.metadata.backend == "duckdb_file" + assert p.metadata.path == ".tycoon/custom_meta.duckdb" + + +class TestSchemaVersionEnforcement: + """T2-4: load_project is permissive; save_project preserves schema_version.""" + + def test_future_schema_version_loads_without_raise(self, tmp_path): + """load_project must not raise for a future schema_version — the gate + lives in load_config() so the import-time singleton never trips it.""" + (tmp_path / "tycoon.yml").write_text(f"name: future\nschema_version: {SCHEMA_VERSION + 1}\n") + p = load_project(tmp_path) + assert p is not None + assert p.schema_version == SCHEMA_VERSION + 1 + + def test_save_project_preserves_schema_version(self, tmp_path): + """save_project preserves whatever schema_version is in the model; + only migrate_project (via init --upgrade) advances the stamp.""" + (tmp_path / "tycoon.yml").write_text(f"name: old\nschema_version: {SCHEMA_VERSION}\n") + p = load_project(tmp_path) + assert p is not None + save_project(p, tmp_path) + reloaded = load_project(tmp_path) + assert reloaded is not None + assert reloaded.schema_version == SCHEMA_VERSION + + def test_save_project_does_not_stamp_when_absent(self, tmp_path): + """A project with no schema_version on disk stays unstamped after save_project.""" + (tmp_path / "tycoon.yml").write_text("name: old-project\n") + p = load_project(tmp_path) + assert p is not None + assert p.schema_version is None + save_project(p, tmp_path) + reloaded = load_project(tmp_path) + assert reloaded is not None + assert reloaded.schema_version is None + + +class TestMigrateProject: + """T2-2: migrate_project writes missing keys and is idempotent.""" + + def test_missing_metadata_block_is_written(self, tmp_path): + """A yml without metadata: gets it added and schema_version stamped.""" + (tmp_path / "tycoon.yml").write_text("name: old-project\nversion: 1.4.2\n") + + modified = migrate_project(tmp_path) + + assert modified is True + p = load_project(tmp_path) + assert p is not None + assert p.metadata.backend == "duckdb_file" + assert p.metadata.path == ".tycoon/metadata.duckdb" + assert p.schema_version == SCHEMA_VERSION + + def test_user_version_not_overwritten(self, tmp_path): + """migrate_project never touches the user's version field.""" + (tmp_path / "tycoon.yml").write_text("name: old-project\nversion: 1.4.2\n") + + migrate_project(tmp_path) + p = load_project(tmp_path) + + assert p is not None + assert p.version == "1.4.2" + + def test_comments_preserved(self, tmp_path): + """Comments and blank lines survive the ruamel.yaml round-trip.""" + original = "# Project config\nname: acme\n\n# owner: data-platform@acme.com\nversion: 1.0.0\n" + (tmp_path / "tycoon.yml").write_text(original) + + migrate_project(tmp_path) + result = (tmp_path / "tycoon.yml").read_text() + + assert "# Project config" in result + assert "# owner: data-platform@acme.com" in result + + def test_second_call_is_no_op(self, tmp_path): + """Running migrate_project twice returns False on the second call.""" + (tmp_path / "tycoon.yml").write_text("name: old-project\nversion: 0.1.0\n") + + migrate_project(tmp_path) + modified_again = migrate_project(tmp_path) + + assert modified_again is False + + def test_already_migrated_yml_is_unchanged(self, tmp_path): + """A yml that already has metadata: and schema_version is left alone.""" + (tmp_path / "tycoon.yml").write_text( + f"name: current-project\nschema_version: {SCHEMA_VERSION}\n" + "metadata:\n backend: duckdb_file\n path: .tycoon/metadata.duckdb\n" + ) + + modified = migrate_project(tmp_path) + + assert modified is False + + def test_future_schema_version_raises(self, tmp_path): + """A yml with schema_version newer than SCHEMA_VERSION raises ValueError.""" + import pytest + + (tmp_path / "tycoon.yml").write_text(f"name: future-project\nschema_version: {SCHEMA_VERSION + 1}\n") + + with pytest.raises(ValueError, match="newer than this tycoon supports"): + migrate_project(tmp_path) + + def test_non_integer_schema_version_raises(self, tmp_path): + """A float schema_version (e.g. 0.2 unquoted in YAML) raises ValueError.""" + import pytest + + (tmp_path / "tycoon.yml").write_text("name: bad-project\nschema_version: 0.2\n") + + with pytest.raises(ValueError, match="must be an integer"): + migrate_project(tmp_path) + + def test_missing_file_returns_false(self, tmp_path): + """migrate_project on a directory with no tycoon.yml returns False.""" + assert migrate_project(tmp_path) is False diff --git a/tests/test_project_validation.py b/tests/test_project_validation.py index 7559eb6..ce13890 100644 --- a/tests/test_project_validation.py +++ b/tests/test_project_validation.py @@ -65,9 +65,7 @@ def test_sync_source_uri_expands(self, tmp_path, monkeypatch): monkeypatch.setenv("TYCOON_TEST_CATALOG", "md:prod_catalog") _write_yml( tmp_path, - "sync:\n" - " sources:\n" - " - from: ${TYCOON_TEST_CATALOG}\n", + "sync:\n sources:\n - from: ${TYCOON_TEST_CATALOG}\n", ) loaded = load_project(tmp_path) assert loaded is not None @@ -138,23 +136,16 @@ def test_underscore_and_hyphen_names_accepted(self): ) def test_malicious_source_names_rejected(self, bad_name): with pytest.raises(ValidationError, match="source name"): - TycoonProject( - sources={bad_name: SourceConfig(type="rest_api", schema="raw_x")} - ) + TycoonProject(sources={bad_name: SourceConfig(type="rest_api", schema="raw_x")}) def test_error_names_offending_key(self): with pytest.raises(ValidationError, match=r"\.\./evil"): - TycoonProject( - sources={"../evil": SourceConfig(type="rest_api", schema="raw_x")} - ) + TycoonProject(sources={"../evil": SourceConfig(type="rest_api", schema="raw_x")}) def test_rejected_at_load_time(self, tmp_path): _write_yml( tmp_path, - "sources:\n" - " ../escape:\n" - " type: rest_api\n" - " schema: raw_x\n", + "sources:\n ../escape:\n type: rest_api\n schema: raw_x\n", ) with pytest.raises(ValidationError, match="source name"): load_project(tmp_path) diff --git a/tests/test_quack.py b/tests/test_quack.py index 4d932fb..5b763b4 100644 --- a/tests/test_quack.py +++ b/tests/test_quack.py @@ -134,14 +134,12 @@ def test_extension_available_false_without_binary(self): def test_extension_available_true_on_clean_load(self): ok = MagicMock(returncode=0) - with patch("shutil.which", return_value="/usr/bin/duckdb"), \ - patch("subprocess.run", return_value=ok): + with patch("shutil.which", return_value="/usr/bin/duckdb"), patch("subprocess.run", return_value=ok): assert quack.extension_available() is True def test_extension_available_false_on_load_failure(self): bad = MagicMock(returncode=1) - with patch("shutil.which", return_value="/usr/bin/duckdb"), \ - patch("subprocess.run", return_value=bad): + with patch("shutil.which", return_value="/usr/bin/duckdb"), patch("subprocess.run", return_value=bad): assert quack.extension_available() is False @@ -205,9 +203,11 @@ def test_warehouse_query_routes_through_quack(self, cli_runner, tmp_path, monkey fake_con.description = [("answer",)] fake_con.fetchall.return_value = [(42,)] - with patch("tycoon.quack.load_token", return_value="tok"), \ - patch("tycoon.quack.is_server_running", return_value=True), \ - patch("tycoon.quack.connect", return_value=fake_con) as connect: + with ( + patch("tycoon.quack.load_token", return_value="tok"), + patch("tycoon.quack.is_server_running", return_value=True), + patch("tycoon.quack.connect", return_value=fake_con) as connect, + ): result = cli_runner.invoke(app, ["data", "query", "SELECT 42 AS answer"]) assert result.exit_code == 0, result.output @@ -217,8 +217,7 @@ def test_warehouse_query_routes_through_quack(self, cli_runner, tmp_path, monkey def test_raw_query_never_uses_quack(self, cli_runner, tmp_path, monkeypatch): """--raw is file-based; the Quack server only holds the warehouse.""" self._bind(tmp_path, monkeypatch) - with patch("tycoon.quack.connect") as connect, \ - patch("tycoon.quack.is_server_running", return_value=True): + with patch("tycoon.quack.connect") as connect, patch("tycoon.quack.is_server_running", return_value=True): result = cli_runner.invoke(app, ["data", "query", "SELECT 1", "--raw"]) # raw.duckdb doesn't exist in the tmp project → file-path error, and we # must never have tried to attach via Quack for a --raw query. diff --git a/tests/test_recipe_doctests.py b/tests/test_recipe_doctests.py index af49c97..6193c62 100644 --- a/tests/test_recipe_doctests.py +++ b/tests/test_recipe_doctests.py @@ -33,6 +33,7 @@ [32]: https://github.com/Database-Tycoon/tycoon-cli/issues/32 """ + from __future__ import annotations import os @@ -74,9 +75,7 @@ def _parse_marker_args(raw: str, source: Path, line: int) -> dict[str, str]: out: dict[str, str] = {} for token in raw.split(): if "=" not in token: - raise ValueError( - f"{source}:{line}: tycoon-test marker token missing '=': {token!r}" - ) + raise ValueError(f"{source}:{line}: tycoon-test marker token missing '=': {token!r}") k, v = token.split("=", 1) out[k] = v return out @@ -102,10 +101,7 @@ def _collect_blocks() -> list[RecipeBlock]: args = _parse_marker_args(match.group("args"), doc, line) mode = args.get("mode", "offline") if mode not in _VALID_MODES: - raise ValueError( - f"{doc}:{line}: invalid mode={mode!r} " - f"(expected one of {sorted(_VALID_MODES)})" - ) + raise ValueError(f"{doc}:{line}: invalid mode={mode!r} (expected one of {sorted(_VALID_MODES)})") blocks.append( RecipeBlock( source=doc, @@ -129,9 +125,7 @@ def pytest_configure(config: pytest.Config) -> None: # pragma: no cover - regis @pytest.mark.recipe @pytest.mark.parametrize("block", _BLOCKS, ids=[b.id for b in _BLOCKS]) -def test_recipe_block( - block: RecipeBlock, tmp_path: Path, request: pytest.FixtureRequest -) -> None: +def test_recipe_block(block: RecipeBlock, tmp_path: Path, request: pytest.FixtureRequest) -> None: if block.mode == "online" and not request.config.getoption("--run-online"): pytest.skip("online block; pass --run-online to enable (nightly only)") @@ -150,7 +144,7 @@ def test_recipe_block( # doesn't hang waiting on a port that isn't listening in CI. env["TYCOON_DISABLE_LLM_PROBE"] = "1" - result = subprocess.run( # noqa: S603 - intentional subprocess for CLI surface test + result = subprocess.run( ["bash", "-e", "-o", "pipefail", "-c", block.body], cwd=tmp_path, env=env, diff --git a/tests/test_register.py b/tests/test_register.py index 7652644..4d4df0e 100644 --- a/tests/test_register.py +++ b/tests/test_register.py @@ -33,9 +33,7 @@ def _scaffold_tycoon_project(root: Path, name: str = "proj") -> Path: def _make_dbt_project(dbt_dir: Path, profile: str, duckdb_path: str) -> None: dbt_dir.mkdir(parents=True, exist_ok=True) - (dbt_dir / "dbt_project.yml").write_text( - yaml.dump({"name": profile, "profile": profile, "config-version": 2}) - ) + (dbt_dir / "dbt_project.yml").write_text(yaml.dump({"name": profile, "profile": profile, "config-version": 2})) (dbt_dir / "profiles.yml").write_text( yaml.dump( { @@ -56,9 +54,7 @@ def _make_snowflake_dbt_project( schema: str = "public", ) -> None: dbt_dir.mkdir(parents=True, exist_ok=True) - (dbt_dir / "dbt_project.yml").write_text( - yaml.dump({"name": profile, "profile": profile, "config-version": 2}) - ) + (dbt_dir / "dbt_project.yml").write_text(yaml.dump({"name": profile, "profile": profile, "config-version": 2})) (dbt_dir / "profiles.yml").write_text( yaml.dump( { @@ -89,9 +85,7 @@ def _make_bigquery_dbt_project( dataset: str = "analytics", ) -> None: dbt_dir.mkdir(parents=True, exist_ok=True) - (dbt_dir / "dbt_project.yml").write_text( - yaml.dump({"name": profile, "profile": profile, "config-version": 2}) - ) + (dbt_dir / "dbt_project.yml").write_text(yaml.dump({"name": profile, "profile": profile, "config-version": 2})) (dbt_dir / "profiles.yml").write_text( yaml.dump( { @@ -122,7 +116,6 @@ def _reload_config(monkeypatch, project_root: Path) -> None: class TestRegisterDbt: - def test_register_dbt_by_local_path(self, cli_runner, tmp_path, monkeypatch): project = tmp_path / "proj" yml = _scaffold_tycoon_project(project, "proj") @@ -226,10 +219,15 @@ def test_register_dbt_persists_profile_flags(self, cli_runner, tmp_path, monkeyp result = cli_runner.invoke( app, [ - "register", "dbt", str(dbt_dir), - "--profiles-dir", str(external_profiles), - "--profile", "ci_profile", - "--target", "prod", + "register", + "dbt", + str(dbt_dir), + "--profiles-dir", + str(external_profiles), + "--profile", + "ci_profile", + "--target", + "prod", ], input="\n", ) @@ -242,9 +240,7 @@ def test_register_dbt_persists_profile_flags(self, cli_runner, tmp_path, monkeyp assert data["dbt_profile"] == "ci_profile" assert data["dbt_target"] == "prod" - def test_register_dbt_drops_stale_profile_keys_when_unspecified( - self, cli_runner, tmp_path, monkeypatch - ): + def test_register_dbt_drops_stale_profile_keys_when_unspecified(self, cli_runner, tmp_path, monkeypatch): """Re-registering without --profile should drop a previously-persisted value.""" project = tmp_path / "proj" yml = _scaffold_tycoon_project(project, "proj") @@ -267,9 +263,7 @@ def test_register_dbt_drops_stale_profile_keys_when_unspecified( assert "dbt_profile" not in final assert "dbt_target" not in final - def test_register_dbt_refuses_missing_profiles_dir( - self, cli_runner, tmp_path, monkeypatch - ): + def test_register_dbt_refuses_missing_profiles_dir(self, cli_runner, tmp_path, monkeypatch): project = tmp_path / "proj" _scaffold_tycoon_project(project, "proj") dbt_dir = tmp_path / "proj-dbt" @@ -280,8 +274,11 @@ def test_register_dbt_refuses_missing_profiles_dir( result = cli_runner.invoke( app, [ - "register", "dbt", str(dbt_dir), - "--profiles-dir", str(tmp_path / "does-not-exist"), + "register", + "dbt", + str(dbt_dir), + "--profiles-dir", + str(tmp_path / "does-not-exist"), ], ) assert result.exit_code != 0 @@ -310,9 +307,7 @@ class TestRegisterDbtCreate: in one shot, the recovery path for users who picked Skip on the dbt prompt during `tycoon init`.""" - def test_create_bootstraps_at_default_sibling_path( - self, cli_runner, tmp_path, monkeypatch - ): + def test_create_bootstraps_at_default_sibling_path(self, cli_runner, tmp_path, monkeypatch): project = tmp_path / "proj" yml = _scaffold_tycoon_project(project, "proj") @@ -332,25 +327,19 @@ def test_create_bootstraps_at_default_sibling_path( # --create marks the project as tycoon-managed (we own it) assert data["stack"]["transformation_managed"] is True - def test_create_with_explicit_path_overrides_default( - self, cli_runner, tmp_path, monkeypatch - ): + def test_create_with_explicit_path_overrides_default(self, cli_runner, tmp_path, monkeypatch): project = tmp_path / "proj" _scaffold_tycoon_project(project, "proj") custom = tmp_path / "custom-dbt-location" monkeypatch.chdir(project) _reload_config(monkeypatch, project) - result = cli_runner.invoke( - app, ["register", "dbt", "--create", str(custom)] - ) + result = cli_runner.invoke(app, ["register", "dbt", "--create", str(custom)]) assert result.exit_code == 0, result.stdout assert (custom / "dbt_project.yml").exists() assert not (tmp_path / "proj-dbt").exists() - def test_create_refuses_when_dbt_project_yml_already_exists( - self, cli_runner, tmp_path, monkeypatch - ): + def test_create_refuses_when_dbt_project_yml_already_exists(self, cli_runner, tmp_path, monkeypatch): project = tmp_path / "proj" _scaffold_tycoon_project(project, "proj") existing = tmp_path / "proj-dbt" @@ -362,13 +351,9 @@ def test_create_refuses_when_dbt_project_yml_already_exists( assert result.exit_code != 0 # Existing project survived — sentinel: dbt_project_dir not registered - assert "dbt_project_dir" not in yaml.safe_load( - (project / "tycoon.yml").read_text() - ) + assert "dbt_project_dir" not in yaml.safe_load((project / "tycoon.yml").read_text()) - def test_create_motherduck_warehouse_writes_md_profile( - self, cli_runner, tmp_path, monkeypatch - ): + def test_create_motherduck_warehouse_writes_md_profile(self, cli_runner, tmp_path, monkeypatch): project = tmp_path / "proj" _scaffold_tycoon_project(project, "proj") # Flip the warehouse type to motherduck before registering. @@ -390,9 +375,7 @@ def test_create_motherduck_warehouse_writes_md_profile( dev = profile_block["outputs"]["dev"] assert dev["path"] == "md:proj" - def test_create_bails_on_unsupported_warehouse( - self, cli_runner, tmp_path, monkeypatch - ): + def test_create_bails_on_unsupported_warehouse(self, cli_runner, tmp_path, monkeypatch): project = tmp_path / "proj" _scaffold_tycoon_project(project, "proj") yml_data = yaml.safe_load((project / "tycoon.yml").read_text()) @@ -406,9 +389,7 @@ def test_create_bails_on_unsupported_warehouse( # No sibling dbt project should have been written assert not (tmp_path / "proj-dbt").exists() - def test_register_without_create_or_source_errors( - self, cli_runner, tmp_path, monkeypatch - ): + def test_register_without_create_or_source_errors(self, cli_runner, tmp_path, monkeypatch): project = tmp_path / "proj" _scaffold_tycoon_project(project, "proj") monkeypatch.chdir(project) @@ -427,9 +408,7 @@ class TestRegisterDbtExternalE2E: by the transform runner. """ - def test_external_dbt_project_runs_with_registered_profile( - self, cli_runner, tmp_path, monkeypatch - ): + def test_external_dbt_project_runs_with_registered_profile(self, cli_runner, tmp_path, monkeypatch): import duckdb # 1. Standalone dbt project at /external_dbt with a custom @@ -449,9 +428,7 @@ def test_external_dbt_project_runs_with_registered_profile( ) models_dir = external_dbt / "models" models_dir.mkdir() - (models_dir / "hello.sql").write_text( - "{{ config(materialized='table') }}\nselect 42 as the_answer\n" - ) + (models_dir / "hello.sql").write_text("{{ config(materialized='table') }}\nselect 42 as the_answer\n") # 2. Profiles.yml in a sibling 'ci-profiles/' dir (proves # --profiles-dir is honored, not the default lookup). @@ -482,10 +459,15 @@ def test_external_dbt_project_runs_with_registered_profile( result = cli_runner.invoke( app, [ - "register", "dbt", str(external_dbt), - "--profiles-dir", str(external_profiles), - "--profile", "ci_profile", - "--target", "prod", + "register", + "dbt", + str(external_dbt), + "--profiles-dir", + str(external_profiles), + "--profile", + "ci_profile", + "--target", + "prod", ], input="\n", ) @@ -494,9 +476,8 @@ def test_external_dbt_project_runs_with_registered_profile( # 4. Transform commands have their own config singleton — rebind. from tycoon.commands import transform as transform_mod from tycoon.config import TycoonConfig - monkeypatch.setattr( - transform_mod, "config", TycoonConfig(project_root=project) - ) + + monkeypatch.setattr(transform_mod, "config", TycoonConfig(project_root=project)) # 5. Run transform — proves the persisted profile/target/profiles_dir # are picked up by `_run_dbt`. @@ -577,17 +558,13 @@ def test_unknown_adapter_surfaces_raw_type(self, tmp_path): dbt_dir = tmp_path / "dbt" dbt_dir.mkdir() - (dbt_dir / "dbt_project.yml").write_text( - yaml.dump({"name": "p", "profile": "p", "config-version": 2}) - ) + (dbt_dir / "dbt_project.yml").write_text(yaml.dump({"name": "p", "profile": "p", "config-version": 2})) (dbt_dir / "profiles.yml").write_text( yaml.dump( { "p": { "target": "dev", - "outputs": { - "dev": {"type": "databricks", "host": "x.databricks.com"} - }, + "outputs": {"dev": {"type": "databricks", "host": "x.databricks.com"}}, } } ) @@ -631,9 +608,7 @@ def test_explicit_profile_name_override(self, tmp_path): dbt_dir = tmp_path / "dbt" dbt_dir.mkdir() # dbt_project.yml says profile=primary - (dbt_dir / "dbt_project.yml").write_text( - yaml.dump({"name": "p", "profile": "primary", "config-version": 2}) - ) + (dbt_dir / "dbt_project.yml").write_text(yaml.dump({"name": "p", "profile": "primary", "config-version": 2})) # profiles.yml has BOTH primary and secondary (dbt_dir / "profiles.yml").write_text( yaml.dump( @@ -660,9 +635,7 @@ def test_explicit_target_name_override(self, tmp_path): dbt_dir = tmp_path / "dbt" dbt_dir.mkdir() - (dbt_dir / "dbt_project.yml").write_text( - yaml.dump({"name": "p", "profile": "p", "config-version": 2}) - ) + (dbt_dir / "dbt_project.yml").write_text(yaml.dump({"name": "p", "profile": "p", "config-version": 2})) (dbt_dir / "profiles.yml").write_text( yaml.dump( { @@ -694,9 +667,7 @@ class TestRegisterDbtCloudAlignment: """v0.1.3 theme #2: Snowflake / BigQuery alignment offers to update ``stack.warehouse`` but leaves ``database.warehouse`` alone.""" - def test_snowflake_alignment_updates_stack_warehouse( - self, cli_runner, tmp_path, monkeypatch - ): + def test_snowflake_alignment_updates_stack_warehouse(self, cli_runner, tmp_path, monkeypatch): project = tmp_path / "proj" _scaffold_tycoon_project(project, "proj") @@ -714,9 +685,7 @@ def test_snowflake_alignment_updates_stack_warehouse( # database.warehouse untouched — still the local DuckDB default assert data["database"]["warehouse"] == "data/warehouse.duckdb" - def test_bigquery_alignment_updates_stack_warehouse( - self, cli_runner, tmp_path, monkeypatch - ): + def test_bigquery_alignment_updates_stack_warehouse(self, cli_runner, tmp_path, monkeypatch): project = tmp_path / "proj" _scaffold_tycoon_project(project, "proj") @@ -733,9 +702,7 @@ def test_bigquery_alignment_updates_stack_warehouse( assert data["stack"]["warehouse"] == "bigquery" assert data["database"]["warehouse"] == "data/warehouse.duckdb" - def test_snowflake_no_change_if_types_already_aligned( - self, cli_runner, tmp_path, monkeypatch - ): + def test_snowflake_no_change_if_types_already_aligned(self, cli_runner, tmp_path, monkeypatch): """When stack.warehouse is already snowflake, no prompt — the value stays.""" project = tmp_path / "proj" yml_path = _scaffold_tycoon_project(project, "proj") @@ -756,9 +723,7 @@ def test_snowflake_no_change_if_types_already_aligned( data = yaml.safe_load((project / "tycoon.yml").read_text()) assert data["stack"]["warehouse"] == "snowflake" - def test_snowflake_account_mismatch_warns( - self, cli_runner, tmp_path, monkeypatch - ): + def test_snowflake_account_mismatch_warns(self, cli_runner, tmp_path, monkeypatch): """When tycoon.yml records a warehouse_connection.account that differs from the dbt profile's account, we warn (non-fatal).""" project = tmp_path / "proj" @@ -780,7 +745,6 @@ def test_snowflake_account_mismatch_warns( class TestRegisterWarehouse: - def test_register_warehouse_local(self, cli_runner, tmp_path, monkeypatch): project = tmp_path / "proj" yml = _scaffold_tycoon_project(project, "proj") @@ -833,9 +797,12 @@ def test_register_warehouse_non_interactive_local(self, cli_runner, tmp_path, mo result = cli_runner.invoke( app, [ - "register", "warehouse", - "--type", "duckdb", - "--path", "data/custom.duckdb", + "register", + "warehouse", + "--type", + "duckdb", + "--path", + "data/custom.duckdb", "--no-prompt", ], ) @@ -856,9 +823,12 @@ def test_register_warehouse_non_interactive_motherduck(self, cli_runner, tmp_pat result = cli_runner.invoke( app, [ - "register", "warehouse", - "--type", "motherduck", - "--catalog", "my_demo", + "register", + "warehouse", + "--type", + "motherduck", + "--catalog", + "my_demo", "--no-prompt", ], ) @@ -875,9 +845,7 @@ def test_register_warehouse_no_prompt_requires_type(self, cli_runner, tmp_path, result = cli_runner.invoke(app, ["register", "warehouse", "--no-prompt"]) assert result.exit_code != 0 - def test_register_warehouse_no_prompt_motherduck_requires_catalog( - self, cli_runner, tmp_path, monkeypatch - ): + def test_register_warehouse_no_prompt_motherduck_requires_catalog(self, cli_runner, tmp_path, monkeypatch): project = tmp_path / "proj" yml = _scaffold_tycoon_project(project, "proj") data = yaml.safe_load(yml.read_text()) @@ -904,9 +872,12 @@ def test_register_warehouse_force_overrides_existing(self, cli_runner, tmp_path, result = cli_runner.invoke( app, [ - "register", "warehouse", - "--type", "duckdb", - "--path", "data/new.duckdb", + "register", + "warehouse", + "--type", + "duckdb", + "--path", + "data/new.duckdb", "--force", "--no-prompt", ], @@ -943,7 +914,6 @@ def test_register_warehouse_prompts_on_overwrite(self, cli_runner, tmp_path, mon class TestRegisterRill: - def test_register_rill_by_local_path(self, cli_runner, tmp_path, monkeypatch): project = tmp_path / "proj" yml = _scaffold_tycoon_project(project, "proj") diff --git a/tests/test_schedule.py b/tests/test_schedule.py index e1445d6..820b546 100644 --- a/tests/test_schedule.py +++ b/tests/test_schedule.py @@ -72,9 +72,7 @@ def test_launchd_hourly_interval(self, tmp_path): assert pl["StartCalendarInterval"] == {"Minute": 30} def test_launchd_weekly_interval(self, tmp_path): - pl = plistlib.loads( - sched.render_launchd_plist(_spec(tmp_path, cadence="weekly", weekday=3), home=tmp_path) - ) + pl = plistlib.loads(sched.render_launchd_plist(_spec(tmp_path, cadence="weekly", weekday=3), home=tmp_path)) assert pl["StartCalendarInterval"]["Weekday"] == 3 def test_systemd_oncalendar_daily(self, tmp_path): @@ -119,7 +117,6 @@ def test_arg_with_backslash_escaped(self, tmp_path): line = self._exec_start_line(tmp_path, ["a\\b"]) assert '"a\\\\b"' in line - def test_dollar_doubled_against_env_expansion(self): from tycoon.schedule import _systemd_quote @@ -138,8 +135,10 @@ class TestControlCharRejection: @pytest.mark.parametrize("evil", ["run\nExecStartPre=/bin/evil", "run\rx", "run\0x"]) def test_control_char_in_arg_raises_before_writing(self, tmp_path, plat, evil): spec = _spec(tmp_path, args=["data", evil]) - with patch("tycoon.schedule.current_platform", return_value=plat), \ - patch("tycoon.schedule.subprocess.run", return_value=MagicMock(returncode=0)) as run: + with ( + patch("tycoon.schedule.current_platform", return_value=plat), + patch("tycoon.schedule.subprocess.run", return_value=MagicMock(returncode=0)) as run, + ): with pytest.raises(sched.ScheduleError, match="must not contain"): sched.add(spec, home=tmp_path) run.assert_not_called() @@ -148,8 +147,10 @@ def test_control_char_in_arg_raises_before_writing(self, tmp_path, plat, evil): def test_newline_in_project_root_raises(self, tmp_path): spec = _spec(tmp_path, project_root=tmp_path / "pro\nj\nExecStart=/bin/evil") - with patch("tycoon.schedule.current_platform", return_value="linux"), \ - patch("tycoon.schedule.subprocess.run", return_value=MagicMock(returncode=0)): + with ( + patch("tycoon.schedule.current_platform", return_value="linux"), + patch("tycoon.schedule.subprocess.run", return_value=MagicMock(returncode=0)), + ): with pytest.raises(sched.ScheduleError, match="must not contain"): sched.add(spec, home=tmp_path) assert not sched.systemd_user_dir(tmp_path).exists() @@ -169,8 +170,10 @@ def test_launchd_rendering_untouched_by_quoting(self, tmp_path): class TestLaunchdBackend: def test_add_writes_plist_and_loads(self, tmp_path): - with patch("tycoon.schedule.current_platform", return_value="darwin"), \ - patch("tycoon.schedule.subprocess.run", return_value=MagicMock(returncode=0)) as run: + with ( + patch("tycoon.schedule.current_platform", return_value="darwin"), + patch("tycoon.schedule.subprocess.run", return_value=MagicMock(returncode=0)) as run, + ): msg = sched.add(_spec(tmp_path), home=tmp_path) plist = sched.launchd_plist_path("daily-refresh", tmp_path) @@ -182,8 +185,10 @@ def test_add_writes_plist_and_loads(self, tmp_path): assert loaded def test_list_globs_managed_plists(self, tmp_path): - with patch("tycoon.schedule.current_platform", return_value="darwin"), \ - patch("tycoon.schedule.subprocess.run", return_value=MagicMock(returncode=0)): + with ( + patch("tycoon.schedule.current_platform", return_value="darwin"), + patch("tycoon.schedule.subprocess.run", return_value=MagicMock(returncode=0)), + ): sched.add(_spec(tmp_path, name="a"), home=tmp_path) sched.add(_spec(tmp_path, name="b"), home=tmp_path) # An unrelated plist must not show up. @@ -192,8 +197,10 @@ def test_list_globs_managed_plists(self, tmp_path): assert names == ["a", "b"] def test_remove_unloads_and_deletes(self, tmp_path): - with patch("tycoon.schedule.current_platform", return_value="darwin"), \ - patch("tycoon.schedule.subprocess.run", return_value=MagicMock(returncode=0)): + with ( + patch("tycoon.schedule.current_platform", return_value="darwin"), + patch("tycoon.schedule.subprocess.run", return_value=MagicMock(returncode=0)), + ): sched.add(_spec(tmp_path), home=tmp_path) plist = sched.launchd_plist_path("daily-refresh", tmp_path) assert plist.exists() @@ -206,8 +213,10 @@ def test_remove_missing_raises(self, tmp_path): sched.remove("ghost", home=tmp_path) def test_add_scheduler_failure_raises(self, tmp_path): - with patch("tycoon.schedule.current_platform", return_value="darwin"), \ - patch("tycoon.schedule.subprocess.run", return_value=MagicMock(returncode=1, stderr="boom", stdout="")): + with ( + patch("tycoon.schedule.current_platform", return_value="darwin"), + patch("tycoon.schedule.subprocess.run", return_value=MagicMock(returncode=1, stderr="boom", stdout="")), + ): with pytest.raises(sched.ScheduleError): sched.add(_spec(tmp_path), home=tmp_path) @@ -219,8 +228,10 @@ def test_add_scheduler_failure_raises(self, tmp_path): class TestSystemdBackend: def test_add_writes_timer_and_service(self, tmp_path): - with patch("tycoon.schedule.current_platform", return_value="linux"), \ - patch("tycoon.schedule.subprocess.run", return_value=MagicMock(returncode=0)) as run: + with ( + patch("tycoon.schedule.current_platform", return_value="linux"), + patch("tycoon.schedule.subprocess.run", return_value=MagicMock(returncode=0)) as run, + ): sched.add(_spec(tmp_path), home=tmp_path) unit_dir = sched.systemd_user_dir(tmp_path) assert (unit_dir / "tycoon-daily-refresh.timer").exists() @@ -229,8 +240,10 @@ def test_add_writes_timer_and_service(self, tmp_path): assert enabled def test_list_and_remove_round_trip(self, tmp_path): - with patch("tycoon.schedule.current_platform", return_value="linux"), \ - patch("tycoon.schedule.subprocess.run", return_value=MagicMock(returncode=0)): + with ( + patch("tycoon.schedule.current_platform", return_value="linux"), + patch("tycoon.schedule.subprocess.run", return_value=MagicMock(returncode=0)), + ): sched.add(_spec(tmp_path), home=tmp_path) assert sched.list_schedules(home=tmp_path) == ["daily-refresh"] sched.remove("daily-refresh", home=tmp_path) @@ -290,8 +303,10 @@ def test_add_bad_time_exits_2(self, cli_runner, tmp_path, monkeypatch): def test_add_happy_path(self, cli_runner, tmp_path, monkeypatch): self._bind(tmp_path, monkeypatch) - with patch("tycoon.schedule.list_schedules", return_value=[]), \ - patch("tycoon.schedule.add", return_value="Scheduled 'nightly' via launchd.") as add: + with ( + patch("tycoon.schedule.list_schedules", return_value=[]), + patch("tycoon.schedule.add", return_value="Scheduled 'nightly' via launchd.") as add, + ): result = cli_runner.invoke( app, ["schedule", "add", "nightly", "--command", "data run-all", "--at", "02:15", "--notify"], @@ -356,14 +371,14 @@ def fake_add(spec, **_kwargs): captured["args"] = spec.args return "installed" - with patch("tycoon.commands.schedule.config") as cfg, \ - patch("tycoon.commands.schedule.sched.list_schedules", return_value=[]), \ - patch("tycoon.commands.schedule.sched.add", side_effect=fake_add): + with ( + patch("tycoon.commands.schedule.config") as cfg, + patch("tycoon.commands.schedule.sched.list_schedules", return_value=[]), + patch("tycoon.commands.schedule.sched.add", side_effect=fake_add), + ): cfg.has_project_file = True cfg.root = tmp_path - result = cli_runner.invoke( - app, ["schedule", "add", "daily-refresh", "--command", "tycoon data run-all"] - ) + result = cli_runner.invoke(app, ["schedule", "add", "daily-refresh", "--command", "tycoon data run-all"]) assert result.exit_code == 0, result.output assert captured["args"] == ["data", "run-all"] @@ -371,14 +386,14 @@ def fake_add(spec, **_kwargs): def test_empty_command_after_strip_exits_2(self, cli_runner, tmp_path): # "--command tycoon" strips to [] — must be rejected, not scheduled as # a bare `tycoon` that just prints help on a timer. - with patch("tycoon.commands.schedule.config") as cfg, \ - patch("tycoon.commands.schedule.sched.list_schedules", return_value=[]), \ - patch("tycoon.commands.schedule.sched.add") as add: + with ( + patch("tycoon.commands.schedule.config") as cfg, + patch("tycoon.commands.schedule.sched.list_schedules", return_value=[]), + patch("tycoon.commands.schedule.sched.add") as add, + ): cfg.has_project_file = True cfg.root = tmp_path - result = cli_runner.invoke( - app, ["schedule", "add", "daily-refresh", "--command", "tycoon"] - ) + result = cli_runner.invoke(app, ["schedule", "add", "daily-refresh", "--command", "tycoon"]) assert result.exit_code == 2, result.output assert "cannot be empty" in (result.stderr or result.output) add.assert_not_called() @@ -390,14 +405,14 @@ def fake_add(spec, **_kwargs): captured["args"] = spec.args return "installed" - with patch("tycoon.commands.schedule.config") as cfg, \ - patch("tycoon.commands.schedule.sched.list_schedules", return_value=[]), \ - patch("tycoon.commands.schedule.sched.add", side_effect=fake_add): + with ( + patch("tycoon.commands.schedule.config") as cfg, + patch("tycoon.commands.schedule.sched.list_schedules", return_value=[]), + patch("tycoon.commands.schedule.sched.add", side_effect=fake_add), + ): cfg.has_project_file = True cfg.root = tmp_path - result = cli_runner.invoke( - app, ["schedule", "add", "daily-refresh", "--command", "data run-all"] - ) + result = cli_runner.invoke(app, ["schedule", "add", "daily-refresh", "--command", "data run-all"]) assert result.exit_code == 0, result.output assert captured["args"] == ["data", "run-all"] diff --git a/tests/test_setup.py b/tests/test_setup.py index 72e17b7..92daa27 100644 --- a/tests/test_setup.py +++ b/tests/test_setup.py @@ -12,8 +12,8 @@ import pytest -from tycoon.cli import app from tycoon import venv as venv_mod +from tycoon.cli import app def _ok(returncode: int = 0, stdout: str = "", stderr: str = "") -> MagicMock: @@ -66,8 +66,7 @@ def test_rejects_unsupported_version_without_shelling_out(self, tmp_path): run.assert_not_called() def test_errors_when_uv_missing(self, tmp_path): - with patch("tycoon.venv.find_uv", return_value=None), \ - patch("tycoon.venv.subprocess.run") as run: + with patch("tycoon.venv.find_uv", return_value=None), patch("tycoon.venv.subprocess.run") as run: result = venv_mod.create_venv(tmp_path, "3.13") assert result.ok is False assert "uv is not installed" in result.message @@ -75,16 +74,17 @@ def test_errors_when_uv_missing(self, tmp_path): def test_refuses_to_clobber_existing_venv(self, tmp_path): (tmp_path / ".venv").mkdir() - with patch("tycoon.venv.find_uv", return_value="/usr/bin/uv"), \ - patch("tycoon.venv.subprocess.run") as run: + with patch("tycoon.venv.find_uv", return_value="/usr/bin/uv"), patch("tycoon.venv.subprocess.run") as run: result = venv_mod.create_venv(tmp_path, "3.13") assert result.ok is False assert "--force" in result.message run.assert_not_called() def test_happy_path_creates_pins_and_installs(self, tmp_path): - with patch("tycoon.venv.find_uv", return_value="/usr/bin/uv"), \ - patch("tycoon.venv.subprocess.run", side_effect=[_ok(), _ok()]) as run: + with ( + patch("tycoon.venv.find_uv", return_value="/usr/bin/uv"), + patch("tycoon.venv.subprocess.run", side_effect=[_ok(), _ok()]) as run, + ): result = venv_mod.create_venv(tmp_path, "3.13") assert result.ok is True, result.message @@ -101,15 +101,19 @@ def test_happy_path_creates_pins_and_installs(self, tmp_path): assert "database-tycoon" in install_cmd def test_no_install_skips_pip(self, tmp_path): - with patch("tycoon.venv.find_uv", return_value="/usr/bin/uv"), \ - patch("tycoon.venv.subprocess.run", side_effect=[_ok()]) as run: + with ( + patch("tycoon.venv.find_uv", return_value="/usr/bin/uv"), + patch("tycoon.venv.subprocess.run", side_effect=[_ok()]) as run, + ): result = venv_mod.create_venv(tmp_path, "3.13", install_spec=None) assert result.ok is True assert run.call_count == 1 # only `uv venv` def test_uv_venv_failure_surfaces_stderr(self, tmp_path): - with patch("tycoon.venv.find_uv", return_value="/usr/bin/uv"), \ - patch("tycoon.venv.subprocess.run", side_effect=[_ok(1, stderr="no such python")]): + with ( + patch("tycoon.venv.find_uv", return_value="/usr/bin/uv"), + patch("tycoon.venv.subprocess.run", side_effect=[_ok(1, stderr="no such python")]), + ): result = venv_mod.create_venv(tmp_path, "3.13") assert result.ok is False assert "no such python" in result.message @@ -117,8 +121,10 @@ def test_uv_venv_failure_surfaces_stderr(self, tmp_path): assert not (tmp_path / ".python-version").exists() def test_install_failure_keeps_venv_but_reports(self, tmp_path): - with patch("tycoon.venv.find_uv", return_value="/usr/bin/uv"), \ - patch("tycoon.venv.subprocess.run", side_effect=[_ok(), _ok(1, stderr="resolution impossible")]): + with ( + patch("tycoon.venv.find_uv", return_value="/usr/bin/uv"), + patch("tycoon.venv.subprocess.run", side_effect=[_ok(), _ok(1, stderr="resolution impossible")]), + ): result = venv_mod.create_venv(tmp_path, "3.13") assert result.ok is False assert "installing" in result.message @@ -128,8 +134,10 @@ def test_install_failure_keeps_venv_but_reports(self, tmp_path): def test_force_recreates_existing(self, tmp_path): (tmp_path / ".venv").mkdir() - with patch("tycoon.venv.find_uv", return_value="/usr/bin/uv"), \ - patch("tycoon.venv.subprocess.run", side_effect=[_ok(), _ok()]) as run: + with ( + patch("tycoon.venv.find_uv", return_value="/usr/bin/uv"), + patch("tycoon.venv.subprocess.run", side_effect=[_ok(), _ok()]) as run, + ): result = venv_mod.create_venv(tmp_path, "3.13", force=True) assert result.ok is True assert run.call_count == 2 @@ -172,8 +180,10 @@ def test_errors_when_uv_missing(self, cli_runner, tmp_path, monkeypatch): def test_happy_path_invokes_create_venv(self, cli_runner, tmp_path, monkeypatch): self._bind(tmp_path, monkeypatch) fake = venv_mod.VenvResult(ok=True, message="Created .venv", venv_path=tmp_path / ".venv") - with patch("tycoon.commands.setup.find_uv", return_value="/usr/bin/uv"), \ - patch("tycoon.commands.setup.create_venv", return_value=fake) as cv: + with ( + patch("tycoon.commands.setup.find_uv", return_value="/usr/bin/uv"), + patch("tycoon.commands.setup.create_venv", return_value=fake) as cv, + ): result = cli_runner.invoke(app, ["setup", "--no-prompt", "--python", "3.12"]) assert result.exit_code == 0, result.output cv.assert_called_once() @@ -184,16 +194,20 @@ def test_happy_path_invokes_create_venv(self, cli_runner, tmp_path, monkeypatch) def test_no_install_threads_through(self, cli_runner, tmp_path, monkeypatch): self._bind(tmp_path, monkeypatch) fake = venv_mod.VenvResult(ok=True, message="ok", venv_path=tmp_path / ".venv") - with patch("tycoon.commands.setup.find_uv", return_value="/usr/bin/uv"), \ - patch("tycoon.commands.setup.create_venv", return_value=fake) as cv: + with ( + patch("tycoon.commands.setup.find_uv", return_value="/usr/bin/uv"), + patch("tycoon.commands.setup.create_venv", return_value=fake) as cv, + ): cli_runner.invoke(app, ["setup", "--no-prompt", "--no-install"]) assert cv.call_args.kwargs["install_spec"] is None def test_failure_exits_nonzero(self, cli_runner, tmp_path, monkeypatch): self._bind(tmp_path, monkeypatch) fake = venv_mod.VenvResult(ok=False, message="uv venv failed") - with patch("tycoon.commands.setup.find_uv", return_value="/usr/bin/uv"), \ - patch("tycoon.commands.setup.create_venv", return_value=fake): + with ( + patch("tycoon.commands.setup.find_uv", return_value="/usr/bin/uv"), + patch("tycoon.commands.setup.create_venv", return_value=fake), + ): result = cli_runner.invoke(app, ["setup", "--no-prompt"]) assert result.exit_code == 1 assert "uv venv failed" in (result.stderr or result.output) @@ -217,8 +231,10 @@ def test_fix_builds_venv_when_uv_present(self, capsys): from tycoon.commands import doctor fake = venv_mod.VenvResult(ok=True, message="Created /x/.venv on Python 3.13.") - with patch("tycoon.venv.find_uv", return_value="/usr/bin/uv"), \ - patch("tycoon.venv.create_venv", return_value=fake) as cv: + with ( + patch("tycoon.venv.find_uv", return_value="/usr/bin/uv"), + patch("tycoon.venv.create_venv", return_value=fake) as cv, + ): doctor._fix_python_env() cv.assert_called_once() out = capsys.readouterr().out diff --git a/tests/test_snapshots.py b/tests/test_snapshots.py index 85eb651..72a566f 100644 --- a/tests/test_snapshots.py +++ b/tests/test_snapshots.py @@ -108,10 +108,7 @@ def test_half_init_suggests_ask_init(self, snapshot): def test_healthy_layer_coverage(self, snapshot): """The success path for the v0.1.7 layer-coverage doctor row.""" - msg = ( - "[green bold]OK[/green bold] Layer coverage: every source (3) " - "has at least one staging model." - ) + msg = "[green bold]OK[/green bold] Layer coverage: every source (3) has at least one staging model." assert _render(msg) == snapshot def test_uncovered_source_warning(self, snapshot): diff --git a/tests/test_sources.py b/tests/test_sources.py index 759aa60..05e6a94 100644 --- a/tests/test_sources.py +++ b/tests/test_sources.py @@ -4,11 +4,9 @@ from pathlib import Path - from tycoon.cli import app from tycoon.project import SourceConfig, TycoonProject, load_project, save_project - # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- @@ -69,9 +67,6 @@ class TestSourcesList: def test_list_shows_sources(self, cli_runner, tmp_path, monkeypatch): _setup_project(tmp_path) monkeypatch.chdir(tmp_path) - # Reload config for the new cwd - from tycoon.config import config - config.__init__(project_root=tmp_path) result = cli_runner.invoke(app, ["data", "sources", "list"]) assert result.exit_code == 0 @@ -83,8 +78,6 @@ def test_list_shows_empty_message(self, cli_runner, tmp_path, monkeypatch): (tmp_path / "pyproject.toml").write_text('[project]\nname = "test"\n') (tmp_path / "tycoon.yml").write_text("name: empty\nsources: {}\n") monkeypatch.chdir(tmp_path) - from tycoon.config import config - config.__init__(project_root=tmp_path) result = cli_runner.invoke(app, ["data", "sources", "list"]) assert result.exit_code == 0 @@ -92,8 +85,6 @@ def test_list_shows_empty_message(self, cli_runner, tmp_path, monkeypatch): def test_list_errors_without_project(self, cli_runner, tmp_path, monkeypatch): monkeypatch.chdir(tmp_path) - from tycoon.config import config - config.__init__(project_root=tmp_path) result = cli_runner.invoke(app, ["data", "sources", "list"]) assert result.exit_code == 1 @@ -110,8 +101,6 @@ class TestSourcesShow: def test_show_existing_source(self, cli_runner, tmp_path, monkeypatch): _setup_project(tmp_path) monkeypatch.chdir(tmp_path) - from tycoon.config import config - config.__init__(project_root=tmp_path) # show is a subcommand of list: tycoon data sources list show result = cli_runner.invoke(app, ["data", "sources", "list", "show", "nyc-dot"]) @@ -123,8 +112,6 @@ def test_show_existing_source(self, cli_runner, tmp_path, monkeypatch): def test_show_nonexistent_source(self, cli_runner, tmp_path, monkeypatch): _setup_project(tmp_path) monkeypatch.chdir(tmp_path) - from tycoon.config import config - config.__init__(project_root=tmp_path) result = cli_runner.invoke(app, ["data", "sources", "list", "show", "nonexistent"]) assert result.exit_code == 1 @@ -142,8 +129,6 @@ class TestSourcesRemove: def test_remove_with_confirmation(self, cli_runner, tmp_path, monkeypatch): _setup_project(tmp_path) monkeypatch.chdir(tmp_path) - from tycoon.config import config - config.__init__(project_root=tmp_path) result = cli_runner.invoke(app, ["data", "sources", "remove", "nyc-dot"], input="y\n") assert result.exit_code == 0 @@ -157,8 +142,6 @@ def test_remove_with_confirmation(self, cli_runner, tmp_path, monkeypatch): def test_remove_abort(self, cli_runner, tmp_path, monkeypatch): _setup_project(tmp_path) monkeypatch.chdir(tmp_path) - from tycoon.config import config - config.__init__(project_root=tmp_path) result = cli_runner.invoke(app, ["data", "sources", "remove", "nyc-dot"], input="n\n") assert result.exit_code == 1 # typer.confirm abort exits 1 @@ -171,8 +154,6 @@ def test_remove_abort(self, cli_runner, tmp_path, monkeypatch): def test_remove_nonexistent_source(self, cli_runner, tmp_path, monkeypatch): _setup_project(tmp_path) monkeypatch.chdir(tmp_path) - from tycoon.config import config - config.__init__(project_root=tmp_path) result = cli_runner.invoke(app, ["data", "sources", "remove", "nonexistent"]) assert result.exit_code == 1 @@ -253,11 +234,11 @@ def test_dlt_extras_dict_has_known_types(self): def test_install_dlt_extra_returns_bool(self): """Verify install_dlt_extra returns a bool (don't actually install).""" - from tycoon.ingestion.source_installer import install_dlt_extra - # We don't want to actually run pip in tests, but we can verify # the function signature and return type by mocking subprocess - from unittest.mock import patch, MagicMock + from unittest.mock import MagicMock, patch + + from tycoon.ingestion.source_installer import install_dlt_extra mock_result = MagicMock() mock_result.returncode = 0 @@ -268,8 +249,9 @@ def test_install_dlt_extra_returns_bool(self): def test_install_dlt_extra_failure(self): """Verify install_dlt_extra returns False on failure.""" + from unittest.mock import MagicMock, patch + from tycoon.ingestion.source_installer import install_dlt_extra - from unittest.mock import patch, MagicMock mock_result = MagicMock() mock_result.returncode = 1 @@ -285,7 +267,7 @@ def test_install_dlt_extra_pins_installed_dlt_version(self): itself at runtime (GH #68). """ import importlib.metadata - from unittest.mock import patch, MagicMock + from unittest.mock import MagicMock, patch from tycoon.ingestion.source_installer import install_dlt_extra @@ -293,9 +275,7 @@ def test_install_dlt_extra_pins_installed_dlt_version(self): mock_result.returncode = 0 expected = f"dlt[rest_api]=={importlib.metadata.version('dlt')}" - with patch( - "tycoon.ingestion.source_installer.subprocess.run", return_value=mock_result - ) as mock_run: + with patch("tycoon.ingestion.source_installer.subprocess.run", return_value=mock_result) as mock_run: assert install_dlt_extra("rest_api") is True cmd = mock_run.call_args.args[0] @@ -323,22 +303,20 @@ def _seed_raw_db(self, path: Path, schema: str, table: str) -> None: finally: con.close() - def _bind_config(self, monkeypatch, project_root: Path) -> None: - from tycoon.commands import sources as sources_mod + def _make_cfg(self, project_root: Path): from tycoon.config import TycoonConfig - cfg = TycoonConfig(project_root=project_root) - monkeypatch.setattr(sources_mod, "config", cfg) + return TycoonConfig(project_root=project_root) def test_no_dbt_project_is_noop(self, tmp_path: Path, monkeypatch): from tycoon.commands.sources import _maybe_auto_scaffold _setup_project(tmp_path) - self._bind_config(monkeypatch, tmp_path) + cfg = self._make_cfg(tmp_path) # No dbt_project/ directory exists. sc = SourceConfig(type="rest_api", schema="raw_nyc_dot", config={}) - _maybe_auto_scaffold("nyc-dot", sc, scaffold=True) + _maybe_auto_scaffold("nyc-dot", sc, cfg=cfg, scaffold=True) # No exception, no files written. Nothing to assert beyond "didn't raise." def test_skips_when_source_already_referenced(self, tmp_path: Path, monkeypatch): @@ -348,30 +326,26 @@ def test_skips_when_source_already_referenced(self, tmp_path: Path, monkeypatch) # Hand-written staging model already references the source. models = tmp_path / "dbt_project" / "models" / "staging" models.mkdir(parents=True) - (models / "stg_existing.sql").write_text( - "select * from {{ source('nyc-dot', 'i4gi-tjb9') }}\n" - ) + (models / "stg_existing.sql").write_text("select * from {{ source('nyc-dot', 'i4gi-tjb9') }}\n") self._seed_raw_db(tmp_path / "data" / "raw.duckdb", "raw_nyc_dot", "i4gi_tjb9") - self._bind_config(monkeypatch, tmp_path) + cfg = self._make_cfg(tmp_path) sc = SourceConfig(type="rest_api", schema="raw_nyc_dot", config={}) - _maybe_auto_scaffold("nyc-dot", sc, scaffold=True) + _maybe_auto_scaffold("nyc-dot", sc, cfg=cfg, scaffold=True) # No nyc-dot subdirectory was created. assert not (models / "nyc-dot").exists() - def test_generates_when_dbt_exists_and_no_prior_reference( - self, tmp_path: Path, monkeypatch - ): + def test_generates_when_dbt_exists_and_no_prior_reference(self, tmp_path: Path, monkeypatch): from tycoon.commands.sources import _maybe_auto_scaffold _setup_project(tmp_path) (tmp_path / "dbt_project" / "models").mkdir(parents=True) self._seed_raw_db(tmp_path / "data" / "raw.duckdb", "raw_nyc_dot", "i4gi_tjb9") - self._bind_config(monkeypatch, tmp_path) + cfg = self._make_cfg(tmp_path) sc = SourceConfig(type="rest_api", schema="raw_nyc_dot", config={}) - _maybe_auto_scaffold("nyc-dot", sc, scaffold=True) + _maybe_auto_scaffold("nyc-dot", sc, cfg=cfg, scaffold=True) sql = tmp_path / "dbt_project" / "models" / "staging" / "nyc-dot" / "stg_nyc-dot__i4gi_tjb9.sql" assert sql.exists() @@ -383,10 +357,10 @@ def test_no_scaffold_flag_skips(self, tmp_path: Path, monkeypatch): _setup_project(tmp_path) (tmp_path / "dbt_project" / "models").mkdir(parents=True) self._seed_raw_db(tmp_path / "data" / "raw.duckdb", "raw_nyc_dot", "i4gi_tjb9") - self._bind_config(monkeypatch, tmp_path) + cfg = self._make_cfg(tmp_path) sc = SourceConfig(type="rest_api", schema="raw_nyc_dot", config={}) - _maybe_auto_scaffold("nyc-dot", sc, scaffold=False) + _maybe_auto_scaffold("nyc-dot", sc, cfg=cfg, scaffold=False) assert not (tmp_path / "dbt_project" / "models" / "staging" / "nyc-dot").exists() @@ -394,16 +368,14 @@ def test_config_opt_out_skips(self, tmp_path: Path, monkeypatch): from tycoon.commands.sources import _maybe_auto_scaffold # tycoon.yml with transform.auto_scaffold: false - (tmp_path / "tycoon.yml").write_text( - _SAMPLE_YML + "transform:\n auto_scaffold: false\n" - ) + (tmp_path / "tycoon.yml").write_text(_SAMPLE_YML + "transform:\n auto_scaffold: false\n") (tmp_path / "pyproject.toml").write_text('[project]\nname = "test"\n') (tmp_path / "dbt_project" / "models").mkdir(parents=True) self._seed_raw_db(tmp_path / "data" / "raw.duckdb", "raw_nyc_dot", "i4gi_tjb9") - self._bind_config(monkeypatch, tmp_path) + cfg = self._make_cfg(tmp_path) sc = SourceConfig(type="rest_api", schema="raw_nyc_dot", config={}) - _maybe_auto_scaffold("nyc-dot", sc, scaffold=True) + _maybe_auto_scaffold("nyc-dot", sc, cfg=cfg, scaffold=True) assert not (tmp_path / "dbt_project" / "models" / "staging" / "nyc-dot").exists() @@ -422,7 +394,7 @@ class TestSourcesAddNoPrompt: """ def _bind(self, tmp_path: Path, monkeypatch): - """Set up an empty project + bind config to tmp_path.""" + """Set up an empty project and chdir so commands find it.""" body = ( "name: test\n" "version: 0.1.0\n" @@ -433,23 +405,21 @@ def _bind(self, tmp_path: Path, monkeypatch): ) (tmp_path / "tycoon.yml").write_text(body) (tmp_path / "pyproject.toml").write_text('[project]\nname = "test"\n') - from tycoon.commands import sources as sources_mod - from tycoon.config import TycoonConfig - - cfg = TycoonConfig(project_root=tmp_path) - monkeypatch.setattr(sources_mod, "config", cfg) - return cfg + monkeypatch.chdir(tmp_path) - def test_rest_api_with_base_url_auto_derives_name_and_schema( - self, cli_runner, tmp_path, monkeypatch - ): + def test_rest_api_with_base_url_auto_derives_name_and_schema(self, cli_runner, tmp_path, monkeypatch): self._bind(tmp_path, monkeypatch) result = cli_runner.invoke( app, [ - "data", "sources", "add", "rest_api", - "--base-url", "https://pokeapi.co/api/v2/", - "--resources", "pokemon,berry,type", + "data", + "sources", + "add", + "rest_api", + "--base-url", + "https://pokeapi.co/api/v2/", + "--resources", + "pokemon,berry,type", "--no-prompt", ], ) @@ -464,28 +434,28 @@ def test_rest_api_with_base_url_auto_derives_name_and_schema( assert src.config["base_url"] == "https://pokeapi.co/api/v2/" assert src.config["resources"] == "pokemon,berry,type" - def test_rest_api_missing_base_url_errors( - self, cli_runner, tmp_path, monkeypatch - ): + def test_rest_api_missing_base_url_errors(self, cli_runner, tmp_path, monkeypatch): self._bind(tmp_path, monkeypatch) - result = cli_runner.invoke( - app, ["data", "sources", "add", "rest_api", "--no-prompt"] - ) + result = cli_runner.invoke(app, ["data", "sources", "add", "rest_api", "--no-prompt"]) assert result.exit_code == 1 # error() writes to stderr assert "--base-url is required" in (result.stderr or result.output) - def test_sql_database_with_connection_string( - self, cli_runner, tmp_path, monkeypatch - ): + def test_sql_database_with_connection_string(self, cli_runner, tmp_path, monkeypatch): self._bind(tmp_path, monkeypatch) result = cli_runner.invoke( app, [ - "data", "sources", "add", "sql_database", - "--name", "warehouse-pg", - "--schema", "raw_pg", - "--connection-string", "${DATABASE_URL}", + "data", + "sources", + "add", + "sql_database", + "--name", + "warehouse-pg", + "--schema", + "raw_pg", + "--connection-string", + "${DATABASE_URL}", "--no-prompt", ], ) @@ -494,71 +464,84 @@ def test_sql_database_with_connection_string( assert "warehouse-pg" in project.sources assert project.sources["warehouse-pg"].config["connection_string"] == "${DATABASE_URL}" - def test_sql_database_requires_name_when_no_prompt( - self, cli_runner, tmp_path, monkeypatch - ): + def test_sql_database_requires_name_when_no_prompt(self, cli_runner, tmp_path, monkeypatch): """sql_database has no auto-naming rule — --name is required.""" self._bind(tmp_path, monkeypatch) result = cli_runner.invoke( app, [ - "data", "sources", "add", "sql_database", - "--connection-string", "postgres://x", + "data", + "sources", + "add", + "sql_database", + "--connection-string", + "postgres://x", "--no-prompt", ], ) assert result.exit_code == 1 assert "--name is required" in (result.stderr or result.output) - def test_no_prompt_without_source_type_errors( - self, cli_runner, tmp_path, monkeypatch - ): + def test_no_prompt_without_source_type_errors(self, cli_runner, tmp_path, monkeypatch): self._bind(tmp_path, monkeypatch) result = cli_runner.invoke(app, ["data", "sources", "add", "--no-prompt"]) assert result.exit_code == 1 assert "requires a source type" in (result.stderr or result.output) - def test_duplicate_without_force_errors( - self, cli_runner, tmp_path, monkeypatch - ): + def test_duplicate_without_force_errors(self, cli_runner, tmp_path, monkeypatch): self._bind(tmp_path, monkeypatch) cli_runner.invoke( app, [ - "data", "sources", "add", "rest_api", - "--base-url", "https://api.x.com/", + "data", + "sources", + "add", + "rest_api", + "--base-url", + "https://api.x.com/", "--no-prompt", ], ) result = cli_runner.invoke( app, [ - "data", "sources", "add", "rest_api", - "--base-url", "https://api.x.com/", + "data", + "sources", + "add", + "rest_api", + "--base-url", + "https://api.x.com/", "--no-prompt", ], ) assert result.exit_code == 1 assert "--force" in (result.stderr or result.output) - def test_force_overwrites_existing( - self, cli_runner, tmp_path, monkeypatch - ): + def test_force_overwrites_existing(self, cli_runner, tmp_path, monkeypatch): self._bind(tmp_path, monkeypatch) cli_runner.invoke( app, [ - "data", "sources", "add", "rest_api", - "--base-url", "https://api.x.com/v1/", + "data", + "sources", + "add", + "rest_api", + "--base-url", + "https://api.x.com/v1/", "--no-prompt", ], ) result = cli_runner.invoke( app, [ - "data", "sources", "add", "rest_api", - "--base-url", "https://api.x.com/v2/", - "--no-prompt", "--force", + "data", + "sources", + "add", + "rest_api", + "--base-url", + "https://api.x.com/v2/", + "--no-prompt", + "--force", ], ) assert result.exit_code == 0, result.output @@ -566,17 +549,21 @@ def test_force_overwrites_existing( # Newer base_url wins. assert project.sources["x"].config["base_url"] == "https://api.x.com/v2/" - def test_config_pairs_merge_into_source_config( - self, cli_runner, tmp_path, monkeypatch - ): + def test_config_pairs_merge_into_source_config(self, cli_runner, tmp_path, monkeypatch): self._bind(tmp_path, monkeypatch) result = cli_runner.invoke( app, [ - "data", "sources", "add", "rest_api", - "--base-url", "https://api.x.com/", - "--config", "headers={'Accept':'application/json'}", - "--config", "timeout=30", + "data", + "sources", + "add", + "rest_api", + "--base-url", + "https://api.x.com/", + "--config", + "headers={'Accept':'application/json'}", + "--config", + "timeout=30", "--no-prompt", ], ) @@ -586,16 +573,19 @@ def test_config_pairs_merge_into_source_config( assert src.config["timeout"] == "30" assert "headers" in src.config - def test_invalid_config_pair_errors( - self, cli_runner, tmp_path, monkeypatch - ): + def test_invalid_config_pair_errors(self, cli_runner, tmp_path, monkeypatch): self._bind(tmp_path, monkeypatch) result = cli_runner.invoke( app, [ - "data", "sources", "add", "rest_api", - "--base-url", "https://api.x.com/", - "--config", "missing_equals_sign", + "data", + "sources", + "add", + "rest_api", + "--base-url", + "https://api.x.com/", + "--config", + "missing_equals_sign", "--no-prompt", ], ) @@ -638,10 +628,16 @@ def test_no_prompt_add_registers_source(self, cli_runner, tmp_path, monkeypatch) result = cli_runner.invoke( app, [ - "data", "sources", "add", "google_sheets", - "--name", "marketing-sheet", - "--config", "spreadsheet_url_or_id=https://docs.google.com/spreadsheets/d/ABC/edit", - "--config", "range_names=Sheet1", + "data", + "sources", + "add", + "google_sheets", + "--name", + "marketing-sheet", + "--config", + "spreadsheet_url_or_id=https://docs.google.com/spreadsheets/d/ABC/edit", + "--config", + "range_names=Sheet1", "--no-prompt", ], ) diff --git a/tests/test_sync.py b/tests/test_sync.py index 76867fb..f63fa75 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -17,7 +17,6 @@ from tycoon.project import SyncSourceSpec from tycoon.sync import sync_to_local - # --------------------------------------------------------------------------- # Fixtures # --------------------------------------------------------------------------- @@ -56,11 +55,6 @@ def project(tmp_path: Path, monkeypatch): "sources: {}\n" ) - from tycoon.commands import sync_cmd as sync_mod - from tycoon.config import TycoonConfig - - cfg = TycoonConfig(project_root=tmp_path) - monkeypatch.setattr(sync_mod, "config", cfg) monkeypatch.chdir(tmp_path) return tmp_path @@ -224,17 +218,13 @@ def test_skips_uncopyable_view_with_record(self, tmp_path: Path): ph.execute("INSERT INTO other VALUES (99)") ph.close() src.execute(f"ATTACH '{phantom}' AS phantom") - src.execute( - "CREATE VIEW mart.broken AS SELECT * FROM phantom.main.other" - ) + src.execute("CREATE VIEW mart.broken AS SELECT * FROM phantom.main.other") src.execute("DETACH phantom") finally: src.close() dest = tmp_path / "snap.duckdb" - result = sync_to_local( - [SyncSourceSpec(from_=str(src_path))], dest, mode="replace" - ) + result = sync_to_local([SyncSourceSpec(from_=str(src_path))], dest, mode="replace") copied = {(t.schema, t.table) for t in result.tables} skipped = {(s.schema, s.table) for s in result.skipped} @@ -257,9 +247,7 @@ def test_no_sources_errors(self, project, cli_runner): assert "Nothing to sync" in combined def test_no_destination_errors(self, project, cli_runner, source_db): - result = cli_runner.invoke( - app, ["data", "sync", "--from", str(source_db)] - ) + result = cli_runner.invoke(app, ["data", "sync", "--from", str(source_db)]) assert result.exit_code == 1 combined = (result.stdout or "") + (result.stderr or "") assert "No destination" in combined @@ -271,9 +259,12 @@ def test_unknown_mode_errors(self, project, cli_runner, source_db): [ "data", "sync", - "--from", str(source_db), - "--to", str(dest), - "--mode", "rebase", + "--from", + str(source_db), + "--to", + str(dest), + "--mode", + "rebase", ], ) assert result.exit_code == 1 @@ -301,19 +292,25 @@ def test_cli_schema_filter(self, project, cli_runner, source_db): [ "data", "sync", - "--from", str(source_db), - "--to", str(dest), - "--schema", "mart", + "--from", + str(source_db), + "--to", + str(dest), + "--schema", + "mart", ], ) assert result.exit_code == 0 con = duckdb.connect(str(dest)) try: - schemas = [r[0] for r in con.execute( - "SELECT DISTINCT table_schema FROM information_schema.tables " - "WHERE table_schema NOT IN ('information_schema','pg_catalog','main') " - "AND table_type = 'BASE TABLE'" - ).fetchall()] + schemas = [ + r[0] + for r in con.execute( + "SELECT DISTINCT table_schema FROM information_schema.tables " + "WHERE table_schema NOT IN ('information_schema','pg_catalog','main') " + "AND table_type = 'BASE TABLE'" + ).fetchall() + ] # Only mart should be present (plus any system schemas filtered above) assert "mart" in schemas assert "raw" not in schemas @@ -337,11 +334,6 @@ def test_uses_tycoon_yml_sync_block(self, project, cli_runner, source_db): f" - from: {source_db}\n" f" schemas: ['mart']\n" ) - # Re-rebind config to pick up the new tycoon.yml - from tycoon.commands import sync_cmd as sync_mod - from tycoon.config import TycoonConfig - sync_mod.config = TycoonConfig(project_root=project) - result = cli_runner.invoke(app, ["data", "sync"]) assert result.exit_code == 0, result.stdout assert dest.exists() diff --git a/tests/test_templates_e2e.py b/tests/test_templates_e2e.py index 2a2b78e..d353929 100644 --- a/tests/test_templates_e2e.py +++ b/tests/test_templates_e2e.py @@ -34,9 +34,7 @@ def _init_template( args.extend(["--param", f"{k}={v}"]) result = cli_runner.invoke(app, args) assert result.exit_code == 0, ( - f"init --template {template} failed:\n" - f"--- stdout ---\n{result.stdout}\n" - f"--- exception ---\n{result.exception!r}" + f"init --template {template} failed:\n--- stdout ---\n{result.stdout}\n--- exception ---\n{result.exception!r}" ) @@ -54,12 +52,10 @@ def _rebind_config(monkeypatch, project: Path) -> None: this patch they pick up the parent-tmpdir root pytest started in. """ import tycoon.config as cfg_mod - from tycoon.commands import sources as sources_mod from tycoon.commands import transform as transform_mod from tycoon.config import TycoonConfig cfg = TycoonConfig(project_root=project) - monkeypatch.setattr(sources_mod, "config", cfg) monkeypatch.setattr(transform_mod, "config", cfg) monkeypatch.setattr(cfg_mod, "config", cfg) @@ -73,7 +69,7 @@ def test_csv_import_e2e(cli_runner, tmp_path, monkeypatch): project = tmp_path / "csv-import" project.mkdir() monkeypatch.chdir(project) - _init_template(cli_runner,"csv-import") + _init_template(cli_runner, "csv-import") input_dir = project / "data" / "input" input_dir.mkdir(parents=True, exist_ok=True) @@ -105,8 +101,7 @@ def test_csv_import_e2e(cli_runner, tmp_path, monkeypatch): tables = [ r[0] for r in con.execute( - "SELECT table_name FROM information_schema.tables " - "WHERE table_schema = 'raw_files'" + "SELECT table_name FROM information_schema.tables WHERE table_schema = 'raw_files'" ).fetchall() ] assert tables, "no tables materialized under raw_files schema" @@ -147,13 +142,9 @@ def test_csv_import_e2e(cli_runner, tmp_path, monkeypatch): # widget_id has not_null + unique tests in schema.yml — assert the # invariant holds in the data, not just at the test layer. - nulls = con.execute( - "SELECT count(*) FROM main.stg_widgets WHERE widget_id IS NULL" - ).fetchone() + nulls = con.execute("SELECT count(*) FROM main.stg_widgets WHERE widget_id IS NULL").fetchone() assert nulls is not None and nulls[0] == 0, "widget_id has nulls" - distinct_ids = con.execute( - "SELECT count(distinct widget_id) FROM main.stg_widgets" - ).fetchone() + distinct_ids = con.execute("SELECT count(distinct widget_id) FROM main.stg_widgets").fetchone() assert distinct_ids is not None and distinct_ids[0] == 5, ( f"widget_id should be unique (expected 5 distinct, got {distinct_ids})" ) @@ -193,9 +184,7 @@ def test_csv_import_e2e(cli_runner, tmp_path, monkeypatch): result = cli_runner.invoke(app, ["data", "transform", "test"]) stderr = result.stderr if result.stderr_bytes else "" assert result.exit_code == 0, ( - f"dbt test failed (exit {result.exit_code}):\n" - f"--- stdout ---\n{result.stdout}\n" - f"--- stderr ---\n{stderr}" + f"dbt test failed (exit {result.exit_code}):\n--- stdout ---\n{result.stdout}\n--- stderr ---\n{stderr}" ) # Observability: the invocation should have been captured. @@ -203,19 +192,12 @@ def test_csv_import_e2e(cli_runner, tmp_path, monkeypatch): assert metadata_db.exists(), "observability metadata DB was not created" con = duckdb.connect(str(metadata_db), read_only=True) try: + row = con.execute("SELECT count(*) FROM dbt_runs WHERE command = 'run'").fetchone() + assert row is not None and row[0] >= 1, f"dbt_runs should contain at least one 'run' invocation; got {row}" row = con.execute( - "SELECT count(*) FROM dbt_runs WHERE command = 'run'" + "SELECT count(*) FROM dbt_nodes WHERE status = 'success' AND node_name LIKE '%stg_widgets%'" ).fetchone() - assert row is not None and row[0] >= 1, ( - f"dbt_runs should contain at least one 'run' invocation; got {row}" - ) - row = con.execute( - "SELECT count(*) FROM dbt_nodes " - "WHERE status = 'success' AND node_name LIKE '%stg_widgets%'" - ).fetchone() - assert row is not None and row[0] >= 1, ( - "dbt_nodes should record the successful stg_widgets run" - ) + assert row is not None and row[0] >= 1, "dbt_nodes should record the successful stg_widgets run" finally: con.close() @@ -248,24 +230,17 @@ def test_csv_import_rerun_idempotent(cli_runner, tmp_path, monkeypatch): # Run the full pipeline twice. for pass_num in (1, 2): result = cli_runner.invoke(app, ["data", "sources", "run", "files"]) - assert result.exit_code == 0, ( - f"sources run pass {pass_num} failed:\n{result.stdout}" - ) + assert result.exit_code == 0, f"sources run pass {pass_num} failed:\n{result.stdout}" result = cli_runner.invoke(app, ["data", "transform", "run"]) - assert result.exit_code == 0, ( - f"transform run pass {pass_num} failed:\n{result.stdout}" - ) + assert result.exit_code == 0, f"transform run pass {pass_num} failed:\n{result.stdout}" # Raw table: same 5 rows after two ingests (replace semantics). raw_db = project / "data" / "files_raw.duckdb" con = duckdb.connect(str(raw_db), read_only=True) try: - rows = con.execute( - 'SELECT count(*) FROM raw_files."_read_csv"' - ).fetchone() + rows = con.execute('SELECT count(*) FROM raw_files."_read_csv"').fetchone() assert rows is not None and rows[0] == 5, ( - f"raw row count drifted across reruns; got {rows[0]} " - "(default write_disposition should be `replace`)" + f"raw row count drifted across reruns; got {rows[0]} (default write_disposition should be `replace`)" ) finally: con.close() @@ -276,12 +251,8 @@ def test_csv_import_rerun_idempotent(cli_runner, tmp_path, monkeypatch): try: stg = con.execute("SELECT count(*) FROM main.stg_widgets").fetchone() assert stg is not None and stg[0] == 5 - mart = con.execute( - "SELECT widget_count, total_quantity FROM main.fct_widget_summary" - ).fetchone() - assert mart == (5, 150), ( - f"mart aggregates drifted across reruns; got {mart}, expected (5, 150)" - ) + mart = con.execute("SELECT widget_count, total_quantity FROM main.fct_widget_summary").fetchone() + assert mart == (5, 150), f"mart aggregates drifted across reruns; got {mart}, expected (5, 150)" finally: con.close() @@ -290,15 +261,9 @@ def test_csv_import_rerun_idempotent(cli_runner, tmp_path, monkeypatch): con = duckdb.connect(str(metadata_db), read_only=True) try: dlt_runs = con.execute("SELECT count(*) FROM dlt_runs").fetchone() - dbt_runs = con.execute( - "SELECT count(*) FROM dbt_runs WHERE command = 'run'" - ).fetchone() - assert dlt_runs is not None and dlt_runs[0] == 2, ( - f"expected 2 dlt loads captured, got {dlt_runs}" - ) - assert dbt_runs is not None and dbt_runs[0] == 2, ( - f"expected 2 dbt 'run' invocations captured, got {dbt_runs}" - ) + dbt_runs = con.execute("SELECT count(*) FROM dbt_runs WHERE command = 'run'").fetchone() + assert dlt_runs is not None and dlt_runs[0] == 2, f"expected 2 dlt loads captured, got {dlt_runs}" + assert dbt_runs is not None and dbt_runs[0] == 2, f"expected 2 dbt 'run' invocations captured, got {dbt_runs}" finally: con.close() @@ -333,13 +298,8 @@ def test_csv_import_transform_ingest_transform(cli_runner, tmp_path, monkeypatch warehouse_db = project / "data" / "files_warehouse.duckdb" con = duckdb.connect(str(warehouse_db), read_only=True) try: - mart = con.execute( - "SELECT widget_count, total_quantity FROM main.fct_widget_summary" - ).fetchone() - assert mart == (5, 150), ( - f"final mart state wrong after transform-then-ingest-then-transform: " - f"got {mart}" - ) + mart = con.execute("SELECT widget_count, total_quantity FROM main.fct_widget_summary").fetchone() + assert mart == (5, 150), f"final mart state wrong after transform-then-ingest-then-transform: got {mart}" finally: con.close() @@ -360,11 +320,6 @@ def test_csv_import_then_data_sync(cli_runner, tmp_path, monkeypatch): _seed_widgets_csv(project / "data" / "input", count=5) _rebind_config(monkeypatch, project) - # Bind sync_cmd's config too, since it imports separately. - from tycoon.commands import sync_cmd as sync_mod - from tycoon.config import TycoonConfig - monkeypatch.setattr(sync_mod, "config", TycoonConfig(project_root=project)) - # ingest + transform assert cli_runner.invoke(app, ["data", "sources", "run", "files"]).exit_code == 0 assert cli_runner.invoke(app, ["data", "transform", "run"]).exit_code == 0 @@ -384,16 +339,12 @@ def test_csv_import_then_data_sync(cli_runner, tmp_path, monkeypatch): # skipped with a warning (asserted via stdout contents). assert "stg_widgets" in result.stdout assert "fct_widget_summary" in result.stdout - assert "Skipped" in result.stdout, ( - f"expected sync to surface skipped views; stdout:\n{result.stdout}" - ) + assert "Skipped" in result.stdout, f"expected sync to surface skipped views; stdout:\n{result.stdout}" # The snapshot should carry the mart and stg tables built by dbt. con = duckdb.connect(str(snapshot), read_only=True) try: - mart = con.execute( - "SELECT widget_count, total_quantity FROM main.fct_widget_summary" - ).fetchone() + mart = con.execute("SELECT widget_count, total_quantity FROM main.fct_widget_summary").fetchone() assert mart == (5, 150), f"snapshot mart mismatched: {mart}" stg = con.execute("SELECT count(*) FROM main.stg_widgets").fetchone() assert stg is not None and stg[0] == 5 @@ -416,24 +367,17 @@ def test_nyc_transit_e2e(cli_runner, tmp_path, monkeypatch): _rebind_config(monkeypatch, project) # Cap records to keep the test under a minute. - result = cli_runner.invoke( - app, ["data", "sources", "run", "nyc-dot", "--max-records", "50"] - ) + result = cli_runner.invoke(app, ["data", "sources", "run", "nyc-dot", "--max-records", "50"]) # Live HTTP can flake — treat non-zero as xfail rather than hard fail. if result.exit_code != 0: - pytest.xfail( - f"nyc-dot ingest returned {result.exit_code}; upstream API may be down:\n" - f"{result.stdout}" - ) + pytest.xfail(f"nyc-dot ingest returned {result.exit_code}; upstream API may be down:\n{result.stdout}") raw_db = project / "data" / "nyc_open_data_raw.duckdb" assert raw_db.exists(), "raw db was not created" # Auto-scaffold should have written staging models for nyc-dot. staging_dir = project / "dbt_project" / "models" / "staging" / "nyc-dot" - assert staging_dir.exists(), ( - f"auto-scaffold did not create staging dir; sources run output:\n{result.stdout}" - ) + assert staging_dir.exists(), f"auto-scaffold did not create staging dir; sources run output:\n{result.stdout}" sql_files = list(staging_dir.glob("stg_nyc-dot__*.sql")) assert sql_files, f"no staging .sql files generated under {staging_dir}" @@ -451,8 +395,7 @@ def test_nyc_transit_e2e(cli_runner, tmp_path, monkeypatch): con = duckdb.connect(str(warehouse_db), read_only=True) try: rows = con.execute( - "SELECT count(*) FROM information_schema.tables " - "WHERE table_name LIKE 'stg_nyc-dot__%'" + "SELECT count(*) FROM information_schema.tables WHERE table_name LIKE 'stg_nyc-dot__%'" ).fetchone() assert rows is not None and rows[0] >= 1, ( f"expected at least one stg_nyc-dot__* table in warehouse DB; got {rows}" @@ -509,13 +452,13 @@ def test_register_dbt_create_e2e(cli_runner, tmp_path, monkeypatch): # register.py also imports config at module scope — rebind that too. from tycoon.commands import register as register_mod from tycoon.config import TycoonConfig + monkeypatch.setattr(register_mod, "config", TycoonConfig(project_root=project)) # The marquee v0.1.6 command — bootstrap dbt from scratch. result = cli_runner.invoke(app, ["register", "dbt", "--create"]) assert result.exit_code == 0, ( - f"register dbt --create failed:\n--- stdout ---\n{result.stdout}\n" - f"--- exception ---\n{result.exception!r}" + f"register dbt --create failed:\n--- stdout ---\n{result.stdout}\n--- exception ---\n{result.exception!r}" ) # Sibling dbt project landed at the default path. @@ -527,9 +470,7 @@ def test_register_dbt_create_e2e(cli_runner, tmp_path, monkeypatch): yml = yaml.safe_load((project / "tycoon.yml").read_text()) assert yml["dbt_project_dir"], "dbt_project_dir not written to tycoon.yml" assert yml["stack"]["transformation"] == "dbt" - assert yml["stack"]["transformation_managed"] is True, ( - "--create should mark project as tycoon-managed" - ) + assert yml["stack"]["transformation_managed"] is True, "--create should mark project as tycoon-managed" # In real CLI use each command is a fresh process and re-reads # tycoon.yml. In-process tests share the singleton, so we rebind diff --git a/tests/test_templates_smoke.py b/tests/test_templates_smoke.py index 6705d51..ce68cc3 100644 --- a/tests/test_templates_smoke.py +++ b/tests/test_templates_smoke.py @@ -8,13 +8,11 @@ from __future__ import annotations - import pytest import yaml from tycoon.cli import app - TEMPLATES = ["csv-import", "nyc-transit"] @@ -30,7 +28,6 @@ def _init_args(template: str) -> list[str]: @pytest.mark.parametrize("template", TEMPLATES) class TestTemplateSmoke: - def test_init_creates_tycoon_yml(self, template, cli_runner, tmp_path, monkeypatch): project = tmp_path / template project.mkdir() @@ -74,9 +71,7 @@ def test_doctor_runs_clean(self, template, cli_runner, tmp_path, monkeypatch): monkeypatch.setattr(doctor_mod, "config", TycoonConfig(project_root=project)) doctor_result = cli_runner.invoke(app, ["doctor"]) - assert doctor_result.exit_code == 0, ( - f"doctor failed for template {template}:\n{doctor_result.stdout}" - ) + assert doctor_result.exit_code == 0, f"doctor failed for template {template}:\n{doctor_result.stdout}" def test_no_unresolved_placeholders(self, template, cli_runner, tmp_path, monkeypatch): """After init, no `{{ param }}` placeholders should remain in tycoon.yml.""" diff --git a/tests/test_utils.py b/tests/test_utils.py index 9581bc8..3bce862 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -7,16 +7,15 @@ import duckdb from rich.table import Table -from tycoon.utils.process import is_port_in_use, command_exists +from tycoon.utils.console import error, info, status_table, success, warn from tycoon.utils.duckdb_utils import ( db_file_size_mb, - get_tables, get_row_count, + get_tables, quote_identifier, remove_wal, ) -from tycoon.utils.console import status_table, success, warn, error, info - +from tycoon.utils.process import command_exists, is_port_in_use # --------------------------------------------------------------------------- # Port checking @@ -24,7 +23,6 @@ class TestPortChecking: - def test_is_port_in_use_returns_bool(self): result = is_port_in_use(59999) assert isinstance(result, bool) @@ -46,7 +44,6 @@ def test_command_exists_nonexistent(self): class TestDuckDBUtils: - def test_db_file_size_mb_missing_file(self, tmp_path: Path): assert db_file_size_mb(tmp_path / "missing.duckdb") is None @@ -132,7 +129,6 @@ def test_remove_wal_with_wal(self, tmp_path: Path): class TestConsoleHelpers: - def test_status_table_returns_table(self): rows = [("Component", "OK", "detail")] result = status_table(rows) diff --git a/tests/test_wizard.py b/tests/test_wizard.py index 795e724..59b3709 100644 --- a/tests/test_wizard.py +++ b/tests/test_wizard.py @@ -14,7 +14,6 @@ _extract_dbt_duckdb_path, ) - # --------------------------------------------------------------------------- # _detect_existing # --------------------------------------------------------------------------- @@ -104,7 +103,6 @@ def test_hidden_siblings_ignored(self, tmp_path: Path) -> None: class TestWizardGreenfield: - def test_default_all_managed(self, cli_runner, tmp_path, monkeypatch): project = tmp_path / "green" project.mkdir() @@ -162,7 +160,6 @@ def test_creates_dbt_at_sibling_path(self, cli_runner, tmp_path, monkeypatch): class TestWizardDetection: - def test_detected_dbt_listed_as_first_option(self, cli_runner, tmp_path, monkeypatch): """When a dbt project exists at a canonical location, wizard offers it as option 1.""" project = tmp_path / "myproj" @@ -193,9 +190,7 @@ class TestExtractDbtDuckdbPath: def _make_dbt_project(self, dbt_dir: Path, profile: str, duckdb_path: str) -> None: dbt_dir.mkdir(parents=True, exist_ok=True) - (dbt_dir / "dbt_project.yml").write_text( - yaml.dump({"name": profile, "profile": profile, "config-version": 2}) - ) + (dbt_dir / "dbt_project.yml").write_text(yaml.dump({"name": profile, "profile": profile, "config-version": 2})) (dbt_dir / "profiles.yml").write_text( yaml.dump( { @@ -224,9 +219,7 @@ def test_resolves_relative_path_against_dbt_dir(self, tmp_path: Path) -> None: def test_returns_none_for_non_duckdb_target(self, tmp_path: Path) -> None: dbt_dir = tmp_path / "mydbt" dbt_dir.mkdir() - (dbt_dir / "dbt_project.yml").write_text( - yaml.dump({"name": "mine", "profile": "mine", "config-version": 2}) - ) + (dbt_dir / "dbt_project.yml").write_text(yaml.dump({"name": "mine", "profile": "mine", "config-version": 2})) (dbt_dir / "profiles.yml").write_text( yaml.dump( { @@ -242,9 +235,7 @@ def test_returns_none_for_non_duckdb_target(self, tmp_path: Path) -> None: def test_returns_none_when_profiles_missing(self, tmp_path: Path) -> None: dbt_dir = tmp_path / "mydbt" dbt_dir.mkdir() - (dbt_dir / "dbt_project.yml").write_text( - yaml.dump({"name": "mine", "profile": "mine"}) - ) + (dbt_dir / "dbt_project.yml").write_text(yaml.dump({"name": "mine", "profile": "mine"})) # Don't write profiles.yml; and ensure ~/.dbt/profiles.yml doesn't exist here # (test may spuriously pass if user has one — but that's orthogonal) # We just assert *this* dir's check returns None given no local profiles.yml. @@ -262,7 +253,6 @@ def test_returns_none_when_dbt_project_yml_missing(self, tmp_path: Path) -> None class TestWizardSkipSemantics: - def test_doctor_reports_skipped_components(self, cli_runner, tmp_path, monkeypatch): """After skipping dbt + rill + orch, `tycoon doctor` should say 'skipped by choice'.""" project = tmp_path / "skippy" diff --git a/uv.lock b/uv.lock index 85d37a9..200fd20 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 3 +revision = 1 requires-python = ">=3.12, <3.14" resolution-markers = [ "python_full_version >= '3.13' and sys_platform == 'win32'", @@ -23,27 +23,27 @@ dependencies = [ { name = "pytimeparse" }, { name = "tzdata", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/29/77/6f5df1c68bf056f5fdefc60ccc616303c6211e71cd6033c830c12735f605/agate-1.9.1.tar.gz", hash = "sha256:bc60880c2ee59636a2a80cd8603d63f995be64526abf3cbba12f00767bcd5b3d", size = 202303, upload-time = "2023-12-21T20:05:24.316Z" } +sdist = { url = "https://files.pythonhosted.org/packages/29/77/6f5df1c68bf056f5fdefc60ccc616303c6211e71cd6033c830c12735f605/agate-1.9.1.tar.gz", hash = "sha256:bc60880c2ee59636a2a80cd8603d63f995be64526abf3cbba12f00767bcd5b3d", size = 202303 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/53/89b197cb472a3175d73384761a3413fd58e6b65a794c1102d148b8de87bd/agate-1.9.1-py2.py3-none-any.whl", hash = "sha256:1cf329510b3dde07c4ad1740b7587c9c679abc3dcd92bb1107eabc10c2e03c50", size = 95085, upload-time = "2023-12-21T20:05:21.954Z" }, + { url = "https://files.pythonhosted.org/packages/d1/53/89b197cb472a3175d73384761a3413fd58e6b65a794c1102d148b8de87bd/agate-1.9.1-py2.py3-none-any.whl", hash = "sha256:1cf329510b3dde07c4ad1740b7587c9c679abc3dcd92bb1107eabc10c2e03c50", size = 95085 }, ] [[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" } +sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288 } 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" }, + { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303 }, ] [[package]] name = "annotated-types" version = "0.7.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } wheels = [ - { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, ] [[package]] @@ -54,77 +54,77 @@ dependencies = [ { name = "idna" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622, upload-time = "2026-03-24T12:59:09.671Z" } +sdist = { url = "https://files.pythonhosted.org/packages/19/14/2c5dd9f512b66549ae92767a9c7b330ae88e1932ca57876909410251fe13/anyio-4.13.0.tar.gz", hash = "sha256:334b70e641fd2221c1505b3890c69882fe4a2df910cba14d97019b90b24439dc", size = 231622 } wheels = [ - { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353, upload-time = "2026-03-24T12:59:08.246Z" }, + { url = "https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl", hash = "sha256:08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708", size = 114353 }, ] [[package]] name = "attrs" version = "26.1.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055 } wheels = [ - { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548 }, ] [[package]] name = "babel" version = "2.18.0" source = { registry = "https://pypi.org/simple" } -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" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/b2/51899539b6ceeeb420d40ed3cd4b7a40519404f9baf3d4ac99dc413a834b/babel-2.18.0.tar.gz", hash = "sha256:b80b99a14bd085fcacfa15c9165f651fbb3406e66cc603abf11c5750937c992d", size = 9959554 } wheels = [ - { 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" }, + { url = "https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl", hash = "sha256:e2b422b277c2b9a9630c1d7903c2a00d0830c409c59ac8cae9081c92f1aeba35", size = 10196845 }, ] [[package]] name = "certifi" version = "2026.2.25" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } +sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, + { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684 }, ] [[package]] name = "charset-normalizer" version = "3.4.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7b/60/e3bec1881450851b087e301bedc3daa9377a4d45f1c26aa90b0b235e38aa/charset_normalizer-3.4.6.tar.gz", hash = "sha256:1ae6b62897110aa7c79ea2f5dd38d1abca6db663687c0b1ad9aed6f6bae3d9d6", size = 143363, upload-time = "2026-03-15T18:53:25.478Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/62/c0815c992c9545347aeea7859b50dc9044d147e2e7278329c6e02ac9a616/charset_normalizer-3.4.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ef7fedc7a6ecbe99969cd09632516738a97eeb8bd7258bf8a0f23114c057dab", size = 295154, upload-time = "2026-03-15T18:50:50.88Z" }, - { url = "https://files.pythonhosted.org/packages/a8/37/bdca6613c2e3c58c7421891d80cc3efa1d32e882f7c4a7ee6039c3fc951a/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4ea868bc28109052790eb2b52a9ab33f3aa7adc02f96673526ff47419490e21", size = 199191, upload-time = "2026-03-15T18:50:52.658Z" }, - { url = "https://files.pythonhosted.org/packages/6c/92/9934d1bbd69f7f398b38c5dae1cbf9cc672e7c34a4adf7b17c0a9c17d15d/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:836ab36280f21fc1a03c99cd05c6b7af70d2697e374c7af0b61ed271401a72a2", size = 218674, upload-time = "2026-03-15T18:50:54.102Z" }, - { url = "https://files.pythonhosted.org/packages/af/90/25f6ab406659286be929fd89ab0e78e38aa183fc374e03aa3c12d730af8a/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f1ce721c8a7dfec21fcbdfe04e8f68174183cf4e8188e0645e92aa23985c57ff", size = 215259, upload-time = "2026-03-15T18:50:55.616Z" }, - { url = "https://files.pythonhosted.org/packages/4e/ef/79a463eb0fff7f96afa04c1d4c51f8fc85426f918db467854bfb6a569ce3/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e28d62a8fc7a1fa411c43bd65e346f3bce9716dc51b897fbe930c5987b402d5", size = 207276, upload-time = "2026-03-15T18:50:57.054Z" }, - { url = "https://files.pythonhosted.org/packages/f7/72/d0426afec4b71dc159fa6b4e68f868cd5a3ecd918fec5813a15d292a7d10/charset_normalizer-3.4.6-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:530d548084c4a9f7a16ed4a294d459b4f229db50df689bfe92027452452943a0", size = 195161, upload-time = "2026-03-15T18:50:58.686Z" }, - { url = "https://files.pythonhosted.org/packages/bf/18/c82b06a68bfcb6ce55e508225d210c7e6a4ea122bfc0748892f3dc4e8e11/charset_normalizer-3.4.6-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:30f445ae60aad5e1f8bdbb3108e39f6fbc09f4ea16c815c66578878325f8f15a", size = 203452, upload-time = "2026-03-15T18:51:00.196Z" }, - { url = "https://files.pythonhosted.org/packages/44/d6/0c25979b92f8adafdbb946160348d8d44aa60ce99afdc27df524379875cb/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ac2393c73378fea4e52aa56285a3d64be50f1a12395afef9cce47772f60334c2", size = 202272, upload-time = "2026-03-15T18:51:01.703Z" }, - { url = "https://files.pythonhosted.org/packages/2e/3d/7fea3e8fe84136bebbac715dd1221cc25c173c57a699c030ab9b8900cbb7/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:90ca27cd8da8118b18a52d5f547859cc1f8354a00cd1e8e5120df3e30d6279e5", size = 195622, upload-time = "2026-03-15T18:51:03.526Z" }, - { url = "https://files.pythonhosted.org/packages/57/8a/d6f7fd5cb96c58ef2f681424fbca01264461336d2a7fc875e4446b1f1346/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e5a94886bedca0f9b78fecd6afb6629142fd2605aa70a125d49f4edc6037ee6", size = 220056, upload-time = "2026-03-15T18:51:05.269Z" }, - { url = "https://files.pythonhosted.org/packages/16/50/478cdda782c8c9c3fb5da3cc72dd7f331f031e7f1363a893cdd6ca0f8de0/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:695f5c2823691a25f17bc5d5ffe79fa90972cc34b002ac6c843bb8a1720e950d", size = 203751, upload-time = "2026-03-15T18:51:06.858Z" }, - { url = "https://files.pythonhosted.org/packages/75/fc/cc2fcac943939c8e4d8791abfa139f685e5150cae9f94b60f12520feaa9b/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:231d4da14bcd9301310faf492051bee27df11f2bc7549bc0bb41fef11b82daa2", size = 216563, upload-time = "2026-03-15T18:51:08.564Z" }, - { url = "https://files.pythonhosted.org/packages/a8/b7/a4add1d9a5f68f3d037261aecca83abdb0ab15960a3591d340e829b37298/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a056d1ad2633548ca18ffa2f85c202cfb48b68615129143915b8dc72a806a923", size = 209265, upload-time = "2026-03-15T18:51:10.312Z" }, - { url = "https://files.pythonhosted.org/packages/6c/18/c094561b5d64a24277707698e54b7f67bd17a4f857bbfbb1072bba07c8bf/charset_normalizer-3.4.6-cp312-cp312-win32.whl", hash = "sha256:c2274ca724536f173122f36c98ce188fd24ce3dad886ec2b7af859518ce008a4", size = 144229, upload-time = "2026-03-15T18:51:11.694Z" }, - { url = "https://files.pythonhosted.org/packages/ab/20/0567efb3a8fd481b8f34f739ebddc098ed062a59fed41a8d193a61939e8f/charset_normalizer-3.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:c8ae56368f8cc97c7e40a7ee18e1cedaf8e780cd8bc5ed5ac8b81f238614facb", size = 154277, upload-time = "2026-03-15T18:51:13.004Z" }, - { url = "https://files.pythonhosted.org/packages/15/57/28d79b44b51933119e21f65479d0864a8d5893e494cf5daab15df0247c17/charset_normalizer-3.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:899d28f422116b08be5118ef350c292b36fc15ec2daeb9ea987c89281c7bb5c4", size = 142817, upload-time = "2026-03-15T18:51:14.408Z" }, - { url = "https://files.pythonhosted.org/packages/1e/1d/4fdabeef4e231153b6ed7567602f3b68265ec4e5b76d6024cf647d43d981/charset_normalizer-3.4.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:11afb56037cbc4b1555a34dd69151e8e069bee82e613a73bef6e714ce733585f", size = 294823, upload-time = "2026-03-15T18:51:15.755Z" }, - { url = "https://files.pythonhosted.org/packages/47/7b/20e809b89c69d37be748d98e84dce6820bf663cf19cf6b942c951a3e8f41/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:423fb7e748a08f854a08a222b983f4df1912b1daedce51a72bd24fe8f26a1843", size = 198527, upload-time = "2026-03-15T18:51:17.177Z" }, - { url = "https://files.pythonhosted.org/packages/37/a6/4f8d27527d59c039dce6f7622593cdcd3d70a8504d87d09eb11e9fdc6062/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d73beaac5e90173ac3deb9928a74763a6d230f494e4bfb422c217a0ad8e629bf", size = 218388, upload-time = "2026-03-15T18:51:18.934Z" }, - { url = "https://files.pythonhosted.org/packages/f6/9b/4770ccb3e491a9bacf1c46cc8b812214fe367c86a96353ccc6daf87b01ec/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d60377dce4511655582e300dc1e5a5f24ba0cb229005a1d5c8d0cb72bb758ab8", size = 214563, upload-time = "2026-03-15T18:51:20.374Z" }, - { url = "https://files.pythonhosted.org/packages/2b/58/a199d245894b12db0b957d627516c78e055adc3a0d978bc7f65ddaf7c399/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:530e8cebeea0d76bdcf93357aa5e41336f48c3dc709ac52da2bb167c5b8271d9", size = 206587, upload-time = "2026-03-15T18:51:21.807Z" }, - { url = "https://files.pythonhosted.org/packages/7e/70/3def227f1ec56f5c69dfc8392b8bd63b11a18ca8178d9211d7cc5e5e4f27/charset_normalizer-3.4.6-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:a26611d9987b230566f24a0a125f17fe0de6a6aff9f25c9f564aaa2721a5fb88", size = 194724, upload-time = "2026-03-15T18:51:23.508Z" }, - { url = "https://files.pythonhosted.org/packages/58/ab/9318352e220c05efd31c2779a23b50969dc94b985a2efa643ed9077bfca5/charset_normalizer-3.4.6-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:34315ff4fc374b285ad7f4a0bf7dcbfe769e1b104230d40f49f700d4ab6bbd84", size = 202956, upload-time = "2026-03-15T18:51:25.239Z" }, - { url = "https://files.pythonhosted.org/packages/75/13/f3550a3ac25b70f87ac98c40d3199a8503676c2f1620efbf8d42095cfc40/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5f8ddd609f9e1af8c7bd6e2aca279c931aefecd148a14402d4e368f3171769fd", size = 201923, upload-time = "2026-03-15T18:51:26.682Z" }, - { url = "https://files.pythonhosted.org/packages/1b/db/c5c643b912740b45e8eec21de1bbab8e7fc085944d37e1e709d3dcd9d72f/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:80d0a5615143c0b3225e5e3ef22c8d5d51f3f72ce0ea6fb84c943546c7b25b6c", size = 195366, upload-time = "2026-03-15T18:51:28.129Z" }, - { url = "https://files.pythonhosted.org/packages/5a/67/3b1c62744f9b2448443e0eb160d8b001c849ec3fef591e012eda6484787c/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:92734d4d8d187a354a556626c221cd1a892a4e0802ccb2af432a1d85ec012194", size = 219752, upload-time = "2026-03-15T18:51:29.556Z" }, - { url = "https://files.pythonhosted.org/packages/f6/98/32ffbaf7f0366ffb0445930b87d103f6b406bc2c271563644bde8a2b1093/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:613f19aa6e082cf96e17e3ffd89383343d0d589abda756b7764cf78361fd41dc", size = 203296, upload-time = "2026-03-15T18:51:30.921Z" }, - { url = "https://files.pythonhosted.org/packages/41/12/5d308c1bbe60cabb0c5ef511574a647067e2a1f631bc8634fcafaccd8293/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2b1a63e8224e401cafe7739f77efd3f9e7f5f2026bda4aead8e59afab537784f", size = 215956, upload-time = "2026-03-15T18:51:32.399Z" }, - { url = "https://files.pythonhosted.org/packages/53/e9/5f85f6c5e20669dbe56b165c67b0260547dea97dba7e187938833d791687/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6cceb5473417d28edd20c6c984ab6fee6c6267d38d906823ebfe20b03d607dc2", size = 208652, upload-time = "2026-03-15T18:51:34.214Z" }, - { url = "https://files.pythonhosted.org/packages/f1/11/897052ea6af56df3eef3ca94edafee410ca699ca0c7b87960ad19932c55e/charset_normalizer-3.4.6-cp313-cp313-win32.whl", hash = "sha256:d7de2637729c67d67cf87614b566626057e95c303bc0a55ffe391f5205e7003d", size = 143940, upload-time = "2026-03-15T18:51:36.15Z" }, - { url = "https://files.pythonhosted.org/packages/a1/5c/724b6b363603e419829f561c854b87ed7c7e31231a7908708ac086cdf3e2/charset_normalizer-3.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:572d7c822caf521f0525ba1bce1a622a0b85cf47ffbdae6c9c19e3b5ac3c4389", size = 154101, upload-time = "2026-03-15T18:51:37.876Z" }, - { url = "https://files.pythonhosted.org/packages/01/a5/7abf15b4c0968e47020f9ca0935fb3274deb87cb288cd187cad92e8cdffd/charset_normalizer-3.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a4474d924a47185a06411e0064b803c68be044be2d60e50e8bddcc2649957c1f", size = 143109, upload-time = "2026-03-15T18:51:39.565Z" }, - { url = "https://files.pythonhosted.org/packages/2a/68/687187c7e26cb24ccbd88e5069f5ef00eba804d36dde11d99aad0838ab45/charset_normalizer-3.4.6-py3-none-any.whl", hash = "sha256:947cf925bc916d90adba35a64c82aace04fa39b46b52d4630ece166655905a69", size = 61455, upload-time = "2026-03-15T18:53:23.833Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/7b/60/e3bec1881450851b087e301bedc3daa9377a4d45f1c26aa90b0b235e38aa/charset_normalizer-3.4.6.tar.gz", hash = "sha256:1ae6b62897110aa7c79ea2f5dd38d1abca6db663687c0b1ad9aed6f6bae3d9d6", size = 143363 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/62/c0815c992c9545347aeea7859b50dc9044d147e2e7278329c6e02ac9a616/charset_normalizer-3.4.6-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ef7fedc7a6ecbe99969cd09632516738a97eeb8bd7258bf8a0f23114c057dab", size = 295154 }, + { url = "https://files.pythonhosted.org/packages/a8/37/bdca6613c2e3c58c7421891d80cc3efa1d32e882f7c4a7ee6039c3fc951a/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a4ea868bc28109052790eb2b52a9ab33f3aa7adc02f96673526ff47419490e21", size = 199191 }, + { url = "https://files.pythonhosted.org/packages/6c/92/9934d1bbd69f7f398b38c5dae1cbf9cc672e7c34a4adf7b17c0a9c17d15d/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:836ab36280f21fc1a03c99cd05c6b7af70d2697e374c7af0b61ed271401a72a2", size = 218674 }, + { url = "https://files.pythonhosted.org/packages/af/90/25f6ab406659286be929fd89ab0e78e38aa183fc374e03aa3c12d730af8a/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f1ce721c8a7dfec21fcbdfe04e8f68174183cf4e8188e0645e92aa23985c57ff", size = 215259 }, + { url = "https://files.pythonhosted.org/packages/4e/ef/79a463eb0fff7f96afa04c1d4c51f8fc85426f918db467854bfb6a569ce3/charset_normalizer-3.4.6-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e28d62a8fc7a1fa411c43bd65e346f3bce9716dc51b897fbe930c5987b402d5", size = 207276 }, + { url = "https://files.pythonhosted.org/packages/f7/72/d0426afec4b71dc159fa6b4e68f868cd5a3ecd918fec5813a15d292a7d10/charset_normalizer-3.4.6-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:530d548084c4a9f7a16ed4a294d459b4f229db50df689bfe92027452452943a0", size = 195161 }, + { url = "https://files.pythonhosted.org/packages/bf/18/c82b06a68bfcb6ce55e508225d210c7e6a4ea122bfc0748892f3dc4e8e11/charset_normalizer-3.4.6-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:30f445ae60aad5e1f8bdbb3108e39f6fbc09f4ea16c815c66578878325f8f15a", size = 203452 }, + { url = "https://files.pythonhosted.org/packages/44/d6/0c25979b92f8adafdbb946160348d8d44aa60ce99afdc27df524379875cb/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ac2393c73378fea4e52aa56285a3d64be50f1a12395afef9cce47772f60334c2", size = 202272 }, + { url = "https://files.pythonhosted.org/packages/2e/3d/7fea3e8fe84136bebbac715dd1221cc25c173c57a699c030ab9b8900cbb7/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:90ca27cd8da8118b18a52d5f547859cc1f8354a00cd1e8e5120df3e30d6279e5", size = 195622 }, + { url = "https://files.pythonhosted.org/packages/57/8a/d6f7fd5cb96c58ef2f681424fbca01264461336d2a7fc875e4446b1f1346/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:8e5a94886bedca0f9b78fecd6afb6629142fd2605aa70a125d49f4edc6037ee6", size = 220056 }, + { url = "https://files.pythonhosted.org/packages/16/50/478cdda782c8c9c3fb5da3cc72dd7f331f031e7f1363a893cdd6ca0f8de0/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:695f5c2823691a25f17bc5d5ffe79fa90972cc34b002ac6c843bb8a1720e950d", size = 203751 }, + { url = "https://files.pythonhosted.org/packages/75/fc/cc2fcac943939c8e4d8791abfa139f685e5150cae9f94b60f12520feaa9b/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:231d4da14bcd9301310faf492051bee27df11f2bc7549bc0bb41fef11b82daa2", size = 216563 }, + { url = "https://files.pythonhosted.org/packages/a8/b7/a4add1d9a5f68f3d037261aecca83abdb0ab15960a3591d340e829b37298/charset_normalizer-3.4.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a056d1ad2633548ca18ffa2f85c202cfb48b68615129143915b8dc72a806a923", size = 209265 }, + { url = "https://files.pythonhosted.org/packages/6c/18/c094561b5d64a24277707698e54b7f67bd17a4f857bbfbb1072bba07c8bf/charset_normalizer-3.4.6-cp312-cp312-win32.whl", hash = "sha256:c2274ca724536f173122f36c98ce188fd24ce3dad886ec2b7af859518ce008a4", size = 144229 }, + { url = "https://files.pythonhosted.org/packages/ab/20/0567efb3a8fd481b8f34f739ebddc098ed062a59fed41a8d193a61939e8f/charset_normalizer-3.4.6-cp312-cp312-win_amd64.whl", hash = "sha256:c8ae56368f8cc97c7e40a7ee18e1cedaf8e780cd8bc5ed5ac8b81f238614facb", size = 154277 }, + { url = "https://files.pythonhosted.org/packages/15/57/28d79b44b51933119e21f65479d0864a8d5893e494cf5daab15df0247c17/charset_normalizer-3.4.6-cp312-cp312-win_arm64.whl", hash = "sha256:899d28f422116b08be5118ef350c292b36fc15ec2daeb9ea987c89281c7bb5c4", size = 142817 }, + { url = "https://files.pythonhosted.org/packages/1e/1d/4fdabeef4e231153b6ed7567602f3b68265ec4e5b76d6024cf647d43d981/charset_normalizer-3.4.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:11afb56037cbc4b1555a34dd69151e8e069bee82e613a73bef6e714ce733585f", size = 294823 }, + { url = "https://files.pythonhosted.org/packages/47/7b/20e809b89c69d37be748d98e84dce6820bf663cf19cf6b942c951a3e8f41/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:423fb7e748a08f854a08a222b983f4df1912b1daedce51a72bd24fe8f26a1843", size = 198527 }, + { url = "https://files.pythonhosted.org/packages/37/a6/4f8d27527d59c039dce6f7622593cdcd3d70a8504d87d09eb11e9fdc6062/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d73beaac5e90173ac3deb9928a74763a6d230f494e4bfb422c217a0ad8e629bf", size = 218388 }, + { url = "https://files.pythonhosted.org/packages/f6/9b/4770ccb3e491a9bacf1c46cc8b812214fe367c86a96353ccc6daf87b01ec/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d60377dce4511655582e300dc1e5a5f24ba0cb229005a1d5c8d0cb72bb758ab8", size = 214563 }, + { url = "https://files.pythonhosted.org/packages/2b/58/a199d245894b12db0b957d627516c78e055adc3a0d978bc7f65ddaf7c399/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:530e8cebeea0d76bdcf93357aa5e41336f48c3dc709ac52da2bb167c5b8271d9", size = 206587 }, + { url = "https://files.pythonhosted.org/packages/7e/70/3def227f1ec56f5c69dfc8392b8bd63b11a18ca8178d9211d7cc5e5e4f27/charset_normalizer-3.4.6-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:a26611d9987b230566f24a0a125f17fe0de6a6aff9f25c9f564aaa2721a5fb88", size = 194724 }, + { url = "https://files.pythonhosted.org/packages/58/ab/9318352e220c05efd31c2779a23b50969dc94b985a2efa643ed9077bfca5/charset_normalizer-3.4.6-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:34315ff4fc374b285ad7f4a0bf7dcbfe769e1b104230d40f49f700d4ab6bbd84", size = 202956 }, + { url = "https://files.pythonhosted.org/packages/75/13/f3550a3ac25b70f87ac98c40d3199a8503676c2f1620efbf8d42095cfc40/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5f8ddd609f9e1af8c7bd6e2aca279c931aefecd148a14402d4e368f3171769fd", size = 201923 }, + { url = "https://files.pythonhosted.org/packages/1b/db/c5c643b912740b45e8eec21de1bbab8e7fc085944d37e1e709d3dcd9d72f/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:80d0a5615143c0b3225e5e3ef22c8d5d51f3f72ce0ea6fb84c943546c7b25b6c", size = 195366 }, + { url = "https://files.pythonhosted.org/packages/5a/67/3b1c62744f9b2448443e0eb160d8b001c849ec3fef591e012eda6484787c/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:92734d4d8d187a354a556626c221cd1a892a4e0802ccb2af432a1d85ec012194", size = 219752 }, + { url = "https://files.pythonhosted.org/packages/f6/98/32ffbaf7f0366ffb0445930b87d103f6b406bc2c271563644bde8a2b1093/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:613f19aa6e082cf96e17e3ffd89383343d0d589abda756b7764cf78361fd41dc", size = 203296 }, + { url = "https://files.pythonhosted.org/packages/41/12/5d308c1bbe60cabb0c5ef511574a647067e2a1f631bc8634fcafaccd8293/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2b1a63e8224e401cafe7739f77efd3f9e7f5f2026bda4aead8e59afab537784f", size = 215956 }, + { url = "https://files.pythonhosted.org/packages/53/e9/5f85f6c5e20669dbe56b165c67b0260547dea97dba7e187938833d791687/charset_normalizer-3.4.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6cceb5473417d28edd20c6c984ab6fee6c6267d38d906823ebfe20b03d607dc2", size = 208652 }, + { url = "https://files.pythonhosted.org/packages/f1/11/897052ea6af56df3eef3ca94edafee410ca699ca0c7b87960ad19932c55e/charset_normalizer-3.4.6-cp313-cp313-win32.whl", hash = "sha256:d7de2637729c67d67cf87614b566626057e95c303bc0a55ffe391f5205e7003d", size = 143940 }, + { url = "https://files.pythonhosted.org/packages/a1/5c/724b6b363603e419829f561c854b87ed7c7e31231a7908708ac086cdf3e2/charset_normalizer-3.4.6-cp313-cp313-win_amd64.whl", hash = "sha256:572d7c822caf521f0525ba1bce1a622a0b85cf47ffbdae6c9c19e3b5ac3c4389", size = 154101 }, + { url = "https://files.pythonhosted.org/packages/01/a5/7abf15b4c0968e47020f9ca0935fb3274deb87cb288cd187cad92e8cdffd/charset_normalizer-3.4.6-cp313-cp313-win_arm64.whl", hash = "sha256:a4474d924a47185a06411e0064b803c68be044be2d60e50e8bddcc2649957c1f", size = 143109 }, + { url = "https://files.pythonhosted.org/packages/2a/68/687187c7e26cb24ccbd88e5069f5ef00eba804d36dde11d99aad0838ab45/charset_normalizer-3.4.6-py3-none-any.whl", hash = "sha256:947cf925bc916d90adba35a64c82aace04fa39b46b52d4630ece166655905a69", size = 61455 }, ] [[package]] @@ -134,86 +134,86 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065 } wheels = [ - { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274 }, ] [[package]] name = "colorama" version = "0.4.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, ] [[package]] name = "coverage" version = "7.13.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9d/e0/70553e3000e345daff267cec284ce4cbf3fc141b6da229ac52775b5428f1/coverage-7.13.5.tar.gz", hash = "sha256:c81f6515c4c40141f83f502b07bbfa5c240ba25bbe73da7b33f1e5b6120ff179", size = 915967, upload-time = "2026-03-17T10:33:18.341Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/c3/a396306ba7db865bf96fc1fb3b7fd29bcbf3d829df642e77b13555163cd6/coverage-7.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:460cf0114c5016fa841214ff5564aa4864f11948da9440bc97e21ad1f4ba1e01", size = 219554, upload-time = "2026-03-17T10:30:42.208Z" }, - { url = "https://files.pythonhosted.org/packages/a6/16/a68a19e5384e93f811dccc51034b1fd0b865841c390e3c931dcc4699e035/coverage-7.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e223ce4b4ed47f065bfb123687686512e37629be25cc63728557ae7db261422", size = 219908, upload-time = "2026-03-17T10:30:43.906Z" }, - { url = "https://files.pythonhosted.org/packages/29/72/20b917c6793af3a5ceb7fb9c50033f3ec7865f2911a1416b34a7cfa0813b/coverage-7.13.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6e3370441f4513c6252bf042b9c36d22491142385049243253c7e48398a15a9f", size = 251419, upload-time = "2026-03-17T10:30:45.545Z" }, - { url = "https://files.pythonhosted.org/packages/8c/49/cd14b789536ac6a4778c453c6a2338bc0a2fb60c5a5a41b4008328b9acc1/coverage-7.13.5-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:03ccc709a17a1de074fb1d11f217342fb0d2b1582ed544f554fc9fc3f07e95f5", size = 254159, upload-time = "2026-03-17T10:30:47.204Z" }, - { url = "https://files.pythonhosted.org/packages/9d/00/7b0edcfe64e2ed4c0340dac14a52ad0f4c9bd0b8b5e531af7d55b703db7c/coverage-7.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f4818d065964db3c1c66dc0fbdac5ac692ecbc875555e13374fdbe7eedb4376", size = 255270, upload-time = "2026-03-17T10:30:48.812Z" }, - { url = "https://files.pythonhosted.org/packages/93/89/7ffc4ba0f5d0a55c1e84ea7cee39c9fc06af7b170513d83fbf3bbefce280/coverage-7.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:012d5319e66e9d5a218834642d6c35d265515a62f01157a45bcc036ecf947256", size = 257538, upload-time = "2026-03-17T10:30:50.77Z" }, - { url = "https://files.pythonhosted.org/packages/81/bd/73ddf85f93f7e6fa83e77ccecb6162d9415c79007b4bc124008a4995e4a7/coverage-7.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8dd02af98971bdb956363e4827d34425cb3df19ee550ef92855b0acb9c7ce51c", size = 251821, upload-time = "2026-03-17T10:30:52.5Z" }, - { url = "https://files.pythonhosted.org/packages/a0/81/278aff4e8dec4926a0bcb9486320752811f543a3ce5b602cc7a29978d073/coverage-7.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f08fd75c50a760c7eb068ae823777268daaf16a80b918fa58eea888f8e3919f5", size = 253191, upload-time = "2026-03-17T10:30:54.543Z" }, - { url = "https://files.pythonhosted.org/packages/70/ee/fe1621488e2e0a58d7e94c4800f0d96f79671553488d401a612bebae324b/coverage-7.13.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:843ea8643cf967d1ac7e8ecd4bb00c99135adf4816c0c0593fdcc47b597fcf09", size = 251337, upload-time = "2026-03-17T10:30:56.663Z" }, - { url = "https://files.pythonhosted.org/packages/37/a6/f79fb37aa104b562207cc23cb5711ab6793608e246cae1e93f26b2236ed9/coverage-7.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9d44d7aa963820b1b971dbecd90bfe5fe8f81cff79787eb6cca15750bd2f79b9", size = 255404, upload-time = "2026-03-17T10:30:58.427Z" }, - { url = "https://files.pythonhosted.org/packages/75/f0/ed15262a58ec81ce457ceb717b7f78752a1713556b19081b76e90896e8d4/coverage-7.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7132bed4bd7b836200c591410ae7d97bf7ae8be6fc87d160b2bd881df929e7bf", size = 250903, upload-time = "2026-03-17T10:31:00.093Z" }, - { url = "https://files.pythonhosted.org/packages/0f/e9/9129958f20e7e9d4d56d51d42ccf708d15cac355ff4ac6e736e97a9393d2/coverage-7.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a698e363641b98843c517817db75373c83254781426e94ada3197cabbc2c919c", size = 252780, upload-time = "2026-03-17T10:31:01.916Z" }, - { url = "https://files.pythonhosted.org/packages/a4/d7/0ad9b15812d81272db94379fe4c6df8fd17781cc7671fdfa30c76ba5ff7b/coverage-7.13.5-cp312-cp312-win32.whl", hash = "sha256:bdba0a6b8812e8c7df002d908a9a2ea3c36e92611b5708633c50869e6d922fdf", size = 222093, upload-time = "2026-03-17T10:31:03.642Z" }, - { url = "https://files.pythonhosted.org/packages/29/3d/821a9a5799fac2556bcf0bd37a70d1d11fa9e49784b6d22e92e8b2f85f18/coverage-7.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:d2c87e0c473a10bffe991502eac389220533024c8082ec1ce849f4218dded810", size = 222900, upload-time = "2026-03-17T10:31:05.651Z" }, - { url = "https://files.pythonhosted.org/packages/d4/fa/2238c2ad08e35cf4f020ea721f717e09ec3152aea75d191a7faf3ef009a8/coverage-7.13.5-cp312-cp312-win_arm64.whl", hash = "sha256:bf69236a9a81bdca3bff53796237aab096cdbf8d78a66ad61e992d9dac7eb2de", size = 221515, upload-time = "2026-03-17T10:31:07.293Z" }, - { url = "https://files.pythonhosted.org/packages/74/8c/74fedc9663dcf168b0a059d4ea756ecae4da77a489048f94b5f512a8d0b3/coverage-7.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ec4af212df513e399cf11610cc27063f1586419e814755ab362e50a85ea69c1", size = 219576, upload-time = "2026-03-17T10:31:09.045Z" }, - { url = "https://files.pythonhosted.org/packages/0c/c9/44fb661c55062f0818a6ffd2685c67aa30816200d5f2817543717d4b92eb/coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:941617e518602e2d64942c88ec8499f7fbd49d3f6c4327d3a71d43a1973032f3", size = 219942, upload-time = "2026-03-17T10:31:10.708Z" }, - { url = "https://files.pythonhosted.org/packages/5f/13/93419671cee82b780bab7ea96b67c8ef448f5f295f36bf5031154ec9a790/coverage-7.13.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:da305e9937617ee95c2e39d8ff9f040e0487cbf1ac174f777ed5eddd7a7c1f26", size = 250935, upload-time = "2026-03-17T10:31:12.392Z" }, - { url = "https://files.pythonhosted.org/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:78e696e1cc714e57e8b25760b33a8b1026b7048d270140d25dafe1b0a1ee05a3", size = 253541, upload-time = "2026-03-17T10:31:14.247Z" }, - { url = "https://files.pythonhosted.org/packages/4e/5e/3ee3b835647be646dcf3c65a7c6c18f87c27326a858f72ab22c12730773d/coverage-7.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02ca0eed225b2ff301c474aeeeae27d26e2537942aa0f87491d3e147e784a82b", size = 254780, upload-time = "2026-03-17T10:31:16.193Z" }, - { url = "https://files.pythonhosted.org/packages/44/b3/cb5bd1a04cfcc49ede6cd8409d80bee17661167686741e041abc7ee1b9a9/coverage-7.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:04690832cbea4e4663d9149e05dba142546ca05cb1848816760e7f58285c970a", size = 256912, upload-time = "2026-03-17T10:31:17.89Z" }, - { url = "https://files.pythonhosted.org/packages/1b/66/c1dceb7b9714473800b075f5c8a84f4588f887a90eb8645282031676e242/coverage-7.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0590e44dd2745c696a778f7bab6aa95256de2cbc8b8cff4f7db8ff09813d6969", size = 251165, upload-time = "2026-03-17T10:31:19.605Z" }, - { url = "https://files.pythonhosted.org/packages/b7/62/5502b73b97aa2e53ea22a39cf8649ff44827bef76d90bf638777daa27a9d/coverage-7.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d7cfad2d6d81dd298ab6b89fe72c3b7b05ec7544bdda3b707ddaecff8d25c161", size = 252908, upload-time = "2026-03-17T10:31:21.312Z" }, - { url = "https://files.pythonhosted.org/packages/7d/37/7792c2d69854397ca77a55c4646e5897c467928b0e27f2d235d83b5d08c6/coverage-7.13.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e092b9499de38ae0fbfbc603a74660eb6ff3e869e507b50d85a13b6db9863e15", size = 250873, upload-time = "2026-03-17T10:31:23.565Z" }, - { url = "https://files.pythonhosted.org/packages/a3/23/bc866fb6163be52a8a9e5d708ba0d3b1283c12158cefca0a8bbb6e247a43/coverage-7.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:48c39bc4a04d983a54a705a6389512883d4a3b9862991b3617d547940e9f52b1", size = 255030, upload-time = "2026-03-17T10:31:25.58Z" }, - { url = "https://files.pythonhosted.org/packages/7d/8b/ef67e1c222ef49860701d346b8bbb70881bef283bd5f6cbba68a39a086c7/coverage-7.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2d3807015f138ffea1ed9afeeb8624fd781703f2858b62a8dd8da5a0994c57b6", size = 250694, upload-time = "2026-03-17T10:31:27.316Z" }, - { url = "https://files.pythonhosted.org/packages/46/0d/866d1f74f0acddbb906db212e096dee77a8e2158ca5e6bb44729f9d93298/coverage-7.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee2aa19e03161671ec964004fb74b2257805d9710bf14a5c704558b9d8dbaf17", size = 252469, upload-time = "2026-03-17T10:31:29.472Z" }, - { url = "https://files.pythonhosted.org/packages/7a/f5/be742fec31118f02ce42b21c6af187ad6a344fed546b56ca60caacc6a9a0/coverage-7.13.5-cp313-cp313-win32.whl", hash = "sha256:ce1998c0483007608c8382f4ff50164bfc5bd07a2246dd272aa4043b75e61e85", size = 222112, upload-time = "2026-03-17T10:31:31.526Z" }, - { url = "https://files.pythonhosted.org/packages/66/40/7732d648ab9d069a46e686043241f01206348e2bbf128daea85be4d6414b/coverage-7.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:631efb83f01569670a5e866ceb80fe483e7c159fac6f167e6571522636104a0b", size = 222923, upload-time = "2026-03-17T10:31:33.633Z" }, - { url = "https://files.pythonhosted.org/packages/48/af/fea819c12a095781f6ccd504890aaddaf88b8fab263c4940e82c7b770124/coverage-7.13.5-cp313-cp313-win_arm64.whl", hash = "sha256:f4cd16206ad171cbc2470dbea9103cf9a7607d5fe8c242fdf1edf36174020664", size = 221540, upload-time = "2026-03-17T10:31:35.445Z" }, - { url = "https://files.pythonhosted.org/packages/23/d2/17879af479df7fbbd44bd528a31692a48f6b25055d16482fdf5cdb633805/coverage-7.13.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0428cbef5783ad91fe240f673cc1f76b25e74bbfe1a13115e4aa30d3f538162d", size = 220262, upload-time = "2026-03-17T10:31:37.184Z" }, - { url = "https://files.pythonhosted.org/packages/5b/4c/d20e554f988c8f91d6a02c5118f9abbbf73a8768a3048cb4962230d5743f/coverage-7.13.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e0b216a19534b2427cc201a26c25da4a48633f29a487c61258643e89d28200c0", size = 220617, upload-time = "2026-03-17T10:31:39.245Z" }, - { url = "https://files.pythonhosted.org/packages/29/9c/f9f5277b95184f764b24e7231e166dfdb5780a46d408a2ac665969416d61/coverage-7.13.5-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:972a9cd27894afe4bc2b1480107054e062df08e671df7c2f18c205e805ccd806", size = 261912, upload-time = "2026-03-17T10:31:41.324Z" }, - { url = "https://files.pythonhosted.org/packages/d5/f6/7f1ab39393eeb50cfe4747ae8ef0e4fc564b989225aa1152e13a180d74f8/coverage-7.13.5-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4b59148601efcd2bac8c4dbf1f0ad6391693ccf7a74b8205781751637076aee3", size = 263987, upload-time = "2026-03-17T10:31:43.724Z" }, - { url = "https://files.pythonhosted.org/packages/a0/d7/62c084fb489ed9c6fbdf57e006752e7c516ea46fd690e5ed8b8617c7d52e/coverage-7.13.5-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:505d7083c8b0c87a8fa8c07370c285847c1f77739b22e299ad75a6af6c32c5c9", size = 266416, upload-time = "2026-03-17T10:31:45.769Z" }, - { url = "https://files.pythonhosted.org/packages/a9/f6/df63d8660e1a0bff6125947afda112a0502736f470d62ca68b288ea762d8/coverage-7.13.5-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:60365289c3741e4db327e7baff2a4aaacf22f788e80fa4683393891b70a89fbd", size = 267558, upload-time = "2026-03-17T10:31:48.293Z" }, - { url = "https://files.pythonhosted.org/packages/5b/02/353ca81d36779bd108f6d384425f7139ac3c58c750dcfaafe5d0bee6436b/coverage-7.13.5-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1b88c69c8ef5d4b6fe7dea66d6636056a0f6a7527c440e890cf9259011f5e606", size = 261163, upload-time = "2026-03-17T10:31:50.125Z" }, - { url = "https://files.pythonhosted.org/packages/2c/16/2e79106d5749bcaf3aee6d309123548e3276517cd7851faa8da213bc61bf/coverage-7.13.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5b13955d31d1633cf9376908089b7cebe7d15ddad7aeaabcbe969a595a97e95e", size = 263981, upload-time = "2026-03-17T10:31:51.961Z" }, - { url = "https://files.pythonhosted.org/packages/29/c7/c29e0c59ffa6942030ae6f50b88ae49988e7e8da06de7ecdbf49c6d4feae/coverage-7.13.5-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f70c9ab2595c56f81a89620e22899eea8b212a4041bd728ac6f4a28bf5d3ddd0", size = 261604, upload-time = "2026-03-17T10:31:53.872Z" }, - { url = "https://files.pythonhosted.org/packages/40/48/097cdc3db342f34006a308ab41c3a7c11c3f0d84750d340f45d88a782e00/coverage-7.13.5-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:084b84a8c63e8d6fc7e3931b316a9bcafca1458d753c539db82d31ed20091a87", size = 265321, upload-time = "2026-03-17T10:31:55.997Z" }, - { url = "https://files.pythonhosted.org/packages/bb/1f/4994af354689e14fd03a75f8ec85a9a68d94e0188bbdab3fc1516b55e512/coverage-7.13.5-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ad14385487393e386e2ea988b09d62dd42c397662ac2dabc3832d71253eee479", size = 260502, upload-time = "2026-03-17T10:31:58.308Z" }, - { url = "https://files.pythonhosted.org/packages/22/c6/9bb9ef55903e628033560885f5c31aa227e46878118b63ab15dc7ba87797/coverage-7.13.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7f2c47b36fe7709a6e83bfadf4eefb90bd25fbe4014d715224c4316f808e59a2", size = 262688, upload-time = "2026-03-17T10:32:00.141Z" }, - { url = "https://files.pythonhosted.org/packages/14/4f/f5df9007e50b15e53e01edea486814783a7f019893733d9e4d6caad75557/coverage-7.13.5-cp313-cp313t-win32.whl", hash = "sha256:67e9bc5449801fad0e5dff329499fb090ba4c5800b86805c80617b4e29809b2a", size = 222788, upload-time = "2026-03-17T10:32:02.246Z" }, - { url = "https://files.pythonhosted.org/packages/e1/98/aa7fccaa97d0f3192bec013c4e6fd6d294a6ed44b640e6bb61f479e00ed5/coverage-7.13.5-cp313-cp313t-win_amd64.whl", hash = "sha256:da86cdcf10d2519e10cabb8ac2de03da1bcb6e4853790b7fbd48523332e3a819", size = 223851, upload-time = "2026-03-17T10:32:04.416Z" }, - { url = "https://files.pythonhosted.org/packages/3d/8b/e5c469f7352651e5f013198e9e21f97510b23de957dd06a84071683b4b60/coverage-7.13.5-cp313-cp313t-win_arm64.whl", hash = "sha256:0ecf12ecb326fe2c339d93fc131816f3a7367d223db37817208905c89bded911", size = 222104, upload-time = "2026-03-17T10:32:06.65Z" }, - { url = "https://files.pythonhosted.org/packages/9e/ee/a4cf96b8ce1e566ed238f0659ac2d3f007ed1d14b181bcb684e19561a69a/coverage-7.13.5-py3-none-any.whl", hash = "sha256:34b02417cf070e173989b3db962f7ed56d2f644307b2cf9d5a0f258e13084a61", size = 211346, upload-time = "2026-03-17T10:33:15.691Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/9d/e0/70553e3000e345daff267cec284ce4cbf3fc141b6da229ac52775b5428f1/coverage-7.13.5.tar.gz", hash = "sha256:c81f6515c4c40141f83f502b07bbfa5c240ba25bbe73da7b33f1e5b6120ff179", size = 915967 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/c3/a396306ba7db865bf96fc1fb3b7fd29bcbf3d829df642e77b13555163cd6/coverage-7.13.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:460cf0114c5016fa841214ff5564aa4864f11948da9440bc97e21ad1f4ba1e01", size = 219554 }, + { url = "https://files.pythonhosted.org/packages/a6/16/a68a19e5384e93f811dccc51034b1fd0b865841c390e3c931dcc4699e035/coverage-7.13.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e223ce4b4ed47f065bfb123687686512e37629be25cc63728557ae7db261422", size = 219908 }, + { url = "https://files.pythonhosted.org/packages/29/72/20b917c6793af3a5ceb7fb9c50033f3ec7865f2911a1416b34a7cfa0813b/coverage-7.13.5-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:6e3370441f4513c6252bf042b9c36d22491142385049243253c7e48398a15a9f", size = 251419 }, + { url = "https://files.pythonhosted.org/packages/8c/49/cd14b789536ac6a4778c453c6a2338bc0a2fb60c5a5a41b4008328b9acc1/coverage-7.13.5-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:03ccc709a17a1de074fb1d11f217342fb0d2b1582ed544f554fc9fc3f07e95f5", size = 254159 }, + { url = "https://files.pythonhosted.org/packages/9d/00/7b0edcfe64e2ed4c0340dac14a52ad0f4c9bd0b8b5e531af7d55b703db7c/coverage-7.13.5-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3f4818d065964db3c1c66dc0fbdac5ac692ecbc875555e13374fdbe7eedb4376", size = 255270 }, + { url = "https://files.pythonhosted.org/packages/93/89/7ffc4ba0f5d0a55c1e84ea7cee39c9fc06af7b170513d83fbf3bbefce280/coverage-7.13.5-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:012d5319e66e9d5a218834642d6c35d265515a62f01157a45bcc036ecf947256", size = 257538 }, + { url = "https://files.pythonhosted.org/packages/81/bd/73ddf85f93f7e6fa83e77ccecb6162d9415c79007b4bc124008a4995e4a7/coverage-7.13.5-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8dd02af98971bdb956363e4827d34425cb3df19ee550ef92855b0acb9c7ce51c", size = 251821 }, + { url = "https://files.pythonhosted.org/packages/a0/81/278aff4e8dec4926a0bcb9486320752811f543a3ce5b602cc7a29978d073/coverage-7.13.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f08fd75c50a760c7eb068ae823777268daaf16a80b918fa58eea888f8e3919f5", size = 253191 }, + { url = "https://files.pythonhosted.org/packages/70/ee/fe1621488e2e0a58d7e94c4800f0d96f79671553488d401a612bebae324b/coverage-7.13.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:843ea8643cf967d1ac7e8ecd4bb00c99135adf4816c0c0593fdcc47b597fcf09", size = 251337 }, + { url = "https://files.pythonhosted.org/packages/37/a6/f79fb37aa104b562207cc23cb5711ab6793608e246cae1e93f26b2236ed9/coverage-7.13.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:9d44d7aa963820b1b971dbecd90bfe5fe8f81cff79787eb6cca15750bd2f79b9", size = 255404 }, + { url = "https://files.pythonhosted.org/packages/75/f0/ed15262a58ec81ce457ceb717b7f78752a1713556b19081b76e90896e8d4/coverage-7.13.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:7132bed4bd7b836200c591410ae7d97bf7ae8be6fc87d160b2bd881df929e7bf", size = 250903 }, + { url = "https://files.pythonhosted.org/packages/0f/e9/9129958f20e7e9d4d56d51d42ccf708d15cac355ff4ac6e736e97a9393d2/coverage-7.13.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a698e363641b98843c517817db75373c83254781426e94ada3197cabbc2c919c", size = 252780 }, + { url = "https://files.pythonhosted.org/packages/a4/d7/0ad9b15812d81272db94379fe4c6df8fd17781cc7671fdfa30c76ba5ff7b/coverage-7.13.5-cp312-cp312-win32.whl", hash = "sha256:bdba0a6b8812e8c7df002d908a9a2ea3c36e92611b5708633c50869e6d922fdf", size = 222093 }, + { url = "https://files.pythonhosted.org/packages/29/3d/821a9a5799fac2556bcf0bd37a70d1d11fa9e49784b6d22e92e8b2f85f18/coverage-7.13.5-cp312-cp312-win_amd64.whl", hash = "sha256:d2c87e0c473a10bffe991502eac389220533024c8082ec1ce849f4218dded810", size = 222900 }, + { url = "https://files.pythonhosted.org/packages/d4/fa/2238c2ad08e35cf4f020ea721f717e09ec3152aea75d191a7faf3ef009a8/coverage-7.13.5-cp312-cp312-win_arm64.whl", hash = "sha256:bf69236a9a81bdca3bff53796237aab096cdbf8d78a66ad61e992d9dac7eb2de", size = 221515 }, + { url = "https://files.pythonhosted.org/packages/74/8c/74fedc9663dcf168b0a059d4ea756ecae4da77a489048f94b5f512a8d0b3/coverage-7.13.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5ec4af212df513e399cf11610cc27063f1586419e814755ab362e50a85ea69c1", size = 219576 }, + { url = "https://files.pythonhosted.org/packages/0c/c9/44fb661c55062f0818a6ffd2685c67aa30816200d5f2817543717d4b92eb/coverage-7.13.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:941617e518602e2d64942c88ec8499f7fbd49d3f6c4327d3a71d43a1973032f3", size = 219942 }, + { url = "https://files.pythonhosted.org/packages/5f/13/93419671cee82b780bab7ea96b67c8ef448f5f295f36bf5031154ec9a790/coverage-7.13.5-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:da305e9937617ee95c2e39d8ff9f040e0487cbf1ac174f777ed5eddd7a7c1f26", size = 250935 }, + { url = "https://files.pythonhosted.org/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:78e696e1cc714e57e8b25760b33a8b1026b7048d270140d25dafe1b0a1ee05a3", size = 253541 }, + { url = "https://files.pythonhosted.org/packages/4e/5e/3ee3b835647be646dcf3c65a7c6c18f87c27326a858f72ab22c12730773d/coverage-7.13.5-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:02ca0eed225b2ff301c474aeeeae27d26e2537942aa0f87491d3e147e784a82b", size = 254780 }, + { url = "https://files.pythonhosted.org/packages/44/b3/cb5bd1a04cfcc49ede6cd8409d80bee17661167686741e041abc7ee1b9a9/coverage-7.13.5-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:04690832cbea4e4663d9149e05dba142546ca05cb1848816760e7f58285c970a", size = 256912 }, + { url = "https://files.pythonhosted.org/packages/1b/66/c1dceb7b9714473800b075f5c8a84f4588f887a90eb8645282031676e242/coverage-7.13.5-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0590e44dd2745c696a778f7bab6aa95256de2cbc8b8cff4f7db8ff09813d6969", size = 251165 }, + { url = "https://files.pythonhosted.org/packages/b7/62/5502b73b97aa2e53ea22a39cf8649ff44827bef76d90bf638777daa27a9d/coverage-7.13.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d7cfad2d6d81dd298ab6b89fe72c3b7b05ec7544bdda3b707ddaecff8d25c161", size = 252908 }, + { url = "https://files.pythonhosted.org/packages/7d/37/7792c2d69854397ca77a55c4646e5897c467928b0e27f2d235d83b5d08c6/coverage-7.13.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:e092b9499de38ae0fbfbc603a74660eb6ff3e869e507b50d85a13b6db9863e15", size = 250873 }, + { url = "https://files.pythonhosted.org/packages/a3/23/bc866fb6163be52a8a9e5d708ba0d3b1283c12158cefca0a8bbb6e247a43/coverage-7.13.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:48c39bc4a04d983a54a705a6389512883d4a3b9862991b3617d547940e9f52b1", size = 255030 }, + { url = "https://files.pythonhosted.org/packages/7d/8b/ef67e1c222ef49860701d346b8bbb70881bef283bd5f6cbba68a39a086c7/coverage-7.13.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2d3807015f138ffea1ed9afeeb8624fd781703f2858b62a8dd8da5a0994c57b6", size = 250694 }, + { url = "https://files.pythonhosted.org/packages/46/0d/866d1f74f0acddbb906db212e096dee77a8e2158ca5e6bb44729f9d93298/coverage-7.13.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ee2aa19e03161671ec964004fb74b2257805d9710bf14a5c704558b9d8dbaf17", size = 252469 }, + { url = "https://files.pythonhosted.org/packages/7a/f5/be742fec31118f02ce42b21c6af187ad6a344fed546b56ca60caacc6a9a0/coverage-7.13.5-cp313-cp313-win32.whl", hash = "sha256:ce1998c0483007608c8382f4ff50164bfc5bd07a2246dd272aa4043b75e61e85", size = 222112 }, + { url = "https://files.pythonhosted.org/packages/66/40/7732d648ab9d069a46e686043241f01206348e2bbf128daea85be4d6414b/coverage-7.13.5-cp313-cp313-win_amd64.whl", hash = "sha256:631efb83f01569670a5e866ceb80fe483e7c159fac6f167e6571522636104a0b", size = 222923 }, + { url = "https://files.pythonhosted.org/packages/48/af/fea819c12a095781f6ccd504890aaddaf88b8fab263c4940e82c7b770124/coverage-7.13.5-cp313-cp313-win_arm64.whl", hash = "sha256:f4cd16206ad171cbc2470dbea9103cf9a7607d5fe8c242fdf1edf36174020664", size = 221540 }, + { url = "https://files.pythonhosted.org/packages/23/d2/17879af479df7fbbd44bd528a31692a48f6b25055d16482fdf5cdb633805/coverage-7.13.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0428cbef5783ad91fe240f673cc1f76b25e74bbfe1a13115e4aa30d3f538162d", size = 220262 }, + { url = "https://files.pythonhosted.org/packages/5b/4c/d20e554f988c8f91d6a02c5118f9abbbf73a8768a3048cb4962230d5743f/coverage-7.13.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e0b216a19534b2427cc201a26c25da4a48633f29a487c61258643e89d28200c0", size = 220617 }, + { url = "https://files.pythonhosted.org/packages/29/9c/f9f5277b95184f764b24e7231e166dfdb5780a46d408a2ac665969416d61/coverage-7.13.5-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:972a9cd27894afe4bc2b1480107054e062df08e671df7c2f18c205e805ccd806", size = 261912 }, + { url = "https://files.pythonhosted.org/packages/d5/f6/7f1ab39393eeb50cfe4747ae8ef0e4fc564b989225aa1152e13a180d74f8/coverage-7.13.5-cp313-cp313t-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4b59148601efcd2bac8c4dbf1f0ad6391693ccf7a74b8205781751637076aee3", size = 263987 }, + { url = "https://files.pythonhosted.org/packages/a0/d7/62c084fb489ed9c6fbdf57e006752e7c516ea46fd690e5ed8b8617c7d52e/coverage-7.13.5-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:505d7083c8b0c87a8fa8c07370c285847c1f77739b22e299ad75a6af6c32c5c9", size = 266416 }, + { url = "https://files.pythonhosted.org/packages/a9/f6/df63d8660e1a0bff6125947afda112a0502736f470d62ca68b288ea762d8/coverage-7.13.5-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:60365289c3741e4db327e7baff2a4aaacf22f788e80fa4683393891b70a89fbd", size = 267558 }, + { url = "https://files.pythonhosted.org/packages/5b/02/353ca81d36779bd108f6d384425f7139ac3c58c750dcfaafe5d0bee6436b/coverage-7.13.5-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:1b88c69c8ef5d4b6fe7dea66d6636056a0f6a7527c440e890cf9259011f5e606", size = 261163 }, + { url = "https://files.pythonhosted.org/packages/2c/16/2e79106d5749bcaf3aee6d309123548e3276517cd7851faa8da213bc61bf/coverage-7.13.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:5b13955d31d1633cf9376908089b7cebe7d15ddad7aeaabcbe969a595a97e95e", size = 263981 }, + { url = "https://files.pythonhosted.org/packages/29/c7/c29e0c59ffa6942030ae6f50b88ae49988e7e8da06de7ecdbf49c6d4feae/coverage-7.13.5-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:f70c9ab2595c56f81a89620e22899eea8b212a4041bd728ac6f4a28bf5d3ddd0", size = 261604 }, + { url = "https://files.pythonhosted.org/packages/40/48/097cdc3db342f34006a308ab41c3a7c11c3f0d84750d340f45d88a782e00/coverage-7.13.5-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:084b84a8c63e8d6fc7e3931b316a9bcafca1458d753c539db82d31ed20091a87", size = 265321 }, + { url = "https://files.pythonhosted.org/packages/bb/1f/4994af354689e14fd03a75f8ec85a9a68d94e0188bbdab3fc1516b55e512/coverage-7.13.5-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ad14385487393e386e2ea988b09d62dd42c397662ac2dabc3832d71253eee479", size = 260502 }, + { url = "https://files.pythonhosted.org/packages/22/c6/9bb9ef55903e628033560885f5c31aa227e46878118b63ab15dc7ba87797/coverage-7.13.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:7f2c47b36fe7709a6e83bfadf4eefb90bd25fbe4014d715224c4316f808e59a2", size = 262688 }, + { url = "https://files.pythonhosted.org/packages/14/4f/f5df9007e50b15e53e01edea486814783a7f019893733d9e4d6caad75557/coverage-7.13.5-cp313-cp313t-win32.whl", hash = "sha256:67e9bc5449801fad0e5dff329499fb090ba4c5800b86805c80617b4e29809b2a", size = 222788 }, + { url = "https://files.pythonhosted.org/packages/e1/98/aa7fccaa97d0f3192bec013c4e6fd6d294a6ed44b640e6bb61f479e00ed5/coverage-7.13.5-cp313-cp313t-win_amd64.whl", hash = "sha256:da86cdcf10d2519e10cabb8ac2de03da1bcb6e4853790b7fbd48523332e3a819", size = 223851 }, + { url = "https://files.pythonhosted.org/packages/3d/8b/e5c469f7352651e5f013198e9e21f97510b23de957dd06a84071683b4b60/coverage-7.13.5-cp313-cp313t-win_arm64.whl", hash = "sha256:0ecf12ecb326fe2c339d93fc131816f3a7367d223db37817208905c89bded911", size = 222104 }, + { url = "https://files.pythonhosted.org/packages/9e/ee/a4cf96b8ce1e566ed238f0659ac2d3f007ed1d14b181bcb684e19561a69a/coverage-7.13.5-py3-none-any.whl", hash = "sha256:34b02417cf070e173989b3db962f7ed56d2f644307b2cf9d5a0f258e13084a61", size = 211346 }, ] [[package]] name = "daff" version = "1.4.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e1/d0/c0a1374db3afad0f9dfe6c795e5df102af03d49ad5e6e8502fb09eb88110/daff-1.4.2.tar.gz", hash = "sha256:47f0391eda7e2b5011f7ccac006b9178accb465bcb94a2c9f284257fff5d2686", size = 148251, upload-time = "2025-05-04T19:24:11.521Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/d0/c0a1374db3afad0f9dfe6c795e5df102af03d49ad5e6e8502fb09eb88110/daff-1.4.2.tar.gz", hash = "sha256:47f0391eda7e2b5011f7ccac006b9178accb465bcb94a2c9f284257fff5d2686", size = 148251 } wheels = [ - { url = "https://files.pythonhosted.org/packages/29/fe/d54a874e8d7b88bc03c459f63a993305db50039b734fab751a0466dabfc1/daff-1.4.2-py3-none-any.whl", hash = "sha256:88981a21d065e4378b5c4bd40b975dbfdea9b7ff540071f3bb5e20cc8b3590b5", size = 144922, upload-time = "2025-05-04T19:24:09.999Z" }, + { url = "https://files.pythonhosted.org/packages/29/fe/d54a874e8d7b88bc03c459f63a993305db50039b734fab751a0466dabfc1/daff-1.4.2-py3-none-any.whl", hash = "sha256:88981a21d065e4378b5c4bd40b975dbfdea9b7ff540071f3bb5e20cc8b3590b5", size = 144922 }, ] [[package]] name = "database-tycoon" -version = "0.1.10" +version = "0.1.11" source = { editable = "." } dependencies = [ { name = "dbt-core" }, @@ -225,6 +225,7 @@ dependencies = [ { name = "pydantic" }, { name = "pyyaml" }, { name = "rich" }, + { name = "ruamel-yaml" }, { name = "typer" }, ] @@ -238,12 +239,13 @@ docs = [ dev = [ { name = "pytest" }, { name = "pytest-cov" }, + { name = "ruff" }, { name = "syrupy" }, ] [package.metadata] requires-dist = [ - { name = "dbt-core", specifier = "==1.11.8" }, + { name = "dbt-core", specifier = "==1.12.0" }, { name = "dbt-duckdb", specifier = "==1.10.1" }, { name = "dlt", extras = ["duckdb"], specifier = "==1.26.0" }, { name = "duckdb", specifier = "==1.5.2" }, @@ -254,7 +256,8 @@ requires-dist = [ { name = "pydantic", specifier = "==2.13.3" }, { name = "pyyaml", specifier = "==6.0.3" }, { name = "rich", specifier = "==15.0.0" }, - { name = "typer", specifier = "==0.25.0" }, + { name = "ruamel-yaml", specifier = "==0.19.1" }, + { name = "typer", specifier = "==0.27.0" }, ] provides-extras = ["docs"] @@ -262,12 +265,13 @@ provides-extras = ["docs"] dev = [ { name = "pytest", specifier = "==9.0.3" }, { name = "pytest-cov", specifier = ">=7.1.0" }, + { name = "ruff", specifier = "==0.15.20" }, { name = "syrupy", specifier = "==5.2.0" }, ] [[package]] name = "dbt-adapters" -version = "1.22.9" +version = "1.24.5" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "agate" }, @@ -278,14 +282,14 @@ dependencies = [ { name = "pytz" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6e/99/7dca7d7f1aeb11297622980a438f2973662a5e41de79f5caf053d744cd16/dbt_adapters-1.22.9.tar.gz", hash = "sha256:cce00029910ce619ab2859727ea1d9ecc9ead61923d5211325c2a532e79efa53", size = 137298, upload-time = "2026-03-16T17:01:49.353Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/d4/bea32dd730def5b875a76c05d281d397ed720b9184d936cc4cedac605948/dbt_adapters-1.24.5.tar.gz", hash = "sha256:87de096a0d5b219905f1079b002f13e3447ffb63558f4f3c5b7278670d198acb", size = 146532 } wheels = [ - { url = "https://files.pythonhosted.org/packages/80/3b/a63fe87c5cc1c6e8c6ab56b704bdedd05412c6ae3b8b69ee81fd50f9f736/dbt_adapters-1.22.9-py3-none-any.whl", hash = "sha256:a882984f6298a4e8e5e5b79b55c9a8cbb02e0dc26ea8e6811e841252a37034cd", size = 173139, upload-time = "2026-03-16T17:01:47.522Z" }, + { url = "https://files.pythonhosted.org/packages/9c/c0/07f0e315458128c6ef246ba9698dc0f5e9333394ab8844508adac468c617/dbt_adapters-1.24.5-py3-none-any.whl", hash = "sha256:32345118b921160722a13f12c3e369b9d59a0fb8fb9b3297e29d8cd53f973411", size = 177259 }, ] [[package]] name = "dbt-common" -version = "1.37.3" +version = "1.38.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "agate" }, @@ -302,14 +306,14 @@ dependencies = [ { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/12/3a/c95078b7ebb87795551f73fd58e5ddbcf7f75b478e9b50ab72fe2939baf0/dbt_common-1.37.3.tar.gz", hash = "sha256:f99304cf93f549c09d302eb61d9b280748bbe24e2245e214189ea08b41196ec3", size = 86217, upload-time = "2026-03-02T17:26:34.027Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c5/27/1cb45b595e280d4e66b122c539655be280236af27a964114b8f163702674/dbt_common-1.38.0.tar.gz", hash = "sha256:05f9c682c1c09b6a0ce79fe18dd7244a4549c3740329692bc19be9e7498f516f", size = 87065 } wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/7e/629351d21ffa1b51a893334faf8c497f0c34f4da3cece9b24d7a5af29d90/dbt_common-1.37.3-py3-none-any.whl", hash = "sha256:e11b81903107d9f254d0ec7ac14b2bcf6d531e46456cbc7881fdbfeb9bbd8eec", size = 87733, upload-time = "2026-03-02T17:26:31.248Z" }, + { url = "https://files.pythonhosted.org/packages/cb/d9/11763d94eb3257c11463781c18aef3e9613829b9f0e55609a494e596f574/dbt_common-1.38.0-py3-none-any.whl", hash = "sha256:1646e5384a8e317fa63890e60e41ca50dd4c5a89e02ad9a141c184d4b672b696", size = 88460 }, ] [[package]] name = "dbt-core" -version = "1.11.8" +version = "1.12.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "agate" }, @@ -317,17 +321,19 @@ dependencies = [ { name = "daff" }, { name = "dbt-adapters" }, { name = "dbt-common" }, + { name = "dbt-core-experimental-parser" }, { name = "dbt-extractor" }, { name = "dbt-protos" }, - { name = "dbt-semantic-interfaces" }, { name = "jinja2" }, { name = "jsonschema" }, { name = "mashumaro", extra = ["msgpack"] }, + { name = "metricflow" }, { name = "networkx" }, { name = "packaging" }, { name = "pathspec" }, { name = "protobuf" }, { name = "pydantic" }, + { name = "python-dotenv" }, { name = "pytz" }, { name = "pyyaml" }, { name = "requests" }, @@ -335,11 +341,17 @@ dependencies = [ { name = "sqlparse" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/54/4f/5524b85f0bd94053ee51fe7e8b0011295f056da707c754975ed7da3888f9/dbt_core-1.11.8.tar.gz", hash = "sha256:1bfe0b40c958785680a1b2b894b0851f5bf780ddc058747dd1e62c389b0d3b1d", size = 972635, upload-time = "2026-04-08T19:01:46.63Z" } +sdist = { url = "https://files.pythonhosted.org/packages/50/a8/da65cff9c221b944040b35122ffcbaf0e7b583c6f0729a473c63d15536c5/dbt_core-1.12.0.tar.gz", hash = "sha256:f7d6a34f6600a371b8771e813f6b1566ab6d1fb8cea691619c791f527b1f8d7e", size = 1027023 } wheels = [ - { url = "https://files.pythonhosted.org/packages/36/47/a07061c70e0872d8f49fbd12cf93f481b22cabb2e75016ce00aa3520a7ab/dbt_core-1.11.8-py3-none-any.whl", hash = "sha256:13ff79252a7ae9c9acc7de243789b72f04f7f915f05930d3407edeeea3758b6f", size = 1061289, upload-time = "2026-04-08T19:01:44.83Z" }, + { url = "https://files.pythonhosted.org/packages/b2/88/ebcb8fa7f2fd8e82ed1bbbd0950fe3717101f1c16d10ee3aa33fac520ed2/dbt_core-1.12.0-py3-none-any.whl", hash = "sha256:8a91ec6aabaf329efc603330b629e72551ea6be8321483d7c284c598541faaab", size = 1123180 }, ] +[[package]] +name = "dbt-core-experimental-parser" +version = "2.0.0a5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/bc/d37f24f564cb32b3c13e470e5dfd8555b71c202580f701580ebd7e9cd10d/dbt_core_experimental_parser-2.0.0a5.tar.gz", hash = "sha256:1bba1807c697113a6a63a92bec3a89568c65d7168c7d17413591cbfa7d5c5375", size = 4474 } + [[package]] name = "dbt-duckdb" version = "1.10.1" @@ -350,64 +362,44 @@ dependencies = [ { name = "dbt-core" }, { name = "duckdb" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c5/d3/0f9f6a4de94e0f95dcfabbba5e8ef7670de695483345edd9e5b36e93d024/dbt_duckdb-1.10.1.tar.gz", hash = "sha256:5d6df1589d4ba21fe20ac08454a32763d3263798c9f5914280eabd1dc285cbc0", size = 145907, upload-time = "2026-02-17T17:29:04.283Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c5/d3/0f9f6a4de94e0f95dcfabbba5e8ef7670de695483345edd9e5b36e93d024/dbt_duckdb-1.10.1.tar.gz", hash = "sha256:5d6df1589d4ba21fe20ac08454a32763d3263798c9f5914280eabd1dc285cbc0", size = 145907 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/26/0ce2b1aeecdd1a18a9fac6f02d08f60c95944f036866f3fe37146298d4e4/dbt_duckdb-1.10.1-py3-none-any.whl", hash = "sha256:90658ecb367082786c5ea2ffbf9e35bb4116fa5ad1bc2f287c4dc1f3984bafa1", size = 85089, upload-time = "2026-02-17T17:29:03.117Z" }, + { url = "https://files.pythonhosted.org/packages/c5/26/0ce2b1aeecdd1a18a9fac6f02d08f60c95944f036866f3fe37146298d4e4/dbt_duckdb-1.10.1-py3-none-any.whl", hash = "sha256:90658ecb367082786c5ea2ffbf9e35bb4116fa5ad1bc2f287c4dc1f3984bafa1", size = 85089 }, ] [[package]] name = "dbt-extractor" version = "0.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f1/06/1f7b5d277af4bd7c3ab5065f79407c46a73950f0879fac69e51067c87649/dbt_extractor-0.6.0.tar.gz", hash = "sha256:d6cf08ec793b8bc2bd6e260ef818230ae68a4f71436fa489f08d7db1a52e2ffe", size = 270461, upload-time = "2025-04-07T16:46:30.532Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/06/1f7b5d277af4bd7c3ab5065f79407c46a73950f0879fac69e51067c87649/dbt_extractor-0.6.0.tar.gz", hash = "sha256:d6cf08ec793b8bc2bd6e260ef818230ae68a4f71436fa489f08d7db1a52e2ffe", size = 270461 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9f/dd/ec8f9e48e7dd5a52a69cca7907681d1779cf1cc8b02f2aa2acb6a2bf8bb4/dbt_extractor-0.6.0-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:4b6b1e70dde78cb904ca7a8958c2c803e77779b6ce108f4ea7ac479f5700db89", size = 790206, upload-time = "2025-04-07T16:46:05.352Z" }, - { url = "https://files.pythonhosted.org/packages/03/5f/233f326336aa21fbd9e7268f239a8464af145abd398a360d894c3286699d/dbt_extractor-0.6.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:dcf14ed245de8df269815ff4c4f555fa72d2621f4fff37c023b8c99d0e421b4f", size = 404381, upload-time = "2025-04-07T16:46:07.471Z" }, - { url = "https://files.pythonhosted.org/packages/c9/2a/e14c13b9a437780c5712525ce537915b531bba45481fc7102deb4492ff83/dbt_extractor-0.6.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:af451633390ac19669d3bde6c79822e657d32f5d903b3388bb00d56333fd52d5", size = 435109, upload-time = "2025-04-07T16:46:09.443Z" }, - { url = "https://files.pythonhosted.org/packages/58/2e/1ef1cd2b36973bea0a6823a7b7cd1b3db29b61ddebb015ceaea88b9e9347/dbt_extractor-0.6.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:05bcfab7ebd70296ceb31742e8333ba66a2c939de44e61a7088bebafa939aaf6", size = 434550, upload-time = "2025-04-07T16:46:10.916Z" }, - { url = "https://files.pythonhosted.org/packages/40/5a/468a2855181aaee5402efbf9ef757d074cd306eec22bbcd267cdd0edbe94/dbt_extractor-0.6.0-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:71b3f8897138cc6698d313b9a3d0450fd021937ff5463269ee18ed415541781b", size = 470137, upload-time = "2025-04-07T16:46:12.36Z" }, - { url = "https://files.pythonhosted.org/packages/b2/18/611dceb2fa7ea668471f290f34fec55fa3283e3ee9d0475d964e6ffaff97/dbt_extractor-0.6.0-cp39-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:868af715a6328d7317ce6e4db238f850f660fef13fb36b7ab4cf9163ed5f54ff", size = 524331, upload-time = "2025-04-07T16:46:14.177Z" }, - { url = "https://files.pythonhosted.org/packages/9e/ad/9dd410d4d95e336ae6b10c53c939bf1ff8e9991e1adb5ea4aefc4a87c445/dbt_extractor-0.6.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c1fd2b083a75e80b13e9874dc9699bfdfddf3baa9b6a8dea48de06d51a082733", size = 517959, upload-time = "2025-04-07T16:46:15.68Z" }, - { url = "https://files.pythonhosted.org/packages/a4/4f/6994cdfb51c5652fad0c8f9cf5b3ec1816cb10e99ed145eb27e6a9bcc16b/dbt_extractor-0.6.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:311f0d3a4994751c541a4fa303d205727ba90e90c85286c03d3d9284e2bf0bd4", size = 494850, upload-time = "2025-04-07T16:46:17.265Z" }, - { url = "https://files.pythonhosted.org/packages/df/5e/fad01e18d68ffd09c0f39cdedeed8fcaaea74a8b46d1a944472b5f95b72b/dbt_extractor-0.6.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aecfa43f7e6f139e76d47e4e1d7b189655ae19a8cf697686230bacb89a94ae74", size = 442739, upload-time = "2025-04-07T16:46:19.002Z" }, - { url = "https://files.pythonhosted.org/packages/9d/82/49068ee2b9f38aa34d0f3196bb7b71d11af86630d5ed5cb6626108c97cd6/dbt_extractor-0.6.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a5cb810edc60c0486f78cc29739ebda70c81b10a1686861e78addc9f91fcd7de", size = 618014, upload-time = "2025-04-07T16:46:21.571Z" }, - { url = "https://files.pythonhosted.org/packages/18/c6/cdaf1ac8959d571b5cb3587b8afef9e5fe60b99fe59aca94560808501d8b/dbt_extractor-0.6.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:080fd1edf123926ed97929c65a75874d0fea687ccd5d3ebbc9e81b339f099604", size = 697290, upload-time = "2025-04-07T16:46:23.089Z" }, - { url = "https://files.pythonhosted.org/packages/94/6d/46bdb9a809c66784fcc19b853311568cfd3041c075f0a578cb7116686841/dbt_extractor-0.6.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:1b9ed7b15df983a735f87773f6765db8458680c02fcebbf89df4e238503c0e08", size = 644443, upload-time = "2025-04-07T16:46:24.463Z" }, - { url = "https://files.pythonhosted.org/packages/3b/02/b111856273e414ac80ef58d2103c9b7c6a5b29b1ec248999d3d5873ada00/dbt_extractor-0.6.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:caeaba8d8c813f8e32d586c12615c0c7d6b99bee4f1be845312e80ef731de164", size = 613017, upload-time = "2025-04-07T16:46:25.913Z" }, - { url = "https://files.pythonhosted.org/packages/c4/de/d1492ab6beaf0a18aee17c7a9562592ac2981e962b4058262f5eb6dabfc5/dbt_extractor-0.6.0-cp39-abi3-win32.whl", hash = "sha256:369dcc3499f160256756585783f1308868076d5a65d0a051348d22da8b90e67d", size = 252721, upload-time = "2025-04-07T16:46:27.295Z" }, - { url = "https://files.pythonhosted.org/packages/60/36/f5b1c4159fa911607f3a49fcbc535e4783870fd887bc0a1b3ad42587cb73/dbt_extractor-0.6.0-cp39-abi3-win_amd64.whl", hash = "sha256:a79a570fdcb672505ac2bdc12360a2a7aec622ef604d8c607225854ff862518c", size = 277146, upload-time = "2025-04-07T16:46:28.991Z" }, + { url = "https://files.pythonhosted.org/packages/9f/dd/ec8f9e48e7dd5a52a69cca7907681d1779cf1cc8b02f2aa2acb6a2bf8bb4/dbt_extractor-0.6.0-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:4b6b1e70dde78cb904ca7a8958c2c803e77779b6ce108f4ea7ac479f5700db89", size = 790206 }, + { url = "https://files.pythonhosted.org/packages/03/5f/233f326336aa21fbd9e7268f239a8464af145abd398a360d894c3286699d/dbt_extractor-0.6.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:dcf14ed245de8df269815ff4c4f555fa72d2621f4fff37c023b8c99d0e421b4f", size = 404381 }, + { url = "https://files.pythonhosted.org/packages/c9/2a/e14c13b9a437780c5712525ce537915b531bba45481fc7102deb4492ff83/dbt_extractor-0.6.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:af451633390ac19669d3bde6c79822e657d32f5d903b3388bb00d56333fd52d5", size = 435109 }, + { url = "https://files.pythonhosted.org/packages/58/2e/1ef1cd2b36973bea0a6823a7b7cd1b3db29b61ddebb015ceaea88b9e9347/dbt_extractor-0.6.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:05bcfab7ebd70296ceb31742e8333ba66a2c939de44e61a7088bebafa939aaf6", size = 434550 }, + { url = "https://files.pythonhosted.org/packages/40/5a/468a2855181aaee5402efbf9ef757d074cd306eec22bbcd267cdd0edbe94/dbt_extractor-0.6.0-cp39-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:71b3f8897138cc6698d313b9a3d0450fd021937ff5463269ee18ed415541781b", size = 470137 }, + { url = "https://files.pythonhosted.org/packages/b2/18/611dceb2fa7ea668471f290f34fec55fa3283e3ee9d0475d964e6ffaff97/dbt_extractor-0.6.0-cp39-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:868af715a6328d7317ce6e4db238f850f660fef13fb36b7ab4cf9163ed5f54ff", size = 524331 }, + { url = "https://files.pythonhosted.org/packages/9e/ad/9dd410d4d95e336ae6b10c53c939bf1ff8e9991e1adb5ea4aefc4a87c445/dbt_extractor-0.6.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c1fd2b083a75e80b13e9874dc9699bfdfddf3baa9b6a8dea48de06d51a082733", size = 517959 }, + { url = "https://files.pythonhosted.org/packages/a4/4f/6994cdfb51c5652fad0c8f9cf5b3ec1816cb10e99ed145eb27e6a9bcc16b/dbt_extractor-0.6.0-cp39-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:311f0d3a4994751c541a4fa303d205727ba90e90c85286c03d3d9284e2bf0bd4", size = 494850 }, + { url = "https://files.pythonhosted.org/packages/df/5e/fad01e18d68ffd09c0f39cdedeed8fcaaea74a8b46d1a944472b5f95b72b/dbt_extractor-0.6.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aecfa43f7e6f139e76d47e4e1d7b189655ae19a8cf697686230bacb89a94ae74", size = 442739 }, + { url = "https://files.pythonhosted.org/packages/9d/82/49068ee2b9f38aa34d0f3196bb7b71d11af86630d5ed5cb6626108c97cd6/dbt_extractor-0.6.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:a5cb810edc60c0486f78cc29739ebda70c81b10a1686861e78addc9f91fcd7de", size = 618014 }, + { url = "https://files.pythonhosted.org/packages/18/c6/cdaf1ac8959d571b5cb3587b8afef9e5fe60b99fe59aca94560808501d8b/dbt_extractor-0.6.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:080fd1edf123926ed97929c65a75874d0fea687ccd5d3ebbc9e81b339f099604", size = 697290 }, + { url = "https://files.pythonhosted.org/packages/94/6d/46bdb9a809c66784fcc19b853311568cfd3041c075f0a578cb7116686841/dbt_extractor-0.6.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:1b9ed7b15df983a735f87773f6765db8458680c02fcebbf89df4e238503c0e08", size = 644443 }, + { url = "https://files.pythonhosted.org/packages/3b/02/b111856273e414ac80ef58d2103c9b7c6a5b29b1ec248999d3d5873ada00/dbt_extractor-0.6.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:caeaba8d8c813f8e32d586c12615c0c7d6b99bee4f1be845312e80ef731de164", size = 613017 }, + { url = "https://files.pythonhosted.org/packages/c4/de/d1492ab6beaf0a18aee17c7a9562592ac2981e962b4058262f5eb6dabfc5/dbt_extractor-0.6.0-cp39-abi3-win32.whl", hash = "sha256:369dcc3499f160256756585783f1308868076d5a65d0a051348d22da8b90e67d", size = 252721 }, + { url = "https://files.pythonhosted.org/packages/60/36/f5b1c4159fa911607f3a49fcbc535e4783870fd887bc0a1b3ad42587cb73/dbt_extractor-0.6.0-cp39-abi3-win_amd64.whl", hash = "sha256:a79a570fdcb672505ac2bdc12360a2a7aec622ef604d8c607225854ff862518c", size = 277146 }, ] [[package]] name = "dbt-protos" -version = "1.0.443" +version = "1.0.541" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "protobuf" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9f/44/a439f5631013de921f4f9716af639740e277286f7c693930450d930fdc00/dbt_protos-1.0.443.tar.gz", hash = "sha256:6cc4b2146ccdf77d597534a0525c97e37d68b0ca34334661f37650d81e09632b", size = 127531, upload-time = "2026-03-17T15:24:56.585Z" } +sdist = { url = "https://files.pythonhosted.org/packages/64/e8/7d67eb52bf39834884fdd02d7d952652b1859ff422bbdbbf9c82f85862f3/dbt_protos-1.0.541.tar.gz", hash = "sha256:c1900f543c1b170bc8e065209537d9b5a8fa5577bc12e06ea26c41f1a4ec7ecc", size = 169206 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d3/01/d9385e15664acd680eff3b19ce4d45f167bc7054de455bfac2e7c638c62f/dbt_protos-1.0.443-py3-none-any.whl", hash = "sha256:f8c4bef794ee3c442248b4c8f7cba3d0af46ad389d4960c0a73a3b307f9434fa", size = 186314, upload-time = "2026-03-17T15:24:54.792Z" }, -] - -[[package]] -name = "dbt-semantic-interfaces" -version = "0.9.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "click" }, - { name = "importlib-metadata" }, - { name = "jinja2" }, - { name = "jsonschema" }, - { name = "more-itertools" }, - { name = "pydantic" }, - { name = "python-dateutil" }, - { name = "pyyaml" }, - { name = "typing-extensions" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b0/91/c702d8fb143541fda10f5eb7a7a89f34bda38ee043ecb3e3653363d0c5a0/dbt_semantic_interfaces-0.9.0.tar.gz", hash = "sha256:5c921257dce8bb51c9ffb5479f2bdd959e16ebfb98ee833de6daa70788c47271", size = 93865, upload-time = "2025-07-09T20:06:30.454Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/82/41708b2b69d5fead88dea5ca0d863d6291da83ca6f1bd19246842d397e2b/dbt_semantic_interfaces-0.9.0-py3-none-any.whl", hash = "sha256:1b54c06ba89190a47a7f0563360930a0cce869e55b484ca09d261ade0e319155", size = 147008, upload-time = "2025-07-09T20:06:32.466Z" }, + { url = "https://files.pythonhosted.org/packages/3f/23/a35738c4a934b314c42d90ed3c681503f4015c5301182e4b4b0c40a1af2e/dbt_protos-1.0.541-py3-none-any.whl", hash = "sha256:4e03f88e6b5c13a8cac68c6aa78267d8eb4396e6341a807112d619ab938318d5", size = 241824 }, ] [[package]] @@ -417,9 +409,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "orderly-set" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/89/50/767448e792d41bfb6094ee317a355c1cb221dca24b2e178e2203bbea2a77/deepdiff-8.6.2.tar.gz", hash = "sha256:186dcbd181e4d76cef11ab05f802d0056c5d6083c5a6748c1473e9d7481e183e", size = 634860, upload-time = "2026-03-18T17:16:33.785Z" } +sdist = { url = "https://files.pythonhosted.org/packages/89/50/767448e792d41bfb6094ee317a355c1cb221dca24b2e178e2203bbea2a77/deepdiff-8.6.2.tar.gz", hash = "sha256:186dcbd181e4d76cef11ab05f802d0056c5d6083c5a6748c1473e9d7481e183e", size = 634860 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2b/5f/c52bd1255db763d0cdcb7084d2e90c42119cb229302c56bdf1d0aa78abd2/deepdiff-8.6.2-py3-none-any.whl", hash = "sha256:4d22034a866c3928303a9332c279362f714192d9305bac17c498720d095fd1b4", size = 91979, upload-time = "2026-03-18T17:16:32.171Z" }, + { url = "https://files.pythonhosted.org/packages/2b/5f/c52bd1255db763d0cdcb7084d2e90c42119cb229302c56bdf1d0aa78abd2/deepdiff-8.6.2-py3-none-any.whl", hash = "sha256:4d22034a866c3928303a9332c279362f714192d9305bac17c498720d095fd1b4", size = 91979 }, ] [[package]] @@ -454,9 +446,9 @@ dependencies = [ { name = "tzdata" }, { name = "win-precise-time", marker = "python_full_version < '3.13' and os_name == 'nt'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/48/20/c47492cd3c78287133fbba98aa41192c8613a371b8db57738d5096d05b8b/dlt-1.26.0.tar.gz", hash = "sha256:1a066fed8df7ace96a695309d9102046f9e3b9d60692e1cb93e842406aaf55ec", size = 1042686, upload-time = "2026-04-28T17:52:20.379Z" } +sdist = { url = "https://files.pythonhosted.org/packages/48/20/c47492cd3c78287133fbba98aa41192c8613a371b8db57738d5096d05b8b/dlt-1.26.0.tar.gz", hash = "sha256:1a066fed8df7ace96a695309d9102046f9e3b9d60692e1cb93e842406aaf55ec", size = 1042686 } wheels = [ - { url = "https://files.pythonhosted.org/packages/72/1a/1430fc5989fe9a476b198a8a9b27e0795c2e6856d2593f7d21e660d99e2d/dlt-1.26.0-py3-none-any.whl", hash = "sha256:81e2d28bdc4d33e97978e3654542bdac95347342d15a35102d09fd79df50c2bd", size = 1312681, upload-time = "2026-04-28T17:52:16.234Z" }, + { url = "https://files.pythonhosted.org/packages/72/1a/1430fc5989fe9a476b198a8a9b27e0795c2e6856d2593f7d21e660d99e2d/dlt-1.26.0-py3-none-any.whl", hash = "sha256:81e2d28bdc4d33e97978e3654542bdac95347342d15a35102d09fd79df50c2bd", size = 1312681 }, ] [package.optional-dependencies] @@ -468,31 +460,31 @@ duckdb = [ name = "duckdb" version = "1.5.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0c/66/744b4931b799a42f8cb9bc7a6f169e7b8e51195b62b246db407fd90bf15f/duckdb-1.5.2.tar.gz", hash = "sha256:638da0d5102b6cb6f7d47f83d0600708ac1d3cb46c5e9aaabc845f9ba4d69246", size = 18017166, upload-time = "2026-04-13T11:30:09.065Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0c/66/744b4931b799a42f8cb9bc7a6f169e7b8e51195b62b246db407fd90bf15f/duckdb-1.5.2.tar.gz", hash = "sha256:638da0d5102b6cb6f7d47f83d0600708ac1d3cb46c5e9aaabc845f9ba4d69246", size = 18017166 } wheels = [ - { url = "https://files.pythonhosted.org/packages/41/de/ebe66bbe78125fc610f4fd415447a65349d94245950f3b3dfb31d028af02/duckdb-1.5.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e6495b00cad16888384119842797c49316a96ae1cb132bb03856d980d95afee1", size = 30064950, upload-time = "2026-04-13T11:29:11.468Z" }, - { url = "https://files.pythonhosted.org/packages/2d/8a/3e25b5d03bcf1fb99d189912f8ce92b1db4f9c8778e1b1f55745973a855a/duckdb-1.5.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d72b8856b1839d35648f38301b058f6232f4d36b463fe4dc8f4d3fdff2df1a2e", size = 15969113, upload-time = "2026-04-13T11:29:14.139Z" }, - { url = "https://files.pythonhosted.org/packages/19/bb/58001f0815002b1a93431bf907f77854085c7d049b83d521814a07b9db0b/duckdb-1.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2a1de4f4d454b8c97aec546c82003fc834d3422ce4bc6a19902f3462ef293bed", size = 14224774, upload-time = "2026-04-13T11:29:16.758Z" }, - { url = "https://files.pythonhosted.org/packages/d3/2f/a7f0de9509d1cef35608aeb382919041cdd70f58c173865c3da6a0d87979/duckdb-1.5.2-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce0b8141a10d37ecef729c45bc41d334854013f4389f1488bd6035c5579aaac1", size = 19313510, upload-time = "2026-04-13T11:29:19.574Z" }, - { url = "https://files.pythonhosted.org/packages/26/78/eb1e064ea8b9df3b87b167bfd7a407b2f615a4291e06cba756727adfa06c/duckdb-1.5.2-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c99ef73a277c8921bc0a1f16dee38d924484251d9cfd20951748c20fcd5ed855", size = 21429692, upload-time = "2026-04-13T11:29:22.575Z" }, - { url = "https://files.pythonhosted.org/packages/5b/12/05b0c47d14839925c5e35b79081d918ca82e3f236bb724a6f58409dd5291/duckdb-1.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:8d599758b4e48bf12e18c9b960cf491d219f0c4972d19a45489c05cc5ab36f83", size = 13107594, upload-time = "2026-04-13T11:29:25.43Z" }, - { url = "https://files.pythonhosted.org/packages/0b/2c/80558a82b236e044330e84a154b96aacddb343316b479f3d49be03ea11cb/duckdb-1.5.2-cp312-cp312-win_arm64.whl", hash = "sha256:fc85a5dbcbe6eccac1113c72370d1d3aacfdd49198d63950bdf7d8638a307f00", size = 13927537, upload-time = "2026-04-13T11:29:27.842Z" }, - { url = "https://files.pythonhosted.org/packages/98/f2/e3d742808f138d374be4bb516fade3d1f33749b813650810ab7885cdc363/duckdb-1.5.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:4420b3f47027a7849d0e1815532007f377fa95ee5810b47ea717d35525c12f79", size = 30064879, upload-time = "2026-04-13T11:29:30.763Z" }, - { url = "https://files.pythonhosted.org/packages/72/0d/f3dc1cf97e1267ca15e4307d456f96ce583961f0703fd75e62b2ad8d64fa/duckdb-1.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bb42e6ed543902e14eae647850da24103a89f0bc2587dec5601b1c1f213bd2ed", size = 15969327, upload-time = "2026-04-13T11:29:33.481Z" }, - { url = "https://files.pythonhosted.org/packages/b1/e0/d5418def53ae4e05a63075705ff44ed5af5a1a5932627eb2b600c5df1c93/duckdb-1.5.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:98c0535cd6d901f61a5ea3c2e26a1fd28482953d794deb183daf568e3aa5dda6", size = 14225107, upload-time = "2026-04-13T11:29:35.882Z" }, - { url = "https://files.pythonhosted.org/packages/16/a7/15aaa59dbecc35e9711980fcdbf525b32a52470b32d18ef678193a146213/duckdb-1.5.2-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:486c862bf7f163c0110b6d85b3e5c031d224a671cca468f12ebb1d3a348f6b39", size = 19313433, upload-time = "2026-04-13T11:29:38.367Z" }, - { url = "https://files.pythonhosted.org/packages/bd/21/d903cc63a5140c822b7b62b373a87dc557e60c29b321dfb435061c5e67cf/duckdb-1.5.2-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:70631c847ca918ee710ec874241b00cf9d2e5be90762cbb2a0389f17823c08f7", size = 21429837, upload-time = "2026-04-13T11:29:41.135Z" }, - { url = "https://files.pythonhosted.org/packages/e3/0a/b770d1f60c70597302130d6247f418549b7094251a02348fbaf1c7e147ae/duckdb-1.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:52a21823f3fbb52f0f0e5425e20b07391ad882464b955879499b5ff0b45a376b", size = 13107699, upload-time = "2026-04-13T11:29:43.905Z" }, - { url = "https://files.pythonhosted.org/packages/d9/cf/e200fe431d700962d1a908d2ce89f53ccee1cc8db260174ae663ba09686b/duckdb-1.5.2-cp313-cp313-win_arm64.whl", hash = "sha256:411ad438bd4140f189a10e7f515781335962c5d18bd07837dc6d202e3985253d", size = 13927646, upload-time = "2026-04-13T11:29:46.598Z" }, + { url = "https://files.pythonhosted.org/packages/41/de/ebe66bbe78125fc610f4fd415447a65349d94245950f3b3dfb31d028af02/duckdb-1.5.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e6495b00cad16888384119842797c49316a96ae1cb132bb03856d980d95afee1", size = 30064950 }, + { url = "https://files.pythonhosted.org/packages/2d/8a/3e25b5d03bcf1fb99d189912f8ce92b1db4f9c8778e1b1f55745973a855a/duckdb-1.5.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d72b8856b1839d35648f38301b058f6232f4d36b463fe4dc8f4d3fdff2df1a2e", size = 15969113 }, + { url = "https://files.pythonhosted.org/packages/19/bb/58001f0815002b1a93431bf907f77854085c7d049b83d521814a07b9db0b/duckdb-1.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2a1de4f4d454b8c97aec546c82003fc834d3422ce4bc6a19902f3462ef293bed", size = 14224774 }, + { url = "https://files.pythonhosted.org/packages/d3/2f/a7f0de9509d1cef35608aeb382919041cdd70f58c173865c3da6a0d87979/duckdb-1.5.2-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ce0b8141a10d37ecef729c45bc41d334854013f4389f1488bd6035c5579aaac1", size = 19313510 }, + { url = "https://files.pythonhosted.org/packages/26/78/eb1e064ea8b9df3b87b167bfd7a407b2f615a4291e06cba756727adfa06c/duckdb-1.5.2-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c99ef73a277c8921bc0a1f16dee38d924484251d9cfd20951748c20fcd5ed855", size = 21429692 }, + { url = "https://files.pythonhosted.org/packages/5b/12/05b0c47d14839925c5e35b79081d918ca82e3f236bb724a6f58409dd5291/duckdb-1.5.2-cp312-cp312-win_amd64.whl", hash = "sha256:8d599758b4e48bf12e18c9b960cf491d219f0c4972d19a45489c05cc5ab36f83", size = 13107594 }, + { url = "https://files.pythonhosted.org/packages/0b/2c/80558a82b236e044330e84a154b96aacddb343316b479f3d49be03ea11cb/duckdb-1.5.2-cp312-cp312-win_arm64.whl", hash = "sha256:fc85a5dbcbe6eccac1113c72370d1d3aacfdd49198d63950bdf7d8638a307f00", size = 13927537 }, + { url = "https://files.pythonhosted.org/packages/98/f2/e3d742808f138d374be4bb516fade3d1f33749b813650810ab7885cdc363/duckdb-1.5.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:4420b3f47027a7849d0e1815532007f377fa95ee5810b47ea717d35525c12f79", size = 30064879 }, + { url = "https://files.pythonhosted.org/packages/72/0d/f3dc1cf97e1267ca15e4307d456f96ce583961f0703fd75e62b2ad8d64fa/duckdb-1.5.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bb42e6ed543902e14eae647850da24103a89f0bc2587dec5601b1c1f213bd2ed", size = 15969327 }, + { url = "https://files.pythonhosted.org/packages/b1/e0/d5418def53ae4e05a63075705ff44ed5af5a1a5932627eb2b600c5df1c93/duckdb-1.5.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:98c0535cd6d901f61a5ea3c2e26a1fd28482953d794deb183daf568e3aa5dda6", size = 14225107 }, + { url = "https://files.pythonhosted.org/packages/16/a7/15aaa59dbecc35e9711980fcdbf525b32a52470b32d18ef678193a146213/duckdb-1.5.2-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:486c862bf7f163c0110b6d85b3e5c031d224a671cca468f12ebb1d3a348f6b39", size = 19313433 }, + { url = "https://files.pythonhosted.org/packages/bd/21/d903cc63a5140c822b7b62b373a87dc557e60c29b321dfb435061c5e67cf/duckdb-1.5.2-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:70631c847ca918ee710ec874241b00cf9d2e5be90762cbb2a0389f17823c08f7", size = 21429837 }, + { url = "https://files.pythonhosted.org/packages/e3/0a/b770d1f60c70597302130d6247f418549b7094251a02348fbaf1c7e147ae/duckdb-1.5.2-cp313-cp313-win_amd64.whl", hash = "sha256:52a21823f3fbb52f0f0e5425e20b07391ad882464b955879499b5ff0b45a376b", size = 13107699 }, + { url = "https://files.pythonhosted.org/packages/d9/cf/e200fe431d700962d1a908d2ce89f53ccee1cc8db260174ae663ba09686b/duckdb-1.5.2-cp313-cp313-win_arm64.whl", hash = "sha256:411ad438bd4140f189a10e7f515781335962c5d18bd07837dc6d202e3985253d", size = 13927646 }, ] [[package]] name = "fsspec" version = "2026.2.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/51/7c/f60c259dcbf4f0c47cc4ddb8f7720d2dcdc8888c8e5ad84c73ea4531cc5b/fsspec-2026.2.0.tar.gz", hash = "sha256:6544e34b16869f5aacd5b90bdf1a71acb37792ea3ddf6125ee69a22a53fb8bff", size = 313441, upload-time = "2026-02-05T21:50:53.743Z" } +sdist = { url = "https://files.pythonhosted.org/packages/51/7c/f60c259dcbf4f0c47cc4ddb8f7720d2dcdc8888c8e5ad84c73ea4531cc5b/fsspec-2026.2.0.tar.gz", hash = "sha256:6544e34b16869f5aacd5b90bdf1a71acb37792ea3ddf6125ee69a22a53fb8bff", size = 313441 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl", hash = "sha256:98de475b5cb3bd66bedd5c4679e87b4fdfe1a3bf4d707b151b3c07e58c9a2437", size = 202505, upload-time = "2026-02-05T21:50:51.819Z" }, + { url = "https://files.pythonhosted.org/packages/e6/ab/fb21f4c939bb440104cc2b396d3be1d9b7a9fd3c6c2a53d98c45b3d7c954/fsspec-2026.2.0-py3-none-any.whl", hash = "sha256:98de475b5cb3bd66bedd5c4679e87b4fdfe1a3bf4d707b151b3c07e58c9a2437", size = 202505 }, ] [[package]] @@ -502,9 +494,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "python-dateutil" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943, upload-time = "2022-05-02T15:47:16.11Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d9/29/d40217cbe2f6b1359e00c6c307bb3fc876ba74068cbab3dde77f03ca0dc4/ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343", size = 10943 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034, upload-time = "2022-05-02T15:47:14.552Z" }, + { url = "https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619", size = 11034 }, ] [[package]] @@ -514,9 +506,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "smmap" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684, upload-time = "2025-01-02T07:20:46.413Z" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/63b0fc47eb32792c7ba1fe1b694daec9a63620db1e313033d18140c2320a/gitdb-4.0.12.tar.gz", hash = "sha256:5ef71f855d191a3326fcfbc0d5da835f26b13fbcba60c32c21091c349ffdb571", size = 394684 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794, upload-time = "2025-01-02T07:20:43.624Z" }, + { url = "https://files.pythonhosted.org/packages/a0/61/5c78b91c3143ed5c14207f463aecfc8f9dbb5092fb2869baf37c273b2705/gitdb-4.0.12-py3-none-any.whl", hash = "sha256:67073e15955400952c6565cc3e707c554a4eea2e428946f7a4c162fab9bd9bcf", size = 62794 }, ] [[package]] @@ -526,27 +518,27 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "gitdb" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/df/b5/59d16470a1f0dfe8c793f9ef56fd3826093fc52b3bd96d6b9d6c26c7e27b/gitpython-3.1.46.tar.gz", hash = "sha256:400124c7d0ef4ea03f7310ac2fbf7151e09ff97f2a3288d64a440c584a29c37f", size = 215371, upload-time = "2026-01-01T15:37:32.073Z" } +sdist = { url = "https://files.pythonhosted.org/packages/df/b5/59d16470a1f0dfe8c793f9ef56fd3826093fc52b3bd96d6b9d6c26c7e27b/gitpython-3.1.46.tar.gz", hash = "sha256:400124c7d0ef4ea03f7310ac2fbf7151e09ff97f2a3288d64a440c584a29c37f", size = 215371 } wheels = [ - { url = "https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl", hash = "sha256:79812ed143d9d25b6d176a10bb511de0f9c67b1fa641d82097b0ab90398a2058", size = 208620, upload-time = "2026-01-01T15:37:30.574Z" }, + { url = "https://files.pythonhosted.org/packages/6a/09/e21df6aef1e1ffc0c816f0522ddc3f6dcded766c3261813131c78a704470/gitpython-3.1.46-py3-none-any.whl", hash = "sha256:79812ed143d9d25b6d176a10bb511de0f9c67b1fa641d82097b0ab90398a2058", size = 208620 }, ] [[package]] name = "giturlparse" version = "0.14.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/09/35/7f25a604a406be7d7d0f849bfcbc1603df084e9e58fe6170980c231138e4/giturlparse-0.14.0.tar.gz", hash = "sha256:0a13208cb3f60e067ee3d09d28e01f9c936065986004fa2d5cd6db7758e9f6e6", size = 15637, upload-time = "2025-10-22T09:21:11.674Z" } +sdist = { url = "https://files.pythonhosted.org/packages/09/35/7f25a604a406be7d7d0f849bfcbc1603df084e9e58fe6170980c231138e4/giturlparse-0.14.0.tar.gz", hash = "sha256:0a13208cb3f60e067ee3d09d28e01f9c936065986004fa2d5cd6db7758e9f6e6", size = 15637 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c4/f9/9ff5a301459f804a885f237453ba81564bc6ee54740e9f2676c2642043f6/giturlparse-0.14.0-py2.py3-none-any.whl", hash = "sha256:04fd9c262ca9a4db86043d2ef32b2b90bfcbcdefc4f6a260fd9402127880931d", size = 16299, upload-time = "2025-10-22T09:21:10.818Z" }, + { url = "https://files.pythonhosted.org/packages/c4/f9/9ff5a301459f804a885f237453ba81564bc6ee54740e9f2676c2642043f6/giturlparse-0.14.0-py2.py3-none-any.whl", hash = "sha256:04fd9c262ca9a4db86043d2ef32b2b90bfcbcdefc4f6a260fd9402127880931d", size = 16299 }, ] [[package]] name = "h11" version = "0.16.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250 } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515 }, ] [[package]] @@ -557,9 +549,9 @@ dependencies = [ { name = "certifi" }, { name = "h11" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784 }, ] [[package]] @@ -572,27 +564,27 @@ dependencies = [ { name = "httpcore" }, { name = "idna" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 }, ] [[package]] name = "humanize" version = "4.15.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ba/66/a3921783d54be8a6870ac4ccffcd15c4dc0dd7fcce51c6d63b8c63935276/humanize-4.15.0.tar.gz", hash = "sha256:1dd098483eb1c7ee8e32eb2e99ad1910baefa4b75c3aff3a82f4d78688993b10", size = 83599, upload-time = "2025-12-20T20:16:13.19Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/66/a3921783d54be8a6870ac4ccffcd15c4dc0dd7fcce51c6d63b8c63935276/humanize-4.15.0.tar.gz", hash = "sha256:1dd098483eb1c7ee8e32eb2e99ad1910baefa4b75c3aff3a82f4d78688993b10", size = 83599 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c5/7b/bca5613a0c3b542420cf92bd5e5fb8ebd5435ce1011a091f66bb7693285e/humanize-4.15.0-py3-none-any.whl", hash = "sha256:b1186eb9f5a9749cd9cb8565aee77919dd7c8d076161cf44d70e59e3301e1769", size = 132203, upload-time = "2025-12-20T20:16:11.67Z" }, + { url = "https://files.pythonhosted.org/packages/c5/7b/bca5613a0c3b542420cf92bd5e5fb8ebd5435ce1011a091f66bb7693285e/humanize-4.15.0-py3-none-any.whl", hash = "sha256:b1186eb9f5a9749cd9cb8565aee77919dd7c8d076161cf44d70e59e3301e1769", size = 132203 }, ] [[package]] name = "idna" version = "3.11" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582 } wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008 }, ] [[package]] @@ -602,27 +594,27 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "zipp" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e7/72/c600ae4f68c28fc19f9c31b9403053e5dbb8cace2e6842c7b7c3e4d42fe9/importlib_metadata-8.9.0.tar.gz", hash = "sha256:58850626cef4bd2df100378b0f2aea9724a7b92f10770d547725b047078f99ee", size = 56140, upload-time = "2026-03-20T16:56:26.362Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/72/c600ae4f68c28fc19f9c31b9403053e5dbb8cace2e6842c7b7c3e4d42fe9/importlib_metadata-8.9.0.tar.gz", hash = "sha256:58850626cef4bd2df100378b0f2aea9724a7b92f10770d547725b047078f99ee", size = 56140 } wheels = [ - { url = "https://files.pythonhosted.org/packages/7d/f9/97f2ca8bb3ec6e4b1d64f983ebe98b9a192faddff67fac3d6303a537e670/importlib_metadata-8.9.0-py3-none-any.whl", hash = "sha256:e0f761b6ea91ced3b0844c14c9d955224d538105921f8e6754c00f6ca79fba7f", size = 27220, upload-time = "2026-03-20T16:56:25.07Z" }, + { url = "https://files.pythonhosted.org/packages/7d/f9/97f2ca8bb3ec6e4b1d64f983ebe98b9a192faddff67fac3d6303a537e670/importlib_metadata-8.9.0-py3-none-any.whl", hash = "sha256:e0f761b6ea91ced3b0844c14c9d955224d538105921f8e6754c00f6ca79fba7f", size = 27220 }, ] [[package]] name = "iniconfig" version = "2.3.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503 } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484 }, ] [[package]] name = "isodate" version = "0.7.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/54/4d/e940025e2ce31a8ce1202635910747e5a87cc3a6a6bb2d00973375014749/isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6", size = 29705, upload-time = "2024-10-08T23:04:11.5Z" } +sdist = { url = "https://files.pythonhosted.org/packages/54/4d/e940025e2ce31a8ce1202635910747e5a87cc3a6a6bb2d00973375014749/isodate-0.7.2.tar.gz", hash = "sha256:4cd1aa0f43ca76f4a6c6c0292a85f40b35ec2e43e315b59f06e6d32171a953e6", size = 29705 } wheels = [ - { url = "https://files.pythonhosted.org/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320, upload-time = "2024-10-08T23:04:09.501Z" }, + { url = "https://files.pythonhosted.org/packages/15/aa/0aca39a37d3c7eb941ba736ede56d689e7be91cab5d9ca846bde3999eba6/isodate-0.7.2-py3-none-any.whl", hash = "sha256:28009937d8031054830160fce6d409ed342816b543597cece116d966c6d99e15", size = 22320 }, ] [[package]] @@ -632,9 +624,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markupsafe" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115 } wheels = [ - { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, ] [[package]] @@ -644,9 +636,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "ply" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6d/86/08646239a313f895186ff0a4573452038eed8c86f54380b3ebac34d32fb2/jsonpath-ng-1.7.0.tar.gz", hash = "sha256:f6f5f7fd4e5ff79c785f1573b394043b39849fb2bb47bcead935d12b00beab3c", size = 37838, upload-time = "2024-10-11T15:41:42.404Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/86/08646239a313f895186ff0a4573452038eed8c86f54380b3ebac34d32fb2/jsonpath-ng-1.7.0.tar.gz", hash = "sha256:f6f5f7fd4e5ff79c785f1573b394043b39849fb2bb47bcead935d12b00beab3c", size = 37838 } wheels = [ - { url = "https://files.pythonhosted.org/packages/35/5a/73ecb3d82f8615f32ccdadeb9356726d6cae3a4bbc840b437ceb95708063/jsonpath_ng-1.7.0-py3-none-any.whl", hash = "sha256:f3d7f9e848cba1b6da28c55b1c26ff915dc9e0b1ba7e752a53d6da8d5cbd00b6", size = 30105, upload-time = "2024-11-20T17:58:30.418Z" }, + { url = "https://files.pythonhosted.org/packages/35/5a/73ecb3d82f8615f32ccdadeb9356726d6cae3a4bbc840b437ceb95708063/jsonpath_ng-1.7.0-py3-none-any.whl", hash = "sha256:f3d7f9e848cba1b6da28c55b1c26ff915dc9e0b1ba7e752a53d6da8d5cbd00b6", size = 30105 }, ] [[package]] @@ -659,9 +651,9 @@ dependencies = [ { name = "referencing" }, { name = "rpds-py" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583 } wheels = [ - { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, + { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630 }, ] [[package]] @@ -671,27 +663,27 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "referencing" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855 } wheels = [ - { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437 }, ] [[package]] name = "leather" version = "0.4.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9e/09/849cf129d7eae1e42f873f2dbd60323267c738390b686a7384fb3fb289ad/leather-0.4.1.tar.gz", hash = "sha256:67119c2aee93be821f077193bd8534e296c05b38bd174d9c5a80c4aa31d1a4d3", size = 44072, upload-time = "2025-12-15T19:01:42.224Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/09/849cf129d7eae1e42f873f2dbd60323267c738390b686a7384fb3fb289ad/leather-0.4.1.tar.gz", hash = "sha256:67119c2aee93be821f077193bd8534e296c05b38bd174d9c5a80c4aa31d1a4d3", size = 44072 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1a/d4/c4dcb02ed11f8884e169b3350fc40aa4c08edf8bed77a8f0f267542e6452/leather-0.4.1-py3-none-any.whl", hash = "sha256:ec61cba1ca3ccb96ed90e38b116fc58757d97d352171006b3288c47ce3fbd183", size = 30340, upload-time = "2025-12-15T19:01:40.823Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d4/c4dcb02ed11f8884e169b3350fc40aa4c08edf8bed77a8f0f267542e6452/leather-0.4.1-py3-none-any.whl", hash = "sha256:ec61cba1ca3ccb96ed90e38b116fc58757d97d352171006b3288c47ce3fbd183", size = 30340 }, ] [[package]] name = "markdown" version = "3.10.2" source = { registry = "https://pypi.org/simple" } -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" } +sdist = { url = "https://files.pythonhosted.org/packages/2b/f4/69fa6ed85ae003c2378ffa8f6d2e3234662abd02c10d216c0ba96081a238/markdown-3.10.2.tar.gz", hash = "sha256:994d51325d25ad8aa7ce4ebaec003febcce822c3f8c911e3b17c52f7f589f950", size = 368805 } wheels = [ - { 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" }, + { url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180 }, ] [[package]] @@ -701,50 +693,50 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mdurl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070 } wheels = [ - { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321 }, ] [[package]] name = "markupsafe" version = "3.0.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, - { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, - { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, - { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, - { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, - { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, - { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, - { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, - { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, - { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, - { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, - { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, - { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, - { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, - { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, - { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, - { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, - { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, - { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, - { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, - { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, - { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, - { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, - { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, - { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, - { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, - { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, - { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, - { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, - { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, - { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, - { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, - { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615 }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020 }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332 }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947 }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962 }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760 }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529 }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015 }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540 }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105 }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906 }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622 }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029 }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374 }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980 }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990 }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784 }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588 }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041 }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543 }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113 }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911 }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658 }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066 }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639 }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569 }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284 }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801 }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769 }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642 }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612 }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200 }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973 }, ] [[package]] @@ -754,9 +746,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/eb/47/0a450b281bef2d7e97ec02c8e1168d821e283f58e02e6c403b2bb4d73c1c/mashumaro-3.14.tar.gz", hash = "sha256:5ef6f2b963892cbe9a4ceb3441dfbea37f8c3412523f25d42e9b3a7186555f1d", size = 166160, upload-time = "2024-10-23T21:48:40.164Z" } +sdist = { url = "https://files.pythonhosted.org/packages/eb/47/0a450b281bef2d7e97ec02c8e1168d821e283f58e02e6c403b2bb4d73c1c/mashumaro-3.14.tar.gz", hash = "sha256:5ef6f2b963892cbe9a4ceb3441dfbea37f8c3412523f25d42e9b3a7186555f1d", size = 166160 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1b/35/8d63733a2c12149d0c7663c29bf626bdbeea5f0ff963afe58a42b4810981/mashumaro-3.14-py3-none-any.whl", hash = "sha256:c12a649599a8f7b1a0b35d18f12e678423c3066189f7bc7bd8dd431c5c8132c3", size = 92183, upload-time = "2024-10-23T21:48:38.334Z" }, + { url = "https://files.pythonhosted.org/packages/1b/35/8d63733a2c12149d0c7663c29bf626bdbeea5f0ff963afe58a42b4810981/mashumaro-3.14-py3-none-any.whl", hash = "sha256:c12a649599a8f7b1a0b35d18f12e678423c3066189f7bc7bd8dd431c5c8132c3", size = 92183 }, ] [package.optional-dependencies] @@ -768,18 +760,41 @@ msgpack = [ name = "mdurl" version = "0.1.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 }, ] [[package]] name = "mergedeep" version = "1.3.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661, upload-time = "2021-02-05T18:55:30.623Z" } +sdist = { url = "https://files.pythonhosted.org/packages/3a/41/580bb4006e3ed0361b8151a01d324fb03f420815446c7def45d02f74c270/mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8", size = 4661 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354, upload-time = "2021-02-05T18:55:29.583Z" }, + { url = "https://files.pythonhosted.org/packages/2c/19/04f9b178c2d8a15b076c8b5140708fa6ffc5601fb6f1e975537072df5b2a/mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307", size = 6354 }, +] + +[[package]] +name = "metricflow" +version = "0.211.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "importlib-metadata" }, + { name = "jinja2" }, + { name = "jsonschema" }, + { name = "more-itertools" }, + { name = "pydantic" }, + { name = "python-dateutil" }, + { name = "pyyaml" }, + { name = "rapidfuzz" }, + { name = "referencing" }, + { name = "sqlglot" }, + { name = "tabulate" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4d/1c/2303b109a1e02216615d5c01cccf61a12d133f326388b9c881d32d00d182/metricflow-0.211.0.tar.gz", hash = "sha256:e15d0c24647f4498fa0926e87c61dd23b15205c9577566bcf848460d58f48bec", size = 1549648 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/b7/de51d5d04ac43aeecbfea7741bf29da84fd5a04995e25a2b85abaa6ee807/metricflow-0.211.0-py3-none-any.whl", hash = "sha256:9cb83ccaa0c47363482fcc5cb57944a3f1af17ca948d90805524161f4e03d600", size = 1914356 }, ] [[package]] @@ -801,9 +816,9 @@ dependencies = [ { name = "pyyaml-env-tag" }, { name = "watchdog" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2", size = 3889159, upload-time = "2024-08-30T12:24:06.899Z" } +sdist = { url = "https://files.pythonhosted.org/packages/bc/c6/bbd4f061bd16b378247f12953ffcb04786a618ce5e904b8c5a01a0309061/mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2", size = 3889159 } wheels = [ - { url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451, upload-time = "2024-08-30T12:24:05.054Z" }, + { url = "https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e", size = 3864451 }, ] [[package]] @@ -815,9 +830,9 @@ dependencies = [ { name = "platformdirs" }, { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ce/25/b3cccb187655b9393572bde9b09261d267c3bf2f2cdabe347673be5976a6/mkdocs_get_deps-0.2.2.tar.gz", hash = "sha256:8ee8d5f316cdbbb2834bc1df6e69c08fe769a83e040060de26d3c19fad3599a1", size = 11047, upload-time = "2026-03-10T02:46:33.632Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/25/b3cccb187655b9393572bde9b09261d267c3bf2f2cdabe347673be5976a6/mkdocs_get_deps-0.2.2.tar.gz", hash = "sha256:8ee8d5f316cdbbb2834bc1df6e69c08fe769a83e040060de26d3c19fad3599a1", size = 11047 } wheels = [ - { url = "https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl", hash = "sha256:e7878cbeac04860b8b5e0ca31d3abad3df9411a75a32cde82f8e44b6c16ff650", size = 9555, upload-time = "2026-03-10T02:46:32.256Z" }, + { url = "https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl", hash = "sha256:e7878cbeac04860b8b5e0ca31d3abad3df9411a75a32cde82f8e44b6c16ff650", size = 9555 }, ] [[package]] @@ -837,157 +852,157 @@ dependencies = [ { name = "regex" }, { name = "requests" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e2/14/8daeeecee2e25bd84239a843fdcb92b20db88ebbcb26e0d32f414ca54a22/mkdocs_material-9.5.49.tar.gz", hash = "sha256:3671bb282b4f53a1c72e08adbe04d2481a98f85fed392530051f80ff94a9621d", size = 3949559, upload-time = "2024-12-16T10:25:10.539Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e2/14/8daeeecee2e25bd84239a843fdcb92b20db88ebbcb26e0d32f414ca54a22/mkdocs_material-9.5.49.tar.gz", hash = "sha256:3671bb282b4f53a1c72e08adbe04d2481a98f85fed392530051f80ff94a9621d", size = 3949559 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/2d/2dd23a36b48421db54f118bb6f6f733dbe2d5c78fe7867375e48649fd3df/mkdocs_material-9.5.49-py3-none-any.whl", hash = "sha256:c3c2d8176b18198435d3a3e119011922f3e11424074645c24019c2dcf08a360e", size = 8684098, upload-time = "2024-12-16T10:25:02.326Z" }, + { url = "https://files.pythonhosted.org/packages/fc/2d/2dd23a36b48421db54f118bb6f6f733dbe2d5c78fe7867375e48649fd3df/mkdocs_material-9.5.49-py3-none-any.whl", hash = "sha256:c3c2d8176b18198435d3a3e119011922f3e11424074645c24019c2dcf08a360e", size = 8684098 }, ] [[package]] name = "mkdocs-material-extensions" version = "1.3.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/79/9b/9b4c96d6593b2a541e1cb8b34899a6d021d208bb357042823d4d2cabdbe7/mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443", size = 11847, upload-time = "2023-11-22T19:09:45.208Z" } +sdist = { url = "https://files.pythonhosted.org/packages/79/9b/9b4c96d6593b2a541e1cb8b34899a6d021d208bb357042823d4d2cabdbe7/mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443", size = 11847 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728, upload-time = "2023-11-22T19:09:43.465Z" }, + { url = "https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31", size = 8728 }, ] [[package]] name = "more-itertools" version = "10.8.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ea/5d/38b681d3fce7a266dd9ab73c66959406d565b3e85f21d5e66e1181d93721/more_itertools-10.8.0.tar.gz", hash = "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd", size = 137431, upload-time = "2025-09-02T15:23:11.018Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/5d/38b681d3fce7a266dd9ab73c66959406d565b3e85f21d5e66e1181d93721/more_itertools-10.8.0.tar.gz", hash = "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd", size = 137431 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b", size = 69667, upload-time = "2025-09-02T15:23:09.635Z" }, + { url = "https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b", size = 69667 }, ] [[package]] name = "msgpack" version = "1.1.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4d/f2/bfb55a6236ed8725a96b0aa3acbd0ec17588e6a2c3b62a93eb513ed8783f/msgpack-1.1.2.tar.gz", hash = "sha256:3b60763c1373dd60f398488069bcdc703cd08a711477b5d480eecc9f9626f47e", size = 173581, upload-time = "2025-10-08T09:15:56.596Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ad/bd/8b0d01c756203fbab65d265859749860682ccd2a59594609aeec3a144efa/msgpack-1.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:70a0dff9d1f8da25179ffcf880e10cf1aad55fdb63cd59c9a49a1b82290062aa", size = 81939, upload-time = "2025-10-08T09:15:01.472Z" }, - { url = "https://files.pythonhosted.org/packages/34/68/ba4f155f793a74c1483d4bdef136e1023f7bcba557f0db4ef3db3c665cf1/msgpack-1.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:446abdd8b94b55c800ac34b102dffd2f6aa0ce643c55dfc017ad89347db3dbdb", size = 85064, upload-time = "2025-10-08T09:15:03.764Z" }, - { url = "https://files.pythonhosted.org/packages/f2/60/a064b0345fc36c4c3d2c743c82d9100c40388d77f0b48b2f04d6041dbec1/msgpack-1.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c63eea553c69ab05b6747901b97d620bb2a690633c77f23feb0c6a947a8a7b8f", size = 417131, upload-time = "2025-10-08T09:15:05.136Z" }, - { url = "https://files.pythonhosted.org/packages/65/92/a5100f7185a800a5d29f8d14041f61475b9de465ffcc0f3b9fba606e4505/msgpack-1.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:372839311ccf6bdaf39b00b61288e0557916c3729529b301c52c2d88842add42", size = 427556, upload-time = "2025-10-08T09:15:06.837Z" }, - { url = "https://files.pythonhosted.org/packages/f5/87/ffe21d1bf7d9991354ad93949286f643b2bb6ddbeab66373922b44c3b8cc/msgpack-1.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2929af52106ca73fcb28576218476ffbb531a036c2adbcf54a3664de124303e9", size = 404920, upload-time = "2025-10-08T09:15:08.179Z" }, - { url = "https://files.pythonhosted.org/packages/ff/41/8543ed2b8604f7c0d89ce066f42007faac1eaa7d79a81555f206a5cdb889/msgpack-1.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:be52a8fc79e45b0364210eef5234a7cf8d330836d0a64dfbb878efa903d84620", size = 415013, upload-time = "2025-10-08T09:15:09.83Z" }, - { url = "https://files.pythonhosted.org/packages/41/0d/2ddfaa8b7e1cee6c490d46cb0a39742b19e2481600a7a0e96537e9c22f43/msgpack-1.1.2-cp312-cp312-win32.whl", hash = "sha256:1fff3d825d7859ac888b0fbda39a42d59193543920eda9d9bea44d958a878029", size = 65096, upload-time = "2025-10-08T09:15:11.11Z" }, - { url = "https://files.pythonhosted.org/packages/8c/ec/d431eb7941fb55a31dd6ca3404d41fbb52d99172df2e7707754488390910/msgpack-1.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:1de460f0403172cff81169a30b9a92b260cb809c4cb7e2fc79ae8d0510c78b6b", size = 72708, upload-time = "2025-10-08T09:15:12.554Z" }, - { url = "https://files.pythonhosted.org/packages/c5/31/5b1a1f70eb0e87d1678e9624908f86317787b536060641d6798e3cf70ace/msgpack-1.1.2-cp312-cp312-win_arm64.whl", hash = "sha256:be5980f3ee0e6bd44f3a9e9dea01054f175b50c3e6cdb692bc9424c0bbb8bf69", size = 64119, upload-time = "2025-10-08T09:15:13.589Z" }, - { url = "https://files.pythonhosted.org/packages/6b/31/b46518ecc604d7edf3a4f94cb3bf021fc62aa301f0cb849936968164ef23/msgpack-1.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4efd7b5979ccb539c221a4c4e16aac1a533efc97f3b759bb5a5ac9f6d10383bf", size = 81212, upload-time = "2025-10-08T09:15:14.552Z" }, - { url = "https://files.pythonhosted.org/packages/92/dc/c385f38f2c2433333345a82926c6bfa5ecfff3ef787201614317b58dd8be/msgpack-1.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:42eefe2c3e2af97ed470eec850facbe1b5ad1d6eacdbadc42ec98e7dcf68b4b7", size = 84315, upload-time = "2025-10-08T09:15:15.543Z" }, - { url = "https://files.pythonhosted.org/packages/d3/68/93180dce57f684a61a88a45ed13047558ded2be46f03acb8dec6d7c513af/msgpack-1.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1fdf7d83102bf09e7ce3357de96c59b627395352a4024f6e2458501f158bf999", size = 412721, upload-time = "2025-10-08T09:15:16.567Z" }, - { url = "https://files.pythonhosted.org/packages/5d/ba/459f18c16f2b3fc1a1ca871f72f07d70c07bf768ad0a507a698b8052ac58/msgpack-1.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fac4be746328f90caa3cd4bc67e6fe36ca2bf61d5c6eb6d895b6527e3f05071e", size = 424657, upload-time = "2025-10-08T09:15:17.825Z" }, - { url = "https://files.pythonhosted.org/packages/38/f8/4398c46863b093252fe67368b44edc6c13b17f4e6b0e4929dbf0bdb13f23/msgpack-1.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fffee09044073e69f2bad787071aeec727183e7580443dfeb8556cbf1978d162", size = 402668, upload-time = "2025-10-08T09:15:19.003Z" }, - { url = "https://files.pythonhosted.org/packages/28/ce/698c1eff75626e4124b4d78e21cca0b4cc90043afb80a507626ea354ab52/msgpack-1.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5928604de9b032bc17f5099496417f113c45bc6bc21b5c6920caf34b3c428794", size = 419040, upload-time = "2025-10-08T09:15:20.183Z" }, - { url = "https://files.pythonhosted.org/packages/67/32/f3cd1667028424fa7001d82e10ee35386eea1408b93d399b09fb0aa7875f/msgpack-1.1.2-cp313-cp313-win32.whl", hash = "sha256:a7787d353595c7c7e145e2331abf8b7ff1e6673a6b974ded96e6d4ec09f00c8c", size = 65037, upload-time = "2025-10-08T09:15:21.416Z" }, - { url = "https://files.pythonhosted.org/packages/74/07/1ed8277f8653c40ebc65985180b007879f6a836c525b3885dcc6448ae6cb/msgpack-1.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:a465f0dceb8e13a487e54c07d04ae3ba131c7c5b95e2612596eafde1dccf64a9", size = 72631, upload-time = "2025-10-08T09:15:22.431Z" }, - { url = "https://files.pythonhosted.org/packages/e5/db/0314e4e2db56ebcf450f277904ffd84a7988b9e5da8d0d61ab2d057df2b6/msgpack-1.1.2-cp313-cp313-win_arm64.whl", hash = "sha256:e69b39f8c0aa5ec24b57737ebee40be647035158f14ed4b40e6f150077e21a84", size = 64118, upload-time = "2025-10-08T09:15:23.402Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/4d/f2/bfb55a6236ed8725a96b0aa3acbd0ec17588e6a2c3b62a93eb513ed8783f/msgpack-1.1.2.tar.gz", hash = "sha256:3b60763c1373dd60f398488069bcdc703cd08a711477b5d480eecc9f9626f47e", size = 173581 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/bd/8b0d01c756203fbab65d265859749860682ccd2a59594609aeec3a144efa/msgpack-1.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:70a0dff9d1f8da25179ffcf880e10cf1aad55fdb63cd59c9a49a1b82290062aa", size = 81939 }, + { url = "https://files.pythonhosted.org/packages/34/68/ba4f155f793a74c1483d4bdef136e1023f7bcba557f0db4ef3db3c665cf1/msgpack-1.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:446abdd8b94b55c800ac34b102dffd2f6aa0ce643c55dfc017ad89347db3dbdb", size = 85064 }, + { url = "https://files.pythonhosted.org/packages/f2/60/a064b0345fc36c4c3d2c743c82d9100c40388d77f0b48b2f04d6041dbec1/msgpack-1.1.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c63eea553c69ab05b6747901b97d620bb2a690633c77f23feb0c6a947a8a7b8f", size = 417131 }, + { url = "https://files.pythonhosted.org/packages/65/92/a5100f7185a800a5d29f8d14041f61475b9de465ffcc0f3b9fba606e4505/msgpack-1.1.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:372839311ccf6bdaf39b00b61288e0557916c3729529b301c52c2d88842add42", size = 427556 }, + { url = "https://files.pythonhosted.org/packages/f5/87/ffe21d1bf7d9991354ad93949286f643b2bb6ddbeab66373922b44c3b8cc/msgpack-1.1.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2929af52106ca73fcb28576218476ffbb531a036c2adbcf54a3664de124303e9", size = 404920 }, + { url = "https://files.pythonhosted.org/packages/ff/41/8543ed2b8604f7c0d89ce066f42007faac1eaa7d79a81555f206a5cdb889/msgpack-1.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:be52a8fc79e45b0364210eef5234a7cf8d330836d0a64dfbb878efa903d84620", size = 415013 }, + { url = "https://files.pythonhosted.org/packages/41/0d/2ddfaa8b7e1cee6c490d46cb0a39742b19e2481600a7a0e96537e9c22f43/msgpack-1.1.2-cp312-cp312-win32.whl", hash = "sha256:1fff3d825d7859ac888b0fbda39a42d59193543920eda9d9bea44d958a878029", size = 65096 }, + { url = "https://files.pythonhosted.org/packages/8c/ec/d431eb7941fb55a31dd6ca3404d41fbb52d99172df2e7707754488390910/msgpack-1.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:1de460f0403172cff81169a30b9a92b260cb809c4cb7e2fc79ae8d0510c78b6b", size = 72708 }, + { url = "https://files.pythonhosted.org/packages/c5/31/5b1a1f70eb0e87d1678e9624908f86317787b536060641d6798e3cf70ace/msgpack-1.1.2-cp312-cp312-win_arm64.whl", hash = "sha256:be5980f3ee0e6bd44f3a9e9dea01054f175b50c3e6cdb692bc9424c0bbb8bf69", size = 64119 }, + { url = "https://files.pythonhosted.org/packages/6b/31/b46518ecc604d7edf3a4f94cb3bf021fc62aa301f0cb849936968164ef23/msgpack-1.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4efd7b5979ccb539c221a4c4e16aac1a533efc97f3b759bb5a5ac9f6d10383bf", size = 81212 }, + { url = "https://files.pythonhosted.org/packages/92/dc/c385f38f2c2433333345a82926c6bfa5ecfff3ef787201614317b58dd8be/msgpack-1.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:42eefe2c3e2af97ed470eec850facbe1b5ad1d6eacdbadc42ec98e7dcf68b4b7", size = 84315 }, + { url = "https://files.pythonhosted.org/packages/d3/68/93180dce57f684a61a88a45ed13047558ded2be46f03acb8dec6d7c513af/msgpack-1.1.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1fdf7d83102bf09e7ce3357de96c59b627395352a4024f6e2458501f158bf999", size = 412721 }, + { url = "https://files.pythonhosted.org/packages/5d/ba/459f18c16f2b3fc1a1ca871f72f07d70c07bf768ad0a507a698b8052ac58/msgpack-1.1.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fac4be746328f90caa3cd4bc67e6fe36ca2bf61d5c6eb6d895b6527e3f05071e", size = 424657 }, + { url = "https://files.pythonhosted.org/packages/38/f8/4398c46863b093252fe67368b44edc6c13b17f4e6b0e4929dbf0bdb13f23/msgpack-1.1.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:fffee09044073e69f2bad787071aeec727183e7580443dfeb8556cbf1978d162", size = 402668 }, + { url = "https://files.pythonhosted.org/packages/28/ce/698c1eff75626e4124b4d78e21cca0b4cc90043afb80a507626ea354ab52/msgpack-1.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5928604de9b032bc17f5099496417f113c45bc6bc21b5c6920caf34b3c428794", size = 419040 }, + { url = "https://files.pythonhosted.org/packages/67/32/f3cd1667028424fa7001d82e10ee35386eea1408b93d399b09fb0aa7875f/msgpack-1.1.2-cp313-cp313-win32.whl", hash = "sha256:a7787d353595c7c7e145e2331abf8b7ff1e6673a6b974ded96e6d4ec09f00c8c", size = 65037 }, + { url = "https://files.pythonhosted.org/packages/74/07/1ed8277f8653c40ebc65985180b007879f6a836c525b3885dcc6448ae6cb/msgpack-1.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:a465f0dceb8e13a487e54c07d04ae3ba131c7c5b95e2612596eafde1dccf64a9", size = 72631 }, + { url = "https://files.pythonhosted.org/packages/e5/db/0314e4e2db56ebcf450f277904ffd84a7988b9e5da8d0d61ab2d057df2b6/msgpack-1.1.2-cp313-cp313-win_arm64.whl", hash = "sha256:e69b39f8c0aa5ec24b57737ebee40be647035158f14ed4b40e6f150077e21a84", size = 64118 }, ] [[package]] name = "networkx" version = "3.6.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025, upload-time = "2025-12-08T17:02:39.908Z" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504, upload-time = "2025-12-08T17:02:38.159Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504 }, ] [[package]] name = "numpy" version = "2.5.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/22/fd/89965aa4ac08c74998539fcbf24fa3540f3e15237fbeb6bcf9c908f4aade/numpy-2.5.1.tar.gz", hash = "sha256:a48a113e6afea91f5608793bafa7ef2ad481fefbda87ec5069f483de61cb9fa3", size = 20755553, upload-time = "2026-07-04T17:08:00.933Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/62/7b/14687aa674250e5e546f616f486b0d56d3631cd5b2415739141ce40bdcea/numpy-2.5.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2c889b56fe48b1018f764b0eec8df59ab654e9148aa91faa12596043500de277", size = 16801574, upload-time = "2026-07-04T17:06:12.423Z" }, - { url = "https://files.pythonhosted.org/packages/e1/19/cc5bb2a3f2913d27d6dbb2c78d25921fabaedc6741d4a5a615a11f3c5bf3/numpy-2.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ab451b59c5643c570974c43aef780703ef1d3b4965d2be07afd530615a9358d1", size = 11772250, upload-time = "2026-07-04T17:06:15.726Z" }, - { url = "https://files.pythonhosted.org/packages/42/77/fdf34a71dd30f54979b18603bee915e0aaf825b07afe79acd60b04b691e2/numpy-2.5.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:78798bd5b9ad744056af8efa90e3b9ddaa53272a0848a483084a1cc0a13b2dc0", size = 5331516, upload-time = "2026-07-04T17:06:17.913Z" }, - { url = "https://files.pythonhosted.org/packages/ce/e2/eb7efa015b4cce41e2517bf182a7fce0d7d5b9d9ed76a29bfa0f4fe4505c/numpy-2.5.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:2ae0ca40bcb22d6ba59c1dfd5446f49940b0f2d821fde133f10dda11f816b84e", size = 6664863, upload-time = "2026-07-04T17:06:20.02Z" }, - { url = "https://files.pythonhosted.org/packages/a9/4b/a2b32dd94ee9ffbeecb28152240042a3949db33b1c834d44090b80e1b3b8/numpy-2.5.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:61ac47e772e6b8ea489e1d2f441a34c5c3ac17327e7ce294cbdf535795ad4e75", size = 15167977, upload-time = "2026-07-04T17:06:21.621Z" }, - { url = "https://files.pythonhosted.org/packages/b8/a9/6e73d68500f80773f65f0654ea932019d6694329a0eb0ed0533de38df376/numpy-2.5.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:59fda5e192b570217ec2580c96f00e9a7e12ef6866a900eb089b62c1a32545ca", size = 16672469, upload-time = "2026-07-04T17:06:24.064Z" }, - { url = "https://files.pythonhosted.org/packages/24/7d/ad3e59015135f5261c95fd4cafeff159c955febd83a99a1d9250c4233815/numpy-2.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f7119ebff1a9829e9f431a4f9d28e703023bb6b9fe7c8f724467dbfc27c94ab3", size = 16527531, upload-time = "2026-07-04T17:06:26.69Z" }, - { url = "https://files.pythonhosted.org/packages/83/d0/a39b2fbcde9cb17a1dac678f254b33a6336298af9df338824c685425d5e8/numpy-2.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e824c2acf8862052246be5a44c15da1777940c60d010dd2aab897824d9c430f9", size = 18431940, upload-time = "2026-07-04T17:06:29.521Z" }, - { url = "https://files.pythonhosted.org/packages/04/12/cff070947791c1ed425ff76413189adbdc2fbe215eba7ce7fa454a03c7f8/numpy-2.5.1-cp312-cp312-win32.whl", hash = "sha256:08d60c810432eb83360958dea0999ac4cfb94531ea8efcbf0b7f277c2068aeb2", size = 6066764, upload-time = "2026-07-04T17:06:32.571Z" }, - { url = "https://files.pythonhosted.org/packages/65/66/53f31807a48a750f9d748da273bc3fcedd12b27ff1f3e373bfec55ef2dc0/numpy-2.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:f7d60026c0bdb1380e83bfa7a0419c4577ee4b9a08880afcb6dadeb74c649fa2", size = 12430966, upload-time = "2026-07-04T17:06:34.926Z" }, - { url = "https://files.pythonhosted.org/packages/2b/2a/d1a88066b1c14186f5d3c0d18c94f17b064511982bab0578d49ee9d43c29/numpy-2.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:17a25e09640602e10bc8de0e6fa2b3fd68eedd84ba6d7842dc8f32f9ab87bd0b", size = 10350488, upload-time = "2026-07-04T17:06:37.785Z" }, - { url = "https://files.pythonhosted.org/packages/eb/07/ec2a3f0c91761581d4b7104a740791800025983f9a4dc4e73f91a99aeac4/numpy-2.5.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0bfebd8695f9863592fe744be833a258120b14a9f39da255e8aa8fade2c0ddd1", size = 16796419, upload-time = "2026-07-04T17:06:40.37Z" }, - { url = "https://files.pythonhosted.org/packages/ab/ab/ddb499fc4f8780354395face5b65c7fd107bcd6e1d667a5f07d046956f6f/numpy-2.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:30b44a6b53a7ae63c54c089a8726e5563ed302716c5b7ccc85afade40b0e7ff6", size = 11765832, upload-time = "2026-07-04T17:06:42.768Z" }, - { url = "https://files.pythonhosted.org/packages/88/b3/3c28c558a09fc72100c646dac6d2fce8e834c471b0edca01a29996706117/numpy-2.5.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:6165343f81b56ef8f514f396989e529b61d9dc709b99421b07e9f3e698e2287d", size = 5325143, upload-time = "2026-07-04T17:06:45.466Z" }, - { url = "https://files.pythonhosted.org/packages/5e/0e/ce19b985bb15c596f4f05954e76cccc77c845083b3b8f938a6c68e523128/numpy-2.5.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4939237038ada79308dda3204ac6462df056b5672b2e25db1149cf873668b3e1", size = 6659749, upload-time = "2026-07-04T17:06:47.288Z" }, - { url = "https://files.pythonhosted.org/packages/2e/20/1ee6614d64332a1bba6411f38e68cb79eec1b2459e20a623777c5c5492a2/numpy-2.5.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c6759f538fb912fc46de0a6b1758ccf7b57bc7c7ebebc23974fdac3de8db0cd", size = 15164716, upload-time = "2026-07-04T17:06:49.494Z" }, - { url = "https://files.pythonhosted.org/packages/ed/a7/2bcd3fdbb87804755c35b729bf8709d62025c5f4cfd7d5b2415997097515/numpy-2.5.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9726558e8db4a5bf7929a70ae50f63abda4daf0efe810e3bfbab95976f75fc1a", size = 16661440, upload-time = "2026-07-04T17:06:52.061Z" }, - { url = "https://files.pythonhosted.org/packages/fc/d7/a41e3310c886fe457d36e670bbf24fae411aca8a7b6ad92a32afd924077c/numpy-2.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3935f3b419b244a02732676fa5317a9193cc596a4c0646db07e5b421229ac9f7", size = 16526305, upload-time = "2026-07-04T17:06:54.605Z" }, - { url = "https://files.pythonhosted.org/packages/53/75/4333a9a707c1edd3a4e1a0c58eca52c0f31e55089fa80db02b5565b24df7/numpy-2.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dc932a65ded7ce9013d120845a2514dcccb1a67bfc8deb8d37633762951904a6", size = 18423008, upload-time = "2026-07-04T17:06:57.54Z" }, - { url = "https://files.pythonhosted.org/packages/ee/90/e314a32b1c11a2ffe818ddad3a57b50b4b6e1b6c487192eb50cdef0415d0/numpy-2.5.1-cp313-cp313-win32.whl", hash = "sha256:4b4ff1608417eb7a59da7b967bbb798cacfe071d2caf526a24281cd562072ed9", size = 6063885, upload-time = "2026-07-04T17:07:00.14Z" }, - { url = "https://files.pythonhosted.org/packages/10/70/800b3fca480af32df9e8ea9f3d4a0c8feb4b32d7f195d174eabbda4829ad/numpy-2.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:6c3fe51bc6a16453d452997053454f309e8e0ed7b42d6b361ce4ac8c32913d74", size = 12425674, upload-time = "2026-07-04T17:07:02.387Z" }, - { url = "https://files.pythonhosted.org/packages/8b/0b/196350c122f50f6ca56846f2d71efd5e0d24b7b2e07355e019b2e2c7a11e/numpy-2.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:f7feb014281029e628ba2d5a007407443b06e418b6fe451d1e2adcbc8eba0107", size = 10350256, upload-time = "2026-07-04T17:07:04.878Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/22/fd/89965aa4ac08c74998539fcbf24fa3540f3e15237fbeb6bcf9c908f4aade/numpy-2.5.1.tar.gz", hash = "sha256:a48a113e6afea91f5608793bafa7ef2ad481fefbda87ec5069f483de61cb9fa3", size = 20755553 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/7b/14687aa674250e5e546f616f486b0d56d3631cd5b2415739141ce40bdcea/numpy-2.5.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2c889b56fe48b1018f764b0eec8df59ab654e9148aa91faa12596043500de277", size = 16801574 }, + { url = "https://files.pythonhosted.org/packages/e1/19/cc5bb2a3f2913d27d6dbb2c78d25921fabaedc6741d4a5a615a11f3c5bf3/numpy-2.5.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ab451b59c5643c570974c43aef780703ef1d3b4965d2be07afd530615a9358d1", size = 11772250 }, + { url = "https://files.pythonhosted.org/packages/42/77/fdf34a71dd30f54979b18603bee915e0aaf825b07afe79acd60b04b691e2/numpy-2.5.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:78798bd5b9ad744056af8efa90e3b9ddaa53272a0848a483084a1cc0a13b2dc0", size = 5331516 }, + { url = "https://files.pythonhosted.org/packages/ce/e2/eb7efa015b4cce41e2517bf182a7fce0d7d5b9d9ed76a29bfa0f4fe4505c/numpy-2.5.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:2ae0ca40bcb22d6ba59c1dfd5446f49940b0f2d821fde133f10dda11f816b84e", size = 6664863 }, + { url = "https://files.pythonhosted.org/packages/a9/4b/a2b32dd94ee9ffbeecb28152240042a3949db33b1c834d44090b80e1b3b8/numpy-2.5.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:61ac47e772e6b8ea489e1d2f441a34c5c3ac17327e7ce294cbdf535795ad4e75", size = 15167977 }, + { url = "https://files.pythonhosted.org/packages/b8/a9/6e73d68500f80773f65f0654ea932019d6694329a0eb0ed0533de38df376/numpy-2.5.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:59fda5e192b570217ec2580c96f00e9a7e12ef6866a900eb089b62c1a32545ca", size = 16672469 }, + { url = "https://files.pythonhosted.org/packages/24/7d/ad3e59015135f5261c95fd4cafeff159c955febd83a99a1d9250c4233815/numpy-2.5.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f7119ebff1a9829e9f431a4f9d28e703023bb6b9fe7c8f724467dbfc27c94ab3", size = 16527531 }, + { url = "https://files.pythonhosted.org/packages/83/d0/a39b2fbcde9cb17a1dac678f254b33a6336298af9df338824c685425d5e8/numpy-2.5.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e824c2acf8862052246be5a44c15da1777940c60d010dd2aab897824d9c430f9", size = 18431940 }, + { url = "https://files.pythonhosted.org/packages/04/12/cff070947791c1ed425ff76413189adbdc2fbe215eba7ce7fa454a03c7f8/numpy-2.5.1-cp312-cp312-win32.whl", hash = "sha256:08d60c810432eb83360958dea0999ac4cfb94531ea8efcbf0b7f277c2068aeb2", size = 6066764 }, + { url = "https://files.pythonhosted.org/packages/65/66/53f31807a48a750f9d748da273bc3fcedd12b27ff1f3e373bfec55ef2dc0/numpy-2.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:f7d60026c0bdb1380e83bfa7a0419c4577ee4b9a08880afcb6dadeb74c649fa2", size = 12430966 }, + { url = "https://files.pythonhosted.org/packages/2b/2a/d1a88066b1c14186f5d3c0d18c94f17b064511982bab0578d49ee9d43c29/numpy-2.5.1-cp312-cp312-win_arm64.whl", hash = "sha256:17a25e09640602e10bc8de0e6fa2b3fd68eedd84ba6d7842dc8f32f9ab87bd0b", size = 10350488 }, + { url = "https://files.pythonhosted.org/packages/eb/07/ec2a3f0c91761581d4b7104a740791800025983f9a4dc4e73f91a99aeac4/numpy-2.5.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0bfebd8695f9863592fe744be833a258120b14a9f39da255e8aa8fade2c0ddd1", size = 16796419 }, + { url = "https://files.pythonhosted.org/packages/ab/ab/ddb499fc4f8780354395face5b65c7fd107bcd6e1d667a5f07d046956f6f/numpy-2.5.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:30b44a6b53a7ae63c54c089a8726e5563ed302716c5b7ccc85afade40b0e7ff6", size = 11765832 }, + { url = "https://files.pythonhosted.org/packages/88/b3/3c28c558a09fc72100c646dac6d2fce8e834c471b0edca01a29996706117/numpy-2.5.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:6165343f81b56ef8f514f396989e529b61d9dc709b99421b07e9f3e698e2287d", size = 5325143 }, + { url = "https://files.pythonhosted.org/packages/5e/0e/ce19b985bb15c596f4f05954e76cccc77c845083b3b8f938a6c68e523128/numpy-2.5.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4939237038ada79308dda3204ac6462df056b5672b2e25db1149cf873668b3e1", size = 6659749 }, + { url = "https://files.pythonhosted.org/packages/2e/20/1ee6614d64332a1bba6411f38e68cb79eec1b2459e20a623777c5c5492a2/numpy-2.5.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1c6759f538fb912fc46de0a6b1758ccf7b57bc7c7ebebc23974fdac3de8db0cd", size = 15164716 }, + { url = "https://files.pythonhosted.org/packages/ed/a7/2bcd3fdbb87804755c35b729bf8709d62025c5f4cfd7d5b2415997097515/numpy-2.5.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9726558e8db4a5bf7929a70ae50f63abda4daf0efe810e3bfbab95976f75fc1a", size = 16661440 }, + { url = "https://files.pythonhosted.org/packages/fc/d7/a41e3310c886fe457d36e670bbf24fae411aca8a7b6ad92a32afd924077c/numpy-2.5.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3935f3b419b244a02732676fa5317a9193cc596a4c0646db07e5b421229ac9f7", size = 16526305 }, + { url = "https://files.pythonhosted.org/packages/53/75/4333a9a707c1edd3a4e1a0c58eca52c0f31e55089fa80db02b5565b24df7/numpy-2.5.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:dc932a65ded7ce9013d120845a2514dcccb1a67bfc8deb8d37633762951904a6", size = 18423008 }, + { url = "https://files.pythonhosted.org/packages/ee/90/e314a32b1c11a2ffe818ddad3a57b50b4b6e1b6c487192eb50cdef0415d0/numpy-2.5.1-cp313-cp313-win32.whl", hash = "sha256:4b4ff1608417eb7a59da7b967bbb798cacfe071d2caf526a24281cd562072ed9", size = 6063885 }, + { url = "https://files.pythonhosted.org/packages/10/70/800b3fca480af32df9e8ea9f3d4a0c8feb4b32d7f195d174eabbda4829ad/numpy-2.5.1-cp313-cp313-win_amd64.whl", hash = "sha256:6c3fe51bc6a16453d452997053454f309e8e0ed7b42d6b361ce4ac8c32913d74", size = 12425674 }, + { url = "https://files.pythonhosted.org/packages/8b/0b/196350c122f50f6ca56846f2d71efd5e0d24b7b2e07355e019b2e2c7a11e/numpy-2.5.1-cp313-cp313-win_arm64.whl", hash = "sha256:f7feb014281029e628ba2d5a007407443b06e418b6fe451d1e2adcbc8eba0107", size = 10350256 }, ] [[package]] name = "orderly-set" version = "5.5.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4a/88/39c83c35d5e97cc203e9e77a4f93bf87ec89cf6a22ac4818fdcc65d66584/orderly_set-5.5.0.tar.gz", hash = "sha256:e87185c8e4d8afa64e7f8160ee2c542a475b738bc891dc3f58102e654125e6ce", size = 27414, upload-time = "2025-07-10T20:10:55.885Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4a/88/39c83c35d5e97cc203e9e77a4f93bf87ec89cf6a22ac4818fdcc65d66584/orderly_set-5.5.0.tar.gz", hash = "sha256:e87185c8e4d8afa64e7f8160ee2c542a475b738bc891dc3f58102e654125e6ce", size = 27414 } wheels = [ - { url = "https://files.pythonhosted.org/packages/12/27/fb8d7338b4d551900fa3e580acbe7a0cf655d940e164cb5c00ec31961094/orderly_set-5.5.0-py3-none-any.whl", hash = "sha256:46f0b801948e98f427b412fcabb831677194c05c3b699b80de260374baa0b1e7", size = 13068, upload-time = "2025-07-10T20:10:54.377Z" }, + { url = "https://files.pythonhosted.org/packages/12/27/fb8d7338b4d551900fa3e580acbe7a0cf655d940e164cb5c00ec31961094/orderly_set-5.5.0-py3-none-any.whl", hash = "sha256:46f0b801948e98f427b412fcabb831677194c05c3b699b80de260374baa0b1e7", size = 13068 }, ] [[package]] name = "orjson" version = "3.11.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/53/45/b268004f745ede84e5798b48ee12b05129d19235d0e15267aa57dcdb400b/orjson-3.11.7.tar.gz", hash = "sha256:9b1a67243945819ce55d24a30b59d6a168e86220452d2c96f4d1f093e71c0c49", size = 6144992, upload-time = "2026-02-02T15:38:49.29Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/80/bf/76f4f1665f6983385938f0e2a5d7efa12a58171b8456c252f3bae8a4cf75/orjson-3.11.7-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:bd03ea7606833655048dab1a00734a2875e3e86c276e1d772b2a02556f0d895f", size = 228545, upload-time = "2026-02-02T15:37:46.376Z" }, - { url = "https://files.pythonhosted.org/packages/79/53/6c72c002cb13b5a978a068add59b25a8bdf2800ac1c9c8ecdb26d6d97064/orjson-3.11.7-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:89e440ebc74ce8ab5c7bc4ce6757b4a6b1041becb127df818f6997b5c71aa60b", size = 125224, upload-time = "2026-02-02T15:37:47.697Z" }, - { url = "https://files.pythonhosted.org/packages/2c/83/10e48852865e5dd151bdfe652c06f7da484578ed02c5fca938e3632cb0b8/orjson-3.11.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ede977b5fe5ac91b1dffc0a517ca4542d2ec8a6a4ff7b2652d94f640796342a", size = 128154, upload-time = "2026-02-02T15:37:48.954Z" }, - { url = "https://files.pythonhosted.org/packages/6e/52/a66e22a2b9abaa374b4a081d410edab6d1e30024707b87eab7c734afe28d/orjson-3.11.7-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b7b1dae39230a393df353827c855a5f176271c23434cfd2db74e0e424e693e10", size = 123548, upload-time = "2026-02-02T15:37:50.187Z" }, - { url = "https://files.pythonhosted.org/packages/de/38/605d371417021359f4910c496f764c48ceb8997605f8c25bf1dfe58c0ebe/orjson-3.11.7-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed46f17096e28fb28d2975834836a639af7278aa87c84f68ab08fbe5b8bd75fa", size = 129000, upload-time = "2026-02-02T15:37:51.426Z" }, - { url = "https://files.pythonhosted.org/packages/44/98/af32e842b0ffd2335c89714d48ca4e3917b42f5d6ee5537832e069a4b3ac/orjson-3.11.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3726be79e36e526e3d9c1aceaadbfb4a04ee80a72ab47b3f3c17fefb9812e7b8", size = 141686, upload-time = "2026-02-02T15:37:52.607Z" }, - { url = "https://files.pythonhosted.org/packages/96/0b/fc793858dfa54be6feee940c1463370ece34b3c39c1ca0aa3845f5ba9892/orjson-3.11.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0724e265bc548af1dedebd9cb3d24b4e1c1e685a343be43e87ba922a5c5fff2f", size = 130812, upload-time = "2026-02-02T15:37:53.944Z" }, - { url = "https://files.pythonhosted.org/packages/dc/91/98a52415059db3f374757d0b7f0f16e3b5cd5976c90d1c2b56acaea039e6/orjson-3.11.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7745312efa9e11c17fbd3cb3097262d079da26930ae9ae7ba28fb738367cbad", size = 133440, upload-time = "2026-02-02T15:37:55.615Z" }, - { url = "https://files.pythonhosted.org/packages/dc/b6/cb540117bda61791f46381f8c26c8f93e802892830a6055748d3bb1925ab/orjson-3.11.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f904c24bdeabd4298f7a977ef14ca2a022ca921ed670b92ecd16ab6f3d01f867", size = 138386, upload-time = "2026-02-02T15:37:56.814Z" }, - { url = "https://files.pythonhosted.org/packages/63/1a/50a3201c334a7f17c231eee5f841342190723794e3b06293f26e7cf87d31/orjson-3.11.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b9fc4d0f81f394689e0814617aadc4f2ea0e8025f38c226cbf22d3b5ddbf025d", size = 408853, upload-time = "2026-02-02T15:37:58.291Z" }, - { url = "https://files.pythonhosted.org/packages/87/cd/8de1c67d0be44fdc22701e5989c0d015a2adf391498ad42c4dc589cd3013/orjson-3.11.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:849e38203e5be40b776ed2718e587faf204d184fc9a008ae441f9442320c0cab", size = 144130, upload-time = "2026-02-02T15:38:00.163Z" }, - { url = "https://files.pythonhosted.org/packages/0f/fe/d605d700c35dd55f51710d159fc54516a280923cd1b7e47508982fbb387d/orjson-3.11.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4682d1db3bcebd2b64757e0ddf9e87ae5f00d29d16c5cdf3a62f561d08cc3dd2", size = 134818, upload-time = "2026-02-02T15:38:01.507Z" }, - { url = "https://files.pythonhosted.org/packages/e4/e4/15ecc67edb3ddb3e2f46ae04475f2d294e8b60c1825fbe28a428b93b3fbd/orjson-3.11.7-cp312-cp312-win32.whl", hash = "sha256:f4f7c956b5215d949a1f65334cf9d7612dde38f20a95f2315deef167def91a6f", size = 127923, upload-time = "2026-02-02T15:38:02.75Z" }, - { url = "https://files.pythonhosted.org/packages/34/70/2e0855361f76198a3965273048c8e50a9695d88cd75811a5b46444895845/orjson-3.11.7-cp312-cp312-win_amd64.whl", hash = "sha256:bf742e149121dc5648ba0a08ea0871e87b660467ef168a3a5e53bc1fbd64bb74", size = 125007, upload-time = "2026-02-02T15:38:04.032Z" }, - { url = "https://files.pythonhosted.org/packages/68/40/c2051bd19fc467610fed469dc29e43ac65891571138f476834ca192bc290/orjson-3.11.7-cp312-cp312-win_arm64.whl", hash = "sha256:26c3b9132f783b7d7903bf1efb095fed8d4a3a85ec0d334ee8beff3d7a4749d5", size = 126089, upload-time = "2026-02-02T15:38:05.297Z" }, - { url = "https://files.pythonhosted.org/packages/89/25/6e0e52cac5aab51d7b6dcd257e855e1dec1c2060f6b28566c509b4665f62/orjson-3.11.7-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:1d98b30cc1313d52d4af17d9c3d307b08389752ec5f2e5febdfada70b0f8c733", size = 228390, upload-time = "2026-02-02T15:38:06.8Z" }, - { url = "https://files.pythonhosted.org/packages/a5/29/a77f48d2fc8a05bbc529e5ff481fb43d914f9e383ea2469d4f3d51df3d00/orjson-3.11.7-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:d897e81f8d0cbd2abb82226d1860ad2e1ab3ff16d7b08c96ca00df9d45409ef4", size = 125189, upload-time = "2026-02-02T15:38:08.181Z" }, - { url = "https://files.pythonhosted.org/packages/89/25/0a16e0729a0e6a1504f9d1a13cdd365f030068aab64cec6958396b9969d7/orjson-3.11.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:814be4b49b228cfc0b3c565acf642dd7d13538f966e3ccde61f4f55be3e20785", size = 128106, upload-time = "2026-02-02T15:38:09.41Z" }, - { url = "https://files.pythonhosted.org/packages/66/da/a2e505469d60666a05ab373f1a6322eb671cb2ba3a0ccfc7d4bc97196787/orjson-3.11.7-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d06e5c5fed5caedd2e540d62e5b1c25e8c82431b9e577c33537e5fa4aa909539", size = 123363, upload-time = "2026-02-02T15:38:10.73Z" }, - { url = "https://files.pythonhosted.org/packages/23/bf/ed73f88396ea35c71b38961734ea4a4746f7ca0768bf28fd551d37e48dd0/orjson-3.11.7-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:31c80ce534ac4ea3739c5ee751270646cbc46e45aea7576a38ffec040b4029a1", size = 129007, upload-time = "2026-02-02T15:38:12.138Z" }, - { url = "https://files.pythonhosted.org/packages/73/3c/b05d80716f0225fc9008fbf8ab22841dcc268a626aa550561743714ce3bf/orjson-3.11.7-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f50979824bde13d32b4320eedd513431c921102796d86be3eee0b58e58a3ecd1", size = 141667, upload-time = "2026-02-02T15:38:13.398Z" }, - { url = "https://files.pythonhosted.org/packages/61/e8/0be9b0addd9bf86abfc938e97441dcd0375d494594b1c8ad10fe57479617/orjson-3.11.7-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e54f3808e2b6b945078c41aa8d9b5834b28c50843846e97807e5adb75fa9705", size = 130832, upload-time = "2026-02-02T15:38:14.698Z" }, - { url = "https://files.pythonhosted.org/packages/c9/ec/c68e3b9021a31d9ec15a94931db1410136af862955854ed5dd7e7e4f5bff/orjson-3.11.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a12b80df61aab7b98b490fe9e4879925ba666fccdfcd175252ce4d9035865ace", size = 133373, upload-time = "2026-02-02T15:38:16.109Z" }, - { url = "https://files.pythonhosted.org/packages/d2/45/f3466739aaafa570cc8e77c6dbb853c48bf56e3b43738020e2661e08b0ac/orjson-3.11.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:996b65230271f1a97026fd0e6a753f51fbc0c335d2ad0c6201f711b0da32693b", size = 138307, upload-time = "2026-02-02T15:38:17.453Z" }, - { url = "https://files.pythonhosted.org/packages/e1/84/9f7f02288da1ffb31405c1be07657afd1eecbcb4b64ee2817b6fe0f785fa/orjson-3.11.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ab49d4b2a6a1d415ddb9f37a21e02e0d5dbfe10b7870b21bf779fc21e9156157", size = 408695, upload-time = "2026-02-02T15:38:18.831Z" }, - { url = "https://files.pythonhosted.org/packages/18/07/9dd2f0c0104f1a0295ffbe912bc8d63307a539b900dd9e2c48ef7810d971/orjson-3.11.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:390a1dce0c055ddf8adb6aa94a73b45a4a7d7177b5c584b8d1c1947f2ba60fb3", size = 144099, upload-time = "2026-02-02T15:38:20.28Z" }, - { url = "https://files.pythonhosted.org/packages/a5/66/857a8e4a3292e1f7b1b202883bcdeb43a91566cf59a93f97c53b44bd6801/orjson-3.11.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1eb80451a9c351a71dfaf5b7ccc13ad065405217726b59fdbeadbcc544f9d223", size = 134806, upload-time = "2026-02-02T15:38:22.186Z" }, - { url = "https://files.pythonhosted.org/packages/0a/5b/6ebcf3defc1aab3a338ca777214966851e92efb1f30dc7fc8285216e6d1b/orjson-3.11.7-cp313-cp313-win32.whl", hash = "sha256:7477aa6a6ec6139c5cb1cc7b214643592169a5494d200397c7fc95d740d5fcf3", size = 127914, upload-time = "2026-02-02T15:38:23.511Z" }, - { url = "https://files.pythonhosted.org/packages/00/04/c6f72daca5092e3117840a1b1e88dfc809cc1470cf0734890d0366b684a1/orjson-3.11.7-cp313-cp313-win_amd64.whl", hash = "sha256:b9f95dcdea9d4f805daa9ddf02617a89e484c6985fa03055459f90e87d7a0757", size = 124986, upload-time = "2026-02-02T15:38:24.836Z" }, - { url = "https://files.pythonhosted.org/packages/03/ba/077a0f6f1085d6b806937246860fafbd5b17f3919c70ee3f3d8d9c713f38/orjson-3.11.7-cp313-cp313-win_arm64.whl", hash = "sha256:800988273a014a0541483dc81021247d7eacb0c845a9d1a34a422bc718f41539", size = 126045, upload-time = "2026-02-02T15:38:26.216Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/53/45/b268004f745ede84e5798b48ee12b05129d19235d0e15267aa57dcdb400b/orjson-3.11.7.tar.gz", hash = "sha256:9b1a67243945819ce55d24a30b59d6a168e86220452d2c96f4d1f093e71c0c49", size = 6144992 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/80/bf/76f4f1665f6983385938f0e2a5d7efa12a58171b8456c252f3bae8a4cf75/orjson-3.11.7-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:bd03ea7606833655048dab1a00734a2875e3e86c276e1d772b2a02556f0d895f", size = 228545 }, + { url = "https://files.pythonhosted.org/packages/79/53/6c72c002cb13b5a978a068add59b25a8bdf2800ac1c9c8ecdb26d6d97064/orjson-3.11.7-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:89e440ebc74ce8ab5c7bc4ce6757b4a6b1041becb127df818f6997b5c71aa60b", size = 125224 }, + { url = "https://files.pythonhosted.org/packages/2c/83/10e48852865e5dd151bdfe652c06f7da484578ed02c5fca938e3632cb0b8/orjson-3.11.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ede977b5fe5ac91b1dffc0a517ca4542d2ec8a6a4ff7b2652d94f640796342a", size = 128154 }, + { url = "https://files.pythonhosted.org/packages/6e/52/a66e22a2b9abaa374b4a081d410edab6d1e30024707b87eab7c734afe28d/orjson-3.11.7-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b7b1dae39230a393df353827c855a5f176271c23434cfd2db74e0e424e693e10", size = 123548 }, + { url = "https://files.pythonhosted.org/packages/de/38/605d371417021359f4910c496f764c48ceb8997605f8c25bf1dfe58c0ebe/orjson-3.11.7-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed46f17096e28fb28d2975834836a639af7278aa87c84f68ab08fbe5b8bd75fa", size = 129000 }, + { url = "https://files.pythonhosted.org/packages/44/98/af32e842b0ffd2335c89714d48ca4e3917b42f5d6ee5537832e069a4b3ac/orjson-3.11.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3726be79e36e526e3d9c1aceaadbfb4a04ee80a72ab47b3f3c17fefb9812e7b8", size = 141686 }, + { url = "https://files.pythonhosted.org/packages/96/0b/fc793858dfa54be6feee940c1463370ece34b3c39c1ca0aa3845f5ba9892/orjson-3.11.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0724e265bc548af1dedebd9cb3d24b4e1c1e685a343be43e87ba922a5c5fff2f", size = 130812 }, + { url = "https://files.pythonhosted.org/packages/dc/91/98a52415059db3f374757d0b7f0f16e3b5cd5976c90d1c2b56acaea039e6/orjson-3.11.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7745312efa9e11c17fbd3cb3097262d079da26930ae9ae7ba28fb738367cbad", size = 133440 }, + { url = "https://files.pythonhosted.org/packages/dc/b6/cb540117bda61791f46381f8c26c8f93e802892830a6055748d3bb1925ab/orjson-3.11.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f904c24bdeabd4298f7a977ef14ca2a022ca921ed670b92ecd16ab6f3d01f867", size = 138386 }, + { url = "https://files.pythonhosted.org/packages/63/1a/50a3201c334a7f17c231eee5f841342190723794e3b06293f26e7cf87d31/orjson-3.11.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b9fc4d0f81f394689e0814617aadc4f2ea0e8025f38c226cbf22d3b5ddbf025d", size = 408853 }, + { url = "https://files.pythonhosted.org/packages/87/cd/8de1c67d0be44fdc22701e5989c0d015a2adf391498ad42c4dc589cd3013/orjson-3.11.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:849e38203e5be40b776ed2718e587faf204d184fc9a008ae441f9442320c0cab", size = 144130 }, + { url = "https://files.pythonhosted.org/packages/0f/fe/d605d700c35dd55f51710d159fc54516a280923cd1b7e47508982fbb387d/orjson-3.11.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4682d1db3bcebd2b64757e0ddf9e87ae5f00d29d16c5cdf3a62f561d08cc3dd2", size = 134818 }, + { url = "https://files.pythonhosted.org/packages/e4/e4/15ecc67edb3ddb3e2f46ae04475f2d294e8b60c1825fbe28a428b93b3fbd/orjson-3.11.7-cp312-cp312-win32.whl", hash = "sha256:f4f7c956b5215d949a1f65334cf9d7612dde38f20a95f2315deef167def91a6f", size = 127923 }, + { url = "https://files.pythonhosted.org/packages/34/70/2e0855361f76198a3965273048c8e50a9695d88cd75811a5b46444895845/orjson-3.11.7-cp312-cp312-win_amd64.whl", hash = "sha256:bf742e149121dc5648ba0a08ea0871e87b660467ef168a3a5e53bc1fbd64bb74", size = 125007 }, + { url = "https://files.pythonhosted.org/packages/68/40/c2051bd19fc467610fed469dc29e43ac65891571138f476834ca192bc290/orjson-3.11.7-cp312-cp312-win_arm64.whl", hash = "sha256:26c3b9132f783b7d7903bf1efb095fed8d4a3a85ec0d334ee8beff3d7a4749d5", size = 126089 }, + { url = "https://files.pythonhosted.org/packages/89/25/6e0e52cac5aab51d7b6dcd257e855e1dec1c2060f6b28566c509b4665f62/orjson-3.11.7-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:1d98b30cc1313d52d4af17d9c3d307b08389752ec5f2e5febdfada70b0f8c733", size = 228390 }, + { url = "https://files.pythonhosted.org/packages/a5/29/a77f48d2fc8a05bbc529e5ff481fb43d914f9e383ea2469d4f3d51df3d00/orjson-3.11.7-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:d897e81f8d0cbd2abb82226d1860ad2e1ab3ff16d7b08c96ca00df9d45409ef4", size = 125189 }, + { url = "https://files.pythonhosted.org/packages/89/25/0a16e0729a0e6a1504f9d1a13cdd365f030068aab64cec6958396b9969d7/orjson-3.11.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:814be4b49b228cfc0b3c565acf642dd7d13538f966e3ccde61f4f55be3e20785", size = 128106 }, + { url = "https://files.pythonhosted.org/packages/66/da/a2e505469d60666a05ab373f1a6322eb671cb2ba3a0ccfc7d4bc97196787/orjson-3.11.7-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d06e5c5fed5caedd2e540d62e5b1c25e8c82431b9e577c33537e5fa4aa909539", size = 123363 }, + { url = "https://files.pythonhosted.org/packages/23/bf/ed73f88396ea35c71b38961734ea4a4746f7ca0768bf28fd551d37e48dd0/orjson-3.11.7-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:31c80ce534ac4ea3739c5ee751270646cbc46e45aea7576a38ffec040b4029a1", size = 129007 }, + { url = "https://files.pythonhosted.org/packages/73/3c/b05d80716f0225fc9008fbf8ab22841dcc268a626aa550561743714ce3bf/orjson-3.11.7-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f50979824bde13d32b4320eedd513431c921102796d86be3eee0b58e58a3ecd1", size = 141667 }, + { url = "https://files.pythonhosted.org/packages/61/e8/0be9b0addd9bf86abfc938e97441dcd0375d494594b1c8ad10fe57479617/orjson-3.11.7-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e54f3808e2b6b945078c41aa8d9b5834b28c50843846e97807e5adb75fa9705", size = 130832 }, + { url = "https://files.pythonhosted.org/packages/c9/ec/c68e3b9021a31d9ec15a94931db1410136af862955854ed5dd7e7e4f5bff/orjson-3.11.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a12b80df61aab7b98b490fe9e4879925ba666fccdfcd175252ce4d9035865ace", size = 133373 }, + { url = "https://files.pythonhosted.org/packages/d2/45/f3466739aaafa570cc8e77c6dbb853c48bf56e3b43738020e2661e08b0ac/orjson-3.11.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:996b65230271f1a97026fd0e6a753f51fbc0c335d2ad0c6201f711b0da32693b", size = 138307 }, + { url = "https://files.pythonhosted.org/packages/e1/84/9f7f02288da1ffb31405c1be07657afd1eecbcb4b64ee2817b6fe0f785fa/orjson-3.11.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ab49d4b2a6a1d415ddb9f37a21e02e0d5dbfe10b7870b21bf779fc21e9156157", size = 408695 }, + { url = "https://files.pythonhosted.org/packages/18/07/9dd2f0c0104f1a0295ffbe912bc8d63307a539b900dd9e2c48ef7810d971/orjson-3.11.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:390a1dce0c055ddf8adb6aa94a73b45a4a7d7177b5c584b8d1c1947f2ba60fb3", size = 144099 }, + { url = "https://files.pythonhosted.org/packages/a5/66/857a8e4a3292e1f7b1b202883bcdeb43a91566cf59a93f97c53b44bd6801/orjson-3.11.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1eb80451a9c351a71dfaf5b7ccc13ad065405217726b59fdbeadbcc544f9d223", size = 134806 }, + { url = "https://files.pythonhosted.org/packages/0a/5b/6ebcf3defc1aab3a338ca777214966851e92efb1f30dc7fc8285216e6d1b/orjson-3.11.7-cp313-cp313-win32.whl", hash = "sha256:7477aa6a6ec6139c5cb1cc7b214643592169a5494d200397c7fc95d740d5fcf3", size = 127914 }, + { url = "https://files.pythonhosted.org/packages/00/04/c6f72daca5092e3117840a1b1e88dfc809cc1470cf0734890d0366b684a1/orjson-3.11.7-cp313-cp313-win_amd64.whl", hash = "sha256:b9f95dcdea9d4f805daa9ddf02617a89e484c6985fa03055459f90e87d7a0757", size = 124986 }, + { url = "https://files.pythonhosted.org/packages/03/ba/077a0f6f1085d6b806937246860fafbd5b17f3919c70ee3f3d8d9c713f38/orjson-3.11.7-cp313-cp313-win_arm64.whl", hash = "sha256:800988273a014a0541483dc81021247d7eacb0c845a9d1a34a422bc718f41539", size = 126045 }, ] [[package]] name = "packaging" version = "26.0" source = { registry = "https://pypi.org/simple" } -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" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416 } wheels = [ - { 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" }, + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366 }, ] [[package]] name = "paginate" version = "0.5.7" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ec/46/68dde5b6bc00c1296ec6466ab27dddede6aec9af1b99090e1107091b3b84/paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945", size = 19252, upload-time = "2024-08-25T14:17:24.139Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ec/46/68dde5b6bc00c1296ec6466ab27dddede6aec9af1b99090e1107091b3b84/paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945", size = 19252 } wheels = [ - { url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746, upload-time = "2024-08-25T14:17:22.55Z" }, + { url = "https://files.pythonhosted.org/packages/90/96/04b8e52da071d28f5e21a805b19cb9390aa17a47462ac87f5e2696b9566d/paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591", size = 13746 }, ] [[package]] @@ -999,58 +1014,58 @@ dependencies = [ { name = "python-dateutil" }, { name = "tzdata", marker = "sys_platform == 'emscripten' or sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/f8/87/4341c6252d1c47b08768c3d25ac487362bf403f0313ddae4a2a26c9b1b4c/pandas-3.0.3.tar.gz", hash = "sha256:696a4a00a2a2a35d4e5deb3fc946641b96c944f02230e4f76137fe35d806c4fc", size = 4651414, upload-time = "2026-05-11T18:54:29.21Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/24/f1/392f8c5bfc16f66a0d2d41561c01627c228fe7ed2a0d056ef11315042570/pandas-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fed2ff7fd9779120e388e285fc029bd5cf9490cdd2e4166a9ee22c0e49a9ab09", size = 10357846, upload-time = "2026-05-11T18:52:36.143Z" }, - { url = "https://files.pythonhosted.org/packages/cf/3d/b16412745651e855f357e5e66930248688378853a6e2698a214e331fba1f/pandas-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b168fc218fd80a6cbdbdbc1a97ddc7889ed057d7eb45f50d866ceab5f39904c4", size = 9899550, upload-time = "2026-05-11T18:52:38.976Z" }, - { url = "https://files.pythonhosted.org/packages/31/a8/fa2535168fffcedf67f4f6de28d2dd903a747ca7c8ea6989451aaeb3a92f/pandas-3.0.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0383c72c75cdcca61a9e116e611143902dbfd08bff356829c2f6d1cf40a9ca8c", size = 10412965, upload-time = "2026-05-11T18:52:41.915Z" }, - { url = "https://files.pythonhosted.org/packages/65/b6/09b01cdbc15224e2850365192d17b7bdebb8bdbd8780ed221fcdf0d9a515/pandas-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6dc0b3fd2169c9157deed50b4d519553a3655c8c6a96027136d654592be973a9", size = 10894600, upload-time = "2026-05-11T18:52:45.02Z" }, - { url = "https://files.pythonhosted.org/packages/c9/a4/2eb28f2fccb4ced4a2c79ab2a5dee9ade1ebf44922ebad6fea158c9f95d4/pandas-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7e65d5407dc0b394f509699650e4a2ec01c0514f21850f453fa60f3be79a5dbf", size = 11422824, upload-time = "2026-05-11T18:52:48.058Z" }, - { url = "https://files.pythonhosted.org/packages/f8/45/830bb57f533a4604b355e07edcb8ea18cf88b5f94e5fca92f27052d7c597/pandas-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f8894dc474d648fe7b6ff0ca9b0bd73950d19952bc1a6534540762c5d79d305c", size = 11950889, upload-time = "2026-05-11T18:52:50.905Z" }, - { url = "https://files.pythonhosted.org/packages/b9/c5/fc1b368f303087d20e8c9bf3d6ceb186263cfac0ade735cd938538bea839/pandas-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:c7be265b62cef88e253a941e4698604973736dcfe242fdb5198f0f7bc473cdcc", size = 9755463, upload-time = "2026-05-11T18:52:53.386Z" }, - { url = "https://files.pythonhosted.org/packages/86/bd/fda8f9705b1b09c6ebe14bfc0fa0e4ec8584d54ea673628f157ff55131af/pandas-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:557409bc4178e70ee8d9ddb494798e51ebf6ea59330f6be22c51bab2a7db6c49", size = 9066158, upload-time = "2026-05-11T18:52:56.038Z" }, - { url = "https://files.pythonhosted.org/packages/c5/90/62d8302883c44308c477e222c3daf7c813a34c8e96985882fbd53d964352/pandas-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:67b3b64c11910cfa29f4e94a14d3bff9ee693b6fc76055e7cad549cee0aec5fa", size = 10331071, upload-time = "2026-05-11T18:52:58.838Z" }, - { url = "https://files.pythonhosted.org/packages/7f/ae/6a6493c783a101f165e4356953ba3c74d6f77f0042fa7d753da9dfbb640c/pandas-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:39436b377d56d2a2e52d0395bdbee171f01068e99af5250509aceeb929f765c7", size = 9875690, upload-time = "2026-05-11T18:53:01.431Z" }, - { url = "https://files.pythonhosted.org/packages/62/7c/5df8e9f56c69a2769fbe9382a5ef8f2658c007e376434e1e2cbb57ad895f/pandas-3.0.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4be06d68f9ddcfc645b87534911da79a8fbffc7573c80e0edcf42a5020624d8", size = 10381634, upload-time = "2026-05-11T18:53:04.393Z" }, - { url = "https://files.pythonhosted.org/packages/99/68/1237369725aa617bb358263d535803e3053fdbc593513ec5ed9c9896b5b6/pandas-3.0.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a4eeb6830daf35a71cc09649bd823e2b542dac246cdee9614c6e4bd65028cd6a", size = 10891243, upload-time = "2026-05-11T18:53:07.643Z" }, - { url = "https://files.pythonhosted.org/packages/25/93/77d108e8af7222b4a503ebde0e30215b1c2e4f8e53a526431890f22d5586/pandas-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1928e07221f82db493cd4af1e23c1bfca524a19a4699887975bff68f49a72bfb", size = 11388659, upload-time = "2026-05-11T18:53:10.634Z" }, - { url = "https://files.pythonhosted.org/packages/d0/bd/eff5b4399f332ac386c853f6cd2bd3fa2ca0061b9f36ecd9c4d7c4265649/pandas-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51b1fe551acb77dac643c6fda86084d8d446c10fe64b06a9cc29c4cc8540e7f2", size = 11942880, upload-time = "2026-05-11T18:53:13.536Z" }, - { url = "https://files.pythonhosted.org/packages/2c/20/559ace4200982c3887d0b86bfd0d856a2143ef8ddab63cc07934951a964c/pandas-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:a82d532a3351d435432cd913edbccaf8b8e01d4dd0e5ced5a8d2e8ecd94c7e44", size = 9757091, upload-time = "2026-05-11T18:53:16.306Z" }, - { url = "https://files.pythonhosted.org/packages/3a/66/69055a09fe200f29f922a3eeec4804611900b95f52d932ece3393c3c0c19/pandas-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:275c14e0fce14a2ec20eee474aecd305478ea3c1e6f6a9d8fe219a165542717e", size = 9057282, upload-time = "2026-05-11T18:53:18.768Z" }, - { url = "https://files.pythonhosted.org/packages/57/0e/efe801b0e6811e8e650cd21b7f2608e30f08a7067e2bf6e8752b0d56ee3c/pandas-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:46997386d528eb40376ecd6b033cf4a8a1e5282580f68f43de875b78cba2199d", size = 10767016, upload-time = "2026-05-11T18:53:21.227Z" }, - { url = "https://files.pythonhosted.org/packages/ea/dc/eb55135a1d5f0f0519f28da1f609a206d2cad1f9c35c32d51e38dd7261ae/pandas-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:261e308dfb22448384b7580cf719d2f998fe2966c92893c3e77d14008af1f066", size = 10420210, upload-time = "2026-05-11T18:53:23.982Z" }, - { url = "https://files.pythonhosted.org/packages/c6/3e/b1d5d955ce33ffecb407465a60bc32769d74fcf68224b7ae67ae11d4dea4/pandas-3.0.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dd1a5d1def6a46002e964510bdc67c368aa0951df5d1d9f8365336f5a1f490cd", size = 10336126, upload-time = "2026-05-11T18:53:26.731Z" }, - { url = "https://files.pythonhosted.org/packages/f5/76/a01261711ab60a22d71b862f0de20e4c504bf80457270ad8cb42110f6abc/pandas-3.0.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d72828c20c6d6e83e1e22a6a3b47b326b71664112fa9705dcbccfd7a39b62085", size = 10728051, upload-time = "2026-05-11T18:53:29.125Z" }, - { url = "https://files.pythonhosted.org/packages/e9/21/ea191195e587b18cf682e97f433f81b2d0fbe341380e80a3e0d6e4403c8e/pandas-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d26cbe1fcfc12e8fd900e2454163e466b2d3af84f7c75481df7683ffc073d870", size = 11350796, upload-time = "2026-05-11T18:53:32.056Z" }, - { url = "https://files.pythonhosted.org/packages/64/69/f0eaaf54939f0e8c6768fd06be9af2cef9b36048b96dfb9e1b2c685a807e/pandas-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3e91cec1879ada0624fc3dc9953c5cbd60208e59c0db28f540c5d6d47502422f", size = 11799741, upload-time = "2026-05-11T18:53:34.985Z" }, - { url = "https://files.pythonhosted.org/packages/45/a4/865e0e510cae5fc2194de4db28be638952de942571ba9125934fd9c01d47/pandas-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:08d789b41f87e0905880e293cedf6197ce71fe67cc081358b1e148a491b9bd13", size = 10499958, upload-time = "2026-05-11T18:53:37.857Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/f8/87/4341c6252d1c47b08768c3d25ac487362bf403f0313ddae4a2a26c9b1b4c/pandas-3.0.3.tar.gz", hash = "sha256:696a4a00a2a2a35d4e5deb3fc946641b96c944f02230e4f76137fe35d806c4fc", size = 4651414 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/24/f1/392f8c5bfc16f66a0d2d41561c01627c228fe7ed2a0d056ef11315042570/pandas-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fed2ff7fd9779120e388e285fc029bd5cf9490cdd2e4166a9ee22c0e49a9ab09", size = 10357846 }, + { url = "https://files.pythonhosted.org/packages/cf/3d/b16412745651e855f357e5e66930248688378853a6e2698a214e331fba1f/pandas-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b168fc218fd80a6cbdbdbc1a97ddc7889ed057d7eb45f50d866ceab5f39904c4", size = 9899550 }, + { url = "https://files.pythonhosted.org/packages/31/a8/fa2535168fffcedf67f4f6de28d2dd903a747ca7c8ea6989451aaeb3a92f/pandas-3.0.3-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0383c72c75cdcca61a9e116e611143902dbfd08bff356829c2f6d1cf40a9ca8c", size = 10412965 }, + { url = "https://files.pythonhosted.org/packages/65/b6/09b01cdbc15224e2850365192d17b7bdebb8bdbd8780ed221fcdf0d9a515/pandas-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6dc0b3fd2169c9157deed50b4d519553a3655c8c6a96027136d654592be973a9", size = 10894600 }, + { url = "https://files.pythonhosted.org/packages/c9/a4/2eb28f2fccb4ced4a2c79ab2a5dee9ade1ebf44922ebad6fea158c9f95d4/pandas-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7e65d5407dc0b394f509699650e4a2ec01c0514f21850f453fa60f3be79a5dbf", size = 11422824 }, + { url = "https://files.pythonhosted.org/packages/f8/45/830bb57f533a4604b355e07edcb8ea18cf88b5f94e5fca92f27052d7c597/pandas-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f8894dc474d648fe7b6ff0ca9b0bd73950d19952bc1a6534540762c5d79d305c", size = 11950889 }, + { url = "https://files.pythonhosted.org/packages/b9/c5/fc1b368f303087d20e8c9bf3d6ceb186263cfac0ade735cd938538bea839/pandas-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:c7be265b62cef88e253a941e4698604973736dcfe242fdb5198f0f7bc473cdcc", size = 9755463 }, + { url = "https://files.pythonhosted.org/packages/86/bd/fda8f9705b1b09c6ebe14bfc0fa0e4ec8584d54ea673628f157ff55131af/pandas-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:557409bc4178e70ee8d9ddb494798e51ebf6ea59330f6be22c51bab2a7db6c49", size = 9066158 }, + { url = "https://files.pythonhosted.org/packages/c5/90/62d8302883c44308c477e222c3daf7c813a34c8e96985882fbd53d964352/pandas-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:67b3b64c11910cfa29f4e94a14d3bff9ee693b6fc76055e7cad549cee0aec5fa", size = 10331071 }, + { url = "https://files.pythonhosted.org/packages/7f/ae/6a6493c783a101f165e4356953ba3c74d6f77f0042fa7d753da9dfbb640c/pandas-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:39436b377d56d2a2e52d0395bdbee171f01068e99af5250509aceeb929f765c7", size = 9875690 }, + { url = "https://files.pythonhosted.org/packages/62/7c/5df8e9f56c69a2769fbe9382a5ef8f2658c007e376434e1e2cbb57ad895f/pandas-3.0.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4be06d68f9ddcfc645b87534911da79a8fbffc7573c80e0edcf42a5020624d8", size = 10381634 }, + { url = "https://files.pythonhosted.org/packages/99/68/1237369725aa617bb358263d535803e3053fdbc593513ec5ed9c9896b5b6/pandas-3.0.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a4eeb6830daf35a71cc09649bd823e2b542dac246cdee9614c6e4bd65028cd6a", size = 10891243 }, + { url = "https://files.pythonhosted.org/packages/25/93/77d108e8af7222b4a503ebde0e30215b1c2e4f8e53a526431890f22d5586/pandas-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1928e07221f82db493cd4af1e23c1bfca524a19a4699887975bff68f49a72bfb", size = 11388659 }, + { url = "https://files.pythonhosted.org/packages/d0/bd/eff5b4399f332ac386c853f6cd2bd3fa2ca0061b9f36ecd9c4d7c4265649/pandas-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51b1fe551acb77dac643c6fda86084d8d446c10fe64b06a9cc29c4cc8540e7f2", size = 11942880 }, + { url = "https://files.pythonhosted.org/packages/2c/20/559ace4200982c3887d0b86bfd0d856a2143ef8ddab63cc07934951a964c/pandas-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:a82d532a3351d435432cd913edbccaf8b8e01d4dd0e5ced5a8d2e8ecd94c7e44", size = 9757091 }, + { url = "https://files.pythonhosted.org/packages/3a/66/69055a09fe200f29f922a3eeec4804611900b95f52d932ece3393c3c0c19/pandas-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:275c14e0fce14a2ec20eee474aecd305478ea3c1e6f6a9d8fe219a165542717e", size = 9057282 }, + { url = "https://files.pythonhosted.org/packages/57/0e/efe801b0e6811e8e650cd21b7f2608e30f08a7067e2bf6e8752b0d56ee3c/pandas-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:46997386d528eb40376ecd6b033cf4a8a1e5282580f68f43de875b78cba2199d", size = 10767016 }, + { url = "https://files.pythonhosted.org/packages/ea/dc/eb55135a1d5f0f0519f28da1f609a206d2cad1f9c35c32d51e38dd7261ae/pandas-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:261e308dfb22448384b7580cf719d2f998fe2966c92893c3e77d14008af1f066", size = 10420210 }, + { url = "https://files.pythonhosted.org/packages/c6/3e/b1d5d955ce33ffecb407465a60bc32769d74fcf68224b7ae67ae11d4dea4/pandas-3.0.3-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:dd1a5d1def6a46002e964510bdc67c368aa0951df5d1d9f8365336f5a1f490cd", size = 10336126 }, + { url = "https://files.pythonhosted.org/packages/f5/76/a01261711ab60a22d71b862f0de20e4c504bf80457270ad8cb42110f6abc/pandas-3.0.3-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d72828c20c6d6e83e1e22a6a3b47b326b71664112fa9705dcbccfd7a39b62085", size = 10728051 }, + { url = "https://files.pythonhosted.org/packages/e9/21/ea191195e587b18cf682e97f433f81b2d0fbe341380e80a3e0d6e4403c8e/pandas-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:d26cbe1fcfc12e8fd900e2454163e466b2d3af84f7c75481df7683ffc073d870", size = 11350796 }, + { url = "https://files.pythonhosted.org/packages/64/69/f0eaaf54939f0e8c6768fd06be9af2cef9b36048b96dfb9e1b2c685a807e/pandas-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:3e91cec1879ada0624fc3dc9953c5cbd60208e59c0db28f540c5d6d47502422f", size = 11799741 }, + { url = "https://files.pythonhosted.org/packages/45/a4/865e0e510cae5fc2194de4db28be638952de942571ba9125934fd9c01d47/pandas-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:08d789b41f87e0905880e293cedf6197ce71fe67cc081358b1e148a491b9bd13", size = 10499958 }, ] [[package]] name = "parsedatetime" version = "2.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a8/20/cb587f6672dbe585d101f590c3871d16e7aec5a576a1694997a3777312ac/parsedatetime-2.6.tar.gz", hash = "sha256:4cb368fbb18a0b7231f4d76119165451c8d2e35951455dfee97c62a87b04d455", size = 60114, upload-time = "2020-05-31T23:50:57.443Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a8/20/cb587f6672dbe585d101f590c3871d16e7aec5a576a1694997a3777312ac/parsedatetime-2.6.tar.gz", hash = "sha256:4cb368fbb18a0b7231f4d76119165451c8d2e35951455dfee97c62a87b04d455", size = 60114 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/a4/3dd804926a42537bf69fb3ebb9fd72a50ba84f807d95df5ae016606c976c/parsedatetime-2.6-py3-none-any.whl", hash = "sha256:cb96edd7016872f58479e35879294258c71437195760746faffedb692aef000b", size = 42548, upload-time = "2020-05-31T23:50:56.315Z" }, + { url = "https://files.pythonhosted.org/packages/9d/a4/3dd804926a42537bf69fb3ebb9fd72a50ba84f807d95df5ae016606c976c/parsedatetime-2.6-py3-none-any.whl", hash = "sha256:cb96edd7016872f58479e35879294258c71437195760746faffedb692aef000b", size = 42548 }, ] [[package]] name = "pathspec" version = "0.12.1" 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/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 } 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/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 }, ] [[package]] name = "pathvalidate" version = "3.3.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fa/2a/52a8da6fe965dea6192eb716b357558e103aea0a1e9a8352ad575a8406ca/pathvalidate-3.3.1.tar.gz", hash = "sha256:b18c07212bfead624345bb8e1d6141cdcf15a39736994ea0b94035ad2b1ba177", size = 63262, upload-time = "2025-06-15T09:07:20.736Z" } +sdist = { url = "https://files.pythonhosted.org/packages/fa/2a/52a8da6fe965dea6192eb716b357558e103aea0a1e9a8352ad575a8406ca/pathvalidate-3.3.1.tar.gz", hash = "sha256:b18c07212bfead624345bb8e1d6141cdcf15a39736994ea0b94035ad2b1ba177", size = 63262 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/70/875f4a23bfc4731703a5835487d0d2fb999031bd415e7d17c0ae615c18b7/pathvalidate-3.3.1-py3-none-any.whl", hash = "sha256:5263baab691f8e1af96092fa5137ee17df5bdfbd6cff1fcac4d6ef4bc2e1735f", size = 24305, upload-time = "2025-06-15T09:07:19.117Z" }, + { url = "https://files.pythonhosted.org/packages/9a/70/875f4a23bfc4731703a5835487d0d2fb999031bd415e7d17c0ae615c18b7/pathvalidate-3.3.1-py3-none-any.whl", hash = "sha256:5263baab691f8e1af96092fa5137ee17df5bdfbd6cff1fcac4d6ef4bc2e1735f", size = 24305 }, ] [[package]] @@ -1061,71 +1076,71 @@ dependencies = [ { name = "python-dateutil" }, { name = "tzdata" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/cb/72/9a51afa0a822b09e286c4cb827ed7b00bc818dac7bd11a5f161e493a217d/pendulum-3.2.0.tar.gz", hash = "sha256:e80feda2d10fa3ff8b1526715f7d33dcb7e08494b3088f2c8a3ac92d4a4331ce", size = 86912, upload-time = "2026-01-30T11:22:24.093Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/41/56/dd0ea9f97d25a0763cda09e2217563b45714786118d8c68b0b745395d6eb/pendulum-3.2.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:bf0b489def51202a39a2a665dcc4162d5e46934a740fe4c4fe3068979610156c", size = 337830, upload-time = "2026-01-30T11:21:08.298Z" }, - { url = "https://files.pythonhosted.org/packages/cf/98/83d62899bf7226fc12396de4bc1fb2b5da27e451c7c60790043aaf8b4731/pendulum-3.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:937a529aa302efa18dcf25e53834964a87ffb2df8f80e3669ab7757a6126beaf", size = 327574, upload-time = "2026-01-30T11:21:09.715Z" }, - { url = "https://files.pythonhosted.org/packages/76/fa/ff2aa992b23f0543c709b1a3f3f9ed760ec71fd02c8bb01f93bf008b52e4/pendulum-3.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85c7689defc65c4dc29bf257f7cca55d210fabb455de9476e1748d2ab2ae80d7", size = 339891, upload-time = "2026-01-30T11:21:11.089Z" }, - { url = "https://files.pythonhosted.org/packages/c5/4e/25b4fa11d19503d50d7b52d7ef943c0f20fd54422aaeb9e38f588c815c50/pendulum-3.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d5e216e5a412563ea2ecf5de467dcf3d02717947fcdabe6811d5ee360726b02b", size = 373726, upload-time = "2026-01-30T11:21:12.493Z" }, - { url = "https://files.pythonhosted.org/packages/4f/30/0acad6396c4e74e5c689aa4f0b0c49e2ecdcfce368e7b5bf35ca1c0fc61a/pendulum-3.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a2af22eeec438fbaac72bb7fba783e0950a514fba980d9a32db394b51afccec", size = 379827, upload-time = "2026-01-30T11:21:14.08Z" }, - { url = "https://files.pythonhosted.org/packages/3a/f7/e6a2fdf2a23d59b4b48b8fa89e8d4bf2dd371aea2c6ba8fcecec20a4acb9/pendulum-3.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3159cceb54f5aa8b85b141c7f0ce3fac8bdd1ffdc7c79e67dca9133eac7c4d11", size = 348921, upload-time = "2026-01-30T11:21:15.816Z" }, - { url = "https://files.pythonhosted.org/packages/7f/f2/c15fa7f9ad4e181aa469b6040b574988bd108ccdf4ae509ad224f9e4db44/pendulum-3.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c39ea5e9ffa20ea8bae986d00e0908bd537c8468b71d6b6503ab0b4c3d76e0ea", size = 517188, upload-time = "2026-01-30T11:21:17.835Z" }, - { url = "https://files.pythonhosted.org/packages/47/c7/5f80b12ee88ec26e930c3a5a602608a63c29cf60c81a0eb066d583772550/pendulum-3.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e5afc753e570cce1f44197676371f68953f7d4f022303d141bb09f804d5fe6d7", size = 561833, upload-time = "2026-01-30T11:21:19.232Z" }, - { url = "https://files.pythonhosted.org/packages/90/15/1ac481626cb63db751f6281e294661947c1f0321ebe5d1c532a3b51a8006/pendulum-3.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:fd55c12560816d9122ca2142d9e428f32c0c083bf77719320b1767539c7a3a3b", size = 258725, upload-time = "2026-01-30T11:21:20.558Z" }, - { url = "https://files.pythonhosted.org/packages/40/ae/50b0398d7d027eb70a3e1e336de7b6e599c6b74431cb7d3863287e1292bb/pendulum-3.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:faef52a7ed99729f0838353b956f3fabf6c550c062db247e9e2fc2b48fcb9457", size = 253089, upload-time = "2026-01-30T11:21:22.497Z" }, - { url = "https://files.pythonhosted.org/packages/27/8c/400c8b8dbd7524424f3d9902ded64741e82e5e321d1aabbd68ade89e71cf/pendulum-3.2.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:addb0512f919fe5b70c8ee534ee71c775630d3efe567ea5763d92acff857cfc3", size = 337820, upload-time = "2026-01-30T11:21:24.305Z" }, - { url = "https://files.pythonhosted.org/packages/59/38/7c16f26cc55d9206d71da294ce6857d0da381e26bc9e0c2a069424c2b173/pendulum-3.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3aaa50342dc174acebdc21089315012e63789353957b39ac83cac9f9fc8d1075", size = 327551, upload-time = "2026-01-30T11:21:25.747Z" }, - { url = "https://files.pythonhosted.org/packages/0b/cd/f36ec5d56d55104232380fdbf84ff53cc05607574af3cbdc8a43991ac8a7/pendulum-3.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:927e9c9ab52ff68e71b76dd410e5f1cd78f5ea6e7f0a9f5eb549aea16a4d5354", size = 339894, upload-time = "2026-01-30T11:21:27.229Z" }, - { url = "https://files.pythonhosted.org/packages/aa/4e/b9a1e546519c3a92d5bc17787cea925e06a20def2ae344fa136d2fc40338/pendulum-3.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:249d18f5543c9f43aba3bd77b34864ec8cf6f64edbead405f442e23c94fce63d", size = 373766, upload-time = "2026-01-30T11:21:28.642Z" }, - { url = "https://files.pythonhosted.org/packages/ea/a6/6471ab87ae2260594501f071586a765fc894817043b7d2d4b04e2eff4f31/pendulum-3.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c644cc15eec5fb02291f0f193195156780fd5a0affd7a349592403826d1a35e", size = 379837, upload-time = "2026-01-30T11:21:30.637Z" }, - { url = "https://files.pythonhosted.org/packages/0d/79/0ba0c14e862388f7b822626e6e989163c23bebe7f96de5ec4b207cbe7c3d/pendulum-3.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:063ab61af953bb56ad5bc8e131fd0431c915ed766d90ccecd7549c8090b51004", size = 348904, upload-time = "2026-01-30T11:21:32.436Z" }, - { url = "https://files.pythonhosted.org/packages/17/34/df922c7c0b12719589d4954bfa5bdca9e02bcde220f5c5c1838a87118960/pendulum-3.2.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:26a3ae26c9dd70a4256f1c2f51addc43641813574c0db6ce5664f9861cd93621", size = 517173, upload-time = "2026-01-30T11:21:34.428Z" }, - { url = "https://files.pythonhosted.org/packages/87/ec/3b9e061eeee97b72a47c1434ee03f6d85f0284d9285d92b12b0fff2d19ac/pendulum-3.2.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:2b10d91dc00f424444a42f47c69e6b3bfd79376f330179dc06bc342184b35f9a", size = 561744, upload-time = "2026-01-30T11:21:35.861Z" }, - { url = "https://files.pythonhosted.org/packages/fd/7e/f12fdb6070b7975c1fcfa5685dbe4ab73c788878a71f4d1d7e3c87979e37/pendulum-3.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:63070ff03e30a57b16c8e793ee27da8dac4123c1d6e0cf74c460ce9ee8a64aa4", size = 258746, upload-time = "2026-01-30T11:21:37.782Z" }, - { url = "https://files.pythonhosted.org/packages/c9/b8/5abd872056357f069ae34a9b24a75ac58e79092d16201d779a8dd31386bb/pendulum-3.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:c8dde63e2796b62070a49ce813ce200aba9186130307f04ec78affcf6c2e8122", size = 253028, upload-time = "2026-01-30T11:21:39.381Z" }, - { url = "https://files.pythonhosted.org/packages/02/fb/d65db067a67df7252f18b0cb7420dda84078b9e8bfb375215469c14a50be/pendulum-3.2.0-py3-none-any.whl", hash = "sha256:f3a9c18a89b4d9ef39c5fa6a78722aaff8d5be2597c129a3b16b9f40a561acf3", size = 114111, upload-time = "2026-01-30T11:22:22.361Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/cb/72/9a51afa0a822b09e286c4cb827ed7b00bc818dac7bd11a5f161e493a217d/pendulum-3.2.0.tar.gz", hash = "sha256:e80feda2d10fa3ff8b1526715f7d33dcb7e08494b3088f2c8a3ac92d4a4331ce", size = 86912 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/56/dd0ea9f97d25a0763cda09e2217563b45714786118d8c68b0b745395d6eb/pendulum-3.2.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:bf0b489def51202a39a2a665dcc4162d5e46934a740fe4c4fe3068979610156c", size = 337830 }, + { url = "https://files.pythonhosted.org/packages/cf/98/83d62899bf7226fc12396de4bc1fb2b5da27e451c7c60790043aaf8b4731/pendulum-3.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:937a529aa302efa18dcf25e53834964a87ffb2df8f80e3669ab7757a6126beaf", size = 327574 }, + { url = "https://files.pythonhosted.org/packages/76/fa/ff2aa992b23f0543c709b1a3f3f9ed760ec71fd02c8bb01f93bf008b52e4/pendulum-3.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85c7689defc65c4dc29bf257f7cca55d210fabb455de9476e1748d2ab2ae80d7", size = 339891 }, + { url = "https://files.pythonhosted.org/packages/c5/4e/25b4fa11d19503d50d7b52d7ef943c0f20fd54422aaeb9e38f588c815c50/pendulum-3.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d5e216e5a412563ea2ecf5de467dcf3d02717947fcdabe6811d5ee360726b02b", size = 373726 }, + { url = "https://files.pythonhosted.org/packages/4f/30/0acad6396c4e74e5c689aa4f0b0c49e2ecdcfce368e7b5bf35ca1c0fc61a/pendulum-3.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a2af22eeec438fbaac72bb7fba783e0950a514fba980d9a32db394b51afccec", size = 379827 }, + { url = "https://files.pythonhosted.org/packages/3a/f7/e6a2fdf2a23d59b4b48b8fa89e8d4bf2dd371aea2c6ba8fcecec20a4acb9/pendulum-3.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3159cceb54f5aa8b85b141c7f0ce3fac8bdd1ffdc7c79e67dca9133eac7c4d11", size = 348921 }, + { url = "https://files.pythonhosted.org/packages/7f/f2/c15fa7f9ad4e181aa469b6040b574988bd108ccdf4ae509ad224f9e4db44/pendulum-3.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c39ea5e9ffa20ea8bae986d00e0908bd537c8468b71d6b6503ab0b4c3d76e0ea", size = 517188 }, + { url = "https://files.pythonhosted.org/packages/47/c7/5f80b12ee88ec26e930c3a5a602608a63c29cf60c81a0eb066d583772550/pendulum-3.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e5afc753e570cce1f44197676371f68953f7d4f022303d141bb09f804d5fe6d7", size = 561833 }, + { url = "https://files.pythonhosted.org/packages/90/15/1ac481626cb63db751f6281e294661947c1f0321ebe5d1c532a3b51a8006/pendulum-3.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:fd55c12560816d9122ca2142d9e428f32c0c083bf77719320b1767539c7a3a3b", size = 258725 }, + { url = "https://files.pythonhosted.org/packages/40/ae/50b0398d7d027eb70a3e1e336de7b6e599c6b74431cb7d3863287e1292bb/pendulum-3.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:faef52a7ed99729f0838353b956f3fabf6c550c062db247e9e2fc2b48fcb9457", size = 253089 }, + { url = "https://files.pythonhosted.org/packages/27/8c/400c8b8dbd7524424f3d9902ded64741e82e5e321d1aabbd68ade89e71cf/pendulum-3.2.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:addb0512f919fe5b70c8ee534ee71c775630d3efe567ea5763d92acff857cfc3", size = 337820 }, + { url = "https://files.pythonhosted.org/packages/59/38/7c16f26cc55d9206d71da294ce6857d0da381e26bc9e0c2a069424c2b173/pendulum-3.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3aaa50342dc174acebdc21089315012e63789353957b39ac83cac9f9fc8d1075", size = 327551 }, + { url = "https://files.pythonhosted.org/packages/0b/cd/f36ec5d56d55104232380fdbf84ff53cc05607574af3cbdc8a43991ac8a7/pendulum-3.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:927e9c9ab52ff68e71b76dd410e5f1cd78f5ea6e7f0a9f5eb549aea16a4d5354", size = 339894 }, + { url = "https://files.pythonhosted.org/packages/aa/4e/b9a1e546519c3a92d5bc17787cea925e06a20def2ae344fa136d2fc40338/pendulum-3.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:249d18f5543c9f43aba3bd77b34864ec8cf6f64edbead405f442e23c94fce63d", size = 373766 }, + { url = "https://files.pythonhosted.org/packages/ea/a6/6471ab87ae2260594501f071586a765fc894817043b7d2d4b04e2eff4f31/pendulum-3.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c644cc15eec5fb02291f0f193195156780fd5a0affd7a349592403826d1a35e", size = 379837 }, + { url = "https://files.pythonhosted.org/packages/0d/79/0ba0c14e862388f7b822626e6e989163c23bebe7f96de5ec4b207cbe7c3d/pendulum-3.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:063ab61af953bb56ad5bc8e131fd0431c915ed766d90ccecd7549c8090b51004", size = 348904 }, + { url = "https://files.pythonhosted.org/packages/17/34/df922c7c0b12719589d4954bfa5bdca9e02bcde220f5c5c1838a87118960/pendulum-3.2.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:26a3ae26c9dd70a4256f1c2f51addc43641813574c0db6ce5664f9861cd93621", size = 517173 }, + { url = "https://files.pythonhosted.org/packages/87/ec/3b9e061eeee97b72a47c1434ee03f6d85f0284d9285d92b12b0fff2d19ac/pendulum-3.2.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:2b10d91dc00f424444a42f47c69e6b3bfd79376f330179dc06bc342184b35f9a", size = 561744 }, + { url = "https://files.pythonhosted.org/packages/fd/7e/f12fdb6070b7975c1fcfa5685dbe4ab73c788878a71f4d1d7e3c87979e37/pendulum-3.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:63070ff03e30a57b16c8e793ee27da8dac4123c1d6e0cf74c460ce9ee8a64aa4", size = 258746 }, + { url = "https://files.pythonhosted.org/packages/c9/b8/5abd872056357f069ae34a9b24a75ac58e79092d16201d779a8dd31386bb/pendulum-3.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:c8dde63e2796b62070a49ce813ce200aba9186130307f04ec78affcf6c2e8122", size = 253028 }, + { url = "https://files.pythonhosted.org/packages/02/fb/d65db067a67df7252f18b0cb7420dda84078b9e8bfb375215469c14a50be/pendulum-3.2.0-py3-none-any.whl", hash = "sha256:f3a9c18a89b4d9ef39c5fa6a78722aaff8d5be2597c129a3b16b9f40a561acf3", size = 114111 }, ] [[package]] name = "platformdirs" version = "4.9.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/19/56/8d4c30c8a1d07013911a8fdbd8f89440ef9f08d07a1b50ab8ca8be5a20f9/platformdirs-4.9.4.tar.gz", hash = "sha256:1ec356301b7dc906d83f371c8f487070e99d3ccf9e501686456394622a01a934", size = 28737, upload-time = "2026-03-05T18:34:13.271Z" } +sdist = { url = "https://files.pythonhosted.org/packages/19/56/8d4c30c8a1d07013911a8fdbd8f89440ef9f08d07a1b50ab8ca8be5a20f9/platformdirs-4.9.4.tar.gz", hash = "sha256:1ec356301b7dc906d83f371c8f487070e99d3ccf9e501686456394622a01a934", size = 28737 } wheels = [ - { url = "https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl", hash = "sha256:68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868", size = 21216, upload-time = "2026-03-05T18:34:12.172Z" }, + { url = "https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl", hash = "sha256:68a9a4619a666ea6439f2ff250c12a853cd1cbd5158d258bd824a7df6be2f868", size = 21216 }, ] [[package]] name = "pluggy" version = "1.6.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412 } wheels = [ - { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538 }, ] [[package]] name = "ply" version = "3.11" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e5/69/882ee5c9d017149285cab114ebeab373308ef0f874fcdac9beb90e0ac4da/ply-3.11.tar.gz", hash = "sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3", size = 159130, upload-time = "2018-02-15T19:01:31.097Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/69/882ee5c9d017149285cab114ebeab373308ef0f874fcdac9beb90e0ac4da/ply-3.11.tar.gz", hash = "sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3", size = 159130 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl", hash = "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce", size = 49567, upload-time = "2018-02-15T19:01:27.172Z" }, + { url = "https://files.pythonhosted.org/packages/a3/58/35da89ee790598a0700ea49b2a66594140f44dec458c07e8e3d4979137fc/ply-3.11-py2.py3-none-any.whl", hash = "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce", size = 49567 }, ] [[package]] name = "protobuf" version = "6.33.6" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/66/70/e908e9c5e52ef7c3a6c7902c9dfbb34c7e29c25d2f81ade3856445fd5c94/protobuf-6.33.6.tar.gz", hash = "sha256:a6768d25248312c297558af96a9f9c929e8c4cee0659cb07e780731095f38135", size = 444531, upload-time = "2026-03-18T19:05:00.988Z" } +sdist = { url = "https://files.pythonhosted.org/packages/66/70/e908e9c5e52ef7c3a6c7902c9dfbb34c7e29c25d2f81ade3856445fd5c94/protobuf-6.33.6.tar.gz", hash = "sha256:a6768d25248312c297558af96a9f9c929e8c4cee0659cb07e780731095f38135", size = 444531 } wheels = [ - { url = "https://files.pythonhosted.org/packages/fc/9f/2f509339e89cfa6f6a4c4ff50438db9ca488dec341f7e454adad60150b00/protobuf-6.33.6-cp310-abi3-win32.whl", hash = "sha256:7d29d9b65f8afef196f8334e80d6bc1d5d4adedb449971fefd3723824e6e77d3", size = 425739, upload-time = "2026-03-18T19:04:48.373Z" }, - { url = "https://files.pythonhosted.org/packages/76/5d/683efcd4798e0030c1bab27374fd13a89f7c2515fb1f3123efdfaa5eab57/protobuf-6.33.6-cp310-abi3-win_amd64.whl", hash = "sha256:0cd27b587afca21b7cfa59a74dcbd48a50f0a6400cfb59391340ad729d91d326", size = 437089, upload-time = "2026-03-18T19:04:50.381Z" }, - { url = "https://files.pythonhosted.org/packages/5c/01/a3c3ed5cd186f39e7880f8303cc51385a198a81469d53d0fdecf1f64d929/protobuf-6.33.6-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:9720e6961b251bde64edfdab7d500725a2af5280f3f4c87e57c0208376aa8c3a", size = 427737, upload-time = "2026-03-18T19:04:51.866Z" }, - { url = "https://files.pythonhosted.org/packages/ee/90/b3c01fdec7d2f627b3a6884243ba328c1217ed2d978def5c12dc50d328a3/protobuf-6.33.6-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:e2afbae9b8e1825e3529f88d514754e094278bb95eadc0e199751cdd9a2e82a2", size = 324610, upload-time = "2026-03-18T19:04:53.096Z" }, - { url = "https://files.pythonhosted.org/packages/9b/ca/25afc144934014700c52e05103c2421997482d561f3101ff352e1292fb81/protobuf-6.33.6-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:c96c37eec15086b79762ed265d59ab204dabc53056e3443e702d2681f4b39ce3", size = 339381, upload-time = "2026-03-18T19:04:54.616Z" }, - { url = "https://files.pythonhosted.org/packages/16/92/d1e32e3e0d894fe00b15ce28ad4944ab692713f2e7f0a99787405e43533a/protobuf-6.33.6-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:e9db7e292e0ab79dd108d7f1a94fe31601ce1ee3f7b79e0692043423020b0593", size = 323436, upload-time = "2026-03-18T19:04:55.768Z" }, - { url = "https://files.pythonhosted.org/packages/c4/72/02445137af02769918a93807b2b7890047c32bfb9f90371cbc12688819eb/protobuf-6.33.6-py3-none-any.whl", hash = "sha256:77179e006c476e69bf8e8ce866640091ec42e1beb80b213c3900006ecfba6901", size = 170656, upload-time = "2026-03-18T19:04:59.826Z" }, + { url = "https://files.pythonhosted.org/packages/fc/9f/2f509339e89cfa6f6a4c4ff50438db9ca488dec341f7e454adad60150b00/protobuf-6.33.6-cp310-abi3-win32.whl", hash = "sha256:7d29d9b65f8afef196f8334e80d6bc1d5d4adedb449971fefd3723824e6e77d3", size = 425739 }, + { url = "https://files.pythonhosted.org/packages/76/5d/683efcd4798e0030c1bab27374fd13a89f7c2515fb1f3123efdfaa5eab57/protobuf-6.33.6-cp310-abi3-win_amd64.whl", hash = "sha256:0cd27b587afca21b7cfa59a74dcbd48a50f0a6400cfb59391340ad729d91d326", size = 437089 }, + { url = "https://files.pythonhosted.org/packages/5c/01/a3c3ed5cd186f39e7880f8303cc51385a198a81469d53d0fdecf1f64d929/protobuf-6.33.6-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:9720e6961b251bde64edfdab7d500725a2af5280f3f4c87e57c0208376aa8c3a", size = 427737 }, + { url = "https://files.pythonhosted.org/packages/ee/90/b3c01fdec7d2f627b3a6884243ba328c1217ed2d978def5c12dc50d328a3/protobuf-6.33.6-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:e2afbae9b8e1825e3529f88d514754e094278bb95eadc0e199751cdd9a2e82a2", size = 324610 }, + { url = "https://files.pythonhosted.org/packages/9b/ca/25afc144934014700c52e05103c2421997482d561f3101ff352e1292fb81/protobuf-6.33.6-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:c96c37eec15086b79762ed265d59ab204dabc53056e3443e702d2681f4b39ce3", size = 339381 }, + { url = "https://files.pythonhosted.org/packages/16/92/d1e32e3e0d894fe00b15ce28ad4944ab692713f2e7f0a99787405e43533a/protobuf-6.33.6-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:e9db7e292e0ab79dd108d7f1a94fe31601ce1ee3f7b79e0692043423020b0593", size = 323436 }, + { url = "https://files.pythonhosted.org/packages/c4/72/02445137af02769918a93807b2b7890047c32bfb9f90371cbc12688819eb/protobuf-6.33.6-py3-none-any.whl", hash = "sha256:77179e006c476e69bf8e8ce866640091ec42e1beb80b213c3900006ecfba6901", size = 170656 }, ] [[package]] @@ -1138,9 +1153,9 @@ dependencies = [ { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/d9/e4/40d09941a2cebcb20609b86a559817d5b9291c49dd6f8c87e5feffbe703a/pydantic-2.13.3.tar.gz", hash = "sha256:af09e9d1d09f4e7fe37145c1f577e1d61ceb9a41924bf0094a36506285d0a84d", size = 844068, upload-time = "2026-04-20T14:46:43.632Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d9/e4/40d09941a2cebcb20609b86a559817d5b9291c49dd6f8c87e5feffbe703a/pydantic-2.13.3.tar.gz", hash = "sha256:af09e9d1d09f4e7fe37145c1f577e1d61ceb9a41924bf0094a36506285d0a84d", size = 844068 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f3/0a/fd7d723f8f8153418fb40cf9c940e82004fce7e987026b08a68a36dd3fe7/pydantic-2.13.3-py3-none-any.whl", hash = "sha256:6db14ac8dfc9a1e57f87ea2c0de670c251240f43cb0c30a5130e9720dc612927", size = 471981, upload-time = "2026-04-20T14:46:41.402Z" }, + { url = "https://files.pythonhosted.org/packages/f3/0a/fd7d723f8f8153418fb40cf9c940e82004fce7e987026b08a68a36dd3fe7/pydantic-2.13.3-py3-none-any.whl", hash = "sha256:6db14ac8dfc9a1e57f87ea2c0de670c251240f43cb0c30a5130e9720dc612927", size = 471981 }, ] [[package]] @@ -1150,51 +1165,47 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/2a/ef/f7abb56c49382a246fd2ce9c799691e3c3e7175ec74b14d99e798bcddb1a/pydantic_core-2.46.3.tar.gz", hash = "sha256:41c178f65b8c29807239d47e6050262eb6bf84eb695e41101e62e38df4a5bc2c", size = 471412, upload-time = "2026-04-20T14:40:56.672Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/4b/cb/5b47425556ecc1f3fe18ed2a0083188aa46e1dd812b06e406475b3a5d536/pydantic_core-2.46.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b11b59b3eee90a80a36701ddb4576d9ae31f93f05cb9e277ceaa09e6bf074a67", size = 2101946, upload-time = "2026-04-20T14:40:52.581Z" }, - { url = "https://files.pythonhosted.org/packages/a1/4f/2fb62c2267cae99b815bbf4a7b9283812c88ca3153ef29f7707200f1d4e5/pydantic_core-2.46.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:af8653713055ea18a3abc1537fe2ebc42f5b0bbb768d1eb79fd74eb47c0ac089", size = 1951612, upload-time = "2026-04-20T14:42:42.996Z" }, - { url = "https://files.pythonhosted.org/packages/50/6e/b7348fd30d6556d132cddd5bd79f37f96f2601fe0608afac4f5fb01ec0b3/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75a519dab6d63c514f3a81053e5266c549679e4aa88f6ec57f2b7b854aceb1b0", size = 1977027, upload-time = "2026-04-20T14:42:02.001Z" }, - { url = "https://files.pythonhosted.org/packages/82/11/31d60ee2b45540d3fb0b29302a393dbc01cd771c473f5b5147bcd353e593/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a6cd87cb1575b1ad05ba98894c5b5c96411ef678fa2f6ed2576607095b8d9789", size = 2063008, upload-time = "2026-04-20T14:44:17.952Z" }, - { url = "https://files.pythonhosted.org/packages/8a/db/3a9d1957181b59258f44a2300ab0f0be9d1e12d662a4f57bb31250455c52/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f80a55484b8d843c8ada81ebf70a682f3f00a3d40e378c06cf17ecb44d280d7d", size = 2233082, upload-time = "2026-04-20T14:40:57.934Z" }, - { url = "https://files.pythonhosted.org/packages/9c/e1/3277c38792aeb5cfb18c2f0c5785a221d9ff4e149abbe1184d53d5f72273/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3861f1731b90c50a3266316b9044f5c9b405eecb8e299b0a7120596334e4fe9c", size = 2304615, upload-time = "2026-04-20T14:42:12.584Z" }, - { url = "https://files.pythonhosted.org/packages/5e/d5/e3d9717c9eba10855325650afd2a9cba8e607321697f18953af9d562da2f/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb528e295ed31570ac3dcc9bfdd6e0150bc11ce6168ac87a8082055cf1a67395", size = 2094380, upload-time = "2026-04-20T14:43:05.522Z" }, - { url = "https://files.pythonhosted.org/packages/a1/20/abac35dedcbfd66c6f0b03e4e3564511771d6c9b7ede10a362d03e110d9b/pydantic_core-2.46.3-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:367508faa4973b992b271ba1494acaab36eb7e8739d1e47be5035fb1ea225396", size = 2135429, upload-time = "2026-04-20T14:41:55.549Z" }, - { url = "https://files.pythonhosted.org/packages/6c/a5/41bfd1df69afad71b5cf0535055bccc73022715ad362edbc124bc1e021d7/pydantic_core-2.46.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ad3c826fe523e4becf4fe39baa44286cff85ef137c729a2c5e269afbfd0905d", size = 2174582, upload-time = "2026-04-20T14:41:45.96Z" }, - { url = "https://files.pythonhosted.org/packages/79/65/38d86ea056b29b2b10734eb23329b7a7672ca604df4f2b6e9c02d4ee22fe/pydantic_core-2.46.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ec638c5d194ef8af27db69f16c954a09797c0dc25015ad6123eb2c73a4d271ca", size = 2187533, upload-time = "2026-04-20T14:40:55.367Z" }, - { url = "https://files.pythonhosted.org/packages/b6/55/a1129141678a2026badc539ad1dee0a71d06f54c2f06a4bd68c030ac781b/pydantic_core-2.46.3-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:28ed528c45446062ee66edb1d33df5d88828ae167de76e773a3c7f64bd14e976", size = 2332985, upload-time = "2026-04-20T14:44:13.05Z" }, - { url = "https://files.pythonhosted.org/packages/d7/60/cb26f4077719f709e54819f4e8e1d43f4091f94e285eb6bd21e1190a7b7c/pydantic_core-2.46.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aed19d0c783886d5bd86d80ae5030006b45e28464218747dcf83dabfdd092c7b", size = 2373670, upload-time = "2026-04-20T14:41:53.421Z" }, - { url = "https://files.pythonhosted.org/packages/6b/7e/c3f21882bdf1d8d086876f81b5e296206c69c6082551d776895de7801fa0/pydantic_core-2.46.3-cp312-cp312-win32.whl", hash = "sha256:06d5d8820cbbdb4147578c1fe7ffcd5b83f34508cb9f9ab76e807be7db6ff0a4", size = 1966722, upload-time = "2026-04-20T14:44:30.588Z" }, - { url = "https://files.pythonhosted.org/packages/57/be/6b5e757b859013ebfbd7adba02f23b428f37c86dcbf78b5bb0b4ffd36e99/pydantic_core-2.46.3-cp312-cp312-win_amd64.whl", hash = "sha256:c3212fda0ee959c1dd04c60b601ec31097aaa893573a3a1abd0a47bcac2968c1", size = 2072970, upload-time = "2026-04-20T14:42:54.248Z" }, - { url = "https://files.pythonhosted.org/packages/bf/f8/a989b21cc75e9a32d24192ef700eea606521221a89faa40c919ce884f2b1/pydantic_core-2.46.3-cp312-cp312-win_arm64.whl", hash = "sha256:f1f8338dd7a7f31761f1f1a3c47503a9a3b34eea3c8b01fa6ee96408affb5e72", size = 2035963, upload-time = "2026-04-20T14:44:20.4Z" }, - { url = "https://files.pythonhosted.org/packages/9b/3c/9b5e8eb9821936d065439c3b0fb1490ffa64163bfe7e1595985a47896073/pydantic_core-2.46.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:12bc98de041458b80c86c56b24df1d23832f3e166cbaff011f25d187f5c62c37", size = 2102109, upload-time = "2026-04-20T14:41:24.219Z" }, - { url = "https://files.pythonhosted.org/packages/91/97/1c41d1f5a19f241d8069f1e249853bcce378cdb76eec8ab636d7bc426280/pydantic_core-2.46.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:85348b8f89d2c3508b65b16c3c33a4da22b8215138d8b996912bb1532868885f", size = 1951820, upload-time = "2026-04-20T14:42:14.236Z" }, - { url = "https://files.pythonhosted.org/packages/30/b4/d03a7ae14571bc2b6b3c7b122441154720619afe9a336fa3a95434df5e2f/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1105677a6df914b1fb71a81b96c8cce7726857e1717d86001f29be06a25ee6f8", size = 1977785, upload-time = "2026-04-20T14:42:31.648Z" }, - { url = "https://files.pythonhosted.org/packages/ae/0c/4086f808834b59e3c8f1aa26df8f4b6d998cdcf354a143d18ef41529d1fe/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87082cd65669a33adeba5470769e9704c7cf026cc30afb9cc77fd865578ebaad", size = 2062761, upload-time = "2026-04-20T14:40:37.093Z" }, - { url = "https://files.pythonhosted.org/packages/fa/71/a649be5a5064c2df0db06e0a512c2281134ed2fcc981f52a657936a7527c/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60e5f66e12c4f5212d08522963380eaaeac5ebd795826cfd19b2dfb0c7a52b9c", size = 2232989, upload-time = "2026-04-20T14:42:59.254Z" }, - { url = "https://files.pythonhosted.org/packages/a2/84/7756e75763e810b3a710f4724441d1ecc5883b94aacb07ca71c5fb5cfb69/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b6cdf19bf84128d5e7c37e8a73a0c5c10d51103a650ac585d42dd6ae233f2b7f", size = 2303975, upload-time = "2026-04-20T14:41:32.287Z" }, - { url = "https://files.pythonhosted.org/packages/6c/35/68a762e0c1e31f35fa0dac733cbd9f5b118042853698de9509c8e5bf128b/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:031bb17f4885a43773c8c763089499f242aee2ea85cf17154168775dccdecf35", size = 2095325, upload-time = "2026-04-20T14:42:47.685Z" }, - { url = "https://files.pythonhosted.org/packages/77/bf/1bf8c9a8e91836c926eae5e3e51dce009bf495a60ca56060689d3df3f340/pydantic_core-2.46.3-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:bcf2a8b2982a6673693eae7348ef3d8cf3979c1d63b54fca7c397a635cc68687", size = 2133368, upload-time = "2026-04-20T14:41:22.766Z" }, - { url = "https://files.pythonhosted.org/packages/e5/50/87d818d6bab915984995157ceb2380f5aac4e563dddbed6b56f0ed057aba/pydantic_core-2.46.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28e8cf2f52d72ced402a137145923a762cbb5081e48b34312f7a0c8f55928ec3", size = 2173908, upload-time = "2026-04-20T14:42:52.044Z" }, - { url = "https://files.pythonhosted.org/packages/91/88/a311fb306d0bd6185db41fa14ae888fb81d0baf648a761ae760d30819d33/pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:17eaface65d9fc5abb940003020309c1bf7a211f5f608d7870297c367e6f9022", size = 2186422, upload-time = "2026-04-20T14:43:29.55Z" }, - { url = "https://files.pythonhosted.org/packages/8f/79/28fd0d81508525ab2054fef7c77a638c8b5b0afcbbaeee493cf7c3fef7e1/pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:93fd339f23408a07e98950a89644f92c54d8729719a40b30c0a30bb9ebc55d23", size = 2332709, upload-time = "2026-04-20T14:42:16.134Z" }, - { url = "https://files.pythonhosted.org/packages/b3/21/795bf5fe5c0f379308b8ef19c50dedab2e7711dbc8d0c2acf08f1c7daa05/pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:23cbdb3aaa74dfe0837975dbf69b469753bbde8eacace524519ffdb6b6e89eb7", size = 2372428, upload-time = "2026-04-20T14:41:10.974Z" }, - { url = "https://files.pythonhosted.org/packages/45/b3/ed14c659cbe7605e3ef063077680a64680aec81eb1a04763a05190d49b7f/pydantic_core-2.46.3-cp313-cp313-win32.whl", hash = "sha256:610eda2e3838f401105e6326ca304f5da1e15393ae25dacae5c5c63f2c275b13", size = 1965601, upload-time = "2026-04-20T14:41:42.128Z" }, - { url = "https://files.pythonhosted.org/packages/ef/bb/adb70d9a762ddd002d723fbf1bd492244d37da41e3af7b74ad212609027e/pydantic_core-2.46.3-cp313-cp313-win_amd64.whl", hash = "sha256:68cc7866ed863db34351294187f9b729964c371ba33e31c26f478471c52e1ed0", size = 2071517, upload-time = "2026-04-20T14:43:36.096Z" }, - { url = "https://files.pythonhosted.org/packages/52/eb/66faefabebfe68bd7788339c9c9127231e680b11906368c67ce112fdb47f/pydantic_core-2.46.3-cp313-cp313-win_arm64.whl", hash = "sha256:f64b5537ac62b231572879cd08ec05600308636a5d63bcbdb15063a466977bec", size = 2035802, upload-time = "2026-04-20T14:43:38.507Z" }, - { url = "https://files.pythonhosted.org/packages/34/42/f426db557e8ab2791bc7562052299944a118655496fbff99914e564c0a94/pydantic_core-2.46.3-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:b12dd51f1187c2eb489af8e20f880362db98e954b54ab792fa5d92e8bcc6b803", size = 2091877, upload-time = "2026-04-20T14:43:27.091Z" }, - { url = "https://files.pythonhosted.org/packages/5c/4f/86a832a9d14df58e663bfdf4627dc00d3317c2bd583c4fb23390b0f04b8e/pydantic_core-2.46.3-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f00a0961b125f1a47af7bcc17f00782e12f4cd056f83416006b30111d941dfa3", size = 1932428, upload-time = "2026-04-20T14:40:45.781Z" }, - { url = "https://files.pythonhosted.org/packages/11/1a/fe857968954d93fb78e0d4b6df5c988c74c4aaa67181c60be7cfe327c0ca/pydantic_core-2.46.3-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57697d7c056aca4bbb680200f96563e841a6386ac1129370a0102592f4dddff5", size = 1997550, upload-time = "2026-04-20T14:44:02.425Z" }, - { url = "https://files.pythonhosted.org/packages/17/eb/9d89ad2d9b0ba8cd65393d434471621b98912abb10fbe1df08e480ba57b5/pydantic_core-2.46.3-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd35aa21299def8db7ef4fe5c4ff862941a9a158ca7b63d61e66fe67d30416b4", size = 2137657, upload-time = "2026-04-20T14:42:45.149Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/2a/ef/f7abb56c49382a246fd2ce9c799691e3c3e7175ec74b14d99e798bcddb1a/pydantic_core-2.46.3.tar.gz", hash = "sha256:41c178f65b8c29807239d47e6050262eb6bf84eb695e41101e62e38df4a5bc2c", size = 471412 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4b/cb/5b47425556ecc1f3fe18ed2a0083188aa46e1dd812b06e406475b3a5d536/pydantic_core-2.46.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b11b59b3eee90a80a36701ddb4576d9ae31f93f05cb9e277ceaa09e6bf074a67", size = 2101946 }, + { url = "https://files.pythonhosted.org/packages/a1/4f/2fb62c2267cae99b815bbf4a7b9283812c88ca3153ef29f7707200f1d4e5/pydantic_core-2.46.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:af8653713055ea18a3abc1537fe2ebc42f5b0bbb768d1eb79fd74eb47c0ac089", size = 1951612 }, + { url = "https://files.pythonhosted.org/packages/50/6e/b7348fd30d6556d132cddd5bd79f37f96f2601fe0608afac4f5fb01ec0b3/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75a519dab6d63c514f3a81053e5266c549679e4aa88f6ec57f2b7b854aceb1b0", size = 1977027 }, + { url = "https://files.pythonhosted.org/packages/82/11/31d60ee2b45540d3fb0b29302a393dbc01cd771c473f5b5147bcd353e593/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a6cd87cb1575b1ad05ba98894c5b5c96411ef678fa2f6ed2576607095b8d9789", size = 2063008 }, + { url = "https://files.pythonhosted.org/packages/8a/db/3a9d1957181b59258f44a2300ab0f0be9d1e12d662a4f57bb31250455c52/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f80a55484b8d843c8ada81ebf70a682f3f00a3d40e378c06cf17ecb44d280d7d", size = 2233082 }, + { url = "https://files.pythonhosted.org/packages/9c/e1/3277c38792aeb5cfb18c2f0c5785a221d9ff4e149abbe1184d53d5f72273/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3861f1731b90c50a3266316b9044f5c9b405eecb8e299b0a7120596334e4fe9c", size = 2304615 }, + { url = "https://files.pythonhosted.org/packages/5e/d5/e3d9717c9eba10855325650afd2a9cba8e607321697f18953af9d562da2f/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb528e295ed31570ac3dcc9bfdd6e0150bc11ce6168ac87a8082055cf1a67395", size = 2094380 }, + { url = "https://files.pythonhosted.org/packages/a1/20/abac35dedcbfd66c6f0b03e4e3564511771d6c9b7ede10a362d03e110d9b/pydantic_core-2.46.3-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:367508faa4973b992b271ba1494acaab36eb7e8739d1e47be5035fb1ea225396", size = 2135429 }, + { url = "https://files.pythonhosted.org/packages/6c/a5/41bfd1df69afad71b5cf0535055bccc73022715ad362edbc124bc1e021d7/pydantic_core-2.46.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ad3c826fe523e4becf4fe39baa44286cff85ef137c729a2c5e269afbfd0905d", size = 2174582 }, + { url = "https://files.pythonhosted.org/packages/79/65/38d86ea056b29b2b10734eb23329b7a7672ca604df4f2b6e9c02d4ee22fe/pydantic_core-2.46.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ec638c5d194ef8af27db69f16c954a09797c0dc25015ad6123eb2c73a4d271ca", size = 2187533 }, + { url = "https://files.pythonhosted.org/packages/b6/55/a1129141678a2026badc539ad1dee0a71d06f54c2f06a4bd68c030ac781b/pydantic_core-2.46.3-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:28ed528c45446062ee66edb1d33df5d88828ae167de76e773a3c7f64bd14e976", size = 2332985 }, + { url = "https://files.pythonhosted.org/packages/d7/60/cb26f4077719f709e54819f4e8e1d43f4091f94e285eb6bd21e1190a7b7c/pydantic_core-2.46.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aed19d0c783886d5bd86d80ae5030006b45e28464218747dcf83dabfdd092c7b", size = 2373670 }, + { url = "https://files.pythonhosted.org/packages/6b/7e/c3f21882bdf1d8d086876f81b5e296206c69c6082551d776895de7801fa0/pydantic_core-2.46.3-cp312-cp312-win32.whl", hash = "sha256:06d5d8820cbbdb4147578c1fe7ffcd5b83f34508cb9f9ab76e807be7db6ff0a4", size = 1966722 }, + { url = "https://files.pythonhosted.org/packages/57/be/6b5e757b859013ebfbd7adba02f23b428f37c86dcbf78b5bb0b4ffd36e99/pydantic_core-2.46.3-cp312-cp312-win_amd64.whl", hash = "sha256:c3212fda0ee959c1dd04c60b601ec31097aaa893573a3a1abd0a47bcac2968c1", size = 2072970 }, + { url = "https://files.pythonhosted.org/packages/bf/f8/a989b21cc75e9a32d24192ef700eea606521221a89faa40c919ce884f2b1/pydantic_core-2.46.3-cp312-cp312-win_arm64.whl", hash = "sha256:f1f8338dd7a7f31761f1f1a3c47503a9a3b34eea3c8b01fa6ee96408affb5e72", size = 2035963 }, + { url = "https://files.pythonhosted.org/packages/9b/3c/9b5e8eb9821936d065439c3b0fb1490ffa64163bfe7e1595985a47896073/pydantic_core-2.46.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:12bc98de041458b80c86c56b24df1d23832f3e166cbaff011f25d187f5c62c37", size = 2102109 }, + { url = "https://files.pythonhosted.org/packages/91/97/1c41d1f5a19f241d8069f1e249853bcce378cdb76eec8ab636d7bc426280/pydantic_core-2.46.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:85348b8f89d2c3508b65b16c3c33a4da22b8215138d8b996912bb1532868885f", size = 1951820 }, + { url = "https://files.pythonhosted.org/packages/30/b4/d03a7ae14571bc2b6b3c7b122441154720619afe9a336fa3a95434df5e2f/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1105677a6df914b1fb71a81b96c8cce7726857e1717d86001f29be06a25ee6f8", size = 1977785 }, + { url = "https://files.pythonhosted.org/packages/ae/0c/4086f808834b59e3c8f1aa26df8f4b6d998cdcf354a143d18ef41529d1fe/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87082cd65669a33adeba5470769e9704c7cf026cc30afb9cc77fd865578ebaad", size = 2062761 }, + { url = "https://files.pythonhosted.org/packages/fa/71/a649be5a5064c2df0db06e0a512c2281134ed2fcc981f52a657936a7527c/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60e5f66e12c4f5212d08522963380eaaeac5ebd795826cfd19b2dfb0c7a52b9c", size = 2232989 }, + { url = "https://files.pythonhosted.org/packages/a2/84/7756e75763e810b3a710f4724441d1ecc5883b94aacb07ca71c5fb5cfb69/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b6cdf19bf84128d5e7c37e8a73a0c5c10d51103a650ac585d42dd6ae233f2b7f", size = 2303975 }, + { url = "https://files.pythonhosted.org/packages/6c/35/68a762e0c1e31f35fa0dac733cbd9f5b118042853698de9509c8e5bf128b/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:031bb17f4885a43773c8c763089499f242aee2ea85cf17154168775dccdecf35", size = 2095325 }, + { url = "https://files.pythonhosted.org/packages/77/bf/1bf8c9a8e91836c926eae5e3e51dce009bf495a60ca56060689d3df3f340/pydantic_core-2.46.3-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:bcf2a8b2982a6673693eae7348ef3d8cf3979c1d63b54fca7c397a635cc68687", size = 2133368 }, + { url = "https://files.pythonhosted.org/packages/e5/50/87d818d6bab915984995157ceb2380f5aac4e563dddbed6b56f0ed057aba/pydantic_core-2.46.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28e8cf2f52d72ced402a137145923a762cbb5081e48b34312f7a0c8f55928ec3", size = 2173908 }, + { url = "https://files.pythonhosted.org/packages/91/88/a311fb306d0bd6185db41fa14ae888fb81d0baf648a761ae760d30819d33/pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:17eaface65d9fc5abb940003020309c1bf7a211f5f608d7870297c367e6f9022", size = 2186422 }, + { url = "https://files.pythonhosted.org/packages/8f/79/28fd0d81508525ab2054fef7c77a638c8b5b0afcbbaeee493cf7c3fef7e1/pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:93fd339f23408a07e98950a89644f92c54d8729719a40b30c0a30bb9ebc55d23", size = 2332709 }, + { url = "https://files.pythonhosted.org/packages/b3/21/795bf5fe5c0f379308b8ef19c50dedab2e7711dbc8d0c2acf08f1c7daa05/pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:23cbdb3aaa74dfe0837975dbf69b469753bbde8eacace524519ffdb6b6e89eb7", size = 2372428 }, + { url = "https://files.pythonhosted.org/packages/45/b3/ed14c659cbe7605e3ef063077680a64680aec81eb1a04763a05190d49b7f/pydantic_core-2.46.3-cp313-cp313-win32.whl", hash = "sha256:610eda2e3838f401105e6326ca304f5da1e15393ae25dacae5c5c63f2c275b13", size = 1965601 }, + { url = "https://files.pythonhosted.org/packages/ef/bb/adb70d9a762ddd002d723fbf1bd492244d37da41e3af7b74ad212609027e/pydantic_core-2.46.3-cp313-cp313-win_amd64.whl", hash = "sha256:68cc7866ed863db34351294187f9b729964c371ba33e31c26f478471c52e1ed0", size = 2071517 }, + { url = "https://files.pythonhosted.org/packages/52/eb/66faefabebfe68bd7788339c9c9127231e680b11906368c67ce112fdb47f/pydantic_core-2.46.3-cp313-cp313-win_arm64.whl", hash = "sha256:f64b5537ac62b231572879cd08ec05600308636a5d63bcbdb15063a466977bec", size = 2035802 }, ] [[package]] name = "pygments" version = "2.19.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, + { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217 }, ] [[package]] @@ -1205,9 +1216,9 @@ dependencies = [ { name = "markdown" }, { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/df/08/f1c908c581fd11913da4711ea7ba32c0eee40b0190000996bb863b0c9349/pymdown_extensions-10.21.2.tar.gz", hash = "sha256:c3f55a5b8a1d0edf6699e35dcbea71d978d34ff3fa79f3d807b8a5b3fa90fbdc", size = 853922, upload-time = "2026-03-29T15:01:55.233Z" } +sdist = { url = "https://files.pythonhosted.org/packages/df/08/f1c908c581fd11913da4711ea7ba32c0eee40b0190000996bb863b0c9349/pymdown_extensions-10.21.2.tar.gz", hash = "sha256:c3f55a5b8a1d0edf6699e35dcbea71d978d34ff3fa79f3d807b8a5b3fa90fbdc", size = 853922 } wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/27/a2fc51a4a122dfd1015e921ae9d22fee3d20b0b8080d9a704578bf9deece/pymdown_extensions-10.21.2-py3-none-any.whl", hash = "sha256:5c0fd2a2bea14eb39af8ff284f1066d898ab2187d81b889b75d46d4348c01638", size = 268901, upload-time = "2026-03-29T15:01:53.244Z" }, + { url = "https://files.pythonhosted.org/packages/f7/27/a2fc51a4a122dfd1015e921ae9d22fee3d20b0b8080d9a704578bf9deece/pymdown_extensions-10.21.2-py3-none-any.whl", hash = "sha256:5c0fd2a2bea14eb39af8ff284f1066d898ab2187d81b889b75d46d4348c01638", size = 268901 }, ] [[package]] @@ -1221,9 +1232,9 @@ dependencies = [ { name = "pluggy" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, + { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249 }, ] [[package]] @@ -1235,9 +1246,9 @@ dependencies = [ { name = "pluggy" }, { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592, upload-time = "2026-03-21T20:11:16.284Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/51/a849f96e117386044471c8ec2bd6cfebacda285da9525c9106aeb28da671/pytest_cov-7.1.0.tar.gz", hash = "sha256:30674f2b5f6351aa09702a9c8c364f6a01c27aae0c1366ae8016160d1efc56b2", size = 55592 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876, upload-time = "2026-03-21T20:11:14.438Z" }, + { url = "https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl", hash = "sha256:a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678", size = 22876 }, ] [[package]] @@ -1247,9 +1258,18 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "six" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432, upload-time = "2024-03-01T18:36:20.211Z" } +sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 }, +] + +[[package]] +name = "python-dotenv" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, + { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101 }, ] [[package]] @@ -1259,27 +1279,27 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "text-unidecode" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/87/c7/5e1547c44e31da50a460df93af11a535ace568ef89d7a811069ead340c4a/python-slugify-8.0.4.tar.gz", hash = "sha256:59202371d1d05b54a9e7720c5e038f928f45daaffe41dd10822f3907b937c856", size = 10921, upload-time = "2024-02-08T18:32:45.488Z" } +sdist = { url = "https://files.pythonhosted.org/packages/87/c7/5e1547c44e31da50a460df93af11a535ace568ef89d7a811069ead340c4a/python-slugify-8.0.4.tar.gz", hash = "sha256:59202371d1d05b54a9e7720c5e038f928f45daaffe41dd10822f3907b937c856", size = 10921 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/62/02da182e544a51a5c3ccf4b03ab79df279f9c60c5e82d5e8bec7ca26ac11/python_slugify-8.0.4-py2.py3-none-any.whl", hash = "sha256:276540b79961052b66b7d116620b36518847f52d5fd9e3a70164fc8c50faa6b8", size = 10051, upload-time = "2024-02-08T18:32:43.911Z" }, + { url = "https://files.pythonhosted.org/packages/a4/62/02da182e544a51a5c3ccf4b03ab79df279f9c60c5e82d5e8bec7ca26ac11/python_slugify-8.0.4-py2.py3-none-any.whl", hash = "sha256:276540b79961052b66b7d116620b36518847f52d5fd9e3a70164fc8c50faa6b8", size = 10051 }, ] [[package]] name = "pytimeparse" version = "1.1.8" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/37/5d/231f5f33c81e09682708fb323f9e4041408d8223e2f0fb9742843328778f/pytimeparse-1.1.8.tar.gz", hash = "sha256:e86136477be924d7e670646a98561957e8ca7308d44841e21f5ddea757556a0a", size = 9403, upload-time = "2018-05-18T17:40:42.76Z" } +sdist = { url = "https://files.pythonhosted.org/packages/37/5d/231f5f33c81e09682708fb323f9e4041408d8223e2f0fb9742843328778f/pytimeparse-1.1.8.tar.gz", hash = "sha256:e86136477be924d7e670646a98561957e8ca7308d44841e21f5ddea757556a0a", size = 9403 } wheels = [ - { url = "https://files.pythonhosted.org/packages/1b/b4/afd75551a3b910abd1d922dbd45e49e5deeb4d47dc50209ce489ba9844dd/pytimeparse-1.1.8-py2.py3-none-any.whl", hash = "sha256:04b7be6cc8bd9f5647a6325444926c3ac34ee6bc7e69da4367ba282f076036bd", size = 9969, upload-time = "2018-05-18T17:40:41.28Z" }, + { url = "https://files.pythonhosted.org/packages/1b/b4/afd75551a3b910abd1d922dbd45e49e5deeb4d47dc50209ce489ba9844dd/pytimeparse-1.1.8-py2.py3-none-any.whl", hash = "sha256:04b7be6cc8bd9f5647a6325444926c3ac34ee6bc7e69da4367ba282f076036bd", size = 9969 }, ] [[package]] name = "pytz" version = "2026.1.post1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/56/db/b8721d71d945e6a8ac63c0fc900b2067181dbb50805958d4d4661cf7d277/pytz-2026.1.post1.tar.gz", hash = "sha256:3378dde6a0c3d26719182142c56e60c7f9af7e968076f31aae569d72a0358ee1", size = 321088, upload-time = "2026-03-03T07:47:50.683Z" } +sdist = { url = "https://files.pythonhosted.org/packages/56/db/b8721d71d945e6a8ac63c0fc900b2067181dbb50805958d4d4661cf7d277/pytz-2026.1.post1.tar.gz", hash = "sha256:3378dde6a0c3d26719182142c56e60c7f9af7e968076f31aae569d72a0358ee1", size = 321088 } wheels = [ - { url = "https://files.pythonhosted.org/packages/10/99/781fe0c827be2742bcc775efefccb3b048a3a9c6ce9aec0cbf4a101677e5/pytz-2026.1.post1-py2.py3-none-any.whl", hash = "sha256:f2fd16142fda348286a75e1a524be810bb05d444e5a081f37f7affc635035f7a", size = 510489, upload-time = "2026-03-03T07:47:49.167Z" }, + { url = "https://files.pythonhosted.org/packages/10/99/781fe0c827be2742bcc775efefccb3b048a3a9c6ce9aec0cbf4a101677e5/pytz-2026.1.post1-py2.py3-none-any.whl", hash = "sha256:f2fd16142fda348286a75e1a524be810bb05d444e5a081f37f7affc635035f7a", size = 510489 }, ] [[package]] @@ -1287,40 +1307,40 @@ name = "pywin32" version = "311" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" }, - { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" }, - { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" }, - { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" }, - { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" }, - { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" }, + { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543 }, + { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040 }, + { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102 }, + { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700 }, + { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700 }, + { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318 }, ] [[package]] name = "pyyaml" version = "6.0.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, - { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, - { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, - { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, - { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, - { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, - { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, - { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, - { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, - { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, - { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, - { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, - { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, - { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, - { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, - { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, - { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, - { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, - { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, - { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063 }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973 }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116 }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011 }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870 }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089 }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181 }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658 }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003 }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344 }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669 }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252 }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081 }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159 }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626 }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613 }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115 }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427 }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090 }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246 }, ] [[package]] @@ -1330,9 +1350,50 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/eb/2e/79c822141bfd05a853236b504869ebc6b70159afc570e1d5a20641782eaa/pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff", size = 5737, upload-time = "2025-05-13T15:24:01.64Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04", size = 4722, upload-time = "2025-05-13T15:23:59.629Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/eb/2e/79c822141bfd05a853236b504869ebc6b70159afc570e1d5a20641782eaa/pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff", size = 5737 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04", size = 4722 }, +] + +[[package]] +name = "rapidfuzz" +version = "3.14.5" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/21/ef6157213316e85790041254259907eb722e00b03480256c0545d98acd33/rapidfuzz-3.14.5.tar.gz", hash = "sha256:ba10ac57884ce82112f7ed910b67e7fb6072d8ef2c06e30dc63c0f604a112e0e", size = 57901753 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/e3/574435c6aafb80254c191ef40d7aca2cb2bb97a095ec9395e9fa59ac307a/rapidfuzz-3.14.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:0d3378f471ef440473a396ce2f8e97ee12f89a78b495540e0a5617bbfe895638", size = 1944601 }, + { url = "https://files.pythonhosted.org/packages/d0/1f/fbad3102a255ecc112ce9a7e779bacab7fd14398217be8868dc9082ba363/rapidfuzz-3.14.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e910eebca9fd0eba245c0555e764597e8a0cccb673a92da2dc2397050725f48", size = 1164293 }, + { url = "https://files.pythonhosted.org/packages/88/37/a3eb7ff6121ed3a5f199a8c38cc86c8e481816f879cb0e0b738b078c9a7e/rapidfuzz-3.14.5-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:01550fe5f60fd176aa66b7611289d46dc4aa4b1b904874c7b6d1d54e581c5ec1", size = 1371999 }, + { url = "https://files.pythonhosted.org/packages/79/72/97a9728c711c7c1b06e107d3f0623880fb4ef90e147ed13c551a1730e7cc/rapidfuzz-3.14.5-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:48bee0b91bebfaec41e1081e351000659ab7570cc4598d617aa04d5bf827f9e6", size = 3145715 }, + { url = "https://files.pythonhosted.org/packages/ed/54/d5caabbea233ac90c286c87c260e49d7641467e87438a18d858e41c82e91/rapidfuzz-3.14.5-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:7e580cb04ad849ae9b786fa21383c6b994b6e6c1444ad1cb9f22392759d72741", size = 1456304 }, + { url = "https://files.pythonhosted.org/packages/fc/a7/2d1a81250ac8c01a0100c026018e76f0e7a097ff63e4c553e02a6938c6fb/rapidfuzz-3.14.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:09d6c9ba091854f07817055d795d604179c12a8f308ba4c7d56f3719dfea1646", size = 2389089 }, + { url = "https://files.pythonhosted.org/packages/65/0d/c47c3872203ae88e6506997c0b576ad731f5261daa25d559be09c9756658/rapidfuzz-3.14.5-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:1e989f86113be66574113b9c7bdf4793f3f863d248e47d911b355e05ca6b6b10", size = 2493404 }, + { url = "https://files.pythonhosted.org/packages/8f/2f/71e0a5a3130792146c8a200a2dd1e52aa16f7c1074012e17f2601eea9a90/rapidfuzz-3.14.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0ebd1a18e2e47bc0b292a07e6ed9c3642f8aaa672d12253885f599b50807a4f9", size = 4251709 }, + { url = "https://files.pythonhosted.org/packages/86/45/d39874901abacef325adb5b34ae416817c8486dfb4fb87c7a9b74ec5b072/rapidfuzz-3.14.5-cp312-cp312-win32.whl", hash = "sha256:9981d38a703b86f0e315a3cd229fd1906fe1d91c989ed121fb975b3c849f89f5", size = 1710069 }, + { url = "https://files.pythonhosted.org/packages/85/0b/f65572c53de8a1c704bda707f63a447b67bdbe95d7cdc70d18885e191df5/rapidfuzz-3.14.5-cp312-cp312-win_amd64.whl", hash = "sha256:d8375e3da319593389727c3187ccaf3e0e84199accc530866b8e0f2b79af05e9", size = 1540630 }, + { url = "https://files.pythonhosted.org/packages/5e/c3/143be3a578f989758cae516f3270d5cbb49783a7bfdf57cc27a670e00456/rapidfuzz-3.14.5-cp312-cp312-win_arm64.whl", hash = "sha256:478b59bb018a6780d73f33e38d0b3ec5e968a6c1ed42876b993dd456b7aa20e8", size = 813137 }, + { url = "https://files.pythonhosted.org/packages/11/66/252803f2010ba699618cdc048b6e1f7cc1f433c08b4a9a17579b92ab0142/rapidfuzz-3.14.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ebd8fd343bf8492a1e60bcb6dc99f90f74f65d98d8241a6b3e1fed225b76ecd6", size = 1940205 }, + { url = "https://files.pythonhosted.org/packages/ea/59/b2afd98e41af9cd54554a4c1c423d84cdd60e6b1c0a09496f033b55f60ec/rapidfuzz-3.14.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6737b35d5af7479c5bf9710f7b17edd9d2c43128d974d25fb4ea653e42c64609", size = 1159639 }, + { url = "https://files.pythonhosted.org/packages/a3/31/7aa7e62c4c516a7af322ed0c4f0774208b72d457d0cfec808bad0df12f4a/rapidfuzz-3.14.5-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b002c7994cc9f2bc9d9856f0fbaee6e8072c983873846c92f25cefba5b2a925f", size = 1367194 }, + { url = "https://files.pythonhosted.org/packages/90/79/2fc252a63bc91d3c3b234d0a3a6ad4ebc460037a23cdcdaf9285f986e6c9/rapidfuzz-3.14.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:17a34330cd2a538c1ce5d400b61ba358c5b72c654b928ff87b362e88f8b864c7", size = 3151805 }, + { url = "https://files.pythonhosted.org/packages/17/54/0c83508f2683ea70e2d05f8527eb07328acf7bb1e9d97a3bece5702378e7/rapidfuzz-3.14.5-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:95d937e74c1a7a1287dfb03b62a827be08ede10a155cf1af73bbf47f2b73ee6e", size = 1455667 }, + { url = "https://files.pythonhosted.org/packages/71/1b/070175e873177814d58850a01ebe80e20ae11e93eb4da894d563988660fa/rapidfuzz-3.14.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:46b92a9970dcc34f0096901c792644094cab49554ac3547f35e3aebbdf0a3610", size = 2388246 }, + { url = "https://files.pythonhosted.org/packages/c9/dd/77caf7aaf9c2be050ad1f128d7c24ff0f59079aa62c5f62f9df41c0af45e/rapidfuzz-3.14.5-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e012177c8e8a8a0754ae0d6027d63042aa5ff036d9f40f07cb3466a6082e21b8", size = 2494333 }, + { url = "https://files.pythonhosted.org/packages/2c/e2/dd7e1f2aa31a8fbbfc16b0610af1d770ffaf1287490f3c8c5b1c52da264f/rapidfuzz-3.14.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a2ae6f53f99c9a0eca7a0afc5b4e45fc73bc1dd4ac74c00509031d76df80ed98", size = 4258579 }, + { url = "https://files.pythonhosted.org/packages/9c/0a/ac99e1ba347ba0e85e0bb60b74231d55fb93c0eff43f2920ccb413d0be08/rapidfuzz-3.14.5-cp313-cp313-win32.whl", hash = "sha256:4a60f0057231188e3bd30216f7b4e0f279b11fa4ec818bb6c1d9f014d1562fbc", size = 1709231 }, + { url = "https://files.pythonhosted.org/packages/cf/cb/0e251d731b3166378644238e8f0cf9e89858c024e19f75ca9f7e3ae83fd5/rapidfuzz-3.14.5-cp313-cp313-win_amd64.whl", hash = "sha256:11bfc2ed8fbe4ab86bd516fadefab126f90e6dcadffa761739fcb304707dfd35", size = 1538519 }, + { url = "https://files.pythonhosted.org/packages/30/6f/4548132acc947db6d5346a248e44a8b3a22d608ef30e770fb578caaf2d00/rapidfuzz-3.14.5-cp313-cp313-win_arm64.whl", hash = "sha256:b486b5218808f6f4dc471b114b1054e63553db69705c97da0271f47bd706aedd", size = 812628 }, + { url = "https://files.pythonhosted.org/packages/00/60/69b177577290c5eab892c6f75fe89c3aff3f9ae80298a78d9372b1cecb9a/rapidfuzz-3.14.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:39ef8658aaf67d51667e7bdaf7096f432333377d8302ac43c70b5df8a4cf89b8", size = 1970231 }, + { url = "https://files.pythonhosted.org/packages/48/38/2fd790052659cc4e2907b63c25433f0987864b445c1aeec1a302ef5ad948/rapidfuzz-3.14.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:9ad37a0be705b544af6296da8edddc260d10a8ae5462530fc9991f66498bb1f9", size = 1194394 }, + { url = "https://files.pythonhosted.org/packages/80/f4/28430ad8472fc3536e8ebd51a864a226e979cfe924c6e3f83d111373aa74/rapidfuzz-3.14.5-cp313-cp313t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d45e06f60729e07d9b20c205f7e5cff90b6ef2584e852eecf46e045aea69627d", size = 1377051 }, + { url = "https://files.pythonhosted.org/packages/77/7e/9aeacabcfd1e77397968362e5b98fe14248b8307011136b17daf99752a8e/rapidfuzz-3.14.5-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e52da10236aa6212de71b9e170bace65b64b129c0dea7fc243d6c9ce976f5074", size = 3160565 }, + { url = "https://files.pythonhosted.org/packages/56/f4/db4dd7be0cd2f2022117ac5407d905f435d60e48baaea313a567ad27e865/rapidfuzz-3.14.5-cp313-cp313t-manylinux_2_39_riscv64.whl", hash = "sha256:440d30faaf682ca496170a7f0cc5453ec942e3e079f0fd802c9a7f938dfb50a3", size = 1442113 }, + { url = "https://files.pythonhosted.org/packages/a4/99/0e9f6aa57f3e32a767216f797e56dc96b720fcecfb9d8ee907ecc82f8d66/rapidfuzz-3.14.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:56227a61fd3d17b0cd9793132431f3a3d07c8654be96794ba9f89fe0fc8b2d09", size = 2396618 }, + { url = "https://files.pythonhosted.org/packages/60/94/44a78e39ffce17cbdd3e2b53b696acc751d5d153be0f499d052b07a4d904/rapidfuzz-3.14.5-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:2e83cd2e25bb4edd97b689d9979d9c3acccdaaf26ceac08212ceece202febcfa", size = 2478220 }, + { url = "https://files.pythonhosted.org/packages/dd/df/454311469a09a507e9d784a35796742bec22e4cebe75551e2da4e0e290fd/rapidfuzz-3.14.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:af3b859726cd3374287e405e14b9634563c078c5531a4f62375508addebddad1", size = 4265027 }, + { url = "https://files.pythonhosted.org/packages/fc/01/175465a9ab3e3b70ba669058372f009d1d49c1746e2dcd56b69df188d3a5/rapidfuzz-3.14.5-cp313-cp313t-win32.whl", hash = "sha256:8ce1d850b3c0178440efde9e884d98421b5e87ff925f364d6d79e23910d7593f", size = 1766814 }, + { url = "https://files.pythonhosted.org/packages/1b/a0/a9b84a47af06ebed94a1439eb2f02adebfb8628bcd30af1fe3e02f5ef56c/rapidfuzz-3.14.5-cp313-cp313t-win_amd64.whl", hash = "sha256:c84af70bcf34e99aee894e46a0f1ac77f17d0ef828179c387407642e2466d28a", size = 1582448 }, + { url = "https://files.pythonhosted.org/packages/1e/f1/5937800238b3f8248e70860d79f69ba8f73e764fff47e36bc9e2f26dbcc6/rapidfuzz-3.14.5-cp313-cp313t-win_arm64.whl", hash = "sha256:aac0ad28c686a5e72b81668b906c030ee28050b244544b8af68e12fb32543895", size = 832932 }, ] [[package]] @@ -1344,65 +1405,65 @@ dependencies = [ { name = "rpds-py" }, { name = "typing-extensions", marker = "python_full_version < '3.13'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766 }, ] [[package]] name = "regex" version = "2026.4.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cb/0e/3a246dbf05666918bd3664d9d787f84a9108f6f43cc953a077e4a7dfdb7e/regex-2026.4.4.tar.gz", hash = "sha256:e08270659717f6973523ce3afbafa53515c4dc5dcad637dc215b6fd50f689423", size = 416000, upload-time = "2026-04-03T20:56:28.155Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e5/28/b972a4d3df61e1d7bcf1b59fdb3cddef22f88b6be43f161bb41ebc0e4081/regex-2026.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c07ab8794fa929e58d97a0e1796b8b76f70943fa39df225ac9964615cf1f9d52", size = 490434, upload-time = "2026-04-03T20:53:40.219Z" }, - { url = "https://files.pythonhosted.org/packages/84/20/30041446cf6dc3e0eab344fc62770e84c23b6b68a3b657821f9f80cb69b4/regex-2026.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2c785939dc023a1ce4ec09599c032cc9933d258a998d16ca6f2b596c010940eb", size = 292061, upload-time = "2026-04-03T20:53:41.862Z" }, - { url = "https://files.pythonhosted.org/packages/62/c8/3baa06d75c98c46d4cc4262b71fd2edb9062b5665e868bca57859dadf93a/regex-2026.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b1ce5c81c9114f1ce2f9288a51a8fd3aeea33a0cc440c415bf02da323aa0a76", size = 289628, upload-time = "2026-04-03T20:53:43.701Z" }, - { url = "https://files.pythonhosted.org/packages/31/87/3accf55634caad8c0acab23f5135ef7d4a21c39f28c55c816ae012931408/regex-2026.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:760ef21c17d8e6a4fe8cf406a97cf2806a4df93416ccc82fc98d25b1c20425be", size = 796651, upload-time = "2026-04-03T20:53:45.379Z" }, - { url = "https://files.pythonhosted.org/packages/f6/0c/aaa2c83f34efedbf06f61cb1942c25f6cf1ee3b200f832c4d05f28306c2e/regex-2026.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7088fcdcb604a4417c208e2169715800d28838fefd7455fbe40416231d1d47c1", size = 865916, upload-time = "2026-04-03T20:53:47.064Z" }, - { url = "https://files.pythonhosted.org/packages/d9/f6/8c6924c865124643e8f37823eca845dc27ac509b2ee58123685e71cd0279/regex-2026.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:07edca1ba687998968f7db5bc355288d0c6505caa7374f013d27356d93976d13", size = 912287, upload-time = "2026-04-03T20:53:49.422Z" }, - { url = "https://files.pythonhosted.org/packages/11/0e/a9f6f81013e0deaf559b25711623864970fe6a098314e374ccb1540a4152/regex-2026.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:993f657a7c1c6ec51b5e0ba97c9817d06b84ea5fa8d82e43b9405de0defdc2b9", size = 801126, upload-time = "2026-04-03T20:53:51.096Z" }, - { url = "https://files.pythonhosted.org/packages/71/61/3a0cc8af2dc0c8deb48e644dd2521f173f7e6513c6e195aad9aa8dd77ac5/regex-2026.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2b69102a743e7569ebee67e634a69c4cb7e59d6fa2e1aa7d3bdbf3f61435f62d", size = 776788, upload-time = "2026-04-03T20:53:52.889Z" }, - { url = "https://files.pythonhosted.org/packages/64/0b/8bb9cbf21ef7dee58e49b0fdb066a7aded146c823202e16494a36777594f/regex-2026.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dac006c8b6dda72d86ea3d1333d45147de79a3a3f26f10c1cf9287ca4ca0ac3", size = 785184, upload-time = "2026-04-03T20:53:55.627Z" }, - { url = "https://files.pythonhosted.org/packages/99/c2/d3e80e8137b25ee06c92627de4e4d98b94830e02b3e6f81f3d2e3f504cf5/regex-2026.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:50a766ee2010d504554bfb5f578ed2e066898aa26411d57e6296230627cdefa0", size = 859913, upload-time = "2026-04-03T20:53:57.249Z" }, - { url = "https://files.pythonhosted.org/packages/bc/e6/9d5d876157d969c804622456ef250017ac7a8f83e0e14f903b9e6df5ce95/regex-2026.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9e2f5217648f68e3028c823df58663587c1507a5ba8419f4fdfc8a461be76043", size = 765732, upload-time = "2026-04-03T20:53:59.428Z" }, - { url = "https://files.pythonhosted.org/packages/82/80/b568935b4421388561c8ed42aff77247285d3ae3bb2a6ca22af63bae805e/regex-2026.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:39d8de85a08e32632974151ba59c6e9140646dcc36c80423962b1c5c0a92e244", size = 852152, upload-time = "2026-04-03T20:54:01.505Z" }, - { url = "https://files.pythonhosted.org/packages/39/29/f0f81217e21cd998245da047405366385d5c6072048038a3d33b37a79dc0/regex-2026.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:55d9304e0e7178dfb1e106c33edf834097ddf4a890e2f676f6c5118f84390f73", size = 789076, upload-time = "2026-04-03T20:54:03.323Z" }, - { url = "https://files.pythonhosted.org/packages/49/1d/1d957a61976ab9d4e767dd4f9d04b66cc0c41c5e36cf40e2d43688b5ae6f/regex-2026.4.4-cp312-cp312-win32.whl", hash = "sha256:04bb679bc0bde8a7bfb71e991493d47314e7b98380b083df2447cda4b6edb60f", size = 266700, upload-time = "2026-04-03T20:54:05.639Z" }, - { url = "https://files.pythonhosted.org/packages/c5/5c/bf575d396aeb58ea13b06ef2adf624f65b70fafef6950a80fc3da9cae3bc/regex-2026.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:db0ac18435a40a2543dbb3d21e161a6c78e33e8159bd2e009343d224bb03bb1b", size = 277768, upload-time = "2026-04-03T20:54:07.312Z" }, - { url = "https://files.pythonhosted.org/packages/c9/27/049df16ec6a6828ccd72add3c7f54b4df029669bea8e9817df6fff58be90/regex-2026.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:4ce255cc05c1947a12989c6db801c96461947adb7a59990f1360b5983fab4983", size = 270568, upload-time = "2026-04-03T20:54:09.484Z" }, - { url = "https://files.pythonhosted.org/packages/9d/83/c4373bc5f31f2cf4b66f9b7c31005bd87fe66f0dce17701f7db4ee79ee29/regex-2026.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:62f5519042c101762509b1d717b45a69c0139d60414b3c604b81328c01bd1943", size = 490273, upload-time = "2026-04-03T20:54:11.202Z" }, - { url = "https://files.pythonhosted.org/packages/46/f8/fe62afbcc3cf4ad4ac9adeaafd98aa747869ae12d3e8e2ac293d0593c435/regex-2026.4.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3790ba9fb5dd76715a7afe34dbe603ba03f8820764b1dc929dd08106214ed031", size = 291954, upload-time = "2026-04-03T20:54:13.412Z" }, - { url = "https://files.pythonhosted.org/packages/5a/92/4712b9fe6a33d232eeb1c189484b80c6c4b8422b90e766e1195d6e758207/regex-2026.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8fae3c6e795d7678963f2170152b0d892cf6aee9ee8afc8c45e6be38d5107fe7", size = 289487, upload-time = "2026-04-03T20:54:15.824Z" }, - { url = "https://files.pythonhosted.org/packages/88/2c/f83b93f85e01168f1070f045a42d4c937b69fdb8dd7ae82d307253f7e36e/regex-2026.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:298c3ec2d53225b3bf91142eb9691025bab610e0c0c51592dde149db679b3d17", size = 796646, upload-time = "2026-04-03T20:54:18.229Z" }, - { url = "https://files.pythonhosted.org/packages/df/55/61a2e17bf0c4dc57e11caf8dd11771280d8aaa361785f9e3bc40d653f4a7/regex-2026.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e9638791082eaf5b3ac112c587518ee78e083a11c4b28012d8fe2a0f536dfb17", size = 865904, upload-time = "2026-04-03T20:54:20.019Z" }, - { url = "https://files.pythonhosted.org/packages/45/32/1ac8ed1b5a346b5993a3d256abe0a0f03b0b73c8cc88d928537368ac65b6/regex-2026.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ae3e764bd4c5ff55035dc82a8d49acceb42a5298edf6eb2fc4d328ee5dd7afae", size = 912304, upload-time = "2026-04-03T20:54:22.403Z" }, - { url = "https://files.pythonhosted.org/packages/26/47/2ee5c613ab546f0eddebf9905d23e07beb933416b1246c2d8791d01979b4/regex-2026.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ffa81f81b80047ba89a3c69ae6a0f78d06f4a42ce5126b0eb2a0a10ad44e0b2e", size = 801126, upload-time = "2026-04-03T20:54:24.308Z" }, - { url = "https://files.pythonhosted.org/packages/75/cd/41dacd129ca9fd20bd7d02f83e0fad83e034ac8a084ec369c90f55ef37e2/regex-2026.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f56ebf9d70305307a707911b88469213630aba821e77de7d603f9d2f0730687d", size = 776772, upload-time = "2026-04-03T20:54:26.319Z" }, - { url = "https://files.pythonhosted.org/packages/89/6d/5af0b588174cb5f46041fa7dd64d3fd5cd2fe51f18766703d1edc387f324/regex-2026.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:773d1dfd652bbffb09336abf890bfd64785c7463716bf766d0eb3bc19c8b7f27", size = 785228, upload-time = "2026-04-03T20:54:28.387Z" }, - { url = "https://files.pythonhosted.org/packages/b7/3b/f5a72b7045bd59575fc33bf1345f156fcfd5a8484aea6ad84b12c5a82114/regex-2026.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d51d20befd5275d092cdffba57ded05f3c436317ee56466c8928ac32d960edaf", size = 860032, upload-time = "2026-04-03T20:54:30.641Z" }, - { url = "https://files.pythonhosted.org/packages/39/a4/72a317003d6fcd7a573584a85f59f525dfe8f67e355ca74eb6b53d66a5e2/regex-2026.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:0a51cdb3c1e9161154f976cb2bef9894bc063ac82f31b733087ffb8e880137d0", size = 765714, upload-time = "2026-04-03T20:54:32.789Z" }, - { url = "https://files.pythonhosted.org/packages/25/1e/5672e16f34dbbcb2560cc7e6a2fbb26dfa8b270711e730101da4423d3973/regex-2026.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ae5266a82596114e41fb5302140e9630204c1b5f325c770bec654b95dd54b0aa", size = 852078, upload-time = "2026-04-03T20:54:34.546Z" }, - { url = "https://files.pythonhosted.org/packages/f7/0d/c813f0af7c6cc7ed7b9558bac2e5120b60ad0fa48f813e4d4bd55446f214/regex-2026.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c882cd92ec68585e9c1cf36c447ec846c0d94edd706fe59e0c198e65822fd23b", size = 789181, upload-time = "2026-04-03T20:54:36.642Z" }, - { url = "https://files.pythonhosted.org/packages/ea/6d/a344608d1adbd2a95090ddd906cec09a11be0e6517e878d02a5123e0917f/regex-2026.4.4-cp313-cp313-win32.whl", hash = "sha256:05568c4fbf3cb4fa9e28e3af198c40d3237cf6041608a9022285fe567ec3ad62", size = 266690, upload-time = "2026-04-03T20:54:38.343Z" }, - { url = "https://files.pythonhosted.org/packages/31/07/54049f89b46235ca6f45cd6c88668a7050e77d4a15555e47dd40fde75263/regex-2026.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:3384df51ed52db0bea967e21458ab0a414f67cdddfd94401688274e55147bb81", size = 277733, upload-time = "2026-04-03T20:54:40.11Z" }, - { url = "https://files.pythonhosted.org/packages/0e/21/61366a8e20f4d43fb597708cac7f0e2baadb491ecc9549b4980b2be27d16/regex-2026.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:acd38177bd2c8e69a411d6521760806042e244d0ef94e2dd03ecdaa8a3c99427", size = 270565, upload-time = "2026-04-03T20:54:41.883Z" }, - { url = "https://files.pythonhosted.org/packages/f1/1e/3a2b9672433bef02f5d39aa1143ca2c08f311c1d041c464a42be9ae648dc/regex-2026.4.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f94a11a9d05afcfcfa640e096319720a19cc0c9f7768e1a61fceee6a3afc6c7c", size = 494126, upload-time = "2026-04-03T20:54:43.602Z" }, - { url = "https://files.pythonhosted.org/packages/4e/4b/c132a4f4fe18ad3340d89fcb56235132b69559136036b845be3c073142ed/regex-2026.4.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:36bcb9d6d1307ab629edc553775baada2aefa5c50ccc0215fbfd2afcfff43141", size = 293882, upload-time = "2026-04-03T20:54:45.41Z" }, - { url = "https://files.pythonhosted.org/packages/f4/5f/eaa38092ce7a023656280f2341dbbd4ad5f05d780a70abba7bb4f4bea54c/regex-2026.4.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:261c015b3e2ed0919157046d768774ecde57f03d8fa4ba78d29793447f70e717", size = 292334, upload-time = "2026-04-03T20:54:47.051Z" }, - { url = "https://files.pythonhosted.org/packages/5f/f6/dd38146af1392dac33db7074ab331cec23cced3759167735c42c5460a243/regex-2026.4.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c228cf65b4a54583763645dcd73819b3b381ca8b4bb1b349dee1c135f4112c07", size = 811691, upload-time = "2026-04-03T20:54:49.074Z" }, - { url = "https://files.pythonhosted.org/packages/7a/f0/dc54c2e69f5eeec50601054998ec3690d5344277e782bd717e49867c1d29/regex-2026.4.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dd2630faeb6876fb0c287f664d93ddce4d50cd46c6e88e60378c05c9047e08ca", size = 871227, upload-time = "2026-04-03T20:54:51.035Z" }, - { url = "https://files.pythonhosted.org/packages/a1/af/cb16bd5dc61621e27df919a4449bbb7e5a1034c34d307e0a706e9cc0f3e3/regex-2026.4.4-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6a50ab11b7779b849472337191f3a043e27e17f71555f98d0092fa6d73364520", size = 917435, upload-time = "2026-04-03T20:54:52.994Z" }, - { url = "https://files.pythonhosted.org/packages/5c/71/8b260897f22996b666edd9402861668f45a2ca259f665ac029e6104a2d7d/regex-2026.4.4-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0734f63afe785138549fbe822a8cfeaccd1bae814c5057cc0ed5b9f2de4fc883", size = 816358, upload-time = "2026-04-03T20:54:54.884Z" }, - { url = "https://files.pythonhosted.org/packages/1c/60/775f7f72a510ef238254906c2f3d737fc80b16ca85f07d20e318d2eea894/regex-2026.4.4-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c4ee50606cb1967db7e523224e05f32089101945f859928e65657a2cbb3d278b", size = 785549, upload-time = "2026-04-03T20:54:57.01Z" }, - { url = "https://files.pythonhosted.org/packages/58/42/34d289b3627c03cf381e44da534a0021664188fa49ba41513da0b4ec6776/regex-2026.4.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6c1818f37be3ca02dcb76d63f2c7aaba4b0dc171b579796c6fbe00148dfec6b1", size = 801364, upload-time = "2026-04-03T20:54:58.981Z" }, - { url = "https://files.pythonhosted.org/packages/fc/20/f6ecf319b382a8f1ab529e898b222c3f30600fcede7834733c26279e7465/regex-2026.4.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:f5bfc2741d150d0be3e4a0401a5c22b06e60acb9aa4daa46d9e79a6dcd0f135b", size = 866221, upload-time = "2026-04-03T20:55:00.88Z" }, - { url = "https://files.pythonhosted.org/packages/92/6a/9f16d3609d549bd96d7a0b2aee1625d7512ba6a03efc01652149ef88e74d/regex-2026.4.4-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:504ffa8a03609a087cad81277a629b6ce884b51a24bd388a7980ad61748618ff", size = 772530, upload-time = "2026-04-03T20:55:03.213Z" }, - { url = "https://files.pythonhosted.org/packages/fa/f6/aa9768bc96a4c361ac96419fbaf2dcdc33970bb813df3ba9b09d5d7b6d96/regex-2026.4.4-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:70aadc6ff12e4b444586e57fc30771f86253f9f0045b29016b9605b4be5f7dfb", size = 856989, upload-time = "2026-04-03T20:55:05.087Z" }, - { url = "https://files.pythonhosted.org/packages/4d/b4/c671db3556be2473ae3e4bb7a297c518d281452871501221251ea4ecba57/regex-2026.4.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f4f83781191007b6ef43b03debc35435f10cad9b96e16d147efe84a1d48bdde4", size = 803241, upload-time = "2026-04-03T20:55:07.162Z" }, - { url = "https://files.pythonhosted.org/packages/2a/5c/83e3b1d89fa4f6e5a1bc97b4abd4a9a97b3c1ac7854164f694f5f0ba98a0/regex-2026.4.4-cp313-cp313t-win32.whl", hash = "sha256:e014a797de43d1847df957c0a2a8e861d1c17547ee08467d1db2c370b7568baa", size = 269921, upload-time = "2026-04-03T20:55:09.62Z" }, - { url = "https://files.pythonhosted.org/packages/28/07/077c387121f42cdb4d92b1301133c0d93b5709d096d1669ab847dda9fe2e/regex-2026.4.4-cp313-cp313t-win_amd64.whl", hash = "sha256:b15b88b0d52b179712632832c1d6e58e5774f93717849a41096880442da41ab0", size = 281240, upload-time = "2026-04-03T20:55:11.521Z" }, - { url = "https://files.pythonhosted.org/packages/9d/22/ead4a4abc7c59a4d882662aa292ca02c8b617f30b6e163bc1728879e9353/regex-2026.4.4-cp313-cp313t-win_arm64.whl", hash = "sha256:586b89cdadf7d67bf86ae3342a4dcd2b8d70a832d90c18a0ae955105caf34dbe", size = 272440, upload-time = "2026-04-03T20:55:13.365Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/cb/0e/3a246dbf05666918bd3664d9d787f84a9108f6f43cc953a077e4a7dfdb7e/regex-2026.4.4.tar.gz", hash = "sha256:e08270659717f6973523ce3afbafa53515c4dc5dcad637dc215b6fd50f689423", size = 416000 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e5/28/b972a4d3df61e1d7bcf1b59fdb3cddef22f88b6be43f161bb41ebc0e4081/regex-2026.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:c07ab8794fa929e58d97a0e1796b8b76f70943fa39df225ac9964615cf1f9d52", size = 490434 }, + { url = "https://files.pythonhosted.org/packages/84/20/30041446cf6dc3e0eab344fc62770e84c23b6b68a3b657821f9f80cb69b4/regex-2026.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2c785939dc023a1ce4ec09599c032cc9933d258a998d16ca6f2b596c010940eb", size = 292061 }, + { url = "https://files.pythonhosted.org/packages/62/c8/3baa06d75c98c46d4cc4262b71fd2edb9062b5665e868bca57859dadf93a/regex-2026.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1b1ce5c81c9114f1ce2f9288a51a8fd3aeea33a0cc440c415bf02da323aa0a76", size = 289628 }, + { url = "https://files.pythonhosted.org/packages/31/87/3accf55634caad8c0acab23f5135ef7d4a21c39f28c55c816ae012931408/regex-2026.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:760ef21c17d8e6a4fe8cf406a97cf2806a4df93416ccc82fc98d25b1c20425be", size = 796651 }, + { url = "https://files.pythonhosted.org/packages/f6/0c/aaa2c83f34efedbf06f61cb1942c25f6cf1ee3b200f832c4d05f28306c2e/regex-2026.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7088fcdcb604a4417c208e2169715800d28838fefd7455fbe40416231d1d47c1", size = 865916 }, + { url = "https://files.pythonhosted.org/packages/d9/f6/8c6924c865124643e8f37823eca845dc27ac509b2ee58123685e71cd0279/regex-2026.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:07edca1ba687998968f7db5bc355288d0c6505caa7374f013d27356d93976d13", size = 912287 }, + { url = "https://files.pythonhosted.org/packages/11/0e/a9f6f81013e0deaf559b25711623864970fe6a098314e374ccb1540a4152/regex-2026.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:993f657a7c1c6ec51b5e0ba97c9817d06b84ea5fa8d82e43b9405de0defdc2b9", size = 801126 }, + { url = "https://files.pythonhosted.org/packages/71/61/3a0cc8af2dc0c8deb48e644dd2521f173f7e6513c6e195aad9aa8dd77ac5/regex-2026.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:2b69102a743e7569ebee67e634a69c4cb7e59d6fa2e1aa7d3bdbf3f61435f62d", size = 776788 }, + { url = "https://files.pythonhosted.org/packages/64/0b/8bb9cbf21ef7dee58e49b0fdb066a7aded146c823202e16494a36777594f/regex-2026.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dac006c8b6dda72d86ea3d1333d45147de79a3a3f26f10c1cf9287ca4ca0ac3", size = 785184 }, + { url = "https://files.pythonhosted.org/packages/99/c2/d3e80e8137b25ee06c92627de4e4d98b94830e02b3e6f81f3d2e3f504cf5/regex-2026.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:50a766ee2010d504554bfb5f578ed2e066898aa26411d57e6296230627cdefa0", size = 859913 }, + { url = "https://files.pythonhosted.org/packages/bc/e6/9d5d876157d969c804622456ef250017ac7a8f83e0e14f903b9e6df5ce95/regex-2026.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:9e2f5217648f68e3028c823df58663587c1507a5ba8419f4fdfc8a461be76043", size = 765732 }, + { url = "https://files.pythonhosted.org/packages/82/80/b568935b4421388561c8ed42aff77247285d3ae3bb2a6ca22af63bae805e/regex-2026.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:39d8de85a08e32632974151ba59c6e9140646dcc36c80423962b1c5c0a92e244", size = 852152 }, + { url = "https://files.pythonhosted.org/packages/39/29/f0f81217e21cd998245da047405366385d5c6072048038a3d33b37a79dc0/regex-2026.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:55d9304e0e7178dfb1e106c33edf834097ddf4a890e2f676f6c5118f84390f73", size = 789076 }, + { url = "https://files.pythonhosted.org/packages/49/1d/1d957a61976ab9d4e767dd4f9d04b66cc0c41c5e36cf40e2d43688b5ae6f/regex-2026.4.4-cp312-cp312-win32.whl", hash = "sha256:04bb679bc0bde8a7bfb71e991493d47314e7b98380b083df2447cda4b6edb60f", size = 266700 }, + { url = "https://files.pythonhosted.org/packages/c5/5c/bf575d396aeb58ea13b06ef2adf624f65b70fafef6950a80fc3da9cae3bc/regex-2026.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:db0ac18435a40a2543dbb3d21e161a6c78e33e8159bd2e009343d224bb03bb1b", size = 277768 }, + { url = "https://files.pythonhosted.org/packages/c9/27/049df16ec6a6828ccd72add3c7f54b4df029669bea8e9817df6fff58be90/regex-2026.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:4ce255cc05c1947a12989c6db801c96461947adb7a59990f1360b5983fab4983", size = 270568 }, + { url = "https://files.pythonhosted.org/packages/9d/83/c4373bc5f31f2cf4b66f9b7c31005bd87fe66f0dce17701f7db4ee79ee29/regex-2026.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:62f5519042c101762509b1d717b45a69c0139d60414b3c604b81328c01bd1943", size = 490273 }, + { url = "https://files.pythonhosted.org/packages/46/f8/fe62afbcc3cf4ad4ac9adeaafd98aa747869ae12d3e8e2ac293d0593c435/regex-2026.4.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:3790ba9fb5dd76715a7afe34dbe603ba03f8820764b1dc929dd08106214ed031", size = 291954 }, + { url = "https://files.pythonhosted.org/packages/5a/92/4712b9fe6a33d232eeb1c189484b80c6c4b8422b90e766e1195d6e758207/regex-2026.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8fae3c6e795d7678963f2170152b0d892cf6aee9ee8afc8c45e6be38d5107fe7", size = 289487 }, + { url = "https://files.pythonhosted.org/packages/88/2c/f83b93f85e01168f1070f045a42d4c937b69fdb8dd7ae82d307253f7e36e/regex-2026.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:298c3ec2d53225b3bf91142eb9691025bab610e0c0c51592dde149db679b3d17", size = 796646 }, + { url = "https://files.pythonhosted.org/packages/df/55/61a2e17bf0c4dc57e11caf8dd11771280d8aaa361785f9e3bc40d653f4a7/regex-2026.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e9638791082eaf5b3ac112c587518ee78e083a11c4b28012d8fe2a0f536dfb17", size = 865904 }, + { url = "https://files.pythonhosted.org/packages/45/32/1ac8ed1b5a346b5993a3d256abe0a0f03b0b73c8cc88d928537368ac65b6/regex-2026.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ae3e764bd4c5ff55035dc82a8d49acceb42a5298edf6eb2fc4d328ee5dd7afae", size = 912304 }, + { url = "https://files.pythonhosted.org/packages/26/47/2ee5c613ab546f0eddebf9905d23e07beb933416b1246c2d8791d01979b4/regex-2026.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ffa81f81b80047ba89a3c69ae6a0f78d06f4a42ce5126b0eb2a0a10ad44e0b2e", size = 801126 }, + { url = "https://files.pythonhosted.org/packages/75/cd/41dacd129ca9fd20bd7d02f83e0fad83e034ac8a084ec369c90f55ef37e2/regex-2026.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f56ebf9d70305307a707911b88469213630aba821e77de7d603f9d2f0730687d", size = 776772 }, + { url = "https://files.pythonhosted.org/packages/89/6d/5af0b588174cb5f46041fa7dd64d3fd5cd2fe51f18766703d1edc387f324/regex-2026.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:773d1dfd652bbffb09336abf890bfd64785c7463716bf766d0eb3bc19c8b7f27", size = 785228 }, + { url = "https://files.pythonhosted.org/packages/b7/3b/f5a72b7045bd59575fc33bf1345f156fcfd5a8484aea6ad84b12c5a82114/regex-2026.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:d51d20befd5275d092cdffba57ded05f3c436317ee56466c8928ac32d960edaf", size = 860032 }, + { url = "https://files.pythonhosted.org/packages/39/a4/72a317003d6fcd7a573584a85f59f525dfe8f67e355ca74eb6b53d66a5e2/regex-2026.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:0a51cdb3c1e9161154f976cb2bef9894bc063ac82f31b733087ffb8e880137d0", size = 765714 }, + { url = "https://files.pythonhosted.org/packages/25/1e/5672e16f34dbbcb2560cc7e6a2fbb26dfa8b270711e730101da4423d3973/regex-2026.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:ae5266a82596114e41fb5302140e9630204c1b5f325c770bec654b95dd54b0aa", size = 852078 }, + { url = "https://files.pythonhosted.org/packages/f7/0d/c813f0af7c6cc7ed7b9558bac2e5120b60ad0fa48f813e4d4bd55446f214/regex-2026.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c882cd92ec68585e9c1cf36c447ec846c0d94edd706fe59e0c198e65822fd23b", size = 789181 }, + { url = "https://files.pythonhosted.org/packages/ea/6d/a344608d1adbd2a95090ddd906cec09a11be0e6517e878d02a5123e0917f/regex-2026.4.4-cp313-cp313-win32.whl", hash = "sha256:05568c4fbf3cb4fa9e28e3af198c40d3237cf6041608a9022285fe567ec3ad62", size = 266690 }, + { url = "https://files.pythonhosted.org/packages/31/07/54049f89b46235ca6f45cd6c88668a7050e77d4a15555e47dd40fde75263/regex-2026.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:3384df51ed52db0bea967e21458ab0a414f67cdddfd94401688274e55147bb81", size = 277733 }, + { url = "https://files.pythonhosted.org/packages/0e/21/61366a8e20f4d43fb597708cac7f0e2baadb491ecc9549b4980b2be27d16/regex-2026.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:acd38177bd2c8e69a411d6521760806042e244d0ef94e2dd03ecdaa8a3c99427", size = 270565 }, + { url = "https://files.pythonhosted.org/packages/f1/1e/3a2b9672433bef02f5d39aa1143ca2c08f311c1d041c464a42be9ae648dc/regex-2026.4.4-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:f94a11a9d05afcfcfa640e096319720a19cc0c9f7768e1a61fceee6a3afc6c7c", size = 494126 }, + { url = "https://files.pythonhosted.org/packages/4e/4b/c132a4f4fe18ad3340d89fcb56235132b69559136036b845be3c073142ed/regex-2026.4.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:36bcb9d6d1307ab629edc553775baada2aefa5c50ccc0215fbfd2afcfff43141", size = 293882 }, + { url = "https://files.pythonhosted.org/packages/f4/5f/eaa38092ce7a023656280f2341dbbd4ad5f05d780a70abba7bb4f4bea54c/regex-2026.4.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:261c015b3e2ed0919157046d768774ecde57f03d8fa4ba78d29793447f70e717", size = 292334 }, + { url = "https://files.pythonhosted.org/packages/5f/f6/dd38146af1392dac33db7074ab331cec23cced3759167735c42c5460a243/regex-2026.4.4-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c228cf65b4a54583763645dcd73819b3b381ca8b4bb1b349dee1c135f4112c07", size = 811691 }, + { url = "https://files.pythonhosted.org/packages/7a/f0/dc54c2e69f5eeec50601054998ec3690d5344277e782bd717e49867c1d29/regex-2026.4.4-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:dd2630faeb6876fb0c287f664d93ddce4d50cd46c6e88e60378c05c9047e08ca", size = 871227 }, + { url = "https://files.pythonhosted.org/packages/a1/af/cb16bd5dc61621e27df919a4449bbb7e5a1034c34d307e0a706e9cc0f3e3/regex-2026.4.4-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6a50ab11b7779b849472337191f3a043e27e17f71555f98d0092fa6d73364520", size = 917435 }, + { url = "https://files.pythonhosted.org/packages/5c/71/8b260897f22996b666edd9402861668f45a2ca259f665ac029e6104a2d7d/regex-2026.4.4-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0734f63afe785138549fbe822a8cfeaccd1bae814c5057cc0ed5b9f2de4fc883", size = 816358 }, + { url = "https://files.pythonhosted.org/packages/1c/60/775f7f72a510ef238254906c2f3d737fc80b16ca85f07d20e318d2eea894/regex-2026.4.4-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c4ee50606cb1967db7e523224e05f32089101945f859928e65657a2cbb3d278b", size = 785549 }, + { url = "https://files.pythonhosted.org/packages/58/42/34d289b3627c03cf381e44da534a0021664188fa49ba41513da0b4ec6776/regex-2026.4.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6c1818f37be3ca02dcb76d63f2c7aaba4b0dc171b579796c6fbe00148dfec6b1", size = 801364 }, + { url = "https://files.pythonhosted.org/packages/fc/20/f6ecf319b382a8f1ab529e898b222c3f30600fcede7834733c26279e7465/regex-2026.4.4-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:f5bfc2741d150d0be3e4a0401a5c22b06e60acb9aa4daa46d9e79a6dcd0f135b", size = 866221 }, + { url = "https://files.pythonhosted.org/packages/92/6a/9f16d3609d549bd96d7a0b2aee1625d7512ba6a03efc01652149ef88e74d/regex-2026.4.4-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:504ffa8a03609a087cad81277a629b6ce884b51a24bd388a7980ad61748618ff", size = 772530 }, + { url = "https://files.pythonhosted.org/packages/fa/f6/aa9768bc96a4c361ac96419fbaf2dcdc33970bb813df3ba9b09d5d7b6d96/regex-2026.4.4-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:70aadc6ff12e4b444586e57fc30771f86253f9f0045b29016b9605b4be5f7dfb", size = 856989 }, + { url = "https://files.pythonhosted.org/packages/4d/b4/c671db3556be2473ae3e4bb7a297c518d281452871501221251ea4ecba57/regex-2026.4.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f4f83781191007b6ef43b03debc35435f10cad9b96e16d147efe84a1d48bdde4", size = 803241 }, + { url = "https://files.pythonhosted.org/packages/2a/5c/83e3b1d89fa4f6e5a1bc97b4abd4a9a97b3c1ac7854164f694f5f0ba98a0/regex-2026.4.4-cp313-cp313t-win32.whl", hash = "sha256:e014a797de43d1847df957c0a2a8e861d1c17547ee08467d1db2c370b7568baa", size = 269921 }, + { url = "https://files.pythonhosted.org/packages/28/07/077c387121f42cdb4d92b1301133c0d93b5709d096d1669ab847dda9fe2e/regex-2026.4.4-cp313-cp313t-win_amd64.whl", hash = "sha256:b15b88b0d52b179712632832c1d6e58e5774f93717849a41096880442da41ab0", size = 281240 }, + { url = "https://files.pythonhosted.org/packages/9d/22/ead4a4abc7c59a4d882662aa292ca02c8b617f30b6e163bc1728879e9353/regex-2026.4.4-cp313-cp313t-win_arm64.whl", hash = "sha256:586b89cdadf7d67bf86ae3342a4dcd2b8d70a832d90c18a0ae955105caf34dbe", size = 272440 }, ] [[package]] @@ -1415,9 +1476,9 @@ dependencies = [ { name = "idna" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/34/64/8860370b167a9721e8956ae116825caff829224fbca0ca6e7bf8ddef8430/requests-2.33.0.tar.gz", hash = "sha256:c7ebc5e8b0f21837386ad0e1c8fe8b829fa5f544d8df3b2253bff14ef29d7652", size = 134232, upload-time = "2026-03-25T15:10:41.586Z" } +sdist = { url = "https://files.pythonhosted.org/packages/34/64/8860370b167a9721e8956ae116825caff829224fbca0ca6e7bf8ddef8430/requests-2.33.0.tar.gz", hash = "sha256:c7ebc5e8b0f21837386ad0e1c8fe8b829fa5f544d8df3b2253bff14ef29d7652", size = 134232 } wheels = [ - { url = "https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl", hash = "sha256:3324635456fa185245e24865e810cecec7b4caf933d7eb133dcde67d48cee69b", size = 65017, upload-time = "2026-03-25T15:10:40.382Z" }, + { url = "https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl", hash = "sha256:3324635456fa185245e24865e810cecec7b4caf933d7eb133dcde67d48cee69b", size = 65017 }, ] [[package]] @@ -1427,9 +1488,9 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "packaging" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/95/96/fb6dbfebb524d5601d359a47c78fe7ba1eef90fc4096404aa60c9a906fbb/requirements_parser-0.13.0.tar.gz", hash = "sha256:0843119ca2cb2331de4eb31b10d70462e39ace698fd660a915c247d2301a4418", size = 22630, upload-time = "2025-05-21T13:42:05.464Z" } +sdist = { url = "https://files.pythonhosted.org/packages/95/96/fb6dbfebb524d5601d359a47c78fe7ba1eef90fc4096404aa60c9a906fbb/requirements_parser-0.13.0.tar.gz", hash = "sha256:0843119ca2cb2331de4eb31b10d70462e39ace698fd660a915c247d2301a4418", size = 22630 } wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/60/50fbb6ffb35f733654466f1a90d162bcbea358adc3b0871339254fbc37b2/requirements_parser-0.13.0-py3-none-any.whl", hash = "sha256:2b3173faecf19ec5501971b7222d38f04cb45bb9d87d0ad629ca71e2e62ded14", size = 14782, upload-time = "2025-05-21T13:42:04.007Z" }, + { url = "https://files.pythonhosted.org/packages/bd/60/50fbb6ffb35f733654466f1a90d162bcbea358adc3b0871339254fbc37b2/requirements_parser-0.13.0-py3-none-any.whl", hash = "sha256:2b3173faecf19ec5501971b7222d38f04cb45bb9d87d0ad629ca71e2e62ded14", size = 14782 }, ] [[package]] @@ -1440,9 +1501,9 @@ dependencies = [ { name = "markdown-it-py" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680 } wheels = [ - { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" }, + { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654 }, ] [[package]] @@ -1452,141 +1513,175 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "rich" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/f7/1c65e0245d4c7009a87ac92908294a66e7e7635eccf76a68550f40c6df80/rich_argparse-1.7.2.tar.gz", hash = "sha256:64fd2e948fc96e8a1a06e0e72c111c2ce7f3af74126d75c0f5f63926e7289cd1", size = 38500, upload-time = "2025-11-01T10:35:44.232Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/f7/1c65e0245d4c7009a87ac92908294a66e7e7635eccf76a68550f40c6df80/rich_argparse-1.7.2.tar.gz", hash = "sha256:64fd2e948fc96e8a1a06e0e72c111c2ce7f3af74126d75c0f5f63926e7289cd1", size = 38500 } wheels = [ - { url = "https://files.pythonhosted.org/packages/04/80/97b6f357ac458d9ad9872cc3183ca09ef7439ac89e030ea43053ba1294b6/rich_argparse-1.7.2-py3-none-any.whl", hash = "sha256:0559b1f47a19bbeb82bf15f95a057f99bcbbc98385532f57937f9fc57acc501a", size = 25476, upload-time = "2025-11-01T10:35:42.681Z" }, + { url = "https://files.pythonhosted.org/packages/04/80/97b6f357ac458d9ad9872cc3183ca09ef7439ac89e030ea43053ba1294b6/rich_argparse-1.7.2-py3-none-any.whl", hash = "sha256:0559b1f47a19bbeb82bf15f95a057f99bcbbc98385532f57937f9fc57acc501a", size = 25476 }, ] [[package]] name = "rpds-py" version = "0.30.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086, upload-time = "2025-11-30T20:22:17.93Z" }, - { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053, upload-time = "2025-11-30T20:22:19.297Z" }, - { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763, upload-time = "2025-11-30T20:22:21.661Z" }, - { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951, upload-time = "2025-11-30T20:22:23.408Z" }, - { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622, upload-time = "2025-11-30T20:22:25.16Z" }, - { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492, upload-time = "2025-11-30T20:22:26.505Z" }, - { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080, upload-time = "2025-11-30T20:22:27.934Z" }, - { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680, upload-time = "2025-11-30T20:22:29.341Z" }, - { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589, upload-time = "2025-11-30T20:22:31.469Z" }, - { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289, upload-time = "2025-11-30T20:22:32.997Z" }, - { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737, upload-time = "2025-11-30T20:22:34.419Z" }, - { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120, upload-time = "2025-11-30T20:22:35.903Z" }, - { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782, upload-time = "2025-11-30T20:22:37.271Z" }, - { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463, upload-time = "2025-11-30T20:22:39.021Z" }, - { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868, upload-time = "2025-11-30T20:22:40.493Z" }, - { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" }, - { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" }, - { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" }, - { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" }, - { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" }, - { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589, upload-time = "2025-11-30T20:22:48.872Z" }, - { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025, upload-time = "2025-11-30T20:22:50.196Z" }, - { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895, upload-time = "2025-11-30T20:22:51.87Z" }, - { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799, upload-time = "2025-11-30T20:22:53.341Z" }, - { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" }, - { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" }, - { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" }, - { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" }, - { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" }, - { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" }, - { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" }, - { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" }, - { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" }, - { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" }, - { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" }, - { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149, upload-time = "2025-11-30T20:23:12.864Z" }, - { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843, upload-time = "2025-11-30T20:23:14.638Z" }, - { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507, upload-time = "2025-11-30T20:23:16.105Z" }, - { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949, upload-time = "2025-11-30T20:23:17.539Z" }, - { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" }, - { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" }, - { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" }, - { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" }, - { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086 }, + { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053 }, + { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763 }, + { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951 }, + { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622 }, + { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492 }, + { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080 }, + { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680 }, + { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589 }, + { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289 }, + { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737 }, + { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120 }, + { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782 }, + { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463 }, + { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868 }, + { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887 }, + { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904 }, + { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945 }, + { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783 }, + { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021 }, + { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589 }, + { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025 }, + { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895 }, + { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799 }, + { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731 }, + { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027 }, + { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020 }, + { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139 }, + { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224 }, + { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645 }, + { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443 }, + { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375 }, + { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850 }, + { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812 }, + { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841 }, + { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149 }, + { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843 }, + { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507 }, + { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949 }, + { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790 }, + { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217 }, + { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806 }, + { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341 }, + { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768 }, +] + +[[package]] +name = "ruamel-yaml" +version = "0.19.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/3b/ebda527b56beb90cb7652cb1c7e4f91f48649fbcd8d2eb2fb6e77cd3329b/ruamel_yaml-0.19.1.tar.gz", hash = "sha256:53eb66cd27849eff968ebf8f0bf61f46cdac2da1d1f3576dd4ccee9b25c31993", size = 142709 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b8/0c/51f6841f1d84f404f92463fc2b1ba0da357ca1e3db6b7fbda26956c3b82a/ruamel_yaml-0.19.1-py3-none-any.whl", hash = "sha256:27592957fedf6e0b62f281e96effd28043345e0e66001f97683aa9a40c667c93", size = 118102 }, +] + +[[package]] +name = "ruff" +version = "0.15.20" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/43/dc/35b341fc554ba02f217fc10da57d1a75168cfbcf75b0ef2202176d4c4f2d/ruff-0.15.20.tar.gz", hash = "sha256:1416eb04349192646b54de98f146c4f59afe37d0decfc02c3cbbf396f3a28566", size = 4755489 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/d9/2d5014f0253ba541d2061d9fa7193f48e941c8b21bb88a7ff9bbe0bd0596/ruff-0.15.20-py3-none-linux_armv6l.whl", hash = "sha256:00e188c53e499c3c1637f73c91dcf2fb56d576cab76ce1be50a27c4e80e37078", size = 10839665 }, + { url = "https://files.pythonhosted.org/packages/c6/d3/ac1798ba64f670698867fcfc591d50e7e421bef137db564858f619a30fcf/ruff-0.15.20-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:9ebd1fd9b9c95fc0bd7b2761aebec1f030013d2e193a2901b224af68fe47251b", size = 11208649 }, + { url = "https://files.pythonhosted.org/packages/47/47/d3ac899991202095dfcf3d5176be4272642be3cf981a2f1a30f72a2afb95/ruff-0.15.20-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c5b16cdd67ca108185cd36dce98c576350c03b1660a751de725fb049193a0632", size = 10622638 }, + { url = "https://files.pythonhosted.org/packages/33/13/4e043fe30aa94d4ff5213a9881fc296d12960f5971b234a5263fdc225312/ruff-0.15.20-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3413bb3c3d2ca6a8208f1f4809cd2dca3c6de6d0b491c0e70847672bde6e6efd", size = 10984227 }, + { url = "https://files.pythonhosted.org/packages/76/e6/92e7bf40388bc5800073b96564f56264f7e48bfd1a498f5ced6ae6d5a769/ruff-0.15.20-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd7ec42b3bb3da066488db093308a69c4ac5ee6d2af333a86ba6e2eb2e7dd44b", size = 10622882 }, + { url = "https://files.pythonhosted.org/packages/13/7a/43460be3f24495a3aa46d4b16873e2c4941b3b5f0b00cf88c03b7b94b339/ruff-0.15.20-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1a36ad0eb77fba9aabfb69ede54de6f376d04ac18ebea022847046d340a8267", size = 11474808 }, + { url = "https://files.pythonhosted.org/packages/27/a0/f37077884873221c6b33b4ab49eb18f9f88e54a16a25a5bca59bef46dd66/ruff-0.15.20-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b6df3b1e4610432f0386dba04d853b5f08cbbc903410c6fcc02f620f05aff53c", size = 12293094 }, + { url = "https://files.pythonhosted.org/packages/a6/74/165545b60256a9704c21ac0ec4a0d07933b320812f9584836c9f4aca4292/ruff-0.15.20-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e89f198a1ea6ef0d727c1cf16088bc91a6cb0ab947dedc966715691647186eae", size = 11526176 }, + { url = "https://files.pythonhosted.org/packages/86/b1/a976a136d40ade83ce743578399865f57001003a409acadc0ecbb3051082/ruff-0.15.20-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309809086c2acb67624950a3c8133e80f32d0d3e27106c0cd60ff26657c9f24b", size = 11520767 }, + { url = "https://files.pythonhosted.org/packages/19/0f/f032696cb01c9b54c0263fa393474d7758f1cdc021a01b04e3cbc2500999/ruff-0.15.20-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:2d2374caa2f2c2f9e2b7da0a50802cfb8b79f55a9b5e49379f564544fbf56487", size = 11500132 }, + { url = "https://files.pythonhosted.org/packages/4b/f4/51b1a14bc69e8c224b15dab9cce8e99b425e0455d462caa2b3c9be2b6a8e/ruff-0.15.20-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a1ed17b65293e0c2f22fc387bc13198a5de94bf4429589b0ff6946b0feaf21a3", size = 10943828 }, + { url = "https://files.pythonhosted.org/packages/71/4b/fe267640783cd02bf6c5cc290b1df1051be2ec294c678b5c15fe19e52343/ruff-0.15.20-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:f701305e66b38ea6c91882490eb73459796808e4c6362a1b765255e0cdcd4053", size = 10645418 }, + { url = "https://files.pythonhosted.org/packages/b0/c0/a65aa4ec2f5e87a1df32dc3ec1fede434fe3dfd5cbcf3b503cafc676ab54/ruff-0.15.20-py3-none-musllinux_1_2_i686.whl", hash = "sha256:5b9c0c367ad8e5d0d5b5b8537864c469a0a0e55417aadfbeca41fa61333be9f4", size = 11211770 }, + { url = "https://files.pythonhosted.org/packages/5a/a4/0caa331d954ae2723d729d351c989cb4ca8b6077d5c6c2cb6de75e98c041/ruff-0.15.20-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:01cc00dd58f0df339d0e902219dd53990ea99996a0344e5d9cc8d45d5307e460", size = 11618698 }, + { url = "https://files.pythonhosted.org/packages/10/9b/5f14927848d2fd4aa891fd88d883788c5a7baba561c7874732364045708c/ruff-0.15.20-py3-none-win32.whl", hash = "sha256:ed65ef510e43a137207e0f01cfcf998aeddb1aeeda5c9d35023e910284d7cf21", size = 10857322 }, + { url = "https://files.pythonhosted.org/packages/fa/f0/fe47c501f9dea92a26d788ff98bb5d92ed4cb4c88792c5c88af6b697dc8e/ruff-0.15.20-py3-none-win_amd64.whl", hash = "sha256:a525c81c70fb0380344dd1d8745d8cc1c890b7fc94a58d5a07bd8eb9557b8415", size = 11993274 }, + { url = "https://files.pythonhosted.org/packages/d7/2b/9555445e1201d92b3195f45cdb153a0b68f24e0a4273f6e3d5ab46e212bb/ruff-0.15.20-py3-none-win_arm64.whl", hash = "sha256:2f5b2a6d614e8700388806a14996c40fab2c47b819ef57d790a34878858ed9ca", size = 11343498 }, ] [[package]] name = "semver" version = "3.0.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/d1/d3159231aec234a59dd7d601e9dd9fe96f3afff15efd33c1070019b26132/semver-3.0.4.tar.gz", hash = "sha256:afc7d8c584a5ed0a11033af086e8af226a9c0b206f313e0301f8dd7b6b589602", size = 269730, upload-time = "2025-01-24T13:19:27.617Z" } +sdist = { url = "https://files.pythonhosted.org/packages/72/d1/d3159231aec234a59dd7d601e9dd9fe96f3afff15efd33c1070019b26132/semver-3.0.4.tar.gz", hash = "sha256:afc7d8c584a5ed0a11033af086e8af226a9c0b206f313e0301f8dd7b6b589602", size = 269730 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a6/24/4d91e05817e92e3a61c8a21e08fd0f390f5301f1c448b137c57c4bc6e543/semver-3.0.4-py3-none-any.whl", hash = "sha256:9c824d87ba7f7ab4a1890799cec8596f15c1241cb473404ea1cb0c55e4b04746", size = 17912, upload-time = "2025-01-24T13:19:24.949Z" }, + { url = "https://files.pythonhosted.org/packages/a6/24/4d91e05817e92e3a61c8a21e08fd0f390f5301f1c448b137c57c4bc6e543/semver-3.0.4-py3-none-any.whl", hash = "sha256:9c824d87ba7f7ab4a1890799cec8596f15c1241cb473404ea1cb0c55e4b04746", size = 17912 }, ] [[package]] name = "setuptools" version = "81.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0d/1c/73e719955c59b8e424d015ab450f51c0af856ae46ea2da83eba51cc88de1/setuptools-81.0.0.tar.gz", hash = "sha256:487b53915f52501f0a79ccfd0c02c165ffe06631443a886740b91af4b7a5845a", size = 1198299, upload-time = "2026-02-06T21:10:39.601Z" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/1c/73e719955c59b8e424d015ab450f51c0af856ae46ea2da83eba51cc88de1/setuptools-81.0.0.tar.gz", hash = "sha256:487b53915f52501f0a79ccfd0c02c165ffe06631443a886740b91af4b7a5845a", size = 1198299 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/e3/c164c88b2e5ce7b24d667b9bd83589cf4f3520d97cad01534cd3c4f55fdb/setuptools-81.0.0-py3-none-any.whl", hash = "sha256:fdd925d5c5d9f62e4b74b30d6dd7828ce236fd6ed998a08d81de62ce5a6310d6", size = 1062021, upload-time = "2026-02-06T21:10:37.175Z" }, + { url = "https://files.pythonhosted.org/packages/e1/e3/c164c88b2e5ce7b24d667b9bd83589cf4f3520d97cad01534cd3c4f55fdb/setuptools-81.0.0-py3-none-any.whl", hash = "sha256:fdd925d5c5d9f62e4b74b30d6dd7828ce236fd6ed998a08d81de62ce5a6310d6", size = 1062021 }, ] [[package]] name = "shellingham" version = "1.5.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310, upload-time = "2023-10-24T04:13:40.426Z" } +sdist = { url = "https://files.pythonhosted.org/packages/58/15/8b3609fd3830ef7b27b655beb4b4e9c62313a4e8da8c676e142cc210d58e/shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de", size = 10310 } wheels = [ - { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755, upload-time = "2023-10-24T04:13:38.866Z" }, + { url = "https://files.pythonhosted.org/packages/e0/f9/0595336914c5619e5f28a1fb793285925a8cd4b432c9da0a987836c7f822/shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686", size = 9755 }, ] [[package]] name = "simplejson" version = "3.20.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/41/f4/a1ac5ed32f7ed9a088d62a59d410d4c204b3b3815722e2ccfb491fa8251b/simplejson-3.20.2.tar.gz", hash = "sha256:5fe7a6ce14d1c300d80d08695b7f7e633de6cd72c80644021874d985b3393649", size = 85784, upload-time = "2025-09-26T16:29:36.64Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9d/9e/1a91e7614db0416885eab4136d49b7303de20528860ffdd798ce04d054db/simplejson-3.20.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4376d5acae0d1e91e78baeba4ee3cf22fbf6509d81539d01b94e0951d28ec2b6", size = 93523, upload-time = "2025-09-26T16:28:00.356Z" }, - { url = "https://files.pythonhosted.org/packages/5e/2b/d2413f5218fc25608739e3d63fe321dfa85c5f097aa6648dbe72513a5f12/simplejson-3.20.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f8fe6de652fcddae6dec8f281cc1e77e4e8f3575249e1800090aab48f73b4259", size = 75844, upload-time = "2025-09-26T16:28:01.756Z" }, - { url = "https://files.pythonhosted.org/packages/ad/f1/efd09efcc1e26629e120fef59be059ce7841cc6e1f949a4db94f1ae8a918/simplejson-3.20.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25ca2663d99328d51e5a138f22018e54c9162438d831e26cfc3458688616eca8", size = 75655, upload-time = "2025-09-26T16:28:03.037Z" }, - { url = "https://files.pythonhosted.org/packages/97/ec/5c6db08e42f380f005d03944be1af1a6bd501cc641175429a1cbe7fb23b9/simplejson-3.20.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12a6b2816b6cab6c3fd273d43b1948bc9acf708272074c8858f579c394f4cbc9", size = 150335, upload-time = "2025-09-26T16:28:05.027Z" }, - { url = "https://files.pythonhosted.org/packages/81/f5/808a907485876a9242ec67054da7cbebefe0ee1522ef1c0be3bfc90f96f6/simplejson-3.20.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac20dc3fcdfc7b8415bfc3d7d51beccd8695c3f4acb7f74e3a3b538e76672868", size = 158519, upload-time = "2025-09-26T16:28:06.5Z" }, - { url = "https://files.pythonhosted.org/packages/66/af/b8a158246834645ea890c36136584b0cc1c0e4b83a73b11ebd9c2a12877c/simplejson-3.20.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db0804d04564e70862ef807f3e1ace2cc212ef0e22deb1b3d6f80c45e5882c6b", size = 148571, upload-time = "2025-09-26T16:28:07.715Z" }, - { url = "https://files.pythonhosted.org/packages/20/05/ed9b2571bbf38f1a2425391f18e3ac11cb1e91482c22d644a1640dea9da7/simplejson-3.20.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:979ce23ea663895ae39106946ef3d78527822d918a136dbc77b9e2b7f006237e", size = 152367, upload-time = "2025-09-26T16:28:08.921Z" }, - { url = "https://files.pythonhosted.org/packages/81/2c/bad68b05dd43e93f77994b920505634d31ed239418eb6a88997d06599983/simplejson-3.20.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a2ba921b047bb029805726800819675249ef25d2f65fd0edb90639c5b1c3033c", size = 150205, upload-time = "2025-09-26T16:28:10.086Z" }, - { url = "https://files.pythonhosted.org/packages/69/46/90c7fc878061adafcf298ce60cecdee17a027486e9dce507e87396d68255/simplejson-3.20.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:12d3d4dc33770069b780cc8f5abef909fe4a3f071f18f55f6d896a370fd0f970", size = 151823, upload-time = "2025-09-26T16:28:11.329Z" }, - { url = "https://files.pythonhosted.org/packages/ab/27/b85b03349f825ae0f5d4f780cdde0bbccd4f06c3d8433f6a3882df887481/simplejson-3.20.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:aff032a59a201b3683a34be1169e71ddda683d9c3b43b261599c12055349251e", size = 158997, upload-time = "2025-09-26T16:28:12.917Z" }, - { url = "https://files.pythonhosted.org/packages/71/ad/d7f3c331fb930638420ac6d236db68e9f4c28dab9c03164c3cd0e7967e15/simplejson-3.20.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:30e590e133b06773f0dc9c3f82e567463df40598b660b5adf53eb1c488202544", size = 154367, upload-time = "2025-09-26T16:28:14.393Z" }, - { url = "https://files.pythonhosted.org/packages/f0/46/5c67324addd40fa2966f6e886cacbbe0407c03a500db94fb8bb40333fcdf/simplejson-3.20.2-cp312-cp312-win32.whl", hash = "sha256:8d7be7c99939cc58e7c5bcf6bb52a842a58e6c65e1e9cdd2a94b697b24cddb54", size = 74285, upload-time = "2025-09-26T16:28:15.931Z" }, - { url = "https://files.pythonhosted.org/packages/fa/c9/5cc2189f4acd3a6e30ffa9775bf09b354302dbebab713ca914d7134d0f29/simplejson-3.20.2-cp312-cp312-win_amd64.whl", hash = "sha256:2c0b4a67e75b945489052af6590e7dca0ed473ead5d0f3aad61fa584afe814ab", size = 75969, upload-time = "2025-09-26T16:28:17.017Z" }, - { url = "https://files.pythonhosted.org/packages/5e/9e/f326d43f6bf47f4e7704a4426c36e044c6bedfd24e072fb8e27589a373a5/simplejson-3.20.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:90d311ba8fcd733a3677e0be21804827226a57144130ba01c3c6a325e887dd86", size = 93530, upload-time = "2025-09-26T16:28:18.07Z" }, - { url = "https://files.pythonhosted.org/packages/35/28/5a4b8f3483fbfb68f3f460bc002cef3a5735ef30950e7c4adce9c8da15c7/simplejson-3.20.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:feed6806f614bdf7f5cb6d0123cb0c1c5f40407ef103aa935cffaa694e2e0c74", size = 75846, upload-time = "2025-09-26T16:28:19.12Z" }, - { url = "https://files.pythonhosted.org/packages/7a/4d/30dfef83b9ac48afae1cf1ab19c2867e27b8d22b5d9f8ca7ce5a0a157d8c/simplejson-3.20.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6b1d8d7c3e1a205c49e1aee6ba907dcb8ccea83651e6c3e2cb2062f1e52b0726", size = 75661, upload-time = "2025-09-26T16:28:20.219Z" }, - { url = "https://files.pythonhosted.org/packages/09/1d/171009bd35c7099d72ef6afd4bb13527bab469965c968a17d69a203d62a6/simplejson-3.20.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:552f55745044a24c3cb7ec67e54234be56d5d6d0e054f2e4cf4fb3e297429be5", size = 150579, upload-time = "2025-09-26T16:28:21.337Z" }, - { url = "https://files.pythonhosted.org/packages/61/ae/229bbcf90a702adc6bfa476e9f0a37e21d8c58e1059043038797cbe75b8c/simplejson-3.20.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2da97ac65165d66b0570c9e545786f0ac7b5de5854d3711a16cacbcaa8c472d", size = 158797, upload-time = "2025-09-26T16:28:22.53Z" }, - { url = "https://files.pythonhosted.org/packages/90/c5/fefc0ac6b86b9108e302e0af1cf57518f46da0baedd60a12170791d56959/simplejson-3.20.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f59a12966daa356bf68927fca5a67bebac0033cd18b96de9c2d426cd11756cd0", size = 148851, upload-time = "2025-09-26T16:28:23.733Z" }, - { url = "https://files.pythonhosted.org/packages/43/f1/b392952200f3393bb06fbc4dd975fc63a6843261705839355560b7264eb2/simplejson-3.20.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133ae2098a8e162c71da97cdab1f383afdd91373b7ff5fe65169b04167da976b", size = 152598, upload-time = "2025-09-26T16:28:24.962Z" }, - { url = "https://files.pythonhosted.org/packages/f4/b4/d6b7279e52a3e9c0fa8c032ce6164e593e8d9cf390698ee981ed0864291b/simplejson-3.20.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7977640af7b7d5e6a852d26622057d428706a550f7f5083e7c4dd010a84d941f", size = 150498, upload-time = "2025-09-26T16:28:26.114Z" }, - { url = "https://files.pythonhosted.org/packages/62/22/ec2490dd859224326d10c2fac1353e8ad5c84121be4837a6dd6638ba4345/simplejson-3.20.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b530ad6d55e71fa9e93e1109cf8182f427a6355848a4ffa09f69cc44e1512522", size = 152129, upload-time = "2025-09-26T16:28:27.552Z" }, - { url = "https://files.pythonhosted.org/packages/33/ce/b60214d013e93dd9e5a705dcb2b88b6c72bada442a97f79828332217f3eb/simplejson-3.20.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bd96a7d981bf64f0e42345584768da4435c05b24fd3c364663f5fbc8fabf82e3", size = 159359, upload-time = "2025-09-26T16:28:28.667Z" }, - { url = "https://files.pythonhosted.org/packages/99/21/603709455827cdf5b9d83abe726343f542491ca8dc6a2528eb08de0cf034/simplejson-3.20.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f28ee755fadb426ba2e464d6fcf25d3f152a05eb6b38e0b4f790352f5540c769", size = 154717, upload-time = "2025-09-26T16:28:30.288Z" }, - { url = "https://files.pythonhosted.org/packages/3c/f9/dc7f7a4bac16cf7eb55a4df03ad93190e11826d2a8950052949d3dfc11e2/simplejson-3.20.2-cp313-cp313-win32.whl", hash = "sha256:472785b52e48e3eed9b78b95e26a256f59bb1ee38339be3075dad799e2e1e661", size = 74289, upload-time = "2025-09-26T16:28:31.809Z" }, - { url = "https://files.pythonhosted.org/packages/87/10/d42ad61230436735c68af1120622b28a782877146a83d714da7b6a2a1c4e/simplejson-3.20.2-cp313-cp313-win_amd64.whl", hash = "sha256:a1a85013eb33e4820286139540accbe2c98d2da894b2dcefd280209db508e608", size = 75972, upload-time = "2025-09-26T16:28:32.883Z" }, - { url = "https://files.pythonhosted.org/packages/05/5b/83e1ff87eb60ca706972f7e02e15c0b33396e7bdbd080069a5d1b53cf0d8/simplejson-3.20.2-py3-none-any.whl", hash = "sha256:3b6bb7fb96efd673eac2e4235200bfffdc2353ad12c54117e1e4e2fc485ac017", size = 57309, upload-time = "2025-09-26T16:29:35.312Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/41/f4/a1ac5ed32f7ed9a088d62a59d410d4c204b3b3815722e2ccfb491fa8251b/simplejson-3.20.2.tar.gz", hash = "sha256:5fe7a6ce14d1c300d80d08695b7f7e633de6cd72c80644021874d985b3393649", size = 85784 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/9e/1a91e7614db0416885eab4136d49b7303de20528860ffdd798ce04d054db/simplejson-3.20.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4376d5acae0d1e91e78baeba4ee3cf22fbf6509d81539d01b94e0951d28ec2b6", size = 93523 }, + { url = "https://files.pythonhosted.org/packages/5e/2b/d2413f5218fc25608739e3d63fe321dfa85c5f097aa6648dbe72513a5f12/simplejson-3.20.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f8fe6de652fcddae6dec8f281cc1e77e4e8f3575249e1800090aab48f73b4259", size = 75844 }, + { url = "https://files.pythonhosted.org/packages/ad/f1/efd09efcc1e26629e120fef59be059ce7841cc6e1f949a4db94f1ae8a918/simplejson-3.20.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25ca2663d99328d51e5a138f22018e54c9162438d831e26cfc3458688616eca8", size = 75655 }, + { url = "https://files.pythonhosted.org/packages/97/ec/5c6db08e42f380f005d03944be1af1a6bd501cc641175429a1cbe7fb23b9/simplejson-3.20.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12a6b2816b6cab6c3fd273d43b1948bc9acf708272074c8858f579c394f4cbc9", size = 150335 }, + { url = "https://files.pythonhosted.org/packages/81/f5/808a907485876a9242ec67054da7cbebefe0ee1522ef1c0be3bfc90f96f6/simplejson-3.20.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac20dc3fcdfc7b8415bfc3d7d51beccd8695c3f4acb7f74e3a3b538e76672868", size = 158519 }, + { url = "https://files.pythonhosted.org/packages/66/af/b8a158246834645ea890c36136584b0cc1c0e4b83a73b11ebd9c2a12877c/simplejson-3.20.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db0804d04564e70862ef807f3e1ace2cc212ef0e22deb1b3d6f80c45e5882c6b", size = 148571 }, + { url = "https://files.pythonhosted.org/packages/20/05/ed9b2571bbf38f1a2425391f18e3ac11cb1e91482c22d644a1640dea9da7/simplejson-3.20.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:979ce23ea663895ae39106946ef3d78527822d918a136dbc77b9e2b7f006237e", size = 152367 }, + { url = "https://files.pythonhosted.org/packages/81/2c/bad68b05dd43e93f77994b920505634d31ed239418eb6a88997d06599983/simplejson-3.20.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a2ba921b047bb029805726800819675249ef25d2f65fd0edb90639c5b1c3033c", size = 150205 }, + { url = "https://files.pythonhosted.org/packages/69/46/90c7fc878061adafcf298ce60cecdee17a027486e9dce507e87396d68255/simplejson-3.20.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:12d3d4dc33770069b780cc8f5abef909fe4a3f071f18f55f6d896a370fd0f970", size = 151823 }, + { url = "https://files.pythonhosted.org/packages/ab/27/b85b03349f825ae0f5d4f780cdde0bbccd4f06c3d8433f6a3882df887481/simplejson-3.20.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:aff032a59a201b3683a34be1169e71ddda683d9c3b43b261599c12055349251e", size = 158997 }, + { url = "https://files.pythonhosted.org/packages/71/ad/d7f3c331fb930638420ac6d236db68e9f4c28dab9c03164c3cd0e7967e15/simplejson-3.20.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:30e590e133b06773f0dc9c3f82e567463df40598b660b5adf53eb1c488202544", size = 154367 }, + { url = "https://files.pythonhosted.org/packages/f0/46/5c67324addd40fa2966f6e886cacbbe0407c03a500db94fb8bb40333fcdf/simplejson-3.20.2-cp312-cp312-win32.whl", hash = "sha256:8d7be7c99939cc58e7c5bcf6bb52a842a58e6c65e1e9cdd2a94b697b24cddb54", size = 74285 }, + { url = "https://files.pythonhosted.org/packages/fa/c9/5cc2189f4acd3a6e30ffa9775bf09b354302dbebab713ca914d7134d0f29/simplejson-3.20.2-cp312-cp312-win_amd64.whl", hash = "sha256:2c0b4a67e75b945489052af6590e7dca0ed473ead5d0f3aad61fa584afe814ab", size = 75969 }, + { url = "https://files.pythonhosted.org/packages/5e/9e/f326d43f6bf47f4e7704a4426c36e044c6bedfd24e072fb8e27589a373a5/simplejson-3.20.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:90d311ba8fcd733a3677e0be21804827226a57144130ba01c3c6a325e887dd86", size = 93530 }, + { url = "https://files.pythonhosted.org/packages/35/28/5a4b8f3483fbfb68f3f460bc002cef3a5735ef30950e7c4adce9c8da15c7/simplejson-3.20.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:feed6806f614bdf7f5cb6d0123cb0c1c5f40407ef103aa935cffaa694e2e0c74", size = 75846 }, + { url = "https://files.pythonhosted.org/packages/7a/4d/30dfef83b9ac48afae1cf1ab19c2867e27b8d22b5d9f8ca7ce5a0a157d8c/simplejson-3.20.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6b1d8d7c3e1a205c49e1aee6ba907dcb8ccea83651e6c3e2cb2062f1e52b0726", size = 75661 }, + { url = "https://files.pythonhosted.org/packages/09/1d/171009bd35c7099d72ef6afd4bb13527bab469965c968a17d69a203d62a6/simplejson-3.20.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:552f55745044a24c3cb7ec67e54234be56d5d6d0e054f2e4cf4fb3e297429be5", size = 150579 }, + { url = "https://files.pythonhosted.org/packages/61/ae/229bbcf90a702adc6bfa476e9f0a37e21d8c58e1059043038797cbe75b8c/simplejson-3.20.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c2da97ac65165d66b0570c9e545786f0ac7b5de5854d3711a16cacbcaa8c472d", size = 158797 }, + { url = "https://files.pythonhosted.org/packages/90/c5/fefc0ac6b86b9108e302e0af1cf57518f46da0baedd60a12170791d56959/simplejson-3.20.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f59a12966daa356bf68927fca5a67bebac0033cd18b96de9c2d426cd11756cd0", size = 148851 }, + { url = "https://files.pythonhosted.org/packages/43/f1/b392952200f3393bb06fbc4dd975fc63a6843261705839355560b7264eb2/simplejson-3.20.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:133ae2098a8e162c71da97cdab1f383afdd91373b7ff5fe65169b04167da976b", size = 152598 }, + { url = "https://files.pythonhosted.org/packages/f4/b4/d6b7279e52a3e9c0fa8c032ce6164e593e8d9cf390698ee981ed0864291b/simplejson-3.20.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7977640af7b7d5e6a852d26622057d428706a550f7f5083e7c4dd010a84d941f", size = 150498 }, + { url = "https://files.pythonhosted.org/packages/62/22/ec2490dd859224326d10c2fac1353e8ad5c84121be4837a6dd6638ba4345/simplejson-3.20.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b530ad6d55e71fa9e93e1109cf8182f427a6355848a4ffa09f69cc44e1512522", size = 152129 }, + { url = "https://files.pythonhosted.org/packages/33/ce/b60214d013e93dd9e5a705dcb2b88b6c72bada442a97f79828332217f3eb/simplejson-3.20.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bd96a7d981bf64f0e42345584768da4435c05b24fd3c364663f5fbc8fabf82e3", size = 159359 }, + { url = "https://files.pythonhosted.org/packages/99/21/603709455827cdf5b9d83abe726343f542491ca8dc6a2528eb08de0cf034/simplejson-3.20.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f28ee755fadb426ba2e464d6fcf25d3f152a05eb6b38e0b4f790352f5540c769", size = 154717 }, + { url = "https://files.pythonhosted.org/packages/3c/f9/dc7f7a4bac16cf7eb55a4df03ad93190e11826d2a8950052949d3dfc11e2/simplejson-3.20.2-cp313-cp313-win32.whl", hash = "sha256:472785b52e48e3eed9b78b95e26a256f59bb1ee38339be3075dad799e2e1e661", size = 74289 }, + { url = "https://files.pythonhosted.org/packages/87/10/d42ad61230436735c68af1120622b28a782877146a83d714da7b6a2a1c4e/simplejson-3.20.2-cp313-cp313-win_amd64.whl", hash = "sha256:a1a85013eb33e4820286139540accbe2c98d2da894b2dcefd280209db508e608", size = 75972 }, + { url = "https://files.pythonhosted.org/packages/05/5b/83e1ff87eb60ca706972f7e02e15c0b33396e7bdbd080069a5d1b53cf0d8/simplejson-3.20.2-py3-none-any.whl", hash = "sha256:3b6bb7fb96efd673eac2e4235200bfffdc2353ad12c54117e1e4e2fc485ac017", size = 57309 }, ] [[package]] name = "six" version = "1.17.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } +sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, + { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 }, ] [[package]] name = "smmap" version = "5.0.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/1f/ea/49c993d6dfdd7338c9b1000a0f36817ed7ec84577ae2e52f890d1a4ff909/smmap-5.0.3.tar.gz", hash = "sha256:4d9debb8b99007ae47165abc08670bd74cb74b5227dda7f643eccc4e9eb5642c", size = 22506, upload-time = "2026-03-09T03:43:26.1Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1f/ea/49c993d6dfdd7338c9b1000a0f36817ed7ec84577ae2e52f890d1a4ff909/smmap-5.0.3.tar.gz", hash = "sha256:4d9debb8b99007ae47165abc08670bd74cb74b5227dda7f643eccc4e9eb5642c", size = 22506 } wheels = [ - { url = "https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl", hash = "sha256:c106e05d5a61449cf6ba9a1e650227ecfb141590d2a98412103ff35d89fc7b2f", size = 24390, upload-time = "2026-03-09T03:43:24.361Z" }, + { url = "https://files.pythonhosted.org/packages/c1/d4/59e74daffcb57a07668852eeeb6035af9f32cbfd7a1d2511f17d2fe6a738/smmap-5.0.3-py3-none-any.whl", hash = "sha256:c106e05d5a61449cf6ba9a1e650227ecfb141590d2a98412103ff35d89fc7b2f", size = 24390 }, ] [[package]] @@ -1597,27 +1692,27 @@ dependencies = [ { name = "requests" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ff/77/1ab6e5bafb9c80d8128f065a355377a04ac5b3c38eb719d920a9909d346e/snowplow_tracker-1.1.0.tar.gz", hash = "sha256:95d8fdc8bd542fd12a0b9a076852239cbaf0599eda8721deaf5f93f7138fe755", size = 34135, upload-time = "2025-02-21T10:58:48.112Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ff/77/1ab6e5bafb9c80d8128f065a355377a04ac5b3c38eb719d920a9909d346e/snowplow_tracker-1.1.0.tar.gz", hash = "sha256:95d8fdc8bd542fd12a0b9a076852239cbaf0599eda8721deaf5f93f7138fe755", size = 34135 } wheels = [ - { url = "https://files.pythonhosted.org/packages/78/10/1c76269cbf2d6e127f4415044d9ddb0295858230678bbf4bfba905593c82/snowplow_tracker-1.1.0-py3-none-any.whl", hash = "sha256:24ea32ddac9cca547421bf9ab162f5f33c00711c6ef118ad5f78093cee962224", size = 44128, upload-time = "2025-02-21T10:58:45.818Z" }, + { url = "https://files.pythonhosted.org/packages/78/10/1c76269cbf2d6e127f4415044d9ddb0295858230678bbf4bfba905593c82/snowplow_tracker-1.1.0-py3-none-any.whl", hash = "sha256:24ea32ddac9cca547421bf9ab162f5f33c00711c6ef118ad5f78093cee962224", size = 44128 }, ] [[package]] name = "sqlglot" version = "28.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/52/8d/9ce5904aca760b81adf821c77a1dcf07c98f9caaa7e3b5c991c541ff89d2/sqlglot-28.0.0.tar.gz", hash = "sha256:cc9a651ef4182e61dac58aa955e5fb21845a5865c6a4d7d7b5a7857450285ad4", size = 5520798, upload-time = "2025-11-17T10:34:57.016Z" } +sdist = { url = "https://files.pythonhosted.org/packages/52/8d/9ce5904aca760b81adf821c77a1dcf07c98f9caaa7e3b5c991c541ff89d2/sqlglot-28.0.0.tar.gz", hash = "sha256:cc9a651ef4182e61dac58aa955e5fb21845a5865c6a4d7d7b5a7857450285ad4", size = 5520798 } wheels = [ - { url = "https://files.pythonhosted.org/packages/56/6d/86de134f40199105d2fee1b066741aa870b3ce75ee74018d9c8508bbb182/sqlglot-28.0.0-py3-none-any.whl", hash = "sha256:ac1778e7fa4812f4f7e5881b260632fc167b00ca4c1226868891fb15467122e4", size = 536127, upload-time = "2025-11-17T10:34:55.192Z" }, + { url = "https://files.pythonhosted.org/packages/56/6d/86de134f40199105d2fee1b066741aa870b3ce75ee74018d9c8508bbb182/sqlglot-28.0.0-py3-none-any.whl", hash = "sha256:ac1778e7fa4812f4f7e5881b260632fc167b00ca4c1226868891fb15467122e4", size = 536127 }, ] [[package]] name = "sqlparse" version = "0.5.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/90/76/437d71068094df0726366574cf3432a4ed754217b436eb7429415cf2d480/sqlparse-0.5.5.tar.gz", hash = "sha256:e20d4a9b0b8585fdf63b10d30066c7c94c5d7a7ec47c889a2d83a3caa93ff28e", size = 120815, upload-time = "2025-12-19T07:17:45.073Z" } +sdist = { url = "https://files.pythonhosted.org/packages/90/76/437d71068094df0726366574cf3432a4ed754217b436eb7429415cf2d480/sqlparse-0.5.5.tar.gz", hash = "sha256:e20d4a9b0b8585fdf63b10d30066c7c94c5d7a7ec47c889a2d83a3caa93ff28e", size = 120815 } wheels = [ - { url = "https://files.pythonhosted.org/packages/49/4b/359f28a903c13438ef59ebeee215fb25da53066db67b305c125f1c6d2a25/sqlparse-0.5.5-py3-none-any.whl", hash = "sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba", size = 46138, upload-time = "2025-12-19T07:17:46.573Z" }, + { url = "https://files.pythonhosted.org/packages/49/4b/359f28a903c13438ef59ebeee215fb25da53066db67b305c125f1c6d2a25/sqlparse-0.5.5-py3-none-any.whl", hash = "sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba", size = 46138 }, ] [[package]] @@ -1627,60 +1722,69 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pytest" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/33/7e/a4793801683d32132d9683be8364e93f6bca2f277bfbe81647eba46b8cfd/syrupy-5.2.0.tar.gz", hash = "sha256:0e6b7abf1e04f060f6c797bed8bca96f2468954168328041e02634a68fc11ff0", size = 50223, upload-time = "2026-05-16T21:11:37.367Z" } +sdist = { url = "https://files.pythonhosted.org/packages/33/7e/a4793801683d32132d9683be8364e93f6bca2f277bfbe81647eba46b8cfd/syrupy-5.2.0.tar.gz", hash = "sha256:0e6b7abf1e04f060f6c797bed8bca96f2468954168328041e02634a68fc11ff0", size = 50223 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/18/dc99a152bea18a898a8ac387bfeb9ec0829e0f5bed11cfec2e2ca189c5a2/syrupy-5.2.0-py3-none-any.whl", hash = "sha256:798cb493a6e20f4839e58ea8f10eb1b0d85684c676442f79786e219bf32618e6", size = 51828 }, +] + +[[package]] +name = "tabulate" +version = "0.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/46/58/8c37dea7bbf769b20d58e7ace7e5edfe65b849442b00ffcdd56be88697c6/tabulate-0.10.0.tar.gz", hash = "sha256:e2cfde8f79420f6deeffdeda9aaec3b6bc5abce947655d17ac662b126e48a60d", size = 91754 } wheels = [ - { url = "https://files.pythonhosted.org/packages/4e/18/dc99a152bea18a898a8ac387bfeb9ec0829e0f5bed11cfec2e2ca189c5a2/syrupy-5.2.0-py3-none-any.whl", hash = "sha256:798cb493a6e20f4839e58ea8f10eb1b0d85684c676442f79786e219bf32618e6", size = 51828, upload-time = "2026-05-16T21:11:34.984Z" }, + { url = "https://files.pythonhosted.org/packages/99/55/db07de81b5c630da5cbf5c7df646580ca26dfaefa593667fc6f2fe016d2e/tabulate-0.10.0-py3-none-any.whl", hash = "sha256:f0b0622e567335c8fabaaa659f1b33bcb6ddfe2e496071b743aa113f8774f2d3", size = 39814 }, ] [[package]] name = "tenacity" version = "9.1.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/47/c6/ee486fd809e357697ee8a44d3d69222b344920433d3b6666ccd9b374630c/tenacity-9.1.4.tar.gz", hash = "sha256:adb31d4c263f2bd041081ab33b498309a57c77f9acf2db65aadf0898179cf93a", size = 49413, upload-time = "2026-02-07T10:45:33.841Z" } +sdist = { url = "https://files.pythonhosted.org/packages/47/c6/ee486fd809e357697ee8a44d3d69222b344920433d3b6666ccd9b374630c/tenacity-9.1.4.tar.gz", hash = "sha256:adb31d4c263f2bd041081ab33b498309a57c77f9acf2db65aadf0898179cf93a", size = 49413 } wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/c1/eb8f9debc45d3b7918a32ab756658a0904732f75e555402972246b0b8e71/tenacity-9.1.4-py3-none-any.whl", hash = "sha256:6095a360c919085f28c6527de529e76a06ad89b23659fa881ae0649b867a9d55", size = 28926, upload-time = "2026-02-07T10:45:32.24Z" }, + { url = "https://files.pythonhosted.org/packages/d7/c1/eb8f9debc45d3b7918a32ab756658a0904732f75e555402972246b0b8e71/tenacity-9.1.4-py3-none-any.whl", hash = "sha256:6095a360c919085f28c6527de529e76a06ad89b23659fa881ae0649b867a9d55", size = 28926 }, ] [[package]] name = "text-unidecode" version = "1.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/ab/e2/e9a00f0ccb71718418230718b3d900e71a5d16e701a3dae079a21e9cd8f8/text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93", size = 76885, upload-time = "2019-08-30T21:36:45.405Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ab/e2/e9a00f0ccb71718418230718b3d900e71a5d16e701a3dae079a21e9cd8f8/text-unidecode-1.3.tar.gz", hash = "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93", size = 76885 } wheels = [ - { url = "https://files.pythonhosted.org/packages/a6/a5/c0b6468d3824fe3fde30dbb5e1f687b291608f9473681bbf7dabbf5a87d7/text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", size = 78154, upload-time = "2019-08-30T21:37:03.543Z" }, + { url = "https://files.pythonhosted.org/packages/a6/a5/c0b6468d3824fe3fde30dbb5e1f687b291608f9473681bbf7dabbf5a87d7/text_unidecode-1.3-py2.py3-none-any.whl", hash = "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", size = 78154 }, ] [[package]] name = "tomlkit" version = "0.14.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c3/af/14b24e41977adb296d6bd1fb59402cf7d60ce364f90c890bd2ec65c43b5a/tomlkit-0.14.0.tar.gz", hash = "sha256:cf00efca415dbd57575befb1f6634c4f42d2d87dbba376128adb42c121b87064", size = 187167, upload-time = "2026-01-13T01:14:53.304Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/af/14b24e41977adb296d6bd1fb59402cf7d60ce364f90c890bd2ec65c43b5a/tomlkit-0.14.0.tar.gz", hash = "sha256:cf00efca415dbd57575befb1f6634c4f42d2d87dbba376128adb42c121b87064", size = 187167 } wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/11/87d6d29fb5d237229d67973a6c9e06e048f01cf4994dee194ab0ea841814/tomlkit-0.14.0-py3-none-any.whl", hash = "sha256:592064ed85b40fa213469f81ac584f67a4f2992509a7c3ea2d632208623a3680", size = 39310, upload-time = "2026-01-13T01:14:51.965Z" }, + { url = "https://files.pythonhosted.org/packages/b5/11/87d6d29fb5d237229d67973a6c9e06e048f01cf4994dee194ab0ea841814/tomlkit-0.14.0-py3-none-any.whl", hash = "sha256:592064ed85b40fa213469f81ac584f67a4f2992509a7c3ea2d632208623a3680", size = 39310 }, ] [[package]] name = "typer" -version = "0.25.0" +version = "0.27.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "annotated-doc" }, - { name = "click" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "rich" }, { name = "shellingham" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/7b/27/ede8cec7596e0041ba7e7b80b47d132562f56ff454313a16f6084e555c9f/typer-0.25.0.tar.gz", hash = "sha256:123eaf9f19bb40fd268310e12a542c0c6b4fab9c98d9d23342a01ff95e3ce930", size = 120150, upload-time = "2026-04-26T08:46:14.767Z" } +sdist = { url = "https://files.pythonhosted.org/packages/37/78/fda3361b56efc27944f24225f6ecd13d96d6fcfe37bd0eb34e2f4c63f9fc/typer-0.27.0.tar.gz", hash = "sha256:629bd12ea5d13a17148125d9a264f949eb171fb3f120f9b04d85873cab054fa5", size = 203430 } wheels = [ - { url = "https://files.pythonhosted.org/packages/9a/72/193d4e586ec5a4db834a36bbeb47641a62f951f114ffd0fe5b1b46e8d56f/typer-0.25.0-py3-none-any.whl", hash = "sha256:ac01b48823d3db9a83c9e164338057eadbb1c9957a2a6b4eeb486669c560b5dc", size = 55993, upload-time = "2026-04-26T08:46:15.889Z" }, + { url = "https://files.pythonhosted.org/packages/40/03/26a383c9e58c213199d1aad1c3d353cfc22d4444ec6d2c0bf8ad02523843/typer-0.27.0-py3-none-any.whl", hash = "sha256:6f4b27631e47f077871b7dc30e933ec0131c1390fbe0e387ea5574b5bac9ccf1", size = 122716 }, ] [[package]] name = "typing-extensions" version = "4.15.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391 } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614 }, ] [[package]] @@ -1690,68 +1794,68 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949 } wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611 }, ] [[package]] name = "tzdata" version = "2025.3" source = { registry = "https://pypi.org/simple" } -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" } +sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772 } wheels = [ - { 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" }, + { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521 }, ] [[package]] name = "urllib3" version = "2.6.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556 } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584 }, ] [[package]] name = "watchdog" version = "6.0.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220, upload-time = "2024-11-01T14:07:13.037Z" } +sdist = { url = "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz", hash = "sha256:9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282", size = 131220 } wheels = [ - { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471, upload-time = "2024-11-01T14:06:37.745Z" }, - { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449, upload-time = "2024-11-01T14:06:39.748Z" }, - { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054, upload-time = "2024-11-01T14:06:41.009Z" }, - { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480, upload-time = "2024-11-01T14:06:42.952Z" }, - { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451, upload-time = "2024-11-01T14:06:45.084Z" }, - { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057, upload-time = "2024-11-01T14:06:47.324Z" }, - { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079, upload-time = "2024-11-01T14:06:59.472Z" }, - { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078, upload-time = "2024-11-01T14:07:01.431Z" }, - { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076, upload-time = "2024-11-01T14:07:02.568Z" }, - { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077, upload-time = "2024-11-01T14:07:03.893Z" }, - { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078, upload-time = "2024-11-01T14:07:05.189Z" }, - { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077, upload-time = "2024-11-01T14:07:06.376Z" }, - { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078, upload-time = "2024-11-01T14:07:07.547Z" }, - { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065, upload-time = "2024-11-01T14:07:09.525Z" }, - { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070, upload-time = "2024-11-01T14:07:10.686Z" }, - { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067, upload-time = "2024-11-01T14:07:11.845Z" }, + { url = "https://files.pythonhosted.org/packages/39/ea/3930d07dafc9e286ed356a679aa02d777c06e9bfd1164fa7c19c288a5483/watchdog-6.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bdd4e6f14b8b18c334febb9c4425a878a2ac20efd1e0b231978e7b150f92a948", size = 96471 }, + { url = "https://files.pythonhosted.org/packages/12/87/48361531f70b1f87928b045df868a9fd4e253d9ae087fa4cf3f7113be363/watchdog-6.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c7c15dda13c4eb00d6fb6fc508b3c0ed88b9d5d374056b239c4ad1611125c860", size = 88449 }, + { url = "https://files.pythonhosted.org/packages/5b/7e/8f322f5e600812e6f9a31b75d242631068ca8f4ef0582dd3ae6e72daecc8/watchdog-6.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f10cb2d5902447c7d0da897e2c6768bca89174d0c6e1e30abec5421af97a5b0", size = 89054 }, + { url = "https://files.pythonhosted.org/packages/68/98/b0345cabdce2041a01293ba483333582891a3bd5769b08eceb0d406056ef/watchdog-6.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:490ab2ef84f11129844c23fb14ecf30ef3d8a6abafd3754a6f75ca1e6654136c", size = 96480 }, + { url = "https://files.pythonhosted.org/packages/85/83/cdf13902c626b28eedef7ec4f10745c52aad8a8fe7eb04ed7b1f111ca20e/watchdog-6.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:76aae96b00ae814b181bb25b1b98076d5fc84e8a53cd8885a318b42b6d3a5134", size = 88451 }, + { url = "https://files.pythonhosted.org/packages/fe/c4/225c87bae08c8b9ec99030cd48ae9c4eca050a59bf5c2255853e18c87b50/watchdog-6.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a175f755fc2279e0b7312c0035d52e27211a5bc39719dd529625b1930917345b", size = 89057 }, + { url = "https://files.pythonhosted.org/packages/a9/c7/ca4bf3e518cb57a686b2feb4f55a1892fd9a3dd13f470fca14e00f80ea36/watchdog-6.0.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:7607498efa04a3542ae3e05e64da8202e58159aa1fa4acddf7678d34a35d4f13", size = 79079 }, + { url = "https://files.pythonhosted.org/packages/5c/51/d46dc9332f9a647593c947b4b88e2381c8dfc0942d15b8edc0310fa4abb1/watchdog-6.0.0-py3-none-manylinux2014_armv7l.whl", hash = "sha256:9041567ee8953024c83343288ccc458fd0a2d811d6a0fd68c4c22609e3490379", size = 79078 }, + { url = "https://files.pythonhosted.org/packages/d4/57/04edbf5e169cd318d5f07b4766fee38e825d64b6913ca157ca32d1a42267/watchdog-6.0.0-py3-none-manylinux2014_i686.whl", hash = "sha256:82dc3e3143c7e38ec49d61af98d6558288c415eac98486a5c581726e0737c00e", size = 79076 }, + { url = "https://files.pythonhosted.org/packages/ab/cc/da8422b300e13cb187d2203f20b9253e91058aaf7db65b74142013478e66/watchdog-6.0.0-py3-none-manylinux2014_ppc64.whl", hash = "sha256:212ac9b8bf1161dc91bd09c048048a95ca3a4c4f5e5d4a7d1b1a7d5752a7f96f", size = 79077 }, + { url = "https://files.pythonhosted.org/packages/2c/3b/b8964e04ae1a025c44ba8e4291f86e97fac443bca31de8bd98d3263d2fcf/watchdog-6.0.0-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:e3df4cbb9a450c6d49318f6d14f4bbc80d763fa587ba46ec86f99f9e6876bb26", size = 79078 }, + { url = "https://files.pythonhosted.org/packages/62/ae/a696eb424bedff7407801c257d4b1afda455fe40821a2be430e173660e81/watchdog-6.0.0-py3-none-manylinux2014_s390x.whl", hash = "sha256:2cce7cfc2008eb51feb6aab51251fd79b85d9894e98ba847408f662b3395ca3c", size = 79077 }, + { url = "https://files.pythonhosted.org/packages/b5/e8/dbf020b4d98251a9860752a094d09a65e1b436ad181faf929983f697048f/watchdog-6.0.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:20ffe5b202af80ab4266dcd3e91aae72bf2da48c0d33bdb15c66658e685e94e2", size = 79078 }, + { url = "https://files.pythonhosted.org/packages/07/f6/d0e5b343768e8bcb4cda79f0f2f55051bf26177ecd5651f84c07567461cf/watchdog-6.0.0-py3-none-win32.whl", hash = "sha256:07df1fdd701c5d4c8e55ef6cf55b8f0120fe1aef7ef39a1c6fc6bc2e606d517a", size = 79065 }, + { url = "https://files.pythonhosted.org/packages/db/d9/c495884c6e548fce18a8f40568ff120bc3a4b7b99813081c8ac0c936fa64/watchdog-6.0.0-py3-none-win_amd64.whl", hash = "sha256:cbafb470cf848d93b5d013e2ecb245d4aa1c8fd0504e863ccefa32445359d680", size = 79070 }, + { url = "https://files.pythonhosted.org/packages/33/e8/e40370e6d74ddba47f002a32919d91310d6074130fe4e17dabcafc15cbf1/watchdog-6.0.0-py3-none-win_ia64.whl", hash = "sha256:a1914259fa9e1454315171103c6a30961236f508b9b623eae470268bbcc6a22f", size = 79067 }, ] [[package]] name = "win-precise-time" version = "1.4.2" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9e/b0/21547e16a47206ccdd15769bf65e143ade1ffae67f0881c855f76e44e9fa/win-precise-time-1.4.2.tar.gz", hash = "sha256:89274785cbc5f2997e01675206da3203835a442c60fd97798415c6b3c179c0b9", size = 7982, upload-time = "2023-10-08T17:08:18.618Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/b0/21547e16a47206ccdd15769bf65e143ade1ffae67f0881c855f76e44e9fa/win-precise-time-1.4.2.tar.gz", hash = "sha256:89274785cbc5f2997e01675206da3203835a442c60fd97798415c6b3c179c0b9", size = 7982 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/96/55a14b5c0e90439951f4a72672223bba81a5f882033c5850f8a6c7f4308b/win_precise_time-1.4.2-cp312-cp312-win32.whl", hash = "sha256:0210dcea88a520c91de1708ae4c881e3c0ddc956daa08b9eabf2b7c35f3109f5", size = 14694, upload-time = "2023-10-08T17:08:09.275Z" }, - { url = "https://files.pythonhosted.org/packages/17/19/7ea9a22a69fc23d5ca02e8edf65e4a335a210497794af1af0ef8fda91fa0/win_precise_time-1.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:85670f77cc8accd8f1e6d05073999f77561c23012a9ee988cbd44bb7ce655062", size = 14913, upload-time = "2023-10-08T17:08:10.677Z" }, + { url = "https://files.pythonhosted.org/packages/2e/96/55a14b5c0e90439951f4a72672223bba81a5f882033c5850f8a6c7f4308b/win_precise_time-1.4.2-cp312-cp312-win32.whl", hash = "sha256:0210dcea88a520c91de1708ae4c881e3c0ddc956daa08b9eabf2b7c35f3109f5", size = 14694 }, + { url = "https://files.pythonhosted.org/packages/17/19/7ea9a22a69fc23d5ca02e8edf65e4a335a210497794af1af0ef8fda91fa0/win_precise_time-1.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:85670f77cc8accd8f1e6d05073999f77561c23012a9ee988cbd44bb7ce655062", size = 14913 }, ] [[package]] name = "zipp" version = "3.23.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547, upload-time = "2025-06-08T17:06:39.4Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/02/0f2892c661036d50ede074e376733dca2ae7c6eb617489437771209d4180/zipp-3.23.0.tar.gz", hash = "sha256:a07157588a12518c9d4034df3fbbee09c814741a33ff63c05fa29d26a2404166", size = 25547 } wheels = [ - { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276, upload-time = "2025-06-08T17:06:38.034Z" }, + { url = "https://files.pythonhosted.org/packages/2e/54/647ade08bf0db230bfea292f893923872fd20be6ac6f53b2b936ba839d75/zipp-3.23.0-py3-none-any.whl", hash = "sha256:071652d6115ed432f5ce1d34c336c0adfd6a884660d1e9712a256d3d3bd4b14e", size = 10276 }, ]