diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000..c7fbef56 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,18 @@ +# CI / build plumbing — changes here can subvert the security model. +.github/ @mtcarlone +scripts/ci/ @mtcarlone +compose.yml @mtcarlone +init-scripts/ @mtcarlone + +# Test runtime config — versions, deps, lock files. +tox.ini @mtcarlone +pyproject.toml @mtcarlone +uv.lock @mtcarlone + +# Package metadata — affects what consumers install. +dbt_project.yml @mtcarlone +packages.yml @mtcarlone +package-lock.yml @mtcarlone + +# Spec docs — change history matters for future maintainers. +specs/ @mtcarlone diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..162f131f --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,34 @@ +# Dependabot configuration. +# +# Why this exists: every action in `.github/workflows/*.yml` is pinned to a +# commit SHA (not a tag) for supply-chain safety. That removes auto-updates, +# so Dependabot fills the gap by opening grouped PRs that re-resolve those +# SHAs from the action's latest stable tag. Each PR includes the action's +# changelog, so the maintainer can review what changed before merging. +# +# See specs/ci-rework/README.md §9 (shared hardening) for the SHA-pin rationale. + +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + # Group all action updates into a single PR per week. Avoids three + # tiny PRs that all need the same review attention. + groups: + github-actions: + patterns: ["*"] + commit-message: + prefix: "ci" + include: "scope" + labels: + - "dependencies" + - "github-actions" + + # NOTE: Python dependency updates (uv.lock / pyproject.toml) are + # intentionally NOT managed by Dependabot. dbt and adapter versions are + # part of the test surface area — bumps deserve a deliberate `tox.ini` + # change and matrix update, not an automated PR. Revisit if this gets + # cumbersome in practice. diff --git a/.github/workflows/cut-release-candidate.yml b/.github/workflows/cut-release-candidate.yml new file mode 100644 index 00000000..74fb83b6 --- /dev/null +++ b/.github/workflows/cut-release-candidate.yml @@ -0,0 +1,70 @@ +# Cut a release-candidate branch via `workflow_dispatch`. +# +# Auto-bumps the version in dbt_project.yml + README.md, creates a +# `release-candidate/X.Y.Z` branch, commits, and pushes. The push then +# triggers Tier 3 (release.yml) for the full warehouse × dbt-version +# matrix. +# +# Auto-bump, but NOT auto-merge. The maintainer reviews Tier 3 results +# and drives the tag (and any merge-back to main) manually. See +# docs/dev-workflow.md Stage 3. +# +# This workflow is a thin shim over scripts/release/cut-candidate.py. +# The same script can be run locally: +# ./scripts/release/cut-candidate.py --patch # or --minor / --major +# ./scripts/release/cut-candidate.py --version 2.11.0 + +name: Cut release-candidate + +on: + workflow_dispatch: + inputs: + bump: + description: "Version bump (ignored if explicit_version is set)" + required: true + type: choice + default: "patch" + options: + - patch + - minor + - major + explicit_version: + description: "(optional) Explicit X.Y.Z; overrides bump" + required: false + type: string + +permissions: + contents: write # needed to push the new branch + +concurrency: + # Prevent two cutters running simultaneously and racing on the version. + group: cut-release-candidate + cancel-in-progress: false + +jobs: + cut: + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Checkout main + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 + with: + ref: main + # Need full history so `git checkout -b` and `git push` work. + fetch-depth: 0 + + - name: Configure git identity + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + - name: Cut release-candidate branch + env: + BUMP: ${{ inputs.bump }} + EXPLICIT: ${{ inputs.explicit_version }} + run: | + if [[ -n "${EXPLICIT}" ]]; then + ./scripts/release/cut-candidate.py --version "${EXPLICIT}" + else + ./scripts/release/cut-candidate.py "--${BUMP}" + fi diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..db233aba --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,163 @@ +# Tier 2 — Post-merge integration. +# +# Runs on every push to `main` (after a maintainer-reviewed PR merge), and on +# manual `workflow_dispatch`. This is the first workflow that touches +# repository secrets — Snowflake credentials and GCP Workload Identity. The +# trust boundary that protects those secrets is **branch protection on +# `main`**, which requires PR + approving review. By the time this workflow's +# event fires, a human has signed off on the code. +# +# See specs/ci-rework/README.md §5 (CI tier design) and §3 (threat model). +# +# Scope: +# - Everything Tier 1 runs (lint deferred from Tier 1 lands here) +# - Plus Snowflake + BigQuery on the latest supported dbt version +# - Full version matrix runs in Tier 3 (release.yml), not here +# +# Secret minimization: +# - The `integration-local` matrix declares no secrets at all — those jobs +# don't need them. Even though this workflow file has access to secrets, +# each job only sees the env vars it explicitly opts into. +# - Snowflake creds are scoped to `lint` and `integration-snowflake`. +# - GCP WIF only happens in `integration-bigquery`, gated on +# `id-token: write` at the job level. + +name: Tier 2 — Post-merge integration + +on: + push: + branches: + - main + workflow_dispatch: + +permissions: + contents: read + +# On `main`, don't cancel in-progress runs — each merge represents a +# distinct state that deserves its own full validation. Group still applies +# to prevent two runs racing for the same ref. +concurrency: + group: tier2-${{ github.ref }} + cancel-in-progress: false + +env: + # Non-secret env vars consumed by the dbt invocations test model. These + # are the same values used pre-rework and are checked in to profiles.yml + # via env_var() — they need to be present, not secret. + DBT_CLOUD_PROJECT_ID: 123 + DBT_CLOUD_JOB_ID: ABC + DBT_CLOUD_RUN_REASON: "String with 'quotes' !" + TEST_ENV_VAR_1: TEST_VALUE + TEST_ENV_VAR_NUMBER: 3 + TEST_ENV_VAR_EMPTY: "" + TEST_ENV_VAR_WITH_QUOTE: "Triggered via Apache Airflow by task 'trigger_dbt_cloud_job_run' in the airtable_ingest DAG." + DBT_ENV_CUSTOM_ENV_FAVOURITE_DBT_PACKAGE: dbt_artifacts + +jobs: + # -------- Lint ----------------------------------------------------------- + # Lives here, not in Tier 1, because the package's models call adapter + # methods at compile time. Removing that requirement would let lint move + # to Tier 1 — tracked in specs/ci-rework/README.md §12.2. + lint: + name: lint (sqlfluff) + runs-on: ubuntu-latest + timeout-minutes: 15 + env: + DBT_ENV_SECRET_SNOWFLAKE_TEST_ACCOUNT: ${{ secrets.SNOWFLAKE_TEST_ACCOUNT }} + DBT_ENV_SECRET_SNOWFLAKE_TEST_USER: ${{ secrets.SNOWFLAKE_TEST_USER }} + DBT_ENV_SECRET_SNOWFLAKE_TEST_PASSWORD: ${{ secrets.SNOWFLAKE_TEST_PASSWORD }} + DBT_ENV_SECRET_SNOWFLAKE_TEST_ROLE: ${{ secrets.SNOWFLAKE_TEST_ROLE }} + DBT_ENV_SECRET_SNOWFLAKE_TEST_DATABASE: ${{ secrets.SNOWFLAKE_TEST_DATABASE }} + DBT_ENV_SECRET_SNOWFLAKE_TEST_WAREHOUSE: ${{ secrets.SNOWFLAKE_TEST_WAREHOUSE }} + steps: + - name: Checkout + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 + - name: Set up uv + uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 + with: + enable-cache: true + - name: Install Python dependencies + run: ./scripts/ci/setup.sh + - name: Run sqlfluff lint + run: ./scripts/ci/lint.sh + + # -------- Local-DWH integration ----------------------------------------- + # Same matrix as Tier 1, run again post-merge on the merged code. Catches + # merge-resolution issues that PR-tier validation couldn't see. + integration-local: + name: integration (${{ matrix.warehouse }}) + runs-on: ubuntu-latest + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + warehouse: [postgres, trino, sqlserver] + steps: + - name: Checkout + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 + - name: Install Microsoft ODBC Driver 18 (sqlserver only) + if: matrix.warehouse == 'sqlserver' + run: | + sudo apt-get update + sudo ACCEPT_EULA=Y apt-get install -y msodbcsql18 + - name: Set up uv + uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 + with: + enable-cache: true + - name: Install Python dependencies + run: ./scripts/ci/setup.sh + - name: Run integration tests against ${{ matrix.warehouse }} + run: ./scripts/ci/test.sh ${{ matrix.warehouse }} + + # -------- Snowflake (cloud) --------------------------------------------- + integration-snowflake: + name: integration (snowflake) + runs-on: ubuntu-latest + timeout-minutes: 30 + env: + DBT_ENV_SECRET_SNOWFLAKE_TEST_ACCOUNT: ${{ secrets.SNOWFLAKE_TEST_ACCOUNT }} + DBT_ENV_SECRET_SNOWFLAKE_TEST_USER: ${{ secrets.SNOWFLAKE_TEST_USER }} + DBT_ENV_SECRET_SNOWFLAKE_TEST_PASSWORD: ${{ secrets.SNOWFLAKE_TEST_PASSWORD }} + DBT_ENV_SECRET_SNOWFLAKE_TEST_ROLE: ${{ secrets.SNOWFLAKE_TEST_ROLE }} + DBT_ENV_SECRET_SNOWFLAKE_TEST_DATABASE: ${{ secrets.SNOWFLAKE_TEST_DATABASE }} + DBT_ENV_SECRET_SNOWFLAKE_TEST_WAREHOUSE: ${{ secrets.SNOWFLAKE_TEST_WAREHOUSE }} + steps: + - name: Checkout + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 + - name: Set up uv + uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 + with: + enable-cache: true + - name: Install Python dependencies + run: ./scripts/ci/setup.sh + - name: Run integration tests against snowflake + run: ./scripts/ci/test.sh snowflake + + # -------- BigQuery (cloud, WIF auth) ------------------------------------ + integration-bigquery: + name: integration (bigquery) + runs-on: ubuntu-latest + timeout-minutes: 30 + # WIF requires an OIDC token from the runner. Scoped to this job only — + # no other job in this workflow gets id-token: write. + permissions: + contents: read + id-token: write + env: + DBT_ENV_SECRET_GCP_PROJECT: ${{ secrets.GCP_PROJECT }} + steps: + - name: Checkout + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 + - name: Authenticate to GCP via Workload Identity Federation + uses: google-github-actions/auth@c200f3691d83b41bf9bbd8638997a462592937ed + with: + workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} + service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} + - name: Set up uv + uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 + with: + enable-cache: true + - name: Install Python dependencies + run: ./scripts/ci/setup.sh + - name: Run integration tests against bigquery + run: ./scripts/ci/test.sh bigquery diff --git a/.github/workflows/main_lint_package.yml b/.github/workflows/main_lint_package.yml deleted file mode 100644 index 4247158a..00000000 --- a/.github/workflows/main_lint_package.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: Main branch lint package - -# triggers for the workflow -on: - workflow_dispatch: - push: - branches: - - main - -env: - # These are configured in GitHub secrets - DBT_PROFILES_DIR: ${{ github.workspace }}/integration_test_project - DBT_ENV_SECRET_SNOWFLAKE_TEST_ACCOUNT: ${{ secrets.SNOWFLAKE_TEST_ACCOUNT }} - DBT_ENV_SECRET_SNOWFLAKE_TEST_USER: ${{ secrets.SNOWFLAKE_TEST_USER }} - DBT_ENV_SECRET_SNOWFLAKE_TEST_PASSWORD: ${{ secrets.SNOWFLAKE_TEST_PASSWORD }} - DBT_ENV_SECRET_SNOWFLAKE_TEST_ROLE: ${{ secrets.SNOWFLAKE_TEST_ROLE }} - DBT_ENV_SECRET_SNOWFLAKE_TEST_DATABASE: ${{ secrets.SNOWFLAKE_TEST_DATABASE }} - DBT_ENV_SECRET_SNOWFLAKE_TEST_WAREHOUSE: ${{ secrets.SNOWFLAKE_TEST_WAREHOUSE }} - DBT_ENV_SECRET_DATABRICKS_HOST: ${{ secrets.DATABRICKS_HOST }} - DBT_ENV_SECRET_DATABRICKS_HTTP_PATH: ${{ secrets.DATABRICKS_HTTP_PATH }} - DBT_ENV_SECRET_DATABRICKS_TOKEN: ${{ secrets.DATABRICKS_TOKEN }} - DBT_ENV_SECRET_GCP_PROJECT: ${{ secrets.GCP_PROJECT }} - # Env vars to test invocations model - DBT_CLOUD_PROJECT_ID: 123 - DBT_CLOUD_JOB_ID: ABC - DBT_CLOUD_RUN_REASON: "String with 'quotes' !" - TEST_ENV_VAR_1: TEST_VALUE - TEST_ENV_VAR_NUMBER: 3 - TEST_ENV_VAR_EMPTY: "" - TEST_ENV_VAR_WITH_QUOTE: "Triggered via Apache Airflow by task 'trigger_dbt_cloud_job_run' in the airtable_ingest DAG." - DBT_ENV_CUSTOM_ENV_FAVOURITE_DBT_PACKAGE: dbt_artifacts - -jobs: - sqlfluff-lint-models: - name: Lint dbt models using SQLFluff - runs-on: ubuntu-latest - - steps: - - name: Checkout branch - uses: actions/checkout@v3 - - - name: Setup Python - uses: actions/setup-python@v4 - with: - python-version: "3.9.x" - architecture: "x64" - - - name: Install Python packages - run: python -m pip install dbt-snowflake~=1.9.0 sqlfluff-templater-dbt~=3.0.0 - - - name: Test database connection - run: dbt debug - - - name: Install dbt packages - run: dbt deps - - - name: Lint dbt models - run: sqlfluff lint models --ignore parsing diff --git a/.github/workflows/main_test_package.yml b/.github/workflows/main_test_package.yml deleted file mode 100644 index cca42e7d..00000000 --- a/.github/workflows/main_test_package.yml +++ /dev/null @@ -1,151 +0,0 @@ -name: Main branch test package - -# triggers for the workflow -on: - workflow_dispatch: - push: - branches: - - main - -env: - # These are configured in GitHub secrets - DBT_PROFILES_DIR: ${{ github.workspace }}/integration_test_project - DBT_ENV_SECRET_SNOWFLAKE_TEST_ACCOUNT: ${{ secrets.SNOWFLAKE_TEST_ACCOUNT }} - DBT_ENV_SECRET_SNOWFLAKE_TEST_USER: ${{ secrets.SNOWFLAKE_TEST_USER }} - DBT_ENV_SECRET_SNOWFLAKE_TEST_PASSWORD: ${{ secrets.SNOWFLAKE_TEST_PASSWORD }} - DBT_ENV_SECRET_SNOWFLAKE_TEST_ROLE: ${{ secrets.SNOWFLAKE_TEST_ROLE }} - DBT_ENV_SECRET_SNOWFLAKE_TEST_DATABASE: ${{ secrets.SNOWFLAKE_TEST_DATABASE }} - DBT_ENV_SECRET_SNOWFLAKE_TEST_WAREHOUSE: ${{ secrets.SNOWFLAKE_TEST_WAREHOUSE }} - DBT_ENV_SECRET_DATABRICKS_HOST: ${{ secrets.DATABRICKS_HOST }} - DBT_ENV_SECRET_DATABRICKS_HTTP_PATH: ${{ secrets.DATABRICKS_HTTP_PATH }} - DBT_ENV_SECRET_DATABRICKS_TOKEN: ${{ secrets.DATABRICKS_TOKEN }} - DBT_ENV_SECRET_GCP_PROJECT: ${{ secrets.GCP_PROJECT }} - # Env vars to test invocations model - DBT_CLOUD_PROJECT_ID: 123 - DBT_CLOUD_JOB_ID: ABC - DBT_CLOUD_RUN_REASON: "String with 'quotes' !" - TEST_ENV_VAR_1: TEST_VALUE - TEST_ENV_VAR_NUMBER: 3 - TEST_ENV_VAR_EMPTY: "" - TEST_ENV_VAR_WITH_QUOTE: "Triggered via Apache Airflow by task 'trigger_dbt_cloud_job_run' in the airtable_ingest DAG." - DBT_ENV_CUSTOM_ENV_FAVOURITE_DBT_PACKAGE: dbt_artifacts - -jobs: - integration: - strategy: - matrix: - warehouse: ["snowflake", "bigquery", "postgres", "trino"] - version: ["1_3_0", "1_4_0", "1_5_0", "1_6_0", "1_7_0", "1_8_0", "1_9_0"] - runs-on: ubuntu-latest - permissions: - contents: "read" - id-token: "write" - - services: - postgres: - image: postgres - env: - POSTGRES_PASSWORD: postgres - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - ports: - - 5432:5432 - - trino: - image: trinodb/trino - ports: - - 8080:8080 - - steps: - - name: Checkout - uses: actions/checkout@v3 - - - uses: actions/setup-python@v4 - with: - python-version: "3.9.x" - architecture: "x64" - - - name: Install tox - run: python3 -m pip install tox - - - name: Install SQL Server - run: docker run -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD=123" -p 1433:1433 -d mcr.microsoft.com/mssql/server:2022-latest - - - name: Install Microsoft ODBC - run: sudo ACCEPT_EULA=Y apt-get install msodbcsql18 -y - - - id: auth - if: ${{ matrix.warehouse == 'bigquery' }} - uses: google-github-actions/auth@v1 - with: - workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} - service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} - - - name: Run ${{ matrix.warehouse }} Tests - env: - DBT_VERSION: ${{ matrix.version }} - run: tox -e integration_${{ matrix.warehouse }}_${{ matrix.version }} - - # Databricks doesn't like the matrix strategy, so moving back to the old integration testing without versioning - # integration-databricks: - # runs-on: ubuntu-latest - - # steps: - # - name: Checkout - # uses: actions/checkout@v3 - - # - uses: actions/setup-python@v4 - # with: - # python-version: '3.8.x' - # architecture: 'x64' - - # - name: Install tox - # run: python3 -m pip install tox - - # - name: Run Databricks Tests - # run: tox -e integration_databricks - - integration-sqlserver: - strategy: - fail-fast: false # Don't fail one DWH if the others fail - matrix: - # When supporting a new version, update the list here - version: ["1_3_0", "1_4_0", "1_7_0", "1_8_0"] - runs-on: ubuntu-22.04 - environment: - name: Approve Integration Tests - - steps: - - uses: actions/setup-python@v4 - with: - python-version: "3.9.x" - architecture: "x64" - - name: Install SQL Server - uses: Particular/install-sql-server-action@v1.2.0 - with: - connection-string-env-var: SQL_SERVER_CONNECTION_STRING - catalog: dbt_artifact_integrationtests - - name: Create DBT User - shell: pwsh - run: | - echo "Create dbt login with sysadmin" - sqlcmd -Q "CREATE LOGIN dbt WITH PASSWORD = '123Administrator', CHECK_POLICY = OFF, CHECK_EXPIRATION = OFF" -d "dbt_artifact_integrationtests" - sqlcmd -Q "ALTER SERVER ROLE sysadmin ADD MEMBER dbt" -d "dbt_artifact_integrationtests" - - name: Install tox - run: python3 -m pip install tox - - - name: Install Microsoft ODBC - run: sudo ACCEPT_EULA=Y apt-get install msodbcsql18 -y - - - name: Checkout - uses: actions/checkout@v3 - with: - ref: ${{ github.event.pull_request.head.sha }} # Check out the code of the PR - - - name: Run Tests on PR - env: - DBT_VERSION: ${{ matrix.version }} - run: tox -e integration_sqlserver_${{ matrix.version }} diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml new file mode 100644 index 00000000..962835a3 --- /dev/null +++ b/.github/workflows/pr.yml @@ -0,0 +1,71 @@ +# Tier 1 — PR smoke test. +# +# Runs on every pull_request targeting `main`, including PRs from forks. +# Uses the `pull_request` event (NOT `pull_request_target`), so this workflow +# runs in an environment where repository secrets are **structurally +# unavailable**. That is the core security guarantee — see specs/ci-rework/README.md +# §3 (threat model) for the full rationale. +# +# Scope: +# - Lint is intentionally NOT run here. The package's models require real +# Snowflake connectivity to compile (some macros call adapter methods at +# compile time), so the dbt templater cannot run in this no-secrets tier. +# Lint runs in Tier 2 (push to main, with secrets). Tracked in spec §13. +# - Tests run against Postgres, Trino, SQL Server only — every warehouse +# that can be containerized locally. Snowflake/BigQuery/Databricks are +# Tier 2/3. +# - Only the latest supported dbt version is tested here. The full +# version matrix runs in Tier 3. +# +# Hardening notes: +# - `permissions: contents: read` only. No `id-token`, no escalation. +# - Third-party actions pinned to commit SHAs (resolved from tags at +# time of writing — see comment after each `uses:` for the human tag). +# To re-resolve, run `gh api repos///commits/`. + +name: Tier 1 — PR smoke test + +on: + pull_request: + branches: + - main + +permissions: + contents: read + +# Cancel in-progress runs when a PR receives a new push. Keyed on the PR +# number so different PRs don't cancel each other. +concurrency: + group: tier1-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + integration-local: + name: integration (${{ matrix.warehouse }}) + runs-on: ubuntu-latest + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + warehouse: [postgres, trino, sqlserver] + + steps: + - name: Checkout PR code + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 + + - name: Install Microsoft ODBC Driver 18 (sqlserver only) + if: matrix.warehouse == 'sqlserver' + run: | + sudo apt-get update + sudo ACCEPT_EULA=Y apt-get install -y msodbcsql18 + + - name: Set up uv + uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 + with: + enable-cache: true + + - name: Install Python dependencies + run: ./scripts/ci/setup.sh + + - name: Run integration tests against ${{ matrix.warehouse }} + run: ./scripts/ci/test.sh ${{ matrix.warehouse }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..222e9e18 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,223 @@ +# Tier 3 — Release validation. +# +# Runs the full warehouse × dbt-version matrix. This is the gate on cutting +# a release tag: if Tier 3 is green on a `release-candidate/X.Y.Z` branch, +# the maintainer tags `X.Y.Z` from that branch's HEAD. +# +# Triggers: +# - push to `release-candidate/**` branches (the standard maintainer flow: +# create the branch, push a version bump, watch Tier 3 run, tag) +# - `workflow_dispatch` for manual re-runs or one-off validation +# - `schedule` weekly — regression run against `main` to detect drift +# from upstream dbt adapter releases. Same matrix, same secrets, same +# guarantees as a release-candidate run; just on a different cadence. +# Scheduled runs always execute against the default branch (`main`), +# so this validates the current state of `main` regardless of any +# in-flight release branches. +# +# Trust model: same as Tier 2 (`main.yml`). Branch protection on `main` +# ensures `release-candidate/**` branches can only be created or updated by +# a maintainer pushing trusted code. Secrets are accessible because the code +# has already cleared the human-review bar via PR-to-main + branch creation. +# +# Scope (per specs/ci-rework/README.md §5.3 / Tier 3): +# - Lint (same as Tier 2) +# - Full version matrix: every supported (warehouse, dbt_version) pair, +# including the unversioned "latest" env per warehouse (early warning +# for upcoming adapter releases) +# - Databricks: stub job, intentionally skipped. See specs §12.3. + +name: Tier 3 — Release validation + +on: + push: + branches: + - "release-candidate/**" + workflow_dispatch: + schedule: + # Weekly regression: Mondays at 06:00 UTC. Aligned with the start of + # the workweek so failures get attention promptly. If this becomes too + # expensive, drop to monthly or trim `max-parallel` in the matrix job. + - cron: "0 6 * * 1" + +permissions: + contents: read + +# Release validations should not cancel each other — a maintainer pushing +# follow-up commits to the same release branch wants every intermediate +# state validated. +concurrency: + group: tier3-${{ github.ref }} + cancel-in-progress: false + +env: + # Non-secret env vars consumed by the invocations test model (see + # integration_test_project/profiles.yml). + DBT_CLOUD_PROJECT_ID: 123 + DBT_CLOUD_JOB_ID: ABC + DBT_CLOUD_RUN_REASON: "String with 'quotes' !" + TEST_ENV_VAR_1: TEST_VALUE + TEST_ENV_VAR_NUMBER: 3 + TEST_ENV_VAR_EMPTY: "" + TEST_ENV_VAR_WITH_QUOTE: "Triggered via Apache Airflow by task 'trigger_dbt_cloud_job_run' in the airtable_ingest DAG." + DBT_ENV_CUSTOM_ENV_FAVOURITE_DBT_PACKAGE: dbt_artifacts + +jobs: + # -------- Lint ----------------------------------------------------------- + lint: + name: lint (sqlfluff) + runs-on: ubuntu-latest + timeout-minutes: 15 + env: + DBT_ENV_SECRET_SNOWFLAKE_TEST_ACCOUNT: ${{ secrets.SNOWFLAKE_TEST_ACCOUNT }} + DBT_ENV_SECRET_SNOWFLAKE_TEST_USER: ${{ secrets.SNOWFLAKE_TEST_USER }} + DBT_ENV_SECRET_SNOWFLAKE_TEST_PASSWORD: ${{ secrets.SNOWFLAKE_TEST_PASSWORD }} + DBT_ENV_SECRET_SNOWFLAKE_TEST_ROLE: ${{ secrets.SNOWFLAKE_TEST_ROLE }} + DBT_ENV_SECRET_SNOWFLAKE_TEST_DATABASE: ${{ secrets.SNOWFLAKE_TEST_DATABASE }} + DBT_ENV_SECRET_SNOWFLAKE_TEST_WAREHOUSE: ${{ secrets.SNOWFLAKE_TEST_WAREHOUSE }} + steps: + - name: Checkout + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 + - name: Set up uv + uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 + with: + enable-cache: true + - name: Install Python dependencies + run: ./scripts/ci/setup.sh + - name: Run sqlfluff lint + run: ./scripts/ci/lint.sh + + # -------- Full version matrix ------------------------------------------- + # Single matrix that enumerates every supported (warehouse, dbt_version) + # combination. Includes an unversioned "latest" entry per warehouse to + # catch breakage from newly-released adapter minors before we add them to + # `tox.ini`. Empty `dbt_version` → `test.sh` uses the unversioned env. + # + # `id-token: write` is granted at the job level so the conditional WIF + # auth step works for bigquery slots. Other slots don't call the auth + # action, so they never mint an OIDC token. + version-matrix: + name: integration (${{ matrix.warehouse }}, ${{ matrix.dbt_version || 'latest' }}) + runs-on: ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read + id-token: write + env: + DBT_ENV_SECRET_SNOWFLAKE_TEST_ACCOUNT: ${{ secrets.SNOWFLAKE_TEST_ACCOUNT }} + DBT_ENV_SECRET_SNOWFLAKE_TEST_USER: ${{ secrets.SNOWFLAKE_TEST_USER }} + DBT_ENV_SECRET_SNOWFLAKE_TEST_PASSWORD: ${{ secrets.SNOWFLAKE_TEST_PASSWORD }} + DBT_ENV_SECRET_SNOWFLAKE_TEST_ROLE: ${{ secrets.SNOWFLAKE_TEST_ROLE }} + DBT_ENV_SECRET_SNOWFLAKE_TEST_DATABASE: ${{ secrets.SNOWFLAKE_TEST_DATABASE }} + DBT_ENV_SECRET_SNOWFLAKE_TEST_WAREHOUSE: ${{ secrets.SNOWFLAKE_TEST_WAREHOUSE }} + DBT_ENV_SECRET_GCP_PROJECT: ${{ secrets.GCP_PROJECT }} + strategy: + fail-fast: false + # Throttle parallelism — 42 slots × full cloud setup would hammer + # Snowflake and pile up runner billing. 8 in flight gives a release + # validation that completes in ~30–40 minutes. + max-parallel: 8 + matrix: + include: + # "Latest" (unversioned env per warehouse) — picks up the + # highest adapter release at the time the workflow runs. + - warehouse: snowflake + dbt_version: "" + - warehouse: bigquery + dbt_version: "" + - warehouse: postgres + dbt_version: "" + - warehouse: trino + dbt_version: "" + - warehouse: sqlserver + dbt_version: "" + + # Snowflake — pinned versions, newest first. + - { warehouse: snowflake, dbt_version: "1_11_0" } + - { warehouse: snowflake, dbt_version: "1_10_0" } + - { warehouse: snowflake, dbt_version: "1_9_0" } + - { warehouse: snowflake, dbt_version: "1_8_0" } + - { warehouse: snowflake, dbt_version: "1_7_0" } + - { warehouse: snowflake, dbt_version: "1_6_0" } + - { warehouse: snowflake, dbt_version: "1_5_0" } + - { warehouse: snowflake, dbt_version: "1_4_0" } + - { warehouse: snowflake, dbt_version: "1_3_0" } + + # BigQuery + - { warehouse: bigquery, dbt_version: "1_11_0" } + - { warehouse: bigquery, dbt_version: "1_10_0" } + - { warehouse: bigquery, dbt_version: "1_9_0" } + - { warehouse: bigquery, dbt_version: "1_8_0" } + - { warehouse: bigquery, dbt_version: "1_7_0" } + - { warehouse: bigquery, dbt_version: "1_6_0" } + - { warehouse: bigquery, dbt_version: "1_5_0" } + - { warehouse: bigquery, dbt_version: "1_4_0" } + - { warehouse: bigquery, dbt_version: "1_3_0" } + + # Postgres — no upstream 1.11 release as of 2026-05-12. + - { warehouse: postgres, dbt_version: "1_10_0" } + - { warehouse: postgres, dbt_version: "1_9_0" } + - { warehouse: postgres, dbt_version: "1_8_0" } + - { warehouse: postgres, dbt_version: "1_7_0" } + - { warehouse: postgres, dbt_version: "1_6_0" } + - { warehouse: postgres, dbt_version: "1_5_0" } + - { warehouse: postgres, dbt_version: "1_4_0" } + - { warehouse: postgres, dbt_version: "1_3_0" } + + # Trino — no upstream 1.11 / 1.9 / 1.8 releases. + - { warehouse: trino, dbt_version: "1_10_0" } + - { warehouse: trino, dbt_version: "1_7_0" } + - { warehouse: trino, dbt_version: "1_6_0" } + - { warehouse: trino, dbt_version: "1_5_0" } + - { warehouse: trino, dbt_version: "1_4_0" } + - { warehouse: trino, dbt_version: "1_3_0" } + + # SQL Server — adapter lags, only 1.9/1.8/1.7/1.4/1.3 published. + - { warehouse: sqlserver, dbt_version: "1_9_0" } + - { warehouse: sqlserver, dbt_version: "1_8_0" } + - { warehouse: sqlserver, dbt_version: "1_7_0" } + - { warehouse: sqlserver, dbt_version: "1_4_0" } + - { warehouse: sqlserver, dbt_version: "1_3_0" } + + steps: + - name: Checkout + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 + + - name: Install Microsoft ODBC Driver 18 (sqlserver only) + if: matrix.warehouse == 'sqlserver' + run: | + sudo apt-get update + sudo ACCEPT_EULA=Y apt-get install -y msodbcsql18 + + - name: Authenticate to GCP via Workload Identity Federation (bigquery only) + if: matrix.warehouse == 'bigquery' + uses: google-github-actions/auth@c200f3691d83b41bf9bbd8638997a462592937ed + with: + workload_identity_provider: ${{ secrets.GCP_WORKLOAD_IDENTITY_PROVIDER }} + service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} + + - name: Set up uv + uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 + with: + enable-cache: true + + - name: Install Python dependencies + run: ./scripts/ci/setup.sh + + - name: Run integration tests + run: ./scripts/ci/test.sh ${{ matrix.warehouse }} ${{ matrix.dbt_version }} + + # -------- Databricks stub ----------------------------------------------- + # Visible in the workflow run so reviewers see Databricks has been + # *considered* and *skipped on purpose*, not forgotten. When Databricks + # is reactivated (specs/ci-rework/README.md §12.3), replace this with a real + # matrix job following the same shape as `version-matrix`. + integration-databricks-stub: + name: integration (databricks) [STUB — skipped] + runs-on: ubuntu-latest + timeout-minutes: 1 + steps: + - name: Announce skip + run: | + echo "::warning title=Databricks tests skipped::Databricks integration is stubbed pending reactivation. See specs/ci-rework/README.md §12.3." + echo "This job exists so the Tier 3 release matrix visibly accounts for Databricks rather than silently omitting it." diff --git a/.gitignore b/.gitignore index 2dbcc67a..b2b60b08 100644 --- a/.gitignore +++ b/.gitignore @@ -17,8 +17,18 @@ env.sh .venv .env .secrets -*.lock **/*/package-lock.yml -pyproject.toml .DS_Store + +.codex +.claude +.agents + +# AI-assistant orientation files — kept local while the team decides +# between tools. Remove this entry or `git add -f` to track. +CLAUDE.md + +# Python bytecode cache (e.g. from running scripts/release/*.py) +__pycache__/ +*.pyc diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index eb364e05..42f88438 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,136 +1,212 @@ # Contributing to the dbt Artifacts Package -Thank you for your interest in contributing to the dbt Artifacts Package! We welcome contributions of all kinds, -including bug reports, feature requests, and pull requests. - -Please read this document to learn how to contribute. Following these guidelines helps to communicate that you respect the time of the developers managing and developing -this open source project. In return, they should reciprocate that respect in addressing your issue, assessing changes, -and helping you finalize your pull requests. - -## Reporting Bugs :bug: - -If you find a bug, please [open an issue](https://github.com/brooklyn-data/dbt_artifacts/issues/new) and describe the -problem. If possible, include a minimal example that maintainers can use to reproduce the issue. If you are able to -fix the bug, please [open a pull request](https://github.com/github/docs/pulls) with a fix. - -## Requesting Features :bulb: - -If you would like to request a new feature, -please [open an issue](https://github.com/brooklyn-data/dbt_artifacts/issues/new) and describe the desired behavior. -If you are able to implement the feature, please [open a pull request](https://github.com/github/docs/pulls) with -your changes. If you need help with either of these steps, please let us know! - -## New Contributor Guide -- To get an overview of the project, read our [README](README.md). -- There's a fair bit of infrastructure to set up for integration testing, read more in [MAINTAINERS.md](docs/MAINTAINERS.md) -- Look for [issues](https://github.com/brooklyn-data/dbt_artifacts/issues) labeled as "good first issue" - these are issues which would be good for newcomers. - -## Contributing Code :computer: - -The high-level flow for contributing code to the dbt Artifacts Package is as follows (see below for details of each stage): - -### Make Changes -1. Fork the dbt Artifacts Package repository - - The first step to contributing code is -to [fork](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo) the dbt Artifacts Package -repository. Once you have a fork, you can clone it locally and begin making changes. -2. Clone your fork locally -3. Create a new branch -4. Make your changes -5. [Run the tests](#running-the-tests) - -### Pull Request -1. Open a pull request against the `main` branch - - Fill out the PR template. This template helps reviewers understand your changes as well as the purpose of your pull request. - - Don't forget to link PR to issue if you are solving one. -2. Make sure your pull request passes all checks -3. Address any review feedback - - As you update your PR and apply changes, mark each conversation as resolved. - - Enable the checkbox to [allow maintainer edits](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork) so the branch can be updated for a merge. Once you submit your PR, a maintainer will review your proposal. We may ask questions or request additional information. -4. Merge your pull request :tada: +Thank you for your interest in contributing! Bug reports, feature +requests, and pull requests are all welcome. -## Setting up your development environment +This guide covers how to set up your local environment, run tests, and +submit a change. For an end-to-end walkthrough of how a change makes it +from a feature branch to a tagged release — including which CI workflow +fires at each stage — see [docs/dev-workflow.md](docs/dev-workflow.md). -#### Running the tests +--- -To run the tests, we use [tox](https://tox.wiki/en/latest/). Tox is a tool that automates testing in multiple Python -environments. Tox is a CLI tool that needs a Python interpreter (version 3.7 or higher) to run. We -recommend [pipx](https://pypa.github.io/pipx/) to install tox into an isolated environment. This has the added benefit -that later you’ll be able to upgrade tox without affecting other parts of the system. +## Reporting bugs -Tox will take care of installing the dependencies for each environment, so you don’t need to worry about that. +[Open an issue](https://github.com/brooklyn-data/dbt_artifacts/issues/new) +and describe the problem. Include a minimal reproduction if possible. -1. Install pipx +## Requesting features - ```bash - pip install pipx - pipx ensurepath - ``` +[Open an issue](https://github.com/brooklyn-data/dbt_artifacts/issues/new) +and describe the desired behavior. Pull requests welcome. -2. Install tox +--- - ```bash - pipx install tox - ``` +## Setting up your development environment -3. Copy and paste the `integration_test_project/example-env.sh` file and save as `env.sh`. Fill in the missing values. - If you want to run only tests for a specific database, you can leave the other values blank. +### Prerequisites +- **`uv`** — Python toolchain. Installs everything Python from + `pyproject.toml` and `uv.lock`. See https://docs.astral.sh/uv/. +- **`docker`** — required for the Postgres / Trino / SQL Server + integration tests (they run in containers). +- **Microsoft ODBC Driver 18** — required on the host (not in the + container) for the SQL Server tests: + - macOS: + ```bash + brew tap microsoft/mssql-release https://github.com/Microsoft/homebrew-mssql-release + brew install msodbcsql18 mssql-tools18 + ``` + - Linux (Debian/Ubuntu): ```bash - cp integration_test_project/example-env.sh env.sh - vim env.sh + sudo ACCEPT_EULA=Y apt-get install -y msodbcsql18 ``` -4. Source the file in your current shell context with the command: +You only need cloud warehouse credentials (Snowflake / BigQuery / +Databricks) if you intend to test against those. Most contributors can +get useful signal from the local-runnable warehouses alone. - ```bash - . ./env.sh - ``` +### One-time setup + +```bash +git clone https://github.com//dbt_artifacts +cd dbt_artifacts +./scripts/ci/setup.sh # uv sync — installs tox, dbt adapters, etc. +``` -5. From the root directory, run the tests for the databases you have access to below: +If you have warehouse credentials, copy the template: - ``` - tox -e integration_snowflake # For the Snowflake tests - tox -e integration_databricks # For the Databricks tests - tox -e integration_bigquery # For the BigQuery tests - ``` +```bash +cp integration_test_project/example-env.sh env.sh +# edit env.sh, fill in any credentials you have +. ./env.sh +``` -The Spark tests require installing the [ODBC driver](https://www.databricks.com/spark/odbc-drivers-download). On a Mac, -DBT_ENV_SPARK_DRIVER_PATH should be set to `/Library/simba/spark/lib/libsparkodbc_sbu.dylib`. Spark tests have not yet -been added to the integration tests. +`env.sh` is gitignored. You only need to source it for warehouses that +need credentials — Postgres, Trino, and SQL Server don't. You can also create a `.env` file in the root of the project. -If you don't have access to a particular database type, this isn't a problem. Test on the one you do have, and let us know in the PR. +--- -#### SQLFluff +## Running tests -We use SQLFluff to keep SQL style consistent. A GitHub action automatically tests pull requests and adds annotations -where there are failures. SQLFluff can also be run locally with `tox`. +Everything runs through [`scripts/ci/`](scripts/ci/README.md). The same +scripts CI uses are the ones you run locally — there is no separate +"local" command path. -Lint all models in the /models directory: +### Lint ```bash -tox +./scripts/ci/lint.sh # lint everything in models/ +./scripts/ci/lint.sh --fix # auto-fix everything +./scripts/ci/lint.sh models/path/file.sql # lint a single file +./scripts/ci/lint.sh --fix models/path/file.sql ``` -Fix all models in the /models directory: +> **Note:** Lint currently requires real Snowflake credentials because +> some models call adapter methods at compile time. If you don't have +> Snowflake access, push your branch — a maintainer will see lint +> results after they merge it. (This limitation is tracked in +> [specs §12.2](specs/ci-rework/README.md) for future fix.) + +### One warehouse ```bash -tox -e fix_all +./scripts/ci/test.sh postgres # Postgres in Docker +./scripts/ci/test.sh trino # Trino in Docker +./scripts/ci/test.sh sqlserver # SQL Server in Docker +./scripts/ci/test.sh snowflake # cloud — needs creds +./scripts/ci/test.sh bigquery # cloud — needs creds +./scripts/ci/test.sh postgres 1_10_0 # pinned dbt version ``` -Lint (or subsitute lint to fix) a specific model: +The script handles `docker compose` lifecycle automatically — brings up +containers, waits for healthy, runs the tests, tears down on exit. Pass +`KEEP_COMPOSE=1` if you want to leave containers running after a +failure for debugging. + +### Every local warehouse (what Tier 1 CI runs) ```bash -tox -e lint -- models/path/to/model.sql +./scripts/ci/test-all-local.sh ``` -Lint (or subsitute lint to fix) a specific directory: +Loops Postgres + Trino + SQL Server. Continues past individual failures +and prints a summary. + +### A subset of dbt models manually ```bash -tox -e lint -- models/path/to/directory +cd integration_test_project +. ../env.sh +uv run dbt deps +uv run dbt run --select --target ``` -##### Rules +Default `--target` is set at the top of +`integration_test_project/profiles.yml` — change it if you don't want +to pass `--target` on every command. + +--- + +## What CI will (and won't) do on your PR + +| You see on the PR | You see after merge | +|---|---| +| Lint: **not run** (needs Snowflake creds) | Lint | +| Postgres, Trino, SQL Server integration | Same, re-run on merged code | +| Snowflake, BigQuery: **not run** (no fork secrets) | Snowflake + BigQuery | + +This is intentional. PRs from forks cannot have access to the +package's warehouse credentials — that's the security model that +replaced the previous `pull_request_target` setup. If you don't have +Snowflake or BigQuery, that's fine; a maintainer will validate those +after merging. Mention in your PR description what you did and didn't +test locally. + +See [docs/dev-workflow.md](docs/dev-workflow.md) for the full picture. + +--- + +## Code style + +SQL is linted by [SQLFluff](https://sqlfluff.com). Rules and config +live in `tox.ini`. Highlights: + +- Lowercase keywords, identifiers, functions +- Leading commas +- No subqueries in `FROM` / `JOIN` — use CTEs + +Run `./scripts/ci/lint.sh` before pushing if you can. + +--- + +## Adding a new dataset column + +This package has a critical invariant — column order must match in +**three** places, always with new fields appended at the **bottom**: + +1. `macros/upload_individual_datasets/upload_.sql` (each + adapter override, not just `default__`) +2. `models/sources/.sql` and the matching `.yml` +3. `macros/upload_results/get_column_name_lists.sql` + +Then surface the column through `models/staging/stg_dbt__.sql` +and any `dim_` / `fct_` model that should expose it. + +**Adding a column is always at least a minor version bump** — consumers +must re-run `dbt run --select dbt_artifacts` after upgrading or the hook +will error. + +--- + +## Adding a new adapter + +1. Add adapter-specific overrides in `macros/database_specific_helpers/` + for: `type_helpers`, `parse_json`, `string_functions`, + `column_identifier`, `generate_surrogate_key`, `get_relation`. +2. Add adapter overrides in each `macros/upload_individual_datasets/upload_*.sql` + for any SQL that isn't portable. +3. Add a target to `integration_test_project/profiles.yml` and a + `[testenv:integration_]` block in `tox.ini`. +4. Wire it into the workflow files: + - Add to the matrix in [`.github/workflows/pr.yml`](.github/workflows/pr.yml) + if it can run locally in Docker. + - Add to [`.github/workflows/main.yml`](.github/workflows/main.yml) + if it requires cloud credentials. + - Add per-version entries to the matrix in + [`.github/workflows/release.yml`](.github/workflows/release.yml). + +--- + +## Submitting your PR + +1. Fork → clone → branch → make changes → run tests. +2. Open a PR against `main`. Use the PR template. +3. Tier 1 CI runs automatically. Address any failures. +4. A maintainer reviews. Address feedback. +5. Maintainer merges. Tier 2 fires on `main` — they'll let you know if + anything cloud-warehouse-specific needs follow-up. -Enforced rules are defined within `tox.ini`. To view the full list of available rules and their configuration, see -the [SQLFluff documentation](https://docs.sqlfluff.com/en/stable/rules.html). +For everything beyond merging — release-candidate branches, tagging, +hotfixes — see [docs/MAINTAINERS.md](docs/MAINTAINERS.md) and +[docs/dev-workflow.md](docs/dev-workflow.md). diff --git a/README.md b/README.md index e21823be..3e88e950 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,17 @@ This package builds a mart of tables and views describing the project it is installed in. In pre V1 versions of the package, the artifacts dbt produces were uploaded to the warehouse, hence the name of the package. That's no longer the case, but the name has stuck! -[![Main branch test package](https://github.com/brooklyn-data/dbt_artifacts/actions/workflows/main_test_package.yml/badge.svg)](https://github.com/brooklyn-data/dbt_artifacts/actions/workflows/main_test_package.yml) -[![Main branch lint package](https://github.com/brooklyn-data/dbt_artifacts/actions/workflows/main_lint_package.yml/badge.svg)](https://github.com/brooklyn-data/dbt_artifacts/actions/workflows/main_lint_package.yml) +[![Tier 2 — main](https://github.com/brooklyn-data/dbt_artifacts/actions/workflows/main.yml/badge.svg)](https://github.com/brooklyn-data/dbt_artifacts/actions/workflows/main.yml) +[![Tier 3 — release validation](https://github.com/brooklyn-data/dbt_artifacts/actions/workflows/release.yml/badge.svg)](https://github.com/brooklyn-data/dbt_artifacts/actions/workflows/release.yml) [![Documentation](https://github.com/brooklyn-data/dbt_artifacts/actions/workflows/publish_docs_on_release.yml/badge.svg)](https://github.com/brooklyn-data/dbt_artifacts/actions/workflows/publish_docs_on_release.yml) ## Contributing -Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) and [MAINTAINERS.md](docs/MAINTAINERS.md) for more information. +Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for +how to set up your environment and submit a PR, and +[docs/dev-workflow.md](docs/dev-workflow.md) for the full +feature-to-release flow. Maintainer-specific guidance lives in +[docs/MAINTAINERS.md](docs/MAINTAINERS.md). ## Supported Data Warehouses diff --git a/compose.yml b/compose.yml new file mode 100644 index 00000000..1db84317 --- /dev/null +++ b/compose.yml @@ -0,0 +1,105 @@ +# Local-runnable data warehouses for dbt_artifacts integration tests. +# +# Used by both local development (`scripts/ci/compose-up.sh`) and GitHub Actions. +# The contract: every service has a healthcheck, so `docker compose up --wait` +# returns only when the stack is actually ready to accept connections. +# +# Ports reserved on the host (deliberately non-standard to avoid clashing +# with anything a developer is already running — local Postgres on 5432, +# whatever's on 8080, etc.). Containers internally still listen on stock +# ports; only the host-side mapping is shifted. profiles.yml hardcodes +# these shifted values so dbt connects to the test stack, not your laptop's +# default services. +# 55432 postgres (container: 5432) +# 58080 trino (container: 8080) +# 51433 sqlserver (container: 1433) +# +# Host-side prerequisites (NOT installed by this file): +# - SQL Server: the Microsoft ODBC Driver 18 must be installed on the host +# because dbt-sqlserver runs on the host and connects into the container. +# macOS: brew tap microsoft/mssql-release https://github.com/Microsoft/homebrew-mssql-release +# brew install msodbcsql18 mssql-tools18 +# Linux: sudo ACCEPT_EULA=Y apt-get install -y msodbcsql18 +# +# Image tags are pinned to specific versions, not floating `:latest`, to +# guarantee local ↔ CI parity. Update tags deliberately, in a single PR. + +services: + postgres: + image: postgres:15-alpine + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: postgres + ports: + - "55432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 5s + timeout: 5s + retries: 10 + start_period: 10s + + trino: + image: trinodb/trino:476 + ports: + - "58080:8080" + environment: + DBT_FILE_FORMAT: csv + volumes: + - ./integration_test_project/.trino-config:/etc/trino + healthcheck: + test: + - "CMD-SHELL" + - "curl -fsS http://localhost:8080/v1/info | grep -q '\"starting\":false'" + interval: 5s + timeout: 5s + retries: 30 + start_period: 30s + + sqlserver: + image: mcr.microsoft.com/mssql/server:2025-latest + user: root + platform: linux/amd64 + environment: + ACCEPT_EULA: "Y" + MSSQL_SA_PASSWORD: "123Administrator" + MSSQL_PID: "DeveloperStandard" + DOCKER_DEFAULT_PLATFORM: "linux/amd64" + ports: + - "51433:1433" + volumes: + - sqlserver_data:/var/opt/mssql + - ./init-scripts/sqlserver:/setup + restart: always + healthcheck: + test: + - "CMD-SHELL" + - "/opt/mssql-tools18/bin/sqlcmd -S 127.0.0.1,1433 -U sa -P ${MSSQL_SA_PASSWORD:-123Administrator} -No -Q 'SELECT 1' || exit 1" + interval: 10s + retries: 10 + start_period: 30s + timeout: 10s + + sqlserver-configurator: + image: mcr.microsoft.com/mssql/server:2025-latest + platform: linux/amd64 + volumes: + - ./init-scripts/sqlserver:/docker-entrypoint-initdb.d + depends_on: + sqlserver: + condition: service_healthy + command: > + bash -c ' + /opt/mssql-tools18/bin/sqlcmd -S sqlserver -U sa -P ${MSSQL_SA_PASSWORD:-123Administrator} -No -d master -i docker-entrypoint-initdb.d/init.sql + && echo "SQL Server bootstrap complete."; + ' + # The configurator exits after init.sql completes; that exit is success. + # `docker compose up --wait` treats a service as healthy when it has no + # healthcheck and exits 0, so this satisfies the wait without a healthcheck. + +volumes: + postgres_data: + sqlserver_data: diff --git a/docs/MAINTAINERS.md b/docs/MAINTAINERS.md index 1a28839c..22c85a3b 100644 --- a/docs/MAINTAINERS.md +++ b/docs/MAINTAINERS.md @@ -1,79 +1,88 @@ # Contributing to the dbt Artifacts Package as a Maintainer -> [!IMPORTANT] -> This guide was written for Brooklyn Data engineers and contains details that are specific to the Brooklyn Data workplace (i.e., Slack channel names), but much of this -> will be useful for all contributors, internal or external to Brooklyn Data. +> [!NOTE] +> Replace the placeholder identifiers below (``, +> ``, etc.) with your team's specifics. The package +> CI uses these via environment variables — no values are baked into +> the repo. Coordinate access to shared test accounts with your team +> in whatever channel your team uses (the project does not assume any +> particular Slack workspace or chat tool). ## General Guidance -- You can create your own branches in the repo - no need to fork. Make sure to ask in the #_dbt_artifacts Slack channel - if you need to be given permissions to contribute. -- If a contributor has a question about a PR, please try to answer it as soon as possible. If you are not sure, ask in - the #_dbt_artifacts Slack channel. -- If you review a PR from a contributor and request changes, please make sure to follow up with them to see if they have - any questions or need help. If you do not receive a response within a few weeks, and you can fix the code and merge it - - do it. If for whatever reasons permissions prevent you from doing so, add a short explanation that you’ve closed the PR - for that reason, copied their work into your branch, and pasted a link so they can follow progress. + +- Maintainers can create branches directly on the repo — no need to + fork. Ask your team for repo permissions if you don't already have + them. +- If a contributor has a question on a PR, try to answer it promptly. +- If you review a PR and request changes, follow up to see whether + the contributor needs help. If you don't hear back within a few + weeks and the fix is small, finish it yourself and merge — leave a + short note on the PR pointing the original contributor to where + their work ended up. ## Warehouse set-up for testing -You can use the following warehouses for testing. The credentials are picked up in the `integration_test_project/profiles.yml` -file via environment variables. So to be able to connect to the different sources, you should include the following in your -`integration_test_project/env.sh` file (these are exported by running the `. ./env.sh` command mentioned below). + +Credentials are read from `integration_test_project/profiles.yml` +via environment variables. Populate `env.sh` (gitignored) and source +it before running tests: `. ./env.sh`. ### Snowflake -This uses our partner Snowflake account. Temporarily, you can make use of the shared CI login credentials. See #_dbt_artifacts -Slack channel for links. + +Coordinate access to your team's shared test Snowflake account through +your team's normal access-request process. ```` # integration_test_project/env.sh -export DBT_ENV_SECRET_SNOWFLAKE_TEST_ACCOUNT=brooklyndatapartner -export DBT_ENV_SECRET_SNOWFLAKE_TEST_USER=dbt_artifacts_ci -export DBT_ENV_SECRET_SNOWFLAKE_TEST_PASSWORD= -export DBT_ENV_SECRET_SNOWFLAKE_TEST_ROLE=public -export DBT_ENV_SECRET_SNOWFLAKE_TEST_DATABASE=dev -export DBT_ENV_SECRET_SNOWFLAKE_TEST_WAREHOUSE=developer +export DBT_ENV_SECRET_SNOWFLAKE_TEST_ACCOUNT= +export DBT_ENV_SECRET_SNOWFLAKE_TEST_USER= +export DBT_ENV_SECRET_SNOWFLAKE_TEST_PASSWORD= +export DBT_ENV_SECRET_SNOWFLAKE_TEST_ROLE= +export DBT_ENV_SECRET_SNOWFLAKE_TEST_DATABASE= +export DBT_ENV_SECRET_SNOWFLAKE_TEST_WAREHOUSE= ```` ### Databricks -This uses the dbt-artifacts project. Add a request in the #_sandbox_stewards Slack channel to be given permissions to use it. -Follow the instructions in [here](https://docs.databricks.com/aws/en/integrations/compute-details) to get the following details -(the Host and Http Path are placeholders to show what they might look like). You will need to get a [personal access token](https://docs.databricks.com/aws/en/dev-tools/auth#personal-access-tokens-for-users) too. + +Get host, HTTP path, and a [personal access token](https://docs.databricks.com/aws/en/dev-tools/auth#personal-access-tokens-for-users) +from your team's Databricks workspace. The +[compute details page](https://docs.databricks.com/aws/en/integrations/compute-details) +shows where to find the host and HTTP path. ```` -export DBT_ENV_SECRET_DATABRICKS_HOST=<>.cloud.databricks.com -export DBT_ENV_SECRET_DATABRICKS_HTTP_PATH=/sql/1.0/warehouses/<> -export DBT_ENV_SECRET_DATABRICKS_TOKEN=abcdefghijklmnop1234567890 +export DBT_ENV_SECRET_DATABRICKS_HOST=.cloud.databricks.com +export DBT_ENV_SECRET_DATABRICKS_HTTP_PATH=/sql/1.0/warehouses/ +export DBT_ENV_SECRET_DATABRICKS_TOKEN= ```` ### BigQuery -This is set up in the dbt-artifacts-ci [project](https://console.cloud.google.com/welcome?project=dbt-artifacts-ci). Add -a request in the #_sandbox_stewards Slack channel to be given permissions to use it. You will need to follow the instructions -[here](https://cloud.google.com/docs/authentication/provide-credentials-adc#how-to) to authorise BigQuery locally. + +Coordinate access to your team's test GCP project. Authorise BigQuery +locally per +[Google's ADC guide](https://cloud.google.com/docs/authentication/provide-credentials-adc#how-to). ```` # integration_test_project/env.sh -export DBT_ENV_SECRET_GCP_PROJECT=dbt-artifacts-ci +export DBT_ENV_SECRET_GCP_PROJECT= ```` -### Postgres -Make sure to download [Docker](https://hub.docker.com/_/postgres), then use the following to spin up a local postgres instance (Make sure that your Dremio -container isn’t also running…). +### Postgres, Trino, SQL Server (local) -```` -docker pull postgres -docker run --name dbt-artifacts-postgres -p 5432:5432 -e POSTGRES_PASSWORD=postgres -e DB_HOST=localhost -d postgres -```` +These run in containers via [`compose.yml`](../compose.yml). You don't +need to `docker run` anything by hand — `scripts/ci/test.sh` brings the +right container up, runs the tests, and tears it down: -### Dremio -Make sure to download [Docker](https://hub.docker.com/_/postgres), then use the following to spin up a local postgres instance (Make sure that your Postgres -container isn’t also running…). +```bash +./scripts/ci/test.sh postgres +./scripts/ci/test.sh trino +./scripts/ci/test.sh sqlserver +``` -```` -docker pull dremio/dremio-oss -docker run --name dbt-artifacts-dremio -p 9047:9047 -p 31010:31010 -p 45678:45678 dremio/dremio-oss -# Stop this running using Ctrl + C -docker start dbt-artifacts-dremio -```` -Navigate to http://localhost:9047/signup and you should see a Dremio sign up screen. Sign up with your details (it’s just a local user). +Note: the host-side ports are deliberately non-standard +(`55432`/`58080`/`51433`) so the test stack never collides with a local +Postgres / web app / SQL Server you might be running. See +[`compose.yml`](../compose.yml) header for details. The SQL Server +target also requires the Microsoft ODBC Driver 18 installed on your +host — see [CONTRIBUTING.md](../CONTRIBUTING.md) for install commands. ## How the package works - The dbt project in the `integration_test_project` is used to test the work that is done in the main folder. If you are running @@ -102,104 +111,128 @@ Navigate to http://localhost:9047/signup and you should see a Dremio sign up scr `upload_*.sql` macros to individually create the results. ## Integration Tests -When you create a PR it will need to pass CI. To get the integration tests to run on your PR you need to approve them. Scroll to the bottom of the PR to find this section: - -![img_2.png](/docs/images/img_2.png) -Click on “Show environments”: +As of the 2026 CI rework, the previous "Approve Integration Tests" +deployment-environment flow has been removed. PR-level CI now runs +**automatically**, without any maintainer approval step, but is also +**scoped to warehouses that don't need secrets** — Postgres, Trino, +SQL Server. Snowflake / BigQuery validation happens automatically after +you merge the PR. -![img_3.png](/docs/images/img_3.png) +For the full end-to-end flow (what fires on a PR vs. on push to `main` +vs. on a `release-candidate/X.Y.Z` branch), see +[docs/dev-workflow.md](dev-workflow.md). For the underlying design and +threat model, see [specs/ci-rework/README.md](../specs/ci-rework/README.md). -Click on the blue link (i.e. integration-snowflake #352) and it will open up the action. Look for this section: +### What you'll see on a PR -![img_4.png](/docs/images/img_4.png) +| | Where it runs | When | +|---|---|---| +| Postgres, Trino, SQL Server (Docker) | `pr.yml` (Tier 1) | Every PR push | +| Lint, Snowflake, BigQuery | `main.yml` (Tier 2) | After merge to `main` | +| Full warehouse × dbt-version matrix | `release.yml` (Tier 3) | Push to `release-candidate/X.Y.Z` | -Click on “Review deployments": +### When a Tier 2 failure surfaces after merge -![img_5.png](/docs/images/img_5.png) +You have two options: -Make sure to check “Approve Integration Tests” and then click “Approve and deploy”. If you head back to the PR you should see the tests are now running: - -![img_6.png](/docs/images/img_6.png) - -(If you notice the checks are still in status “Waiting” you may have to “Approve and deploy” again) +1. Revert the merge commit on `main`. +2. Push a follow-up fix through the normal PR flow (which will run + Tier 1 on the PR, then Tier 2 on the post-merge push). ## How to test / verify issues locally + > [!NOTE] -> If you want to test an existing PR opened by another contributor you will have to add their repo as a new remote -> ````` -> git remote add their-username https://github.com/their-username/repo-name +> To test a PR opened by an external contributor, add their fork as a +> remote: +> ``` +> git remote add their-username https://github.com/their-username/dbt_artifacts > git fetch their-username -> ````` +> git checkout their-username/ +> ``` -- Following instructions in the [Contributing README](https://github.com/brooklyn-data/dbt_artifacts/blob/main/CONTRIBUTING.md#setting-up-your-development-environment) to - get the `integration_test_project/env.sh` file ready, and update using the credentials above, as well as updating the `GITHUB_SHA=_test` to - include your initials (e.g. `gd_test`), and setting the version number in `DBT_VERSION` to a version you want to use e.g. `1_5_0`. -- Test you can run it with the commands included in the README: - ```` - cd integration_test_project - . ./env.sh - dbt deps - tox -e integration_snowflake # For the Snowflake tests - tox -e integration_databricks # For the Databricks tests (if used) - tox -e integration_bigquery # For the BigQuery tests (if used) - tox -e integration_postgres # For the Postgres tests (if used) - ```` -- You can check results within the warehouse using: - ```` - use database dev; -- For Snowflake - select * from dbt_artifacts_test_commit___test.; - ```` -- Before doing development it’s good to make sure the structure you are testing against is the same as main so drop the schema using: - ```` - drop dbt_artifacts_test_commit___test. cascade; - ```` - Then run against the latest version of main: - ```` - git checkout main && git pull origin main - tox -e integration_<> # Run on any warehouses you are testing on - ```` - Once the models have been built from what is on main, then you can checkout the branch you want to test and run them from there. -- If you want to test specific tables, you might want to create different environment for the adapters. You could use dbtenv for - this, or pyenv if you are more comfortable with that. This is how I set it up with [pyenv virtualenv](https://github.com/pyenv/pyenv-virtualenv): - ```` - # Install latest 3.8 release (matches the CI test version) - pyenv install 3.8.16 - - # Snowflake environment - pyenv virtualenv 3.8.16 dbt-artifacts-snowflake - pyenv activate dbt-artifacts-snowflake - pip install dbt-snowflake~=1.6.0 - pyenv local dbt-artifacts-snowflake # set as default - optional - - # Bigquery environment (if using) - pyenv virtualenv 3.8.16 dbt-artifacts-bigquery - pyenv activate dbt-artifacts-bigquery - pip install dbt-bigquery~=1.6.0 - - # Databricks environment (if using) - pyenv virtualenv 3.8.16 dbt-artifacts-databricks - pyenv activate dbt-artifacts-databricks - pip install dbt-databricks~=1.6.0 - - # Postgres environment (if using) - pyenv virtualenv 3.8.16 dbt-artifacts-postgres - pyenv activate dbt-artifacts-postgres - pip install dbt-postgres~=1.6.0 - ```` - To test on a specific environment, you will need to activate the environment first, then get dbt to run using the appropriate target - e.g. to test the `aliased` table with `bigquery` - ```` - pyenv activate dbt-artifacts-bigquery - dbt run --select aliased --target bigquery - ```` -> [!NOTE] -> If you don’t want to keep adding `--target bigquery` to your dbt commands, you can temporarily set it as the default in the `integration_test_project/profiles.yml` file by changing the target to `bigquery` at the top of the `dbt_artifacts` definition. +### Quick path + +1. One-time setup per [CONTRIBUTING.md](../CONTRIBUTING.md): `uv` and + `docker` installed, `env.sh` populated for any cloud warehouses + you'll use. +2. Source credentials and run: + ```bash + . ./env.sh + ./scripts/ci/test.sh snowflake # or bigquery / postgres / trino / sqlserver + ``` +3. Optional: pin a specific dbt version: + ```bash + ./scripts/ci/test.sh snowflake 1_10_0 + ``` + +The script defaults `GITHUB_SHA` to your current git HEAD short SHA +prefixed with `local_`, so your schemas won't collide with CI runs. +You can override it: `export GITHUB_SHA=local_` before +running if you want stable schema names across runs. + +### Checking results in the warehouse + +```sql +-- Snowflake +use database dev; +select * from dbt_artifacts_test_commit__.; + +-- (Note: the dbt_version segment of the schema is empty when running +-- the unversioned env, populated when running a pinned env like 1_10_0.) +``` + +### Testing against `main` before your branch + +To verify the diff you're introducing rather than the cumulative state: + +```bash +git checkout main && git pull +./scripts/ci/test.sh +# now switch to your branch +git checkout +./scripts/ci/test.sh +``` + +If you'd like a fully clean rebuild, drop the test schema first: + +```sql +drop schema dbt_artifacts_test_commit__ cascade; +``` + +### Testing against multiple dbt versions + +`tox` (invoked by `scripts/ci/test.sh`) creates an isolated venv per +env automatically. You no longer need `pyenv virtualenv` or per-version +manual setup — just pass the version: + +```bash +./scripts/ci/test.sh snowflake 1_8_0 +./scripts/ci/test.sh snowflake 1_11_0 +``` + +See `tox.ini` for the full list of pinned versions per adapter. ## How to release -- Use [semantic versioning](https://semver.org/) to work out whether it’s a patch, minor or major change. If it contains breaking changes (including new fields), then it should be at least a minor change. -- Do a find and replace of the current version (e.g. `2.2.1`) with the new version (e.g. `2.2.2`) -- Merge a PR which applies that change -- Make a new release: [Releases · brooklyn-data/dbt_artifacts](https://github.com/brooklyn-data/dbt_artifacts/releases) - - This will automatically update the documentation through a GH action - - Per the [dbt guidance](https://docs.getdbt.com/guides/legacy/building-packages), the repo ([GitHub - dbt-labs/hubcap](https://github.com/dbt-labs/hubcap)) that adds releases to dbt Hub will run every hour and pick up any new versions. It will only pick up full versions, so use the guidance if you want to create a pre-release. + +The release procedure has changed as of the 2026 CI rework. See +[docs/dev-workflow.md → Stage 3 & 4](dev-workflow.md) for the full +walkthrough. Summary: + +1. From up-to-date `main`, create `release-candidate/X.Y.Z`. +2. Bump the version on that branch in `dbt_project.yml` and the + `packages.yml` example in `README.md`. Push. +3. Watch Tier 3 (`release.yml`) — the full warehouse × dbt-version + matrix runs. ~30–40 minutes. +4. When green, [create a GitHub Release](https://github.com/brooklyn-data/dbt_artifacts/releases/new) + tagged `X.Y.Z`, targeting the candidate branch. +5. The docs site rebuilds automatically via + `publish_docs_on_release.yml`. dbt Hub picks up the new tag within + an hour via [dbt-labs/hubcap](https://github.com/dbt-labs/hubcap) + (full releases only — see hubcap guidance for pre-releases). +6. Delete the `release-candidate/X.Y.Z` branch unless you anticipate a + same-day hotfix on the same minor. + +Use [semantic versioning](https://semver.org/). New fields are always +at least a minor bump (consumers must re-run `dbt run --select +dbt_artifacts` after upgrading or the on-run-end hook errors). diff --git a/docs/dev-workflow.md b/docs/dev-workflow.md new file mode 100644 index 00000000..42f2d93e --- /dev/null +++ b/docs/dev-workflow.md @@ -0,0 +1,218 @@ +# Development workflow: feature → release + +This doc walks through every stage of getting a change from a feature +branch to a tagged release on GitHub, including which CI workflow fires at +each step and why. It is the operational counterpart to the high-level +design in [`specs/ci-rework/README.md`](../specs/ci-rework/README.md). + +If you are new to the project, read [CONTRIBUTING.md](../CONTRIBUTING.md) +first. + +## At a glance + +``` + feature/* ─────PR─────► main ────push─────► release-candidate/X.Y.Z ──tag──► X.Y.Z + │ │ │ │ + ▼ ▼ ▼ ▼ + Tier 1 CI Tier 2 CI Tier 3 CI publish_docs + (pr.yml) (main.yml) (release.yml) (existing) + no secrets secrets, Snowflake + secrets, full release tag + lint disabled¹ BigQuery, latest dbt version matrix triggers + docs site +``` + +¹ Lint is currently in Tier 2 only because the package's models call +adapter methods at compile time and need a real Snowflake connection. +Tracked for fix in [specs §12.2](../specs/ci-rework/README.md). + +--- + +## Stage 1 — Feature branch + PR + +### As a fork contributor (external) + +1. Fork the repo, clone your fork, create a branch. +2. Make your changes. Use `./scripts/ci/test.sh ` to test + locally against any warehouse you have access to. Postgres / Trino / + SQL Server run in Docker containers — no warehouse credentials + needed for these. +3. Open a PR against `brooklyn-data/dbt_artifacts:main`. + +**What you'll see on your PR:** + +- **Tier 1 CI runs** ([`pr.yml`](../.github/workflows/pr.yml)). One job + per local-runnable warehouse (Postgres, Trino, SQL Server). Each + spins up a container, runs the integration tests, tears down. +- **No lint, no Snowflake / BigQuery / Databricks signal on your PR.** + This is deliberate — those require secrets, which forks cannot + access by design. A maintainer will run the higher-tier checks for + you after they merge (Stage 2). +- The matrix is configured with `fail-fast: false`, so a break in one + warehouse does not hide results from the others. + +If Tier 1 passes, a maintainer will review. If it fails, fix it locally +(`./scripts/ci/test.sh `) and push again — the same CI fires +on every push. + +### As an internal contributor + +Same flow as a fork. Internal contributors **also** PR against `main` +— there is no shortcut, no privileged branch. The PR-level CI is +identical to what a fork sees. The only difference is internal +contributors typically have their own warehouse credentials and can +run all warehouses locally before opening the PR. + +--- + +## Stage 2 — Merge to `main` + +After review and approval, the maintainer merges the PR. This is the +trust boundary — **only code merged through a reviewed PR ever sees +repository secrets.** + +**What fires on push to `main`** ([`main.yml`](../.github/workflows/main.yml)): + +| Job | What it does | Secrets used | +|---|---|---| +| `lint` | sqlfluff against `models/` | Snowflake (templater needs a real connection) | +| `integration-local` (matrix) | Re-runs Postgres / Trino / SQL Server against merged code | None | +| `integration-snowflake` | Latest `dbt-snowflake` against the test Snowflake account | Snowflake | +| `integration-bigquery` | Latest `dbt-bigquery` via Workload Identity Federation | GCP (WIF — keyless) | + +If Tier 2 fails after a merge, the maintainer either: + +- Reverts the merge commit on `main`, or +- Pushes a follow-up fix (which itself goes through a PR and Tier 1 + again, then Tier 2 reruns on push). + +This is also where Snowflake / BigQuery regressions get caught for +changes contributed by forks who couldn't run those tests themselves. + +--- + +## Stage 3 — Cut a `release-candidate` branch + +When you have a set of merged changes ready to ship (~monthly cadence), +cut the release candidate. **Recommended path**: use the cutter, which +auto-bumps the version in `dbt_project.yml` and `README.md`, creates +the branch, commits, and pushes — one step: + +**Via GitHub UI** (`workflow_dispatch`): + +1. Actions tab → "Cut release-candidate" workflow → "Run workflow" +2. Pick `patch`, `minor`, or `major` (or paste an explicit `X.Y.Z`) +3. Click "Run" + +**Or locally** (same script — single source of truth): + +```bash +git checkout main && git pull +./scripts/release/cut-candidate.py --minor # or --patch / --major / --version X.Y.Z +``` + +This automation is **bump, not merge**: the maintainer still reviews +Tier 3 results and drives the tag manually (Stage 4). + +**What fires** ([`release.yml`](../.github/workflows/release.yml)): + +| Job | What it does | +|---|---| +| `lint` | Same as Tier 2 | +| `version-matrix` | 42 entries: every supported (warehouse, dbt_version) pair, plus an unversioned "latest" per warehouse. `max-parallel: 8`. | +| `integration-databricks-stub` | Visible-but-skipped placeholder until Databricks is reactivated (see [specs §12.3](../specs/ci-rework/README.md)). | + +This typically takes 30–40 minutes. Watch the Actions tab. If anything +goes red, you have two choices: + +1. **Fix forward on the release-candidate branch.** Push commits + directly to `release-candidate/X.Y.Z` — Tier 3 reruns. Use this + when the fix is small and clearly part of the release scope. +2. **Fix via a PR to `main`, then re-cut.** Delete the + release-candidate branch, fix on a new feature branch through the + normal PR flow, then re-create the release-candidate branch from + the updated `main`. Use this when the fix is substantive enough + to deserve PR review. + +--- + +## Stage 4 — Tag and release + +Once `release.yml` is green on the candidate branch: + +1. Open a PR from `release-candidate/X.Y.Z` to `main` (if there were + commits made directly to the candidate branch). Otherwise skip — + the candidate branch already matches `main` at the head it was + created from. +2. Merge the PR (or confirm the branches are in sync). +3. Create a [GitHub Release](https://github.com/brooklyn-data/dbt_artifacts/releases/new): + - **Tag**: `X.Y.Z` + - **Target**: `main` (the merge commit if you opened a PR, or the + candidate branch head if it matches) + - Title and notes summarizing the changes +4. Publish. + +**What fires**: [`publish_docs_on_release.yml`](../.github/workflows/publish_docs_on_release.yml) +rebuilds the docs site. dbt Hub picks up the new release within an hour +via [dbt-labs/hubcap](https://github.com/dbt-labs/hubcap). + +5. **Delete the `release-candidate/X.Y.Z` branch.** It's served its + purpose. Keep it around only if you anticipate a same-day hotfix + targeting the same minor. + +--- + +## Hotfix flow + +For a critical bug on a released version: + +``` + hotfix/critical-bug ───PR───► main ───push───► release-candidate/X.Y.Z+1 + │ │ │ + ▼ ▼ ▼ + Tier 1 CI Tier 2 CI Tier 3 CI + then tag +``` + +Hotfixes are not a separate code path — they're feature branches with a +narrow scope. Same PR → main → release-candidate → tag flow as Stage 1–4. +The only difference is urgency: maintainers may compress the timeline +(e.g., merge same-day rather than holding for a batch). + +If a hotfix targets an older minor (you cannot fix-forward to the +latest), branch from the tag of that minor instead of `main`, then PR +into a `release-candidate/X.Y.Z+1` branch directly. This is rare and +not currently automated — talk to maintainers before attempting. + +--- + +## Workflow file → tier mapping + +| File | Tier | When it runs | Secrets | +|---|---|---|---| +| [`pr.yml`](../.github/workflows/pr.yml) | 1 | `pull_request` → `main` | **None** | +| [`main.yml`](../.github/workflows/main.yml) | 2 | `push` → `main`, manual | Snowflake, GCP WIF | +| [`release.yml`](../.github/workflows/release.yml) | 3 | `push` → `release-candidate/**`, manual, **Mondays 06:00 UTC** (weekly regression on `main`) | Snowflake, GCP WIF | +| [`cut-release-candidate.yml`](../.github/workflows/cut-release-candidate.yml) | — | `workflow_dispatch` only — auto-bumps and pushes a `release-candidate/X.Y.Z` branch | `contents: write` (push only) | +| `publish_docs_on_release.yml` | 4 | Release tag created | (none — docs only) | + +### Weekly regression detail + +Every Monday at 06:00 UTC the full Tier 3 matrix runs against the +current head of `main`. This catches drift from upstream dbt adapter +releases that land between our scheduled releases — e.g., a new +`dbt-snowflake` minor that breaks our package. If the weekly run +fails, treat it like any other Tier 2/3 failure: investigate, fix +forward via a PR, or pin the offending adapter version. + +--- + +## See also + +- [`CONTRIBUTING.md`](../CONTRIBUTING.md) — how to set up your local + environment and submit a first PR +- [`docs/MAINTAINERS.md`](MAINTAINERS.md) — maintainer-specific + guidance (warehouse credentials, release procedure detail) +- [`scripts/ci/README.md`](../scripts/ci/README.md) — script-by-script + reference for the local CI shims +- [`specs/ci-rework/README.md`](../specs/ci-rework/README.md) — design rationale, + threat model, tech debt backlog diff --git a/init-scripts/sqlserver/init.sql b/init-scripts/sqlserver/init.sql new file mode 100644 index 00000000..0d4e89c2 --- /dev/null +++ b/init-scripts/sqlserver/init.sql @@ -0,0 +1,29 @@ +USE [master]; +GO + +IF NOT EXISTS (SELECT * FROM sys.sql_logins WHERE name = 'dbt') +BEGIN + CREATE LOGIN [dbt] WITH PASSWORD = '123Administrator', CHECK_POLICY = OFF; + ALTER SERVER ROLE [sysadmin] ADD MEMBER [dbt]; +END +GO + +IF DB_ID('dbt_artifact_integrationtests') IS NOT NULL +BEGIN + ALTER DATABASE [dbt_artifact_integrationtests] SET SINGLE_USER WITH ROLLBACK IMMEDIATE; + DROP DATABASE [dbt_artifact_integrationtests]; +END +GO + +CREATE DATABASE [dbt_artifact_integrationtests]; +GO + +USE [dbt_artifact_integrationtests]; +GO + +IF NOT EXISTS (SELECT * FROM sys.sql_logins WHERE name = 'dbt') +BEGIN + CREATE LOGIN [dbt] WITH PASSWORD = '123Administrator', CHECK_POLICY = OFF; + ALTER SERVER ROLE [sysadmin] ADD MEMBER [dbt]; +END +GO diff --git a/integration_test_project/docker-compose.yml b/integration_test_project/docker-compose.yml deleted file mode 100644 index f0163999..00000000 --- a/integration_test_project/docker-compose.yml +++ /dev/null @@ -1,36 +0,0 @@ -services: - postgres: - image: postgres:15 - environment: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_DB: postgres - ports: - - "5432:5432" - volumes: - - postgres_data:/var/lib/postgresql/data - - trino: - image: trinodb/trino:latest - ports: - - "8080:8080" - environment: - DBT_FILE_FORMAT: csv - volumes: - - ./.trino-config:/etc/trino - -# sqlserver: -# image: mcr.microsoft.com/mssql/server:2022-latest -# environment: -# ACCEPT_EULA: "Y" -# SA_PASSWORD: "123Administrator" -# MSSQL_PID: "Developer" -# DOCKER_DEFAULT_PLATFORM: "linux/amd64" -# ports: -# - "1433:1433" -# volumes: -# - sqlserver_data:/var/opt/mssql - -volumes: - postgres_data: -# sqlserver_data: diff --git a/integration_test_project/models/microbatch.sql b/integration_test_project/models/microbatch.sql index 29356124..0aa1e929 100644 --- a/integration_test_project/models/microbatch.sql +++ b/integration_test_project/models/microbatch.sql @@ -11,7 +11,9 @@ 'field': 'transaction_ts', 'data_type': 'datetime', 'granularity': 'day' - } + }, + + enabled = target.type != 'trino' ) }} diff --git a/integration_test_project/profiles.yml b/integration_test_project/profiles.yml index 84d85ddc..e12458c0 100644 --- a/integration_test_project/profiles.yml +++ b/integration_test_project/profiles.yml @@ -43,20 +43,23 @@ dbt_artifacts: timeout_seconds: 300 priority: interactive retries: 1 + # Ports for postgres/sqlserver/trino are deliberately non-standard so + # they don't collide with a developer's locally-running services. See + # the header of compose.yml at the repo root for the full rationale. postgres: type: postgres host: localhost user: postgres password: postgres - port: 5432 + port: 55432 dbname: postgres schema: public threads: 8 sqlserver: type: sqlserver driver: 'ODBC Driver 18 for SQL Server' - server: localhost - port: 1433 + server: 127.0.0.1 + port: 51433 database: dbt_artifact_integrationtests schema: dbo windows_login: False @@ -72,5 +75,5 @@ dbt_artifacts: host: localhost database: memory schema: default - port: 8080 + port: 58080 threads: 8 diff --git a/integration_test_project/run_tests.sh b/integration_test_project/run_tests.sh deleted file mode 100755 index e98c72b1..00000000 --- a/integration_test_project/run_tests.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash - -# Start all services -docker-compose up -d - -# Wait for services to be ready -echo "Waiting for services to be ready..." -for i in {1..15}; do echo -n "..."; sleep 1; done -echo "" -echo "Running tests" - -# Run tests for each database -for db in postgres trino; do - echo "================================================" - echo "Running tests for $db..." - echo "================================================" - dbt deps --quiet - dbt build --target $db --quiet --full-refresh -done - -# Stop all services -docker-compose down diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..ffab540c --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,32 @@ +# Python toolchain manifest for the dbt_artifacts package. +# +# Everything here is consumed by `uv sync` (invoked from +# `scripts/ci/setup.sh` both locally and in CI). This file and the +# accompanying `uv.lock` are tracked so fresh clones reproduce the +# exact same dependency tree on every machine and runner. +# +# Per-dbt-version adapter installs are handled separately by `tox` +# (`tox.ini`) — those deliberately are NOT pinned at the top level +# here, so individual `tox -e integration__X_Y_Z` envs +# can install their own pinned adapter version in an isolated venv. + +[project] +name = "dbt-artifacts" +version = "0.1.0" +description = "Multi-adapter dbt package that builds a mart of artifacts about your dbt project." +readme = "README.md" +requires-python = ">=3.12" +dependencies = [ + "dbt-bigquery>=1.9.1", + "dbt-postgres>=1.9.0", + "dbt-snowflake>=1.9.2", + "tox>=4.25.0", + # pyarrow is pulled in transitively by google-cloud-bigquery / db-dtypes / + # pandas-gbq. We don't use it directly — the explicit cap here exists only + # to protect against a too-new pyarrow that would break those BigQuery + # transitive deps. The `[pandas]` extra previously declared here doesn't + # exist on pyarrow and emitted a uv warning; dropped. + "pyarrow<19.0.0", + "dbt-trino>=1.9.1", + "dbt-sqlserver>=1.8.7", +] diff --git a/scripts/ci/README.md b/scripts/ci/README.md new file mode 100644 index 00000000..458ff796 --- /dev/null +++ b/scripts/ci/README.md @@ -0,0 +1,120 @@ +# scripts/ci/ + +Single source of truth for how `dbt_artifacts` integration tests run. + +GitHub Actions workflows in `.github/workflows/` are thin shells that +`checkout → setup → invoke a script here`. Local development invokes the +**same scripts**. There is no second implementation that "almost matches" CI. + +See [`specs/ci-rework/README.md`](../../specs/ci-rework/README.md) for the design +rationale and the three-tier CI model these scripts feed into. + +## Entry points + +| Script | Purpose | +|---|---| +| `setup.sh` | `uv sync` — install Python deps. Idempotent. | +| `compose-up.sh [services...]` | Start local DWH containers; wait for healthy. | +| `compose-down.sh` | Tear down containers and volumes. | +| `lint.sh [--fix] [path]` | SQLFluff lint (or fix) on `models/`. | +| `test.sh []` | Run integration tests against one warehouse. | +| `test-all-local.sh []` | Run `test.sh` for every local-runnable warehouse. | + +Supported `` values: `postgres`, `trino`, `sqlserver`, `snowflake`, +`bigquery`, `databricks`, `spark`. The first three run locally via +`compose.yml`; the rest require credentials in env vars. + +`` follows the tox-env naming convention, e.g. `1_9_0`, `1_8_0`. +Omit for the latest supported adapter version. + +## Quick start + +```bash +# One-time setup +./scripts/ci/setup.sh + +# Lint +./scripts/ci/lint.sh + +# Test one local warehouse +./scripts/ci/test.sh postgres + +# Test every local warehouse (what Tier 1 CI runs) +./scripts/ci/test-all-local.sh + +# Test a cloud warehouse (env vars must be set) +. ./env.sh +./scripts/ci/test.sh snowflake +``` + +## Host ports + +The compose stack binds to **non-standard host ports** so it never clashes +with services a developer is already running locally: + +| Warehouse | Host port | Container port | +|---|---|---| +| postgres | `55432` | `5432` | +| trino | `58080` | `8080` | +| sqlserver | `51433` | `1433` | + +`integration_test_project/profiles.yml` is wired to these shifted ports. +You don't need to remember them — every test path is already configured. +If you reach for `psql -h localhost` out of habit, you'll hit your **local** +Postgres, not the test container; that's intentional. + +## Host-side prerequisites + +- **`uv`** — Python toolchain. See https://docs.astral.sh/uv/. +- **`docker`** — required for `postgres`, `trino`, `sqlserver` paths. +- **Microsoft ODBC Driver 18** — required on the host (not in the SQL Server + container) for the `sqlserver` warehouse. The dbt-sqlserver adapter runs on + the host and connects into the container. + - macOS: + ```bash + brew tap microsoft/mssql-release https://github.com/Microsoft/homebrew-mssql-release + brew install msodbcsql18 mssql-tools18 + ``` + - Linux (Debian/Ubuntu): + ```bash + sudo ACCEPT_EULA=Y apt-get install -y msodbcsql18 + ``` +- For **`bigquery`**: either run with workload-identity credentials (CI), or + `gcloud auth application-default login` locally. + +## Conventions for editing these scripts + +- `set -euo pipefail` at the top of every script. +- `source _lib.sh` for shared helpers (`require_env`, `require_cmd`, + `banner`, `die`, `ensure_github_sha`). +- **No GitHub-Actions-isms** (no `$GITHUB_*` vars used as control flow, no + `::set-output`, no conditional on CI environment). Scripts behave + identically on a laptop and on a runner. +- Validate required env vars up front with `require_env`. +- Validate required binaries up front with `require_cmd`. +- Always operate from `${repo_root}` (already `cd`'d by `_lib.sh`). +- Per-warehouse cleanup is the responsibility of the script that brought + state up. Use `trap` for compose teardown so failures don't leak containers. + +## Known pre-existing constraints + +These are package-level issues surfaced (not introduced) by this script layer. +Tracked for fixing in follow-up work; documented here so they don't surprise +you the first time you run the scripts. + +- **Lint requires real Snowflake credentials.** `lint.sh` uses the dbt + templater against `profiles.yml`'s default target, and some models in + this package call adapter methods at compile time. You need a valid + `DBT_ENV_SECRET_SNOWFLAKE_TEST_*` set in your environment. The active CI + lint workflow does the same — it's not a CI-only requirement. +## Debugging a failed run + +Pass `KEEP_COMPOSE=1` to skip the teardown trap, then poke at containers: + +```bash +KEEP_COMPOSE=1 ./scripts/ci/test.sh postgres +docker compose ps +docker compose logs postgres +# When done: +./scripts/ci/compose-down.sh +``` diff --git a/scripts/ci/_lib.sh b/scripts/ci/_lib.sh new file mode 100755 index 00000000..82df5407 --- /dev/null +++ b/scripts/ci/_lib.sh @@ -0,0 +1,79 @@ +# Shared helpers for scripts/ci/*.sh. Source, do not execute. +# +# Conventions: +# - All scripts `set -euo pipefail`. +# - All scripts `cd` to the repo root via `repo_root` before doing work. +# - All scripts validate their env contract via `require_env` before running. + +# Resolve the repo root from this file's location. Works regardless of the +# caller's cwd. Realpath via a portable shell idiom (no `realpath` binary). +_lib_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +repo_root="$(cd "${_lib_dir}/../.." && pwd)" + +# Print a banner. Used to visually separate phases when running test-all-local. +banner() { + printf '\n================================================\n' + printf '%s\n' "$*" + printf '================================================\n' +} + +# Print to stderr. +log() { + printf '%s\n' "$*" >&2 +} + +# Abort with a message. +die() { + log "error: $*" + exit 1 +} + +# Validate that each named env var is set and non-empty. +# Usage: require_env VAR1 VAR2 VAR3 +require_env() { + local missing=() + local var + for var in "$@"; do + if [[ -z "${!var:-}" ]]; then + missing+=("${var}") + fi + done + if (( ${#missing[@]} > 0 )); then + die "missing required env vars: ${missing[*]}" + fi +} + +# Require that a command is on PATH. +# Usage: require_cmd uv docker +require_cmd() { + local missing=() + local cmd + for cmd in "$@"; do + if ! command -v "${cmd}" >/dev/null 2>&1; then + missing+=("${cmd}") + fi + done + if (( ${#missing[@]} > 0 )); then + die "missing required commands: ${missing[*]}" + fi +} + +# Set GITHUB_SHA if it's not already set. Used by integration_test_project +# profiles.yml to generate per-run schema names. CI sets this automatically; +# local runs need a value so dbt schemas don't collide. +ensure_github_sha() { + if [[ -z "${GITHUB_SHA:-}" ]]; then + if command -v git >/dev/null 2>&1 && git -C "${repo_root}" rev-parse HEAD >/dev/null 2>&1; then + export GITHUB_SHA="local_$(git -C "${repo_root}" rev-parse --short HEAD)" + else + export GITHUB_SHA="local_$(date +%s)" + fi + log "GITHUB_SHA not set; using ${GITHUB_SHA}" + fi +} + +# Set DBT_VERSION (used in schema names) if not set. Defaults to empty (which +# is what the `integration_` envs use — no version suffix). +ensure_dbt_version() { + export DBT_VERSION="${DBT_VERSION:-}" +} diff --git a/scripts/ci/compose-down.sh b/scripts/ci/compose-down.sh new file mode 100755 index 00000000..8695ec61 --- /dev/null +++ b/scripts/ci/compose-down.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# Tear down local data warehouse containers and remove their volumes. +# +# Usage: scripts/ci/compose-down.sh + +set -euo pipefail +source "$(dirname "${BASH_SOURCE[0]}")/_lib.sh" + +cd "${repo_root}" + +require_cmd docker + +banner "Stopping containers and removing volumes" +docker compose down -v --remove-orphans + +log "compose stack torn down" diff --git a/scripts/ci/compose-up.sh b/scripts/ci/compose-up.sh new file mode 100755 index 00000000..87664496 --- /dev/null +++ b/scripts/ci/compose-up.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# Bring up local data warehouse containers and wait for them to be healthy. +# +# Usage: +# scripts/ci/compose-up.sh # all services +# scripts/ci/compose-up.sh postgres # one service (plus its dependencies) +# scripts/ci/compose-up.sh trino sqlserver +# +# Uses `docker compose up --wait`, which exits 0 only when every requested +# service is healthy (or has run-to-completion successfully, in the case of +# the sqlserver-configurator init job). + +set -euo pipefail +source "$(dirname "${BASH_SOURCE[0]}")/_lib.sh" + +cd "${repo_root}" + +require_cmd docker + +# Map the warehouse short-name a user might pass to its compose service set. +# `sqlserver` requires the configurator sidecar to run init.sql. +services=() +if (( $# == 0 )); then + services=(postgres trino sqlserver sqlserver-configurator) +else + for svc in "$@"; do + case "${svc}" in + sqlserver) + services+=(sqlserver sqlserver-configurator) + ;; + postgres|trino|sqlserver-configurator) + services+=("${svc}") + ;; + *) + die "unknown service: ${svc} (valid: postgres, trino, sqlserver)" + ;; + esac + done +fi + +banner "Starting services: ${services[*]}" +docker compose up -d --wait "${services[@]}" + +log "all requested services are healthy" diff --git a/scripts/ci/lint.sh b/scripts/ci/lint.sh new file mode 100755 index 00000000..526d1532 --- /dev/null +++ b/scripts/ci/lint.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash +# Run SQLFluff against the dbt models. +# +# Usage: +# scripts/ci/lint.sh # lint everything in models/ +# scripts/ci/lint.sh models/path/file.sql # lint a single file +# scripts/ci/lint.sh --fix # auto-fix everything in models/ +# scripts/ci/lint.sh --fix models/path/file.sql + +set -euo pipefail +source "$(dirname "${BASH_SOURCE[0]}")/_lib.sh" + +cd "${repo_root}" + +require_cmd uv + +# Lint uses the dbt templater, which compiles every model. Some models in +# this package call adapter methods at compile time (e.g. `run_query`), so +# the templater needs a **real** working connection to the default target +# in profiles.yml — currently `snowflake`. This means linting locally +# requires valid Snowflake credentials in the environment, the same way +# the CI lint workflow injects them from secrets. +# +# The dummies below let `env_var()` resolution succeed for the few targets +# that do *not* introspect at compile time, but if you don't have real +# Snowflake creds set, lint will fail with a clearer "could not connect" +# error rather than a cryptic Jinja env_var miss. +: "${DBT_ENV_SECRET_SNOWFLAKE_TEST_ACCOUNT:=lint-only}" +: "${DBT_ENV_SECRET_SNOWFLAKE_TEST_USER:=lint-only}" +: "${DBT_ENV_SECRET_SNOWFLAKE_TEST_PASSWORD:=lint-only}" +: "${DBT_ENV_SECRET_SNOWFLAKE_TEST_ROLE:=lint-only}" +: "${DBT_ENV_SECRET_SNOWFLAKE_TEST_DATABASE:=lint-only}" +: "${DBT_ENV_SECRET_SNOWFLAKE_TEST_WAREHOUSE:=lint-only}" +: "${GITHUB_SHA:=lint-local}" +export DBT_ENV_SECRET_SNOWFLAKE_TEST_ACCOUNT \ + DBT_ENV_SECRET_SNOWFLAKE_TEST_USER \ + DBT_ENV_SECRET_SNOWFLAKE_TEST_PASSWORD \ + DBT_ENV_SECRET_SNOWFLAKE_TEST_ROLE \ + DBT_ENV_SECRET_SNOWFLAKE_TEST_DATABASE \ + DBT_ENV_SECRET_SNOWFLAKE_TEST_WAREHOUSE \ + GITHUB_SHA + +# Parse: optional `--fix` flag, then optional path. +fix=0 +if [[ "${1:-}" == "--fix" ]]; then + fix=1 + shift +fi + +if (( fix )); then + if (( $# == 0 )); then + banner "sqlfluff fix (all models)" + uv run tox -e fix_all + else + banner "sqlfluff fix: $*" + uv run tox -e fix -- "$@" + fi +else + if (( $# == 0 )); then + banner "sqlfluff lint (all models)" + uv run tox -e lint_all + else + banner "sqlfluff lint: $*" + uv run tox -e lint -- "$@" + fi +fi diff --git a/scripts/ci/setup.sh b/scripts/ci/setup.sh new file mode 100755 index 00000000..f8a01e6a --- /dev/null +++ b/scripts/ci/setup.sh @@ -0,0 +1,17 @@ +#!/usr/bin/env bash +# Install / refresh Python dependencies via uv. +# Idempotent: safe to run repeatedly. +# +# Usage: scripts/ci/setup.sh + +set -euo pipefail +source "$(dirname "${BASH_SOURCE[0]}")/_lib.sh" + +cd "${repo_root}" + +require_cmd uv + +banner "Syncing Python dependencies (uv sync)" +uv sync + +log "setup complete" diff --git a/scripts/ci/test-all-local.sh b/scripts/ci/test-all-local.sh new file mode 100755 index 00000000..65f4a3b9 --- /dev/null +++ b/scripts/ci/test-all-local.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# Run integration tests against every local-runnable data warehouse. +# Convenience wrapper around test.sh — same script CI calls for Tier 1. +# +# Usage: scripts/ci/test-all-local.sh [] +# +# Continues past a single warehouse's failure so you see the full picture +# rather than stopping at the first red. Aggregates exit codes at the end. + +set -euo pipefail +source "$(dirname "${BASH_SOURCE[0]}")/_lib.sh" + +cd "${repo_root}" + +dbt_version="${1:-}" + +warehouses=(postgres trino sqlserver) +failed=() + +for warehouse in "${warehouses[@]}"; do + banner "=== ${warehouse} ===" + if "${repo_root}/scripts/ci/test.sh" "${warehouse}" "${dbt_version}"; then + log "${warehouse}: PASS" + else + log "${warehouse}: FAIL" + failed+=("${warehouse}") + fi +done + +banner "Summary" +for warehouse in "${warehouses[@]}"; do + status="PASS" + for f in "${failed[@]:-}"; do + [[ "${f}" == "${warehouse}" ]] && status="FAIL" + done + printf ' %-12s %s\n' "${warehouse}" "${status}" +done + +if (( ${#failed[@]} > 0 )); then + die "${#failed[@]} warehouse(s) failed: ${failed[*]}" +fi + +log "all local warehouses passed" diff --git a/scripts/ci/test.sh b/scripts/ci/test.sh new file mode 100755 index 00000000..20a5ae7f --- /dev/null +++ b/scripts/ci/test.sh @@ -0,0 +1,107 @@ +#!/usr/bin/env bash +# Run integration tests against a single data warehouse. +# +# This is the single entrypoint that both local development and GitHub Actions +# invoke. Whatever this script does locally is exactly what CI does. +# +# Usage: +# scripts/ci/test.sh [] +# +# Examples: +# scripts/ci/test.sh postgres # latest supported dbt-postgres +# scripts/ci/test.sh postgres 1_9_0 # pinned dbt-postgres 1.9.x +# scripts/ci/test.sh snowflake # requires Snowflake env vars +# +# Local warehouses (postgres, trino, sqlserver) are started via docker compose +# automatically and torn down on exit. Cloud warehouses (snowflake, bigquery, +# databricks) require env vars to be set; the script validates them up front. +# +# Optional env vars: +# KEEP_COMPOSE=1 skip the teardown trap (useful for debugging a failed run) + +set -euo pipefail +source "$(dirname "${BASH_SOURCE[0]}")/_lib.sh" + +cd "${repo_root}" + +require_cmd uv docker + +if (( $# < 1 )); then + die "usage: scripts/ci/test.sh []" +fi + +warehouse="$1" +dbt_version="${2:-}" + +case "${warehouse}" in + postgres|trino|sqlserver|snowflake|bigquery|databricks|spark) + ;; + *) + die "unknown warehouse: ${warehouse}" + ;; +esac + +# Resolve the tox env name. Empty dbt_version → no suffix → use the +# `integration_` env, which pulls the highest supported adapter. +if [[ -n "${dbt_version}" ]]; then + tox_env="integration_${warehouse}_${dbt_version}" +else + tox_env="integration_${warehouse}" +fi + +# Per-warehouse env contract. +case "${warehouse}" in + snowflake) + require_env \ + DBT_ENV_SECRET_SNOWFLAKE_TEST_ACCOUNT \ + DBT_ENV_SECRET_SNOWFLAKE_TEST_USER \ + DBT_ENV_SECRET_SNOWFLAKE_TEST_PASSWORD \ + DBT_ENV_SECRET_SNOWFLAKE_TEST_ROLE \ + DBT_ENV_SECRET_SNOWFLAKE_TEST_DATABASE \ + DBT_ENV_SECRET_SNOWFLAKE_TEST_WAREHOUSE + ;; + bigquery) + require_env DBT_ENV_SECRET_GCP_PROJECT + # GOOGLE_APPLICATION_CREDENTIALS is set by google-github-actions/auth in + # CI, or by the developer's gcloud login locally. Don't require here — + # let dbt-bigquery surface a clearer error if it's missing. + ;; + databricks) + require_env \ + DBT_ENV_SECRET_DATABRICKS_HOST \ + DBT_ENV_SECRET_DATABRICKS_HTTP_PATH \ + DBT_ENV_SECRET_DATABRICKS_TOKEN + ;; + postgres|trino|sqlserver|spark) + ;; +esac + +# Local warehouses: bring up compose, register teardown. +needs_compose=0 +case "${warehouse}" in + postgres|trino|sqlserver) + needs_compose=1 + ;; +esac + +if (( needs_compose )); then + "${repo_root}/scripts/ci/compose-up.sh" "${warehouse}" + if [[ -z "${KEEP_COMPOSE:-}" ]]; then + # Tear down whether the test passes or fails. KEEP_COMPOSE=1 disables this + # so you can poke at the container after a failure. + trap '"${repo_root}/scripts/ci/compose-down.sh" || true' EXIT + fi +fi + +ensure_github_sha +ensure_dbt_version + +banner "Running ${tox_env} (warehouse=${warehouse}, dbt_version=${dbt_version:-latest})" + +# Export DBT_VERSION so it reaches tox (which then passes it to dbt via +# passenv). Already ensured non-empty default above. +export DBT_VERSION + +uv run tox -e "${tox_env}" + +log "test.sh complete: ${tox_env}" diff --git a/scripts/release/cut-candidate.py b/scripts/release/cut-candidate.py new file mode 100755 index 00000000..c21e59f8 --- /dev/null +++ b/scripts/release/cut-candidate.py @@ -0,0 +1,168 @@ +#!/usr/bin/env python3 +""" +Cut a release-candidate branch. + +Mechanical part of the release flow described in docs/dev-workflow.md +Stage 3: bump the package version in `dbt_project.yml` and the Quickstart +example in `README.md`, create a `release-candidate/X.Y.Z` branch, commit, +and push. The push triggers `.github/workflows/release.yml` (Tier 3). + +The maintainer reviews the Tier 3 results and then drives the merge-back +and tag manually — this script intentionally does NOT auto-merge or auto-tag. + +Usage (local): + ./scripts/release/cut-candidate.py --patch + ./scripts/release/cut-candidate.py --minor + ./scripts/release/cut-candidate.py --major + ./scripts/release/cut-candidate.py --version 2.11.0 + ./scripts/release/cut-candidate.py --minor --no-push # dry-ish run + +Usage (CI): invoked by `.github/workflows/cut-release-candidate.yml`. +""" + +from __future__ import annotations + +import argparse +import re +import subprocess +import sys +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parent.parent.parent +DBT_PROJECT = REPO_ROOT / "dbt_project.yml" +README = REPO_ROOT / "README.md" + +# `version: "X.Y.Z"` in dbt_project.yml (line 2 today). +VERSION_RE_DBT = re.compile(r'^(version:\s*")([^"]+)(")', re.MULTILINE) + +# ` version: X.Y.Z` inside the Quickstart packages.yml example block. +VERSION_RE_README = re.compile(r'^(\s*version:\s+)(\d+\.\d+\.\d+)\s*$', re.MULTILINE) + +SEMVER_RE = re.compile(r"^\d+\.\d+\.\d+$") + + +def die(msg: str) -> None: + print(f"cut-candidate: error: {msg}", file=sys.stderr) + sys.exit(1) + + +def run(cmd: list[str]) -> None: + print(f"+ {' '.join(cmd)}", file=sys.stderr) + subprocess.run(cmd, check=True) + + +def current_version() -> str: + text = DBT_PROJECT.read_text() + m = VERSION_RE_DBT.search(text) + if not m: + die(f"could not find `version:` line in {DBT_PROJECT}") + return m.group(2) + + +def bump(version: str, kind: str) -> str: + try: + major, minor, patch = (int(x) for x in version.split(".")) + except ValueError: + die(f"current version is not valid semver: {version!r}") + if kind == "patch": + return f"{major}.{minor}.{patch + 1}" + if kind == "minor": + return f"{major}.{minor + 1}.0" + if kind == "major": + return f"{major + 1}.0.0" + die(f"unknown bump kind: {kind!r}") + + +def update_file(path: Path, pattern: re.Pattern[str], new_version: str) -> None: + text = path.read_text() + new_text, count = pattern.subn( + lambda m: m.group(1) + new_version + (m.group(3) if m.lastindex == 3 else ""), + text, + count=1, + ) + if count != 1: + die(f"failed to update version in {path} (matched {count} times)") + path.write_text(new_text) + + +def assert_clean_main() -> None: + branch = subprocess.check_output( + ["git", "rev-parse", "--abbrev-ref", "HEAD"], text=True + ).strip() + if branch != "main": + die(f"must be on `main`; currently on `{branch}`") + + status = subprocess.check_output(["git", "status", "--porcelain"], text=True).strip() + if status: + die(f"working tree not clean:\n{status}") + + +def assert_branch_does_not_exist(branch: str) -> None: + # Local + result = subprocess.run( + ["git", "rev-parse", "--verify", "--quiet", branch], + capture_output=True, + ) + if result.returncode == 0: + die(f"branch {branch!r} already exists locally") + # Remote + result = subprocess.run( + ["git", "ls-remote", "--exit-code", "origin", f"refs/heads/{branch}"], + capture_output=True, + ) + if result.returncode == 0: + die(f"branch {branch!r} already exists on origin") + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + group = parser.add_mutually_exclusive_group(required=True) + group.add_argument("--patch", action="store_const", const="patch", dest="bump_kind") + group.add_argument("--minor", action="store_const", const="minor", dest="bump_kind") + group.add_argument("--major", action="store_const", const="major", dest="bump_kind") + group.add_argument( + "--version", + metavar="X.Y.Z", + help="explicit version; skips bump computation", + ) + parser.add_argument( + "--no-push", + action="store_true", + help="create the branch and commit locally but do not push to origin", + ) + args = parser.parse_args() + + assert_clean_main() + + current = current_version() + new = args.version if args.version else bump(current, args.bump_kind) + if not SEMVER_RE.match(new): + die(f"target version is not valid semver: {new!r}") + if new == current: + die(f"target version {new} is the same as current; nothing to do") + + branch = f"release-candidate/{new}" + assert_branch_does_not_exist(branch) + + print(f"bumping {current} -> {new}, branch={branch}", file=sys.stderr) + + run(["git", "checkout", "-b", branch]) + update_file(DBT_PROJECT, VERSION_RE_DBT, new) + update_file(README, VERSION_RE_README, new) + run(["git", "add", str(DBT_PROJECT.relative_to(REPO_ROOT)), str(README.relative_to(REPO_ROOT))]) + run(["git", "commit", "-m", f"release: bump to {new}"]) + + if args.no_push: + print(f"\nbranch {branch} created locally; not pushed (--no-push given)", file=sys.stderr) + return + + run(["git", "push", "-u", "origin", branch]) + print( + f"\npushed {branch}. Tier 3 (release.yml) will fire on the push.\n" + f"Watch: https://github.com/brooklyn-data/dbt_artifacts/actions/workflows/release.yml", + file=sys.stderr, + ) + + +if __name__ == "__main__": + main() diff --git a/specs/ci-rework/README.md b/specs/ci-rework/README.md new file mode 100644 index 00000000..157d8612 --- /dev/null +++ b/specs/ci-rework/README.md @@ -0,0 +1,898 @@ +# CI Rework — Security-driven redesign of dbt_artifacts CI + +**Status:** Approved plan, pre-implementation +**Author:** Michael Carlone +**Last updated:** 2026-05-11 + +--- + +## 1. Background + +`brooklyn-data/dbt_artifacts` is an open-source dbt **package** consumed by many +downstream dbt projects. Historically, CI ran on every contributor PR — including +forks — via the GitHub Actions `pull_request_target` event so that secrets +(warehouse credentials, GCP Workload Identity, Databricks tokens) were available +when running integration tests against Snowflake, BigQuery, Databricks, Postgres, +SQL Server, and Trino. + +A security issue was identified: `pull_request_target` runs in the **base repo's +context with secrets available**, but the prior workflows then `checkout` the PR +head SHA. That gives attacker-controlled code access to warehouse credentials. +A malicious PR could modify `tox.ini`, `pyproject.toml`, `dbt_project.yml`, +macros, pre/post hooks, or sqlfluff config to exfiltrate secrets the moment a +maintainer clicks "approve" on the Environments gate. + +As of this writing, **all integration CI is disabled** (every workflow file +except `main_lint_package.yml` and `publish_docs_on_release.yml` is commented +out). The package is shipping without integration test signal, which has +disrupted the intended **monthly release cadence**. + +This spec defines a new CI design that (a) removes the `pull_request_target` +vulnerability, (b) gives contributors meaningful PR signal, (c) preserves +maintainer ability to validate against cloud warehouses pre-release, and (d) +maintains a single source of truth between local development and CI execution. + +--- + +## 2. Goals & non-goals + +### Goals + +- **No `pull_request_target` anywhere.** PRs from forks must never have access + to repository secrets. +- **Contributors get real signal.** Fork PRs should run lint and every + warehouse that can be containerized locally — without secrets. +- **Maintainers can verify against cloud warehouses** before merging / + releasing. +- **Local ↔ CI parity.** Anything CI runs, a developer can run with the same + command on their laptop. No GitHub-Actions-specific glue logic. +- **Path of least resistance for contributors.** Minimal external tooling + beyond what is already in use (`uv`, `tox`, `docker compose`). +- **Restore monthly release cadence** by making release verification a + reproducible, automatable step. + +### Non-goals + +- Changing where contributors PR. They continue to target `main`. We do **not** + introduce a long-lived `release-candidate` branch. +- Adding Databricks local emulation (impossible). Databricks remains + cloud-only and is stubbed in the matrix. +- Adding `make` as a tooling layer. Shell scripts only. +- Replacing branch protection configuration via code. Branch protection is + already configured on `main`; status checks will be updated by the + repository admin as new workflows land. + +--- + +## 3. Threat model + +The trust boundary for this package is **"code that has been reviewed and +merged into `main` by a maintainer."** + +- Code on `main` is trusted with full warehouse credentials. +- Code on `release-candidate/*` branches is trusted (created by maintainers + during release prep). +- Code on **any** PR branch (including internal feature branches) is **not + trusted** until merged. Even maintainer-authored branches go through PR. + +The key insight: by using `pull_request` (not `pull_request_target`), CI on PRs +runs in a context where secrets are **structurally unavailable** — not gated by +human approval, but absent from the runner environment entirely. This is a much +stronger guarantee than the old "Environments approval" approach. + +### Important caveat about the pre-rework state + +When this work began, we believed all integration CI had been disabled. The +`ci_test_*.yml` and `main_test_*.yml` files were commented out — except for +two that were **live**: + +- `ci_test_latest_version_on_feature_branch.yml` +- `ci_test_supported_versions_on_feature_branch.yml` + +Both used `pull_request_target` and were firing on every PR with full +warehouse secrets in the environment. The vulnerability was not "dormant +pending CI rework" — it was active. Those two files were removed in Step 5 +of this rework. The rework's switch from `pull_request_target` to +`pull_request` for the new `pr.yml` is what makes that removal safe to do +without losing PR-level test signal. + +### Files that previously expanded the attack surface + +These remain sensitive — modifications to them must be reviewed carefully even +though the new design prevents direct exfiltration: + +- `.github/workflows/**` +- `scripts/ci/**` (new in this spec) +- `compose.yml`, `init-scripts/**` +- `tox.ini`, `pyproject.toml`, `uv.lock` +- `dbt_project.yml`, `packages.yml`, `package-lock.yml` + +A `CODEOWNERS` file (added as part of this work) will require maintainer review +on changes to these paths. + +--- + +## 4. Branching model + +Unchanged from current practice — contributors PR against `main`: + +``` +fork-PR ─┐ + ├─► main ─► release-candidate/X.Y.Z ─► tag X.Y.Z +internal-PR ┘ │ + └─► hotfix branches off main, PRs into main +``` + +`release-candidate/X.Y.Z` branches are **ephemeral**, created by a maintainer +to bump the version and run the full release matrix before tagging. They are +deleted after the tag is cut. + +--- + +## 5. CI tier design + +Three tiers, gated by GitHub Actions event and ref. **Each tier is a superset +of the previous tier** — Tier 2 runs everything Tier 1 runs, plus its own +additions; Tier 3 runs everything Tier 2 runs, plus its own additions. + +### Tier 1 — PR smoke test (untrusted, no secrets) + +| | | +|---|---| +| **Trigger** | `pull_request` targeting `main` (any source, including forks) | +| **Permissions** | `contents: read` only. No `id-token`. | +| **Secrets** | None available — by virtue of the `pull_request` event. | +| **Jobs** | SQLFluff lint; integration tests against Postgres, Trino, SQL Server via `docker compose` | +| **dbt version** | Latest supported only | +| **Concurrency** | One run per PR; new pushes cancel prior runs | + +Tier 1 is the only signal a fork contributor sees on their PR. It is safe +because even arbitrary code execution on the runner gains nothing — there are +no secrets to steal. + +### Tier 2 — Post-merge integration (trusted, cloud DWH) + +| | | +|---|---| +| **Trigger** | `push` to `main` or `release-candidate/**` | +| **Permissions** | `contents: read`, `id-token: write` (for GCP WIF) | +| **Secrets** | Snowflake, BigQuery (via Workload Identity Federation), and any others required | +| **Jobs** | Everything in Tier 1 **plus** Snowflake and BigQuery on latest dbt | +| **dbt version** | Latest supported only | +| **Rationale** | Minimal cloud testing per maintainer convention: maintainers test their own DWH locally before merging; Tier 2 catches regressions on warehouses they don't personally use. | + +Tier 2 is gated by branch protection on `main` requiring PR + maintainer +approval. Code only reaches a secrets-bearing context after human review of +the diff. + +### Tier 3 — Release matrix (trusted, full validation) + +| | | +|---|---| +| **Trigger** | `push` to `release-candidate/**`; `workflow_dispatch` | +| **Permissions** | Same as Tier 2 | +| **Secrets** | Same as Tier 2 | +| **Jobs** | Everything in Tier 2 **plus** full matrix: every warehouse × every supported dbt version | +| **dbt version** | All currently supported (matrix lives in `tox.ini`) | +| **Databricks** | Present in matrix as a stubbed job that exits with a documented "skipped — see issue #NNN" message | + +Tier 3 is the gate on cutting a release tag. If Tier 3 is green on a +`release-candidate/X.Y.Z` branch, the maintainer tags `X.Y.Z` from that +branch's HEAD. + +### Tier 4 — Docs publishing (unchanged) + +Existing `publish_docs_on_release.yml`. Triggered on release tag. + +### Summary table + +| Event | Tier(s) that run | Secrets | Notes | +|---|---|---|---| +| `pull_request` → `main` | 1 | No | Fork-safe | +| `push` → `main` | 1 + 2 | Yes | Post-merge verification | +| `push` → `release-candidate/**` | 1 + 2 + 3 | Yes | Pre-release validation | +| `workflow_dispatch` (manual) | 3 (selectable) | Yes | Maintainer escape hatch | +| Release tag | 4 | N/A | Docs only | + +--- + +## 6. Single-source-of-truth script layer + +All test execution lives in `scripts/ci/`. GitHub Actions workflows are thin +shells that `checkout → setup → invoke a script`. The same scripts run locally. + +### Proposed layout + +``` +scripts/ci/ + setup.sh # uv sync; validate required env vars; dbt deps + compose-up.sh # docker compose up -d for local DWHs + wait-for-ready healthchecks + compose-down.sh # docker compose down -v + lint.sh # uv run tox -e lint (sqlfluff) + test.sh # USAGE: test.sh [] + # Dispatches to: uv run tox -e integration_[_] + # Handles compose-up / compose-down for local DWHs automatically. + test-all-local.sh # convenience: test.sh postgres + trino + sqlserver +``` + +### Design rules for scripts + +- **No GitHub-Actions-isms.** No `${GITHUB_*}` env vars, no `::set-output`, no + conditional logic on CI environment. Scripts behave identically on a laptop. +- **Fail fast** (`set -euo pipefail`). +- **Explicit env contract.** Each script documents the env vars it requires at + the top and validates them before doing work (clear error → easier debugging + for contributors). +- **Self-contained cleanup.** `test.sh postgres` is responsible for tearing + down what it brought up, even on failure (`trap`-based cleanup). +- **No assumptions about cwd.** Scripts `cd` to the repo root themselves. + +### What lives in GitHub Actions vs. in scripts + +| GitHub Actions only | Shell scripts (shared) | +|---|---| +| `actions/checkout` | `uv sync` | +| `actions/setup-python` (if needed) | `docker compose up/down` | +| `google-github-actions/auth` (WIF) | `tox -e integration_*` invocation | +| Matrix definition | Healthcheck wait loops | +| Secret → env var mapping | Per-warehouse setup quirks | +| Concurrency config | dbt deps / debug | + +### `act` compatibility + +The shell-script design naturally supports `nektos/act` for local workflow +testing. Contributors who want to validate workflow changes can run `act +pull_request` and it will exercise the same scripts. We will **not** make `act` +a hard dependency, but documenting it as an optional tool is encouraged. + +--- + +## 7. `compose.yml` (delivered in Step 1) + +[`compose.yml`](../compose.yml) covers Postgres, Trino, SQL Server. Changes +made during Step 1: + +1. **Fixed broken Trino config path.** The old file referenced `./.trino-config` + (nonexistent at repo root); the actual config lives at + `integration_test_project/.trino-config/`. Volume mount updated accordingly. +2. **Removed `sqlserver.post_start` apt-install.** It was installing ODBC tools + *inside the SQL Server container*, which is useless — the dbt-sqlserver + adapter runs on the host and connects in. The host-side ODBC driver + requirement is now documented in `compose.yml`'s header and + `scripts/ci/README.md`. No Dockerfile derivation was needed since the + bundled `/opt/mssql-tools18/` is already present in the base image for the + healthcheck and configurator sidecar. +3. **Added healthchecks** to `postgres` (`pg_isready`) and `trino` + (`/v1/info` curl). SQL Server already had one. `docker compose up --wait` + now blocks deterministically until services are accepting connections, + replacing the old `sleep 15` pattern in `integration_test_project/run_tests.sh`. +4. **Pinned image tags.** `postgres:15-alpine`, `trinodb/trino:476`. SQL + Server stays on `mcr.microsoft.com/mssql/server:2025-latest` (MS doesn't + publish more granular stable tags publicly). +5. **Shifted host-side ports to non-standard values** to eliminate clashes + with developer-local services. See §8.5. +6. **Renamed `sqlserver.configurator` → `sqlserver-configurator`** (compose + service names can't contain dots when used positionally with + `docker compose up `). + +No Snowflake / BigQuery / Databricks entries — these are cloud-only and +remain out of `compose.yml`. + +--- + +## 7.5. tox.ini modernization (delivered in Step 1) + +Adjacent to the `compose.yml` cleanup, `tox.ini` was brought current with +real-world adapter releases (PyPI snapshot taken 2026-05-11): + +| Adapter | Latest published | New version-pinned env(s) added | +|---|---|---| +| dbt-snowflake | 1.11.4 | `_1_10_0`, `_1_11_0` | +| dbt-bigquery | 1.11.1 | `_1_10_0`, `_1_11_0` | +| dbt-databricks | 1.11.8 | `_1_10_0`, `_1_11_0` | +| dbt-postgres | 1.10.0 | `_1_10_0` (no upstream 1.11 yet) | +| dbt-trino | 1.10.1 | `_1_10_0` (no upstream 1.11 yet) | +| dbt-sqlserver | 1.9.0 | `_1_9_0` (was missing); no 1.10/1.11 upstream | + +Other changes: + +- **Unversioned `integration_` envs** had their adapter upper + bound tightened from `<3.0.0` to `<2.0.0`. Honest pin given there is no + `dbt-*` 2.x line today; if/when one ships it will need deliberate review. +- **sqlfluff** moved from `~=3.0.0` (only 3.0.x) to `~=3.0` (all 3.x). + Resolves to **3.5.0** today. A future bump to **4.x** is deferred — + major version bumps surface new rule violations and deserve their own + focused change. +- **`dbt-snowflake` companion of the sqlfluff env** moved to `~=1.11.0` to + match what the active lint workflow installs. +- **dbt Fusion stub.** Fusion (the Rust reimplementation) is not on PyPI + and cannot be installed by `pip` / `uv`. A commented placeholder section + was added at the bottom of `tox.ini` showing the intended naming + convention (`integration_fusion_`), so when Fusion becomes + installable we wire it without re-debating the layout. + +Old-version envs (`_1_3_0` through `_1_8_0`) were **not** removed. Whether +to drop EOL versions is a separate consumer-impact decision tracked in §13. + +## 8. Host port decision (delivered in Step 1) + +Local `compose.yml` binds to **non-standard host ports** so the test stack +never collides with whatever a developer is already running: + +| Warehouse | Host port | Container port | +|---|---|---| +| postgres | `55432` | `5432` | +| trino | `58080` | `8080` | +| sqlserver | `51433` | `1433` | + +`integration_test_project/profiles.yml` hardcodes the shifted host ports for +the postgres/sqlserver/trino targets. CI runners get the same ports, since +they read the same compose file and same profiles.yml — no special-casing. + +**Why not stock ports + env-var override?** Considered. Rejected because: + +- Stock ports collide with `brew services` Postgres, locally-running web + apps on `:8080`, etc. — friction every time a dev runs tests. +- Env-var indirection adds a thing to remember and a thing to forget. The + failure mode (silent connection to the wrong DB) is worse than the + failure mode of non-standard ports (everyone uses them, including CI). +- Connecting to `localhost:5432` with `psql` now hits the **developer's** + Postgres, not the test container. That's actually safer. + +If a future contributor's environment happens to already use `:55432` or +`:58080`, we'll revisit — but those are uncommon enough that it isn't worth +the indirection up front. + +## 9. Workflow files + +End state of `.github/workflows/`: + +| File | Purpose | Tier | Status | +|---|---|---|---| +| `pr.yml` | `pull_request` → `main`. Local DWH matrix (no lint, see below). | 1 | ✅ delivered | +| `main.yml` | `push` → `main` + `workflow_dispatch`. Lint + local DWH matrix + Snowflake + BigQuery on latest dbt. | 1 + 2 | ✅ delivered | +| `release.yml` | `push` → `release-candidate/**` and `workflow_dispatch`. Full matrix. | 1 + 2 + 3 | ✅ delivered | +| `publish_docs_on_release.yml` | Existing. Triggered on release tag. | 4 | unchanged | + +The six currently-commented-out workflow files are deleted as the final step. + +### Tier 1 lint deferral + +`pr.yml` deliberately does **not** run lint. The package's models call +adapter methods (`run_query`-style) at dbt compile time, so the sqlfluff +dbt-templater needs a real Snowflake connection to render templates. Since +Tier 1 has no secrets by design, lint cannot run there until the +introspection-at-compile-time issue is fixed (tracked in §13). Until then +lint lives in Tier 2 (which has secrets via merge gating). This is an +acknowledged trade-off documented in `scripts/ci/README.md` and +`.github/workflows/pr.yml`. + +### Shared hardening applied to every workflow + +- `permissions:` block defaulting to `read-all`, with explicit opt-in for jobs + that need `id-token: write` (BigQuery WIF). +- Third-party actions pinned to commit SHAs, not tags. **Status:** SHA-pinned + across `pr.yml` and `main.yml` as of Step 3. `release.yml` will follow the + same pattern when delivered. +- `concurrency:` group keyed by workflow + PR number / ref so re-pushes + cancel prior runs. +- `timeout-minutes` set per job to bound runaway costs. +- `fail-fast: false` on the matrix so one warehouse failure doesn't mask + others. + +--- + +## 10. CODEOWNERS + +A new `.github/CODEOWNERS` file requires maintainer review on changes to: + +- `.github/**` +- `scripts/ci/**` +- `compose.yml`, `init-scripts/**` +- `tox.ini`, `pyproject.toml`, `uv.lock` +- `dbt_project.yml`, `packages.yml`, `package-lock.yml` + +This is defense-in-depth: the `pull_request` event removes the **direct** +exfiltration path, and CODEOWNERS ensures changes to sensitive plumbing get a +maintainer's eyes before merging exposes them to Tier 2/3 secrets. + +--- + +## 11. Implementation order + +Each step is independently mergeable and verifiable. Steps 1–2 do not touch +secrets and can be merged conservatively. Steps 3+ are where the new +trust-bearing CI lights up. + +1. ✅ **`scripts/ci/` + `compose.yml` cleanup.** Delivered and live-verified. + Files: `scripts/ci/{_lib,setup,compose-up,compose-down,lint,test,test-all-local}.sh`, + `scripts/ci/README.md`, refactored `compose.yml`, updated + `integration_test_project/profiles.yml` (shifted ports), bumped + `tox.ini` lint deps to match the active workflow's known-working pair + (`sqlfluff-templater-dbt~=3.0.0` + `dbt-snowflake~=1.9.0`). See §7 and §8 + for what changed. + + **Live verification results:** + - `./scripts/ci/setup.sh` — OK + - `./scripts/ci/test.sh postgres` — PASS=49 ERROR=0, compose teardown clean + - `./scripts/ci/test.sh sqlserver` — PASS=49 ERROR=0, sidecar bootstrap clean + - `./scripts/ci/test.sh trino` — initially FAILed on the `microbatch` + model (`TrinoUserError: This connector does not support modifying + table rows`); fixed by gating that model with + `enabled = target.type != 'trino'` in + `integration_test_project/models/microbatch.sql`. After the fix: + PASS=48 ERROR=0. The Trino `memory` connector doesn't support MERGE, + which the microbatch incremental strategy requires. + - `./scripts/ci/lint.sh` — FAIL without real Snowflake credentials; + PASSes when they are set. The active `main_lint_package.yml` workflow + has the same requirement. Documented in `scripts/ci/README.md`. +2. ✅ **Tier 1 workflow** (`.github/workflows/pr.yml`). Delivered. + `pull_request` → `main`, matrix over `postgres`/`trino`/`sqlserver`, + each slot delegates to `scripts/ci/test.sh `. No secrets, + `permissions: read-all`, `concurrency` cancels superseded runs. + Validated via `act --list` and `act -n` (auth error on action-fetch is + an `act`-only quirk; workflow structure parses cleanly). Real signal + comes when this lands on `main` and a PR fires it. +3. ✅ **Tier 2 workflow** (`.github/workflows/main.yml`). Delivered. + `push` → `main` and `workflow_dispatch`. Four jobs: + `lint`, `integration-local` (postgres/trino/sqlserver matrix), + `integration-snowflake`, `integration-bigquery`. Secret minimization + verified — `integration-local` declares no secret env vars; Snowflake + creds scoped to the two jobs that need them; `id-token: write` lives + only on the BigQuery job. All actions SHA-pinned (and `pr.yml` + back-ported to SHA pins in the same change): + - `actions/checkout` → `34e114876b0b11c390a56381ad16ebd13914f8d5` (v4) + - `astral-sh/setup-uv` → `caf0cab7a618c569241d31dcd442f54681755d39` (v3) + - `google-github-actions/auth` → `c200f3691d83b41bf9bbd8638997a462592937ed` (v2) + GCP Workload Identity Federation preserved from the pre-rework + workflow — no static service account keys. WIF still has to be + verified live; that happens when this lands on `main` and the next + push fires it. +4. ✅ **Tier 3 workflow** (`.github/workflows/release.yml`) + Databricks + stub + `.github/CODEOWNERS` + `.github/dependabot.yml`. Delivered. + - `release.yml`: fires on `push` → `release-candidate/**` and + `workflow_dispatch`. Three jobs: `lint`, `version-matrix` + (42 entries — 5 unversioned "latest" + 37 pinned versions across 5 + warehouses), and `integration-databricks-stub` (visible-but-skipped). + `max-parallel: 8` throttles the matrix to ~30–40 minute total + runtime. Same SHA-pins as `pr.yml` / `main.yml`. `id-token: write` + scoped to the matrix job only (for conditional WIF on bigquery + slots). + - `CODEOWNERS`: requires maintainer review on `.github/**`, + `scripts/ci/**`, `compose.yml`, `init-scripts/**`, `tox.ini`, + `pyproject.toml`, `uv.lock`, `dbt_project.yml`, `packages.yml`, + `package-lock.yml`, and `specs/`. **Action required**: replace the + placeholder `@brooklyn-data/dbt-artifacts-maintainers` handle with + the real team slug before this lands. + - `dependabot.yml`: weekly grouped action-update PRs. Closes the + maintainability gap that SHA-pinning otherwise creates. Python deps + intentionally NOT auto-bumped — dbt / adapter versions are test + surface area and need deliberate `tox.ini` changes. +5. ✅ **Delete superseded files.** Delivered. Ten files removed: + - **Five commented-out workflows**: `ci_test_package.yml`, + `main_test_package.yml`, `main_test_latest_version.yml`, + `main_test_supported_versions.yml`, `main_lint_package.yml`. + - **Two ACTIVE `pull_request_target` workflows** — + `ci_test_latest_version_on_feature_branch.yml` and + `ci_test_supported_versions_on_feature_branch.yml`. **These were + live, not commented out.** They fired on every PR with full + Snowflake / GCP / Databricks secrets in the environment. They are + the precise vulnerability that motivated this rework, and we + operated for several steps believing they had already been + disabled. **Their removal in this step is the actual closing of + the disclosed vulnerability**, not just a tidying pass. + - **Three superseded support files**: + `integration_test_project/docker-compose.yml.bak` (older copy of + compose.yml), `integration_test_project/run_tests.sh` (replaced by + `scripts/ci/test-all-local.sh`), `init-scripts/progress.sh` (the + 15-second sleep loop replaced by container healthchecks). + + Post-cleanup `.github/workflows/` contains exactly four files: + `pr.yml`, `main.yml`, `release.yml`, `publish_docs_on_release.yml`. + `compose config -q` still validates. +6. ✅ **Contributor docs refreshed.** Delivered: + - **New** `docs/dev-workflow.md` — end-to-end walkthrough of feature + branch → PR → merge → release-candidate → tag, with a workflow ↔ + tier table and a hotfix section. + - **Rewrote** `CONTRIBUTING.md` — replaced `pipx`/`tox`-direct with + `uv` + `scripts/ci/`; added "What CI will and won't do on your PR" + section so fork contributors understand the Tier 1 scope; updated + the "add a new adapter" checklist with the three workflow files. + - **Updated** `README.md` — replaced the two badges pointing at + deleted workflows (`main_test_package.yml`, `main_lint_package.yml`) + with `main.yml` and `release.yml` badges; expanded Contributing + section to point at `dev-workflow.md` and `MAINTAINERS.md`. + - **Updated** `docs/MAINTAINERS.md` — removed the dead "Approve + Integration Tests" deployment-environment section; rewrote + "How to release" to use the `release-candidate/X.Y.Z` flow; + replaced the standalone `docker run` Postgres/Dremio snippets + with the `compose.yml`-based local-DWH path; cleaned up the + `pyenv virtualenv` section now that `tox` handles per-version + isolation automatically. + - **Updated** `CLAUDE.md` — new "CI model" orientation section at + the top so future Claude sessions know the tier model and the + `pull_request_target` prohibition; rewrote test-running commands + to point at `scripts/ci/`; fixed the dead `main_test_package.yml` + reference in "When adding support for a new adapter"; refreshed + the Releasing section to point at `docs/dev-workflow.md`. +7. ✅ **Release-candidate cutter** (`scripts/release/cut-candidate.py` + + `.github/workflows/cut-release-candidate.yml`). Delivered. Auto-bumps + version in `dbt_project.yml` and the `README.md` Quickstart example, + creates `release-candidate/X.Y.Z`, commits, and pushes — which then + fires Tier 3. **Auto-bump, not auto-merge**: the maintainer still + drives the tag and any merge-back to `main` manually. Available + both via `workflow_dispatch` and locally as `./scripts/release/cut-candidate.py + --patch | --minor | --major | --version X.Y.Z`. Safety guards: must + be on `main`, working tree must be clean, target branch must not + already exist locally or on origin. +8. ✅ **Weekly regression on `main`** (added to `release.yml`). + Delivered. `schedule: '0 6 * * 1'` (Mondays 06:00 UTC) runs the full + Tier 3 matrix against `main`. Scheduled runs execute on the default + branch, so this validates the current state of `main` regardless of + any in-flight release branches. Catches drift from upstream dbt + adapter releases between scheduled releases. + +--- + +## 12. Tech debt — to close after the framework is complete + +Items we have identified during Steps 1–2 that we will work through **once +Steps 3–6 are merged and the framework itself is operational**. Each item is +a discrete piece of follow-up work, not part of the core CI rework. + +### 12.1. Documentation debt + +- **`CONTRIBUTORS.md`** (new file). The repo currently has no contributor + guide. With the new CI model — and the fact that fork PRs now get a + meaningfully different signal than internal PRs — we need to explain: + - How to set up `uv`, Docker, ODBC driver + - How to run `scripts/ci/test.sh ` locally + - What CI does and does not do on a fork PR (no lint, no cloud DWH — + that's expected, not a bug) + - What the maintainer flow looks like (review → merge → Tier 2 runs) + - How to bump `tox.ini` when a new dbt version drops + - Code style: SQLFluff rules in `tox.ini`, line-leading commas, etc. +- **Documented end-to-end dev workflow on GitHub.** A short doc + (`docs/dev-workflow.md` or section of `CONTRIBUTORS.md`) that walks + through `feature branch → PR → review → merge → release-candidate → + tag` with screenshots of what passes/fails at each stage and which + workflows fire. Should make the trust-boundary visible to a new + maintainer joining the project. +- **`CLAUDE.md` + `README.md` updates** (Step 6) — describe the new CI + flow in the existing places contributors already look. + +### 12.2. Lint and model debt + +- ~~**`{% if execute %}` guards on introspecting models.**~~ **Resolved + as a no-op after survey.** The original hypothesis was that some + package models call `run_query` / adapter methods at compile time, + forcing the dbt templater to connect to Snowflake just to lint. A + full grep across `models/` and `macros/` found that every DB-touching + macro (`get_relation`, `insert_into_metadata_table` via + `upload_results`, `safe_cast`) is **already guarded** by `{% if + execute %}`. The actual cause of "lint needs Snowflake creds" is at + the dbt-snowflake **adapter init** layer — the adapter establishes + a session before any model compiles. Fixing this would require + switching the lint target to Postgres (or another lightweight + adapter), not changing any models. **Decision: keep lint in Tier 2.** + Rationale: "loose at Tier 1, strict at Tier 2" is a defensible + standalone position — fork contributors get fast structural signal; + lint provides format control without wrangling external + contributors. No further action needed. +#### 12.2.1. Bump sqlfluff to 4.x — **planned, not yet executed** + +**Status:** Migration plan documented. Execution deferred — bundle with +the next major release (3.0.0, alongside the §12.3.1 EOL drop) so all +maintainer-facing breakage lands in one coordinated release. Update +this section when execution begins. + +##### Current state + +- `tox.ini` `[sqlfluff]` deps: `sqlfluff-templater-dbt~=3.0` + (resolves to 3.5.0 today) +- `dbt-snowflake~=1.11.0` paired with it (matches what the active + lint runs against) + +##### What changes in 4.x (per sqlfluff 4.0.0 release notes) + +1. **Rust-based parser/lexer as an opt-in extra** (`sqlfluff[rs]`). + The Python parser remains the default. 5.0 will flip the default + to Rust. We can adopt later — not part of the 4.x migration scope. +2. **Drops dbt 1.4 and older.** We're on dbt 1.10/1.11 — no impact. +3. **Adds dbt 1.10 templater support.** Aligns with our active dbt + matrix. +4. **Bug fixes for CV12 (`structure.distinct`) and RF01 + (`references.from`).** This is the risk: previously-passing models + may now flag. +5. **New "force implicit indents" capability.** Opt-in; safe to + ignore. + +##### Risks + +- **New rule violations on existing models.** CV12 / RF01 fixes may + surface lint failures we didn't see before. Estimated handful of + models max; we'll know after running once. +- **Templater compatibility.** `sqlfluff-templater-dbt` 4.x requires + dbt-core 1.5+. We're already past this floor. +- **CI lint failures during the bump.** Maintainer-only impact — Tier + 1 PRs are unaffected because lint lives in Tier 2 (see §9 "Tier 1 + lint deferral"). + +##### Migration steps (when ready) + +1. **Cut a focused branch** off the `release-candidate/3.0.0` work + (or its own `release-candidate/3.0.0` is fine — the bump is + maintainer-visible only, not consumer-breaking). +2. **Bump the deps in `tox.ini`:** + ```ini + [sqlfluff] + deps = + sqlfluff-templater-dbt~=4.0 + dbt-snowflake~=1.11.0 + ``` +3. **Run lint locally** with real Snowflake creds set (per §12.2's + "lint requires Snowflake creds" constraint): + ```bash + . ./env.sh + ./scripts/ci/lint.sh + ``` +4. **Triage any new violations:** + - **Fix-able** (a real style issue): run `./scripts/ci/lint.sh + --fix` to auto-resolve. + - **False positive on this codebase**: add the specific rule code + to the `rules` exclusion list at the top of `tox.ini` with a + comment explaining why, OR add a `noqa` comment on the offending + line in the model. + - **Bug in 4.x itself**: open an upstream issue on sqlfluff; + temporarily exclude the rule; revisit when fixed. +5. **Re-run lint until clean.** +6. **Verify integration tests still pass** — sqlfluff doesn't affect + runtime SQL, but a sanity check on `./scripts/ci/test.sh postgres` + confirms the upgrade hasn't broken `uv sync` resolution. +7. **Commit and push the release-candidate branch.** Tier 3 fires; + confirm the lint job is green. +8. **Update this spec section** marking 12.2.1 ✅ delivered and note + any rules that needed to be excluded (so future maintainers know + why). + +##### Files that change + +- `tox.ini` — `[sqlfluff] deps` block, line ~7-9. +- Possibly individual model `.sql` files if violations need fixing. +- Possibly `tox.ini`'s `rules = ...` line (lines ~37) if rules need + excluding. + +##### Rollback + +If 4.x proves too disruptive, revert the `tox.ini` deps change: + +```ini +[sqlfluff] +deps = + sqlfluff-templater-dbt~=3.0 + dbt-snowflake~=1.11.0 +``` + +No data migration, no consumer impact — the rollback is trivial. + +##### Why coordinate with 3.0.0? + +sqlfluff is a CI-only tool; this bump doesn't affect consumers of the +package. But it **does** affect maintainer workflow if new violations +surface. Coordinating the bump with the 3.0.0 EOL release means all +the "this is the release where maintainer workflow changed" friction +lands once, not twice. If sqlfluff 4.x surfaces zero new violations +in practice, the coordination is harmless — bump it in 2.11.0 +instead. + +#### 12.2.2. Other lint / model debt + +(Originally tracked the `{% if execute %}` guards item — resolved +as a no-op after survey. See 12.2 introduction.) + +### 12.3. Test matrix debt + +#### 12.3.1. Drop EOL dbt versions — **planned, not yet executed** + +**Status:** Decision made (Option B below). Execution deferred to a +separate branch + PR after a broad announcement. The next major +release of `dbt_artifacts` (3.0.0) will land the removal. Update this +section when execution begins. + +**Decision: drop dbt 1.3, 1.4, 1.5, 1.6 from the test matrix.** Keep +1.7, 1.8, 1.9, 1.10, 1.11 (where adapter releases exist). + +##### The data + +dbt-core release dates from PyPI, cross-referenced against dbt-labs' +published ~12-month active-support window. Pulled 2026-05-12. + +| Minor | Released | Age (months) | Upstream status | +|---|---|---|---| +| 1.11 | 2025-12-19 | 4.7 | **supported** | +| 1.10 | 2025-06-16 | 10.8 | **supported** | +| 1.9 | 2024-12-09 | 17.0 | EOL | +| 1.8 | 2024-05-09 | 24.1 | EOL | +| 1.7 | 2023-11-02 | 30.3 | EOL | +| 1.6 | 2023-07-31 | 33.4 | EOL | +| 1.5 | 2023-04-27 | 36.5 | EOL | +| 1.4 | 2023-01-25 | 39.5 | EOL | +| 1.3 | 2022-10-12 | 43.0 | EOL | + +> **Caveat on the data:** This is *upstream EOL* (what dbt-labs no +> longer supports). It is **not** direct consumer-usage data. That +> data lives in PyPI BigQuery dumps and dbt Hub usage figures we don't +> have easy access to. The recommendation is based on upstream policy +> + the prior maintainer judgment encoded in `tox.ini`'s existing +> "End of Life" comments on the 1.3-1.6 envs. + +##### Options considered + +| Option | Versions kept | Matrix size | Stance | +|---|---|---|---| +| A — strict | 1.10, 1.11 only | 42 → 12 (-71%) | Match dbt-labs' active-support window exactly | +| **B — chosen** | **1.7-1.11** | **42 → 22 (-48%)** | **Drop what `tox.ini` already labels EOL** | +| C — conservative | 1.6-1.11 | 42 → 27 (-36%) | Keep one extra year of legacy | + +##### Per-warehouse impact + +| Warehouse | Before | After | Removed | +|---|---|---|---| +| Snowflake | 1.3-1.11 (9) | 1.7, 1.8, 1.9, 1.10, 1.11 (5) | 4 envs | +| BigQuery | 1.3-1.11 (9) | 1.7, 1.8, 1.9, 1.10, 1.11 (5) | 4 envs | +| Postgres | 1.3-1.10 (8) | 1.7, 1.8, 1.9, 1.10 (4) | 4 envs | +| Trino | 1.3-1.7, 1.10 (6) | 1.7, 1.10 (2) | 4 envs | +| SQL Server | 1.3, 1.4, 1.7-1.9 (5) | 1.7, 1.8, 1.9 (3) | 2 envs | +| Databricks | stub | stub | 0 (stub stays) | +| **Total pinned** | **37** | **19** | **18 envs removed** | + +Plus 5 unversioned "latest" entries remain in both states. + +##### Rationale (for the announcement) + +1. **Upstream is unsupported.** dbt-core 1.3-1.6 stopped receiving + active maintenance from dbt-labs 33-43 months ago. Anyone still + pinned to those versions is already running an unsupported dbt + regardless of which packages they use. +2. **Prior maintainer intent.** `tox.ini` comments have flagged + 1.3-1.6 as "End of Life" envs slated for removal — we're acting on + that long-standing plan. +3. **CI cost.** Tier 3 (full version matrix) currently runs ~42 + warehouse × dbt-version slots. Dropping 1.3-1.6 cuts it to ~24, + shrinking Tier 3 (and the weekly regression that uses the same + matrix) runtime by roughly half. +4. **Not a hard break.** The package code may still work on 1.3-1.6 + even after CI stops testing it. "Deprecated" here means *no longer + tested*, not *intentionally broken*. Consumers on those versions + can keep using older `dbt_artifacts` releases (pre-3.0.0). + +##### Two-phase execution plan + +| Phase | When | What happens | Where | +|---|---|---|---| +| 1. Announcement | In 2.11.0 (next minor) | Public deprecation notice — no code change, just docs. README, CHANGELOG, release notes. `tox.ini`'s existing "echo Warnings:" messages continue to fire. | `dbt-artifacts/dbt-artifacts` repo, dbt Hub, any community channels | +| 2. Removal | In 3.0.0 (next major) | Delete the EOL envs from `tox.ini`, drop the matrix entries from `release.yml`, bump `require-dbt-version` in `dbt_project.yml` to `>=1.7.0`. | Done in a focused branch like `release-candidate/3.0.0` | + +Removing the envs is a **major-version bump** because consumers pinned +to dbt 1.3-1.6 will lose the ability to install new releases of +`dbt_artifacts`. Per semver, that's a breaking change. + +##### Files that change in Phase 2 (the actual removal) + +- `tox.ini` — delete `[testenv:integration__1_3_0]` through + `_1_6_0` blocks (~50 lines per warehouse, ~30 deletions total). +- `.github/workflows/release.yml` — delete the corresponding entries + from the `version-matrix` job's `matrix.include` block (18 entries). +- `dbt_project.yml` — bump `require-dbt-version: [">=1.3.0", "<3.0.0"]` + to `require-dbt-version: [">=1.7.0", "<3.0.0"]`. +- `README.md` — note the new minimum dbt version in the Quickstart + section. +- `dbt_project.yml` — bump package version to `3.0.0`. + +##### Draft announcement text + +Adapt to taste; usable as-is for a GitHub Discussion, release notes, +or dbt Slack #package-ecosystem channel. + +> **Heads up: `dbt_artifacts` 3.0.0 will drop support for dbt 1.3-1.6** +> +> The next major release of `dbt_artifacts` (3.0.0, target month: TBD) +> will remove CI coverage and support for dbt-core 1.3, 1.4, 1.5, and +> 1.6. The minimum supported dbt version becomes **1.7.0**. +> +> **Why now** +> +> dbt-labs ended active support for these versions between 33 and 43 +> months ago. If you're still pinned to one of them, you're running +> upstream-unsupported dbt — `dbt_artifacts` testing against those +> versions doesn't change that. Removing them lets us focus CI cost +> on versions that are actively maintained. +> +> **What to do** +> +> - On dbt **1.7 or newer**: nothing. Continue using `dbt_artifacts` +> normally; 3.0.0 will be a drop-in upgrade for you. +> - On dbt **1.3-1.6**: upgrade your dbt-core version first (see +> https://docs.getdbt.com/docs/dbt-versions/upgrade-dbt-version-in-cloud). +> If you cannot upgrade, pin `dbt_artifacts` to the last 2.x release +> in your `packages.yml`: +> ```yaml +> packages: +> - package: brooklyn-data/dbt_artifacts +> version: ">=2.10.0,<3.0.0" +> ``` +> +> **Timeline** +> +> - 2.11.0 (this release): announcement only. No code change. +> Existing deprecation warnings (already emitted at test time) +> continue. +> - 3.0.0 (target: TBD): the CI envs and adapter matrix entries are +> removed. +> +> Questions or concerns: open an issue or comment on this discussion. +> If a significant number of consumers are still on these versions, +> we'll revisit. + +#### 12.3.2. Other test matrix items + +- **Databricks reactivation.** Stubbed in `tox.ini` and Tier 3 as + skipped-by-design. Needs its own design pass to wire up safely. +- **dbt Fusion.** Placeholder section in `tox.ini`. Wire up when + Fusion becomes pip-installable (it isn't today). + +### 12.4. Cleanup debt + +- ✅ ~~**Remove `go-task-bin` from `pyproject.toml`**~~ Done. + Survey surfaced a more significant scope: `Taskfile.yml` (~130 + lines, parallel orchestrator to `scripts/ci/`, already broken by + Step 1 file deletions/renames) was using `go-task-bin`. Deleted + the Taskfile and the dep. **Bonus finding during this work:** + `pyproject.toml` and `uv.lock` were both in `.gitignore`, meaning + the new CI system's `uv sync` step would have failed on any fresh + checkout (including every CI runner). Removed the gitignore + entries and made both files tracked so fresh clones reproduce the + exact dep tree. **This unblocked a latent P0 issue** that would + have surfaced the first time CI ran on a clean runner. +- ✅ ~~**Delete `integration_test_project/run_tests.sh` and + `integration_test_project/docker-compose.yml.bak`**~~ Done in Step 5. +- ✅ ~~**Delete `init-scripts/progress.sh`**~~ Done in Step 5. +- **Fix the `pyarrow[pandas]<19.0.0` dep spec.** `uv sync` warns: + `pyarrow==18.1.0 does not have an extra named pandas`. Pre-existing + in `pyproject.toml`; surfaced once the file became tracked. Either + drop the `[pandas]` extra (if just pyarrow is needed) or split into + `pyarrow<19.0.0` and `pandas` as separate deps. ~5 min fix. + +### 12.5. Hardening debt — operational + +- **Pre-commit hooks for SQLFluff.** Catches lint issues before they + reach CI. Optional, contributor convenience. Constraint inherited + from §12.2: lint requires the dbt-snowflake adapter init, which + requires Snowflake creds. So pre-commit hooks would be + internal-contributor-only (or use `--templater=jinja` for a + coarser, offline lint path). Decide at implementation time. +- **Cost of Tier 2 on every push to `main`.** Today: 2 cloud DWH jobs + per merge. If this becomes expensive, options: throttle to one-per-day, + batch via `workflow_dispatch`, or move Snowflake/BigQuery to Tier 3 + only. Revisit after we have 1–2 months of real billing data. +- ✅ ~~**Weekly Tier 3 against `main`.**~~ Delivered in Step 7 + (scheduled `release.yml` trigger). Mondays 06:00 UTC. Drop or + throttle if cost becomes a concern. +- ✅ ~~**Auto-bump version + create `release-candidate/X.Y.Z` branch.**~~ + Delivered in Step 7 (`scripts/release/cut-candidate.py` + + `.github/workflows/cut-release-candidate.yml`). Auto-bump, not + auto-merge — the maintainer still drives the tag. + +--- + +## 13. Maybe-someday — not actively planned + +Things we've considered and consciously parked. Listed for posterity so +future maintainers can see the decision space. + +- **BigQuery emulator** (`ghcr.io/goccy/bigquery-emulator`). Would let + BigQuery move to Tier 1. Unreliable for this package's surface area + today; revisit if it matures. +- **Replace tox with native `uv run` + pytest-style invocation.** Would + simplify the script layer further; punted because tox already works + and the script-first design hides this from contributors anyway. diff --git a/tox.ini b/tox.ini index 92803151..f997f0d9 100644 --- a/tox.ini +++ b/tox.ini @@ -2,11 +2,13 @@ skipsdist = True envlist = lint_all +## SQLFluff Rules and Configurations [sqlfluff] +deps = + sqlfluff-templater-dbt~=3.0 + dbt-snowflake~=1.11.0 dialect = snowflake templater = dbt -rules = LT01,LT03,CP01,AL01,AL02,CP02,ST08,LT06,LT07,AM01,LT08,AL05,RF02,RF03,CP03,ST01,LT09,AM03,CP04,LT10,ST05,ST03,JJ01,AM05,CV08 - # LT01: [aliasing.table] Implicit/explicit aliasing of table. # AL02: [aliasing.column] Implicit/explicit aliasing of columns. # AL05: [aliasing.unused] Tables should not be aliased if that alias is not used. @@ -32,10 +34,7 @@ rules = LT01,LT03,CP01,AL01,AL02,CP02,ST08,LT06,LT07,AM01,LT08,AL05,RF02,RF03,CP # ST03: [structure.unused_cte] Query defines a CTE (common-table expression) but does not use it. # ST05: [structure.subquery] Join/From clauses should not contain subqueries. Use CTEs instead. # ST08: [structure.distinct] 'DISTINCT' used with parentheses. - -deps = - sqlfluff-templater-dbt~=2.0.2 - dbt-snowflake~=1.8.0 +rules = LT01,LT03,CP01,AL01,AL02,CP02,ST08,LT06,LT07,AM01,LT08,AL05,RF02,RF03,CP03,ST01,LT09,AM03,CP04,LT10,ST05,ST03,JJ01,AM05,CV08 [sqlfluff:indentation] indent_unit = space @@ -66,6 +65,7 @@ forbid_subquery_in = both [sqlfluff:templater:dbt] profiles_dir = integration_test_project +## Test Environments [testenv] passenv = DBT_PROFILES_DIR @@ -95,6 +95,7 @@ passenv = TEST_ENV_VAR_EMPTY TEST_ENV_VAR_WITH_QUOTE + [testenv:lint] deps = {[sqlfluff]deps} commands = sqlfluff lint {posargs} --ignore parsing @@ -111,48 +112,43 @@ commands = sqlfluff fix {posargs} --ignore parsing deps = {[sqlfluff]deps} commands = sqlfluff fix models --ignore parsing -# Generate docs -[testenv:generate_docs] -deps = dbt-snowflake~=1.9.0 -commands = dbt docs generate --profiles-dir integration_test_project - -# Snowflake integration tests +## Integration Tests +### Snowflake ############################################################################################# [testenv:integration_snowflake] changedir = integration_test_project -deps = dbt-snowflake~=1.9.0 +deps = dbt-snowflake<2.0.0 commands = dbt clean dbt deps dbt build --target snowflake - -[testenv:integration_snowflake_1_3_0] +[testenv:integration_snowflake_1_11_0] changedir = integration_test_project -deps = dbt-snowflake~=1.3.0 +deps = dbt-snowflake~=1.11.0 commands = dbt clean dbt deps dbt build --target snowflake -[testenv:integration_snowflake_1_4_0] +[testenv:integration_snowflake_1_10_0] changedir = integration_test_project -deps = dbt-snowflake~=1.4.0 +deps = dbt-snowflake~=1.10.0 commands = dbt clean dbt deps dbt build --target snowflake -[testenv:integration_snowflake_1_5_0] +[testenv:integration_snowflake_1_9_0] changedir = integration_test_project -deps = dbt-snowflake~=1.5.0 +deps = dbt-snowflake~=1.9.0 commands = dbt clean dbt deps - dbt build --target snowflake + ; dbt build --target snowflake -[testenv:integration_snowflake_1_6_0] +[testenv:integration_snowflake_1_8_0] changedir = integration_test_project -deps = dbt-snowflake~=1.6.0 +deps = dbt-snowflake~=1.8.0 commands = dbt clean dbt deps @@ -166,103 +162,112 @@ commands = dbt deps dbt build --target snowflake -[testenv:integration_snowflake_1_8_0] +#### End of Life Snowflake Versions +#### At a future date, these will be removed. +[testenv:integration_snowflake_1_6_0] changedir = integration_test_project -deps = dbt-snowflake~=1.8.0 +deps = dbt-snowflake~=1.6.0 commands = dbt clean dbt deps dbt build --target snowflake + echo "Warnings: This version will be removed from dbt_artifacts in a future release." -[testenv:integration_snowflake_1_9_0] +[testenv:integration_snowflake_1_5_0] changedir = integration_test_project -deps = dbt-snowflake~=1.9.0 +deps = dbt-snowflake~=1.5.0 commands = dbt clean dbt deps dbt build --target snowflake + echo "Warnings: This version will be removed from dbt_artifacts in a future release." -# Databricks integration tests -[testenv:integration_databricks] +[testenv:integration_snowflake_1_4_0] changedir = integration_test_project -deps = dbt-databricks~=1.9.0 +deps = dbt-snowflake~=1.4.0 commands = dbt clean dbt deps - dbt build --target databricks + dbt build --target snowflake + echo "Warnings: This version will be removed from dbt_artifacts in a future release." -[testenv:integration_databricks_1_3_0] +[testenv:integration_snowflake_1_3_0] changedir = integration_test_project -deps = dbt-databricks~=1.3.0 +deps = dbt-snowflake~=1.3.0 commands = dbt clean dbt deps - dbt build --target databricks + dbt build --target snowflake + echo "Warnings: This version will be removed from dbt_artifacts in a future release." -[testenv:integration_databricks_1_4_0] +### BigQuery ############################################################################################# +[testenv:integration_bigquery] changedir = integration_test_project -deps = dbt-databricks~=1.4.0 +deps = dbt-bigquery<2.0.0 commands = dbt clean dbt deps - dbt build --target databricks + dbt build --target bigquery --vars '"my_var": "my value"' -[testenv:integration_databricks_1_5_0] +[testenv:integration_bigquery_1_11_0] changedir = integration_test_project -deps = dbt-databricks~=1.5.0 +deps = dbt-bigquery~=1.11.0 commands = dbt clean dbt deps - dbt build --target databricks + dbt build --target bigquery --vars '"my_var": "my value"' -[testenv:integration_databricks_1_6_0] +[testenv:integration_bigquery_1_10_0] changedir = integration_test_project -deps = dbt-databricks~=1.6.0 +deps = dbt-bigquery~=1.10.0 commands = dbt clean dbt deps - dbt build --target databricks + dbt build --target bigquery --vars '"my_var": "my value"' -[testenv:integration_databricks_1_7_0] +[testenv:integration_bigquery_1_9_0] changedir = integration_test_project -deps = dbt-databricks~=1.7.0 +deps = dbt-bigquery~=1.9.0 commands = dbt clean dbt deps - dbt build --target databricks + dbt build --target bigquery --vars '"my_var": "my value"' -[testenv:integration_databricks_1_8_0] +[testenv:integration_bigquery_1_8_0] changedir = integration_test_project -deps = dbt-databricks~=1.8.0 +deps = dbt-bigquery~=1.8.0 commands = dbt clean dbt deps - dbt build --target databricks + dbt build --target bigquery --vars '"my_var": "my value"' -[testenv:integration_databricks_1_9_0] +[testenv:integration_bigquery_1_7_0] changedir = integration_test_project -deps = dbt-databricks~=1.9.0 +deps = dbt-bigquery~=1.7.0 commands = dbt clean dbt deps - dbt build --target databricks + dbt build --target bigquery --vars '"my_var": "my value"' -# Bigquery integration tests -[testenv:integration_bigquery] +#### End of Life BigQuery Versions +#### At a future date, these will be removed. +[testenv:integration_bigquery_1_6_0] changedir = integration_test_project -deps = dbt-bigquery~=1.9.0 +deps = dbt-bigquery~=1.6.0 commands = dbt clean dbt deps dbt build --target bigquery --vars '"my_var": "my value"' + echo "Warnings: This version will be removed from dbt_artifacts in a future release." -[testenv:integration_bigquery_1_3_0] +[testenv:integration_bigquery_1_5_0] changedir = integration_test_project -deps = dbt-bigquery~=1.3.0 +deps = dbt-bigquery~=1.5.0 commands = dbt clean dbt deps dbt build --target bigquery --vars '"my_var": "my value"' + echo "Warnings: This version will be removed from dbt_artifacts in a future release." [testenv:integration_bigquery_1_4_0] changedir = integration_test_project @@ -271,111 +276,122 @@ commands = dbt clean dbt deps dbt build --target bigquery --vars '"my_var": "my value"' + echo "Warnings: This version will be removed from dbt_artifacts in a future release." -[testenv:integration_bigquery_1_5_0] +[testenv:integration_bigquery_1_3_0] changedir = integration_test_project -deps = dbt-bigquery~=1.5.0 +deps = dbt-bigquery~=1.3.0 commands = dbt clean dbt deps dbt build --target bigquery --vars '"my_var": "my value"' + echo "Warnings: This version will be removed from dbt_artifacts in a future release." -[testenv:integration_bigquery_1_6_0] +### Databricks ############################################################################################# +[testenv:integration_databricks] changedir = integration_test_project -deps = dbt-bigquery~=1.6.0 +deps = dbt-databricks<2.0.0 commands = dbt clean dbt deps - dbt build --target bigquery --vars '"my_var": "my value"' + dbt build --target databricks -[testenv:integration_bigquery_1_7_0] +[testenv:integration_databricks_1_11_0] changedir = integration_test_project -deps = dbt-bigquery~=1.7.0 +deps = dbt-databricks~=1.11.0 commands = dbt clean dbt deps - dbt build --target bigquery --vars '"my_var": "my value"' + dbt build --target databricks -[testenv:integration_bigquery_1_8_0] +[testenv:integration_databricks_1_10_0] changedir = integration_test_project -deps = dbt-bigquery~=1.8.0 +deps = dbt-databricks~=1.10.0 commands = dbt clean dbt deps - dbt build --target bigquery --vars '"my_var": "my value"' + dbt build --target databricks -[testenv:integration_bigquery_1_9_0] +[testenv:integration_databricks_1_9_0] changedir = integration_test_project -deps = dbt-bigquery~=1.9.0 +deps = dbt-databricks~=1.9.0 commands = dbt clean dbt deps - dbt build --target bigquery --vars '"my_var": "my value"' + dbt build --target databricks -# Spark integration test (disabled) -[testenv:integration_spark] +[testenv:integration_databricks_1_8_0] changedir = integration_test_project -deps = dbt-spark[ODBC]~=1.4.0 +deps = dbt-databricks~=1.8.0 commands = dbt clean dbt deps - dbt build --exclude snapshot --target spark + dbt build --target databricks -[testenv:integration_postgres] +[testenv:integration_databricks_1_7_0] changedir = integration_test_project -deps = - dbt-core~=1.9.0 - dbt-postgres~=1.9.0 +deps = dbt-databricks~=1.7.0 commands = dbt clean dbt deps - dbt build --target postgres + dbt build --target databricks -[testenv:integration_postgres_1_3_0] +#### End of Life Databricks Versions +#### At a future date, these will be removed. +[testenv:integration_databricks_1_6_0] changedir = integration_test_project -deps = dbt-postgres~=1.3.0 +deps = dbt-databricks~=1.6.0 commands = dbt clean dbt deps - dbt build --target postgres + dbt build --target databricks + echo "Warnings: This version will be removed from dbt_artifacts in a future release." -[testenv:integration_postgres_1_4_0] +[testenv:integration_databricks_1_5_0] changedir = integration_test_project -deps = dbt-postgres~=1.4.0 +deps = dbt-databricks~=1.5.0 commands = dbt clean dbt deps - dbt build --target postgres + dbt build --target databricks + echo "Warnings: This version will be removed from dbt_artifacts in a future release." -[testenv:integration_postgres_1_5_0] +[testenv:integration_databricks_1_4_0] changedir = integration_test_project -deps = dbt-postgres~=1.5.0 +deps = dbt-databricks~=1.4.0 commands = dbt clean dbt deps - dbt build --target postgres + dbt build --target databricks + echo "Warnings: This version will be removed from dbt_artifacts in a future release." -[testenv:integration_postgres_1_6_0] +[testenv:integration_databricks_1_3_0] changedir = integration_test_project -deps = dbt-postgres~=1.6.0 +deps = dbt-databricks~=1.3.0 commands = dbt clean dbt deps - dbt build --target postgres + dbt build --target databricks + echo "Warnings: This version will be removed from dbt_artifacts in a future release." -[testenv:integration_postgres_1_7_0] +### Postgres ############################################################################################# +[testenv:integration_postgres] changedir = integration_test_project -deps = dbt-postgres~=1.7.0 +deps = + dbt-core<2.0.0 + dbt-postgres<2.0.0 commands = dbt clean dbt deps dbt build --target postgres -[testenv:integration_postgres_1_8_0] +# Note: dbt-postgres has no 1.11 release as of 2026-05-11; 1.10 is the latest. + +[testenv:integration_postgres_1_10_0] changedir = integration_test_project deps = - dbt-core~=1.8.0 - dbt-postgres~=1.8.0 + dbt-core~=1.10.0 + dbt-postgres~=1.10.0 commands = dbt clean dbt deps @@ -391,65 +407,92 @@ commands = dbt deps dbt build --target postgres -[testenv:integration_sqlserver] +[testenv:integration_postgres_1_8_0] changedir = integration_test_project -deps = dbt-sqlserver~=1.8.0 +deps = + dbt-core~=1.8.0 + dbt-postgres~=1.8.0 commands = dbt clean dbt deps - dbt build --target sqlserver + dbt build --target postgres -[testenv:integration_sqlserver_1_3_0] +[testenv:integration_postgres_1_7_0] changedir = integration_test_project -deps = dbt-sqlserver~=1.3.0 +deps = dbt-postgres~=1.7.0 commands = dbt clean dbt deps - dbt build --target sqlserver + dbt build --target postgres -[testenv:integration_sqlserver_1_4_0] +#### End of Life Postgres Versions +#### At a future date, these will be removed. +[testenv:integration_postgres_1_6_0] changedir = integration_test_project -deps = dbt-sqlserver~=1.4.0 +deps = dbt-postgres~=1.6.0 commands = dbt clean dbt deps - dbt build --target sqlserver + dbt build --target postgres + echo "Warnings: This version will be removed from dbt_artifacts in a future release." -[testenv:integration_sqlserver_1_7_0] +[testenv:integration_postgres_1_5_0] changedir = integration_test_project -deps = dbt-sqlserver~=1.7.0 +deps = dbt-postgres~=1.5.0 commands = dbt clean dbt deps - dbt build --target sqlserver + dbt build --target postgres + echo "Warnings: This version will be removed from dbt_artifacts in a future release." -[testenv:integration_sqlserver_1_8_0] +[testenv:integration_postgres_1_4_0] changedir = integration_test_project -deps = dbt-sqlserver~=1.8.0 +deps = dbt-postgres~=1.4.0 commands = dbt clean dbt deps - dbt build --target sqlserver -#trino integration tests + dbt build --target postgres + echo "Warnings: This version will be removed from dbt_artifacts in a future release." + +[testenv:integration_postgres_1_3_0] +changedir = integration_test_project +deps = dbt-postgres~=1.3.0 +commands = + dbt clean + dbt deps + dbt build --target postgres + echo "Warnings: This version will be removed from dbt_artifacts in a future release." + +### Trino ############################################################################################# [testenv:integration_trino] changedir = integration_test_project -deps = dbt-trino~=1.7.0 +deps = dbt-trino<2.0.0 commands = dbt clean dbt deps dbt build --target trino -[testenv:integration_trino_1_3_0] +# Note: dbt-trino has no 1.11 release as of 2026-05-11; 1.10 is the latest. + +[testenv:integration_trino_1_10_0] changedir = integration_test_project -deps = dbt-trino~=1.3.0 +deps = dbt-trino~=1.10.0 commands = dbt clean dbt deps dbt build --target trino -[testenv:integration_trino_1_4_0] +[testenv:integration_trino_1_7_0] changedir = integration_test_project -deps = dbt-trino~=1.4.0 +deps = dbt-trino~=1.7.0 +commands = + dbt clean + dbt deps + dbt build --target trino + +[testenv:integration_trino_1_6_0] +changedir = integration_test_project +deps = dbt-trino~=1.6.0 commands = dbt clean dbt deps @@ -463,18 +506,104 @@ commands = dbt deps dbt build --target trino -[testenv:integration_trino_1_6_0] +#### End of Life Trino Versions +#### At a future date, these will be removed. +[testenv:integration_trino_1_4_0] changedir = integration_test_project -deps = dbt-trino~=1.6.0 +deps = dbt-trino~=1.4.0 commands = dbt clean dbt deps dbt build --target trino + echo "Warnings: This version will be removed from dbt_artifacts in a future release." -[testenv:integration_trino_1_7_0] +[testenv:integration_trino_1_3_0] changedir = integration_test_project -deps = dbt-trino~=1.7.0 +deps = dbt-trino~=1.3.0 +commands = + dbt clean + dbt deps + dbt build --target trino + echo "Warnings: This version will be removed from dbt_artifacts in a future release." + +### SQL Server ############################################################################################# +# Note: dbt-sqlserver lags the other adapters — latest release is 1.9.0 as of +# 2026-05-11. No 1.10 / 1.11 envs because no upstream releases exist yet. +[testenv:integration_sqlserver] +changedir = integration_test_project +deps = dbt-sqlserver<2.0.0 +commands = + dbt clean + dbt deps + dbt build --target sqlserver + +[testenv:integration_sqlserver_1_9_0] +changedir = integration_test_project +deps = dbt-sqlserver~=1.9.0 +commands = + dbt clean + dbt deps + dbt build --target sqlserver + +[testenv:integration_sqlserver_1_8_0] +changedir = integration_test_project +deps = dbt-sqlserver~=1.8.0 +commands = + dbt clean + dbt deps + dbt build --target sqlserver + +[testenv:integration_sqlserver_1_7_0] +changedir = integration_test_project +deps = dbt-sqlserver~=1.7.0 +commands = + dbt clean + dbt deps + dbt build --target sqlserver + +#### End of Life SQL Server Versions +#### At a future date, these will be removed. +[testenv:integration_sqlserver_1_4_0] +changedir = integration_test_project +deps = dbt-sqlserver~=1.4.0 +commands = + dbt clean + dbt deps + dbt build --target sqlserver + echo "Warnings: This version will be removed from dbt_artifacts in a future release." + +[testenv:integration_sqlserver_1_3_0] +changedir = integration_test_project +deps = dbt-sqlserver~=1.3.0 commands = dbt clean dbt deps - dbt build --target trino \ No newline at end of file + dbt build --target sqlserver + echo "Warnings: This version will be removed from dbt_artifacts in a future release." + + +### Disabled ############################################################################################# +### Spark ############################################################################################# +[testenv:integration_spark] +changedir = integration_test_project +deps = dbt-spark[ODBC]~=1.4.0 +commands = + dbt clean + dbt deps + dbt build --exclude snapshot --target spark + +### dbt Fusion ############################################################################################# +# Stub. Fusion is the Rust-based dbt reimplementation; it is NOT a PyPI +# package and cannot be installed via `pip` / `uv` today. When Fusion ships +# in a way that's installable from CI runners, wire up envs here following +# the same naming convention as the other adapters (e.g. +# `integration_fusion_snowflake`). Until then this section is a placeholder +# so reviewers know fusion has been considered, not forgotten. +# +# Tracking: specs/ci-rework.md §12 ("Decisions to revisit later"). +# +# [testenv:integration_fusion_] +# changedir = integration_test_project +# allowlist_externals = ... # path to fusion binary +# commands = +# ... diff --git a/uv.lock b/uv.lock new file mode 100644 index 00000000..dccb9231 --- /dev/null +++ b/uv.lock @@ -0,0 +1,1895 @@ +version = 1 +revision = 1 +requires-python = ">=3.12" +resolution-markers = [ + "python_full_version >= '3.13'", + "python_full_version < '3.13'", +] + +[[package]] +name = "agate" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "babel" }, + { name = "isodate" }, + { name = "leather" }, + { name = "parsedatetime" }, + { name = "python-slugify" }, + { 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 } +wheels = [ + { 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-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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, +] + +[[package]] +name = "asn1crypto" +version = "1.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/de/cf/d547feed25b5244fcb9392e288ff9fdc3280b10260362fc45d37a798a6ee/asn1crypto-1.5.1.tar.gz", hash = "sha256:13ae38502be632115abf8a24cbe5f4da52e3b5231990aff31123c805306ccb9c", size = 121080 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/7f/09065fd9e27da0eda08b4d6897f1c13535066174cc023af248fc2a8d5e5a/asn1crypto-1.5.1-py2.py3-none-any.whl", hash = "sha256:db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67", size = 105045 }, +] + +[[package]] +name = "attrs" +version = "25.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/1367933a8532ee6ff8d63537de4f1177af4bff9f3e829baf7331f595bb24/attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b", size = 812032 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/06/bb80f5f86020c4551da315d78b3ab75e8228f89f0162f2c3a819e407941a/attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3", size = 63815 }, +] + +[[package]] +name = "azure-core" +version = "1.34.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, + { name = "six" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/29/ff7a519a315e41c85bab92a7478c6acd1cf0b14353139a08caee4c691f77/azure_core-1.34.0.tar.gz", hash = "sha256:bdb544989f246a0ad1c85d72eeb45f2f835afdcbc5b45e43f0dbde7461c81ece", size = 297999 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/84/9e/5c87b49f65bb16571599bc789857d0ded2f53014d3392bc88a5d1f3ad779/azure_core-1.34.0-py3-none-any.whl", hash = "sha256:0615d3b756beccdb6624d1c0ae97284f38b78fb59a2a9839bf927c66fbbdddd6", size = 207409 }, +] + +[[package]] +name = "azure-identity" +version = "1.22.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "azure-core" }, + { name = "cryptography" }, + { name = "msal" }, + { name = "msal-extensions" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/8e/1b5916f5e1696bf05b009cf7d41383cea54aa8536d4a4f6f88cca15eb6a4/azure_identity-1.22.0.tar.gz", hash = "sha256:c8f5ef23e5295c2fa300c984dd9f5e1fe43503fc25c121c37ff6a15e39b800b9", size = 263346 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/06/1a/6f13d7f95f68f37303c0e00e011d498e4524e70d354b2e11ef5ae89e0ce0/azure_identity-1.22.0-py3-none-any.whl", hash = "sha256:26d6c63f2ca453c77c3e74be8613941ad074e05d0c8be135247573752c249ad8", size = 185524 }, +] + +[[package]] +name = "babel" +version = "2.17.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/6b/d52e42361e1aa00709585ecc30b3f9684b3ab62530771402248b1b1d6240/babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d", size = 9951852 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b8/3fe70c75fe32afc4bb507f75563d39bc5642255d1d94f1f23604725780bf/babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2", size = 10182537 }, +] + +[[package]] +name = "cachetools" +version = "5.5.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6c/81/3747dad6b14fa2cf53fcf10548cf5aea6913e96fab41a3c198676f8948a5/cachetools-5.5.2.tar.gz", hash = "sha256:1a661caa9175d26759571b2e19580f9d6393969e5dfca11fdb1f947a23e640d4", size = 28380 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/76/20fa66124dbe6be5cafeb312ece67de6b61dd91a0247d1ea13db4ebb33c2/cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a", size = 10080 }, +] + +[[package]] +name = "certifi" +version = "2025.1.31" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 }, +] + +[[package]] +name = "cffi" +version = "1.17.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/97/c783634659c2920c3fc70419e3af40972dbaf758daa229a7d6ea6135c90d/cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824", size = 516621 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/84/e94227139ee5fb4d600a7a4927f322e1d4aea6fdc50bd3fca8493caba23f/cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4", size = 183178 }, + { url = "https://files.pythonhosted.org/packages/da/ee/fb72c2b48656111c4ef27f0f91da355e130a923473bf5ee75c5643d00cca/cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c", size = 178840 }, + { url = "https://files.pythonhosted.org/packages/cc/b6/db007700f67d151abadf508cbfd6a1884f57eab90b1bb985c4c8c02b0f28/cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36", size = 454803 }, + { url = "https://files.pythonhosted.org/packages/1a/df/f8d151540d8c200eb1c6fba8cd0dfd40904f1b0682ea705c36e6c2e97ab3/cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5", size = 478850 }, + { url = "https://files.pythonhosted.org/packages/28/c0/b31116332a547fd2677ae5b78a2ef662dfc8023d67f41b2a83f7c2aa78b1/cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff", size = 485729 }, + { url = "https://files.pythonhosted.org/packages/91/2b/9a1ddfa5c7f13cab007a2c9cc295b70fbbda7cb10a286aa6810338e60ea1/cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99", size = 471256 }, + { url = "https://files.pythonhosted.org/packages/b2/d5/da47df7004cb17e4955df6a43d14b3b4ae77737dff8bf7f8f333196717bf/cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93", size = 479424 }, + { url = "https://files.pythonhosted.org/packages/0b/ac/2a28bcf513e93a219c8a4e8e125534f4f6db03e3179ba1c45e949b76212c/cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3", size = 484568 }, + { url = "https://files.pythonhosted.org/packages/d4/38/ca8a4f639065f14ae0f1d9751e70447a261f1a30fa7547a828ae08142465/cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8", size = 488736 }, + { url = "https://files.pythonhosted.org/packages/86/c5/28b2d6f799ec0bdecf44dced2ec5ed43e0eb63097b0f58c293583b406582/cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65", size = 172448 }, + { url = "https://files.pythonhosted.org/packages/50/b9/db34c4755a7bd1cb2d1603ac3863f22bcecbd1ba29e5ee841a4bc510b294/cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903", size = 181976 }, + { url = "https://files.pythonhosted.org/packages/8d/f8/dd6c246b148639254dad4d6803eb6a54e8c85c6e11ec9df2cffa87571dbe/cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e", size = 182989 }, + { url = "https://files.pythonhosted.org/packages/8b/f1/672d303ddf17c24fc83afd712316fda78dc6fce1cd53011b839483e1ecc8/cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2", size = 178802 }, + { url = "https://files.pythonhosted.org/packages/0e/2d/eab2e858a91fdff70533cab61dcff4a1f55ec60425832ddfdc9cd36bc8af/cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3", size = 454792 }, + { url = "https://files.pythonhosted.org/packages/75/b2/fbaec7c4455c604e29388d55599b99ebcc250a60050610fadde58932b7ee/cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683", size = 478893 }, + { url = "https://files.pythonhosted.org/packages/4f/b7/6e4a2162178bf1935c336d4da8a9352cccab4d3a5d7914065490f08c0690/cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5", size = 485810 }, + { url = "https://files.pythonhosted.org/packages/c7/8a/1d0e4a9c26e54746dc08c2c6c037889124d4f59dffd853a659fa545f1b40/cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4", size = 471200 }, + { url = "https://files.pythonhosted.org/packages/26/9f/1aab65a6c0db35f43c4d1b4f580e8df53914310afc10ae0397d29d697af4/cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd", size = 479447 }, + { url = "https://files.pythonhosted.org/packages/5f/e4/fb8b3dd8dc0e98edf1135ff067ae070bb32ef9d509d6cb0f538cd6f7483f/cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed", size = 484358 }, + { url = "https://files.pythonhosted.org/packages/f1/47/d7145bf2dc04684935d57d67dff9d6d795b2ba2796806bb109864be3a151/cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9", size = 488469 }, + { url = "https://files.pythonhosted.org/packages/bf/ee/f94057fa6426481d663b88637a9a10e859e492c73d0384514a17d78ee205/cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d", size = 172475 }, + { url = "https://files.pythonhosted.org/packages/7c/fc/6a8cb64e5f0324877d503c854da15d76c1e50eb722e320b15345c4d0c6de/cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a", size = 182009 }, +] + +[[package]] +name = "chardet" +version = "5.2.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/0d/f7b6ab21ec75897ed80c17d79b15951a719226b9fababf1e40ea74d69079/chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7", size = 2069618 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/6f/f5fbc992a329ee4e0f288c1fe0e2ad9485ed064cac731ed2fe47dcc38cbf/chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970", size = 199385 }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105 }, + { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404 }, + { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423 }, + { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184 }, + { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268 }, + { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601 }, + { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098 }, + { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520 }, + { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852 }, + { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488 }, + { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192 }, + { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550 }, + { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785 }, + { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 }, + { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 }, + { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 }, + { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 }, + { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 }, + { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 }, + { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 }, + { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 }, + { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 }, + { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 }, + { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 }, + { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 }, + { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 }, + { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 }, +] + +[[package]] +name = "click" +version = "8.1.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/d4/7ebdbd03970677812aac39c869717059dbb71a4cfc033ca6e5221787892c/click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2", size = 98188 }, +] + +[[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 } +wheels = [ + { 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 = "cryptography" +version = "44.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cd/25/4ce80c78963834b8a9fd1cc1266be5ed8d1840785c0f2e1b73b8d128d505/cryptography-44.0.2.tar.gz", hash = "sha256:c63454aa261a0cf0c5b4718349629793e9e634993538db841165b3df74f37ec0", size = 710807 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/92/ef/83e632cfa801b221570c5f58c0369db6fa6cef7d9ff859feab1aae1a8a0f/cryptography-44.0.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:efcfe97d1b3c79e486554efddeb8f6f53a4cdd4cf6086642784fa31fc384e1d7", size = 6676361 }, + { url = "https://files.pythonhosted.org/packages/30/ec/7ea7c1e4c8fc8329506b46c6c4a52e2f20318425d48e0fe597977c71dbce/cryptography-44.0.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29ecec49f3ba3f3849362854b7253a9f59799e3763b0c9d0826259a88efa02f1", size = 3952350 }, + { url = "https://files.pythonhosted.org/packages/27/61/72e3afdb3c5ac510330feba4fc1faa0fe62e070592d6ad00c40bb69165e5/cryptography-44.0.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc821e161ae88bfe8088d11bb39caf2916562e0a2dc7b6d56714a48b784ef0bb", size = 4166572 }, + { url = "https://files.pythonhosted.org/packages/26/e4/ba680f0b35ed4a07d87f9e98f3ebccb05091f3bf6b5a478b943253b3bbd5/cryptography-44.0.2-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:3c00b6b757b32ce0f62c574b78b939afab9eecaf597c4d624caca4f9e71e7843", size = 3958124 }, + { url = "https://files.pythonhosted.org/packages/9c/e8/44ae3e68c8b6d1cbc59040288056df2ad7f7f03bbcaca6b503c737ab8e73/cryptography-44.0.2-cp37-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:7bdcd82189759aba3816d1f729ce42ffded1ac304c151d0a8e89b9996ab863d5", size = 3678122 }, + { url = "https://files.pythonhosted.org/packages/27/7b/664ea5e0d1eab511a10e480baf1c5d3e681c7d91718f60e149cec09edf01/cryptography-44.0.2-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:4973da6ca3db4405c54cd0b26d328be54c7747e89e284fcff166132eb7bccc9c", size = 4191831 }, + { url = "https://files.pythonhosted.org/packages/2a/07/79554a9c40eb11345e1861f46f845fa71c9e25bf66d132e123d9feb8e7f9/cryptography-44.0.2-cp37-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:4e389622b6927d8133f314949a9812972711a111d577a5d1f4bee5e58736b80a", size = 3960583 }, + { url = "https://files.pythonhosted.org/packages/bb/6d/858e356a49a4f0b591bd6789d821427de18432212e137290b6d8a817e9bf/cryptography-44.0.2-cp37-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:f514ef4cd14bb6fb484b4a60203e912cfcb64f2ab139e88c2274511514bf7308", size = 4191753 }, + { url = "https://files.pythonhosted.org/packages/b2/80/62df41ba4916067fa6b125aa8c14d7e9181773f0d5d0bd4dcef580d8b7c6/cryptography-44.0.2-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:1bc312dfb7a6e5d66082c87c34c8a62176e684b6fe3d90fcfe1568de675e6688", size = 4079550 }, + { url = "https://files.pythonhosted.org/packages/f3/cd/2558cc08f7b1bb40683f99ff4327f8dcfc7de3affc669e9065e14824511b/cryptography-44.0.2-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:3b721b8b4d948b218c88cb8c45a01793483821e709afe5f622861fc6182b20a7", size = 4298367 }, + { url = "https://files.pythonhosted.org/packages/71/59/94ccc74788945bc3bd4cf355d19867e8057ff5fdbcac781b1ff95b700fb1/cryptography-44.0.2-cp37-abi3-win32.whl", hash = "sha256:51e4de3af4ec3899d6d178a8c005226491c27c4ba84101bfb59c901e10ca9f79", size = 2772843 }, + { url = "https://files.pythonhosted.org/packages/ca/2c/0d0bbaf61ba05acb32f0841853cfa33ebb7a9ab3d9ed8bb004bd39f2da6a/cryptography-44.0.2-cp37-abi3-win_amd64.whl", hash = "sha256:c505d61b6176aaf982c5717ce04e87da5abc9a36a5b39ac03905c4aafe8de7aa", size = 3209057 }, + { url = "https://files.pythonhosted.org/packages/9e/be/7a26142e6d0f7683d8a382dd963745e65db895a79a280a30525ec92be890/cryptography-44.0.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8e0ddd63e6bf1161800592c71ac794d3fb8001f2caebe0966e77c5234fa9efc3", size = 6677789 }, + { url = "https://files.pythonhosted.org/packages/06/88/638865be7198a84a7713950b1db7343391c6066a20e614f8fa286eb178ed/cryptography-44.0.2-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81276f0ea79a208d961c433a947029e1a15948966658cf6710bbabb60fcc2639", size = 3951919 }, + { url = "https://files.pythonhosted.org/packages/d7/fc/99fe639bcdf58561dfad1faa8a7369d1dc13f20acd78371bb97a01613585/cryptography-44.0.2-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a1e657c0f4ea2a23304ee3f964db058c9e9e635cc7019c4aa21c330755ef6fd", size = 4167812 }, + { url = "https://files.pythonhosted.org/packages/53/7b/aafe60210ec93d5d7f552592a28192e51d3c6b6be449e7fd0a91399b5d07/cryptography-44.0.2-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:6210c05941994290f3f7f175a4a57dbbb2afd9273657614c506d5976db061181", size = 3958571 }, + { url = "https://files.pythonhosted.org/packages/16/32/051f7ce79ad5a6ef5e26a92b37f172ee2d6e1cce09931646eef8de1e9827/cryptography-44.0.2-cp39-abi3-manylinux_2_28_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:d1c3572526997b36f245a96a2b1713bf79ce99b271bbcf084beb6b9b075f29ea", size = 3679832 }, + { url = "https://files.pythonhosted.org/packages/78/2b/999b2a1e1ba2206f2d3bca267d68f350beb2b048a41ea827e08ce7260098/cryptography-44.0.2-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:b042d2a275c8cee83a4b7ae30c45a15e6a4baa65a179a0ec2d78ebb90e4f6699", size = 4193719 }, + { url = "https://files.pythonhosted.org/packages/72/97/430e56e39a1356e8e8f10f723211a0e256e11895ef1a135f30d7d40f2540/cryptography-44.0.2-cp39-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:d03806036b4f89e3b13b6218fefea8d5312e450935b1a2d55f0524e2ed7c59d9", size = 3960852 }, + { url = "https://files.pythonhosted.org/packages/89/33/c1cf182c152e1d262cac56850939530c05ca6c8d149aa0dcee490b417e99/cryptography-44.0.2-cp39-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:c7362add18b416b69d58c910caa217f980c5ef39b23a38a0880dfd87bdf8cd23", size = 4193906 }, + { url = "https://files.pythonhosted.org/packages/e1/99/87cf26d4f125380dc674233971069bc28d19b07f7755b29861570e513650/cryptography-44.0.2-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:8cadc6e3b5a1f144a039ea08a0bdb03a2a92e19c46be3285123d32029f40a922", size = 4081572 }, + { url = "https://files.pythonhosted.org/packages/b3/9f/6a3e0391957cc0c5f84aef9fbdd763035f2b52e998a53f99345e3ac69312/cryptography-44.0.2-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:6f101b1f780f7fc613d040ca4bdf835c6ef3b00e9bd7125a4255ec574c7916e4", size = 4298631 }, + { url = "https://files.pythonhosted.org/packages/e2/a5/5bc097adb4b6d22a24dea53c51f37e480aaec3465285c253098642696423/cryptography-44.0.2-cp39-abi3-win32.whl", hash = "sha256:3dc62975e31617badc19a906481deacdeb80b4bb454394b4098e3f2525a488c5", size = 2773792 }, + { url = "https://files.pythonhosted.org/packages/33/cf/1f7649b8b9a3543e042d3f348e398a061923ac05b507f3f4d95f11938aa9/cryptography-44.0.2-cp39-abi3-win_amd64.whl", hash = "sha256:5f6f90b72d8ccadb9c6e311c775c8305381db88374c65fa1a68250aa8a9cb3a6", size = 3210957 }, +] + +[[package]] +name = "daff" +version = "1.3.46" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0e/fc/82796c10545f3df9882566c79debac28b664e3a3a08fdb493ac3cc418709/daff-1.3.46.tar.gz", hash = "sha256:22d0da9fd6a3275b54c926a9c97b180f9258aad65113ea18f3fec52cbadcd818", size = 149820 } + +[[package]] +name = "db-dtypes" +version = "1.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "pyarrow" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7c/f3/65b49a0b3f6cc0e4daf46112f7590d189eba87ee36f915cc13a2e6a0cecf/db_dtypes-1.4.2.tar.gz", hash = "sha256:04348969e0d533de5f11ec3ac8fcb2dd983ac40229d042198ab9d8de51801a6e", size = 33539 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/aa/8f09c6af64d562606d128acab327dab759ac005a204f470c6d257f47d857/db_dtypes-1.4.2-py2.py3-none-any.whl", hash = "sha256:b3cd0128c8310a2e9ef249da2353e5cb07c62d8a3ce800c7990f9998eee74582", size = 18970 }, +] + +[[package]] +name = "dbt-adapters" +version = "1.14.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "agate" }, + { name = "dbt-common" }, + { name = "mashumaro", extra = ["msgpack"] }, + { name = "protobuf" }, + { name = "pytz" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/6f/fa/b89f66fbbda324f28778da16cb589a7d9e38704aca97c9329bfc474b2c12/dbt_adapters-1.14.4.tar.gz", hash = "sha256:e94617506648cc763177c27e6af9a721f76282d828554d25136f278366f443c3", size = 132425 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/0b/0221677be60aad1ee75108a4ca0ab75f5b4de64c0dbeff1e41484b600c7e/dbt_adapters-1.14.4-py3-none-any.whl", hash = "sha256:695bd43085f671a19e430ce44af38194e6ad624afa4cfa5db03dbe20d857bedc", size = 172201 }, +] + +[[package]] +name = "dbt-artifacts" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "dbt-bigquery" }, + { name = "dbt-postgres" }, + { name = "dbt-snowflake" }, + { name = "dbt-sqlserver" }, + { name = "dbt-trino" }, + { name = "pyarrow" }, + { name = "tox" }, +] + +[package.metadata] +requires-dist = [ + { name = "dbt-bigquery", specifier = ">=1.9.1" }, + { name = "dbt-postgres", specifier = ">=1.9.0" }, + { name = "dbt-snowflake", specifier = ">=1.9.2" }, + { name = "dbt-sqlserver", specifier = ">=1.8.7" }, + { name = "dbt-trino", specifier = ">=1.9.1" }, + { name = "pyarrow", specifier = "<19.0.0" }, + { name = "tox", specifier = ">=4.25.0" }, +] + +[[package]] +name = "dbt-bigquery" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dbt-adapters" }, + { name = "dbt-common" }, + { name = "dbt-core" }, + { name = "google-api-core" }, + { name = "google-cloud-bigquery", extra = ["pandas"] }, + { name = "google-cloud-dataproc" }, + { name = "google-cloud-storage" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/2b/5e7632fe49bfdb0a30a01aedd95fef5726ecc1bff818a01c403a36c44d10/dbt_bigquery-1.9.1.tar.gz", hash = "sha256:da1c7f1fa7e0a06265881d990e385f5867369529bbebf4b36500db0b3bcd495b", size = 57231 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/86/777ffda6e87b75b7f00574d2b8cef75189e25c8671a067ad4b9ac952a942/dbt_bigquery-1.9.1-py3-none-any.whl", hash = "sha256:e2f55050e0b212403701cc21aa2de1829aa724dfbca788db02061acf9be0603f", size = 79863 }, +] + +[[package]] +name = "dbt-common" +version = "1.17.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "agate" }, + { name = "colorama" }, + { name = "deepdiff" }, + { name = "isodate" }, + { name = "jinja2" }, + { name = "jsonschema" }, + { name = "mashumaro", extra = ["msgpack"] }, + { name = "pathspec" }, + { name = "protobuf" }, + { name = "python-dateutil" }, + { name = "requests" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/bd/0efb61e88eb54c92e84d18583ea2d3abb971769e5a57ffda292dc67979ed/dbt_common-1.17.0.tar.gz", hash = "sha256:257c94a7726ce47698d54e553f0df5b98a410281b4d8fc91713d1dc3319dd8ad", size = 83675 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/19/19b16118b1c24107820b829699c4935741fb0e783579ed3c4b6d8b25064e/dbt_common-1.17.0-py3-none-any.whl", hash = "sha256:2f8ed5721908d84d3487e2461e4cec11068db02c3f93a0aaf06771ab4f92055b", size = 86466 }, +] + +[[package]] +name = "dbt-core" +version = "1.8.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "agate" }, + { name = "click" }, + { name = "daff" }, + { name = "dbt-adapters" }, + { name = "dbt-common" }, + { name = "dbt-extractor" }, + { name = "dbt-semantic-interfaces" }, + { name = "jinja2" }, + { name = "logbook" }, + { name = "mashumaro", extra = ["msgpack"] }, + { name = "minimal-snowplow-tracker" }, + { name = "networkx" }, + { name = "packaging" }, + { name = "pathspec" }, + { name = "protobuf" }, + { name = "pytz" }, + { name = "pyyaml" }, + { name = "requests" }, + { name = "sqlparse" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/40/79/1e742f9f1195015b953a37d7913f6a1e5cbd48ee68073d7f31b0f2eb404c/dbt_core-1.8.9.tar.gz", hash = "sha256:763a69b0fa4551ceff7a98b88b4f7af05eff0db959086373369b30b342bb5b26", size = 824457 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/7c/57d8500eb9d8b3484c81ca66859d130b605db1a591fe4d676849793591ba/dbt_core-1.8.9-py3-none-any.whl", hash = "sha256:d376cf9c6eb13ed1e2a654e164bf84f4e66c57621d1681733835e49bb9cb1683", size = 900465 }, +] + +[[package]] +name = "dbt-extractor" +version = "0.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2c/d0/4ee14955ad0214da695b3c15dc0acf2ab54c9d263242f36073c999cb699a/dbt_extractor-0.5.1.tar.gz", hash = "sha256:cd5d95576a8dea4190240aaf9936a37fd74b4b7913ca69a3c368fc4472bb7e13", size = 266278 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/77/1f/ca6d66d67464df1ea8e814d09b1100d15672ae4ce7f0dff41f67956e5f7f/dbt_extractor-0.5.1-cp38-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:3b91e6106b967d908b34f83929d3f50ee2b498876a1be9c055fe060ed728c556", size = 865677 }, + { url = "https://files.pythonhosted.org/packages/3b/be/0ae4a5c6c721ee42d849482084b5f4544acafe3c8cf4c84170f35c63fe50/dbt_extractor-0.5.1-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:3614ce9f83ae4cd0dc95f77730034a793a1c090a52dcf698ba1c94050afe3a8b", size = 438730 }, + { url = "https://files.pythonhosted.org/packages/a9/ac/bbe5d223a03632d4192414a8af0aa6e2c16555a6e7d33515225b4c978096/dbt_extractor-0.5.1-cp38-abi3-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ea4edf33035d0a060b1e01c42fb2d99316457d44c954d6ed4eed9f1948664d87", size = 1385155 }, + { url = "https://files.pythonhosted.org/packages/6d/96/caef63d79f3a06bcae1aca43302c1b9efa58590644efca41c4404607510e/dbt_extractor-0.5.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3b9bf50eb062b4344d9546fe42038996c6e7e7daa10724aa955d64717260e5d", size = 1344382 }, + { url = "https://files.pythonhosted.org/packages/66/ce/8c248ba3def50203925a1404d21a03999e2fe32bf7611e6f9de1006817ba/dbt_extractor-0.5.1-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c0ce901d4ebf0664977e4e1cbf596d4afc6c1339fcc7d2cf67ce3481566a626f", size = 1343152 }, + { url = "https://files.pythonhosted.org/packages/11/73/5ead77c8b742453e1a34a064d921933bbca4f8941ad8f14fd47d0a15c49c/dbt_extractor-0.5.1-cp38-abi3-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:cbe338b76e9ffaa18275456e041af56c21bb517f6fbda7a58308138703da0996", size = 1498587 }, + { url = "https://files.pythonhosted.org/packages/51/e6/140058fbeb482071a7b199986c40385dfdc97f23b0ea20b0740762d2e116/dbt_extractor-0.5.1-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b25fa7a276ab26aa2d70ff6e0cf4cfb1490d7831fb57ee1337c24d2b0333b84", size = 1482391 }, + { url = "https://files.pythonhosted.org/packages/63/e6/a40a89c75701fa91fc7297b9d77f303fc93669a32a10be4457a02de0584f/dbt_extractor-0.5.1-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c5651e458be910ff567c0da3ea2eb084fd01884cc88888ac2cf1e240dcddacc2", size = 1517273 }, + { url = "https://files.pythonhosted.org/packages/30/da/a9528ca8224317aad1dab22f77468dd13e94c46b56db953b5b1e3b698a8f/dbt_extractor-0.5.1-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62e4f040fd338b652683421ce48e903812e27fd6e7af58b1b70a4e1f9f2c79e3", size = 1346957 }, + { url = "https://files.pythonhosted.org/packages/7b/2b/48ad70e0490e492b1f59e260d447b3c9eaaad661eb4b46baacc2f328dabf/dbt_extractor-0.5.1-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:91e25ad78f1f4feadd27587ebbcc46ad909cfad843118908f30336d08d8400ca", size = 1524362 }, + { url = "https://files.pythonhosted.org/packages/6c/cc/6dce67509e94080535b400b03d7d13fecd2acba72c10c21df8b7755212ce/dbt_extractor-0.5.1-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:cdf9938b36cd098bcdd80f43dc03864da3f69f57d903a9160a32236540d4ddcd", size = 1603552 }, + { url = "https://files.pythonhosted.org/packages/58/b6/14ab2c80385a29ad013a0a0642522b393bf1220d6c01587aad4796784cc1/dbt_extractor-0.5.1-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:475e2c05b17eb4976eff6c8f7635be42bec33f15a74ceb87a40242c94a99cebf", size = 1550461 }, + { url = "https://files.pythonhosted.org/packages/7c/04/19af8b0cb0e341d091cca21ff3cfed95f152e39f598b7313c79a6804f32f/dbt_extractor-0.5.1-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:100453ba06e169cbdb118234ab3f06f6722a2e0e316089b81c88dea701212abc", size = 1520792 }, + { url = "https://files.pythonhosted.org/packages/10/dd/b3c440b8eeac318a2d3b0f190783feedad60b962fe984d6d0cb482b128b4/dbt_extractor-0.5.1-cp38-abi3-win32.whl", hash = "sha256:6916aae085fd5f2af069fd6947933e78b742c9e3d2165e1740c2e28ae543309a", size = 261615 }, + { url = "https://files.pythonhosted.org/packages/8c/ad/fa331537dbe97250dda06342775891ae2b1fb8b54cf9219e47781f641657/dbt_extractor-0.5.1-cp38-abi3-win_amd64.whl", hash = "sha256:eecc08f3743e802a8ede60c89f7b2bce872acc86120cbc0ae7df229bb8a95083", size = 283481 }, +] + +[[package]] +name = "dbt-fabric" +version = "1.8.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "azure-identity" }, + { name = "dbt-adapters" }, + { name = "dbt-common" }, + { name = "dbt-core" }, + { name = "pyodbc" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/54/40/9cf6b74cb4ac8eeb08eaf3372c24144b252b60a56ff5295895aadddb4eb1/dbt-fabric-1.8.9.tar.gz", hash = "sha256:f162d6b0a361e94601597d6a3cd81190e7502310ce1fd2030671dbdf8c5077b2", size = 31298 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/ae/32cc12c94908dbe4d46c5965dbdd7967cf5fac29a71f1e43bc77825e2967/dbt_fabric-1.8.9-py3-none-any.whl", hash = "sha256:dd565aefa3da001617b5ceb1dcb79a61e9975e7e47998ed636e5225e292a7384", size = 45300 }, +] + +[[package]] +name = "dbt-postgres" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "agate" }, + { name = "dbt-adapters" }, + { name = "dbt-common" }, + { name = "dbt-core" }, + { name = "psycopg2-binary" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/09/24/03eae698e7f0bffb579a120758fcf95cdf7a58caccec79254195b4a1cb4c/dbt_postgres-1.9.0.tar.gz", hash = "sha256:b0574e9e1e66d8a5cd627b1d464ec0278eef7342f0b5babe4f987eee9d02a143", size = 23555 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/30/bb/8e48678036e5f89b49f72c98e41fa41ebe853c219e658cad3797afbc50b9/dbt_postgres-1.9.0-py3-none-any.whl", hash = "sha256:c85d1adb419251ac989e5f720fdbb964aa6c280da7739dc8c48d44e6f45d354a", size = 35182 }, +] + +[[package]] +name = "dbt-semantic-interfaces" +version = "0.5.1" +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/ba/1b/c7516c333db7a287fded9083209063731d9095e4958c9cda7c73b17178c7/dbt_semantic_interfaces-0.5.1.tar.gz", hash = "sha256:3a497abef1ba8112affdf804b26bfdcd5468ed95cc924b509068e18d371c7c4d", size = 76089 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/31/ec1943c95ea18eabfcf7d6c882e7265e245f749dc465101343028eac33b8/dbt_semantic_interfaces-0.5.1-py3-none-any.whl", hash = "sha256:b95ff3a6721dc30f6278cb84933d95e0ef27766e67eeb6bb41906242e77f7c9b", size = 119672 }, +] + +[[package]] +name = "dbt-snowflake" +version = "1.9.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "agate" }, + { name = "dbt-adapters" }, + { name = "dbt-common" }, + { name = "dbt-core" }, + { name = "snowflake-connector-python", extra = ["secure-local-storage"] }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/7f/f0589e88d04baa80bb5af84420fc319ddcf72cb9adf2e0a153825771f3e9/dbt_snowflake-1.9.2.tar.gz", hash = "sha256:4aec61a236a32b27eec6424700327820c9d1f34cd192308ad57d23e679012160", size = 41673 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/e0/52bfd6ef22802facd05f8ec1c80a095ae4a900bd897240412ac364493d59/dbt_snowflake-1.9.2-py3-none-any.whl", hash = "sha256:b19a4fa065d4b765e7e24491aad7e9b908dce7b91631895f5341574f87d2a5df", size = 63150 }, +] + +[[package]] +name = "dbt-sqlserver" +version = "1.8.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dbt-adapters" }, + { name = "dbt-common" }, + { name = "dbt-core" }, + { name = "dbt-fabric" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/08/f0/4d9c9f18050098d831035f2c0d8f48513cabd6eb93f66148af67c5c799a7/dbt-sqlserver-1.8.7.tar.gz", hash = "sha256:3d41ed1fa4b3edc71a6d54f88760c3ff1baa5990638be1a4b558434227fde508", size = 20525 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/6f/1fb6bcf8711ecaeeff7d8c05e253a016b6707f540b1a6cda1c2ce4787975/dbt_sqlserver-1.8.7-py3-none-any.whl", hash = "sha256:abb3a529d8f166ea36c69172c7d5aaba5c9ec6df5f7208e66075dc1ecede4347", size = 28611 }, +] + +[[package]] +name = "dbt-trino" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "dbt-adapters" }, + { name = "dbt-common" }, + { name = "dbt-core" }, + { name = "trino" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/36/2c/6be3cbf01fe434ada42ba57ea14f219ea8eb7d7aa43034f10b4750f7a719/dbt-trino-1.9.1.tar.gz", hash = "sha256:6823e39721e276826e0adde449e93f28044a5f08a1fe4a5003d3c41453c012c7", size = 30473 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/21/8bdc43ef72d822b63246b4d9350f74ff5373e9d2feb5f2efcec7dc954358/dbt_trino-1.9.1-py3-none-any.whl", hash = "sha256:b89c0ce5dd28c3db8ab40b55908502ded358b99758c148660d1442e70395b3f1", size = 36280 }, +] + +[[package]] +name = "deepdiff" +version = "7.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "ordered-set" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/70/10/6f4b0bd0627d542f63a24f38e29d77095dc63d5f45bc1a7b4a6ca8750fa9/deepdiff-7.0.1.tar.gz", hash = "sha256:260c16f052d4badbf60351b4f77e8390bee03a0b516246f6839bc813fb429ddf", size = 421718 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/e6/d27d37dc55dbf40cdbd665aa52844b065ac760c9a02a02265f97ea7a4256/deepdiff-7.0.1-py3-none-any.whl", hash = "sha256:447760081918216aa4fd4ca78a4b6a848b81307b2ea94c810255334b759e1dc3", size = 80825 }, +] + +[[package]] +name = "distlib" +version = "0.3.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/dd/1bec4c5ddb504ca60fc29472f3d27e8d4da1257a854e1d96742f15c1d02d/distlib-0.3.9.tar.gz", hash = "sha256:a60f20dea646b8a33f3e7772f74dc0b2d0772d2837ee1342a00645c81edf9403", size = 613923 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973 }, +] + +[[package]] +name = "filelock" +version = "3.18.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0a/10/c23352565a6544bdc5353e0b15fc1c563352101f30e24bf500207a54df9a/filelock-3.18.0.tar.gz", hash = "sha256:adbc88eabb99d2fec8c9c1b229b171f18afa655400173ddc653d5d01501fb9f2", size = 18075 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4d/36/2a115987e2d8c300a974597416d9de88f2444426de9571f4b59b2cca3acc/filelock-3.18.0-py3-none-any.whl", hash = "sha256:c401f4f8377c4464e6db25fff06205fd89bdd83b65eb0488ed1b160f780e21de", size = 16215 }, +] + +[[package]] +name = "google-api-core" +version = "2.24.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-auth" }, + { name = "googleapis-common-protos" }, + { name = "proto-plus" }, + { name = "protobuf" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/09/5c/085bcb872556934bb119e5e09de54daa07873f6866b8f0303c49e72287f7/google_api_core-2.24.2.tar.gz", hash = "sha256:81718493daf06d96d6bc76a91c23874dbf2fac0adbbf542831b805ee6e974696", size = 163516 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/95/f472d85adab6e538da2025dfca9e976a0d125cc0af2301f190e77b76e51c/google_api_core-2.24.2-py3-none-any.whl", hash = "sha256:810a63ac95f3c441b7c0e43d344e372887f62ce9071ba972eacf32672e072de9", size = 160061 }, +] + +[package.optional-dependencies] +grpc = [ + { name = "grpcio" }, + { name = "grpcio-status" }, +] + +[[package]] +name = "google-auth" +version = "2.38.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cachetools" }, + { name = "pyasn1-modules" }, + { name = "rsa" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c6/eb/d504ba1daf190af6b204a9d4714d457462b486043744901a6eeea711f913/google_auth-2.38.0.tar.gz", hash = "sha256:8285113607d3b80a3f1543b75962447ba8a09fe85783432a784fdeef6ac094c4", size = 270866 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9d/47/603554949a37bca5b7f894d51896a9c534b9eab808e2520a748e081669d0/google_auth-2.38.0-py2.py3-none-any.whl", hash = "sha256:e7dae6694313f434a2727bf2906f27ad259bae090d7aa896590d86feec3d9d4a", size = 210770 }, +] + +[[package]] +name = "google-auth-oauthlib" +version = "1.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-auth" }, + { name = "requests-oauthlib" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cc/0f/1772edb8d75ecf6280f1c7f51cbcebe274e8b17878b382f63738fd96cee5/google_auth_oauthlib-1.2.1.tar.gz", hash = "sha256:afd0cad092a2eaa53cd8e8298557d6de1034c6cb4a740500b5357b648af97263", size = 24970 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/8e/22a28dfbd218033e4eeaf3a0533b2b54852b6530da0c0fe934f0cc494b29/google_auth_oauthlib-1.2.1-py2.py3-none-any.whl", hash = "sha256:2d58a27262d55aa1b87678c3ba7142a080098cbc2024f903c62355deb235d91f", size = 24930 }, +] + +[[package]] +name = "google-cloud-bigquery" +version = "3.31.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core", extra = ["grpc"] }, + { name = "google-auth" }, + { name = "google-cloud-core" }, + { name = "google-resumable-media" }, + { name = "packaging" }, + { name = "python-dateutil" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/73/91/4c7274f4d5faf13ac000b06353deaf3579575bf0e4bbad07fa68b9f09ba9/google_cloud_bigquery-3.31.0.tar.gz", hash = "sha256:b89dc716dbe4abdb7a4f873f7050100287bc98514e0614c5d54cd6a8e9fb0991", size = 479961 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/bc/4cb8c61fc6dd817a4a390b745ec7b305f4578f547a16d09d54c8a790624b/google_cloud_bigquery-3.31.0-py3-none-any.whl", hash = "sha256:97f4a3219854ff01d6a3a57312feecb0b6e13062226b823f867e2d3619c4787b", size = 250099 }, +] + +[package.optional-dependencies] +pandas = [ + { name = "db-dtypes" }, + { name = "grpcio" }, + { name = "pandas" }, + { name = "pandas-gbq" }, + { name = "pyarrow" }, +] + +[[package]] +name = "google-cloud-core" +version = "2.4.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core" }, + { name = "google-auth" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d6/b8/2b53838d2acd6ec6168fd284a990c76695e84c65deee79c9f3a4276f6b4f/google_cloud_core-2.4.3.tar.gz", hash = "sha256:1fab62d7102844b278fe6dead3af32408b1df3eb06f5c7e8634cbd40edc4da53", size = 35861 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/40/86/bda7241a8da2d28a754aad2ba0f6776e35b67e37c36ae0c45d49370f1014/google_cloud_core-2.4.3-py2.py3-none-any.whl", hash = "sha256:5130f9f4c14b4fafdff75c79448f9495cfade0d8775facf1b09c3bf67e027f6e", size = 29348 }, +] + +[[package]] +name = "google-cloud-dataproc" +version = "5.18.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core", extra = ["grpc"] }, + { name = "google-auth" }, + { name = "grpc-google-iam-v1" }, + { name = "proto-plus" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fd/af/ba83512f21d13e6f16a1515cbd8d68cf8e36f6b7047abeaf9b286c28ce0d/google_cloud_dataproc-5.18.1.tar.gz", hash = "sha256:bc81afdf6ef96e79e5f9991a652d2dd7e210440c4d804becdcd5b8963c703a5b", size = 562692 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/3f/27a4a172cae62b4246cd0a855f8ec86f1bd10af0b6ad825dbf83a6eb19e3/google_cloud_dataproc-5.18.1-py3-none-any.whl", hash = "sha256:679b0c72882178a67ddd819e939540af14d6583c6e114315042943da21a11d60", size = 480545 }, +] + +[[package]] +name = "google-cloud-storage" +version = "2.19.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-api-core" }, + { name = "google-auth" }, + { name = "google-cloud-core" }, + { name = "google-crc32c" }, + { name = "google-resumable-media" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/36/76/4d965702e96bb67976e755bed9828fa50306dca003dbee08b67f41dd265e/google_cloud_storage-2.19.0.tar.gz", hash = "sha256:cd05e9e7191ba6cb68934d8eb76054d9be4562aa89dbc4236feee4d7d51342b2", size = 5535488 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d5/94/6db383d8ee1adf45dc6c73477152b82731fa4c4a46d9c1932cc8757e0fd4/google_cloud_storage-2.19.0-py2.py3-none-any.whl", hash = "sha256:aeb971b5c29cf8ab98445082cbfe7b161a1f48ed275822f59ed3f1524ea54fba", size = 131787 }, +] + +[[package]] +name = "google-crc32c" +version = "1.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/19/ae/87802e6d9f9d69adfaedfcfd599266bf386a54d0be058b532d04c794f76d/google_crc32c-1.7.1.tar.gz", hash = "sha256:2bff2305f98846f3e825dbeec9ee406f89da7962accdb29356e4eadc251bd472", size = 14495 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dd/b7/787e2453cf8639c94b3d06c9d61f512234a82e1d12d13d18584bd3049904/google_crc32c-1.7.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:2d73a68a653c57281401871dd4aeebbb6af3191dcac751a76ce430df4d403194", size = 30470 }, + { url = "https://files.pythonhosted.org/packages/ed/b4/6042c2b0cbac3ec3a69bb4c49b28d2f517b7a0f4a0232603c42c58e22b44/google_crc32c-1.7.1-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:22beacf83baaf59f9d3ab2bbb4db0fb018da8e5aebdce07ef9f09fce8220285e", size = 30315 }, + { url = "https://files.pythonhosted.org/packages/29/ad/01e7a61a5d059bc57b702d9ff6a18b2585ad97f720bd0a0dbe215df1ab0e/google_crc32c-1.7.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19eafa0e4af11b0a4eb3974483d55d2d77ad1911e6cf6f832e1574f6781fd337", size = 33180 }, + { url = "https://files.pythonhosted.org/packages/3b/a5/7279055cf004561894ed3a7bfdf5bf90a53f28fadd01af7cd166e88ddf16/google_crc32c-1.7.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6d86616faaea68101195c6bdc40c494e4d76f41e07a37ffdef270879c15fb65", size = 32794 }, + { url = "https://files.pythonhosted.org/packages/0f/d6/77060dbd140c624e42ae3ece3df53b9d811000729a5c821b9fd671ceaac6/google_crc32c-1.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:b7491bdc0c7564fcf48c0179d2048ab2f7c7ba36b84ccd3a3e1c3f7a72d3bba6", size = 33477 }, + { url = "https://files.pythonhosted.org/packages/8b/72/b8d785e9184ba6297a8620c8a37cf6e39b81a8ca01bb0796d7cbb28b3386/google_crc32c-1.7.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:df8b38bdaf1629d62d51be8bdd04888f37c451564c2042d36e5812da9eff3c35", size = 30467 }, + { url = "https://files.pythonhosted.org/packages/34/25/5f18076968212067c4e8ea95bf3b69669f9fc698476e5f5eb97d5b37999f/google_crc32c-1.7.1-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:e42e20a83a29aa2709a0cf271c7f8aefaa23b7ab52e53b322585297bb94d4638", size = 30309 }, + { url = "https://files.pythonhosted.org/packages/92/83/9228fe65bf70e93e419f38bdf6c5ca5083fc6d32886ee79b450ceefd1dbd/google_crc32c-1.7.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:905a385140bf492ac300026717af339790921f411c0dfd9aa5a9e69a08ed32eb", size = 33133 }, + { url = "https://files.pythonhosted.org/packages/c3/ca/1ea2fd13ff9f8955b85e7956872fdb7050c4ace8a2306a6d177edb9cf7fe/google_crc32c-1.7.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b211ddaf20f7ebeec5c333448582c224a7c90a9d98826fbab82c0ddc11348e6", size = 32773 }, + { url = "https://files.pythonhosted.org/packages/89/32/a22a281806e3ef21b72db16f948cad22ec68e4bdd384139291e00ff82fe2/google_crc32c-1.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:0f99eaa09a9a7e642a61e06742856eec8b19fc0037832e03f941fe7cf0c8e4db", size = 33475 }, + { url = "https://files.pythonhosted.org/packages/b8/c5/002975aff514e57fc084ba155697a049b3f9b52225ec3bc0f542871dd524/google_crc32c-1.7.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32d1da0d74ec5634a05f53ef7df18fc646666a25efaaca9fc7dcfd4caf1d98c3", size = 33243 }, + { url = "https://files.pythonhosted.org/packages/61/cb/c585282a03a0cea70fcaa1bf55d5d702d0f2351094d663ec3be1c6c67c52/google_crc32c-1.7.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e10554d4abc5238823112c2ad7e4560f96c7bf3820b202660373d769d9e6e4c9", size = 32870 }, +] + +[[package]] +name = "google-resumable-media" +version = "2.7.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-crc32c" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/58/5a/0efdc02665dca14e0837b62c8a1a93132c264bd02054a15abb2218afe0ae/google_resumable_media-2.7.2.tar.gz", hash = "sha256:5280aed4629f2b60b847b0d42f9857fd4935c11af266744df33d8074cae92fe0", size = 2163099 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/35/b8d3baf8c46695858cb9d8835a53baa1eeb9906ddaf2f728a5f5b640fd1e/google_resumable_media-2.7.2-py2.py3-none-any.whl", hash = "sha256:3ce7551e9fe6d99e9a126101d2536612bb73486721951e9562fee0f90c6ababa", size = 81251 }, +] + +[[package]] +name = "googleapis-common-protos" +version = "1.69.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1b/d7/ee9d56af4e6dbe958562b5020f46263c8a4628e7952070241fc0e9b182ae/googleapis_common_protos-1.69.2.tar.gz", hash = "sha256:3e1b904a27a33c821b4b749fd31d334c0c9c30e6113023d495e48979a3dc9c5f", size = 144496 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/53/d35476d547a286506f0a6a634ccf1e5d288fffd53d48f0bd5fef61d68684/googleapis_common_protos-1.69.2-py3-none-any.whl", hash = "sha256:0b30452ff9c7a27d80bfc5718954063e8ab53dd3697093d3bc99581f5fd24212", size = 293215 }, +] + +[package.optional-dependencies] +grpc = [ + { name = "grpcio" }, +] + +[[package]] +name = "grpc-google-iam-v1" +version = "0.14.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "googleapis-common-protos", extra = ["grpc"] }, + { name = "grpcio" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/4e/8d0ca3b035e41fe0b3f31ebbb638356af720335e5a11154c330169b40777/grpc_google_iam_v1-0.14.2.tar.gz", hash = "sha256:b3e1fc387a1a329e41672197d0ace9de22c78dd7d215048c4c78712073f7bd20", size = 16259 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/66/6f/dd9b178aee7835b96c2e63715aba6516a9d50f6bebbd1cc1d32c82a2a6c3/grpc_google_iam_v1-0.14.2-py3-none-any.whl", hash = "sha256:a3171468459770907926d56a440b2bb643eec1d7ba215f48f3ecece42b4d8351", size = 19242 }, +] + +[[package]] +name = "grpcio" +version = "1.71.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1c/95/aa11fc09a85d91fbc7dd405dcb2a1e0256989d67bf89fa65ae24b3ba105a/grpcio-1.71.0.tar.gz", hash = "sha256:2b85f7820475ad3edec209d3d89a7909ada16caab05d3f2e08a7e8ae3200a55c", size = 12549828 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/83/bd4b6a9ba07825bd19c711d8b25874cd5de72c2a3fbf635c3c344ae65bd2/grpcio-1.71.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:0ff35c8d807c1c7531d3002be03221ff9ae15712b53ab46e2a0b4bb271f38537", size = 5184101 }, + { url = "https://files.pythonhosted.org/packages/31/ea/2e0d90c0853568bf714693447f5c73272ea95ee8dad107807fde740e595d/grpcio-1.71.0-cp312-cp312-macosx_10_14_universal2.whl", hash = "sha256:b78a99cd1ece4be92ab7c07765a0b038194ded2e0a26fd654591ee136088d8d7", size = 11310927 }, + { url = "https://files.pythonhosted.org/packages/ac/bc/07a3fd8af80467390af491d7dc66882db43884128cdb3cc8524915e0023c/grpcio-1.71.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:dc1a1231ed23caac1de9f943d031f1bc38d0f69d2a3b243ea0d664fc1fbd7fec", size = 5654280 }, + { url = "https://files.pythonhosted.org/packages/16/af/21f22ea3eed3d0538b6ef7889fce1878a8ba4164497f9e07385733391e2b/grpcio-1.71.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6beeea5566092c5e3c4896c6d1d307fb46b1d4bdf3e70c8340b190a69198594", size = 6312051 }, + { url = "https://files.pythonhosted.org/packages/49/9d/e12ddc726dc8bd1aa6cba67c85ce42a12ba5b9dd75d5042214a59ccf28ce/grpcio-1.71.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5170929109450a2c031cfe87d6716f2fae39695ad5335d9106ae88cc32dc84c", size = 5910666 }, + { url = "https://files.pythonhosted.org/packages/d9/e9/38713d6d67aedef738b815763c25f092e0454dc58e77b1d2a51c9d5b3325/grpcio-1.71.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:5b08d03ace7aca7b2fadd4baf291139b4a5f058805a8327bfe9aece7253b6d67", size = 6012019 }, + { url = "https://files.pythonhosted.org/packages/80/da/4813cd7adbae6467724fa46c952d7aeac5e82e550b1c62ed2aeb78d444ae/grpcio-1.71.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:f903017db76bf9cc2b2d8bdd37bf04b505bbccad6be8a81e1542206875d0e9db", size = 6637043 }, + { url = "https://files.pythonhosted.org/packages/52/ca/c0d767082e39dccb7985c73ab4cf1d23ce8613387149e9978c70c3bf3b07/grpcio-1.71.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:469f42a0b410883185eab4689060a20488a1a0a00f8bbb3cbc1061197b4c5a79", size = 6186143 }, + { url = "https://files.pythonhosted.org/packages/00/61/7b2c8ec13303f8fe36832c13d91ad4d4ba57204b1c723ada709c346b2271/grpcio-1.71.0-cp312-cp312-win32.whl", hash = "sha256:ad9f30838550695b5eb302add33f21f7301b882937460dd24f24b3cc5a95067a", size = 3604083 }, + { url = "https://files.pythonhosted.org/packages/fd/7c/1e429c5fb26122055d10ff9a1d754790fb067d83c633ff69eddcf8e3614b/grpcio-1.71.0-cp312-cp312-win_amd64.whl", hash = "sha256:652350609332de6dac4ece254e5d7e1ff834e203d6afb769601f286886f6f3a8", size = 4272191 }, + { url = "https://files.pythonhosted.org/packages/04/dd/b00cbb45400d06b26126dcfdbdb34bb6c4f28c3ebbd7aea8228679103ef6/grpcio-1.71.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:cebc1b34ba40a312ab480ccdb396ff3c529377a2fce72c45a741f7215bfe8379", size = 5184138 }, + { url = "https://files.pythonhosted.org/packages/ed/0a/4651215983d590ef53aac40ba0e29dda941a02b097892c44fa3357e706e5/grpcio-1.71.0-cp313-cp313-macosx_10_14_universal2.whl", hash = "sha256:85da336e3649a3d2171e82f696b5cad2c6231fdd5bad52616476235681bee5b3", size = 11310747 }, + { url = "https://files.pythonhosted.org/packages/57/a3/149615b247f321e13f60aa512d3509d4215173bdb982c9098d78484de216/grpcio-1.71.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:f9a412f55bb6e8f3bb000e020dbc1e709627dcb3a56f6431fa7076b4c1aab0db", size = 5653991 }, + { url = "https://files.pythonhosted.org/packages/ca/56/29432a3e8d951b5e4e520a40cd93bebaa824a14033ea8e65b0ece1da6167/grpcio-1.71.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47be9584729534660416f6d2a3108aaeac1122f6b5bdbf9fd823e11fe6fbaa29", size = 6312781 }, + { url = "https://files.pythonhosted.org/packages/a3/f8/286e81a62964ceb6ac10b10925261d4871a762d2a763fbf354115f9afc98/grpcio-1.71.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c9c80ac6091c916db81131d50926a93ab162a7e97e4428ffc186b6e80d6dda4", size = 5910479 }, + { url = "https://files.pythonhosted.org/packages/35/67/d1febb49ec0f599b9e6d4d0d44c2d4afdbed9c3e80deb7587ec788fcf252/grpcio-1.71.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:789d5e2a3a15419374b7b45cd680b1e83bbc1e52b9086e49308e2c0b5bbae6e3", size = 6013262 }, + { url = "https://files.pythonhosted.org/packages/a1/04/f9ceda11755f0104a075ad7163fc0d96e2e3a9fe25ef38adfc74c5790daf/grpcio-1.71.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:1be857615e26a86d7363e8a163fade914595c81fec962b3d514a4b1e8760467b", size = 6643356 }, + { url = "https://files.pythonhosted.org/packages/fb/ce/236dbc3dc77cf9a9242adcf1f62538734ad64727fabf39e1346ad4bd5c75/grpcio-1.71.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:a76d39b5fafd79ed604c4be0a869ec3581a172a707e2a8d7a4858cb05a5a7637", size = 6186564 }, + { url = "https://files.pythonhosted.org/packages/10/fd/b3348fce9dd4280e221f513dd54024e765b21c348bc475516672da4218e9/grpcio-1.71.0-cp313-cp313-win32.whl", hash = "sha256:74258dce215cb1995083daa17b379a1a5a87d275387b7ffe137f1d5131e2cfbb", size = 3601890 }, + { url = "https://files.pythonhosted.org/packages/be/f8/db5d5f3fc7e296166286c2a397836b8b042f7ad1e11028d82b061701f0f7/grpcio-1.71.0-cp313-cp313-win_amd64.whl", hash = "sha256:22c3bc8d488c039a199f7a003a38cb7635db6656fa96437a8accde8322ce2366", size = 4273308 }, +] + +[[package]] +name = "grpcio-status" +version = "1.71.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "googleapis-common-protos" }, + { name = "grpcio" }, + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d7/53/a911467bece076020456401f55a27415d2d70d3bc2c37af06b44ea41fc5c/grpcio_status-1.71.0.tar.gz", hash = "sha256:11405fed67b68f406b3f3c7c5ae5104a79d2d309666d10d61b152e91d28fb968", size = 13669 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/d6/31fbc43ff097d8c4c9fc3df741431b8018f67bf8dfbe6553a555f6e5f675/grpcio_status-1.71.0-py3-none-any.whl", hash = "sha256:843934ef8c09e3e858952887467f8256aac3910c55f077a359a65b2b3cde3e68", size = 14424 }, +] + +[[package]] +name = "idna" +version = "3.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, +] + +[[package]] +name = "importlib-metadata" +version = "6.11.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "zipp" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ee/eb/58c2ab27ee628ad801f56d4017fe62afab0293116f6d0b08f1d5bd46e06f/importlib_metadata-6.11.0.tar.gz", hash = "sha256:1231cf92d825c9e03cfc4da076a16de6422c863558229ea0b22b675657463443", size = 54593 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/59/9b/ecce94952ab5ea74c31dcf9ccf78ccd484eebebef06019bf8cb579ab4519/importlib_metadata-6.11.0-py3-none-any.whl", hash = "sha256:f0afba6205ad8f8947c7d338b5342d5db2afbfd82f9cbef7879a9539cc12eb9b", size = 23427 }, +] + +[[package]] +name = "isodate" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/db/7a/c0a56c7d56c7fa723988f122fa1f1ccf8c5c4ccc48efad0d214b49e5b1af/isodate-0.6.1.tar.gz", hash = "sha256:48c5881de7e8b0a0d648cb024c8062dc84e7b840ed81e864c7614fd3c127bde9", size = 28443 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b6/85/7882d311924cbcfc70b1890780763e36ff0b140c7e51c110fc59a532f087/isodate-0.6.1-py2.py3-none-any.whl", hash = "sha256:0751eece944162659049d35f4f549ed815792b38793f07cf73381c1c87cbed96", size = 41722 }, +] + +[[package]] +name = "jaraco-classes" +version = "3.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "more-itertools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/c0/ed4a27bc5571b99e3cff68f8a9fa5b56ff7df1c2251cc715a652ddd26402/jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", size = 11780 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790", size = 6777 }, +] + +[[package]] +name = "jaraco-context" +version = "6.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/ad/f3777b81bf0b6e7bc7514a1656d3e637b2e8e15fab2ce3235730b3e7a4e6/jaraco_context-6.0.1.tar.gz", hash = "sha256:9bae4ea555cf0b14938dc0aee7c9f32ed303aa20a3b73e7dc80111628792d1b3", size = 13912 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ff/db/0c52c4cf5e4bd9f5d7135ec7669a3a767af21b3a308e1ed3674881e52b62/jaraco.context-6.0.1-py3-none-any.whl", hash = "sha256:f797fc481b490edb305122c9181830a3a5b76d84ef6d1aef2fb9b47ab956f9e4", size = 6825 }, +] + +[[package]] +name = "jaraco-functools" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "more-itertools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ab/23/9894b3df5d0a6eb44611c36aec777823fc2e07740dabbd0b810e19594013/jaraco_functools-4.1.0.tar.gz", hash = "sha256:70f7e0e2ae076498e212562325e805204fc092d7b4c17e0e86c959e249701a9d", size = 19159 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/4f/24b319316142c44283d7540e76c7b5a6dbd5db623abd86bb7b3491c21018/jaraco.functools-4.1.0-py3-none-any.whl", hash = "sha256:ad159f13428bc4acbf5541ad6dec511f91573b90fba04df61dafa2a1231cf649", size = 10187 }, +] + +[[package]] +name = "jeepney" +version = "0.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/6f/357efd7602486741aa73ffc0617fb310a29b588ed0fd69c2399acbb85b0c/jeepney-0.9.0.tar.gz", hash = "sha256:cf0e9e845622b81e4a28df94c40345400256ec608d0e55bb8a3feaa9163f5732", size = 106758 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl", hash = "sha256:97e5714520c16fc0a45695e5365a2e11b81ea79bba796e26f9f1d178cb182683", size = 49010 }, +] + +[[package]] +name = "jinja2" +version = "3.1.6" +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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899 }, +] + +[[package]] +name = "jsonschema" +version = "4.23.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/2e/03362ee4034a4c917f697890ccd4aec0800ccf9ded7f511971c75451deec/jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4", size = 325778 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/4a/4f9dbeb84e8850557c02365a0eee0649abe5eb1d84af92a25731c6c0f922/jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566", size = 88462 }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2024.10.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/10/db/58f950c996c793472e336ff3655b13fbcf1e3b359dcf52dcf3ed3b52c352/jsonschema_specifications-2024.10.1.tar.gz", hash = "sha256:0f38b83639958ce1152d02a7f062902c41c8fd20d558b0c34344292d417ae272", size = 15561 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/0f/8910b19ac0670a0f80ce1008e5e751c4a57e14d2c4c13a482aa6079fa9d6/jsonschema_specifications-2024.10.1-py3-none-any.whl", hash = "sha256:a09a0680616357d9a0ecf05c12ad234479f549239d0f5b55f3deea67475da9bf", size = 18459 }, +] + +[[package]] +name = "keyring" +version = "25.6.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jaraco-classes" }, + { name = "jaraco-context" }, + { name = "jaraco-functools" }, + { name = "jeepney", marker = "sys_platform == 'linux'" }, + { name = "pywin32-ctypes", marker = "sys_platform == 'win32'" }, + { name = "secretstorage", marker = "sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/70/09/d904a6e96f76ff214be59e7aa6ef7190008f52a0ab6689760a98de0bf37d/keyring-25.6.0.tar.gz", hash = "sha256:0b39998aa941431eb3d9b0d4b2460bc773b9df6fed7621c2dfb291a7e0187a66", size = 62750 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/32/da7f44bcb1105d3e88a0b74ebdca50c59121d2ddf71c9e34ba47df7f3a56/keyring-25.6.0-py3-none-any.whl", hash = "sha256:552a3f7af126ece7ed5c89753650eec89c7eaae8617d0aa4d9ad2b75111266bd", size = 39085 }, +] + +[[package]] +name = "leather" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ed/6e/48a05e2f7f62a616d675cfee182643f2dd8023bf7429aa326f4bebd629c8/leather-0.4.0.tar.gz", hash = "sha256:f964bec2086f3153a6c16e707f20cb718f811f57af116075f4c0f4805c608b95", size = 43877 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a1/30/9ec597c962c5249ebd5c580386e4b5f2884cd943af42634291ee3b406415/leather-0.4.0-py2.py3-none-any.whl", hash = "sha256:18290bc93749ae39039af5e31e871fcfad74d26c4c3ea28ea4f681f4571b3a2b", size = 30256 }, +] + +[[package]] +name = "logbook" +version = "1.5.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/2f/d9/16ac346f7c0102835814cc9e5b684aaadea101560bb932a2403bd26b2320/Logbook-1.5.3.tar.gz", hash = "sha256:66f454ada0f56eae43066f604a222b09893f98c1adc18df169710761b8f32fe8", size = 85783 } + +[[package]] +name = "lz4" +version = "4.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c6/5a/945f5086326d569f14c84ac6f7fcc3229f0b9b1e8cc536b951fd53dfb9e1/lz4-4.4.4.tar.gz", hash = "sha256:070fd0627ec4393011251a094e08ed9fdcc78cb4e7ab28f507638eee4e39abda", size = 171884 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/2d/5523b4fabe11cd98f040f715728d1932eb7e696bfe94391872a823332b94/lz4-4.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:23ae267494fdd80f0d2a131beff890cf857f1b812ee72dbb96c3204aab725553", size = 220669 }, + { url = "https://files.pythonhosted.org/packages/91/06/1a5bbcacbfb48d8ee5b6eb3fca6aa84143a81d92946bdb5cd6b005f1863e/lz4-4.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fff9f3a1ed63d45cb6514bfb8293005dc4141341ce3500abdfeb76124c0b9b2e", size = 189661 }, + { url = "https://files.pythonhosted.org/packages/fa/08/39eb7ac907f73e11a69a11576a75a9e36406b3241c0ba41453a7eb842abb/lz4-4.4.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ea7f07329f85a8eda4d8cf937b87f27f0ac392c6400f18bea2c667c8b7f8ecc", size = 1238775 }, + { url = "https://files.pythonhosted.org/packages/e9/26/05840fbd4233e8d23e88411a066ab19f1e9de332edddb8df2b6a95c7fddc/lz4-4.4.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ccab8f7f7b82f9fa9fc3b0ba584d353bd5aa818d5821d77d5b9447faad2aaad", size = 1265143 }, + { url = "https://files.pythonhosted.org/packages/b7/5d/5f2db18c298a419932f3ab2023deb689863cf8fd7ed875b1c43492479af2/lz4-4.4.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e43e9d48b2daf80e486213128b0763deed35bbb7a59b66d1681e205e1702d735", size = 1185032 }, + { url = "https://files.pythonhosted.org/packages/c4/e6/736ab5f128694b0f6aac58343bcf37163437ac95997276cd0be3ea4c3342/lz4-4.4.4-cp312-cp312-win32.whl", hash = "sha256:33e01e18e4561b0381b2c33d58e77ceee850a5067f0ece945064cbaac2176962", size = 88284 }, + { url = "https://files.pythonhosted.org/packages/40/b8/243430cb62319175070e06e3a94c4c7bd186a812e474e22148ae1290d47d/lz4-4.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:d21d1a2892a2dcc193163dd13eaadabb2c1b803807a5117d8f8588b22eaf9f12", size = 99918 }, + { url = "https://files.pythonhosted.org/packages/6c/e1/0686c91738f3e6c2e1a243e0fdd4371667c4d2e5009b0a3605806c2aa020/lz4-4.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:2f4f2965c98ab254feddf6b5072854a6935adab7bc81412ec4fe238f07b85f62", size = 89736 }, + { url = "https://files.pythonhosted.org/packages/3b/3c/d1d1b926d3688263893461e7c47ed7382a969a0976fc121fc678ec325fc6/lz4-4.4.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ed6eb9f8deaf25ee4f6fad9625d0955183fdc90c52b6f79a76b7f209af1b6e54", size = 220678 }, + { url = "https://files.pythonhosted.org/packages/26/89/8783d98deb058800dabe07e6cdc90f5a2a8502a9bad8c5343c641120ace2/lz4-4.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:18ae4fe3bafb344dbd09f976d45cbf49c05c34416f2462828f9572c1fa6d5af7", size = 189670 }, + { url = "https://files.pythonhosted.org/packages/22/ab/a491ace69a83a8914a49f7391e92ca0698f11b28d5ce7b2ececa2be28e9a/lz4-4.4.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57fd20c5fc1a49d1bbd170836fccf9a338847e73664f8e313dce6ac91b8c1e02", size = 1238746 }, + { url = "https://files.pythonhosted.org/packages/97/12/a1f2f4fdc6b7159c0d12249456f9fe454665b6126e98dbee9f2bd3cf735c/lz4-4.4.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9cb387c33f014dae4db8cb4ba789c8d2a0a6d045ddff6be13f6c8d9def1d2a6", size = 1265119 }, + { url = "https://files.pythonhosted.org/packages/50/6e/e22e50f5207649db6ea83cd31b79049118305be67e96bec60becf317afc6/lz4-4.4.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0be9f68240231e1e44118a4ebfecd8a5d4184f0bdf5c591c98dd6ade9720afd", size = 1184954 }, + { url = "https://files.pythonhosted.org/packages/4c/c4/2a458039645fcc6324ece731d4d1361c5daf960b553d1fcb4261ba07d51c/lz4-4.4.4-cp313-cp313-win32.whl", hash = "sha256:e9ec5d45ea43684f87c316542af061ef5febc6a6b322928f059ce1fb289c298a", size = 88289 }, + { url = "https://files.pythonhosted.org/packages/00/96/b8e24ea7537ab418074c226279acfcaa470e1ea8271003e24909b6db942b/lz4-4.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:a760a175b46325b2bb33b1f2bbfb8aa21b48e1b9653e29c10b6834f9bb44ead4", size = 99925 }, + { url = "https://files.pythonhosted.org/packages/a5/a5/f9838fe6aa132cfd22733ed2729d0592259fff074cefb80f19aa0607367b/lz4-4.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:f4c21648d81e0dda38b4720dccc9006ae33b0e9e7ffe88af6bf7d4ec124e2fba", size = 89743 }, +] + +[[package]] +name = "markupsafe" +version = "3.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b2/97/5d42485e71dfc078108a86d6de8fa46db44a1a9295e89c5d6d4a06e23a62/markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0", size = 20537 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/09/d1f21434c97fc42f09d290cbb6350d44eb12f09cc62c9476effdb33a18aa/MarkupSafe-3.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:9778bd8ab0a994ebf6f84c2b949e65736d5575320a17ae8984a77fab08db94cf", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/6b/b0/18f76bba336fa5aecf79d45dcd6c806c280ec44538b3c13671d49099fdd0/MarkupSafe-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846ade7b71e3536c4e56b386c2a47adf5741d2d8b94ec9dc3e92e5e1ee1e2225", size = 12348 }, + { url = "https://files.pythonhosted.org/packages/e0/25/dd5c0f6ac1311e9b40f4af06c78efde0f3b5cbf02502f8ef9501294c425b/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c99d261bd2d5f6b59325c92c73df481e05e57f19837bdca8413b9eac4bd8028", size = 24149 }, + { url = "https://files.pythonhosted.org/packages/f3/f0/89e7aadfb3749d0f52234a0c8c7867877876e0a20b60e2188e9850794c17/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e17c96c14e19278594aa4841ec148115f9c7615a47382ecb6b82bd8fea3ab0c8", size = 23118 }, + { url = "https://files.pythonhosted.org/packages/d5/da/f2eeb64c723f5e3777bc081da884b414671982008c47dcc1873d81f625b6/MarkupSafe-3.0.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88416bd1e65dcea10bc7569faacb2c20ce071dd1f87539ca2ab364bf6231393c", size = 22993 }, + { url = "https://files.pythonhosted.org/packages/da/0e/1f32af846df486dce7c227fe0f2398dc7e2e51d4a370508281f3c1c5cddc/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2181e67807fc2fa785d0592dc2d6206c019b9502410671cc905d132a92866557", size = 24178 }, + { url = "https://files.pythonhosted.org/packages/c4/f6/bb3ca0532de8086cbff5f06d137064c8410d10779c4c127e0e47d17c0b71/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:52305740fe773d09cffb16f8ed0427942901f00adedac82ec8b67752f58a1b22", size = 23319 }, + { url = "https://files.pythonhosted.org/packages/a2/82/8be4c96ffee03c5b4a034e60a31294daf481e12c7c43ab8e34a1453ee48b/MarkupSafe-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ad10d3ded218f1039f11a75f8091880239651b52e9bb592ca27de44eed242a48", size = 23352 }, + { url = "https://files.pythonhosted.org/packages/51/ae/97827349d3fcffee7e184bdf7f41cd6b88d9919c80f0263ba7acd1bbcb18/MarkupSafe-3.0.2-cp312-cp312-win32.whl", hash = "sha256:0f4ca02bea9a23221c0182836703cbf8930c5e9454bacce27e767509fa286a30", size = 15097 }, + { url = "https://files.pythonhosted.org/packages/c1/80/a61f99dc3a936413c3ee4e1eecac96c0da5ed07ad56fd975f1a9da5bc630/MarkupSafe-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:8e06879fc22a25ca47312fbe7c8264eb0b662f6db27cb2d3bbbc74b1df4b9b87", size = 15601 }, + { url = "https://files.pythonhosted.org/packages/83/0e/67eb10a7ecc77a0c2bbe2b0235765b98d164d81600746914bebada795e97/MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ba9527cdd4c926ed0760bc301f6728ef34d841f405abf9d4f959c478421e4efd", size = 14274 }, + { url = "https://files.pythonhosted.org/packages/2b/6d/9409f3684d3335375d04e5f05744dfe7e9f120062c9857df4ab490a1031a/MarkupSafe-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f8b3d067f2e40fe93e1ccdd6b2e1d16c43140e76f02fb1319a05cf2b79d99430", size = 12352 }, + { url = "https://files.pythonhosted.org/packages/d2/f5/6eadfcd3885ea85fe2a7c128315cc1bb7241e1987443d78c8fe712d03091/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:569511d3b58c8791ab4c2e1285575265991e6d8f8700c7be0e88f86cb0672094", size = 24122 }, + { url = "https://files.pythonhosted.org/packages/0c/91/96cf928db8236f1bfab6ce15ad070dfdd02ed88261c2afafd4b43575e9e9/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15ab75ef81add55874e7ab7055e9c397312385bd9ced94920f2802310c930396", size = 23085 }, + { url = "https://files.pythonhosted.org/packages/c2/cf/c9d56af24d56ea04daae7ac0940232d31d5a8354f2b457c6d856b2057d69/MarkupSafe-3.0.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f3818cb119498c0678015754eba762e0d61e5b52d34c8b13d770f0719f7b1d79", size = 22978 }, + { url = "https://files.pythonhosted.org/packages/2a/9f/8619835cd6a711d6272d62abb78c033bda638fdc54c4e7f4272cf1c0962b/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:cdb82a876c47801bb54a690c5ae105a46b392ac6099881cdfb9f6e95e4014c6a", size = 24208 }, + { url = "https://files.pythonhosted.org/packages/f9/bf/176950a1792b2cd2102b8ffeb5133e1ed984547b75db47c25a67d3359f77/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:cabc348d87e913db6ab4aa100f01b08f481097838bdddf7c7a84b7575b7309ca", size = 23357 }, + { url = "https://files.pythonhosted.org/packages/ce/4f/9a02c1d335caabe5c4efb90e1b6e8ee944aa245c1aaaab8e8a618987d816/MarkupSafe-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:444dcda765c8a838eaae23112db52f1efaf750daddb2d9ca300bcae1039adc5c", size = 23344 }, + { url = "https://files.pythonhosted.org/packages/ee/55/c271b57db36f748f0e04a759ace9f8f759ccf22b4960c270c78a394f58be/MarkupSafe-3.0.2-cp313-cp313-win32.whl", hash = "sha256:bcf3e58998965654fdaff38e58584d8937aa3096ab5354d493c77d1fdd66d7a1", size = 15101 }, + { url = "https://files.pythonhosted.org/packages/29/88/07df22d2dd4df40aba9f3e402e6dc1b8ee86297dddbad4872bd5e7b0094f/MarkupSafe-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:e6a2a455bd412959b57a172ce6328d2dd1f01cb2135efda2e4576e8a23fa3b0f", size = 15603 }, + { url = "https://files.pythonhosted.org/packages/62/6a/8b89d24db2d32d433dffcd6a8779159da109842434f1dd2f6e71f32f738c/MarkupSafe-3.0.2-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:b5a6b3ada725cea8a5e634536b1b01c30bcdcd7f9c6fff4151548d5bf6b3a36c", size = 14510 }, + { url = "https://files.pythonhosted.org/packages/7a/06/a10f955f70a2e5a9bf78d11a161029d278eeacbd35ef806c3fd17b13060d/MarkupSafe-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a904af0a6162c73e3edcb969eeeb53a63ceeb5d8cf642fade7d39e7963a22ddb", size = 12486 }, + { url = "https://files.pythonhosted.org/packages/34/cf/65d4a571869a1a9078198ca28f39fba5fbb910f952f9dbc5220afff9f5e6/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4aa4e5faecf353ed117801a068ebab7b7e09ffb6e1d5e412dc852e0da018126c", size = 25480 }, + { url = "https://files.pythonhosted.org/packages/0c/e3/90e9651924c430b885468b56b3d597cabf6d72be4b24a0acd1fa0e12af67/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0ef13eaeee5b615fb07c9a7dadb38eac06a0608b41570d8ade51c56539e509d", size = 23914 }, + { url = "https://files.pythonhosted.org/packages/66/8c/6c7cf61f95d63bb866db39085150df1f2a5bd3335298f14a66b48e92659c/MarkupSafe-3.0.2-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d16a81a06776313e817c951135cf7340a3e91e8c1ff2fac444cfd75fffa04afe", size = 23796 }, + { url = "https://files.pythonhosted.org/packages/bb/35/cbe9238ec3f47ac9a7c8b3df7a808e7cb50fe149dc7039f5f454b3fba218/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:6381026f158fdb7c72a168278597a5e3a5222e83ea18f543112b2662a9b699c5", size = 25473 }, + { url = "https://files.pythonhosted.org/packages/e6/32/7621a4382488aa283cc05e8984a9c219abad3bca087be9ec77e89939ded9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:3d79d162e7be8f996986c064d1c7c817f6df3a77fe3d6859f6f9e7be4b8c213a", size = 24114 }, + { url = "https://files.pythonhosted.org/packages/0d/80/0985960e4b89922cb5a0bac0ed39c5b96cbc1a536a99f30e8c220a996ed9/MarkupSafe-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:131a3c7689c85f5ad20f9f6fb1b866f402c445b220c19fe4308c0b147ccd2ad9", size = 24098 }, + { url = "https://files.pythonhosted.org/packages/82/78/fedb03c7d5380df2427038ec8d973587e90561b2d90cd472ce9254cf348b/MarkupSafe-3.0.2-cp313-cp313t-win32.whl", hash = "sha256:ba8062ed2cf21c07a9e295d5b8a2a5ce678b913b45fdf68c32d95d6c1291e0b6", size = 15208 }, + { url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 }, +] + +[[package]] +name = "mashumaro" +version = "3.14" +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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/35/8d63733a2c12149d0c7663c29bf626bdbeea5f0ff963afe58a42b4810981/mashumaro-3.14-py3-none-any.whl", hash = "sha256:c12a649599a8f7b1a0b35d18f12e678423c3066189f7bc7bd8dd431c5c8132c3", size = 92183 }, +] + +[package.optional-dependencies] +msgpack = [ + { name = "msgpack" }, +] + +[[package]] +name = "minimal-snowplow-tracker" +version = "0.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, + { name = "six" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e4/9f/004f810169a48ed5c520279d98327e7793b6491f09d42cb2c5636c994f34/minimal-snowplow-tracker-0.0.2.tar.gz", hash = "sha256:acabf7572db0e7f5cbf6983d495eef54081f71be392330eb3aadb9ccb39daaa4", size = 12542 } + +[[package]] +name = "more-itertools" +version = "10.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/88/3b/7fa1fe835e2e93fd6d7b52b2f95ae810cf5ba133e1845f726f5a992d62c2/more-itertools-10.6.0.tar.gz", hash = "sha256:2cd7fad1009c31cc9fb6a035108509e6547547a7a738374f10bd49a09eb3ee3b", size = 125009 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/23/62/0fe302c6d1be1c777cab0616e6302478251dfbf9055ad426f5d0def75c89/more_itertools-10.6.0-py3-none-any.whl", hash = "sha256:6eb054cb4b6db1473f6e15fcc676a08e4732548acd47c708f0e179c2c7c01e89", size = 63038 }, +] + +[[package]] +name = "msal" +version = "1.32.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "pyjwt", extra = ["crypto"] }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3f/90/81dcc50f0be11a8c4dcbae1a9f761a26e5f905231330a7cacc9f04ec4c61/msal-1.32.3.tar.gz", hash = "sha256:5eea038689c78a5a70ca8ecbe1245458b55a857bd096efb6989c69ba15985d35", size = 151449 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/bf/81516b9aac7fd867709984d08eb4db1d2e3fe1df795c8e442cde9b568962/msal-1.32.3-py3-none-any.whl", hash = "sha256:b2798db57760b1961b142f027ffb7c8169536bf77316e99a0df5c4aaebb11569", size = 115358 }, +] + +[[package]] +name = "msal-extensions" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "msal" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/01/99/5d239b6156eddf761a636bded1118414d161bd6b7b37a9335549ed159396/msal_extensions-1.3.1.tar.gz", hash = "sha256:c5b0fd10f65ef62b5f1d62f4251d51cbcaf003fcedae8c91b040a488614be1a4", size = 23315 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5e/75/bd9b7bb966668920f06b200e84454c8f3566b102183bc55c5473d96cb2b9/msal_extensions-1.3.1-py3-none-any.whl", hash = "sha256:96d3de4d034504e969ac5e85bae8106c8373b5c6568e4c8fa7af2eca9dbe6bca", size = 20583 }, +] + +[[package]] +name = "msgpack" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cb/d0/7555686ae7ff5731205df1012ede15dd9d927f6227ea151e901c7406af4f/msgpack-1.1.0.tar.gz", hash = "sha256:dd432ccc2c72b914e4cb77afce64aab761c1137cc698be3984eee260bcb2896e", size = 167260 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e1/d6/716b7ca1dbde63290d2973d22bbef1b5032ca634c3ff4384a958ec3f093a/msgpack-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d46cf9e3705ea9485687aa4001a76e44748b609d260af21c4ceea7f2212a501d", size = 152421 }, + { url = "https://files.pythonhosted.org/packages/70/da/5312b067f6773429cec2f8f08b021c06af416bba340c912c2ec778539ed6/msgpack-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5dbad74103df937e1325cc4bfeaf57713be0b4f15e1c2da43ccdd836393e2ea2", size = 85277 }, + { url = "https://files.pythonhosted.org/packages/28/51/da7f3ae4462e8bb98af0d5bdf2707f1b8c65a0d4f496e46b6afb06cbc286/msgpack-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58dfc47f8b102da61e8949708b3eafc3504509a5728f8b4ddef84bd9e16ad420", size = 82222 }, + { url = "https://files.pythonhosted.org/packages/33/af/dc95c4b2a49cff17ce47611ca9ba218198806cad7796c0b01d1e332c86bb/msgpack-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4676e5be1b472909b2ee6356ff425ebedf5142427842aa06b4dfd5117d1ca8a2", size = 392971 }, + { url = "https://files.pythonhosted.org/packages/f1/54/65af8de681fa8255402c80eda2a501ba467921d5a7a028c9c22a2c2eedb5/msgpack-1.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17fb65dd0bec285907f68b15734a993ad3fc94332b5bb21b0435846228de1f39", size = 401403 }, + { url = "https://files.pythonhosted.org/packages/97/8c/e333690777bd33919ab7024269dc3c41c76ef5137b211d776fbb404bfead/msgpack-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a51abd48c6d8ac89e0cfd4fe177c61481aca2d5e7ba42044fd218cfd8ea9899f", size = 385356 }, + { url = "https://files.pythonhosted.org/packages/57/52/406795ba478dc1c890559dd4e89280fa86506608a28ccf3a72fbf45df9f5/msgpack-1.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2137773500afa5494a61b1208619e3871f75f27b03bcfca7b3a7023284140247", size = 383028 }, + { url = "https://files.pythonhosted.org/packages/e7/69/053b6549bf90a3acadcd8232eae03e2fefc87f066a5b9fbb37e2e608859f/msgpack-1.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:398b713459fea610861c8a7b62a6fec1882759f308ae0795b5413ff6a160cf3c", size = 391100 }, + { url = "https://files.pythonhosted.org/packages/23/f0/d4101d4da054f04274995ddc4086c2715d9b93111eb9ed49686c0f7ccc8a/msgpack-1.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:06f5fd2f6bb2a7914922d935d3b8bb4a7fff3a9a91cfce6d06c13bc42bec975b", size = 394254 }, + { url = "https://files.pythonhosted.org/packages/1c/12/cf07458f35d0d775ff3a2dc5559fa2e1fcd06c46f1ef510e594ebefdca01/msgpack-1.1.0-cp312-cp312-win32.whl", hash = "sha256:ad33e8400e4ec17ba782f7b9cf868977d867ed784a1f5f2ab46e7ba53b6e1e1b", size = 69085 }, + { url = "https://files.pythonhosted.org/packages/73/80/2708a4641f7d553a63bc934a3eb7214806b5b39d200133ca7f7afb0a53e8/msgpack-1.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:115a7af8ee9e8cddc10f87636767857e7e3717b7a2e97379dc2054712693e90f", size = 75347 }, + { url = "https://files.pythonhosted.org/packages/c8/b0/380f5f639543a4ac413e969109978feb1f3c66e931068f91ab6ab0f8be00/msgpack-1.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:071603e2f0771c45ad9bc65719291c568d4edf120b44eb36324dcb02a13bfddf", size = 151142 }, + { url = "https://files.pythonhosted.org/packages/c8/ee/be57e9702400a6cb2606883d55b05784fada898dfc7fd12608ab1fdb054e/msgpack-1.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0f92a83b84e7c0749e3f12821949d79485971f087604178026085f60ce109330", size = 84523 }, + { url = "https://files.pythonhosted.org/packages/7e/3a/2919f63acca3c119565449681ad08a2f84b2171ddfcff1dba6959db2cceb/msgpack-1.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1964df7b81285d00a84da4e70cb1383f2e665e0f1f2a7027e683956d04b734", size = 81556 }, + { url = "https://files.pythonhosted.org/packages/7c/43/a11113d9e5c1498c145a8925768ea2d5fce7cbab15c99cda655aa09947ed/msgpack-1.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59caf6a4ed0d164055ccff8fe31eddc0ebc07cf7326a2aaa0dbf7a4001cd823e", size = 392105 }, + { url = "https://files.pythonhosted.org/packages/2d/7b/2c1d74ca6c94f70a1add74a8393a0138172207dc5de6fc6269483519d048/msgpack-1.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0907e1a7119b337971a689153665764adc34e89175f9a34793307d9def08e6ca", size = 399979 }, + { url = "https://files.pythonhosted.org/packages/82/8c/cf64ae518c7b8efc763ca1f1348a96f0e37150061e777a8ea5430b413a74/msgpack-1.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65553c9b6da8166e819a6aa90ad15288599b340f91d18f60b2061f402b9a4915", size = 383816 }, + { url = "https://files.pythonhosted.org/packages/69/86/a847ef7a0f5ef3fa94ae20f52a4cacf596a4e4a010197fbcc27744eb9a83/msgpack-1.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:7a946a8992941fea80ed4beae6bff74ffd7ee129a90b4dd5cf9c476a30e9708d", size = 380973 }, + { url = "https://files.pythonhosted.org/packages/aa/90/c74cf6e1126faa93185d3b830ee97246ecc4fe12cf9d2d31318ee4246994/msgpack-1.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4b51405e36e075193bc051315dbf29168d6141ae2500ba8cd80a522964e31434", size = 387435 }, + { url = "https://files.pythonhosted.org/packages/7a/40/631c238f1f338eb09f4acb0f34ab5862c4e9d7eda11c1b685471a4c5ea37/msgpack-1.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b4c01941fd2ff87c2a934ee6055bda4ed353a7846b8d4f341c428109e9fcde8c", size = 399082 }, + { url = "https://files.pythonhosted.org/packages/e9/1b/fa8a952be252a1555ed39f97c06778e3aeb9123aa4cccc0fd2acd0b4e315/msgpack-1.1.0-cp313-cp313-win32.whl", hash = "sha256:7c9a35ce2c2573bada929e0b7b3576de647b0defbd25f5139dcdaba0ae35a4cc", size = 69037 }, + { url = "https://files.pythonhosted.org/packages/b6/bc/8bd826dd03e022153bfa1766dcdec4976d6c818865ed54223d71f07862b3/msgpack-1.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:bce7d9e614a04d0883af0b3d4d501171fbfca038f12c77fa838d9f198147a23f", size = 75140 }, +] + +[[package]] +name = "networkx" +version = "3.4.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/1d/06475e1cd5264c0b870ea2cc6fdb3e37177c1e565c43f56ff17a10e3937f/networkx-3.4.2.tar.gz", hash = "sha256:307c3669428c5362aab27c8a1260aa8f47c4e91d3891f48be0141738d8d053e1", size = 2151368 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b9/54/dd730b32ea14ea797530a4479b2ed46a6fb250f682a9cfb997e968bf0261/networkx-3.4.2-py3-none-any.whl", hash = "sha256:df5d4365b724cf81b8c6a7312509d0c22386097011ad1abe274afd5e9d3bbc5f", size = 1723263 }, +] + +[[package]] +name = "numpy" +version = "2.2.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/78/31103410a57bc2c2b93a3597340a8119588571f6a4539067546cb9a0bfac/numpy-2.2.4.tar.gz", hash = "sha256:9ba03692a45d3eef66559efe1d1096c4b9b75c0986b5dff5530c378fb8331d4f", size = 20270701 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a2/30/182db21d4f2a95904cec1a6f779479ea1ac07c0647f064dea454ec650c42/numpy-2.2.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a7b9084668aa0f64e64bd00d27ba5146ef1c3a8835f3bd912e7a9e01326804c4", size = 20947156 }, + { url = "https://files.pythonhosted.org/packages/24/6d/9483566acfbda6c62c6bc74b6e981c777229d2af93c8eb2469b26ac1b7bc/numpy-2.2.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:dbe512c511956b893d2dacd007d955a3f03d555ae05cfa3ff1c1ff6df8851854", size = 14133092 }, + { url = "https://files.pythonhosted.org/packages/27/f6/dba8a258acbf9d2bed2525cdcbb9493ef9bae5199d7a9cb92ee7e9b2aea6/numpy-2.2.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:bb649f8b207ab07caebba230d851b579a3c8711a851d29efe15008e31bb4de24", size = 5163515 }, + { url = "https://files.pythonhosted.org/packages/62/30/82116199d1c249446723c68f2c9da40d7f062551036f50b8c4caa42ae252/numpy-2.2.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:f34dc300df798742b3d06515aa2a0aee20941c13579d7a2f2e10af01ae4901ee", size = 6696558 }, + { url = "https://files.pythonhosted.org/packages/0e/b2/54122b3c6df5df3e87582b2e9430f1bdb63af4023c739ba300164c9ae503/numpy-2.2.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c3f7ac96b16955634e223b579a3e5798df59007ca43e8d451a0e6a50f6bfdfba", size = 14084742 }, + { url = "https://files.pythonhosted.org/packages/02/e2/e2cbb8d634151aab9528ef7b8bab52ee4ab10e076509285602c2a3a686e0/numpy-2.2.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f92084defa704deadd4e0a5ab1dc52d8ac9e8a8ef617f3fbb853e79b0ea3592", size = 16134051 }, + { url = "https://files.pythonhosted.org/packages/8e/21/efd47800e4affc993e8be50c1b768de038363dd88865920439ef7b422c60/numpy-2.2.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7a4e84a6283b36632e2a5b56e121961f6542ab886bc9e12f8f9818b3c266bfbb", size = 15578972 }, + { url = "https://files.pythonhosted.org/packages/04/1e/f8bb88f6157045dd5d9b27ccf433d016981032690969aa5c19e332b138c0/numpy-2.2.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:11c43995255eb4127115956495f43e9343736edb7fcdb0d973defd9de14cd84f", size = 17898106 }, + { url = "https://files.pythonhosted.org/packages/2b/93/df59a5a3897c1f036ae8ff845e45f4081bb06943039ae28a3c1c7c780f22/numpy-2.2.4-cp312-cp312-win32.whl", hash = "sha256:65ef3468b53269eb5fdb3a5c09508c032b793da03251d5f8722b1194f1790c00", size = 6311190 }, + { url = "https://files.pythonhosted.org/packages/46/69/8c4f928741c2a8efa255fdc7e9097527c6dc4e4df147e3cadc5d9357ce85/numpy-2.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:2aad3c17ed2ff455b8eaafe06bcdae0062a1db77cb99f4b9cbb5f4ecb13c5146", size = 12644305 }, + { url = "https://files.pythonhosted.org/packages/2a/d0/bd5ad792e78017f5decfb2ecc947422a3669a34f775679a76317af671ffc/numpy-2.2.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cf4e5c6a278d620dee9ddeb487dc6a860f9b199eadeecc567f777daace1e9e7", size = 20933623 }, + { url = "https://files.pythonhosted.org/packages/c3/bc/2b3545766337b95409868f8e62053135bdc7fa2ce630aba983a2aa60b559/numpy-2.2.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1974afec0b479e50438fc3648974268f972e2d908ddb6d7fb634598cdb8260a0", size = 14148681 }, + { url = "https://files.pythonhosted.org/packages/6a/70/67b24d68a56551d43a6ec9fe8c5f91b526d4c1a46a6387b956bf2d64744e/numpy-2.2.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:79bd5f0a02aa16808fcbc79a9a376a147cc1045f7dfe44c6e7d53fa8b8a79392", size = 5148759 }, + { url = "https://files.pythonhosted.org/packages/1c/8b/e2fc8a75fcb7be12d90b31477c9356c0cbb44abce7ffb36be39a0017afad/numpy-2.2.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:3387dd7232804b341165cedcb90694565a6015433ee076c6754775e85d86f1fc", size = 6683092 }, + { url = "https://files.pythonhosted.org/packages/13/73/41b7b27f169ecf368b52533edb72e56a133f9e86256e809e169362553b49/numpy-2.2.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f527d8fdb0286fd2fd97a2a96c6be17ba4232da346931d967a0630050dfd298", size = 14081422 }, + { url = "https://files.pythonhosted.org/packages/4b/04/e208ff3ae3ddfbafc05910f89546382f15a3f10186b1f56bd99f159689c2/numpy-2.2.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bce43e386c16898b91e162e5baaad90c4b06f9dcbe36282490032cec98dc8ae7", size = 16132202 }, + { url = "https://files.pythonhosted.org/packages/fe/bc/2218160574d862d5e55f803d88ddcad88beff94791f9c5f86d67bd8fbf1c/numpy-2.2.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:31504f970f563d99f71a3512d0c01a645b692b12a63630d6aafa0939e52361e6", size = 15573131 }, + { url = "https://files.pythonhosted.org/packages/a5/78/97c775bc4f05abc8a8426436b7cb1be806a02a2994b195945600855e3a25/numpy-2.2.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:81413336ef121a6ba746892fad881a83351ee3e1e4011f52e97fba79233611fd", size = 17894270 }, + { url = "https://files.pythonhosted.org/packages/b9/eb/38c06217a5f6de27dcb41524ca95a44e395e6a1decdc0c99fec0832ce6ae/numpy-2.2.4-cp313-cp313-win32.whl", hash = "sha256:f486038e44caa08dbd97275a9a35a283a8f1d2f0ee60ac260a1790e76660833c", size = 6308141 }, + { url = "https://files.pythonhosted.org/packages/52/17/d0dd10ab6d125c6d11ffb6dfa3423c3571befab8358d4f85cd4471964fcd/numpy-2.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:207a2b8441cc8b6a2a78c9ddc64d00d20c303d79fba08c577752f080c4007ee3", size = 12636885 }, + { url = "https://files.pythonhosted.org/packages/fa/e2/793288ede17a0fdc921172916efb40f3cbc2aa97e76c5c84aba6dc7e8747/numpy-2.2.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:8120575cb4882318c791f839a4fd66161a6fa46f3f0a5e613071aae35b5dd8f8", size = 20961829 }, + { url = "https://files.pythonhosted.org/packages/3a/75/bb4573f6c462afd1ea5cbedcc362fe3e9bdbcc57aefd37c681be1155fbaa/numpy-2.2.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a761ba0fa886a7bb33c6c8f6f20213735cb19642c580a931c625ee377ee8bd39", size = 14161419 }, + { url = "https://files.pythonhosted.org/packages/03/68/07b4cd01090ca46c7a336958b413cdbe75002286295f2addea767b7f16c9/numpy-2.2.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:ac0280f1ba4a4bfff363a99a6aceed4f8e123f8a9b234c89140f5e894e452ecd", size = 5196414 }, + { url = "https://files.pythonhosted.org/packages/a5/fd/d4a29478d622fedff5c4b4b4cedfc37a00691079623c0575978d2446db9e/numpy-2.2.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:879cf3a9a2b53a4672a168c21375166171bc3932b7e21f622201811c43cdd3b0", size = 6709379 }, + { url = "https://files.pythonhosted.org/packages/41/78/96dddb75bb9be730b87c72f30ffdd62611aba234e4e460576a068c98eff6/numpy-2.2.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f05d4198c1bacc9124018109c5fba2f3201dbe7ab6e92ff100494f236209c960", size = 14051725 }, + { url = "https://files.pythonhosted.org/packages/00/06/5306b8199bffac2a29d9119c11f457f6c7d41115a335b78d3f86fad4dbe8/numpy-2.2.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f085ce2e813a50dfd0e01fbfc0c12bbe5d2063d99f8b29da30e544fb6483b8", size = 16101638 }, + { url = "https://files.pythonhosted.org/packages/fa/03/74c5b631ee1ded596945c12027649e6344614144369fd3ec1aaced782882/numpy-2.2.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:92bda934a791c01d6d9d8e038363c50918ef7c40601552a58ac84c9613a665bc", size = 15571717 }, + { url = "https://files.pythonhosted.org/packages/cb/dc/4fc7c0283abe0981e3b89f9b332a134e237dd476b0c018e1e21083310c31/numpy-2.2.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ee4d528022f4c5ff67332469e10efe06a267e32f4067dc76bb7e2cddf3cd25ff", size = 17879998 }, + { url = "https://files.pythonhosted.org/packages/e5/2b/878576190c5cfa29ed896b518cc516aecc7c98a919e20706c12480465f43/numpy-2.2.4-cp313-cp313t-win32.whl", hash = "sha256:05c076d531e9998e7e694c36e8b349969c56eadd2cdcd07242958489d79a7286", size = 6366896 }, + { url = "https://files.pythonhosted.org/packages/3e/05/eb7eec66b95cf697f08c754ef26c3549d03ebd682819f794cb039574a0a6/numpy-2.2.4-cp313-cp313t-win_amd64.whl", hash = "sha256:188dcbca89834cc2e14eb2f106c96d6d46f200fe0200310fc29089657379c58d", size = 12739119 }, +] + +[[package]] +name = "oauthlib" +version = "3.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6d/fa/fbf4001037904031639e6bfbfc02badfc7e12f137a8afa254df6c4c8a670/oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918", size = 177352 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/80/cab10959dc1faead58dc8384a781dfbf93cb4d33d50988f7a69f1b7c9bbe/oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca", size = 151688 }, +] + +[[package]] +name = "ordered-set" +version = "4.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/ca/bfac8bc689799bcca4157e0e0ced07e70ce125193fc2e166d2e685b7e2fe/ordered-set-4.1.0.tar.gz", hash = "sha256:694a8e44c87657c59292ede72891eb91d34131f6531463aab3009191c77364a8", size = 12826 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/55/af02708f230eb77084a299d7b08175cff006dea4f2721074b92cdb0296c0/ordered_set-4.1.0-py3-none-any.whl", hash = "sha256:046e1132c71fcf3330438a539928932caf51ddbc582496833e23de611de14562", size = 7634 }, +] + +[[package]] +name = "packaging" +version = "24.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/63/68dbb6eb2de9cb10ee4c9c14a0148804425e13c4fb20d61cce69f53106da/packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f", size = 163950 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759", size = 65451 }, +] + +[[package]] +name = "pandas" +version = "2.2.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "tzdata" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9c/d6/9f8431bacc2e19dca897724cd097b1bb224a6ad5433784a44b587c7c13af/pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667", size = 4399213 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/17/a3/fb2734118db0af37ea7433f57f722c0a56687e14b14690edff0cdb4b7e58/pandas-2.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b1d432e8d08679a40e2a6d8b2f9770a5c21793a6f9f47fdd52c5ce1948a5a8a9", size = 12529893 }, + { url = "https://files.pythonhosted.org/packages/e1/0c/ad295fd74bfac85358fd579e271cded3ac969de81f62dd0142c426b9da91/pandas-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5a1595fe639f5988ba6a8e5bc9649af3baf26df3998a0abe56c02609392e0a4", size = 11363475 }, + { url = "https://files.pythonhosted.org/packages/c6/2a/4bba3f03f7d07207481fed47f5b35f556c7441acddc368ec43d6643c5777/pandas-2.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5de54125a92bb4d1c051c0659e6fcb75256bf799a732a87184e5ea503965bce3", size = 15188645 }, + { url = "https://files.pythonhosted.org/packages/38/f8/d8fddee9ed0d0c0f4a2132c1dfcf0e3e53265055da8df952a53e7eaf178c/pandas-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb8ae78d8af97f849404f21411c95062db1496aeb3e56f146f0355c9989319", size = 12739445 }, + { url = "https://files.pythonhosted.org/packages/20/e8/45a05d9c39d2cea61ab175dbe6a2de1d05b679e8de2011da4ee190d7e748/pandas-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfcb5ee8d4d50c06a51c2fffa6cff6272098ad6540aed1a76d15fb9318194d8", size = 16359235 }, + { url = "https://files.pythonhosted.org/packages/1d/99/617d07a6a5e429ff90c90da64d428516605a1ec7d7bea494235e1c3882de/pandas-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:062309c1b9ea12a50e8ce661145c6aab431b1e99530d3cd60640e255778bd43a", size = 14056756 }, + { url = "https://files.pythonhosted.org/packages/29/d4/1244ab8edf173a10fd601f7e13b9566c1b525c4f365d6bee918e68381889/pandas-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:59ef3764d0fe818125a5097d2ae867ca3fa64df032331b7e0917cf5d7bf66b13", size = 11504248 }, + { url = "https://files.pythonhosted.org/packages/64/22/3b8f4e0ed70644e85cfdcd57454686b9057c6c38d2f74fe4b8bc2527214a/pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015", size = 12477643 }, + { url = "https://files.pythonhosted.org/packages/e4/93/b3f5d1838500e22c8d793625da672f3eec046b1a99257666c94446969282/pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28", size = 11281573 }, + { url = "https://files.pythonhosted.org/packages/f5/94/6c79b07f0e5aab1dcfa35a75f4817f5c4f677931d4234afcd75f0e6a66ca/pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0", size = 15196085 }, + { url = "https://files.pythonhosted.org/packages/e8/31/aa8da88ca0eadbabd0a639788a6da13bb2ff6edbbb9f29aa786450a30a91/pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24", size = 12711809 }, + { url = "https://files.pythonhosted.org/packages/ee/7c/c6dbdb0cb2a4344cacfb8de1c5808ca885b2e4dcfde8008266608f9372af/pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659", size = 16356316 }, + { url = "https://files.pythonhosted.org/packages/57/b7/8b757e7d92023b832869fa8881a992696a0bfe2e26f72c9ae9f255988d42/pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb", size = 14022055 }, + { url = "https://files.pythonhosted.org/packages/3b/bc/4b18e2b8c002572c5a441a64826252ce5da2aa738855747247a971988043/pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d", size = 11481175 }, + { url = "https://files.pythonhosted.org/packages/76/a3/a5d88146815e972d40d19247b2c162e88213ef51c7c25993942c39dbf41d/pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468", size = 12615650 }, + { url = "https://files.pythonhosted.org/packages/9c/8c/f0fd18f6140ddafc0c24122c8a964e48294acc579d47def376fef12bcb4a/pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18", size = 11290177 }, + { url = "https://files.pythonhosted.org/packages/ed/f9/e995754eab9c0f14c6777401f7eece0943840b7a9fc932221c19d1abee9f/pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2", size = 14651526 }, + { url = "https://files.pythonhosted.org/packages/25/b0/98d6ae2e1abac4f35230aa756005e8654649d305df9a28b16b9ae4353bff/pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4", size = 11871013 }, + { url = "https://files.pythonhosted.org/packages/cc/57/0f72a10f9db6a4628744c8e8f0df4e6e21de01212c7c981d31e50ffc8328/pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d", size = 15711620 }, + { url = "https://files.pythonhosted.org/packages/ab/5f/b38085618b950b79d2d9164a711c52b10aefc0ae6833b96f626b7021b2ed/pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a", size = 13098436 }, +] + +[[package]] +name = "pandas-gbq" +version = "0.28.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "db-dtypes" }, + { name = "google-api-core" }, + { name = "google-auth" }, + { name = "google-auth-oauthlib" }, + { name = "google-cloud-bigquery" }, + { name = "numpy" }, + { name = "packaging" }, + { name = "pandas" }, + { name = "pyarrow" }, + { name = "pydata-google-auth" }, + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cf/01/772caa64d1987f6b0eca64c643ac14f8899402dec70e9b08ae15ba410096/pandas_gbq-0.28.0.tar.gz", hash = "sha256:daa4ffb80c1c262185059adb4551ac0cc52013ca3b7ab72c11cec1011f242ae5", size = 64056 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/72/eba5962318d580defd234cc759e710ba588c6e6c1a1bc4d633939111827e/pandas_gbq-0.28.0-py2.py3-none-any.whl", hash = "sha256:6be441dff24cde87ebf1e61ee66a3a7c51c4894aa1db0f9983a2c927f57caad3", size = 37911 }, +] + +[[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 } +wheels = [ + { 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 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 }, +] + +[[package]] +name = "platformdirs" +version = "4.3.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/2d/7d512a3913d60623e7eb945c6d1b4f0bddf1d0b7ada5225274c87e5b53d1/platformdirs-4.3.7.tar.gz", hash = "sha256:eb437d586b6a0986388f0d6f74aa0cde27b48d0e3d66843640bfb6bdcdb6e351", size = 21291 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6d/45/59578566b3275b8fd9157885918fcd0c4d74162928a5310926887b856a51/platformdirs-4.3.7-py3-none-any.whl", hash = "sha256:a03875334331946f13c549dbd8f4bac7a13a50a895a0eb1e8c6a8ace80d40a94", size = 18499 }, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, +] + +[[package]] +name = "proto-plus" +version = "1.26.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "protobuf" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f4/ac/87285f15f7cce6d4a008f33f1757fb5a13611ea8914eb58c3d0d26243468/proto_plus-1.26.1.tar.gz", hash = "sha256:21a515a4c4c0088a773899e23c7bbade3d18f9c66c73edd4c7ee3816bc96a012", size = 56142 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4e/6d/280c4c2ce28b1593a19ad5239c8b826871fc6ec275c21afc8e1820108039/proto_plus-1.26.1-py3-none-any.whl", hash = "sha256:13285478c2dcf2abb829db158e1047e2f1e8d63a077d94263c2b88b043c75a66", size = 50163 }, +] + +[[package]] +name = "protobuf" +version = "5.29.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/17/7d/b9dca7365f0e2c4fa7c193ff795427cfa6290147e5185ab11ece280a18e7/protobuf-5.29.4.tar.gz", hash = "sha256:4f1dfcd7997b31ef8f53ec82781ff434a28bf71d9102ddde14d076adcfc78c99", size = 424902 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/b2/043a1a1a20edd134563699b0e91862726a0dc9146c090743b6c44d798e75/protobuf-5.29.4-cp310-abi3-win32.whl", hash = "sha256:13eb236f8eb9ec34e63fc8b1d6efd2777d062fa6aaa68268fb67cf77f6839ad7", size = 422709 }, + { url = "https://files.pythonhosted.org/packages/79/fc/2474b59570daa818de6124c0a15741ee3e5d6302e9d6ce0bdfd12e98119f/protobuf-5.29.4-cp310-abi3-win_amd64.whl", hash = "sha256:bcefcdf3976233f8a502d265eb65ea740c989bacc6c30a58290ed0e519eb4b8d", size = 434506 }, + { url = "https://files.pythonhosted.org/packages/46/de/7c126bbb06aa0f8a7b38aaf8bd746c514d70e6a2a3f6dd460b3b7aad7aae/protobuf-5.29.4-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:307ecba1d852ec237e9ba668e087326a67564ef83e45a0189a772ede9e854dd0", size = 417826 }, + { url = "https://files.pythonhosted.org/packages/a2/b5/bade14ae31ba871a139aa45e7a8183d869efe87c34a4850c87b936963261/protobuf-5.29.4-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:aec4962f9ea93c431d5714ed1be1c93f13e1a8618e70035ba2b0564d9e633f2e", size = 319574 }, + { url = "https://files.pythonhosted.org/packages/46/88/b01ed2291aae68b708f7d334288ad5fb3e7aa769a9c309c91a0d55cb91b0/protobuf-5.29.4-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:d7d3f7d1d5a66ed4942d4fefb12ac4b14a29028b209d4bfb25c68ae172059922", size = 319672 }, + { url = "https://files.pythonhosted.org/packages/12/fb/a586e0c973c95502e054ac5f81f88394f24ccc7982dac19c515acd9e2c93/protobuf-5.29.4-py3-none-any.whl", hash = "sha256:3fde11b505e1597f71b875ef2fc52062b6a9740e5f7c8997ce878b6009145862", size = 172551 }, +] + +[[package]] +name = "psycopg2-binary" +version = "2.9.10" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/cb/0e/bdc8274dc0585090b4e3432267d7be4dfbfd8971c0fa59167c711105a6bf/psycopg2-binary-2.9.10.tar.gz", hash = "sha256:4b3df0e6990aa98acda57d983942eff13d824135fe2250e6522edaa782a06de2", size = 385764 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/7d/465cc9795cf76f6d329efdafca74693714556ea3891813701ac1fee87545/psycopg2_binary-2.9.10-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:880845dfe1f85d9d5f7c412efea7a08946a46894537e4e5d091732eb1d34d9a0", size = 3044771 }, + { url = "https://files.pythonhosted.org/packages/8b/31/6d225b7b641a1a2148e3ed65e1aa74fc86ba3fee850545e27be9e1de893d/psycopg2_binary-2.9.10-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:9440fa522a79356aaa482aa4ba500b65f28e5d0e63b801abf6aa152a29bd842a", size = 3275336 }, + { url = "https://files.pythonhosted.org/packages/30/b7/a68c2b4bff1cbb1728e3ec864b2d92327c77ad52edcd27922535a8366f68/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e3923c1d9870c49a2d44f795df0c889a22380d36ef92440ff618ec315757e539", size = 2851637 }, + { url = "https://files.pythonhosted.org/packages/0b/b1/cfedc0e0e6f9ad61f8657fd173b2f831ce261c02a08c0b09c652b127d813/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b2c956c028ea5de47ff3a8d6b3cc3330ab45cf0b7c3da35a2d6ff8420896526", size = 3082097 }, + { url = "https://files.pythonhosted.org/packages/18/ed/0a8e4153c9b769f59c02fb5e7914f20f0b2483a19dae7bf2db54b743d0d0/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f758ed67cab30b9a8d2833609513ce4d3bd027641673d4ebc9c067e4d208eec1", size = 3264776 }, + { url = "https://files.pythonhosted.org/packages/10/db/d09da68c6a0cdab41566b74e0a6068a425f077169bed0946559b7348ebe9/psycopg2_binary-2.9.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cd9b4f2cfab88ed4a9106192de509464b75a906462fb846b936eabe45c2063e", size = 3020968 }, + { url = "https://files.pythonhosted.org/packages/94/28/4d6f8c255f0dfffb410db2b3f9ac5218d959a66c715c34cac31081e19b95/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dc08420625b5a20b53551c50deae6e231e6371194fa0651dbe0fb206452ae1f", size = 2872334 }, + { url = "https://files.pythonhosted.org/packages/05/f7/20d7bf796593c4fea95e12119d6cc384ff1f6141a24fbb7df5a668d29d29/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d7cd730dfa7c36dbe8724426bf5612798734bff2d3c3857f36f2733f5bfc7c00", size = 2822722 }, + { url = "https://files.pythonhosted.org/packages/4d/e4/0c407ae919ef626dbdb32835a03b6737013c3cc7240169843965cada2bdf/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:155e69561d54d02b3c3209545fb08938e27889ff5a10c19de8d23eb5a41be8a5", size = 2920132 }, + { url = "https://files.pythonhosted.org/packages/2d/70/aa69c9f69cf09a01da224909ff6ce8b68faeef476f00f7ec377e8f03be70/psycopg2_binary-2.9.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3cc28a6fd5a4a26224007712e79b81dbaee2ffb90ff406256158ec4d7b52b47", size = 2959312 }, + { url = "https://files.pythonhosted.org/packages/d3/bd/213e59854fafe87ba47814bf413ace0dcee33a89c8c8c814faca6bc7cf3c/psycopg2_binary-2.9.10-cp312-cp312-win32.whl", hash = "sha256:ec8a77f521a17506a24a5f626cb2aee7850f9b69a0afe704586f63a464f3cd64", size = 1025191 }, + { url = "https://files.pythonhosted.org/packages/92/29/06261ea000e2dc1e22907dbbc483a1093665509ea586b29b8986a0e56733/psycopg2_binary-2.9.10-cp312-cp312-win_amd64.whl", hash = "sha256:18c5ee682b9c6dd3696dad6e54cc7ff3a1a9020df6a5c0f861ef8bfd338c3ca0", size = 1164031 }, + { url = "https://files.pythonhosted.org/packages/3e/30/d41d3ba765609c0763505d565c4d12d8f3c79793f0d0f044ff5a28bf395b/psycopg2_binary-2.9.10-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:26540d4a9a4e2b096f1ff9cce51253d0504dca5a85872c7f7be23be5a53eb18d", size = 3044699 }, + { url = "https://files.pythonhosted.org/packages/35/44/257ddadec7ef04536ba71af6bc6a75ec05c5343004a7ec93006bee66c0bc/psycopg2_binary-2.9.10-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e217ce4d37667df0bc1c397fdcd8de5e81018ef305aed9415c3b093faaeb10fb", size = 3275245 }, + { url = "https://files.pythonhosted.org/packages/1b/11/48ea1cd11de67f9efd7262085588790a95d9dfcd9b8a687d46caf7305c1a/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:245159e7ab20a71d989da00f280ca57da7641fa2cdcf71749c193cea540a74f7", size = 2851631 }, + { url = "https://files.pythonhosted.org/packages/62/e0/62ce5ee650e6c86719d621a761fe4bc846ab9eff8c1f12b1ed5741bf1c9b/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c4ded1a24b20021ebe677b7b08ad10bf09aac197d6943bfe6fec70ac4e4690d", size = 3082140 }, + { url = "https://files.pythonhosted.org/packages/27/ce/63f946c098611f7be234c0dd7cb1ad68b0b5744d34f68062bb3c5aa510c8/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3abb691ff9e57d4a93355f60d4f4c1dd2d68326c968e7db17ea96df3c023ef73", size = 3264762 }, + { url = "https://files.pythonhosted.org/packages/43/25/c603cd81402e69edf7daa59b1602bd41eb9859e2824b8c0855d748366ac9/psycopg2_binary-2.9.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8608c078134f0b3cbd9f89b34bd60a943b23fd33cc5f065e8d5f840061bd0673", size = 3020967 }, + { url = "https://files.pythonhosted.org/packages/5f/d6/8708d8c6fca531057fa170cdde8df870e8b6a9b136e82b361c65e42b841e/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:230eeae2d71594103cd5b93fd29d1ace6420d0b86f4778739cb1a5a32f607d1f", size = 2872326 }, + { url = "https://files.pythonhosted.org/packages/ce/ac/5b1ea50fc08a9df82de7e1771537557f07c2632231bbab652c7e22597908/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:bb89f0a835bcfc1d42ccd5f41f04870c1b936d8507c6df12b7737febc40f0909", size = 2822712 }, + { url = "https://files.pythonhosted.org/packages/c4/fc/504d4503b2abc4570fac3ca56eb8fed5e437bf9c9ef13f36b6621db8ef00/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f0c2d907a1e102526dd2986df638343388b94c33860ff3bbe1384130828714b1", size = 2920155 }, + { url = "https://files.pythonhosted.org/packages/b2/d1/323581e9273ad2c0dbd1902f3fb50c441da86e894b6e25a73c3fda32c57e/psycopg2_binary-2.9.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f8157bed2f51db683f31306aa497311b560f2265998122abe1dce6428bd86567", size = 2959356 }, + { url = "https://files.pythonhosted.org/packages/08/50/d13ea0a054189ae1bc21af1d85b6f8bb9bbc5572991055d70ad9006fe2d6/psycopg2_binary-2.9.10-cp313-cp313-win_amd64.whl", hash = "sha256:27422aa5f11fbcd9b18da48373eb67081243662f9b46e6fd07c3eb46e4535142", size = 2569224 }, +] + +[[package]] +name = "pyarrow" +version = "18.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7f/7b/640785a9062bb00314caa8a387abce547d2a420cf09bd6c715fe659ccffb/pyarrow-18.1.0.tar.gz", hash = "sha256:9386d3ca9c145b5539a1cfc75df07757dff870168c959b473a0bccbc3abc8c73", size = 1118671 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/50/12829e7111b932581e51dda51d5cb39207a056c30fe31ef43f14c63c4d7e/pyarrow-18.1.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:9f3a76670b263dc41d0ae877f09124ab96ce10e4e48f3e3e4257273cee61ad0d", size = 29514620 }, + { url = "https://files.pythonhosted.org/packages/d1/41/468c944eab157702e96abab3d07b48b8424927d4933541ab43788bb6964d/pyarrow-18.1.0-cp312-cp312-macosx_12_0_x86_64.whl", hash = "sha256:da31fbca07c435be88a0c321402c4e31a2ba61593ec7473630769de8346b54ee", size = 30856494 }, + { url = "https://files.pythonhosted.org/packages/68/f9/29fb659b390312a7345aeb858a9d9c157552a8852522f2c8bad437c29c0a/pyarrow-18.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:543ad8459bc438efc46d29a759e1079436290bd583141384c6f7a1068ed6f992", size = 39203624 }, + { url = "https://files.pythonhosted.org/packages/6e/f6/19360dae44200e35753c5c2889dc478154cd78e61b1f738514c9f131734d/pyarrow-18.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0743e503c55be0fdb5c08e7d44853da27f19dc854531c0570f9f394ec9671d54", size = 40139341 }, + { url = "https://files.pythonhosted.org/packages/bb/e6/9b3afbbcf10cc724312e824af94a2e993d8ace22994d823f5c35324cebf5/pyarrow-18.1.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:d4b3d2a34780645bed6414e22dda55a92e0fcd1b8a637fba86800ad737057e33", size = 38618629 }, + { url = "https://files.pythonhosted.org/packages/3a/2e/3b99f8a3d9e0ccae0e961978a0d0089b25fb46ebbcfb5ebae3cca179a5b3/pyarrow-18.1.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:c52f81aa6f6575058d8e2c782bf79d4f9fdc89887f16825ec3a66607a5dd8e30", size = 40078661 }, + { url = "https://files.pythonhosted.org/packages/76/52/f8da04195000099d394012b8d42c503d7041b79f778d854f410e5f05049a/pyarrow-18.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:0ad4892617e1a6c7a551cfc827e072a633eaff758fa09f21c4ee548c30bcaf99", size = 25092330 }, + { url = "https://files.pythonhosted.org/packages/cb/87/aa4d249732edef6ad88899399047d7e49311a55749d3c373007d034ee471/pyarrow-18.1.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:84e314d22231357d473eabec709d0ba285fa706a72377f9cc8e1cb3c8013813b", size = 29497406 }, + { url = "https://files.pythonhosted.org/packages/3c/c7/ed6adb46d93a3177540e228b5ca30d99fc8ea3b13bdb88b6f8b6467e2cb7/pyarrow-18.1.0-cp313-cp313-macosx_12_0_x86_64.whl", hash = "sha256:f591704ac05dfd0477bb8f8e0bd4b5dc52c1cadf50503858dce3a15db6e46ff2", size = 30835095 }, + { url = "https://files.pythonhosted.org/packages/41/d7/ed85001edfb96200ff606943cff71d64f91926ab42828676c0fc0db98963/pyarrow-18.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:acb7564204d3c40babf93a05624fc6a8ec1ab1def295c363afc40b0c9e66c191", size = 39194527 }, + { url = "https://files.pythonhosted.org/packages/59/16/35e28eab126342fa391593415d79477e89582de411bb95232f28b131a769/pyarrow-18.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74de649d1d2ccb778f7c3afff6085bd5092aed4c23df9feeb45dd6b16f3811aa", size = 40131443 }, + { url = "https://files.pythonhosted.org/packages/0c/95/e855880614c8da20f4cd74fa85d7268c725cf0013dc754048593a38896a0/pyarrow-18.1.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:f96bd502cb11abb08efea6dab09c003305161cb6c9eafd432e35e76e7fa9b90c", size = 38608750 }, + { url = "https://files.pythonhosted.org/packages/54/9d/f253554b1457d4fdb3831b7bd5f8f00f1795585a606eabf6fec0a58a9c38/pyarrow-18.1.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:36ac22d7782554754a3b50201b607d553a8d71b78cdf03b33c1125be4b52397c", size = 40066690 }, + { url = "https://files.pythonhosted.org/packages/2f/58/8912a2563e6b8273e8aa7b605a345bba5a06204549826f6493065575ebc0/pyarrow-18.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:25dbacab8c5952df0ca6ca0af28f50d45bd31c1ff6fcf79e2d120b4a65ee7181", size = 25081054 }, + { url = "https://files.pythonhosted.org/packages/82/f9/d06ddc06cab1ada0c2f2fd205ac8c25c2701182de1b9c4bf7a0a44844431/pyarrow-18.1.0-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:6a276190309aba7bc9d5bd2933230458b3521a4317acfefe69a354f2fe59f2bc", size = 29525542 }, + { url = "https://files.pythonhosted.org/packages/ab/94/8917e3b961810587ecbdaa417f8ebac0abb25105ae667b7aa11c05876976/pyarrow-18.1.0-cp313-cp313t-macosx_12_0_x86_64.whl", hash = "sha256:ad514dbfcffe30124ce655d72771ae070f30bf850b48bc4d9d3b25993ee0e386", size = 30829412 }, + { url = "https://files.pythonhosted.org/packages/5e/e3/3b16c3190f3d71d3b10f6758d2d5f7779ef008c4fd367cedab3ed178a9f7/pyarrow-18.1.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aebc13a11ed3032d8dd6e7171eb6e86d40d67a5639d96c35142bd568b9299324", size = 39119106 }, + { url = "https://files.pythonhosted.org/packages/1d/d6/5d704b0d25c3c79532f8c0639f253ec2803b897100f64bcb3f53ced236e5/pyarrow-18.1.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d6cf5c05f3cee251d80e98726b5c7cc9f21bab9e9783673bac58e6dfab57ecc8", size = 40090940 }, + { url = "https://files.pythonhosted.org/packages/37/29/366bc7e588220d74ec00e497ac6710c2833c9176f0372fe0286929b2d64c/pyarrow-18.1.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:11b676cd410cf162d3f6a70b43fb9e1e40affbc542a1e9ed3681895f2962d3d9", size = 38548177 }, + { url = "https://files.pythonhosted.org/packages/c8/11/fabf6ecabb1fe5b7d96889228ca2a9158c4c3bb732e3b8ee3f7f6d40b703/pyarrow-18.1.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:b76130d835261b38f14fc41fdfb39ad8d672afb84c447126b84d5472244cfaba", size = 40043567 }, +] + +[[package]] +name = "pyasn1" +version = "0.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ba/e9/01f1a64245b89f039897cb0130016d79f77d52669aae6ee7b159a6c4c018/pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034", size = 145322 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/f1/d6a797abb14f6283c0ddff96bbdd46937f64122b8c925cab503dd37f8214/pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629", size = 83135 }, +] + +[[package]] +name = "pyasn1-modules" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e9/e6/78ebbb10a8c8e4b61a59249394a4a594c1a7af95593dc933a349c8d00964/pyasn1_modules-0.4.2.tar.gz", hash = "sha256:677091de870a80aae844b1ca6134f54652fa2c8c5a52aa396440ac3106e941e6", size = 307892 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/8d/d529b5d697919ba8c11ad626e835d4039be708a35b0d22de83a269a6682c/pyasn1_modules-0.4.2-py3-none-any.whl", hash = "sha256:29253a9207ce32b64c3ac6600edc75368f98473906e8fd1043bd6b5b1de2c14a", size = 181259 }, +] + +[[package]] +name = "pycparser" +version = "2.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/b2/31537cf4b1ca988837256c910a668b553fceb8f069bedc4b1c826024b52c/pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6", size = 172736 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/13/a3/a812df4e2dd5696d1f351d58b8fe16a405b234ad2886a0dab9183fb78109/pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc", size = 117552 }, +] + +[[package]] +name = "pydantic" +version = "2.11.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/93/a3/698b87a4d4d303d7c5f62ea5fbf7a79cab236ccfbd0a17847b7f77f8163e/pydantic-2.11.1.tar.gz", hash = "sha256:442557d2910e75c991c39f4b4ab18963d57b9b55122c8b2a9cd176d8c29ce968", size = 782817 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cc/12/f9221a949f2419e2e23847303c002476c26fbcfd62dc7f3d25d0bec5ca99/pydantic-2.11.1-py3-none-any.whl", hash = "sha256:5b6c415eee9f8123a14d859be0c84363fec6b1feb6b688d6435801230b56e0b8", size = 442648 }, +] + +[[package]] +name = "pydantic-core" +version = "2.33.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b9/05/91ce14dfd5a3a99555fce436318cc0fd1f08c4daa32b3248ad63669ea8b4/pydantic_core-2.33.0.tar.gz", hash = "sha256:40eb8af662ba409c3cbf4a8150ad32ae73514cd7cb1f1a2113af39763dd616b3", size = 434080 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/c4/c9381323cbdc1bb26d352bc184422ce77c4bc2f2312b782761093a59fafc/pydantic_core-2.33.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:6c32a40712e3662bebe524abe8abb757f2fa2000028d64cc5a1006016c06af43", size = 2025127 }, + { url = "https://files.pythonhosted.org/packages/6f/bd/af35278080716ecab8f57e84515c7dc535ed95d1c7f52c1c6f7b313a9dab/pydantic_core-2.33.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8ec86b5baa36f0a0bfb37db86c7d52652f8e8aa076ab745ef7725784183c3fdd", size = 1851687 }, + { url = "https://files.pythonhosted.org/packages/12/e4/a01461225809c3533c23bd1916b1e8c2e21727f0fea60ab1acbffc4e2fca/pydantic_core-2.33.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4deac83a8cc1d09e40683be0bc6d1fa4cde8df0a9bf0cda5693f9b0569ac01b6", size = 1892232 }, + { url = "https://files.pythonhosted.org/packages/51/17/3d53d62a328fb0a49911c2962036b9e7a4f781b7d15e9093c26299e5f76d/pydantic_core-2.33.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:175ab598fb457a9aee63206a1993874badf3ed9a456e0654273e56f00747bbd6", size = 1977896 }, + { url = "https://files.pythonhosted.org/packages/30/98/01f9d86e02ec4a38f4b02086acf067f2c776b845d43f901bd1ee1c21bc4b/pydantic_core-2.33.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f36afd0d56a6c42cf4e8465b6441cf546ed69d3a4ec92724cc9c8c61bd6ecf4", size = 2127717 }, + { url = "https://files.pythonhosted.org/packages/3c/43/6f381575c61b7c58b0fd0b92134c5a1897deea4cdfc3d47567b3ff460a4e/pydantic_core-2.33.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0a98257451164666afafc7cbf5fb00d613e33f7e7ebb322fbcd99345695a9a61", size = 2680287 }, + { url = "https://files.pythonhosted.org/packages/01/42/c0d10d1451d161a9a0da9bbef023b8005aa26e9993a8cc24dc9e3aa96c93/pydantic_core-2.33.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ecc6d02d69b54a2eb83ebcc6f29df04957f734bcf309d346b4f83354d8376862", size = 2008276 }, + { url = "https://files.pythonhosted.org/packages/20/ca/e08df9dba546905c70bae44ced9f3bea25432e34448d95618d41968f40b7/pydantic_core-2.33.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1a69b7596c6603afd049ce7f3835bcf57dd3892fc7279f0ddf987bebed8caa5a", size = 2115305 }, + { url = "https://files.pythonhosted.org/packages/03/1f/9b01d990730a98833113581a78e595fd40ed4c20f9693f5a658fb5f91eff/pydantic_core-2.33.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ea30239c148b6ef41364c6f51d103c2988965b643d62e10b233b5efdca8c0099", size = 2068999 }, + { url = "https://files.pythonhosted.org/packages/20/18/fe752476a709191148e8b1e1139147841ea5d2b22adcde6ee6abb6c8e7cf/pydantic_core-2.33.0-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:abfa44cf2f7f7d7a199be6c6ec141c9024063205545aa09304349781b9a125e6", size = 2241488 }, + { url = "https://files.pythonhosted.org/packages/81/22/14738ad0a0bf484b928c9e52004f5e0b81dd8dabbdf23b843717b37a71d1/pydantic_core-2.33.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:20d4275f3c4659d92048c70797e5fdc396c6e4446caf517ba5cad2db60cd39d3", size = 2248430 }, + { url = "https://files.pythonhosted.org/packages/e8/27/be7571e215ac8d321712f2433c445b03dbcd645366a18f67b334df8912bc/pydantic_core-2.33.0-cp312-cp312-win32.whl", hash = "sha256:918f2013d7eadea1d88d1a35fd4a1e16aaf90343eb446f91cb091ce7f9b431a2", size = 1908353 }, + { url = "https://files.pythonhosted.org/packages/be/3a/be78f28732f93128bd0e3944bdd4b3970b389a1fbd44907c97291c8dcdec/pydantic_core-2.33.0-cp312-cp312-win_amd64.whl", hash = "sha256:aec79acc183865bad120b0190afac467c20b15289050648b876b07777e67ea48", size = 1955956 }, + { url = "https://files.pythonhosted.org/packages/21/26/b8911ac74faa994694b76ee6a22875cc7a4abea3c381fdba4edc6c6bef84/pydantic_core-2.33.0-cp312-cp312-win_arm64.whl", hash = "sha256:5461934e895968655225dfa8b3be79e7e927e95d4bd6c2d40edd2fa7052e71b6", size = 1903259 }, + { url = "https://files.pythonhosted.org/packages/79/20/de2ad03ce8f5b3accf2196ea9b44f31b0cd16ac6e8cfc6b21976ed45ec35/pydantic_core-2.33.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f00e8b59e1fc8f09d05594aa7d2b726f1b277ca6155fc84c0396db1b373c4555", size = 2032214 }, + { url = "https://files.pythonhosted.org/packages/f9/af/6817dfda9aac4958d8b516cbb94af507eb171c997ea66453d4d162ae8948/pydantic_core-2.33.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a73be93ecef45786d7d95b0c5e9b294faf35629d03d5b145b09b81258c7cd6d", size = 1852338 }, + { url = "https://files.pythonhosted.org/packages/44/f3/49193a312d9c49314f2b953fb55740b7c530710977cabe7183b8ef111b7f/pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff48a55be9da6930254565ff5238d71d5e9cd8c5487a191cb85df3bdb8c77365", size = 1896913 }, + { url = "https://files.pythonhosted.org/packages/06/e0/c746677825b2e29a2fa02122a8991c83cdd5b4c5f638f0664d4e35edd4b2/pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:26a4ea04195638dcd8c53dadb545d70badba51735b1594810e9768c2c0b4a5da", size = 1986046 }, + { url = "https://files.pythonhosted.org/packages/11/ec/44914e7ff78cef16afb5e5273d480c136725acd73d894affdbe2a1bbaad5/pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:41d698dcbe12b60661f0632b543dbb119e6ba088103b364ff65e951610cb7ce0", size = 2128097 }, + { url = "https://files.pythonhosted.org/packages/fe/f5/c6247d424d01f605ed2e3802f338691cae17137cee6484dce9f1ac0b872b/pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ae62032ef513fe6281ef0009e30838a01057b832dc265da32c10469622613885", size = 2681062 }, + { url = "https://files.pythonhosted.org/packages/f0/85/114a2113b126fdd7cf9a9443b1b1fe1b572e5bd259d50ba9d5d3e1927fa9/pydantic_core-2.33.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f225f3a3995dbbc26affc191d0443c6c4aa71b83358fd4c2b7d63e2f6f0336f9", size = 2007487 }, + { url = "https://files.pythonhosted.org/packages/e6/40/3c05ed28d225c7a9acd2b34c5c8010c279683a870219b97e9f164a5a8af0/pydantic_core-2.33.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5bdd36b362f419c78d09630cbaebc64913f66f62bda6d42d5fbb08da8cc4f181", size = 2121382 }, + { url = "https://files.pythonhosted.org/packages/8a/22/e70c086f41eebd323e6baa92cc906c3f38ddce7486007eb2bdb3b11c8f64/pydantic_core-2.33.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:2a0147c0bef783fd9abc9f016d66edb6cac466dc54a17ec5f5ada08ff65caf5d", size = 2072473 }, + { url = "https://files.pythonhosted.org/packages/3e/84/d1614dedd8fe5114f6a0e348bcd1535f97d76c038d6102f271433cd1361d/pydantic_core-2.33.0-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:c860773a0f205926172c6644c394e02c25421dc9a456deff16f64c0e299487d3", size = 2249468 }, + { url = "https://files.pythonhosted.org/packages/b0/c0/787061eef44135e00fddb4b56b387a06c303bfd3884a6df9bea5cb730230/pydantic_core-2.33.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:138d31e3f90087f42aa6286fb640f3c7a8eb7bdae829418265e7e7474bd2574b", size = 2254716 }, + { url = "https://files.pythonhosted.org/packages/ae/e2/27262eb04963201e89f9c280f1e10c493a7a37bc877e023f31aa72d2f911/pydantic_core-2.33.0-cp313-cp313-win32.whl", hash = "sha256:d20cbb9d3e95114325780f3cfe990f3ecae24de7a2d75f978783878cce2ad585", size = 1916450 }, + { url = "https://files.pythonhosted.org/packages/13/8d/25ff96f1e89b19e0b70b3cd607c9ea7ca27e1dcb810a9cd4255ed6abf869/pydantic_core-2.33.0-cp313-cp313-win_amd64.whl", hash = "sha256:ca1103d70306489e3d006b0f79db8ca5dd3c977f6f13b2c59ff745249431a606", size = 1956092 }, + { url = "https://files.pythonhosted.org/packages/1b/64/66a2efeff657b04323ffcd7b898cb0354d36dae3a561049e092134a83e9c/pydantic_core-2.33.0-cp313-cp313-win_arm64.whl", hash = "sha256:6291797cad239285275558e0a27872da735b05c75d5237bbade8736f80e4c225", size = 1908367 }, + { url = "https://files.pythonhosted.org/packages/52/54/295e38769133363d7ec4a5863a4d579f331728c71a6644ff1024ee529315/pydantic_core-2.33.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:7b79af799630af263eca9ec87db519426d8c9b3be35016eddad1832bac812d87", size = 1813331 }, + { url = "https://files.pythonhosted.org/packages/4c/9c/0c8ea02db8d682aa1ef48938abae833c1d69bdfa6e5ec13b21734b01ae70/pydantic_core-2.33.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eabf946a4739b5237f4f56d77fa6668263bc466d06a8036c055587c130a46f7b", size = 1986653 }, + { url = "https://files.pythonhosted.org/packages/8e/4f/3fb47d6cbc08c7e00f92300e64ba655428c05c56b8ab6723bd290bae6458/pydantic_core-2.33.0-cp313-cp313t-win_amd64.whl", hash = "sha256:8a1d581e8cdbb857b0e0e81df98603376c1a5c34dc5e54039dcc00f043df81e7", size = 1931234 }, +] + +[[package]] +name = "pydata-google-auth" +version = "1.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "google-auth" }, + { name = "google-auth-oauthlib" }, + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3f/0d/455cb39f0d5a914412b57c55c6b16977c61a5ac74b615eea4fb0dc54e329/pydata-google-auth-1.9.1.tar.gz", hash = "sha256:0a51ce41c601ca0bc69b8795bf58bedff74b4a6a007c9106c7cbcdec00eaced2", size = 29814 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/cb/cdeaba62aa3c48f0d8834afb82b4a21463cd83df34fe01f9daa89a08ec6c/pydata_google_auth-1.9.1-py2.py3-none-any.whl", hash = "sha256:75ffce5d106e34b717b31844c1639ea505b7d9550dc23b96fb6c20d086b53fa3", size = 15552 }, +] + +[[package]] +name = "pyjwt" +version = "2.10.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e7/46/bd74733ff231675599650d3e47f361794b22ef3e3770998dda30d3b63726/pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953", size = 87785 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/ad/689f02752eeec26aed679477e80e632ef1b682313be70793d798c1d5fc8f/PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb", size = 22997 }, +] + +[package.optional-dependencies] +crypto = [ + { name = "cryptography" }, +] + +[[package]] +name = "pyodbc" +version = "5.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d5/5b/a93f7017d4df84c3971cf60ee935149f12e0d1e111febc67ba2e23015a79/pyodbc-5.1.0.tar.gz", hash = "sha256:397feee44561a6580be08cedbe986436859563f4bb378f48224655c8e987ea60", size = 115450 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ad/ca/a95dffabbd52b1fbdde7e54c2995a88df60a40a538cc257c57e0ca2a9a03/pyodbc-5.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d3d9cc4af703c4817b6e604315910b0cf5dcb68056d52b25ca072dd59c52dcbc", size = 73192 }, + { url = "https://files.pythonhosted.org/packages/04/0e/3948f989f0f9c123885848a7e931775d1730a1a0263b04531693bfa51650/pyodbc-5.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:406b8fa2133a7b6a713aa5187dba2d08cf763b5884606bed77610a7660fdfabe", size = 72227 }, + { url = "https://files.pythonhosted.org/packages/81/65/555af79473f9b5ce70d826303487bc62842f1607b254fc46f12d204a1718/pyodbc-5.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8488c3818f12207650836c5c6f7352f9ff9f56a05a05512145995e497c0bbb1", size = 347181 }, + { url = "https://files.pythonhosted.org/packages/db/41/08495c42ba0430ba74b039537426925cd71a7ff04d094a3a04eb8ca9febe/pyodbc-5.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0df69e3a500791b70b5748c68a79483b24428e4c16027b56aa0305e95c143a4", size = 352977 }, + { url = "https://files.pythonhosted.org/packages/80/d7/c8563abacf048916bc763f090c7aa88198cfcf60f1faead5c92b66260c3b/pyodbc-5.1.0-cp312-cp312-win32.whl", hash = "sha256:aa4e02d3a9bf819394510b726b25f1566f8b3f0891ca400ad2d4c8b86b535b78", size = 62817 }, + { url = "https://files.pythonhosted.org/packages/71/6e/6b8ec142713bbb8e34da6b73cf281699904823e1a61f9a78b6cbda92301a/pyodbc-5.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:33f4984af38872e7bdec78007a34e4d43ae72bf9d0bae3344e79d9d0db157c0e", size = 69259 }, +] + +[[package]] +name = "pyopenssl" +version = "25.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/9f/26/e25b4a374b4639e0c235527bbe31c0524f26eda701d79456a7e1877f4cc5/pyopenssl-25.0.0.tar.gz", hash = "sha256:cd2cef799efa3936bb08e8ccb9433a575722b9dd986023f1cabc4ae64e9dac16", size = 179573 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/d7/eb76863d2060dcbe7c7e6cccfd95ac02ea0b9acc37745a0d99ff6457aefb/pyOpenSSL-25.0.0-py3-none-any.whl", hash = "sha256:424c247065e46e76a37411b9ab1782541c23bb658bf003772c3405fbaa128e90", size = 56453 }, +] + +[[package]] +name = "pyproject-api" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7e/66/fdc17e94486836eda4ba7113c0db9ac7e2f4eea1b968ee09de2fe75e391b/pyproject_api-1.9.0.tar.gz", hash = "sha256:7e8a9854b2dfb49454fae421cb86af43efbb2b2454e5646ffb7623540321ae6e", size = 22714 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b0/1d/92b7c765df46f454889d9610292b0ccab15362be3119b9a624458455e8d5/pyproject_api-1.9.0-py3-none-any.whl", hash = "sha256:326df9d68dea22d9d98b5243c46e3ca3161b07a1b9b18e213d1e24fd0e605766", size = 13131 }, +] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +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 } +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-slugify" +version = "8.0.4" +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 } +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 }, +] + +[[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 } +wheels = [ + { 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 = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225 }, +] + +[[package]] +name = "pywin32-ctypes" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/85/9f/01a1a99704853cb63f253eea009390c88e7131c67e66a0a02099a8c917cb/pywin32-ctypes-0.2.3.tar.gz", hash = "sha256:d162dc04946d704503b2edc4d55f3dba5c1d539ead017afa00142c38b9885755", size = 29471 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756 }, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, + { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, + { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, + { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, + { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, + { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, + { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, + { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, + { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, + { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, + { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, + { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, + { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, + { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, + { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, + { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, + { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, + { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, +] + +[[package]] +name = "referencing" +version = "0.36.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/2f/db/98b5c277be99dd18bfd91dd04e1b759cad18d1a338188c936e92f921c7e2/referencing-0.36.2.tar.gz", hash = "sha256:df2e89862cd09deabbdba16944cc3f10feb6b3e6f18e902f7cc25609a34775aa", size = 74744 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c1/b1/3baf80dc6d2b7bc27a95a67752d0208e410351e3feb4eb78de5f77454d8d/referencing-0.36.2-py3-none-any.whl", hash = "sha256:e8699adbbf8b5c7de96d8ffa0eb5c158b3beafce084968e2ea8bb08c6794dcd0", size = 26775 }, +] + +[[package]] +name = "requests" +version = "2.32.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, +] + +[[package]] +name = "requests-oauthlib" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "oauthlib" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/f2/05f29bc3913aea15eb670be136045bf5c5bbf4b99ecb839da9b422bb2c85/requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9", size = 55650 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/5d/63d4ae3b9daea098d5d6f5da83984853c1bbacd5dc826764b249fe119d24/requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36", size = 24179 }, +] + +[[package]] +name = "rpds-py" +version = "0.24.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0b/b3/52b213298a0ba7097c7ea96bee95e1947aa84cc816d48cebb539770cdf41/rpds_py-0.24.0.tar.gz", hash = "sha256:772cc1b2cd963e7e17e6cc55fe0371fb9c704d63e44cacec7b9b7f523b78919e", size = 26863 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1a/e0/1c55f4a3be5f1ca1a4fd1f3ff1504a1478c1ed48d84de24574c4fa87e921/rpds_py-0.24.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:d8551e733626afec514b5d15befabea0dd70a343a9f23322860c4f16a9430205", size = 366945 }, + { url = "https://files.pythonhosted.org/packages/39/1b/a3501574fbf29118164314dbc800d568b8c1c7b3258b505360e8abb3902c/rpds_py-0.24.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0e374c0ce0ca82e5b67cd61fb964077d40ec177dd2c4eda67dba130de09085c7", size = 351935 }, + { url = "https://files.pythonhosted.org/packages/dc/47/77d3d71c55f6a374edde29f1aca0b2e547325ed00a9da820cabbc9497d2b/rpds_py-0.24.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d69d003296df4840bd445a5d15fa5b6ff6ac40496f956a221c4d1f6f7b4bc4d9", size = 390817 }, + { url = "https://files.pythonhosted.org/packages/4e/ec/1e336ee27484379e19c7f9cc170f4217c608aee406d3ae3a2e45336bff36/rpds_py-0.24.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8212ff58ac6dfde49946bea57474a386cca3f7706fc72c25b772b9ca4af6b79e", size = 401983 }, + { url = "https://files.pythonhosted.org/packages/07/f8/39b65cbc272c635eaea6d393c2ad1ccc81c39eca2db6723a0ca4b2108fce/rpds_py-0.24.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:528927e63a70b4d5f3f5ccc1fa988a35456eb5d15f804d276709c33fc2f19bda", size = 451719 }, + { url = "https://files.pythonhosted.org/packages/32/05/05c2b27dd9c30432f31738afed0300659cb9415db0ff7429b05dfb09bbde/rpds_py-0.24.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a824d2c7a703ba6daaca848f9c3d5cb93af0505be505de70e7e66829affd676e", size = 442546 }, + { url = "https://files.pythonhosted.org/packages/7d/e0/19383c8b5d509bd741532a47821c3e96acf4543d0832beba41b4434bcc49/rpds_py-0.24.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44d51febb7a114293ffd56c6cf4736cb31cd68c0fddd6aa303ed09ea5a48e029", size = 393695 }, + { url = "https://files.pythonhosted.org/packages/9d/15/39f14e96d94981d0275715ae8ea564772237f3fa89bc3c21e24de934f2c7/rpds_py-0.24.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3fab5f4a2c64a8fb64fc13b3d139848817a64d467dd6ed60dcdd6b479e7febc9", size = 427218 }, + { url = "https://files.pythonhosted.org/packages/22/b9/12da7124905a680f690da7a9de6f11de770b5e359f5649972f7181c8bf51/rpds_py-0.24.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9be4f99bee42ac107870c61dfdb294d912bf81c3c6d45538aad7aecab468b6b7", size = 568062 }, + { url = "https://files.pythonhosted.org/packages/88/17/75229017a2143d915f6f803721a6d721eca24f2659c5718a538afa276b4f/rpds_py-0.24.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:564c96b6076a98215af52f55efa90d8419cc2ef45d99e314fddefe816bc24f91", size = 596262 }, + { url = "https://files.pythonhosted.org/packages/aa/64/8e8a1d8bd1b6b638d6acb6d41ab2cec7f2067a5b8b4c9175703875159a7c/rpds_py-0.24.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:75a810b7664c17f24bf2ffd7f92416c00ec84b49bb68e6a0d93e542406336b56", size = 564306 }, + { url = "https://files.pythonhosted.org/packages/68/1c/a7eac8d8ed8cb234a9b1064647824c387753343c3fab6ed7c83481ed0be7/rpds_py-0.24.0-cp312-cp312-win32.whl", hash = "sha256:f6016bd950be4dcd047b7475fdf55fb1e1f59fc7403f387be0e8123e4a576d30", size = 224281 }, + { url = "https://files.pythonhosted.org/packages/bb/46/b8b5424d1d21f2f2f3f2d468660085318d4f74a8df8289e3dd6ad224d488/rpds_py-0.24.0-cp312-cp312-win_amd64.whl", hash = "sha256:998c01b8e71cf051c28f5d6f1187abbdf5cf45fc0efce5da6c06447cba997034", size = 239719 }, + { url = "https://files.pythonhosted.org/packages/9d/c3/3607abc770395bc6d5a00cb66385a5479fb8cd7416ddef90393b17ef4340/rpds_py-0.24.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:3d2d8e4508e15fc05b31285c4b00ddf2e0eb94259c2dc896771966a163122a0c", size = 367072 }, + { url = "https://files.pythonhosted.org/packages/d8/35/8c7ee0fe465793e3af3298dc5a9f3013bd63e7a69df04ccfded8293a4982/rpds_py-0.24.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0f00c16e089282ad68a3820fd0c831c35d3194b7cdc31d6e469511d9bffc535c", size = 351919 }, + { url = "https://files.pythonhosted.org/packages/91/d3/7e1b972501eb5466b9aca46a9c31bcbbdc3ea5a076e9ab33f4438c1d069d/rpds_py-0.24.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:951cc481c0c395c4a08639a469d53b7d4afa252529a085418b82a6b43c45c240", size = 390360 }, + { url = "https://files.pythonhosted.org/packages/a2/a8/ccabb50d3c91c26ad01f9b09a6a3b03e4502ce51a33867c38446df9f896b/rpds_py-0.24.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c9ca89938dff18828a328af41ffdf3902405a19f4131c88e22e776a8e228c5a8", size = 400704 }, + { url = "https://files.pythonhosted.org/packages/53/ae/5fa5bf0f3bc6ce21b5ea88fc0ecd3a439e7cb09dd5f9ffb3dbe1b6894fc5/rpds_py-0.24.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed0ef550042a8dbcd657dfb284a8ee00f0ba269d3f2286b0493b15a5694f9fe8", size = 450839 }, + { url = "https://files.pythonhosted.org/packages/e3/ac/c4e18b36d9938247e2b54f6a03746f3183ca20e1edd7d3654796867f5100/rpds_py-0.24.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b2356688e5d958c4d5cb964af865bea84db29971d3e563fb78e46e20fe1848b", size = 441494 }, + { url = "https://files.pythonhosted.org/packages/bf/08/b543969c12a8f44db6c0f08ced009abf8f519191ca6985509e7c44102e3c/rpds_py-0.24.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78884d155fd15d9f64f5d6124b486f3d3f7fd7cd71a78e9670a0f6f6ca06fb2d", size = 393185 }, + { url = "https://files.pythonhosted.org/packages/da/7e/f6eb6a7042ce708f9dfc781832a86063cea8a125bbe451d663697b51944f/rpds_py-0.24.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6a4a535013aeeef13c5532f802708cecae8d66c282babb5cd916379b72110cf7", size = 426168 }, + { url = "https://files.pythonhosted.org/packages/38/b0/6cd2bb0509ac0b51af4bb138e145b7c4c902bb4b724d6fd143689d6e0383/rpds_py-0.24.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:84e0566f15cf4d769dade9b366b7b87c959be472c92dffb70462dd0844d7cbad", size = 567622 }, + { url = "https://files.pythonhosted.org/packages/64/b0/c401f4f077547d98e8b4c2ec6526a80e7cb04f519d416430ec1421ee9e0b/rpds_py-0.24.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:823e74ab6fbaa028ec89615ff6acb409e90ff45580c45920d4dfdddb069f2120", size = 595435 }, + { url = "https://files.pythonhosted.org/packages/9f/ec/7993b6e803294c87b61c85bd63e11142ccfb2373cf88a61ec602abcbf9d6/rpds_py-0.24.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c61a2cb0085c8783906b2f8b1f16a7e65777823c7f4d0a6aaffe26dc0d358dd9", size = 563762 }, + { url = "https://files.pythonhosted.org/packages/1f/29/4508003204cb2f461dc2b83dd85f8aa2b915bc98fe6046b9d50d4aa05401/rpds_py-0.24.0-cp313-cp313-win32.whl", hash = "sha256:60d9b630c8025b9458a9d114e3af579a2c54bd32df601c4581bd054e85258143", size = 223510 }, + { url = "https://files.pythonhosted.org/packages/f9/12/09e048d1814195e01f354155fb772fb0854bd3450b5f5a82224b3a319f0e/rpds_py-0.24.0-cp313-cp313-win_amd64.whl", hash = "sha256:6eea559077d29486c68218178ea946263b87f1c41ae7f996b1f30a983c476a5a", size = 239075 }, + { url = "https://files.pythonhosted.org/packages/d2/03/5027cde39bb2408d61e4dd0cf81f815949bb629932a6c8df1701d0257fc4/rpds_py-0.24.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:d09dc82af2d3c17e7dd17120b202a79b578d79f2b5424bda209d9966efeed114", size = 362974 }, + { url = "https://files.pythonhosted.org/packages/bf/10/24d374a2131b1ffafb783e436e770e42dfdb74b69a2cd25eba8c8b29d861/rpds_py-0.24.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5fc13b44de6419d1e7a7e592a4885b323fbc2f46e1f22151e3a8ed3b8b920405", size = 348730 }, + { url = "https://files.pythonhosted.org/packages/7a/d1/1ef88d0516d46cd8df12e5916966dbf716d5ec79b265eda56ba1b173398c/rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c347a20d79cedc0a7bd51c4d4b7dbc613ca4e65a756b5c3e57ec84bd43505b47", size = 387627 }, + { url = "https://files.pythonhosted.org/packages/4e/35/07339051b8b901ecefd449ebf8e5522e92bcb95e1078818cbfd9db8e573c/rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:20f2712bd1cc26a3cc16c5a1bfee9ed1abc33d4cdf1aabd297fe0eb724df4272", size = 394094 }, + { url = "https://files.pythonhosted.org/packages/dc/62/ee89ece19e0ba322b08734e95441952062391065c157bbd4f8802316b4f1/rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aad911555286884be1e427ef0dc0ba3929e6821cbeca2194b13dc415a462c7fd", size = 449639 }, + { url = "https://files.pythonhosted.org/packages/15/24/b30e9f9e71baa0b9dada3a4ab43d567c6b04a36d1cb531045f7a8a0a7439/rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0aeb3329c1721c43c58cae274d7d2ca85c1690d89485d9c63a006cb79a85771a", size = 438584 }, + { url = "https://files.pythonhosted.org/packages/28/d9/49f7b8f3b4147db13961e19d5e30077cd0854ccc08487026d2cb2142aa4a/rpds_py-0.24.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a0f156e9509cee987283abd2296ec816225145a13ed0391df8f71bf1d789e2d", size = 391047 }, + { url = "https://files.pythonhosted.org/packages/49/b0/e66918d0972c33a259ba3cd7b7ff10ed8bd91dbcfcbec6367b21f026db75/rpds_py-0.24.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:aa6800adc8204ce898c8a424303969b7aa6a5e4ad2789c13f8648739830323b7", size = 418085 }, + { url = "https://files.pythonhosted.org/packages/e1/6b/99ed7ea0a94c7ae5520a21be77a82306aac9e4e715d4435076ead07d05c6/rpds_py-0.24.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a18fc371e900a21d7392517c6f60fe859e802547309e94313cd8181ad9db004d", size = 564498 }, + { url = "https://files.pythonhosted.org/packages/28/26/1cacfee6b800e6fb5f91acecc2e52f17dbf8b0796a7c984b4568b6d70e38/rpds_py-0.24.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9168764133fd919f8dcca2ead66de0105f4ef5659cbb4fa044f7014bed9a1797", size = 590202 }, + { url = "https://files.pythonhosted.org/packages/a9/9e/57bd2f9fba04a37cef673f9a66b11ca8c43ccdd50d386c455cd4380fe461/rpds_py-0.24.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5f6e3cec44ba05ee5cbdebe92d052f69b63ae792e7d05f1020ac5e964394080c", size = 561771 }, + { url = "https://files.pythonhosted.org/packages/9f/cf/b719120f375ab970d1c297dbf8de1e3c9edd26fe92c0ed7178dd94b45992/rpds_py-0.24.0-cp313-cp313t-win32.whl", hash = "sha256:8ebc7e65ca4b111d928b669713865f021b7773350eeac4a31d3e70144297baba", size = 221195 }, + { url = "https://files.pythonhosted.org/packages/2d/e5/22865285789f3412ad0c3d7ec4dc0a3e86483b794be8a5d9ed5a19390900/rpds_py-0.24.0-cp313-cp313t-win_amd64.whl", hash = "sha256:675269d407a257b8c00a6b58205b72eec8231656506c56fd429d924ca00bb350", size = 237354 }, +] + +[[package]] +name = "rsa" +version = "4.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pyasn1" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/aa/65/7d973b89c4d2351d7fb232c2e452547ddfa243e93131e7cfa766da627b52/rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21", size = 29711 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/49/97/fa78e3d2f65c02c8e1268b9aba606569fe97f6c8f7c2d74394553347c145/rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7", size = 34315 }, +] + +[[package]] +name = "secretstorage" +version = "3.3.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cryptography" }, + { name = "jeepney" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/53/a4/f48c9d79cb507ed1373477dbceaba7401fd8a23af63b837fa61f1dcd3691/SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77", size = 19739 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/24/b4293291fa1dd830f353d2cb163295742fa87f179fcc8a20a306a81978b7/SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99", size = 15221 }, +] + +[[package]] +name = "setuptools" +version = "78.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a9/5a/0db4da3bc908df06e5efae42b44e75c81dd52716e10192ff36d0c1c8e379/setuptools-78.1.0.tar.gz", hash = "sha256:18fd474d4a82a5f83dac888df697af65afa82dec7323d09c3e37d1f14288da54", size = 1367827 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/21/f43f0a1fa8b06b32812e0975981f4677d28e0f3271601dc88ac5a5b83220/setuptools-78.1.0-py3-none-any.whl", hash = "sha256:3e386e96793c8702ae83d17b853fb93d3e09ef82ec62722e61da5cd22376dcd8", size = 1256108 }, +] + +[[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 } +wheels = [ + { 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 = "snowflake-connector-python" +version = "3.14.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "asn1crypto" }, + { name = "certifi" }, + { name = "cffi" }, + { name = "charset-normalizer" }, + { name = "cryptography" }, + { name = "filelock" }, + { name = "idna" }, + { name = "packaging" }, + { name = "platformdirs" }, + { name = "pyjwt" }, + { name = "pyopenssl" }, + { name = "pytz" }, + { name = "requests" }, + { name = "sortedcontainers" }, + { name = "tomlkit" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/49/f5/f36873ba13a4bc0f673f02d8723862118a61e09633a24682b6c2df3ef9a7/snowflake_connector_python-3.14.0.tar.gz", hash = "sha256:baa10f3f8a2cdbe2be0ff973f2313df684f4d0147db6a4f76f3b311bedc299ed", size = 749507 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/b7/6812673ac0f41757604f6e060a5f2bfb55bfb056f118b984f7979f67f035/snowflake_connector_python-3.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1276f5eb148c3eb11c3c50b4bc040d1452ec3f86b7bda44e9992d8e1b8378a81", size = 963237 }, + { url = "https://files.pythonhosted.org/packages/8d/6b/4172f5a12cc68610e8e39b0596c45b1763fc16bc7177dc31bd1472c0ec21/snowflake_connector_python-3.14.0-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:9647a4247e5b05ef7605cbd848d6e441f418500af728f82a176a11bf2bbce88a", size = 974734 }, + { url = "https://files.pythonhosted.org/packages/bb/8b/0415b5149fe9812a4a821835d54d879d81b4a1cc668a54dcf8696d8271f6/snowflake_connector_python-3.14.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf057c86f9cdd101da0832f75c95ed762077f0e66d6b1e835f99b1850ea222d7", size = 2539705 }, + { url = "https://files.pythonhosted.org/packages/83/8b/08d1862a3893882324872a3b50277f18f2c89c6726ce5c7393a96f274dd8/snowflake_connector_python-3.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74eeedaf3c9275f6d56336ac3d1d19522ae69db11c88a6a4866ec66e51fd3ed1", size = 2563526 }, + { url = "https://files.pythonhosted.org/packages/b3/a2/3f59e5bb994de797b980d39ea0c4ce30f95efcd11ca3f3b9d72115c2c3e5/snowflake_connector_python-3.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:1224d2b33ce6f42d99bb01aaf4ad585a72cf9de53334dd849fecfaca22880560", size = 922618 }, +] + +[package.optional-dependencies] +secure-local-storage = [ + { name = "keyring" }, +] + +[[package]] +name = "sortedcontainers" +version = "2.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e8/c4/ba2f8066cceb6f23394729afe52f3bf7adec04bf9ed2c820b39e19299111/sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", size = 30594 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/32/46/9cb0e58b2deb7f82b84065f37f3bffeb12413f947f9388e4cac22c4621ce/sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0", size = 29575 }, +] + +[[package]] +name = "sqlparse" +version = "0.5.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e5/40/edede8dd6977b0d3da179a342c198ed100dd2aba4be081861ee5911e4da4/sqlparse-0.5.3.tar.gz", hash = "sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272", size = 84999 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a9/5c/bfd6bd0bf979426d405cc6e71eceb8701b148b16c21d2dc3c261efc61c7b/sqlparse-0.5.3-py3-none-any.whl", hash = "sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca", size = 44415 }, +] + +[[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 } +wheels = [ + { 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.13.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/09/a439bec5888f00a54b8b9f05fa94d7f901d6735ef4e55dcec9bc37b5d8fa/tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79", size = 192885 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/b6/a447b5e4ec71e13871be01ba81f5dfc9d0af7e473da256ff46bc0e24026f/tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde", size = 37955 }, +] + +[[package]] +name = "tox" +version = "4.25.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cachetools" }, + { name = "chardet" }, + { name = "colorama" }, + { name = "filelock" }, + { name = "packaging" }, + { name = "platformdirs" }, + { name = "pluggy" }, + { name = "pyproject-api" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fe/87/692478f0a194f1cad64803692642bd88c12c5b64eee16bf178e4a32e979c/tox-4.25.0.tar.gz", hash = "sha256:dd67f030317b80722cf52b246ff42aafd3ed27ddf331c415612d084304cf5e52", size = 196255 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/38/33348de6fc4b1afb3d76d8485c8aecbdabcfb3af8da53d40c792332e2b37/tox-4.25.0-py3-none-any.whl", hash = "sha256:4dfdc7ba2cc6fdc6688dde1b21e7b46ff6c41795fb54586c91a3533317b5255c", size = 172420 }, +] + +[[package]] +name = "trino" +version = "0.334.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "lz4" }, + { name = "python-dateutil" }, + { name = "pytz" }, + { name = "requests" }, + { name = "tzlocal" }, + { name = "zstandard" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/27/31/ed7aa87ae8413a5a1d48bbdb671a852ec17b84b5df0b8e44518ae8a0c2ca/trino-0.334.0.tar.gz", hash = "sha256:dcb0e6dcd5218de373ba27a26d4ad5d48971cc1ceb9597e957e1a17373aac153", size = 55322 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ce/8d/28ae260fa7489e45b62eb212617c1b9d5ee2a4c87b92a41f9a5e1fd2aa24/trino-0.334.0-py3-none-any.whl", hash = "sha256:2c424a1cbe8dd196fa3862af2040aa8322073400848672773d00bbdf913be154", size = 57840 }, +] + +[[package]] +name = "typing-extensions" +version = "4.13.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0e/3e/b00a62db91a83fff600de219b6ea9908e6918664899a2d85db222f4fbf19/typing_extensions-4.13.0.tar.gz", hash = "sha256:0a4ac55a5820789d87e297727d229866c9650f6521b64206413c4fbada24d95b", size = 106520 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/86/39b65d676ec5732de17b7e3c476e45bb80ec64eb50737a8dce1a4178aba1/typing_extensions-4.13.0-py3-none-any.whl", hash = "sha256:c8dd92cc0d6425a97c18fbb9d1954e5ff92c1ca881a309c45f06ebc0b79058e5", size = 45683 }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/82/5c/e6082df02e215b846b4b8c0b887a64d7d08ffaba30605502639d44c06b82/typing_inspection-0.4.0.tar.gz", hash = "sha256:9765c87de36671694a67904bf2c96e395be9c6439bb6c87b5142569dcdd65122", size = 76222 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/31/08/aa4fdfb71f7de5176385bd9e90852eaf6b5d622735020ad600f2bab54385/typing_inspection-0.4.0-py3-none-any.whl", hash = "sha256:50e72559fcd2a6367a19f7a7e610e6afcb9fac940c650290eed893d61386832f", size = 14125 }, +] + +[[package]] +name = "tzdata" +version = "2025.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839 }, +] + +[[package]] +name = "tzlocal" +version = "5.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "tzdata", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/8b/2e/c14812d3d4d9cd1773c6be938f89e5735a1f11a9f184ac3639b93cef35d5/tzlocal-5.3.1.tar.gz", hash = "sha256:cceffc7edecefea1f595541dbd6e990cb1ea3d19bf01b2809f362a03dd7921fd", size = 30761 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-py3-none-any.whl", hash = "sha256:eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d", size = 18026 }, +] + +[[package]] +name = "urllib3" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/63/e53da845320b757bf29ef6a9062f5c669fe997973f966045cb019c3f4b66/urllib3-2.3.0.tar.gz", hash = "sha256:f8c5449b3cf0861679ce7e0503c7b44b5ec981bec0d1d3795a07f1ba96f0204d", size = 307268 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c8/19/4ec628951a74043532ca2cf5d97b7b14863931476d117c471e8e2b1eb39f/urllib3-2.3.0-py3-none-any.whl", hash = "sha256:1cee9ad369867bfdbbb48b7dd50374c0967a0bb7710050facf0dd6911440e3df", size = 128369 }, +] + +[[package]] +name = "virtualenv" +version = "20.30.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/38/e0/633e369b91bbc664df47dcb5454b6c7cf441e8f5b9d0c250ce9f0546401e/virtualenv-20.30.0.tar.gz", hash = "sha256:800863162bcaa5450a6e4d721049730e7f2dae07720e0902b0e4040bd6f9ada8", size = 4346945 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/ed/3cfeb48175f0671ec430ede81f628f9fb2b1084c9064ca67ebe8c0ed6a05/virtualenv-20.30.0-py3-none-any.whl", hash = "sha256:e34302959180fca3af42d1800df014b35019490b119eba981af27f2fa486e5d6", size = 4329461 }, +] + +[[package]] +name = "zipp" +version = "3.21.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/3f/50/bad581df71744867e9468ebd0bcd6505de3b275e06f202c2cb016e3ff56f/zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4", size = 24545 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/1a/7e4798e9339adc931158c9d69ecc34f5e6791489d469f5e50ec15e35f458/zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931", size = 9630 }, +] + +[[package]] +name = "zstandard" +version = "0.23.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation == 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ed/f6/2ac0287b442160a89d726b17a9184a4c615bb5237db763791a7fd16d9df1/zstandard-0.23.0.tar.gz", hash = "sha256:b2d8c62d08e7255f68f7a740bae85b3c9b8e5466baa9cbf7f57f1cde0ac6bc09", size = 681701 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7b/83/f23338c963bd9de687d47bf32efe9fd30164e722ba27fb59df33e6b1719b/zstandard-0.23.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b4567955a6bc1b20e9c31612e615af6b53733491aeaa19a6b3b37f3b65477094", size = 788713 }, + { url = "https://files.pythonhosted.org/packages/5b/b3/1a028f6750fd9227ee0b937a278a434ab7f7fdc3066c3173f64366fe2466/zstandard-0.23.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1e172f57cd78c20f13a3415cc8dfe24bf388614324d25539146594c16d78fcc8", size = 633459 }, + { url = "https://files.pythonhosted.org/packages/26/af/36d89aae0c1f95a0a98e50711bc5d92c144939efc1f81a2fcd3e78d7f4c1/zstandard-0.23.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0e166f698c5a3e914947388c162be2583e0c638a4703fc6a543e23a88dea3c1", size = 4945707 }, + { url = "https://files.pythonhosted.org/packages/cd/2e/2051f5c772f4dfc0aae3741d5fc72c3dcfe3aaeb461cc231668a4db1ce14/zstandard-0.23.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12a289832e520c6bd4dcaad68e944b86da3bad0d339ef7989fb7e88f92e96072", size = 5306545 }, + { url = "https://files.pythonhosted.org/packages/0a/9e/a11c97b087f89cab030fa71206963090d2fecd8eb83e67bb8f3ffb84c024/zstandard-0.23.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d50d31bfedd53a928fed6707b15a8dbeef011bb6366297cc435accc888b27c20", size = 5337533 }, + { url = "https://files.pythonhosted.org/packages/fc/79/edeb217c57fe1bf16d890aa91a1c2c96b28c07b46afed54a5dcf310c3f6f/zstandard-0.23.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72c68dda124a1a138340fb62fa21b9bf4848437d9ca60bd35db36f2d3345f373", size = 5436510 }, + { url = "https://files.pythonhosted.org/packages/81/4f/c21383d97cb7a422ddf1ae824b53ce4b51063d0eeb2afa757eb40804a8ef/zstandard-0.23.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:53dd9d5e3d29f95acd5de6802e909ada8d8d8cfa37a3ac64836f3bc4bc5512db", size = 4859973 }, + { url = "https://files.pythonhosted.org/packages/ab/15/08d22e87753304405ccac8be2493a495f529edd81d39a0870621462276ef/zstandard-0.23.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6a41c120c3dbc0d81a8e8adc73312d668cd34acd7725f036992b1b72d22c1772", size = 4936968 }, + { url = "https://files.pythonhosted.org/packages/eb/fa/f3670a597949fe7dcf38119a39f7da49a8a84a6f0b1a2e46b2f71a0ab83f/zstandard-0.23.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:40b33d93c6eddf02d2c19f5773196068d875c41ca25730e8288e9b672897c105", size = 5467179 }, + { url = "https://files.pythonhosted.org/packages/4e/a9/dad2ab22020211e380adc477a1dbf9f109b1f8d94c614944843e20dc2a99/zstandard-0.23.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:9206649ec587e6b02bd124fb7799b86cddec350f6f6c14bc82a2b70183e708ba", size = 4848577 }, + { url = "https://files.pythonhosted.org/packages/08/03/dd28b4484b0770f1e23478413e01bee476ae8227bbc81561f9c329e12564/zstandard-0.23.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76e79bc28a65f467e0409098fa2c4376931fd3207fbeb6b956c7c476d53746dd", size = 4693899 }, + { url = "https://files.pythonhosted.org/packages/2b/64/3da7497eb635d025841e958bcd66a86117ae320c3b14b0ae86e9e8627518/zstandard-0.23.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:66b689c107857eceabf2cf3d3fc699c3c0fe8ccd18df2219d978c0283e4c508a", size = 5199964 }, + { url = "https://files.pythonhosted.org/packages/43/a4/d82decbab158a0e8a6ebb7fc98bc4d903266bce85b6e9aaedea1d288338c/zstandard-0.23.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9c236e635582742fee16603042553d276cca506e824fa2e6489db04039521e90", size = 5655398 }, + { url = "https://files.pythonhosted.org/packages/f2/61/ac78a1263bc83a5cf29e7458b77a568eda5a8f81980691bbc6eb6a0d45cc/zstandard-0.23.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a8fffdbd9d1408006baaf02f1068d7dd1f016c6bcb7538682622c556e7b68e35", size = 5191313 }, + { url = "https://files.pythonhosted.org/packages/e7/54/967c478314e16af5baf849b6ee9d6ea724ae5b100eb506011f045d3d4e16/zstandard-0.23.0-cp312-cp312-win32.whl", hash = "sha256:dc1d33abb8a0d754ea4763bad944fd965d3d95b5baef6b121c0c9013eaf1907d", size = 430877 }, + { url = "https://files.pythonhosted.org/packages/75/37/872d74bd7739639c4553bf94c84af7d54d8211b626b352bc57f0fd8d1e3f/zstandard-0.23.0-cp312-cp312-win_amd64.whl", hash = "sha256:64585e1dba664dc67c7cdabd56c1e5685233fbb1fc1966cfba2a340ec0dfff7b", size = 495595 }, + { url = "https://files.pythonhosted.org/packages/80/f1/8386f3f7c10261fe85fbc2c012fdb3d4db793b921c9abcc995d8da1b7a80/zstandard-0.23.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:576856e8594e6649aee06ddbfc738fec6a834f7c85bf7cadd1c53d4a58186ef9", size = 788975 }, + { url = "https://files.pythonhosted.org/packages/16/e8/cbf01077550b3e5dc86089035ff8f6fbbb312bc0983757c2d1117ebba242/zstandard-0.23.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:38302b78a850ff82656beaddeb0bb989a0322a8bbb1bf1ab10c17506681d772a", size = 633448 }, + { url = "https://files.pythonhosted.org/packages/06/27/4a1b4c267c29a464a161aeb2589aff212b4db653a1d96bffe3598f3f0d22/zstandard-0.23.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2240ddc86b74966c34554c49d00eaafa8200a18d3a5b6ffbf7da63b11d74ee2", size = 4945269 }, + { url = "https://files.pythonhosted.org/packages/7c/64/d99261cc57afd9ae65b707e38045ed8269fbdae73544fd2e4a4d50d0ed83/zstandard-0.23.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2ef230a8fd217a2015bc91b74f6b3b7d6522ba48be29ad4ea0ca3a3775bf7dd5", size = 5306228 }, + { url = "https://files.pythonhosted.org/packages/7a/cf/27b74c6f22541f0263016a0fd6369b1b7818941de639215c84e4e94b2a1c/zstandard-0.23.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:774d45b1fac1461f48698a9d4b5fa19a69d47ece02fa469825b442263f04021f", size = 5336891 }, + { url = "https://files.pythonhosted.org/packages/fa/18/89ac62eac46b69948bf35fcd90d37103f38722968e2981f752d69081ec4d/zstandard-0.23.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f77fa49079891a4aab203d0b1744acc85577ed16d767b52fc089d83faf8d8ed", size = 5436310 }, + { url = "https://files.pythonhosted.org/packages/a8/a8/5ca5328ee568a873f5118d5b5f70d1f36c6387716efe2e369010289a5738/zstandard-0.23.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac184f87ff521f4840e6ea0b10c0ec90c6b1dcd0bad2f1e4a9a1b4fa177982ea", size = 4859912 }, + { url = "https://files.pythonhosted.org/packages/ea/ca/3781059c95fd0868658b1cf0440edd832b942f84ae60685d0cfdb808bca1/zstandard-0.23.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:c363b53e257246a954ebc7c488304b5592b9c53fbe74d03bc1c64dda153fb847", size = 4936946 }, + { url = "https://files.pythonhosted.org/packages/ce/11/41a58986f809532742c2b832c53b74ba0e0a5dae7e8ab4642bf5876f35de/zstandard-0.23.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e7792606d606c8df5277c32ccb58f29b9b8603bf83b48639b7aedf6df4fe8171", size = 5466994 }, + { url = "https://files.pythonhosted.org/packages/83/e3/97d84fe95edd38d7053af05159465d298c8b20cebe9ccb3d26783faa9094/zstandard-0.23.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a0817825b900fcd43ac5d05b8b3079937073d2b1ff9cf89427590718b70dd840", size = 4848681 }, + { url = "https://files.pythonhosted.org/packages/6e/99/cb1e63e931de15c88af26085e3f2d9af9ce53ccafac73b6e48418fd5a6e6/zstandard-0.23.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9da6bc32faac9a293ddfdcb9108d4b20416219461e4ec64dfea8383cac186690", size = 4694239 }, + { url = "https://files.pythonhosted.org/packages/ab/50/b1e703016eebbc6501fc92f34db7b1c68e54e567ef39e6e59cf5fb6f2ec0/zstandard-0.23.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fd7699e8fd9969f455ef2926221e0233f81a2542921471382e77a9e2f2b57f4b", size = 5200149 }, + { url = "https://files.pythonhosted.org/packages/aa/e0/932388630aaba70197c78bdb10cce2c91fae01a7e553b76ce85471aec690/zstandard-0.23.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d477ed829077cd945b01fc3115edd132c47e6540ddcd96ca169facff28173057", size = 5655392 }, + { url = "https://files.pythonhosted.org/packages/02/90/2633473864f67a15526324b007a9f96c96f56d5f32ef2a56cc12f9548723/zstandard-0.23.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa6ce8b52c5987b3e34d5674b0ab529a4602b632ebab0a93b07bfb4dfc8f8a33", size = 5191299 }, + { url = "https://files.pythonhosted.org/packages/b0/4c/315ca5c32da7e2dc3455f3b2caee5c8c2246074a61aac6ec3378a97b7136/zstandard-0.23.0-cp313-cp313-win32.whl", hash = "sha256:a9b07268d0c3ca5c170a385a0ab9fb7fdd9f5fd866be004c4ea39e44edce47dd", size = 430862 }, + { url = "https://files.pythonhosted.org/packages/a2/bf/c6aaba098e2d04781e8f4f7c0ba3c7aa73d00e4c436bcc0cf059a66691d1/zstandard-0.23.0-cp313-cp313-win_amd64.whl", hash = "sha256:f3513916e8c645d0610815c257cbfd3242adfd5c4cfa78be514e5a3ebb42a41b", size = 495578 }, +]