dataplat 0.2.3__tar.gz → 0.4.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.3 → dataplat-0.4.0}/.github/workflows/ci.yml +21 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/.github/workflows/release.yml +13 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/CHANGELOG.md +129 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/CONTRIBUTING.md +116 -8
- dataplat-0.4.0/PKG-INFO +726 -0
- dataplat-0.4.0/README.md +677 -0
- dataplat-0.4.0/dataplat/cli/_exit.py +68 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/_lazy.py +104 -3
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/bi/superset.py +26 -27
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/cloud/aws/_common.py +48 -3
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/cloud/aws/rds.py +27 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/cloud/aws/redshift.py +36 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/cloud/aws/secrets.py +90 -5
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/config.py +147 -16
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/db/__init__.py +105 -15
- dataplat-0.4.0/dataplat/cli/db/_common.py +635 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/db/dbt_orphans.py +91 -19
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/db/describe.py +73 -13
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/db/long_queries.py +69 -4
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/db/role.py +29 -12
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/db/role_create.py +26 -12
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/db/role_drop.py +26 -12
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/db/role_list.py +24 -12
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/db/top_tables.py +195 -29
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/ingest/airbyte/_common.py +8 -8
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/ingest/airbyte/connections.py +21 -19
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/ingest/airbyte/definitions.py +6 -9
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/ingest/airbyte/tags.py +6 -9
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/ingest/airbyte/templates.py +5 -8
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/ingest/airbyte/workspaces.py +6 -9
- dataplat-0.4.0/dataplat/cli/status.py +544 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/core/deps.py +63 -1
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/core/envrc.py +81 -10
- dataplat-0.4.0/dataplat/core/errors.py +94 -0
- dataplat-0.4.0/dataplat/core/registry.py +381 -0
- dataplat-0.4.0/dataplat/core/trace.py +332 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/main.py +22 -3
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/services/airbyte/client.py +95 -3
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/services/aws/auth.py +35 -0
- dataplat-0.4.0/dataplat/services/db/capabilities.py +305 -0
- dataplat-0.4.0/dataplat/services/db/connection.py +493 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/services/db/describe.py +582 -39
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/services/db/orphans.py +20 -4
- dataplat-0.4.0/dataplat/services/db/top_tables.py +329 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/services/superset/client.py +50 -1
- {dataplat-0.2.3 → dataplat-0.4.0}/pyproject.toml +27 -3
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/cli/test_airbyte_commands.py +47 -6
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/cli/test_aws_secrets.py +147 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/cli/test_cli_smoke.py +282 -3
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/cli/test_config.py +162 -0
- dataplat-0.4.0/tests/cli/test_db_common.py +851 -0
- dataplat-0.4.0/tests/cli/test_db_long_queries.py +376 -0
- dataplat-0.4.0/tests/cli/test_db_query.py +640 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/cli/test_dbt_orphans.py +187 -2
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/cli/test_describe.py +349 -0
- dataplat-0.4.0/tests/cli/test_exit.py +129 -0
- dataplat-0.4.0/tests/cli/test_rds.py +1001 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/cli/test_redshift.py +71 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/cli/test_regression.py +4 -1
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/cli/test_role.py +169 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/cli/test_role_create.py +82 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/cli/test_role_drop.py +63 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/cli/test_status.py +555 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/cli/test_superset.py +64 -4
- dataplat-0.4.0/tests/cli/test_top_tables.py +472 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/core/test_deps.py +85 -1
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/core/test_envrc.py +82 -0
- dataplat-0.4.0/tests/core/test_errors.py +128 -0
- dataplat-0.4.0/tests/core/test_registry.py +495 -0
- dataplat-0.4.0/tests/core/test_trace.py +373 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/integration/conftest.py +17 -0
- dataplat-0.4.0/tests/integration/duckdb/__init__.py +1 -0
- dataplat-0.4.0/tests/integration/duckdb/conftest.py +339 -0
- dataplat-0.4.0/tests/integration/duckdb/test_duckdb_services.py +1411 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/integration/test_describe_pg.py +108 -10
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/integration/test_harness.py +94 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/integration/test_orphans_pg.py +96 -1
- dataplat-0.4.0/tests/services/airbyte/test_client.py +265 -0
- dataplat-0.4.0/tests/services/aws/test_auth.py +290 -0
- dataplat-0.4.0/tests/services/db/test_capabilities.py +225 -0
- dataplat-0.4.0/tests/services/db/test_connection.py +528 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/services/db/test_describe.py +700 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/services/db/test_orphans.py +58 -0
- dataplat-0.4.0/tests/services/db/test_top_tables.py +372 -0
- dataplat-0.4.0/tests/services/superset/test_client.py +144 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/uv.lock +37 -3
- dataplat-0.2.3/PKG-INFO +0 -412
- dataplat-0.2.3/README.md +0 -367
- dataplat-0.2.3/dataplat/cli/db/_common.py +0 -136
- dataplat-0.2.3/dataplat/cli/status.py +0 -331
- dataplat-0.2.3/dataplat/core/errors.py +0 -23
- dataplat-0.2.3/dataplat/core/registry.py +0 -110
- dataplat-0.2.3/dataplat/services/db/connection.py +0 -169
- dataplat-0.2.3/dataplat/services/db/top_tables.py +0 -189
- dataplat-0.2.3/tests/cli/test_db_common.py +0 -130
- dataplat-0.2.3/tests/cli/test_db_long_queries.py +0 -217
- dataplat-0.2.3/tests/cli/test_db_query.py +0 -199
- dataplat-0.2.3/tests/cli/test_rds.py +0 -297
- dataplat-0.2.3/tests/cli/test_top_tables.py +0 -240
- dataplat-0.2.3/tests/core/test_registry.py +0 -96
- dataplat-0.2.3/tests/services/airbyte/test_client.py +0 -43
- dataplat-0.2.3/tests/services/aws/test_auth.py +0 -107
- dataplat-0.2.3/tests/services/db/test_connection.py +0 -142
- dataplat-0.2.3/tests/services/db/test_top_tables.py +0 -125
- dataplat-0.2.3/tests/services/superset/test_client.py +0 -54
- {dataplat-0.2.3 → dataplat-0.4.0}/.gitignore +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/.python-version +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/LICENSE +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/__init__.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/__init__.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/_missing.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/_options.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/_prompt.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/_render.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/bi/__init__.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/bi/app.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/ci/__init__.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/ci/app.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/ci/github/__init__.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/ci/github/app.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/ci/github/runner.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/cloud/__init__.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/cloud/app.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/cloud/aws/__init__.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/cloud/aws/app.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/db/_report.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/ingest/__init__.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/ingest/airbyte/__init__.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/ingest/airbyte/_cursor.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/ingest/airbyte/_resource.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/ingest/airbyte/app.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/ingest/airbyte/destinations.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/ingest/airbyte/enums.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/ingest/airbyte/jobs.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/ingest/airbyte/sources.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/ingest/airbyte/tui.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/ingest/app.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/cli/open.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/core/__init__.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/services/__init__.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/services/airbyte/__init__.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/services/airbyte/_resource.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/services/airbyte/connections.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/services/airbyte/definitions.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/services/airbyte/destinations.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/services/airbyte/jobs.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/services/airbyte/sources.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/services/airbyte/tags.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/services/airbyte/workspaces.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/services/aws/__init__.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/services/db/__init__.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/services/db/_like.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/services/db/long_queries.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/services/db/role.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/services/db/role_admin.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/services/db/role_dialects.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/services/db/targets.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/dataplat/services/superset/__init__.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/__init__.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/cli/__init__.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/cli/test_airbyte_cursor_logic.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/cli/test_airbyte_guards.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/cli/test_airbyte_tui.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/cli/test_aws_secrets_write.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/cli/test_github_runner.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/cli/test_missing_deps.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/cli/test_open.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/cli/test_prompt.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/cli/test_render.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/conftest.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/core/__init__.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/integration/__init__.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/integration/redshift/__init__.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/integration/redshift/conftest.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/integration/redshift/test_conformance.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/integration/redshift/test_harness.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/integration/test_long_queries_pg.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/integration/test_roles_pg.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/integration/test_top_tables_pg.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/services/__init__.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/services/airbyte/__init__.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/services/airbyte/test_connections.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/services/airbyte/test_definitions.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/services/airbyte/test_destinations.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/services/airbyte/test_jobs.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/services/airbyte/test_sources.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/services/airbyte/test_workspaces.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/services/aws/__init__.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/services/db/__init__.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/services/db/test_long_queries.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/services/db/test_role.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/services/db/test_role_admin.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/services/db/test_role_dialects.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/services/db/test_targets.py +0 -0
- {dataplat-0.2.3 → dataplat-0.4.0}/tests/services/superset/__init__.py +0 -0
|
@@ -32,6 +32,10 @@ jobs:
|
|
|
32
32
|
- name: Setup Python
|
|
33
33
|
run: uv python install ${{ matrix.python-version }}
|
|
34
34
|
|
|
35
|
+
# --all-extras is what installs psycopg (the "db" extra) and duckdb (the
|
|
36
|
+
# "duckdb" extra). The DuckDB step below needs the latter and has no skip
|
|
37
|
+
# path, so dropping this flag turns into a hard failure rather than a
|
|
38
|
+
# quietly shrinking test run.
|
|
35
39
|
- name: Install dependencies
|
|
36
40
|
run: uv sync --group dev --all-extras
|
|
37
41
|
|
|
@@ -53,6 +57,23 @@ jobs:
|
|
|
53
57
|
- name: Pytest (unit)
|
|
54
58
|
run: uv run pytest -m "not integration"
|
|
55
59
|
|
|
60
|
+
# The third engine's real-SQL tier, and the only one that belongs in this
|
|
61
|
+
# job: DuckDB is a library, so it needs no container, no credentials and no
|
|
62
|
+
# wait loop -- just the extra `uv sync` already installed. Running it in
|
|
63
|
+
# both matrix legs is the point, since an embedded engine is the one place
|
|
64
|
+
# an interpreter difference could plausibly show up in a database.
|
|
65
|
+
#
|
|
66
|
+
# Selected by path rather than by marker. tests/integration/duckdb/ sits
|
|
67
|
+
# under tests/integration/, so its conftest chain stamps every test with
|
|
68
|
+
# the `integration` marker the step above deselects; a `duckdb` marker
|
|
69
|
+
# would have to be registered in pyproject.toml, and one line here is
|
|
70
|
+
# cheaper than a marker that then has to be kept in sync with a directory.
|
|
71
|
+
# These tests are consequently also collected by the `integration` job
|
|
72
|
+
# below -- harmless, since they run in under two seconds and touch no
|
|
73
|
+
# server, but *this* is where DuckDB is proved, on both interpreters.
|
|
74
|
+
- name: Pytest (DuckDB integration)
|
|
75
|
+
run: uv run pytest tests/integration/duckdb
|
|
76
|
+
|
|
56
77
|
# A separate job, not extra steps in the matrix above, for two reasons: the
|
|
57
78
|
# unit legs are the fast signal and must not queue behind a container boot,
|
|
58
79
|
# and a database per Python version would buy nothing -- the SQL these tests
|
|
@@ -50,6 +50,9 @@ jobs:
|
|
|
50
50
|
print(f"Tag '{tag}' matches [project].version.")
|
|
51
51
|
PY
|
|
52
52
|
|
|
53
|
+
# --all-extras installs psycopg ("db") and duckdb ("duckdb"). The wheel
|
|
54
|
+
# advertises both as extras, so both are installed before the tag is
|
|
55
|
+
# allowed through.
|
|
53
56
|
- name: Install dependencies
|
|
54
57
|
run: uv sync --group dev --all-extras
|
|
55
58
|
|
|
@@ -67,6 +70,16 @@ jobs:
|
|
|
67
70
|
- name: Pytest (unit)
|
|
68
71
|
run: uv run pytest -m "not integration"
|
|
69
72
|
|
|
73
|
+
# Also mirrors ci.yml, and it belongs in the tag gate for the same reason
|
|
74
|
+
# the `integration` job does: a version can be yanked but never replaced,
|
|
75
|
+
# and `verify`'s fake cursors cannot tell whether a `duckdb_*()` query
|
|
76
|
+
# parses. This one costs no container and runs on both interpreters, so
|
|
77
|
+
# there is no argument for leaving the third engine's SQL unexecuted at
|
|
78
|
+
# publish time. Path-selected because the tier inherits the `integration`
|
|
79
|
+
# marker the step above deselects -- see the note in ci.yml.
|
|
80
|
+
- name: Pytest (DuckDB integration)
|
|
81
|
+
run: uv run pytest tests/integration/duckdb
|
|
82
|
+
|
|
70
83
|
# This gate exists because the alternative is shipping SQL no server has ever
|
|
71
84
|
# parsed. dataplat's db services are thousands of lines of generated SQL, and
|
|
72
85
|
# the fake-cursor tests in `verify` prove only that execute() was called -- an
|
|
@@ -1,5 +1,134 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.0
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **DuckDB is a supported engine.** Set `<NAME>_ENGINE=duckdb` and
|
|
8
|
+
`<NAME>_PATH=/path/to.duckdb` (or `:memory:`), and install
|
|
9
|
+
`dataplat[duckdb]`. `<NAME>_DATABASE` works as a fallback for the path, and
|
|
10
|
+
`<NAME>_READ_ONLY=1` opens the file read-only.
|
|
11
|
+
|
|
12
|
+
DuckDB is not a smaller PostgreSQL — it runs inside the `dp` process, backed by
|
|
13
|
+
a file, with a single implicit user — so only the commands that mean something
|
|
14
|
+
there are available:
|
|
15
|
+
|
|
16
|
+
| command | DuckDB | why |
|
|
17
|
+
| --- | --- | --- |
|
|
18
|
+
| `db query` | works | |
|
|
19
|
+
| `db describe` | works | sizes and view definitions come from DuckDB's own catalog |
|
|
20
|
+
| `db top-tables` | works | sizes are DuckDB estimates, not comparable to PostgreSQL's |
|
|
21
|
+
| `db role *` | refused | there are no users or roles to describe |
|
|
22
|
+
| `db long-queries`, `db kill` | refused | in-process: there are no other sessions |
|
|
23
|
+
| `db dbt-orphans` | refused | it works by renaming, and DuckDB refuses to rename a relation a view depends on |
|
|
24
|
+
|
|
25
|
+
A refused command exits 2 and says which engine and why. These are properties
|
|
26
|
+
of the database, not gaps — the messages say so, because a user deserves to
|
|
27
|
+
know a thing can never work rather than assume it is coming.
|
|
28
|
+
|
|
29
|
+
`db describe` reports what DuckDB has no concept of — materialized views,
|
|
30
|
+
partitions, triggers, row-level security, privileges — as not applicable, with
|
|
31
|
+
the reason, rather than as empty sections that would read as "you have none".
|
|
32
|
+
|
|
33
|
+
Unlike PostgreSQL (needs a container) and Redshift (needs a cluster), the
|
|
34
|
+
DuckDB test tier needs nothing at all, so it never skips: this is the first
|
|
35
|
+
dialect with unconditional executing coverage in CI, and the second real-SQL
|
|
36
|
+
target the shared queries have ever been checked against.
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
|
|
40
|
+
- `dp config doctor` reported failures for a valid DuckDB target: it demanded
|
|
41
|
+
`<NAME>_HOST`, `<NAME>_USER` and `<NAME>_PASSWORD`, then failed the connection
|
|
42
|
+
check by dialing a server that does not exist. It now asks for the variables
|
|
43
|
+
the engine actually uses, and probes a DuckDB target by opening its file —
|
|
44
|
+
which is the health check.
|
|
45
|
+
|
|
46
|
+
## 0.3.0
|
|
47
|
+
|
|
48
|
+
### Changed
|
|
49
|
+
|
|
50
|
+
- **Exit codes now say what went wrong.** Every failure used to be `1`, so a
|
|
51
|
+
wrapper script could not tell "your config is wrong" from "the warehouse is
|
|
52
|
+
down" — and only one of those is worth retrying. Typed failures now carry
|
|
53
|
+
their own code: `2` invalid input, `3` configuration, `4` authentication,
|
|
54
|
+
`5` external service. `0`, `1` and `2` keep their conventional meanings, and
|
|
55
|
+
`2` is deliberately shared with Click's own usage error, because
|
|
56
|
+
`--format nope` and `-t nosuchtarget` are one condition to the caller.
|
|
57
|
+
|
|
58
|
+
An unreachable warehouse exits `5`, since that is the retryable case. A bad
|
|
59
|
+
statement against a reachable server stays `1`: retrying a syntax error would
|
|
60
|
+
fail identically forever. Untyped failures and a declined confirmation also
|
|
61
|
+
stay `1`. The full table is in the README.
|
|
62
|
+
|
|
63
|
+
**Scripts that branch on a non-zero exit will see new numbers.** Anything
|
|
64
|
+
testing `== 1` for a config or auth problem needs updating.
|
|
65
|
+
|
|
66
|
+
- **`dp db dbt-orphans` is more aggressive.** Its "which models are live" query
|
|
67
|
+
interpolated the dbt project name into a `LIKE` pattern without escaping it,
|
|
68
|
+
and dbt project names are snake_case — so the `_` in `my_project` matched any
|
|
69
|
+
character and a *sibling* project's models (`my2project`) sharing the
|
|
70
|
+
`dbt_artifacts` schema were counted as live. The same applied to
|
|
71
|
+
`DP_DBT_INVOCATION_COMMAND`, where `_` is ordinary in a dbt selector.
|
|
72
|
+
|
|
73
|
+
The direction is the point: an over-large live set makes **fewer** objects look
|
|
74
|
+
orphaned. Escaping it shrinks the live set, so dbt-orphans will now rename —
|
|
75
|
+
and after the grace period drop — objects it previously left alone. Run
|
|
76
|
+
`dp db dbt-orphans` (dry-run is the default) and read the plan before you
|
|
77
|
+
`--no-dry-run` the first time after upgrading.
|
|
78
|
+
|
|
79
|
+
- **`dp status` runs its checks concurrently**: 40.7s to 10.4s with five
|
|
80
|
+
targets, one unreachable. Sections run in parallel and each database target is
|
|
81
|
+
probed in parallel within them, which is where the time actually went — a 10s
|
|
82
|
+
connect timeout per target was paid serially. Key order and section order are
|
|
83
|
+
unchanged, because both pools iterate the declared mapping rather than
|
|
84
|
+
completion order. The AWS section stays serial and last: it may hand the
|
|
85
|
+
terminal to an interactive `aws sso login`.
|
|
86
|
+
|
|
87
|
+
### Added
|
|
88
|
+
|
|
89
|
+
- **`--verbose` (or `DP_VERBOSE=1`) shows what dataplat actually sent** — SQL
|
|
90
|
+
statements, HTTP requests with status and duration, and AWS service calls.
|
|
91
|
+
It writes to stderr only, so `--json` and `--format csv` stay pipeable, and
|
|
92
|
+
everything passes through a redactor: passwords, secret values, bearer tokens
|
|
93
|
+
and API keys are never traced. Parameter values and response bodies are not
|
|
94
|
+
traced either — those are the data, not the request.
|
|
95
|
+
|
|
96
|
+
- **Third-party command areas.** `dp` discovers areas declared in the
|
|
97
|
+
`dataplat.areas` entry-point group, so a package can add a command area
|
|
98
|
+
without a change here. Discovery reads only the entry-point metadata, never
|
|
99
|
+
imports the plugin, so `dp --version` and `dp --help` stay import-free and
|
|
100
|
+
fast. A plugin that fails to import warns on stderr and leaves the built-in
|
|
101
|
+
areas working; it cannot shadow a built-in area.
|
|
102
|
+
|
|
103
|
+
- `dp config doctor` warns when a loaded `.envrc` value still contains an
|
|
104
|
+
unexpanded `$VAR`. dataplat does not run a shell, so
|
|
105
|
+
`export PGHOST=$DB_HOST` loads the literal text — which then surfaces as a
|
|
106
|
+
baffling connection failure rather than as the configuration mistake it is.
|
|
107
|
+
|
|
108
|
+
- Shell completion is documented (`dp --install-completion`). It always worked;
|
|
109
|
+
the README never said so.
|
|
110
|
+
|
|
111
|
+
### Fixed
|
|
112
|
+
|
|
113
|
+
- `dp db query --format json|csv` could emit output that would not parse. The
|
|
114
|
+
progress spinner painted to stdout, which was invisible while Rich only did
|
|
115
|
+
that for a real terminal — the frames are erased — but `FORCE_COLOR` makes
|
|
116
|
+
Rich treat a pipe as a terminal too, and then the escape sequences ended up in
|
|
117
|
+
the redirected file. The spinner now follows the same sink as the notices.
|
|
118
|
+
|
|
119
|
+
- `dp db describe` reported the owner's grant option two different ways
|
|
120
|
+
depending on whether you asked about a schema or a relation. PostgreSQL grants
|
|
121
|
+
an owner every grant option implicitly and never records it in the ACL, so
|
|
122
|
+
reading the ACL reported "cannot delegate" about a role that demonstrably can.
|
|
123
|
+
Schema privileges now agree with relation privileges.
|
|
124
|
+
|
|
125
|
+
- The `Operating System :: OS Independent` classifier was an overclaim and has
|
|
126
|
+
been narrowed. Four things break on Windows: `dp config init` creates a
|
|
127
|
+
symlink, the dependency auto-install re-execs through `os.execvp`, the runner
|
|
128
|
+
commands shell out to `docker` with a POSIX default workdir, and the
|
|
129
|
+
credentials file is written with a `0o600` mode Windows ignores — after which
|
|
130
|
+
dataplat reports its own file as insecurely permissioned. CI tests Linux only.
|
|
131
|
+
|
|
3
132
|
## 0.2.3
|
|
4
133
|
|
|
5
134
|
Redshift-only fixes. Nothing changes for PostgreSQL targets.
|
|
@@ -19,8 +19,10 @@ uv run ruff format --check .
|
|
|
19
19
|
uv run mypy dataplat
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
`uv run pytest` is green without Docker: the
|
|
23
|
-
|
|
22
|
+
`uv run pytest` is green without Docker: the PostgreSQL-backed tests skip. The
|
|
23
|
+
DuckDB ones do not — there is no server for them to miss, so they always run
|
|
24
|
+
(see below). To exercise the PostgreSQL half, start a server and point the suite
|
|
25
|
+
at it:
|
|
24
26
|
|
|
25
27
|
```bash
|
|
26
28
|
docker run -d --name dp-pg-test \
|
|
@@ -30,7 +32,7 @@ docker exec dp-pg-test psql -U postgres -d dataplat_test \
|
|
|
30
32
|
-c 'CREATE EXTENSION IF NOT EXISTS pg_stat_statements'
|
|
31
33
|
|
|
32
34
|
DP_TEST_PG_REQUIRED=1 uv run pytest # everything
|
|
33
|
-
uv run pytest -m "not integration" #
|
|
35
|
+
uv run pytest -m "not integration" # deselect the real-database tiers
|
|
34
36
|
docker rm -f -v dp-pg-test # -v, or the volume dangles
|
|
35
37
|
```
|
|
36
38
|
|
|
@@ -40,6 +42,40 @@ and still report success.
|
|
|
40
42
|
|
|
41
43
|
Commits follow [Conventional Commits](https://www.conventionalcommits.org/).
|
|
42
44
|
|
|
45
|
+
## Testing against DuckDB
|
|
46
|
+
|
|
47
|
+
There is nothing to provision. DuckDB is a library that opens a file inside the
|
|
48
|
+
test process, so the fixtures in `tests/integration/duckdb/` build a throwaway
|
|
49
|
+
database in pytest's `tmp_path`:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
uv run pytest tests/integration/duckdb # ~1s, no server, no environment
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
This tier deliberately has **none** of the availability machinery the other two
|
|
56
|
+
have: no DSN variable, no `_REQUIRED` switch, no disposability gate, no
|
|
57
|
+
read-only guard, and no skip path. Its `conftest.py` argues each omission at
|
|
58
|
+
length; the short version is that `DP_TEST_*_REQUIRED` exists because a
|
|
59
|
+
silently-skipping tier reports green having executed none of the SQL it was
|
|
60
|
+
written to validate — and here there is nothing that *can* be unavailable. The
|
|
61
|
+
one thing that can be missing is the `duckdb` package, which is a broken dev
|
|
62
|
+
environment rather than absent infrastructure (`uv sync --group dev
|
|
63
|
+
--all-extras`), so it is a hard collection error carrying the install hint,
|
|
64
|
+
never a skip.
|
|
65
|
+
|
|
66
|
+
It carries the `integration` marker, because everything under
|
|
67
|
+
`tests/integration/` does — that is the safety net which keeps
|
|
68
|
+
`-m 'not integration'` airtight. Select it by path, as above, when you want it
|
|
69
|
+
without the PostgreSQL container.
|
|
70
|
+
|
|
71
|
+
Isolation is a fresh database file per test rather than the PostgreSQL harness's
|
|
72
|
+
BEGIN/ROLLBACK. DuckDB *does* roll back DDL, so the trick would work
|
|
73
|
+
mechanically — but a table created inside an open transaction reports
|
|
74
|
+
`estimated_size = 0`, and `pragma_database_size()` reports `0 bytes` until a
|
|
75
|
+
`CHECKPOINT` that cannot run in a writing transaction, so every row-estimate and
|
|
76
|
+
size assertion would have passed against zeros. Both facts are pinned by tests,
|
|
77
|
+
so the decision can be re-checked instead of re-derived.
|
|
78
|
+
|
|
43
79
|
## Testing against a real Redshift cluster
|
|
44
80
|
|
|
45
81
|
Redshift is a managed service, so there is no container and CI cannot cover it.
|
|
@@ -108,9 +144,58 @@ has taught nobody anything.
|
|
|
108
144
|
|
|
109
145
|
## Dialect changes: what counts as evidence
|
|
110
146
|
|
|
111
|
-
`dataplat/services/db` targets
|
|
112
|
-
|
|
113
|
-
|
|
147
|
+
`dataplat/services/db` targets three engines, and they are not equally knowable:
|
|
148
|
+
|
|
149
|
+
| Engine | SQL really executed? | What it costs to find out |
|
|
150
|
+
| --- | --- | --- |
|
|
151
|
+
| PostgreSQL | yes, in CI | a container |
|
|
152
|
+
| DuckDB | yes, in CI — and its tier cannot skip | nothing: a library and a temp file |
|
|
153
|
+
| Redshift | **no** — generated and asserted, never run | a cluster you own; CI cannot have one |
|
|
154
|
+
|
|
155
|
+
That asymmetry is the whole reason this section exists, and it is now lopsided in
|
|
156
|
+
one direction only. PostgreSQL has a real integration suite behind it. So does
|
|
157
|
+
DuckDB, and DuckDB is the cheap case: there is no server to be unreachable and
|
|
158
|
+
nothing to provision, so a claim about it can always be checked *before* it is
|
|
159
|
+
made. Redshift has none and cannot get one cheaply — it is a managed service, so
|
|
160
|
+
there is no container to run in CI.
|
|
161
|
+
|
|
162
|
+
Which *commands* an engine refuses, and why, is declared in one place:
|
|
163
|
+
`dataplat/services/db/capabilities.py` — one entry per engine with no defaults,
|
|
164
|
+
so adding an engine and forgetting what it can do is a construction error there
|
|
165
|
+
rather than a wrong answer somewhere downstream. Change what an engine can be
|
|
166
|
+
asked there and nowhere else, and cite the code or the probe the entry rests on,
|
|
167
|
+
exactly as the existing ones do.
|
|
168
|
+
|
|
169
|
+
A command that still runs but has to leave a *section* out is the other case, and
|
|
170
|
+
it declares that next to the section (`services/db/describe.py`'s
|
|
171
|
+
`NotApplicable`) and prints it — `dp db describe` on DuckDB ends with the list of
|
|
172
|
+
sections the engine has no concept of, with a reason each. Either way the reason
|
|
173
|
+
is written once, next to the fact, and a refusal never says "not implemented"
|
|
174
|
+
about something the engine simply is not.
|
|
175
|
+
|
|
176
|
+
### DuckDB: the rules apply, and the excuse does not
|
|
177
|
+
|
|
178
|
+
The evidence classes below apply to DuckDB unchanged in principle — same
|
|
179
|
+
ordering, same requirement to record in the code *why* a line is the way it is.
|
|
180
|
+
In practice you will never reach past the first one, because the strongest class
|
|
181
|
+
(0: confirmed against a real engine) is always available: `uv run pytest
|
|
182
|
+
tests/integration/duckdb` runs in about a second, needs no server, no
|
|
183
|
+
credentials and no container, and cannot skip.
|
|
184
|
+
|
|
185
|
+
So, plainly: **"I could not test it" is never an acceptable reason for a DuckDB
|
|
186
|
+
change.** If a DuckDB behaviour is in doubt, open a connection and find out —
|
|
187
|
+
then turn the probe into a test in `tests/integration/duckdb/`, so the next
|
|
188
|
+
person inherits the answer instead of the doubt. A comment recording an
|
|
189
|
+
*unverified* DuckDB assumption is not a legitimate outcome here the way it is for
|
|
190
|
+
Redshift; it is a probe someone declined to run.
|
|
191
|
+
|
|
192
|
+
The corollary applies to the capability matrix too. Every DuckDB `False` in
|
|
193
|
+
`capabilities.py` names something the engine does not have, and
|
|
194
|
+
`tests/integration/duckdb/test_duckdb_services.py` asserts the declaration
|
|
195
|
+
*alongside the missing catalog* — so a future DuckDB that grows `pg_roles` fails
|
|
196
|
+
the suite instead of quietly going on being refused.
|
|
197
|
+
|
|
198
|
+
### Redshift: weighing evidence without a server
|
|
114
199
|
|
|
115
200
|
For a while the rule was simply "don't touch SQL that runs on Redshift, because
|
|
116
201
|
you can't test it." That is a good instinct and a bad rule. Applied literally it
|
|
@@ -159,10 +244,20 @@ comment is what lets the next person re-evaluate instead of rediscovering.
|
|
|
159
244
|
|
|
160
245
|
- **Keep the engine constants split.** Never edit a `_*_SQL_REDSHIFT` constant
|
|
161
246
|
to fix a PostgreSQL bug. If a shared statement needs to diverge, split it and
|
|
162
|
-
leave the Redshift half byte-for-byte as it was.
|
|
247
|
+
leave the Redshift half byte-for-byte as it was. The DuckDB statements are
|
|
248
|
+
split the same way (`_*_SQL_DUCKDB`, `_DUCKDB_*_SQL`), with one difference: you
|
|
249
|
+
can run them, so a DuckDB constant you touched comes back through
|
|
250
|
+
`tests/integration/duckdb/`, not through inspection.
|
|
163
251
|
- **Add a fake-cursor test** asserting what the Redshift branch emits. It is the
|
|
164
252
|
only mechanism that covers that path at all, and it catches the common
|
|
165
|
-
accident of "fixed both branches when I meant one".
|
|
253
|
+
accident of "fixed both branches when I meant one". DuckDB's equivalent is an
|
|
254
|
+
*executing* test — a fake cursor there proves less than the real engine does,
|
|
255
|
+
for the same effort.
|
|
256
|
+
- **Do not translate placeholders between engines.** psycopg binds `%s`; DuckDB
|
|
257
|
+
binds `?`. Rewriting one into the other means deciding which `%` in a
|
|
258
|
+
statement is a literal, and getting that wrong changes the SQL — so
|
|
259
|
+
`DuckDbCursor` passes statements through untouched, and DuckDB SQL is written
|
|
260
|
+
for DuckDB.
|
|
166
261
|
- **Record the evidence in the code**, not just the commit message. A future
|
|
167
262
|
reader deciding whether they may touch the line needs to see why it is the way
|
|
168
263
|
it is.
|
|
@@ -175,3 +270,16 @@ never be NULL. A report that states something false is worse than one that
|
|
|
175
270
|
admits a gap — especially a report someone is using for an audit. When the
|
|
176
271
|
server will not tell you, say so, and say why in the same breath: a bare
|
|
177
272
|
"unknown" reads as a tool defect.
|
|
273
|
+
|
|
274
|
+
`dp db top-tables` on DuckDB is the same rule applied to a whole column, and it
|
|
275
|
+
shows where the rule ends. DuckDB has no per-relation size function and no
|
|
276
|
+
catalog column carrying bytes, so every `size_bytes` there is `None` — never `0`,
|
|
277
|
+
because a table whose size cannot be read is not an empty table, and `--json`
|
|
278
|
+
consumers have to be able to tell those apart.
|
|
279
|
+
|
|
280
|
+
The table rendering then goes one step further and *drops* the Size and
|
|
281
|
+
"% of disk" columns instead of filling them with `—`, printing the basis for the
|
|
282
|
+
numbers it does show. Withdrawing a claim is not enough on its own when the gap
|
|
283
|
+
is an entire column: a report of dashes under a `0 B (0.0% of disk)` footer reads
|
|
284
|
+
as a broken tool, not as the engine's answer. Say what you cannot know, then
|
|
285
|
+
show what you can.
|