dataplat 0.1.0__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dataplat-0.2.0/.github/workflows/ci.yml +48 -0
- dataplat-0.2.0/.github/workflows/release.yml +119 -0
- dataplat-0.2.0/CHANGELOG.md +123 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/PKG-INFO +28 -4
- {dataplat-0.1.0 → dataplat-0.2.0}/README.md +25 -2
- dataplat-0.2.0/dataplat/__init__.py +27 -0
- dataplat-0.2.0/dataplat/cli/_lazy.py +108 -0
- dataplat-0.2.0/dataplat/cli/_missing.py +133 -0
- dataplat-0.2.0/dataplat/cli/_options.py +29 -0
- dataplat-0.2.0/dataplat/cli/_prompt.py +66 -0
- dataplat-0.2.0/dataplat/cli/_render.py +56 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/bi/superset.py +36 -38
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/ci/github/runner.py +42 -18
- dataplat-0.2.0/dataplat/cli/cloud/aws/_common.py +132 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/cloud/aws/rds.py +21 -23
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/cloud/aws/redshift.py +18 -14
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/cloud/aws/secrets.py +144 -145
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/config.py +109 -37
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/db/__init__.py +25 -31
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/db/_common.py +12 -6
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/db/_report.py +15 -1
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/db/dbt_orphans.py +160 -152
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/db/describe.py +86 -101
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/db/long_queries.py +24 -32
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/db/role.py +87 -43
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/db/role_create.py +109 -78
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/db/role_drop.py +65 -49
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/db/role_list.py +21 -9
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/db/top_tables.py +27 -34
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/ingest/airbyte/_common.py +5 -2
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/ingest/airbyte/_cursor.py +78 -33
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/ingest/airbyte/_resource.py +24 -20
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/ingest/airbyte/connections.py +195 -130
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/ingest/airbyte/definitions.py +36 -19
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/ingest/airbyte/jobs.py +24 -16
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/ingest/airbyte/tags.py +13 -8
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/ingest/airbyte/templates.py +42 -15
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/ingest/airbyte/tui.py +21 -12
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/ingest/airbyte/workspaces.py +21 -11
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/open.py +6 -1
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/status.py +40 -17
- dataplat-0.2.0/dataplat/core/deps.py +227 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/core/envrc.py +80 -14
- dataplat-0.2.0/dataplat/core/registry.py +110 -0
- dataplat-0.2.0/dataplat/main.py +77 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/services/airbyte/client.py +7 -5
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/services/airbyte/connections.py +20 -9
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/services/airbyte/definitions.py +13 -4
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/services/airbyte/destinations.py +1 -3
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/services/airbyte/tags.py +7 -3
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/services/airbyte/workspaces.py +3 -1
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/services/aws/auth.py +3 -1
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/services/db/describe.py +64 -27
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/services/db/long_queries.py +10 -28
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/services/db/orphans.py +4 -6
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/services/db/role.py +168 -96
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/services/db/role_admin.py +63 -37
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/services/db/role_dialects.py +104 -59
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/services/superset/client.py +12 -4
- {dataplat-0.1.0 → dataplat-0.2.0}/pyproject.toml +15 -5
- dataplat-0.2.0/tests/cli/test_airbyte_commands.py +1554 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/tests/cli/test_airbyte_cursor_logic.py +103 -54
- dataplat-0.2.0/tests/cli/test_airbyte_guards.py +310 -0
- dataplat-0.2.0/tests/cli/test_airbyte_tui.py +129 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/tests/cli/test_aws_secrets.py +272 -9
- dataplat-0.2.0/tests/cli/test_aws_secrets_write.py +336 -0
- dataplat-0.2.0/tests/cli/test_cli_smoke.py +495 -0
- dataplat-0.2.0/tests/cli/test_config.py +279 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/tests/cli/test_db_common.py +55 -1
- {dataplat-0.1.0 → dataplat-0.2.0}/tests/cli/test_db_long_queries.py +54 -0
- dataplat-0.2.0/tests/cli/test_db_query.py +199 -0
- dataplat-0.2.0/tests/cli/test_dbt_orphans.py +763 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/tests/cli/test_describe.py +277 -21
- {dataplat-0.1.0 → dataplat-0.2.0}/tests/cli/test_github_runner.py +290 -7
- dataplat-0.2.0/tests/cli/test_missing_deps.py +277 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/tests/cli/test_open.py +15 -0
- dataplat-0.2.0/tests/cli/test_prompt.py +149 -0
- dataplat-0.2.0/tests/cli/test_rds.py +297 -0
- dataplat-0.2.0/tests/cli/test_redshift.py +173 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/tests/cli/test_regression.py +4 -4
- dataplat-0.2.0/tests/cli/test_render.py +95 -0
- dataplat-0.2.0/tests/cli/test_role.py +660 -0
- dataplat-0.2.0/tests/cli/test_role_create.py +457 -0
- dataplat-0.2.0/tests/cli/test_role_drop.py +299 -0
- dataplat-0.2.0/tests/cli/test_status.py +798 -0
- dataplat-0.2.0/tests/cli/test_superset.py +473 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/tests/cli/test_top_tables.py +73 -18
- {dataplat-0.1.0 → dataplat-0.2.0}/tests/conftest.py +20 -5
- dataplat-0.2.0/tests/core/test_deps.py +299 -0
- dataplat-0.2.0/tests/core/test_envrc.py +265 -0
- dataplat-0.2.0/tests/core/test_registry.py +96 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/tests/services/airbyte/test_client.py +5 -1
- {dataplat-0.1.0 → dataplat-0.2.0}/tests/services/airbyte/test_connections.py +7 -2
- {dataplat-0.1.0 → dataplat-0.2.0}/tests/services/airbyte/test_definitions.py +11 -3
- {dataplat-0.1.0 → dataplat-0.2.0}/tests/services/airbyte/test_destinations.py +13 -2
- {dataplat-0.1.0 → dataplat-0.2.0}/tests/services/airbyte/test_sources.py +13 -2
- {dataplat-0.1.0 → dataplat-0.2.0}/tests/services/airbyte/test_workspaces.py +1 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/tests/services/aws/test_auth.py +6 -2
- {dataplat-0.1.0 → dataplat-0.2.0}/tests/services/db/test_connection.py +26 -9
- {dataplat-0.1.0 → dataplat-0.2.0}/tests/services/db/test_describe.py +283 -125
- {dataplat-0.1.0 → dataplat-0.2.0}/tests/services/db/test_long_queries.py +1 -1
- {dataplat-0.1.0 → dataplat-0.2.0}/tests/services/db/test_orphans.py +20 -37
- {dataplat-0.1.0 → dataplat-0.2.0}/tests/services/db/test_role.py +180 -141
- {dataplat-0.1.0 → dataplat-0.2.0}/tests/services/db/test_role_admin.py +94 -42
- {dataplat-0.1.0 → dataplat-0.2.0}/tests/services/db/test_role_dialects.py +39 -28
- {dataplat-0.1.0 → dataplat-0.2.0}/tests/services/db/test_top_tables.py +13 -14
- {dataplat-0.1.0 → dataplat-0.2.0}/tests/services/superset/test_client.py +11 -3
- {dataplat-0.1.0 → dataplat-0.2.0}/uv.lock +34 -2
- dataplat-0.1.0/.github/workflows/ci.yml +0 -31
- dataplat-0.1.0/.github/workflows/release.yml +0 -58
- dataplat-0.1.0/CHANGELOG.md +0 -17
- dataplat-0.1.0/dataplat/__init__.py +0 -3
- dataplat-0.1.0/dataplat/cli/_missing.py +0 -108
- dataplat-0.1.0/dataplat/cli/cloud/aws/_common.py +0 -77
- dataplat-0.1.0/dataplat/core/deps.py +0 -138
- dataplat-0.1.0/dataplat/main.py +0 -87
- dataplat-0.1.0/tests/cli/test_airbyte_commands.py +0 -555
- dataplat-0.1.0/tests/cli/test_airbyte_guards.py +0 -127
- dataplat-0.1.0/tests/cli/test_cli_smoke.py +0 -267
- dataplat-0.1.0/tests/cli/test_config.py +0 -152
- dataplat-0.1.0/tests/cli/test_db_query.py +0 -70
- dataplat-0.1.0/tests/cli/test_dbt_orphans.py +0 -102
- dataplat-0.1.0/tests/cli/test_missing_deps.py +0 -107
- dataplat-0.1.0/tests/cli/test_redshift.py +0 -17
- dataplat-0.1.0/tests/cli/test_role.py +0 -389
- dataplat-0.1.0/tests/cli/test_role_create.py +0 -213
- dataplat-0.1.0/tests/cli/test_status.py +0 -138
- dataplat-0.1.0/tests/core/test_deps.py +0 -117
- dataplat-0.1.0/tests/core/test_envrc.py +0 -134
- {dataplat-0.1.0 → dataplat-0.2.0}/.gitignore +0 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/.python-version +0 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/LICENSE +0 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/__init__.py +0 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/bi/__init__.py +0 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/bi/app.py +0 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/ci/__init__.py +0 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/ci/app.py +0 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/ci/github/__init__.py +0 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/ci/github/app.py +0 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/cloud/__init__.py +0 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/cloud/app.py +0 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/cloud/aws/__init__.py +0 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/cloud/aws/app.py +0 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/ingest/__init__.py +0 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/ingest/airbyte/__init__.py +0 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/ingest/airbyte/app.py +0 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/ingest/airbyte/destinations.py +0 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/ingest/airbyte/enums.py +0 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/ingest/airbyte/sources.py +0 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/cli/ingest/app.py +0 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/core/__init__.py +0 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/core/errors.py +0 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/services/__init__.py +0 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/services/airbyte/__init__.py +0 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/services/airbyte/_resource.py +0 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/services/airbyte/jobs.py +0 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/services/airbyte/sources.py +0 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/services/aws/__init__.py +0 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/services/db/__init__.py +0 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/services/db/connection.py +0 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/services/db/targets.py +0 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/services/db/top_tables.py +0 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/dataplat/services/superset/__init__.py +0 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/tests/__init__.py +0 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/tests/cli/__init__.py +0 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/tests/core/__init__.py +0 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/tests/services/__init__.py +0 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/tests/services/airbyte/__init__.py +0 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/tests/services/airbyte/test_jobs.py +0 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/tests/services/aws/__init__.py +0 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/tests/services/db/__init__.py +0 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/tests/services/db/test_targets.py +0 -0
- {dataplat-0.1.0 → dataplat-0.2.0}/tests/services/superset/__init__.py +0 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: ["**"]
|
|
6
|
+
# A tag push matches no branch, so without this a release tag would run no
|
|
7
|
+
# checks at all before release.yml publishes it.
|
|
8
|
+
tags: ["[0-9]*.[0-9]*.[0-9]*"]
|
|
9
|
+
pull_request:
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
checks:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
strategy:
|
|
15
|
+
# The floor and the ceiling break for different reasons; report both
|
|
16
|
+
# rather than hiding one behind the other.
|
|
17
|
+
fail-fast: false
|
|
18
|
+
matrix:
|
|
19
|
+
# Lowest version requires-python allows, plus the dev pin.
|
|
20
|
+
python-version: ["3.12", "3.13"]
|
|
21
|
+
env:
|
|
22
|
+
# Overrides .python-version (3.13). Without it every uv command in the
|
|
23
|
+
# 3.12 leg would still pick the pin and we would test 3.13 twice.
|
|
24
|
+
UV_PYTHON: ${{ matrix.python-version }}
|
|
25
|
+
steps:
|
|
26
|
+
- name: Checkout
|
|
27
|
+
uses: actions/checkout@v4
|
|
28
|
+
|
|
29
|
+
- name: Setup uv
|
|
30
|
+
uses: astral-sh/setup-uv@v5
|
|
31
|
+
|
|
32
|
+
- name: Setup Python
|
|
33
|
+
run: uv python install ${{ matrix.python-version }}
|
|
34
|
+
|
|
35
|
+
- name: Install dependencies
|
|
36
|
+
run: uv sync --group dev --all-extras
|
|
37
|
+
|
|
38
|
+
- name: Ruff
|
|
39
|
+
run: uv run ruff check .
|
|
40
|
+
|
|
41
|
+
- name: Ruff format
|
|
42
|
+
run: uv run ruff format --check .
|
|
43
|
+
|
|
44
|
+
- name: MyPy
|
|
45
|
+
run: uv run mypy dataplat
|
|
46
|
+
|
|
47
|
+
- name: Pytest
|
|
48
|
+
run: uv run pytest
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["[0-9]*.[0-9]*.[0-9]*"]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
# Nothing downstream re-checks the tree, so this job is the only thing
|
|
9
|
+
# standing between an untested commit and PyPI. Publishing is irreversible:
|
|
10
|
+
# a version can be yanked but never replaced.
|
|
11
|
+
verify:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
strategy:
|
|
14
|
+
fail-fast: false
|
|
15
|
+
matrix:
|
|
16
|
+
# Must mirror ci.yml: the wheel advertises >=3.12, so the floor has to
|
|
17
|
+
# pass before we publish, not just the dev pin.
|
|
18
|
+
python-version: ["3.12", "3.13"]
|
|
19
|
+
env:
|
|
20
|
+
UV_PYTHON: ${{ matrix.python-version }}
|
|
21
|
+
steps:
|
|
22
|
+
- name: Checkout
|
|
23
|
+
uses: actions/checkout@v4
|
|
24
|
+
|
|
25
|
+
- name: Setup uv
|
|
26
|
+
uses: astral-sh/setup-uv@v5
|
|
27
|
+
|
|
28
|
+
- name: Setup Python
|
|
29
|
+
run: uv python install ${{ matrix.python-version }}
|
|
30
|
+
|
|
31
|
+
# Before the expensive sync: a mismatch means the tag would either
|
|
32
|
+
# publish the wrong version number or die at upload against an already
|
|
33
|
+
# occupied filename.
|
|
34
|
+
- name: Assert tag matches project version
|
|
35
|
+
run: |
|
|
36
|
+
uv run --no-project python - <<'PY'
|
|
37
|
+
import os
|
|
38
|
+
import sys
|
|
39
|
+
import tomllib
|
|
40
|
+
|
|
41
|
+
tag = os.environ["GITHUB_REF_NAME"]
|
|
42
|
+
with open("pyproject.toml", "rb") as handle:
|
|
43
|
+
version = tomllib.load(handle)["project"]["version"]
|
|
44
|
+
if tag != version:
|
|
45
|
+
sys.exit(
|
|
46
|
+
f"Tag '{tag}' does not match [project].version '{version}' in "
|
|
47
|
+
"pyproject.toml. Bump the version, commit, and re-tag; "
|
|
48
|
+
"refusing to publish a mismatched artifact."
|
|
49
|
+
)
|
|
50
|
+
print(f"Tag '{tag}' matches [project].version.")
|
|
51
|
+
PY
|
|
52
|
+
|
|
53
|
+
- name: Install dependencies
|
|
54
|
+
run: uv sync --group dev --all-extras
|
|
55
|
+
|
|
56
|
+
- name: Ruff
|
|
57
|
+
run: uv run ruff check .
|
|
58
|
+
|
|
59
|
+
- name: Ruff format
|
|
60
|
+
run: uv run ruff format --check .
|
|
61
|
+
|
|
62
|
+
- name: MyPy
|
|
63
|
+
run: uv run mypy dataplat
|
|
64
|
+
|
|
65
|
+
- name: Pytest
|
|
66
|
+
run: uv run pytest
|
|
67
|
+
|
|
68
|
+
build:
|
|
69
|
+
needs: verify
|
|
70
|
+
runs-on: ubuntu-latest
|
|
71
|
+
steps:
|
|
72
|
+
- name: Checkout
|
|
73
|
+
uses: actions/checkout@v4
|
|
74
|
+
|
|
75
|
+
- name: Setup uv
|
|
76
|
+
uses: astral-sh/setup-uv@v5
|
|
77
|
+
|
|
78
|
+
- name: Build sdist and wheel
|
|
79
|
+
run: uv build
|
|
80
|
+
|
|
81
|
+
- name: Upload artifacts
|
|
82
|
+
uses: actions/upload-artifact@v4
|
|
83
|
+
with:
|
|
84
|
+
name: dist
|
|
85
|
+
path: dist/
|
|
86
|
+
|
|
87
|
+
publish:
|
|
88
|
+
needs: build
|
|
89
|
+
runs-on: ubuntu-latest
|
|
90
|
+
environment: pypi
|
|
91
|
+
permissions:
|
|
92
|
+
id-token: write # PyPI Trusted Publishing (OIDC)
|
|
93
|
+
steps:
|
|
94
|
+
- name: Download artifacts
|
|
95
|
+
uses: actions/download-artifact@v4
|
|
96
|
+
with:
|
|
97
|
+
name: dist
|
|
98
|
+
path: dist/
|
|
99
|
+
|
|
100
|
+
- name: Publish to PyPI
|
|
101
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
102
|
+
|
|
103
|
+
github-release:
|
|
104
|
+
needs: publish
|
|
105
|
+
runs-on: ubuntu-latest
|
|
106
|
+
permissions:
|
|
107
|
+
contents: write
|
|
108
|
+
steps:
|
|
109
|
+
- name: Download artifacts
|
|
110
|
+
uses: actions/download-artifact@v4
|
|
111
|
+
with:
|
|
112
|
+
name: dist
|
|
113
|
+
path: dist/
|
|
114
|
+
|
|
115
|
+
- name: Create GitHub release
|
|
116
|
+
uses: softprops/action-gh-release@v2
|
|
117
|
+
with:
|
|
118
|
+
files: dist/*
|
|
119
|
+
generate_release_notes: true
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- Data containing square brackets no longer breaks output. Rich reads `[...]`
|
|
8
|
+
as markup in every string it renders, so a warehouse value, identifier,
|
|
9
|
+
comment, secret, API payload, or driver message containing `[/x]` aborted the
|
|
10
|
+
command with a `MarkupError` traceback, and one containing a style name like
|
|
11
|
+
`[bold]` was silently swallowed — the output quietly misreported the data.
|
|
12
|
+
Every renderer now emits external values as literal text, table *headers*
|
|
13
|
+
included: `dp ingest airbyte connections list --all-columns` takes its column
|
|
14
|
+
labels straight from Airbyte's JSON keys, and the Textual TUI passes the same
|
|
15
|
+
labels to a `DataTable`, so both had the identical failure.
|
|
16
|
+
|
|
17
|
+
- `dp db dbt-orphans revert` and `purge --older-than` could not find their own
|
|
18
|
+
audit logs if the log directory's path contained a bracket — a home directory
|
|
19
|
+
named `[work]` was enough. The discovery glob interpolated the directory
|
|
20
|
+
without escaping it, so the brackets read as a character class and matched
|
|
21
|
+
nothing: `revert` reported no history, and `purge --older-than` treated every
|
|
22
|
+
object as having no recorded rename.
|
|
23
|
+
|
|
24
|
+
- Installing a missing extra no longer changes anything else about your
|
|
25
|
+
install. It was resolved unpinned, so hitting a stubbed area could upgrade
|
|
26
|
+
`dataplat` itself and then re-exec your command against the new code; and
|
|
27
|
+
because `--force` rebuilds the environment from the spec it is given,
|
|
28
|
+
installing `db` silently removed an `ingest` you already had. The command is
|
|
29
|
+
now pinned to the running version and unions the extras already present.
|
|
30
|
+
|
|
31
|
+
- `dp ci github runner start|stop|status` identified containers with an
|
|
32
|
+
unanchored `docker --filter name=`, which is a substring match: a runner
|
|
33
|
+
named `foo` matched `gha-runner-foobar`, so `status` could report a
|
|
34
|
+
different runner's state as yours. All filters are anchored.
|
|
35
|
+
|
|
36
|
+
- `dp status --json` emitted valid JSON only while nothing needed saying. An
|
|
37
|
+
expired SSO token wrote its notice to stdout, mixed into the document.
|
|
38
|
+
Spinners and notices now go to stderr; stdout carries only the payload.
|
|
39
|
+
|
|
40
|
+
- Pressing TAB inside an area whose extra is missing misbehaved badly. Shell
|
|
41
|
+
completion walks the command tree with click's resilient parsing, which calls
|
|
42
|
+
the same resolution hook the missing-extra stub overrode, so completion ran
|
|
43
|
+
the interactive installer: the offer text landed on the stream the shell
|
|
44
|
+
evaluates (zsh even re-invoked `dp` from the backticks in the message), a
|
|
45
|
+
full traceback went to the terminal, and under bash the install itself
|
|
46
|
+
executed from a keypress. Completion now falls through silently.
|
|
47
|
+
|
|
48
|
+
- Installing an extra inside a virtualenv that has no `pip` — the default for
|
|
49
|
+
`uv venv` — prescribed `python -m pip install` and could only fail, reporting
|
|
50
|
+
just a bare exit code. It now uses `uv pip install --python <env>` when pip is
|
|
51
|
+
absent, and says so plainly when neither installer is reachable.
|
|
52
|
+
|
|
53
|
+
- The `Will run:` line is shell-quoted. It prints a command the user may want
|
|
54
|
+
to run themselves, but the requirement spec's brackets (and now its `==`
|
|
55
|
+
pin) meant pasting it into zsh failed on globbing.
|
|
56
|
+
|
|
57
|
+
### Added
|
|
58
|
+
|
|
59
|
+
- `DP_ENVRC_ALLOW_CWD=0` stops `.envrc` being picked up from the current
|
|
60
|
+
directory. Loading it made every command sensitive to where you stood, with
|
|
61
|
+
no way to see that had happened: `dp config show` and `dp config doctor` now
|
|
62
|
+
name the active file and which candidate produced it, and warn when it came
|
|
63
|
+
from the working directory.
|
|
64
|
+
|
|
65
|
+
- `DP_AWS_PROFILE_ALIASES` now works across the whole `dp cloud aws` group.
|
|
66
|
+
Only `secrets` resolved aliases, so `dp cloud aws rds metrics -p prod` passed
|
|
67
|
+
the literal `prod` to boto3 and failed with a profile-not-found error while
|
|
68
|
+
the same flag worked one command over. `rds list` and `redshift metrics` also
|
|
69
|
+
print the profile they actually used rather than the shorthand you typed.
|
|
70
|
+
|
|
71
|
+
- Python 3.12 support. `requires-python` was 3.13 while the only construct
|
|
72
|
+
above 3.11 in the package is one PEP 695 generic, so 3.12 users hit an
|
|
73
|
+
install failure for nothing. CI now tests the advertised floor as well as
|
|
74
|
+
the dev pin.
|
|
75
|
+
|
|
76
|
+
- The release workflow verifies that the pushed tag matches
|
|
77
|
+
`[project].version` and runs the full check matrix before building. CI also
|
|
78
|
+
runs on tag pushes — a tag matched no branch filter, so releases were
|
|
79
|
+
published without any checks having run.
|
|
80
|
+
|
|
81
|
+
### Changed
|
|
82
|
+
|
|
83
|
+
- Areas mount lazily, cutting `dp --version` from 360 ms to 100 ms. Every
|
|
84
|
+
area's dependencies — psycopg, textual, httpx, boto3 — were imported at
|
|
85
|
+
startup regardless of the command; now nothing heavy loads until the area
|
|
86
|
+
that needs it is actually used. Areas resolve through the registry
|
|
87
|
+
(`dataplat/core/registry.py`) and carry their own dependency contract, so a
|
|
88
|
+
future third-party area is no longer tied to a lookup in a built-in dict.
|
|
89
|
+
|
|
90
|
+
- One confirmation gate covers every destructive command. Three idioms had
|
|
91
|
+
drifted apart and only one told you what to do when stdin was not a TTY;
|
|
92
|
+
non-interactive runs now consistently name the flag that would have worked
|
|
93
|
+
and exit 1 rather than aborting with no explanation.
|
|
94
|
+
|
|
95
|
+
**Behaviour change worth knowing before you upgrade:** piping a confirmation
|
|
96
|
+
into a destructive command no longer authorizes it. `echo y | dp db role drop
|
|
97
|
+
old_user` used to be accepted by the eight gates that had no TTY check, and
|
|
98
|
+
now exits 1 naming `--yes`. Data-destroying work should not proceed on a `y`
|
|
99
|
+
that arrived from a pipe rather than a person, and `--yes` has always been
|
|
100
|
+
the documented scriptable path — but any script relying on the old behaviour
|
|
101
|
+
fails loudly and needs `--yes` instead. Sites that already refused a piped
|
|
102
|
+
answer (`dp db query`'s write guard, the AWS secrets writes) are unchanged.
|
|
103
|
+
|
|
104
|
+
- `line-length = 88` is enforced. It was configured but unchecked, because
|
|
105
|
+
pycodestyle was not among the selected rule sets, and the tree had drifted
|
|
106
|
+
to 164 violations across 71 unformatted files. The repo is now
|
|
107
|
+
`ruff format`-clean and CI keeps it that way.
|
|
108
|
+
|
|
109
|
+
## 0.1.0
|
|
110
|
+
|
|
111
|
+
Initial public release.
|
|
112
|
+
|
|
113
|
+
- Optional per-area dependency extras (`dataplat[db,ingest,bi,cloud,all]`)
|
|
114
|
+
with auto-detection: `dp config sync` installs what your enabled areas
|
|
115
|
+
need, and hitting a stubbed area offers to install its extra and re-run
|
|
116
|
+
your command.
|
|
117
|
+
|
|
118
|
+
- `dp` command with areas per component type: `db`, `ingest` (Airbyte),
|
|
119
|
+
`bi` (Superset), `cloud` (AWS), `ci` (GitHub runners), plus `status`,
|
|
120
|
+
`open`, and `config`.
|
|
121
|
+
- Fully config-driven database targets via `DP_TARGETS` and per-target
|
|
122
|
+
`<NAME>_*` environment variables (Postgres and Redshift engines).
|
|
123
|
+
- `.envrc` loading with a global config link (`dp config init`).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dataplat
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: One command to manage any shape of data platform: databases, ingestion, BI, cloud, and CI.
|
|
5
5
|
Project-URL: Homepage, https://github.com/hanslemm/dataplat
|
|
6
6
|
Project-URL: Repository, https://github.com/hanslemm/dataplat
|
|
@@ -15,11 +15,12 @@ Classifier: Environment :: Console
|
|
|
15
15
|
Classifier: Intended Audience :: Developers
|
|
16
16
|
Classifier: Intended Audience :: System Administrators
|
|
17
17
|
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
19
|
Classifier: Programming Language :: Python :: 3.13
|
|
19
20
|
Classifier: Topic :: Database
|
|
20
21
|
Classifier: Topic :: System :: Systems Administration
|
|
21
22
|
Classifier: Topic :: Utilities
|
|
22
|
-
Requires-Python: >=3.
|
|
23
|
+
Requires-Python: >=3.12
|
|
23
24
|
Requires-Dist: rich>=14.2.0
|
|
24
25
|
Requires-Dist: typer>=0.21.1
|
|
25
26
|
Provides-Extra: all
|
|
@@ -93,6 +94,8 @@ dp
|
|
|
93
94
|
|
|
94
95
|
## Installation
|
|
95
96
|
|
|
97
|
+
Requires Python 3.12 or newer.
|
|
98
|
+
|
|
96
99
|
```bash
|
|
97
100
|
uv tool install "dataplat[all]" # recommended: everything
|
|
98
101
|
# or
|
|
@@ -133,6 +136,11 @@ and re-runs your original command. Non-interactive sessions never install
|
|
|
133
136
|
silently — they print the command and exit instead. `dp config doctor`
|
|
134
137
|
also reports per-area dependency status.
|
|
135
138
|
|
|
139
|
+
Either path only ever *adds* to your install: the command is pinned to the
|
|
140
|
+
`dataplat` version you already run, so installing an extra never upgrades the
|
|
141
|
+
tool underneath you, and it carries your existing extras along, so adding
|
|
142
|
+
`db` cannot drop an `ingest` you already had.
|
|
143
|
+
|
|
136
144
|
## Quick start
|
|
137
145
|
|
|
138
146
|
1. Declare your database targets — any names you like:
|
|
@@ -180,11 +188,21 @@ already set in your shell. Lookup order:
|
|
|
180
188
|
1. `DP_ENVRC_PATH`
|
|
181
189
|
2. `~/.config/dataplat/.envrc` (the global link — set it with `dp config init`)
|
|
182
190
|
3. `.envrc` in the current directory
|
|
191
|
+
4. the `.envrc` beside a development checkout of dataplat itself
|
|
192
|
+
|
|
193
|
+
Candidate 3 makes every command sensitive to where you run it: standing in a
|
|
194
|
+
cloned repo points `dp` at whatever host and credentials that repo's `.envrc`
|
|
195
|
+
exports. `dp config show` and `dp config doctor` always name the active file
|
|
196
|
+
*and* which candidate produced it, and warn when it came from the current
|
|
197
|
+
directory. Set `DP_ENVRC_ALLOW_CWD=0` to drop that candidate entirely and
|
|
198
|
+
rely only on the global link you chose.
|
|
183
199
|
|
|
184
200
|
## Configuration reference
|
|
185
201
|
|
|
186
202
|
| Variable | Purpose |
|
|
187
203
|
| --- | --- |
|
|
204
|
+
| `DP_ENVRC_PATH` | Explicit `.envrc` to load, ahead of every other candidate. |
|
|
205
|
+
| `DP_ENVRC_ALLOW_CWD` | Set to `0` to stop picking up `.envrc` from the current directory. |
|
|
188
206
|
| `DP_TARGETS` | Comma-separated DB target names (e.g. `warehouse,lake`). |
|
|
189
207
|
| `DP_DEFAULT_TARGET` | Target used when `--target` is omitted (default: first of `DP_TARGETS`). |
|
|
190
208
|
| `<NAME>_ENGINE` | `postgresql` (default) or `redshift`, per target. |
|
|
@@ -312,13 +330,19 @@ cd dataplat
|
|
|
312
330
|
uv sync --group dev --all-extras
|
|
313
331
|
uv run pytest
|
|
314
332
|
uv run ruff check .
|
|
333
|
+
uv run ruff format --check .
|
|
315
334
|
uv run mypy dataplat
|
|
316
335
|
```
|
|
317
336
|
|
|
337
|
+
CI runs those four across Python 3.12 and 3.13 — the floor the wheel
|
|
338
|
+
advertises as well as the pinned dev version.
|
|
339
|
+
|
|
318
340
|
## Releasing
|
|
319
341
|
|
|
320
|
-
|
|
321
|
-
|
|
342
|
+
Bump `[project].version`, then tag `X.Y.Z` (bare semver, no `v` prefix) on
|
|
343
|
+
`main`. The release workflow refuses to publish unless the tag matches that
|
|
344
|
+
version, and runs the full check matrix first; only then does GitHub Actions
|
|
345
|
+
build and publish to PyPI via Trusted Publishing. Commits follow
|
|
322
346
|
[Conventional Commits](https://www.conventionalcommits.org/).
|
|
323
347
|
|
|
324
348
|
## License
|
|
@@ -49,6 +49,8 @@ dp
|
|
|
49
49
|
|
|
50
50
|
## Installation
|
|
51
51
|
|
|
52
|
+
Requires Python 3.12 or newer.
|
|
53
|
+
|
|
52
54
|
```bash
|
|
53
55
|
uv tool install "dataplat[all]" # recommended: everything
|
|
54
56
|
# or
|
|
@@ -89,6 +91,11 @@ and re-runs your original command. Non-interactive sessions never install
|
|
|
89
91
|
silently — they print the command and exit instead. `dp config doctor`
|
|
90
92
|
also reports per-area dependency status.
|
|
91
93
|
|
|
94
|
+
Either path only ever *adds* to your install: the command is pinned to the
|
|
95
|
+
`dataplat` version you already run, so installing an extra never upgrades the
|
|
96
|
+
tool underneath you, and it carries your existing extras along, so adding
|
|
97
|
+
`db` cannot drop an `ingest` you already had.
|
|
98
|
+
|
|
92
99
|
## Quick start
|
|
93
100
|
|
|
94
101
|
1. Declare your database targets — any names you like:
|
|
@@ -136,11 +143,21 @@ already set in your shell. Lookup order:
|
|
|
136
143
|
1. `DP_ENVRC_PATH`
|
|
137
144
|
2. `~/.config/dataplat/.envrc` (the global link — set it with `dp config init`)
|
|
138
145
|
3. `.envrc` in the current directory
|
|
146
|
+
4. the `.envrc` beside a development checkout of dataplat itself
|
|
147
|
+
|
|
148
|
+
Candidate 3 makes every command sensitive to where you run it: standing in a
|
|
149
|
+
cloned repo points `dp` at whatever host and credentials that repo's `.envrc`
|
|
150
|
+
exports. `dp config show` and `dp config doctor` always name the active file
|
|
151
|
+
*and* which candidate produced it, and warn when it came from the current
|
|
152
|
+
directory. Set `DP_ENVRC_ALLOW_CWD=0` to drop that candidate entirely and
|
|
153
|
+
rely only on the global link you chose.
|
|
139
154
|
|
|
140
155
|
## Configuration reference
|
|
141
156
|
|
|
142
157
|
| Variable | Purpose |
|
|
143
158
|
| --- | --- |
|
|
159
|
+
| `DP_ENVRC_PATH` | Explicit `.envrc` to load, ahead of every other candidate. |
|
|
160
|
+
| `DP_ENVRC_ALLOW_CWD` | Set to `0` to stop picking up `.envrc` from the current directory. |
|
|
144
161
|
| `DP_TARGETS` | Comma-separated DB target names (e.g. `warehouse,lake`). |
|
|
145
162
|
| `DP_DEFAULT_TARGET` | Target used when `--target` is omitted (default: first of `DP_TARGETS`). |
|
|
146
163
|
| `<NAME>_ENGINE` | `postgresql` (default) or `redshift`, per target. |
|
|
@@ -268,13 +285,19 @@ cd dataplat
|
|
|
268
285
|
uv sync --group dev --all-extras
|
|
269
286
|
uv run pytest
|
|
270
287
|
uv run ruff check .
|
|
288
|
+
uv run ruff format --check .
|
|
271
289
|
uv run mypy dataplat
|
|
272
290
|
```
|
|
273
291
|
|
|
292
|
+
CI runs those four across Python 3.12 and 3.13 — the floor the wheel
|
|
293
|
+
advertises as well as the pinned dev version.
|
|
294
|
+
|
|
274
295
|
## Releasing
|
|
275
296
|
|
|
276
|
-
|
|
277
|
-
|
|
297
|
+
Bump `[project].version`, then tag `X.Y.Z` (bare semver, no `v` prefix) on
|
|
298
|
+
`main`. The release workflow refuses to publish unless the tag matches that
|
|
299
|
+
version, and runs the full check matrix first; only then does GitHub Actions
|
|
300
|
+
build and publish to PyPI via Trusted Publishing. Commits follow
|
|
278
301
|
[Conventional Commits](https://www.conventionalcommits.org/).
|
|
279
302
|
|
|
280
303
|
## License
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""dataplat — one command to manage any shape of data platform."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
__all__ = ["__version__"]
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def __getattr__(name: str) -> Any:
|
|
11
|
+
"""Serve ``__version__`` from the installed distribution, on demand.
|
|
12
|
+
|
|
13
|
+
A literal here would be a second place to edit at release time, and the
|
|
14
|
+
kind that goes stale silently: nothing in the package reads it, so a
|
|
15
|
+
mismatch with ``[project].version`` would never surface. Reading the
|
|
16
|
+
metadata keeps pyproject the only source of truth, and doing it in
|
|
17
|
+
``__getattr__`` (PEP 562) keeps ``importlib.metadata`` off the import path
|
|
18
|
+
of every ``dp`` invocation that never asks.
|
|
19
|
+
"""
|
|
20
|
+
if name == "__version__":
|
|
21
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
return version("dataplat")
|
|
25
|
+
except PackageNotFoundError: # running from a bare source tree
|
|
26
|
+
return "unknown"
|
|
27
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""Mount areas by name; import one only when it is actually run.
|
|
2
|
+
|
|
3
|
+
``main`` used to call :func:`~dataplat.core.registry.load_app` for every area
|
|
4
|
+
at import time, so ``dp --version`` paid for psycopg, httpx and croniter
|
|
5
|
+
(~250 ms of a ~280 ms run) just to print one line. Here an area is mounted as
|
|
6
|
+
an empty placeholder group carrying nothing but the name and help text the
|
|
7
|
+
registry already knows, and :class:`LazyRootGroup` swaps in the real Typer app
|
|
8
|
+
— or the missing-deps stub — when click resolves that area, and only then.
|
|
9
|
+
|
|
10
|
+
Placeholders are mounted through ``add_typer`` rather than injected into the
|
|
11
|
+
click group, so command order, ``--help``, "did you mean" suggestions and
|
|
12
|
+
top-level completion keep coming out of Typer's own machinery instead of a
|
|
13
|
+
reimplementation of it, and nothing else in the CLI has to know that areas are
|
|
14
|
+
lazy. Those paths are all import-free (verified: ``dp --help`` and ``dp <TAB>``
|
|
15
|
+
leave psycopg unimported). Completing *inside* an area — ``dp db <TAB>`` — does
|
|
16
|
+
import it, because the subcommand list it has to offer is the area's own; that
|
|
17
|
+
is the one non-dispatch path that pays, and there is no way around it.
|
|
18
|
+
|
|
19
|
+
No direct click import here either — typer vendors click, so only the
|
|
20
|
+
``TyperGroup`` surface is safe to rely on (see :mod:`dataplat.cli._missing`).
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
from typing import Any
|
|
26
|
+
|
|
27
|
+
import typer
|
|
28
|
+
from typer.core import TyperGroup
|
|
29
|
+
from typer.main import get_group
|
|
30
|
+
|
|
31
|
+
from dataplat.core.deps import ready
|
|
32
|
+
from dataplat.core.registry import AreaMount, area_by_name, load_app, mount_help
|
|
33
|
+
|
|
34
|
+
__all__ = [
|
|
35
|
+
"AreaPlaceholderGroup",
|
|
36
|
+
"LazyRootGroup",
|
|
37
|
+
"area_command",
|
|
38
|
+
"area_placeholder",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class AreaPlaceholderGroup(TyperGroup):
|
|
43
|
+
"""Stands in for an area nobody has imported yet.
|
|
44
|
+
|
|
45
|
+
Deliberately empty: it is only ever asked to describe itself (name, help
|
|
46
|
+
text, hidden), and :meth:`LazyRootGroup.resolve_command` replaces it before
|
|
47
|
+
anything can be invoked through it.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def area_placeholder(mount: AreaMount) -> typer.Typer:
|
|
52
|
+
"""A command-less Typer app holding just ``mount``'s name and help line."""
|
|
53
|
+
return typer.Typer(
|
|
54
|
+
name=mount.name,
|
|
55
|
+
help=mount_help(mount),
|
|
56
|
+
cls=AreaPlaceholderGroup,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def area_command(mount: AreaMount) -> typer.Typer:
|
|
61
|
+
"""``mount``'s real Typer app, or the stub that offers to install its extra.
|
|
62
|
+
|
|
63
|
+
Readiness is decided here rather than at mount time, so an area whose extra
|
|
64
|
+
is missing costs nothing until someone runs it — and it is decided from
|
|
65
|
+
``mount.deps``, so an area that is not one of the built-ins still works.
|
|
66
|
+
"""
|
|
67
|
+
if mount.deps is None or ready(mount.deps):
|
|
68
|
+
return load_app(mount)
|
|
69
|
+
# Imported on this branch only: the stub pulls in subprocess, which costs
|
|
70
|
+
# more to import (~5 ms) than every readiness check in the CLI together.
|
|
71
|
+
from dataplat.cli._missing import build_missing_deps_app
|
|
72
|
+
|
|
73
|
+
return build_missing_deps_app(mount)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class LazyRootGroup(TyperGroup):
|
|
77
|
+
"""Root group that resolves an area's real app the first time it is used."""
|
|
78
|
+
|
|
79
|
+
def resolve_command(self, ctx: Any, args: Any) -> Any:
|
|
80
|
+
"""Import the area behind a placeholder, now that it is needed.
|
|
81
|
+
|
|
82
|
+
Resolution is what "the user is reaching into this area" looks like to
|
|
83
|
+
click. ``--help``, typo suggestions and top-level completion never come
|
|
84
|
+
through here — they read the placeholder and stay import-free. Shell
|
|
85
|
+
completion *descending* into an area does come through here, with
|
|
86
|
+
``ctx.resilient_parsing`` set, and must still import: the completions it
|
|
87
|
+
owes the shell are the area's own subcommands.
|
|
88
|
+
"""
|
|
89
|
+
name, cmd, rest = super().resolve_command(ctx, args)
|
|
90
|
+
if isinstance(cmd, AreaPlaceholderGroup) and name is not None:
|
|
91
|
+
cmd = self._load_area(name)
|
|
92
|
+
return name, cmd, rest
|
|
93
|
+
|
|
94
|
+
def _load_area(self, name: str) -> Any:
|
|
95
|
+
mount = area_by_name(name)
|
|
96
|
+
# The placeholder exists because a mount was registered under this name.
|
|
97
|
+
assert mount is not None, f"no registered area named {name!r}"
|
|
98
|
+
# get_group, not get_command: the latter bolts the completion options
|
|
99
|
+
# onto whatever it builds, which add_typer never did for an area.
|
|
100
|
+
group = get_group(area_command(mount))
|
|
101
|
+
# add_typer(name=...) used to name the mounted group; get_group takes
|
|
102
|
+
# the name off the area's own Typer, so keep the mount authoritative —
|
|
103
|
+
# a third-party area may well name its app something else.
|
|
104
|
+
group.name = name
|
|
105
|
+
# Cached in place: replacing the placeholder keeps command order intact
|
|
106
|
+
# and means one process imports an area at most once.
|
|
107
|
+
self.commands[name] = group
|
|
108
|
+
return group
|