Skip to main content

keystones-dbt

Gate a dbt model on the SQL it means, not on the text it is written as.

A keystones keystone pins a review gate to an AST node. dbt models are Jinja, which no SQL grammar can parse, so without a preprocessor they can only be gated on normalised text - which trips on every reformat.

This package is two plugins. The preprocessor masks the templating so the SQL underneath parses, and hands the masked content back so it is still gated. The parser reads any SQL dialect sqlglot knows, for warehouses the tree-sitter grammar pack has no grammar for.

Install

pip install "keystones-dbt[sqlglot]"
[[tool.keystones.language]]
extensions   = [".sql"]
parser       = { plugin = "keystones_dbt.parsers:sqlglot", dialect = "snowflake" }
preprocessor = { plugin = "keystones_dbt:preprocess", control_flow = "first-branch" }

Then mark a CTE by hand with -- keystone(finance): orders and run keystones add --id orders, or let keystones write the marker:

keystones add models/revenue.sql::orders --id orders --category finance \
    -m "GAAP revenue recognition"

dialect is any sqlglot knows: snowflake, bigquery, redshift, databricks, postgres, duckdb and more. Parsing is offline; nothing connects to a warehouse. Where the grammar pack has a grammar for your dialect you may use builtin = "sql_bigquery" in place of parser and skip the extra.

What the mask does

Jinja becomes
{# comment #} removed
{{ config(...) }} alone on a line removed
any other {{ ... }} a placeholder derived from its content
{% set x = 1 %}, {% do %}, {% import %}, {% from %} removed
{% if %}, {% for %} governed by control_flow, below
{% macro %}, block {% set %}, {% call %}, {% raw %}, other block tags refused

Masked content is hashed verbatim, so changing ref('orders') to ref('payments') trips the gate while reformatting the expression does not.

A keystone hashes its own lines, so a node keystone on a CTE does not see the config() block above it or an incremental filter below it. To gate materialization or the filter, use keystone(file).

Control flow

control_flow {% if a %} x {% else %} y {% endif %}
first-branch (default) tags removed, x kept and masked, y removed; the whole block is hashed as one span
drop the whole block removed and hashed as one span
refuse the file is refused; gate it on text with a region

With first-branch the SQL of the first branch still parses and anchors nodes. The whole block, every branch included, is in the hash, so a change to y still gates and {% if a %}x{% endif %}y cannot collide with x{% if a %}y{% endif %}. A block whose first branch is not valid SQL on its own fails to parse and is reported with the hash=text guidance.

A block that opens or closes outside a keystone's target, because a region or CTE boundary cuts through it, is refused: gate the whole file or a region that contains the whole block.

Refused models can always be gated on text with a region:

-- keystone:start(finance, hash=text): revenue-recognition
    amount * 0.97 as net_revenue
-- keystone:end

Whole-line -- comments inside such a region are kept out of the semantic hash, so editing one is C4 and clears with keystones fix and no note. A repo that gated regions on keystones 0.3 runs keystones migrate once after upgrading, for any region that holds a comment.

Macros

A {% macro %} in a model is refused. Files under macros/ are SQL fragments that no dialect parses as statements; gate them with keystone(file) or a region on hash=text.

Metadata

Release files for keystones-dbt 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for keystones-dbt 0.1.1
File Size Uploaded
keystones_dbt-0.1.1.tar.gz 17.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for keystones-dbt 0.1.1
File Interpreter ABI Platform
keystones_dbt-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 26.6 kB

Release files / keystones_dbt-0.1.1.tar.gz

Download URL keystones_dbt-0.1.1.tar.gz
Size 17.0 kB
Tags Source
SHA-256 checksum
How to use checksums
06469cc44b035f135f395d0439d96dcf6bd32c3389446258a846235d8ec53128
BLAKE2b-256 checksum
How to use checksums
4698ef5d2225978ef5186120c635b5bb5ce125fac533a5e41d354940bd263253
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.19 {"installer":{"name":"uv","version":"0.12.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / keystones_dbt-0.1.1-py3-none-any.whl

Download URL keystones_dbt-0.1.1-py3-none-any.whl
Size 9.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
33aee806dd8390ae07fc6c60bd04f28983b70e5db160d8092df8af6672ac2913
BLAKE2b-256 checksum
How to use checksums
c865dcb12961b38dc1fc937f2f39bab9523822cb2ddfc5ae3a8df95ec50a7a46
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.19 {"installer":{"name":"uv","version":"0.12.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

0.2.0

2 release files

This release

0.1.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page