truequery 0.1.0__py3-none-any.whl
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.
- truequery/__init__.py +3 -0
- truequery/analyzers/__init__.py +0 -0
- truequery/analyzers/project_checks.py +220 -0
- truequery/analyzers/sql.py +529 -0
- truequery/checks/__init__.py +0 -0
- truequery/checks/sql.py +1278 -0
- truequery/cli.py +710 -0
- truequery/engine.py +346 -0
- truequery/hook.py +860 -0
- truequery/keylookup.py +135 -0
- truequery/models.py +32 -0
- truequery/project.py +561 -0
- truequery/registry_impl.json +127 -0
- truequery/report.py +343 -0
- truequery/schema.py +206 -0
- truequery/settings.py +239 -0
- truequery/standard/consume/README.md +111 -0
- truequery/standard/consume/adhoc-limit-required.yaml +50 -0
- truequery/standard/consume/aggregate-before-export.yaml +39 -0
- truequery/standard/consume/consumer-cost-guard.yaml +44 -0
- truequery/standard/consume/consumer-reads-marts.yaml +38 -0
- truequery/standard/consume/dashboard-query-budget.yaml +40 -0
- truequery/standard/consume/dashboard-refresh-schedule.yaml +47 -0
- truequery/standard/consume/deterministic-export-order.yaml +44 -0
- truequery/standard/consume/explicit-timezone-consumers.yaml +44 -0
- truequery/standard/consume/explore-answer-carries-reproducible-query.yaml +56 -0
- truequery/standard/consume/explore-caveats-and-unchecked-stated.yaml +52 -0
- truequery/standard/consume/explore-day-boundaries-and-partial-periods.yaml +51 -0
- truequery/standard/consume/explore-experiment-readout-follows-plan.yaml +63 -0
- truequery/standard/consume/explore-freshness-and-coverage-checked.yaml +54 -0
- truequery/standard/consume/explore-inspect-distributions-and-examples.yaml +47 -0
- truequery/standard/consume/explore-join-row-count-checks.yaml +53 -0
- truequery/standard/consume/explore-null-aware-negative-filters.yaml +50 -0
- truequery/standard/consume/explore-question-metric-grain-stated.yaml +55 -0
- truequery/standard/consume/explore-rates-not-counts-across-segments.yaml +51 -0
- truequery/standard/consume/explore-reconcile-to-known-total.yaml +51 -0
- truequery/standard/consume/explore-reuse-governed-metric.yaml +52 -0
- truequery/standard/consume/export-row-cap.yaml +45 -0
- truequery/standard/consume/freshness-displayed.yaml +44 -0
- truequery/standard/consume/no-deep-offset.yaml +38 -0
- truequery/standard/consume/parameterized-dashboard-filters.yaml +38 -0
- truequery/standard/consume/reverse-etl-idempotent-keys.yaml +45 -0
- truequery/standard/consume/reverse-etl-pii-consent.yaml +50 -0
- truequery/standard/consume/reverse-etl-schema-contract.yaml +47 -0
- truequery/standard/dq/README.md +117 -0
- truequery/standard/dq/accuracy-proxy-or-unmeasurable.yaml +45 -0
- truequery/standard/dq/audits-blocking-every-run.yaml +47 -0
- truequery/standard/dq/checks-versioned-with-code.yaml +52 -0
- truequery/standard/dq/completeness-declare-missing-sentinels.yaml +44 -0
- truequery/standard/dq/completeness-fill-rate-thresholds.yaml +45 -0
- truequery/standard/dq/completeness-not-null-required-columns.yaml +60 -0
- truequery/standard/dq/completeness-table-not-empty.yaml +48 -0
- truequery/standard/dq/consistency-cross-column-row-rules.yaml +49 -0
- truequery/standard/dq/consistency-no-orphan-keys.yaml +49 -0
- truequery/standard/dq/consistency-paired-aggregate-reconciliation.yaml +48 -0
- truequery/standard/dq/schema-conformance-checked-on-read.yaml +48 -0
- truequery/standard/dq/unenforced-constraints-are-tested.yaml +51 -0
- truequery/standard/dq/uniqueness-composite-grain.yaml +52 -0
- truequery/standard/dq/uniqueness-key-no-duplicates.yaml +53 -0
- truequery/standard/dq/unit-tests-with-fixtures.yaml +52 -0
- truequery/standard/dq/validity-allowed-value-sets.yaml +46 -0
- truequery/standard/dq/validity-enforce-model-contracts.yaml +51 -0
- truequery/standard/dq/validity-range-format-type-rules.yaml +50 -0
- truequery/standard/gov/README.md +94 -0
- truequery/standard/gov/audit-columns-or-snapshots.yaml +45 -0
- truequery/standard/gov/column-grants-for-pii.yaml +41 -0
- truequery/standard/gov/declared-model-owner.yaml +43 -0
- truequery/standard/gov/description-coverage-for-shared-models.yaml +40 -0
- truequery/standard/gov/erasure-path-documented.yaml +43 -0
- truequery/standard/gov/exposures-registered.yaml +73 -0
- truequery/standard/gov/group-private-access.yaml +41 -0
- truequery/standard/gov/least-privilege-grants.yaml +80 -0
- truequery/standard/gov/lineage-from-declared-sources.yaml +41 -0
- truequery/standard/gov/mesh-public-contracts-only.yaml +41 -0
- truequery/standard/gov/no-bypassrls-roles.yaml +39 -0
- truequery/standard/gov/no-public-privileges.yaml +39 -0
- truequery/standard/gov/no-raw-pii-in-seeds.yaml +41 -0
- truequery/standard/gov/pii-classification-declared.yaml +42 -0
- truequery/standard/gov/production-pii-not-in-dev.yaml +43 -0
- truequery/standard/gov/redacting-views-for-pii.yaml +41 -0
- truequery/standard/gov/retention-declared.yaml +42 -0
- truequery/standard/gov/row-policy-for-sensitive-tables.yaml +38 -0
- truequery/standard/ingest/README.md +100 -0
- truequery/standard/ingest/cap-intake-cost-and-volume.yaml +46 -0
- truequery/standard/ingest/cdc-replica-identity-required.yaml +47 -0
- truequery/standard/ingest/checkpoint-streaming-intake.yaml +42 -0
- truequery/standard/ingest/choose-batch-vs-streaming-deliberately.yaml +45 -0
- truequery/standard/ingest/declare-source-freshness.yaml +80 -0
- truequery/standard/ingest/dedupe-on-natural-key-before-append.yaml +45 -0
- truequery/standard/ingest/handle-schema-drift-explicitly.yaml +45 -0
- truequery/standard/ingest/idempotent-batch-loads.yaml +49 -0
- truequery/standard/ingest/isolate-source-failures.yaml +50 -0
- truequery/standard/ingest/landing-rebuildable-from-source.yaml +43 -0
- truequery/standard/ingest/monitor-cdc-lag-and-slots.yaml +47 -0
- truequery/standard/ingest/pin-connector-and-schema-versions.yaml +42 -0
- truequery/standard/ingest/pin-file-format-options.yaml +42 -0
- truequery/standard/ingest/propagate-deletes-explicitly.yaml +48 -0
- truequery/standard/ingest/quarantine-invalid-rows-at-intake.yaml +56 -0
- truequery/standard/ingest/raw-landing-append-only.yaml +48 -0
- truequery/standard/ingest/reconcile-row-counts-per-load.yaml +43 -0
- truequery/standard/ingest/record-file-lineage-at-intake.yaml +48 -0
- truequery/standard/ingest/reference-sources-not-raw-tables.yaml +42 -0
- truequery/standard/ingest/require-completeness-marker.yaml +50 -0
- truequery/standard/ingest/scan-pii-at-landing.yaml +45 -0
- truequery/standard/ingest/seeds-small-static-only.yaml +43 -0
- truequery/standard/ingest/test-intake-against-fixtures.yaml +46 -0
- truequery/standard/ingest/watermark-and-late-data-policy.yaml +49 -0
- truequery/standard/model/README.md +91 -0
- truequery/standard/model/dbt-contract-enforced.yaml +50 -0
- truequery/standard/model/delete-handling-incremental.yaml +51 -0
- truequery/standard/model/explicit-column-lists.yaml +83 -0
- truequery/standard/model/full-refresh-parity.yaml +49 -0
- truequery/standard/model/grain-preserved-joins.yaml +52 -0
- truequery/standard/model/idempotent-reexec.yaml +48 -0
- truequery/standard/model/incremental-lookback-window.yaml +53 -0
- truequery/standard/model/incremental-watermark-filter.yaml +48 -0
- truequery/standard/model/jinja-macro-determinism.yaml +46 -0
- truequery/standard/model/jinja-no-nested-curlies.yaml +43 -0
- truequery/standard/model/materialization-choice.yaml +51 -0
- truequery/standard/model/merge-source-deduped.yaml +57 -0
- truequery/standard/model/microbatch-event-time.yaml +39 -0
- truequery/standard/model/null-join-keys-excluded.yaml +45 -0
- truequery/standard/model/snapshot-scd2-validity.yaml +45 -0
- truequery/standard/model/snapshot-strategy-choice.yaml +41 -0
- truequery/standard/model/staging-no-business-logic.yaml +44 -0
- truequery/standard/model/surrogate-keys-stable.yaml +37 -0
- truequery/standard/model/unique-key-merge-key.yaml +55 -0
- truequery/standard/ops/README.md +98 -0
- truequery/standard/ops/breaking-change-categorization.yaml +35 -0
- truequery/standard/ops/ci-pr-checks-required.yaml +44 -0
- truequery/standard/ops/ci-slim-builds-only-changed.yaml +42 -0
- truequery/standard/ops/cost-estimate-before-run.yaml +41 -0
- truequery/standard/ops/cost-guardrail-confidence-levels.yaml +39 -0
- truequery/standard/ops/dev-environment-expiry.yaml +42 -0
- truequery/standard/ops/disposable-dev-environments.yaml +42 -0
- truequery/standard/ops/drift-detection-history-first.yaml +42 -0
- truequery/standard/ops/full-refresh-approval.yaml +76 -0
- truequery/standard/ops/model-versioning-breaking-changes.yaml +83 -0
- truequery/standard/ops/plan-preview-before-apply.yaml +39 -0
- truequery/standard/ops/post-deploy-data-validation.yaml +42 -0
- truequery/standard/ops/pre-destructive-snapshot.yaml +35 -0
- truequery/standard/ops/prod-credentials-isolation.yaml +96 -0
- truequery/standard/ops/run-artifact-retention.yaml +41 -0
- truequery/standard/ops/staged-upgrades-with-backup.yaml +39 -0
- truequery/standard/orch/README.md +74 -0
- truequery/standard/orch/concurrency-pools-and-limits.yaml +63 -0
- truequery/standard/orch/dag-ci-parse-and-unit-tests.yaml +70 -0
- truequery/standard/orch/data-aware-scheduling-over-polling.yaml +62 -0
- truequery/standard/orch/declared-asset-lineage.yaml +66 -0
- truequery/standard/orch/deferrable-sensors-with-timeout.yaml +75 -0
- truequery/standard/orch/explicit-catchup-policy.yaml +53 -0
- truequery/standard/orch/explicit-schedule-timezone.yaml +68 -0
- truequery/standard/orch/failure-alert-routing.yaml +100 -0
- truequery/standard/orch/idempotent-backfill-windowing.yaml +68 -0
- truequery/standard/orch/idempotent-retries-with-limits.yaml +79 -0
- truequery/standard/orch/light-dag-parse-no-heavy-top-level.yaml +75 -0
- truequery/standard/orch/no-circular-dependencies.yaml +67 -0
- truequery/standard/orch/partitioned-assets-windowing.yaml +66 -0
- truequery/standard/orch/sla-deadline-alerts.yaml +76 -0
- truequery/standard/orch/task-atomicity-no-partial-writes.yaml +62 -0
- truequery/standard/orch/task-timeout-execution-limit.yaml +70 -0
- truequery/standard/py/README.md +63 -0
- truequery/standard/py/airflow-isolate-task-dependencies.yaml +40 -0
- truequery/standard/py/dagster-asset-declared-dependencies.yaml +47 -0
- truequery/standard/py/dagster-io-manager-not-manual-io.yaml +47 -0
- truequery/standard/py/dagster-resources-for-external-clients.yaml +53 -0
- truequery/standard/py/dbt-python-model-contract.yaml +48 -0
- truequery/standard/py/deterministic-model-outputs.yaml +83 -0
- truequery/standard/py/hook-adapter-connection-lifecycle.yaml +59 -0
- truequery/standard/py/idempotent-task-logic.yaml +55 -0
- truequery/standard/py/no-n-plus-one-queries-in-loops.yaml +46 -0
- truequery/standard/py/pandas-filter-before-collect.yaml +44 -0
- truequery/standard/py/pandas-no-chained-assignment.yaml +41 -0
- truequery/standard/py/pandas-no-row-loop-mutations.yaml +44 -0
- truequery/standard/py/pin-pipeline-dependencies.yaml +82 -0
- truequery/standard/py/sqlmesh-audit-python-outputs.yaml +49 -0
- truequery/standard/py/sqlmesh-python-model-execution-context.yaml +52 -0
- truequery/standard/sem/README.md +97 -0
- truequery/standard/sem/aggregation-type-matches-additivity.yaml +61 -0
- truequery/standard/sem/conversion-metrics-window-bounds.yaml +54 -0
- truequery/standard/sem/count-distinct-uses-entity-key.yaml +52 -0
- truequery/standard/sem/cumulative-metrics-time-spine.yaml +53 -0
- truequery/standard/sem/derived-metrics-compose-from-governed.yaml +52 -0
- truequery/standard/sem/description-and-ownership-per-metric.yaml +50 -0
- truequery/standard/sem/dimension-naming-conventions.yaml +52 -0
- truequery/standard/sem/entity-primary-key-declared.yaml +54 -0
- truequery/standard/sem/join-paths-declared-via-entities.yaml +47 -0
- truequery/standard/sem/metric-filters-declared-not-hidden.yaml +57 -0
- truequery/standard/sem/metric-grain-declaration.yaml +52 -0
- truequery/standard/sem/metric-smoke-test-on-change.yaml +43 -0
- truequery/standard/sem/no-fanout-double-counting.yaml +52 -0
- truequery/standard/sem/no-metric-defined-off-staging.yaml +47 -0
- truequery/standard/sem/null-handling-in-aggregations.yaml +57 -0
- truequery/standard/sem/percentile-metrics-use-approximate.yaml +51 -0
- truequery/standard/sem/ratio-metrics-divide-after-aggregation.yaml +49 -0
- truequery/standard/sem/saved-query-canonical-slices.yaml +56 -0
- truequery/standard/sem/semantic-model-validates.yaml +50 -0
- truequery/standard/sem/single-governed-metric-definition.yaml +68 -0
- truequery/standard/sem/time-dimension-standardized.yaml +51 -0
- truequery/standard/sem/unit-consistency.yaml +54 -0
- truequery/standard/sql/README.md +126 -0
- truequery/standard/sql/accidental-cross-join.yaml +58 -0
- truequery/standard/sql/average-of-ratios.yaml +63 -0
- truequery/standard/sql/between-date-on-timestamp.yaml +74 -0
- truequery/standard/sql/case-without-else-in-aggregate.yaml +67 -0
- truequery/standard/sql/count-star-after-left-join.yaml +67 -0
- truequery/standard/sql/distinct-dedup-bandaid.yaml +59 -0
- truequery/standard/sql/explore-limit-is-not-a-sample.yaml +56 -0
- truequery/standard/sql/explore-profile-filter-columns.yaml +48 -0
- truequery/standard/sql/fanout-join-pre-aggregation.yaml +64 -0
- truequery/standard/sql/group-by-bare-column.yaml +75 -0
- truequery/standard/sql/insert-explicit-column-list.yaml +60 -0
- truequery/standard/sql/integer-division-truncation.yaml +94 -0
- truequery/standard/sql/keyset-pagination.yaml +70 -0
- truequery/standard/sql/left-join-null-rejecting-where.yaml +58 -0
- truequery/standard/sql/limit-requires-order-by.yaml +67 -0
- truequery/standard/sql/natural-join-explicit-keys.yaml +56 -0
- truequery/standard/sql/negative-filter-drops-nulls.yaml +70 -0
- truequery/standard/sql/nondeterministic-merge-keys.yaml +69 -0
- truequery/standard/sql/not-in-nullable-subquery.yaml +57 -0
- truequery/standard/sql/null-equality-comparison.yaml +65 -0
- truequery/standard/sql/random-sampling-no-sort.yaml +48 -0
- truequery/standard/sql/row-number-dedupe-tiebreaker.yaml +67 -0
- truequery/standard/sql/sargable-filter-bare-column.yaml +53 -0
- truequery/standard/sql/set-operation-column-position.yaml +59 -0
- truequery/standard/sql/unguarded-update-delete.yaml +63 -0
- truequery/standard/sql/union-all-default.yaml +70 -0
- truequery/standard/store/README.md +88 -0
- truequery/standard/store/cluster-on-frequent-filters-joins.yaml +44 -0
- truequery/standard/store/compact-small-files-optimize.yaml +44 -0
- truequery/standard/store/declare-not-null-check-constraints.yaml +43 -0
- truequery/standard/store/declare-sort-order-for-range-queries.yaml +44 -0
- truequery/standard/store/enable-change-feed-for-incremental-consumers.yaml +44 -0
- truequery/standard/store/evolve-partitions-by-metadata-not-rewrite.yaml +45 -0
- truequery/standard/store/explicit-physical-types-no-implicit-casts.yaml +43 -0
- truequery/standard/store/full-refresh-by-replace-not-delete-insert.yaml +44 -0
- truequery/standard/store/incremental-watermark-partition-alignment.yaml +40 -0
- truequery/standard/store/no-high-cardinality-partition-column.yaml +47 -0
- truequery/standard/store/partition-expiration-retention-policy.yaml +43 -0
- truequery/standard/store/partition-on-filter-column.yaml +43 -0
- truequery/standard/store/partition-predicate-required.yaml +41 -0
- truequery/standard/store/prefer-auto-liquid-clustering-over-manual-tune.yaml +37 -0
- truequery/standard/store/rewrite-manifests-and-stats-periodically.yaml +37 -0
- truequery/standard/store/single-open-format-per-medallion-layer.yaml +37 -0
- truequery/standard/store/time-travel-retention-explicit.yaml +46 -0
- truequery/standard/store/use-transient-temporary-for-ephemeral.yaml +35 -0
- truequery/standard/store/vacuum-expire-snapshots-with-retention-floor.yaml +42 -0
- truequery/std_registry.py +3310 -0
- truequery-0.1.0.dist-info/METADATA +52 -0
- truequery-0.1.0.dist-info/RECORD +254 -0
- truequery-0.1.0.dist-info/WHEEL +4 -0
- truequery-0.1.0.dist-info/entry_points.txt +3 -0
- truequery-0.1.0.dist-info/licenses/LICENSE +201 -0
- truequery-0.1.0.dist-info/licenses/NOTICE +4 -0
truequery/keylookup.py
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""Declared-key index: the first consumer of contracts/.
|
|
2
|
+
|
|
3
|
+
Maps a relation name (lowercased) to its candidate keys. Each candidate
|
|
4
|
+
is a frozenset of columns that is unique on its own; a relation may
|
|
5
|
+
have several (a surrogate id and a natural key). Sources:
|
|
6
|
+
|
|
7
|
+
- ``contracts/<table>.yaml``: every ``keys:`` entry is one candidate
|
|
8
|
+
(a nested list such as ``[currency, rate_date]`` is one composite
|
|
9
|
+
candidate), and
|
|
10
|
+
- dbt schema YAML under a ``models`` tree: each column with a
|
|
11
|
+
``unique`` test, and each model-level
|
|
12
|
+
``unique_combination_of_columns`` test.
|
|
13
|
+
|
|
14
|
+
Deterministic, cached per project. Offline, file-level evidence only.
|
|
15
|
+
Unreadable YAML contributes nothing (the receipt reports invalid
|
|
16
|
+
contracts; this index never breaks a check).
|
|
17
|
+
"""
|
|
18
|
+
import os
|
|
19
|
+
|
|
20
|
+
import yaml
|
|
21
|
+
|
|
22
|
+
from truequery.project import SKIP_DIRS
|
|
23
|
+
|
|
24
|
+
_CACHE: dict[tuple, dict[str, list[frozenset]]] = {}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _read_yaml(path):
|
|
28
|
+
try:
|
|
29
|
+
with open(path, encoding="utf-8") as handle:
|
|
30
|
+
return yaml.safe_load(handle)
|
|
31
|
+
except (OSError, ValueError, yaml.YAMLError):
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _add(index, table, columns):
|
|
36
|
+
key = frozenset(str(c).lower() for c in columns if isinstance(c, str))
|
|
37
|
+
if key and key not in index.setdefault(table, []):
|
|
38
|
+
index[table].append(key)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _test_entries(spec):
|
|
42
|
+
"""(name, args) for each test on a model or column (dbt >= 1.8
|
|
43
|
+
spells `tests:` as `data_tests:`)."""
|
|
44
|
+
for test in (spec.get("tests") or []) + (spec.get("data_tests") or []):
|
|
45
|
+
if isinstance(test, str):
|
|
46
|
+
yield test, {}
|
|
47
|
+
elif isinstance(test, dict):
|
|
48
|
+
for name, args in test.items():
|
|
49
|
+
yield str(name), args if isinstance(args, dict) else {}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _from_contracts(cdir, index):
|
|
53
|
+
for name in sorted(os.listdir(cdir)):
|
|
54
|
+
if not name.endswith((".yaml", ".yml")):
|
|
55
|
+
continue
|
|
56
|
+
doc = _read_yaml(os.path.join(cdir, name))
|
|
57
|
+
if not isinstance(doc, dict):
|
|
58
|
+
continue
|
|
59
|
+
table = str(doc.get("table") or os.path.splitext(name)[0]).lower()
|
|
60
|
+
for key in doc.get("keys") or []:
|
|
61
|
+
_add(index, table, key if isinstance(key, list) else [key])
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _from_dbt_yaml(path, index):
|
|
65
|
+
doc = _read_yaml(path)
|
|
66
|
+
if not isinstance(doc, dict):
|
|
67
|
+
return
|
|
68
|
+
for model in doc.get("models") or []:
|
|
69
|
+
if not isinstance(model, dict) or not model.get("name"):
|
|
70
|
+
continue
|
|
71
|
+
table = str(model["name"]).lower()
|
|
72
|
+
for name, args in _test_entries(model):
|
|
73
|
+
# A model-level unique test without a column list pins nothing.
|
|
74
|
+
if name.split(".")[-1] == "unique_combination_of_columns":
|
|
75
|
+
_add(index, table, args.get("combination_of_columns") or [])
|
|
76
|
+
for col in model.get("columns") or []:
|
|
77
|
+
if not isinstance(col, dict) or not col.get("name"):
|
|
78
|
+
continue
|
|
79
|
+
if any(name.split(".")[-1] == "unique"
|
|
80
|
+
for name, _ in _test_entries(col)):
|
|
81
|
+
_add(index, table, [col["name"]])
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _nearest(path, test):
|
|
85
|
+
"""Nearest ancestor directory of path for which test(dir) holds."""
|
|
86
|
+
directory = os.path.dirname(os.path.abspath(path))
|
|
87
|
+
while True:
|
|
88
|
+
if test(directory):
|
|
89
|
+
return directory
|
|
90
|
+
parent = os.path.dirname(directory)
|
|
91
|
+
if parent == directory:
|
|
92
|
+
return None
|
|
93
|
+
directory = parent
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _project_root(path):
|
|
97
|
+
"""Nearest ancestor with a project marker, or None: without one we
|
|
98
|
+
don't walk the disk looking for keys (they'd belong to someone else)."""
|
|
99
|
+
markers = ("contracts", "dbt_project.yml", "DATA.md")
|
|
100
|
+
return _nearest(path, lambda d: any(
|
|
101
|
+
os.path.exists(os.path.join(d, m)) for m in markers))
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _models_ymls(root):
|
|
105
|
+
found = []
|
|
106
|
+
for dirpath, dirnames, filenames in os.walk(root):
|
|
107
|
+
# A nested dbt project is a different project: its keys aren't ours.
|
|
108
|
+
dirnames[:] = sorted(
|
|
109
|
+
d for d in dirnames
|
|
110
|
+
if d not in SKIP_DIRS and not d.startswith(".")
|
|
111
|
+
and not os.path.isfile(os.path.join(dirpath, d, "dbt_project.yml")))
|
|
112
|
+
parts = os.path.relpath(dirpath, root).split(os.sep)
|
|
113
|
+
if dirpath != root and "models" not in parts:
|
|
114
|
+
continue
|
|
115
|
+
found += [os.path.join(dirpath, name) for name in sorted(filenames)
|
|
116
|
+
if name.endswith((".yml", ".yaml"))]
|
|
117
|
+
return found
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def declared_keys_for(path):
|
|
121
|
+
"""{relation name -> [candidate key, ...]} for the project of path.
|
|
122
|
+
|
|
123
|
+
contracts/ may sit above the dbt project (examples/shop/contracts
|
|
124
|
+
next to dbt_repo/), so it is looked up on its own."""
|
|
125
|
+
root = _project_root(path)
|
|
126
|
+
cdir = _nearest(path, lambda d: os.path.isdir(os.path.join(d, "contracts")))
|
|
127
|
+
cache_key = (root, cdir)
|
|
128
|
+
if cache_key not in _CACHE:
|
|
129
|
+
index: dict[str, list[frozenset]] = {}
|
|
130
|
+
if cdir is not None:
|
|
131
|
+
_from_contracts(os.path.join(cdir, "contracts"), index)
|
|
132
|
+
for yml in _models_ymls(root) if root is not None else []:
|
|
133
|
+
_from_dbt_yaml(yml, index)
|
|
134
|
+
_CACHE[cache_key] = index
|
|
135
|
+
return _CACHE[cache_key]
|
truequery/models.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Finding and diagnostic shapes. One shape for the CLI, the hook, and tests."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class Finding:
|
|
10
|
+
rule: str
|
|
11
|
+
severity: str
|
|
12
|
+
mode: str
|
|
13
|
+
evidence: str
|
|
14
|
+
file: str
|
|
15
|
+
line: int
|
|
16
|
+
col: int
|
|
17
|
+
snippet: str
|
|
18
|
+
why: str
|
|
19
|
+
fix: str
|
|
20
|
+
|
|
21
|
+
def sort_key(self):
|
|
22
|
+
return (self.file, self.line, self.col, self.rule)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class Diagnostic:
|
|
27
|
+
file: str
|
|
28
|
+
message: str
|
|
29
|
+
detail: str = ""
|
|
30
|
+
|
|
31
|
+
def sort_key(self):
|
|
32
|
+
return (self.file, self.message)
|