Python API
Everything the command line and the MCP server do is available from Python, through one class,
konstruct.Project, and typed results.
import konstruct
project = konstruct.Project('path/to/repo')
project.build(update=True)
walk = project.impact('stg_payments.amount')
print(walk.summary()) # 7 columns, 3 relations, 5 tests, 1 exposure, 2 tasks and 2 business terms downstream.
for column in walk.columns:
print(column.depth, column.label, column.transform, column.path)
Every result is a dataclass. str(result) is the text the CLI prints to a pipe, result.text(style)
the same result drawn for a terminal (with a konstruct.interfaces.layout.Style giving colour,
Unicode and width), and result.to_dict() the JSON that --json and the MCP server return, so a
script can move between them freely (--json and MCP add the result’s format_version, see
Compatibility). A name that matches nothing, or several things, raises
konstruct.QueryError, whose suggestions are the closest names and hint a command to try,
and a graph.json written by a newer Konstruct raises konstruct.FormatError.
konstruct.Project, the result types it returns and konstruct.QueryError are imported from
konstruct itself. The modules behind them are grouped by role, for code that needs one directly:
Package |
Modules |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Opening and Building a Project
A Project points at a repository. It reads nothing until asked: build()
writes konstruct-out/, and every question reads the graph from there, rereading it whenever a
build changes it.
project = konstruct.Project('analytics') # or any directory inside a built project
if not project.has_graph() or project.changed_inputs():
result = project.build(update=True) # reuse the analysis of anything unchanged
print(result.inputs) # {'dbt_projects': 1, 'warehouses': 1, ...}
print(result.graph.warnings) # problems with the inputs, if any
The repository’s own warehouse connections and paths outside it are used, and dbt is run, once
trust() has recorded them, as konstruct trust does (see
Trusting a Project’s Connections).
- class konstruct.Project(path='.', *, out_dir=None, plugins=True)[source]
Bases:
objectA repository Konstruct builds a graph for, and every question that can be asked of it.
Creating a
Projectreads nothing.build()writes the graph; the question methods read it, rereadinggraph.jsonwhenever a build changes it, and raiseQueryErrorwhen there is no graph or a name matches nothing (or several things). Names are accepted as the CLI accepts them:orders,main.orders,orders.amount, a dbt source assource_name.table, a task asdag_id.task_id, or a node id.Every result is a dataclass:
str(result)is the CLI’s text andresult.to_dict()the JSON that--jsonand the MCP server return, which add itsformat_version. Agraph.jsonwritten by a newer Konstruct raisesFormatErrorrather than being misread.- Parameters:
path (str | Path) – The project root, or any directory inside one that has already been built.
out_dir (str | Path | None) – Where the graph is written and read. By default, the
konstruct-outdirectory found at or abovepath, or<path>/konstruct-out.
Example:
project = Project('analytics') if not project.has_graph() or project.changed_inputs(): project.build(update=True) print(project.explain('orders.amount').composed)
- Project.build(*, update=False, run_dbt=None, warehouses=True, dialect=None, config=None)[source]
Build the graph and write
graph.json,REPORT.mdandgraph.html.Discovers dbt projects, warehouses, Airflow and Dagster files, SQL files and definitions (or reads what the configuration pins), analyses the SQL and writes the outputs. Problems with the inputs do not fail the build; they are recorded in
result.graph.warnings.- Parameters:
update (bool) – Reuse the cached analysis of anything unchanged since the last build.
run_dbt (bool | None) – Run
dbt docs generatewhen a project’s artefacts are missing or stale.Nonefollows the configuration (on by default).warehouses (bool) – Read warehouse metadata.
Falsebuilds from files alone.dialect (str | None) – The SQL dialect, overriding the one taken from dbt or the warehouse.
config (Config | None) – A configuration to build with instead of reading
konstruct.toml.
- Returns:
The graph, the files written, the time taken, cache use and counts of what was read.
- Return type:
Example:
result = project.build(update=True, warehouses=False) print(result.inputs, result.graph.warnings)
- Project.changed_inputs()[source]
Input files that changed after the graph was built.
- Returns:
Paths relative to the project root; empty when the graph is up to date or not built.
- Return type:
list[str]
- Project.config()[source]
The project’s configuration:
konstruct.toml(or[tool.konstruct]inpyproject.toml) over the defaults, with nothing discovered yet.- Raises:
konstruct.engine.config.ConfigError – The configuration file is invalid.
- Project.plugins()[source]
The plugin registry for this project: Konstruct’s own inputs, export formats, gate checks and ontology packs, and every installed plugin with whether it runs here and why.
Example:
for plugin in project.plugins().installed(): print(plugin.distribution, plugin.group, plugin.name, plugin.enabled, plugin.reason)
- Project.trust(*, revoke=False)[source]
Trust the project’s own connections and paths, as they are now, and let the build run dbt.
A repository’s
konstruct.tomlandprofiles.ymlcan name warehouses, read environment variables into them and point outside the project, and dbt runs the repository’s macros and models. The build uses those settings, and runs dbt, only once the project is trusted, and any change to the settings withdraws the trust until they are trusted again. The record does not cover the dbt project’s code. It is kept outside the repository, in the user’s configuration directory.- Parameters:
revoke (bool) – Remove the record instead.
- Returns:
Whether the project is now trusted, the files the trust covers, the environment variables their connections read and the dbt projects it lets the build run dbt in.
- Return type:
Example:
print(project.trust()) # after reviewing konstruct.toml and profiles.yml
- property Project.graph: Graph
The built graph, reread whenever
graph.jsonchanges on disk.- Raises:
konstruct.QueryError – No graph has been built.
- property Project.root: Path
The directory the graph was built from (
pathuntil a graph exists).
- property Project.graph_path: Path
The path of
graph.jsonin the output directory.
Impact, Trace and Paths
downstream = project.impact('orders.amount')
upstream = project.trace('customer_ltv.lifetime_value')
for column in upstream.columns:
print(column.depth, column.label, column.transform, column.verified)
path = project.path_between('raw_payments', 'customer_dashboard')
print(' -> '.join(step.label for step in path.steps))
- Project.impact(ref, max_depth=None)[source]
Everything downstream of a table, model or column: what a change to it affects.
For a column: the columns derived from it, the relations that join, filter or group on it, and the tests, exposures, orchestration tasks and business terms on anything reached. A join key reaching a relation does not mark every column of that relation as affected.
- Parameters:
ref (str) – A table, model or column, such as
ordersorstg_payments.amount.max_depth (int | None) – Stop after this many steps.
- Returns:
The walk, grouped into columns, relations, tests, exposures, tasks and terms.
- Raises:
konstruct.QueryError –
refmatches nothing or several things.- Return type:
Example:
walk = project.impact('stg_payments.amount') walk.summary() # '7 columns, 3 relations, 5 tests, 1 exposure, 2 tasks and ...' [column.label for column in walk.columns if column.transform == 'direct']
- Project.trace(ref, max_depth=None)[source]
Everything upstream of a table, model or column: where its data comes from.
Each column step is marked
direct(passed through or renamed) ortransformed. For a column,filtered_bylists the columns that join or filter its relation, and the tasks that build each relation on the way are included.- Parameters:
ref (str) – A table, model or column.
max_depth (int | None) – Stop after this many steps.
- Returns:
The walk upstream.
- Raises:
konstruct.QueryError –
refmatches nothing or several things.- Return type:
Example:
for column in project.trace('customer_ltv.lifetime_value').columns: print(column.depth, column.label, column.transform, column.verified)
- Project.path_between(start, end)[source]
The shortest connection between two nodes, along data flow where there is one.
- Parameters:
start (str) – Where the path starts.
end (str) – Where it ends.
- Returns:
The steps from
starttoend;result.foundis False when they are not connected.- Raises:
konstruct.QueryError – Either name matches nothing or several things.
- Return type:
Example:
path = project.path_between('raw_payments', 'customer_dashboard') [step.label for step in path.steps]
Finding and Describing Nodes
project.search('lifetime value', kinds=['column']).results
project.search('revenue', mode='semantic').results # by meaning: synonyms, formulas, lineage
project.describe('orders').columns
project.list(['model', 'table']).items
project.query('what breaks if stg_payments.amount changes').impact
- Project.search(text, limit=20, kinds=None, mode='keyword')[source]
Find nodes whose names, synonyms or descriptions match the words in
text, or, withmode='semantic', that match them by meaning.Words matching a name count for more than words in a description, rarer words for more than common ones, and business terms rank above columns with the same words. Search by meaning also finds a node through the abbreviations and synonyms of data work, its formula and its lineage (see
konstruct.analysis.semantic.search()), with exact names still first; its index is made on first use and saved asvectors.binin the output directory.- Parameters:
text (str) – The words to look for.
limit (int) – The most results to return.
kinds (list[str] | None) – Only these node kinds, such as
['column']or['model', 'table'].mode (Literal['keyword', 'semantic']) –
keyword(the default) orsemantic.
- Returns:
Matching nodes, best first, each with a
score, and in a search by meaning what the match rests on (matched).- Return type:
Example:
project.search('lifetime value', kinds=['column']).results[0].label project.search('revenue', mode='semantic').results[0].matched
- Project.describe(ref)[source]
One node in detail.
For a relation: its columns with types and descriptions, tests, the joins, filters and grouping that shape its rows, and its immediate neighbours. For a column: its expression and the business terms that define it. For a task: what it runs and the tasks around it. For a business term: its definition, owner, status and columns.
- Parameters:
ref (str) – Any node: a relation, column, task, DAG, test, exposure or term.
- Raises:
konstruct.QueryError –
refmatches nothing or several things.
Example:
orders = project.describe('orders') [(c.name, c.data_type) for c in orders.columns] orders.conditions # ['GROUP BY: stg_payments.order_id', 'LEFT JOIN ON: ...']
- Project.list(kinds=None)[source]
Every node of the given kinds.
- Parameters:
kinds (list[str] | None) – Node kinds such as
['model']or['task', 'dag']. By default, tables, models, sources, seeds and snapshots.
Example:
[item.label for item in project.list(['model']).items]
- Project.query(question, depth=1)[source]
Answer a question in words with the nodes it is about and their connections.
Ranks nodes by the question’s words (there is no language model), returns the best matches with the edges around them, leads with the definitions of any business terms matched, and adds impact or lineage when the question asks what breaks or where something comes from.
- Parameters:
question (str) – The question, such as
what breaks if stg_payments.amount changes.depth (int) – How many steps of connections around each match to include.
Example:
answer = project.query('what is lifetime value') answer.definitions[0].definition
Formulas, Definitions, Conflicts and the Ontology
explanation = project.explain('orders.credit_card_amount')
print(explanation.composed)
# SUM(CASE WHEN raw_payments.payment_method = 'credit_card' THEN raw_payments.amount / 100 ELSE 0 END)
print(project.english('orders.credit_card_amount').english)
# the sum of ([amount] divided by 100 if [payment method] is 'credit_card', else 0)
term = project.define('lifetime value').terms[0]
print(term.definition, term.owner, [column.label for column in term.columns])
for conflict in project.conflicts().conflicts:
print(conflict.idea, conflict.a.label, conflict.b.label, [d.detail for d in conflict.differences])
history = project.history('customers.customer_lifetime_value')
print([(change.commit.author, change.before, change.after) for change in history.formulas])
print(history.last_approval.commit.author if history.last_approval else None)
ontology = project.ontology()
print([(entity.name, entity.key) for entity in ontology.entities])
print(ontology.measure('customer lifetime value').columns[0].formula)
- Project.explain(ref)[source]
How a column is calculated.
Returns the column’s expression with CTEs, subqueries and aliases inlined, the same formula composed through every model upstream until only source columns remain, and step by step each column on the way with the joins, filters and grouping of its relation.
- Parameters:
ref (str) – A column, such as
orders.credit_card_amount.- Returns:
The explanation.
composedis None for a source column, andcompleteis False when some column on the way has no recorded expression.- Raises:
konstruct.QueryError –
refis not a column, or matches nothing or several things.- Return type:
Example:
explanation = project.explain('customer_ltv.lifetime_value') explanation.expression # 'customers.customer_lifetime_value' explanation.composed # 'SUM(raw_payments.amount / 100)' [step.conditions for step in explanation.steps]
- Project.english(ref)[source]
A column’s formula in plain English, worded from the formula by a fixed grammar.
The formula is composed down to source columns, as
explain()gives it, and each kind of expression has one wording, with brackets where the SQL’s grouping matters, so the sentence says what the SQL says and nothing more. A part with no wording is quoted as SQL and listed inverbatim. The joins, filters and grouping of every relation on the way, the grain of the column’s relation and the required filters of each bound business term are worded the same way.- Parameters:
ref (str) – A column, such as
orders.credit_card_amount.- Returns:
The sentence, the conditions of each relation in English, the grain, the bound terms and the column each wording names.
- Raises:
konstruct.QueryError –
refis not a column, or matches nothing or several things.- Return type:
Example:
result = project.english('orders.credit_card_amount') result.english # "the sum of ([amount] divided by 100 if [payment method] is 'credit_card', else 0)" [(n.wording, n.column) for n in result.names] # [('[amount]', 'raw_payments.amount'), ...]
- Project.history(ref, *, limit=50)[source]
When a column’s formula changed, and who changed the business terms bound to it.
Reads the git history of the file that defines the column and of each
definitions.ymlthat binds a term to it. The formula is worked out at each commit, so a commit that only reformats the SQL is not a change; uncommitted edits are the last entry.- Parameters:
ref (str) – A column, such as
customers.lifetime_value.limit (int) – The most commits to read for each file.
- Returns:
The formula changes and term changes, oldest first, each with its commit’s author and date, and whether a bound term was approved after the latest formula change.
- Raises:
konstruct.QueryError –
refis not a column, or matches nothing or several things, orlimitis below 1.- Return type:
Example:
history = project.history('customers.lifetime_value') [(c.commit.author, c.before, c.after) for c in history.formulas] history.last_approval.commit.author if history.last_approval else None
- Project.define(ref)[source]
The business definition of a term, or the terms that define a column.
Looks
refup as a term name or synonym first (case and spacing ignored), then as a column or relation, returning the terms bound to it.- Parameters:
ref (str) – A term (
lifetime value), a synonym (LTV) or a column (orders.amount).- Returns:
The matching terms, approved first and retired last, with their definition, owner, status, grain, required filters and columns.
termsis empty when a column is not bound to any term.- Raises:
konstruct.QueryError – Nothing matches
ref, or it names several columns.- Return type:
Example:
term = project.define('lifetime value').terms[0] term.status, term.owner, [c.label for c in term.columns]
- Project.conflicts(limit=None, *, write=False)[source]
Columns that appear to be one business idea computed in different ways.
Computed columns are grouped by what their names mean once qualifiers and synonyms are folded together, by business-term synonyms and by shared terms, then compared on their formulas in terms of sources, their source columns, and the filters and joins on their routes back to source. Pairs that agree, or where one is derived from the other, are left out.
- Parameters:
limit (int | None) – The most conflicts to return.
write (bool) – Also write
CONFLICTS.mdandconflicts.jsonbeside the graph, and show the conflicts ingraph.html.
- Returns:
The conflicts, ranked by what depends on each side downstream.
- Return type:
Example:
for conflict in project.conflicts().conflicts: print(conflict.a.label, conflict.b.label) for difference in conflict.differences: print(' ', difference.kind, difference.detail, difference.path)
- Project.ontology()[source]
The ontology of the data stack, derived from the graph.
Entities come from identifier-like columns with uniqueness tests, single-column primary keys and identifier fields, terms of kind
entityand the grains of measures; measures are business terms with a formula, each bound column with its formula composed down to source and how many edges on the way are verified; relationships come from dbtrelationshipstests and from joins that match a column to an entity’s key. The rules list what breaks the ontology now.konstruct buildwrites the same toontology.yml. It is derived once and kept, forentity(),metric()andrelate()too, until the graph is reread, gains a node or an edge, or itsmetachanges; after editing a node or an edge in place, callgraph.changed(). Each call returns a copy of its own.- Returns:
The entities, measures, relationships and rules.
- Return type:
Example:
ontology = project.ontology() [(e.name, e.key, e.relations) for e in ontology.entities] ontology.measure('customer lifetime value').columns[0].formula ontology.violations # [] when every rule holds
- Project.entity(name)[source]
One business entity: how it is identified, what describes it, how it relates to the others and what is measured about it.
- Parameters:
name (str) – The entity’s name or its term’s (
customer,Customer,customers); case, spaces and underscores are not told apart, and a plural finds the singular.- Returns:
The entity with its key, the relations holding it, its confidence and evidence; its properties (the columns that describe it, with their descriptions, types and terms); the relationships it takes part in; and the metrics whose grain it is.
- Raises:
konstruct.QueryError – No entity has that name.
- Return type:
Example:
customer = project.entity('customer') customer.entity.confidence # 'verified' once konstruct verify confirmed its key [p.name for p in customer.properties]
- Project.metric(name=None, limit=None)[source]
The business metrics, or those a name finds, each with its definition, formula, grain, filters, owner, status and confidence.
Metrics are the ontology’s measures, those dbt’s semantic layer, Looker and Power BI define, and proposals Konstruct reads from the code where nothing defines one: aggregate columns with a grain, numeric columns named as measures and the count of each entity. A proposal is
inferred, has no status and is never approved.- Parameters:
name (str | None) – A metric’s name, or a column that holds it (
orders.amount); all of them when left out.limit (int | None) – At most this many metrics;
totalstill counts every one found.
- Returns:
defined ones first, then proposals.
- Return type:
The metrics found
- Raises:
konstruct.QueryError –
namefinds no metric.
Example:
[m.name for m in project.metric().metrics if m.proposed] project.metric('lifetime value').metrics[0].formula
- Project.relate(start, end)[source]
How two business entities connect: the relationships between them, the cardinality and confidence of the path, and the SQL joins that follow it.
- Parameters:
start (str) – The entity to start from, named as
entity()takes it.end (str) – The entity to reach.
- Returns:
The shortest path of relationships, the most trusted of the shortest, with whether it fans out (
one-to-many: aggregate before joining) and the joins;foundis false when no relationship connects them.- Raises:
konstruct.QueryError –
startorendis no entity.- Return type:
Example:
connection = project.relate('customer', 'payment') print(connection.join) # FROM ... AS customers JOIN ... AS orders ON ...
- Project.sensitivity(ref=None)[source]
Where sensitive data is, where the column lineage takes it and where a transform stops it.
A column is marked sensitive by a business term with a
sensitivity(indefinitions.ymlor an ontology pack) that binds it, or by a warehouse tag or masking policy on it. Every column computed from a sensitive column is sensitive too, a hashed one as pseudonymised, unless every read of it is inside a function[sensitivity] sanitisersnames or an aggregate over groups the SQL requires to hold at least[sensitivity] krows. Built relations and exposures that receive it are checked against[sensitivity] holders.- Parameters:
ref (str | None) – A table, model or column to report on alone. By default, the whole graph.
- Returns:
The sensitive columns with their marks, sources and paths, the reads a sanitiser stops, the exposures that show the data, the columns that only join or filter, the unmasked copies of masked columns, and the relations whose lineage is not known.
- Raises:
konstruct.QueryError –
refmatches nothing, or several things.konstruct.engine.config.ConfigError –
[sensitivity]in konstruct.toml is invalid.
- Return type:
Example:
result = project.sensitivity() [(c.label, c.sensitivity, c.held) for c in result.unheld]
- Project.erasure(ref, *, levels=3)[source]
Where one person’s data ends up: every relation holding their rows, for an erasure request.
Follows the key up and down the column lineage (copies, transformed copies and hashed copies), across every key column of its entity, through the foreign keys that refer to it, and on to the rows the person owns (an order’s payments), as the ontology derives them.
- Parameters:
ref (str) – A key column (
customers.customer_id), an entity (customer) or a relation, for the key of its entity.levels (int) – How many steps of owned rows to follow.
- Returns:
Each relation with the columns holding the key and how, its sensitive columns, and the action it needs (
delete,rebuild,full refreshornonefor a view), and the exposures that show the data.- Raises:
konstruct.QueryError –
refmatches nothing, several things, or a relation with no key.- Return type:
Example:
for holding in project.erasure('customer').holdings: print(holding.relation, holding.action, [k.column for k in holding.keys])
- Project.compile_metric(metric, *, by=(), where=(), since=None, until=None, time=None, dialect=None)[source]
A metric, grouped and filtered by the business’s entities and properties, compiled to SQL without running it.
The SQL reads the metric from the relation that holds it and joins only along relationships the project shows many-to-one or one-to-one, each onto a key a test, a primary key something enforces or
konstruct verifyshows unique; it refuses to add up a value its lineage shows copied from another grain, and it always applies the filters the metric’s definition requires. What it rests on is what the project states and Konstruct reads: a key that repeats despite its test can still repeat rows. The result lists every fact it rests on with its confidence, and flags those that are only inferred, and awherecondition on a joined property, which leaves out the rows that find no match. A query whose joins could repeat rows, that rests on a checkkonstruct verifyfailed, or that asks for what cannot be placed is refused, with the reason and the link, and has no SQL; so is a condition that is not one SQL condition, a date that is notYYYY-MM-DDand a dialect sqlglot does not know.- Parameters:
metric (str) – The metric’s name or synonym, or a column holding it, as
metric()takes it.by (Sequence[str]) – Entities (
customer, for its key), properties (customer.first_name, orfirst_namewhen one entity reached has it) and time grains (month, ororder_date:month) to group by.where (Sequence[str]) – Conditions on properties, as SQL (
customer.first_name = 'Michael').since (str | None) – The first date covered,
YYYY-MM-DD.until (str | None) – The date after the last one covered.
time (str | None) – The property that dates the metric, for a grain or a range; by default the one date or time column of the metric’s relation.
dialect (str | None) – The SQL dialect to write, by default the project’s.
- Returns:
The SQL, the relationships its joins follow, the required filters it added, the facts with their confidence, the flags and anything refused.
- Raises:
konstruct.QueryError –
metricfinds no metric.- Return type:
Example:
result = project.compile_metric('lifetime value', by=['customer.first_name']) print(result.sql) result.confidence, result.flags, result.refused
Checking SQL
result = project.check_sql(open('query.sql').read())
if not result.passed:
for finding in result.findings:
print(finding.severity, finding.rule, finding.message, finding.fix)
print(result.corrected) # the SQL with the safe fixes applied, or None
history = project.check_history(days=30, pseudonymise=True)
for metric in history.shadow:
print(metric.term, metric.detail, metric.queries, metric.users)
- Project.check_sql(sql)[source]
Check SQL against the project’s definitions, keys and relationships, without running it.
Each statement is read in the graph’s dialect. Every output column that aggregates is a measure: its formula is composed down to source columns, with the filters on the way, and compared with the business term it is named for or the column of the project that already holds it. The checks find a measure that leaves out a filter its definition requires or computes it differently, a column of a retired term, a sum, count or average over a join that repeats the rows it aggregates, a measure of a coarser grain repeated on the rows of a finer one, a join off the columns of a relationship, and a relation or column the project does not have. Each finding carries its fix, and
correctedholds the SQL with every fix that changes nothing else applied.- Parameters:
sql (str) – One or more statements.
- Returns:
The findings, errors first.
passedis True when every statement was analysed (checked) and nothing breaks an approved definition.- Return type:
Example:
result = project.check_sql('select region, sum(amount) as revenue from orders group by 1') result.passed [(f.rule, f.fix) for f in result.findings] result.corrected
- Project.check_history(*, days=None, limit=5000, pseudonymise=False)[source]
Run
check_sql()over the query history of the project’s Snowflake, BigQuery and Databricks warehouses, and gather the queries that compute a measure differently from its definition (shadow metrics), with how many queries, runs and people each has.Reads history metadata only, over the project’s own connections, so the project must be trusted (
konstruct trust).- Parameters:
days (int | None) – How far back to read, by default
[usage] daysinkonstruct.toml.limit (int) – The most distinct query texts to read and check, those run most often first.
pseudonymise (bool) – Count people without naming them, and leave the literals out of the example queries.
- Returns:
The shadow metrics, approved terms first, and how often each term is read through its own columns.
- Raises:
konstruct.QueryError –
daysorlimitis below 1, no such warehouse is configured, the project is not trusted, or the warehouse refuses the history.- Return type:
Example:
history = project.check_history(days=30, pseudonymise=True) [(s.term, s.detail, s.queries, s.users) for s in history.shadow]
Reviewing Changes and Checking Lineage
change = project.diff(base='origin/main')
if change.breaking:
for relation, column in change.breaking:
print(f'{relation}.{column.column} is still read by {", ".join(column.breaks)}')
print(change.selection.command()) # dbt build --select <only what changed>
result = project.verify(seed=1729)
print(result.totals()) # {'claimed': 36, 'confirmed': 36, ...}
tests = project.mutate(models=['orders'], max_mutants=10)
print(tests.score) # killed / (killed + survived)
for survivor in tests.models[0].survivors:
print(survivor.mutant.description, survivor.suggestions[:1])
rows = project.why('customer_ltv.lifetime_value', {'customer_id': '3'})
print(rows.value, [c.values for c in rows.contributions])
notes = project.release_notes('v1.4')
print(notes.markdown())
effects = project.effect('v1.4', models=['customers'])
print([(c.headline(), c.effect.summary()) for c in effects.changes if c.effect])
timeline = project.timeline('v1.4') # how the business's meaning changed, commit by commit
print([(e.commit.by, [c.headline() for c in e.changes]) for e in timeline.entries])
print(project.timeline_effect('v1.4').release_notes())
receipt = project.receipt(['customer_ltv.lifetime_value'], question='LTV of customer 3?')
print(receipt.citation)
print([(r.id, r.status) for r in project.check_receipts().receipts])
laws = project.laws(confirm=True) # writes konstruct-out/laws.json, which the gate reads
for law in laws.laws:
print(law.status, law.statement, law.detail)
project.lock() # pin the upstream columns this project reads in konstruct.lock
pins = project.check_lock()
for pinned in pins.changes:
print(pinned.column, pinned.reasons, pinned.accept)
- Project.diff(base=None, head=None)[source]
What a change does to the data stack, column by column.
Compares each changed dbt model, SQL file, DAG, dbt YAML file and definitions file between
baseandhead. Columns are compared by their canonical expressions, so formatting and renamed CTEs do not count as changes. A removed column that something downstream still reads is a breaking change.- Parameters:
base (str | None) – The commit to compare with. By default, the merge base with
main.head (str | None) – The commit to compare. By default, the working tree.
- Returns:
The changes, with
breaking, the downstream impact of each, and a column-awaredbt build --selectinselection.- Raises:
konstruct.analysis.diff.DiffError – The project is not in a git repository, or a commit is unknown.
- Return type:
Example:
change = project.diff(base='origin/main') for relation, column in change.breaking: print(relation, column.column, column.breaks) print(change.selection.command())
- Project.gate(base=None, *, update=True, run_dbt=None, warehouses=True)[source]
The check a change must pass before it is proposed.
Brings the graph up to date with the working tree, compares with
baseasdiff()does, and reports what stops the change: a removed column something still reads, an approved term whose meaning changes (in definitions.yml or through a column it names), a column the change alters that computes a business idea differently from another (asconflicts()finds), a data law inlaws.jsonthat held on all the data and that the changed SQL no longer gives (seelaws()), a column the change alters that a repository inrepositoriespins in itskonstruct.lock(seelock()), sensitive data the change moves into a model or exposure not marked to hold it, a masked column it copies in clear and a sensitivity mark it takes away (assensitivity()finds, with the stricter of the base’s and the working tree’s[sensitivity]settings, and no mark the change itself adds counting as marking what it alters to hold the data), and the findings of the gate checks of the plugins the project enables. The plugins enabled atbaserun as well as those enabled now, in the update and in the checks, so a change cannot turn off a plugin that judges it. Each finding says what to do about it.- Parameters:
Example:
result = project.gate() if not result.passed: for finding in result.findings: print(finding.rule, finding.message, finding.fix)
- Project.verify(*, warehouse=None, seed=1729, max_rows=10000, models=None, write=True, ontology=True)[source]
Check the graph’s column lineage against the data.
For every dbt model and SQL-built table, copies its inputs (DuckDB), works inside a transaction that is rolled back (Postgres), or copies a sample of each input into an in-memory DuckDB database (Snowflake, BigQuery, Databricks, Trino), perturbs one input column at a time by shifting, nulling and shuffling it, reruns the model’s SQL and records which outputs change. A claimed edge is confirmed when its output changes; an observed influence the graph does not claim is reported as unexplained.
- Parameters:
warehouse (str | Path | None) – A DuckDB file, a
postgresql://URL, orsnowflake,bigquery,databricksortrinofor the configured warehouse of that type, sampled. By default, the DuckDB or Postgres warehouse the build read.seed (int) – The random seed, so a run can be repeated exactly.
max_rows (int) – The most rows sampled from any input.
models (Sequence[str] | None) – Only these models or tables; the ontology’s keys and relationships are checked only where all their tables are among them.
write (bool) – Write
VERIFY.mdandverify.json, and mark verified edges ingraph.json,graph.html,explain.jsonandontology.yml.ontology (bool) – Also check each key and relationship of the ontology over its whole tables, on DuckDB and Postgres;
Falsereads only what the lineage check needs.
- Returns:
One result per model, with
totals()across them.- Raises:
konstruct.QueryError – No warehouse was found.
- Return type:
Example:
result = project.verify(seed=7) result.totals() # {'claimed': 36, 'confirmed': 36, 'unexplained': 0, ...}
- Project.mutate(*, warehouse=None, seed=1729, max_rows=1000, max_mutants=30, operators=None, models=None, suggest=True, write=True)[source]
Mutation test the project’s dbt tests: would they fail if a model were wrong?
For each dbt model, makes small semantic mutants of its compiled SQL (an aggregate swapped, a filter or join condition dropped or negated, a join type, a comparison or a date moved, a COALESCE removed, a CASE branch dropped), runs each in verify’s read-only engines on a sample of the model’s inputs, and runs the dbt data tests and unit tests that read the model against it. A mutant is killed when a test fails on it. For each survivor, a test that would have killed it is suggested.
- Parameters:
warehouse (str | Path | None) – As for
verify().seed (int) – The seed of the mutant sample, so a run can be repeated exactly.
max_rows (int) – The most rows sampled from any input.
max_mutants (int) – The most mutants run per model.
operators (Sequence[str] | None) – The kinds of mutation to make (by default all of
konstruct.core.mutate.OPERATORS).models (Sequence[str] | None) – Only these models.
suggest (bool) – Find, for each survivor, a test that would kill it.
write (bool) – Write
MUTATION.mdandmutation.json.
- Returns:
One result per model, with
totals()andscoreacross them.- Raises:
konstruct.QueryError – No warehouse was found, or an operator is unknown.
- Return type:
Example:
result = project.mutate(models=['orders'], max_mutants=10) result.score # killed / (killed + survived)
- Project.why(column, where, *, warehouse=None, limit=50, max_rows=1000000)[source]
The source rows behind one value of a column, each with its share, and the related rows a filter or a join left out, with the reason.
The value is the row of the column’s model that
wherepicks ({'customer_id': '42'}). Runs read-only in verify’s engines: the model’s SQL is composed down to source tables, each source is copied for the key alone where that is exact, and the computing step is rerun with each row carrying the ids of its source rows.- Parameters:
column (str) – A column, such as
customer_ltv.lifetime_value.where (Mapping[str, str]) – Output columns of the model and the values that pick one row.
warehouse (str | Path | None) – As for
verify().limit (int) – The most rows listed in each table.
max_rows (int) – The most rows copied from any source.
- Returns:
The value, its contributing rows with their shares, and the rows left out. Values of sensitive columns (see
sensitivity()) are masked, and so is every share when any column the value is computed from is sensitive;maskednames what was masked.- Raises:
konstruct.QueryError – The column is not built by SQL that verify can rerun, the key does not lead to the rows that compute it, or the SQL is of a shape whose rows cannot be traced.
- Return type:
Example:
result = project.why('customer_ltv.lifetime_value', {'customer_id': '3'}) result.value, [c.values for c in result.contributions]
- Project.release_notes(since=None, *, until=None, limit=200)[source]
The changes to formulas, rows and business terms in a range of commits, newest first.
Each dbt model and SQL file a commit changed is compared with its parent by canonical expressions, as
diff()compares a branch, so reformatting is not a change. Each changed column, and each relation whose joins, filters or grouping changed, is an entry with the formula or conditions before and after as SQL and in plain English, the commit’s author and date, what it affects downstream (columns, exposures and their owners, business terms), and for each business term bound to it or downstream of it the commit that approved the term after the change, if one has. Changes to the business terms themselves are entries too. Reads only git and the graph.- Parameters:
since (str | None) – A commit, tag or date (
2026-10-01) the range starts after; by default the latest tag, or the whole history when there is none.until (str | None) – A commit, tag or date the range ends at (default HEAD).
limit (int) – The most commits read.
- Returns:
The entries, newest commit first.
markdown()gives them as Markdown to post.- Raises:
konstruct.QueryError –
sinceoruntilis not a commit, tag or date, the project is not in a git repository, orlimitis below 1.- Return type:
Example:
notes = project.release_notes('v1.4') [(c.commit.author, c.headline(), c.after_english) for c in notes.changes] print(notes.markdown())
- Project.effect(since=None, *, until=None, warehouse=None, models=None, limit=200, max_rows=1000000)[source]
The release notes, with what each formula and row change does on today’s data.
For each change, the changed model’s SQL as it was before the commit and as it was after are run on the same data, the warehouse as it is now, so the difference is the definition’s alone. Runs read-only in verify’s engines: each version must be a single query that only reads, the tables it reads are copied, and nothing is written to the warehouse.
- Parameters:
since (str | None) – As for
release_notes().until (str | None) – As for
release_notes().warehouse (str | Path | None) – As for
verify().models (Sequence[str] | None) – Only changes to these models or tables, by name or label.
limit (int) – As for
release_notes().max_rows (int) – The most rows copied from any table.
- Returns:
the rows each version returns and, for the changed column (or for a change to the rows, each column), its sum in each and, with a key, the rows whose value changed. A version whose templating cannot be rendered as it would be run is not run, and its effect says why.
- Return type:
The release notes, each formula and row change with its
effect- Raises:
konstruct.QueryError – As for
release_notes(), or no warehouse is found.
Example:
notes = project.effect('2026-10-01') [(c.headline(), c.effect.summary()) for c in notes.changes if c.effect]
- Project.timeline(since=None, *, until=None, paths=(), limit=50)[source]
How the business’s meaning changed, commit by commit.
Builds the world (entities, relationships, properties and metrics) at each commit from that commit’s own files and compares it with the world at the commit before: what was added, removed, renamed or changed, a formula or the rows a metric is computed over, required filters, trust, and each definition’s status, owner and approval date, with the commit’s author, co-authors and date and in plain English. A commit that changes none of that, such as one that only reformats SQL, has no entry. dbt models are rendered with each commit’s macros and today’s manifest; nothing runs dbt or connects to a warehouse. Each commit’s world is kept in the user’s cache directory, so a second call builds only new commits.
- Parameters:
since (str | None) – A commit, tag or date (
2026-10-01) the range starts after; by default the whole history.until (str | None) – A commit, tag or date the range ends at (default HEAD).
paths (Sequence[str]) – Only commits that changed these files or directories, relative to the project.
limit (int) – The most commits read, the most recent.
- Returns:
The entries, oldest first;
release_notes()gives a sentence for each metric change andmarkdown()the whole as Markdown to post.- Raises:
konstruct.QueryError –
sinceoruntilis not a commit, tag or date, the project is not in a git repository, orlimitis below 1.- Return type:
Example:
timeline = project.timeline('v1.4') [(e.commit.author, [c.headline() for c in e.changes]) for e in timeline.entries] timeline.release_notes()
- Project.timeline_effect(since=None, *, until=None, paths=(), limit=50, warehouse=None, max_rows=1000000)[source]
The timeline, with what each change to a metric’s formula or rows does on today’s data.
For each such change, the change in the same commit to one of the metric’s columns or relations, or to one upstream of them, is run as it was before the commit and as it was after on the same data, as
effect()runs it: read-only in verify’s engines, the tables it reads copied, nothing written to the warehouse.- Parameters:
since (str | None) – As for
timeline().until (str | None) – As for
timeline().paths (Sequence[str]) – As for
timeline().limit (int) – As for
timeline().warehouse (str | Path | None) – As for
verify().max_rows (int) – The most rows copied from any table.
- Returns:
The timeline, each metric change of formula or rows with its
effect, and its release notes saying what the change does on today’s data.- Raises:
konstruct.QueryError – As for
timeline(), or no warehouse is found.- Return type:
Example:
timeline = project.timeline_effect('v1.4') timeline.release_notes()
- Project.receipt(refs, *, question=None, answer=None, write=True)[source]
A receipt for an answer: what it rests on, recorded so that
check_receipts()can say later whether that still holds.For each column
refsnames (directly, or through a relation, an exposure or a business term), the receipt records its formula composed down to source columns, the joins, filters and grouping that decide its rows, its source columns, its type, the terms bound to it with their status and owner, and whetherkonstruct verifyconfirmed its lineage; and the build it answered from. It is appended tokonstruct-out/receipts/<date>.jsonlwith a digest of its contents.- Parameters:
refs (Sequence[str]) – What the answer rests on: columns, relations, exposures or business terms.
question (str | None) – The question answered, recorded as given.
answer (str | None) – The answer given, recorded as given.
write (bool) – False to make the receipt without recording it.
- Returns:
The receipt, with
citation, a paragraph to put under the answer.- Raises:
konstruct.QueryError – A name matches nothing or several things, or names something an answer cannot rest on, or
refsis empty.- Return type:
Example:
receipt = project.receipt(['customers.customer_lifetime_value'], question='LTV of 3?') print(receipt.citation)
- Project.receipts(ids=None)[source]
The receipts issued for this project, in the order issued, or those
idsname (each an id, or the start of one, such asr-7f3a).- Raises:
konstruct.QueryError – An id names no receipt, or several, or a receipt it names does not match its digest or cannot be read.
- Project.check_receipts(ids=None, *, history=True, refresh=True)[source]
Whether what each receipt’s answer rests on still holds in the current graph.
A receipt is stale when a column it rests on was removed, or its formula, row conditions, sources or type changed, or an approved term it rests on changed its meaning or status; tampered when its record does not match its digest. A rebuild that changes none of these leaves it holding. A graph older than its inputs cannot say a receipt holds, so the graph is brought up to date first, as
build(update=True)does; withrefreshFalse, a receipt it would say holds is unchecked instead.- Parameters:
ids (Sequence[str] | None) – Only these receipts (each an id or the start of one); by default, every one.
history (bool) – Read git history to say which commit changed what.
refresh (bool) – Update the graph first when inputs changed since it was built.
- Returns:
Each receipt with its status, what changed and who changed it;
passedis True when every one holds.- Raises:
konstruct.QueryError – An id names no receipt, or several.
- Return type:
Example:
result = project.check_receipts() [(r.id, r.status, [c.reasons for c in r.changes]) for r in result.receipts]
- Project.laws(*, confirm=False, warehouse=None, tolerance=None, max_rows=10000, write=True)[source]
The data laws the lineage implies: equalities between totals that the data must obey, proposed from each model’s compiled SQL and, with
confirm, checked against the data.Where a model sums or maps an input through joins that can neither repeat nor drop a row (a uniqueness test, or a GROUP BY in the same SQL, covers the key joined to), the total of its output equals the total of its input under the same filters and factors, followed across models for as long as each keeps the total: a conservation law, a count law for row counts and COUNT columns, and an additivity law for each aggregate measure. Confirming compares the totals per day, read-only, in verify’s engines, and probes each measure’s additivity on a sample of its model’s input.
- Parameters:
confirm (bool) – Check the laws against the warehouse, read-only. Without it, nothing is run.
warehouse (str | Path | None) – As for
verify().tolerance (float | None) – The relative tolerance of a floating-point total, against the day’s sum of magnitudes (default
konstruct.analysis.laws.TOLERANCE). Integer and decimal totals are compared exactly.max_rows (int) – The most rows of each input the additivity probes copy.
write (bool) – With
confirm, writelaws.json, whichgate()reads: a law that held on every day and that a change no longer satisfies fails the gate. Laws that were only proposed are never written, so they cannot replace the ones checked.
- Returns:
Every law with its status, and the measures that have none with the reason.
- Raises:
konstruct.QueryError –
confirmand no warehouse was found.- Return type:
Example:
laws = project.laws(confirm=True) print(laws.summary()) for law in laws.laws: if law.status == 'broken': print(law.statement, law.broken_at, law.failures[:1])
- Project.lock(reads=None, *, update=False, write=True)[source]
Pin, in
konstruct.lock, what each upstream column this project reads means: a fingerprint of its formula composed down to source columns, the row conditions of every relation on the way, its sources, its type and the business terms bound to it.An upstream column is one the project reads and does not build itself: from another repository listed in
repositories, or a table in a shared warehouse. Commit the file, andcheck_lock()(konstruct lock --check) fails when one of them comes to mean something else. Formatting, renamed CTEs, passthrough models and a filter moved upstream are not changes.- Parameters:
reads (Sequence[str] | None) – What to pin: columns, relations, exposures or terms. By default, what the lockfile names already, or every upstream column the project’s models and exposures read.
update (bool | Sequence[str]) – Accept every change to a pinned column (
True), or the changes to these columns. Otherwise a pinned column keeps its pin, as a package lockfile keeps its versions, and only what fails no check (an approval date, a verification) is brought up to date.write (bool) – Write
konstruct.lockat the project’s root.
- Returns:
The pins, with the columns added, accepted, removed and kept.
- Raises:
konstruct.QueryError – Nothing to pin, a name matches nothing, or the lockfile cannot be read.
- Return type:
Example:
project.lock() # every upstream column the project reads project.lock(update=['main.ltv.total']) # accept a reviewed change
- Project.check_lock(*, history=True)[source]
Whether every column
konstruct.lockpins still means what was locked.Fails on a pinned column whose composed formula, row conditions, sources or type changed, that is no longer built, or whose approved business term was reworded. Each change says what changed (the locked and current formula side by side), who changed it (from git history), which of this project’s columns read it, and how to accept it or keep the locked meaning.
- Parameters:
history (bool) – Read git history to say who made each change.
- Returns:
passed, and each change with what it affects.- Raises:
konstruct.QueryError – There is no lockfile, or it cannot be read.
- Return type:
Example:
result = project.check_lock() if not result.passed: for change in result.changes: print(change.column, change.reasons, change.accept)
- Project.export(fmt, *, select=None, columns=False, namespace=None)[source]
The graph in a format other tools read.
- Parameters:
fmt (str) –
openlineage(JSON lines of staticJobEventobjects with schema and column lineage facets),mermaid,dot,csv(one row per column edge), or the ontology asossie(an Apache Ossie semantic model),owl(OWL 2 in Turtle),semantic-view(a SnowflakeCREATE SEMANTIC VIEWstatement) ormetric-view(Databricks metric views), or a format a plugin the project enables adds.select (str | None) – Only the lineage, both directions, of this table or column (not for the ontology formats).
columns (bool) – For
mermaidanddot, draw columns and column lineage.namespace (str | None) – For
openlineage, the dataset namespace (default<dialect>://konstruct); forowl, the ontology’s IRI (defaulturn:konstruct:<project>).
- Returns:
The export as text.
- Raises:
konstruct.outputs.export.ExportError –
fmtis not one of the formats,selectis given with an ontology format, the ontology has no entities or measures to export, or a plugin’s format failed. It is aValueError.konstruct.QueryError –
selectmatches nothing or several things.
- Return type:
str
Example:
Path('lineage.jsonl').write_text(project.export('openlineage', namespace='duckdb://shop')) print(project.export('mermaid', select='orders.amount', columns=True)) Path('shop.ttl').write_text(project.export('owl', namespace='https://example.com/shop'))
Results
Walks and Items
- class konstruct.Walk(direction, target, columns=<factory>, relations=<factory>, joins_and_filters=<factory>, filtered_by=<factory>, tests=<factory>, exposures=<factory>, tasks=<factory>, terms=<factory>, entities=<factory>, parents=<factory>, owners=<factory>, start=<factory>)[source]
Bases:
objectEverything downstream (impact) or upstream (trace) of one node, grouped by what it is.
- direction: Literal['downstream', 'upstream']
downstreamfor impact,upstreamfor trace.
- joins_and_filters: list[Item]
Relations that join, filter or group on the target (impact of a column).
- entities: dict[str, list[str]]
The business entities (from the ontology) whose relations the walk reaches, each with those relations by label: which business objects a change touches.
- parents: dict[str, str | None]
the walk as a tree, for rendering. Not part of
to_dict().- Type:
For each node reached, the id of the node it was reached from
- owners: dict[str, str]
For each column reached, its relation’s id, so a tree can be drawn between relations.
- start: list[str]
the target and, for a relation, its columns.
- Type:
The ids the walk started from
- property total: int
- class konstruct.Item(id, kind, label, name, relation_kind=None, path=None, data_type=None, description=None, depth=None, via=None, transform=None, verified=None, role=None, command=None, through=None, direction=None, score=None, matched=None, status=None)[source]
Bases:
objectOne node in a result, with how it was reached when it came from a walk.
- id: str
The node id, such as
model.shop.orders#amount.
- kind: str
The node kind, such as
column,model,table,source,taskorterm.
- label: str
The short name the CLI prints, such as
orders.amount.
- name: str
The node’s own name, such as
amountfor that column.
- relation_kind: str | None = None
For a column, the kind of relation it belongs to.
- path: str | None = None
The file that defines the node (or its relation), relative to the project.
- data_type: str | None = None
Column type, when known.
- description: str | None = None
Description from dbt, the warehouse or definitions.yml.
- depth: int | None = None
In a walk, how many steps from the starting node.
- via: str | None = None
In a walk, the kind of edge that reached this node.
- transform: str | None = None
direct(passed through or renamed) ortransformed, for column edges.
- verified: str | None = None
konstruct verify’s result for the edge that reached it, such asconfirmed.
- role: str | None = None
referencewhen the node joins, filters or groups the next relation.
- command: str | None = None
For an orchestration task, the dbt command that runs the node.
- through: str | None = None
The task whose SQL produced the edge, when one did.
- direction: str | None = None
In a path,
forwardorbackwardalong data flow.
- score: float | None = None
In a search, how well the node matched.
- matched: str | None = None
the node’s own
words, itslineage, itsformulaor itsstructure.- Type:
In a semantic search, what a match by meaning rests on most
- status: str | None = None
For a business term, its status (draft, agreed, approved or retired).
- class konstruct.PathResult(start: 'Item', end: 'Item', steps: 'list[Item]')[source]
Bases:
object- property found: bool
- class konstruct.SearchResult(query: 'str', results: 'list[Item]')[source]
Bases:
object- query: str
The words searched for.
- class konstruct.Listing(kinds: 'list[str]', items: 'list[Item]')[source]
Bases:
object- kinds: list[str]
The node kinds listed.
- class konstruct.Description(node: 'Item', relation: 'str | None' = None, parent: 'str | None' = None, origins: 'list[str]' = <factory>, attrs: 'dict[str, Any]'=<factory>, columns: 'list[Item]' = <factory>, tests: 'list[str]' = <factory>, upstream: 'list[str]' = <factory>, downstream: 'list[str]' = <factory>, tasks: 'list[str]' = <factory>, runs: 'list[str]' = <factory>, after: 'list[str]' = <factory>, before: 'list[str]' = <factory>, expression: 'str | None' = None, conditions: 'list[str]' = <factory>, terms: 'list[str]' = <factory>, defines: 'list[str]' = <factory>, related: 'list[str]' = <factory>, neighbours: 'dict[str, list[Item]]'=<factory>)[source]
Bases:
object- relation: str | None = None
For a relation, its warehouse name.
- parent: str | None = None
For a column, its relation’s id.
- origins: list[str]
Inputs the node came from, such as
dbtandduckdb.
- attrs: dict[str, Any]
Every other attribute, such as materialisation, schedule, operator or owner.
- tests: list[str]
Names of the dbt tests on the node or its columns.
- upstream: list[str]
Immediate upstream neighbours, as
label (kind).
- downstream: list[str]
Immediate downstream neighbours.
- tasks: list[str]
For a DAG or job, its tasks.
- runs: list[str]
For a task, what it runs.
- after: list[str]
For a task, the tasks it runs after.
- before: list[str]
For a task, the tasks that run after it.
- expression: str | None = None
For a column, its SQL over physical columns (see
konstruct explain).
- conditions: list[str]
For a relation, the joins, filters and grouping that shape its rows.
- terms: list[str]
For a column or relation, the business terms that define it.
- defines: list[str]
For a term, the columns it is bound to.
For a term, its related terms.
- class konstruct.QueryResult(question: 'str', matches: 'list[Item]' = <factory>, edges: 'list[EdgeItem]' = <factory>, neighbours: 'list[Item]' = <factory>, impact: 'Walk | None' = None, lineage: 'Walk | None' = None, definitions: 'list[TermDefinition]' = <factory>)[source]
Bases:
object- question: str
The question asked.
- impact: Walk | None = None
Everything downstream of the first match, when the question asks what breaks.
- lineage: Walk | None = None
Everything upstream of the first match, when the question asks where it comes from.
- definitions: list[TermDefinition]
Business terms among the matches, or defining them, with their agreed definitions.
- class konstruct.EdgeItem(source: 'str', target: 'str', kind: 'str', source_label: 'str', target_label: 'str', transform: 'str | None' = None, role: 'str | None' = None)[source]
Bases:
object- source: str
Source node id.
- target: str
Target node id.
- kind: str
The edge kind, such as
feeds,derived_from,runs,testsordefines.
- source_label: str
Short name of the source.
- target_label: str
Short name of the target.
- transform: str | None = None
For
derived_from,directortransformed.
- role: str | None = None
For
feeds,referencewhen the source joins, filters or groups.
Formulas
- class konstruct.Explanation(column: 'str', label: 'str', data_type: 'str | None', description: 'str | None', expression: 'str | None', composed: 'str | None', sources: 'list[str]' = <factory>, steps: 'list[Step]' = <factory>, complete: 'bool' = True)[source]
Bases:
object- column: str
The column explained.
- label: str
Its short name.
- data_type: str | None
Its type.
- description: str | None
Its description.
- expression: str | None
Its own expression, with CTEs and aliases inlined, or None for a source column.
- composed: str | None
The formula in terms of source columns, or None when nothing could be substituted.
- sources: list[str]
The source columns the formula reaches.
- complete: bool = True
False when some column on the way has no recorded expression (Python, unparsed SQL).
- class konstruct.analysis.explain.Step(column, label, relation_kind, expression, conditions=<factory>, inputs=<factory>, verified=<factory>)[source]
Bases:
objectOne column on the way down: its formula and the conditions on the rows of its relation.
- column: str
The column id.
- label: str
Its short name.
- relation_kind: str
The kind of the column’s relation.
- expression: str | None
The column’s own expression with short names, or None for a source column.
- conditions: list[str]
Joins, filters and grouping of the column’s relation.
- inputs: list[str]
Labels of the columns the expression reads.
- verified: list[str]
For each input,
verify’s result for the edge, when it has been run.
- class konstruct.analysis.english.English(column, label, formula, english, sentence, composed=False, complete=True, rows=<factory>, grain=None, terms=<factory>, names=<factory>, verbatim=<factory>)[source]
Bases:
objectA column’s formula, the rows it is computed over and its business terms, in English.
- column: str
The column id.
- label: str
Its short name.
- formula: str | None
the formula composed down to sources where it could be composed, else the column’s own expression; None for a source column.
- Type:
The SQL the sentence renders
- english: str | None
formulain English.
- sentence: str | None
The column and its formula as one sentence (
customers.ltv is the sum of amount.).
- composed: bool = False
Whether
formulais in terms of source columns.
- complete: bool = True
False when some column on the way has no recorded expression, so the formula stops short.
- grain: str | None = None
a bound term’s grain, or the key of the entity the relation holds.
- Type:
What one row of the column’s relation is, and the evidence
- verbatim: list[str]
The SQL of each part the grammar has no wording for, quoted between backticks instead.
- property exact: bool
Whether every part is worded, with nothing quoted as SQL.
- class konstruct.analysis.english.Rows(relation, conditions=<factory>, english=<factory>)[source]
Bases:
objectThe joins, filters and grouping of one relation on the way, in SQL and in English.
- relation: str
The relation, by label.
- conditions: list[str]
Each condition as
explainshows it (WHERE: stg_orders.status <> 'returned').
- english: list[str]
Each condition in English, in the same order.
- class konstruct.analysis.english.Term(name, status=None, kind=None, owner=None, definition=None, grain=None, unit=None, currency=None, filters=<factory>, english=<factory>)[source]
Bases:
objectA business term bound to the column, with what its definition adds to the formula.
- name: str
The term’s name.
- status: str | None = None
draft,agreed,approvedorretired.
- kind: str | None = None
entity,measureorterm, where the definition says.
- owner: str | None = None
Who owns the definition.
- definition: str | None = None
The agreed wording.
- grain: str | None = None
The term’s grain as written (
one row per customer).
- unit: str | None = None
Its unit, such as
currency.
- currency: str | None = None
Its currency, for a monetary measure.
- filters: list[str]
The filters any answer must apply, as SQL.
- english: list[str]
The same filters in English.
History
- class konstruct.analysis.history.History(column: 'str', label: 'str', formula: 'str | None', path: 'str | None', formulas: 'list[FormulaChange]' = <factory>, terms: 'list[TermChange]' = <factory>, read_formulas: 'bool' = False, unknown: 'list[Commit]' = <factory>, window: 'Commit | None' = None, notes: 'list[str]' = <factory>)[source]
Bases:
object- column: str
- label: str
- formula: str | None
The formula now, as the graph has it.
- path: str | None
The file that defines the column, relative to the project.
- formulas: list[FormulaChange]
In history order, oldest first.
- terms: list[TermChange]
In history order, oldest first.
- read_formulas: bool = False
Whether the formula’s history was read (it is defined by SQL in a git repository).
- window: Commit | None = None
When the formula’s history was cut short (a shallow clone or the limit), the oldest commit read: what came before it is not known.
- notes: list[str]
- property last_approval: TermChange | None
- property approved_since_last_change: bool | None
Whether a bound term was approved after every formula change, by ancestry.
True when one approval follows every change read (and, if the history was cut short, the oldest commit read); False when a term was approved at or before a change that no later approval follows; None when the formula’s history was not read, nothing settles it, or a commit after the last approval could not be worked out.
- class konstruct.analysis.history.FormulaChange(commit: 'Commit', path: 'str', before: 'str | None', after: 'str | None')[source]
Bases:
object- path: str
The file at that commit, relative to its repository.
- before: str | None
The formula before this commit, or None where the column was added.
- after: str | None
The formula after it, or None where the column was removed.
- class konstruct.analysis.history.TermChange(commit: 'Commit', term: 'str', path: 'str', changes: 'dict[str, tuple[Any, Any]]', status: 'str | None' = None)[source]
Bases:
object- term: str
- path: str
The definitions file at that commit, relative to its repository.
- changes: dict[str, tuple[Any, Any]]
Each changed field (status, approved, owner, definition) with its value before and after.
- status: str | None = None
The term’s status after this commit (None where it is gone).
- property approved: bool
made an existing term approved, or, while it stayed approved, set a new approval date. A term first seen already approved, such as a renamed one, is not approved by this commit.
- Type:
Whether this commit approved the term
- property leaves_approved: bool
Whether the term is approved after this commit.
- class konstruct.analysis.history.Commit(sha: 'str', author: 'str', date: 'str', subject: 'str', order: 'int' = 0, repository: 'Path | None' = None, authored: 'str' = '', co_authors: 'list[str]' = <factory>)[source]
Bases:
object- sha: str
Empty for uncommitted changes.
- author: str
- date: str
When the commit was made (its committer date), in UTC, ISO 8601.
- subject: str
- order: int = 0
Its place in a topological order of history, oldest first, for display.
- repository: Path | None = None
The repository it is in, to ask git about ancestry.
- authored: str = ''
When its author wrote it (its author date), in UTC, ISO 8601; a rebase keeps it.
- co_authors: list[str]
The people its
Co-authored-bytrailers name.
- follows(other)[source]
Whether this commit comes after
otherin history:otheris one of its ancestors, or this is the working tree. Commits on parallel branches follow neither way.
- property short: str
- property by: str
its author, with any co-authors (
Sam with Ada).- Type:
Who wrote it
Business Terms
- class konstruct.Definitions(query, terms=<factory>, column=None)[source]
Bases:
objectThe answer to konstruct define: the terms a name or column refers to, approved first.
- query: str
What was asked for.
- terms: list[TermDefinition]
Matching terms, approved first and retired last.
- class konstruct.TermDefinition(name, definition, status, owner=None, approved=None, synonyms=<factory>, grain=None, columns=<factory>, filters=<factory>, unit=None, currency=None, related=<factory>, broader=<factory>, narrower=<factory>, exceptions=None, path=None, line=None, iri=None, id='')[source]
Bases:
objectOne business term: what it means, who owns it and the columns that hold it.
- name: str
The term.
- definition: str | None
The agreed wording.
- status: str
draft,agreed,approvedorretired.
- owner: dict[str, Any] | None = None
{"name": ..., "role": ...}, when set.
- approved: str | None = None
The approval date,
YYYY-MM-DD.
- synonyms: list[str]
Other names for the term.
- grain: str | None = None
What one row means, such as
one row per customer.
- filters: list[str]
Filters any answer about the term must apply, as SQL.
- unit: str | None = None
Unit of measure, such as
currency.
- currency: str | None = None
ISO 4217 currency code.
Names of related terms.
- broader: list[str]
The wider concepts this term is a kind of.
- narrower: list[str]
The terms that are kinds of this one.
- exceptions: str | None = None
Known exceptions to the definition.
- path: str | None = None
The definitions file.
- line: int | None = None
The line the term starts on.
- iri: str | None = None
For a term from a SKOS or OWL vocabulary, the concept’s IRI.
- id: str = ''
The node id,
term.<slug>.
Ontology
- class konstruct.semantics.ontology.Ontology(entities=<factory>, measures=<factory>, relationships=<factory>, rules=<factory>, concepts=<factory>, unique_ids=<factory>)[source]
Bases:
objectEntities, measures, relationships and rules, derived from one graph.
- relationships: list[Relationship]
How the entities refer to each other.
- concepts: list[Concept]
each term with a broader or narrower term.
- Type:
The concept hierarchy
- unique_ids: frozenset[str]
The graph ids of the columns a test, a primary key or an identifier field shows unique, for tools; not written to
ontology.yml.
- property violations: list[str]
Every rule violation, each prefixed with its rule.
- text(style=None)[source]
The result as the CLI prints it: plain text, as
str()gives, or drawn for a terminal withstyle.
- class konstruct.semantics.ontology.Entity(name, key, keys=<factory>, relations=<factory>, evidence=<factory>, term=None, description=None, confidence='inferred', check=None, key_ids=<factory>, unique_ids=<factory>)[source]
Bases:
objectA business object, such as a customer or an order.
- name: str
The entity’s name, singular and lower case (
customer).
- key: str
The name of the column that identifies one instance (
customer_id); when relations name it differently, the most common name.
- keys: list[str]
The key column of each relation, by label (
customers.customer_id).
- relations: list[str]
The relations that hold one row per instance, by label.
- evidence: list[str]
the tests, primary keys, identifier fields, terms and grains that say so.
- Type:
Why it is an entity
- term: str | None = None
The business term that names it, if one does.
- description: str | None = None
The term’s definition, if one names it.
- confidence: str = 'inferred'
verifiedwhenkonstruct verifyfound every one of its key columns unique and never null;declaredwhen a test, constraint, primary key, identifier field, Ossie model or term of kind entity states every one of them;inferredwhen only names, a grain or an ontology pack show one of them.- Type:
As trusted as its least trusted key
- check: str | None = None
what
konstruct verifyfound of its key columns in the warehouse, with the detail inevidence; left out when it checked none.- Type:
confirmedorfailed
- key_ids: list[str]
The graph ids of the key columns, for tools; not written to
ontology.yml.
- unique_ids: list[str]
The graph ids of the key columns known to be unique (by a
uniquetest, a primary key, an identifier field or an Ossie primary key), for tools; not written toontology.yml. A key named only by a term is not among them.
- class konstruct.semantics.ontology.Measure(name, definition=None, status='draft', owner=None, approved=None, entity=None, grain=None, unit=None, currency=None, filters=<factory>, columns=<factory>, path=None, expression=None, confidence='inferred', evidence=<factory>)[source]
Bases:
objectA business term with a formula: what is measured, how, and on whose authority.
- name: str
The term’s name.
- definition: str | None = None
The agreed wording.
- status: str = 'draft'
draft,agreed,approvedorretired.
- owner: str | None = None
Who owns the definition.
- approved: str | None = None
When it was approved.
- entity: str | None = None
The entity its grain names (
one row per customergivescustomer).
- grain: str | None = None
The term’s grain as written.
- unit: str | None = None
Its unit, such as
currency.
- currency: str | None = None
Its currency, for a monetary measure.
- filters: list[str]
Filters any answer must apply.
- columns: list[MeasureColumn]
The columns that hold it, each with its formula.
- path: str | None = None
The file that defines it, with the line.
- expression: str | None = None
For a measure defined as a metric in an Apache Ossie model, the metric’s own expression; its columns are then the columns the expression reads.
- confidence: str = 'inferred'
verifiedwhenkonstruct verifyconfirmed every lineage edge from its columns to source;declaredwhendefinitions.yml, a vocabulary or an Ossie model defines it;inferredwhen an ontology pack bound it to the project’s tables by their names.
- evidence: list[str]
Where it comes from and what checked it.
- class konstruct.semantics.ontology.MeasureColumn(column, formula, sources=<factory>, complete=True, verified=0, edges=0, usage=None, id=None)[source]
Bases:
objectOne column that holds a measure, with its formula and how far to trust it.
- column: str
The column, by label (
customers.customer_lifetime_value).
- formula: str | None
The column’s formula composed down to source columns, if it could be composed.
- sources: list[str]
The source columns the formula reaches.
- complete: bool = True
False when some column on the way had no expression, so the formula stops short of source.
- verified: int = 0
Lineage edges on the way to source that
konstruct verifyconfirmed.
- edges: int = 0
Lineage edges on the way to source.
- usage: int | None = None
The column’s usage score from query history (0 to 100), when usage is on.
- id: str | None = None
The column’s graph id, for tools; not written to
ontology.yml.
- class konstruct.semantics.ontology.Relationship(source, target, via, cardinality=None, tested=False, evidence=<factory>, confidence='inferred', check=None, columns=None)[source]
Bases:
objectOne entity referring to another, such as an order belonging to a customer.
- source: str
The entity that refers (
order).
- target: str
The entity referred to (
customer).
- via: str
The columns that join them (
orders.customer_id = customers.customer_id).
- cardinality: str | None = None
many-to-one, orone-to-onewhen both columns are unique; left out when the target column is not known to be unique, so the relationship is claimed without its cardinality.
- tested: bool = False
Whether a dbt
relationshipstest vouches for it, rather than only a join.
- evidence: list[str]
The tests and joins that show it, and what
konstruct verifyfound.
- confidence: str = 'inferred'
verifiedwhenkonstruct verifyfound every value of the referring column in the key it refers to, and that key unique;declaredwhen a dbtrelationshipstest or an Ossie model states it;inferredwhen only a join or an ontology pack shows it.
- check: str | None = None
what
konstruct verifyfound in the warehouse, with the detail inevidence; left out when it did not check it.- Type:
confirmedorfailed
- columns: tuple[str, str] | None = None
The graph ids of the joining columns in
via, for tools; not written toontology.yml.
- class konstruct.semantics.world.EntityDetail(entity, properties=<factory>, relationships=<factory>, metrics=<factory>)[source]
Bases:
objectOne entity of the business: how it is identified, what describes it, how it relates to the others and what is measured about it.
- properties: list[Property]
The columns that describe it, across the relations holding it, keys left out.
- relationships: list[Relationship]
The relationships it takes part in, either side.
- class konstruct.semantics.world.Property(name, columns=<factory>, description=None, data_type=None, terms=<factory>, refers_to=None)[source]
Bases:
objectA column that describes an entity, gathered across the relations that hold it.
- name: str
The column’s name, lower case (
first_name).
- columns: list[str]
The columns of that name in the entity’s relations, by label.
- description: str | None = None
The first description among those columns.
- data_type: str | None = None
The type of the first column with one.
- terms: list[str]
The business terms bound to any of the columns.
- refers_to: str | None = None
The entity it refers to, when it is the referring column of a relationship.
- class konstruct.semantics.world.Metrics(query, metrics=<factory>, total=0)[source]
Bases:
objectThe metrics a name finds, or every metric.
- query: str | None
The name asked for, or None for every metric.
- metrics: list[Metric]
Defined metrics first, then proposals, each in name order; at most the limit asked for.
- total: int = 0
How many metrics were found, before any limit.
- class konstruct.semantics.world.Metric(name, source, confidence, proposed=False, synonyms=<factory>, definition=None, status=None, owner=None, approved=None, entity=None, grain=None, grain_confidence=None, unit=None, currency=None, filters=<factory>, formula=None, aggregation=None, additive=None, columns=<factory>, evidence=<factory>, path=None, exposure=None)[source]
Bases:
objectA business measure: what it measures, how it is computed, at what grain, on whose authority and how far to trust it.
- name: str
Its name (
Customer lifetime value, or for a proposal one made from its column).
- source: str
definitions,vocabulary,ossie,ontology pack,dbt semantic layer,looker,power bior the name of the plugin input that read it when something defines it;aggregate,measure columnorentity countfor a proposal Konstruct read from the code.- Type:
Where it comes from
- confidence: str
verified,declaredorinferred; a proposal is alwaysinferred.
- proposed: bool = False
it has no status and is not approved.
- Type:
True when nothing defines it and Konstruct proposes it from the code
- synonyms: list[str]
Other names it goes by.
- definition: str | None = None
The agreed wording, or the description where it is defined.
- status: str | None = None
draft,agreed,approvedorretiredfor a business term; None for anything else, a proposal included.
- owner: str | None = None
Who owns the definition.
- approved: str | None = None
When it was approved.
- entity: str | None = None
The entity it measures, from its grain.
- grain: str | None = None
Its grain (
one row per customer).
- grain_confidence: str | None = None
declaredwhen its definition states the grain,inferredwhen Konstruct read the grain and the entity from the relation that holds it; None without a grain.
- unit: str | None = None
Its unit, such as
currency.
- currency: str | None = None
Its currency, for a monetary measure.
- filters: list[str]
Filters any answer must apply, as its definition writes them.
- formula: str | None = None
Its formula as written where it is defined, or composed down to source columns from SQL; left out when its columns compute it in different ways or the tool’s aggregation has no exact SQL.
- aggregation: str | None = None
sum,countoraveragewhen its formula aggregates, as far as it can be read.
- additive: bool | None = None
true for a sum or a count, false for an average, a distinct count or a measure its definition makes non-additive; None when it cannot be said, as for a price or a balance.
- Type:
Whether its values add up across groups
- columns: list[MeasureColumn]
The columns that hold it (or, for a metric defined over a model, that it reads), each with its formula and how many of its lineage edges
konstruct verifyconfirmed.
- evidence: list[str]
Where it comes from and what checked it.
- path: str | None = None
The file that defines it, with the line where known.
- exposure: str | None = None
For a measure of a BI view or semantic model (Looker, Power BI or a plugin’s input), the graph id of the exposure that defines it: one file can hold several views, each with a measure of the same name.
- class konstruct.semantics.world.Connection(start, end, found, hops=<factory>, cardinality=None, confidence=None, check=None, join=None)[source]
Bases:
objectHow two entities connect: the relationships between them, the cardinality and confidence of the whole path, and the joins that follow it.
- start: str
The entity it starts from.
- end: str
The entity it ends at.
- found: bool
Whether the relationships connect them.
- hops: list[Hop]
fewest failed checks first, then fewest relationships, then the most trusted.
- Type:
The relationships on the way
- cardinality: str | None = None
many-to-onewhen eachstartrow has at most oneend,one-to-manywhen it can have several (aggregateendbefore joining it tostart),many-to-many,one-to-one, or None when a relationship on the way has no known cardinality.- Type:
Of the whole path
- confidence: str | None = None
the path is as trustworthy as that.
- Type:
The weakest confidence of a relationship on the way
- check: str | None = None
failedwhenkonstruct verifyfound the data contradicting a relationship on the way, so the joins can lose or repeat rows;confirmedwhen it confirmed every one.
- join: str | None = None
the warehouse’s relation names, each with an alias, joined on the columns of each relationship or, between two relations holding one entity, on that entity’s keys, each shown unique where it is joined. None when that cannot be written: a relation with no warehouse name, or a key not shown unique.
- Type:
SQL joins that follow the path, from a relation that holds
start
- class konstruct.semantics.world.Hop(start, end, via, cardinality, confidence, check=None, evidence=<factory>, same_row=False)[source]
Bases:
objectOne relationship on the way from one entity to another, in the direction it is walked.
- start: str
The entity walked from.
- end: str
The entity walked to.
- via: str
The columns that join them.
- cardinality: str | None
many-to-one(each row ofstarthas oneend),one-to-many,one-to-one, or None when it is not known, as whenkonstruct verifyfound the key it refers to repeated.- Type:
In the direction walked
- confidence: str
The relationship’s confidence.
- check: str | None = None
what
konstruct verifyfound of the relationship, with the detail inevidence; left out when it did not check it.- Type:
confirmedorfailed
- evidence: list[str]
The tests, joins and checks that show it.
- same_row: bool = False
True for a step along one row of a relation that holds both entities, which joins nothing and is no relationship.
- class konstruct.analysis.compile.Compiled(query, metric=None, sql=None, dialect=None, relation=None, grain=None, aggregation=None, path=<factory>, filters=<factory>, facts=<factory>, confidence=None, flags=<factory>, refused=<factory>)[source]
Bases:
objectA metric query compiled to SQL, with the joins, filters and facts it rests on, or what was refused and why.
- query: OntologyQuery
What was asked.
- metric: str | None = None
The metric found.
- sql: str | None = None
The SQL, or None when anything was refused.
- dialect: str | None = None
The dialect the SQL is written in.
- relation: str | None = None
The relation whose rows the metric is read from.
- grain: str | None = None
The entity that relation holds one row per, when it holds one.
- aggregation: str | None = None
none(the query keeps one row per row of its relation, so the column is read as it is),sum(its values added up) orformula(its own aggregate over the relation’s rows).- Type:
How the metric is read
- path: list[Hop]
The relationships the joins follow, each in the direction walked, every one many-to-one or one-to-one as the project shows it.
- filters: list[RequiredFilter]
The filters the metric’s definition requires, every one in the SQL.
- confidence: str | None = None
the result is as trustworthy as that.
- Type:
The weakest confidence of the facts
- flags: list[str]
The facts that are only inferred, and anything else to say when the result is used.
- property compiled: bool
nothing was refused.
- Type:
Whether SQL was written
- class konstruct.analysis.compile.OntologyQuery(metric, by=<factory>, where=<factory>, since=None, until=None, time=None)[source]
Bases:
objectA question about a metric in the business’s terms: the metric, what to group it by, the conditions on the rows it adds up and the time it covers.
- metric: str
The metric’s name or synonym (
lifetime value), or a column that holds it, askonstruct metricfinds it.
- by: list[str]
an entity (
customer, its key), an entity’s property (customer.first_name, orfirst_namewhen one entity has it), a time grain of the metric’s time property (month) or of a named one (order_date:month). The grains areday,week,month,quarterandyear.- Type:
What to group by
- where: list[str]
Conditions on properties, as SQL (
customer.first_name = 'Michael'), each named as inby; all of them hold.
- since: str | None = None
The first date the metric covers (
2024-01-01), on its time property, whateverbygroups by.
- until: str | None = None
the range is
sinceup to, not including,until.- Type:
The date after the last one it covers
- time: str | None = None
by default the one date or time column of the relation that holds the metric.
- Type:
The metric’s time property, for a bare time grain in
byand for the range
- class konstruct.analysis.compile.Fact(kind, name, confidence, check=None, evidence=<factory>)[source]
Bases:
objectOne thing the compiled SQL rests on, with how far to trust it.
- kind: str
metric,grain,column,relationship,keyorfilter.
- name: str
the metric’s name, a relation’s grain, a column, a relationship’s columns, a filter.
- Type:
What it is
- confidence: str
verified,declaredorinferred, as the ontology gives it.
- check: str | None = None
confirmedwhenkonstruct verifyconfirmed it against the data.
- evidence: list[str]
What shows it.
- class konstruct.analysis.compile.RequiredFilter(filter, applied, source, by='query')[source]
Bases:
objectA filter the metric’s definition requires, and how the SQL applies it.
- filter: str
As the definition writes it.
- applied: str | None
As the SQL’s WHERE clause applies it, on the query’s own relations; None when the SQL of the column that holds the metric already applies it, on a query the column is computed through, to the rows it aggregates: Konstruct’s reading of that SQL, so inferred.
- source: str
the metric and its file.
- Type:
Where it is required
- by: str = 'query'
querywhen the SQL’s WHERE clause applies it, else the relations whose SQL applies it on the way to the metric’s column.
- class konstruct.analysis.compile.Refusal(item, reason, link=None)[source]
Bases:
objectSomething the compiler would not write, and why.
- item: str
the metric, a
byitem, a condition, a filter or the time range.- Type:
What was asked for
- reason: str
Why it was refused.
- link: str | None = None
The relationship (its columns) whose join could repeat rows, when that is the reason.
- class konstruct.semantics.ontology.Rule(name, description, violations=<factory>)[source]
Bases:
objectA rule the build holds the ontology to, with what breaks it now.
- name: str
A short identifier (
one-formula-per-measure).
- description: str
What the rule requires.
- violations: list[str]
What breaks it in this build; each is also a build warning.
Sensitive Data
- class konstruct.analysis.sensitivity.Sensitivity(columns=<factory>, sanitised=<factory>, exposures=<factory>, references=<factory>, unmasked=<factory>, uncertain=<factory>, settings=<factory>, subject=None, seconds=0.0)[source]
Bases:
objectWhere sensitive data is, where the lineage takes it and where a transform stops it.
- columns: list[SensitiveColumn]
Every sensitive column, by relation.
- exposures: list[ExposureFlow]
Exposures that show sensitive data.
- unmasked: list[UnmaskedCopy]
Tables that copy a masked column in clear.
- uncertain: list[Uncertain]
Relations that may hold sensitive data through lineage Konstruct does not know.
- subject: str | None = None
The table or column asked about, when the result is about one.
- seconds: float = 0.0
Time taken.
- property marked: list[SensitiveColumn]
The columns marked sensitive themselves.
- property unheld: list[SensitiveColumn]
Sensitive columns of built relations that nothing marks to hold the data.
- property unheld_exposures: list[ExposureFlow]
Exposures showing sensitive data that nothing marks to hold it.
- class konstruct.analysis.sensitivity.SensitiveColumn(column, label, relation, relation_id, sensitivity, marks=<factory>, sources=<factory>, path=<factory>, transform='marked', built=False, held=None, masking_policy=None)[source]
Bases:
objectA column holding sensitive data: marked itself, or computed from one that is.
- column: str
The column id.
- label: str
Its short name.
- relation: str
Its relation’s short name.
- relation_id: str
Its relation’s id.
- sensitivity: list[str]
The kinds of sensitive data it holds.
- sources: list[str]
The marked columns it is computed from, by short name; empty for a marked column.
- path: list[str]
The columns from a marked column to this one, the marked one first.
- transform: str = 'marked'
marked;directwhen every step on the path passes the value unchanged;pseudonymisedwhen every path to it hashes the value; elsetransformed.
- built: bool = False
Whether something in the graph builds its relation (a model, a SQL file, a task).
- held: str | None = None
What marks it, or its relation, to hold sensitive data; None when nothing does.
- masking_policy: str | None = None
The warehouse’s masking policy on it, if it has one.
- class konstruct.analysis.sensitivity.Mark(sensitivity, by)[source]
Bases:
objectWhat marks a column sensitive.
- sensitivity: str
The kind of data, such as
personal, or the warehouse tag’s name or value.
- by: str
a business term, a warehouse tag or a masking policy.
- Type:
What says so
- class konstruct.analysis.sensitivity.Sanitised(column, source, by)[source]
Bases:
objectA read of a sensitive column that a transform stops.
- column: str
The column computed, by short name.
- source: str
The sensitive column it reads, by short name.
- by: str
a sanitiser (
TOKENISE), orSUM over groups of at least 10 distinct keys.- Type:
What stops it
- class konstruct.analysis.sensitivity.ExposureFlow(exposure, exposure_id, sensitivity, columns=<factory>, column_ids=<factory>, held=None, path=None)[source]
Bases:
objectAn exposure (a dashboard, a BI view) that shows sensitive data.
- exposure: str
The exposure’s short name.
- exposure_id: str
Its id.
- sensitivity: list[str]
The kinds of sensitive data it shows.
- columns: list[str]
The sensitive columns it reads, by short name.
- column_ids: list[str]
Their ids, for tools.
- held: str | None = None
What marks it to hold sensitive data; None when nothing does.
- path: str | None = None
The file that defines it, for the gate.
- class konstruct.analysis.sensitivity.Reference(relation, column, sensitivity)[source]
Bases:
objectA sensitive column that only joins, filters or groups a relation’s rows.
- relation: str
The relation whose rows it shapes, by short name.
- column: str
The sensitive column, by short name.
- sensitivity: list[str]
Its kinds of sensitive data.
- class konstruct.analysis.sensitivity.UnmaskedCopy(column, column_id, source, policy)[source]
Bases:
objectA column of a table that holds, in clear, the values of a column the warehouse masks.
- column: str
The copy, by short name.
- column_id: str
Its id.
- source: str
The masked column it comes from, by short name.
- policy: str
The source’s masking policy.
- class konstruct.analysis.sensitivity.Uncertain(relation, source, sensitivity, relation_id='')[source]
Bases:
objectA relation fed by one holding sensitive data whose column lineage Konstruct does not know, so it may hold that data.
- relation: str
The relation fed, by short name.
- source: str
The relation with sensitive columns that feeds it.
- sensitivity: list[str]
The kinds of data the source holds.
- relation_id: str = ''
The id of the relation fed, for the gate.
- class konstruct.analysis.sensitivity.Settings(holders=(), sanitisers=(), tags=(), k=10)[source]
Bases:
object[sensitivity]inkonstruct.toml.- holders: tuple[str, ...] = ()
Relations and exposures marked to hold sensitive data, by name, label, warehouse name or id, each a glob pattern (
stg_*).
- sanitisers: tuple[str, ...] = ()
Functions that stop sensitivity, such as a tokenisation function.
- tags: tuple[str, ...] = ()
Warehouse tags that mark a column sensitive, besides the built-in ones.
- k: int = 10
The smallest group an aggregate must be taken over to stop sensitivity.
- classmethod read(table)[source]
The settings in a
[sensitivity]table (None for none).- Raises:
konstruct.engine.config.ConfigError – The table is not valid.
- class konstruct.analysis.erasure.ErasureMap(subject, key, entity=None, holdings=<factory>, exposures=<factory>, seconds=0.0)[source]
Bases:
objectEvery relation holding one person’s rows, by the key that finds them.
- subject: str
The key, entity or relation asked about.
- key: str
The key column the map starts from, by short name.
- entity: str | None = None
The entity the key identifies, when the ontology knows it.
- exposures: list[str]
The exposures that read any of those relations.
- seconds: float = 0.0
Time taken.
- class konstruct.analysis.erasure.Holding(relation, relation_id, kind, action, keys=<factory>, personal=<factory>, level=0, path=<factory>)[source]
Bases:
objectA relation that holds rows of the person.
- relation: str
The relation, by short name.
- relation_id: str
Its id.
- kind: str
source,model,seed,snapshotortable.- Type:
Its node kind
- action: str
delete,rebuild,full refreshornone(see the module documentation).
- keys: list[ErasureKey]
The columns holding the key, the first found first.
- personal: list[str]
Its sensitive columns, by name.
- level: int = 0
0 for rows keyed by the person, 1 for rows of something the person owns, and so on.
- path: list[str]
The columns from the key asked about to this relation’s first key column.
- class konstruct.analysis.erasure.ErasureKey(column, how, via=None, entity=None)[source]
Bases:
objectA column of a relation that holds the person’s key, or the key of rows the person owns.
- column: str
The column, by short name.
- how: str
key(the entity’s key),copy,transformed,hashedorforeign key.
- via: str | None = None
The column it was reached from, by short name; None for the key asked about.
- entity: str | None = None
the person’s, or one the person owns.
- Type:
The entity whose instance the value identifies
Conflicts
- class konstruct.analysis.conflicts.Conflicts(conflicts: 'list[Conflict]' = <factory>, candidates: 'int' = 0, groups: 'int' = 0)[source]
Bases:
object- candidates: int = 0
Computed columns considered.
- groups: int = 0
Groups of columns that appear to be one idea.
- class konstruct.analysis.conflicts.Conflict(idea: 'str', reason: 'str', a: 'Side', b: 'Side', differences: 'list[Difference]' = <factory>)[source]
Bases:
object- idea: str
What the columns appear to measure, as a term name or the shared words of their names.
- reason: str
Why the two columns were compared.
- differences: list[Difference]
What differs between them.
- property score: int
- class konstruct.analysis.conflicts.Side(column: 'str', label: 'str', relation_kind: 'str', path: 'str | None', expression: 'str | None', composed: 'str | None', sources: 'list[str]' = <factory>, filters: 'list[tuple[str, str, str | None]]'=<factory>, joins: 'list[tuple[str, str, str | None]]'=<factory>, reach: 'dict[str, int]'=<factory>, terms: 'list[str]' = <factory>, carried_into: 'list[str]' = <factory>)[source]
Bases:
object- column: str
The column id.
- label: str
Its short name.
- relation_kind: str
The kind of its relation.
- path: str | None
The file that builds it.
- expression: str | None
Its own expression.
- composed: str | None
Its formula in terms of source columns.
- sources: list[str]
The source columns it reads.
- filters: list[tuple[str, str, str | None]]
(relation, condition, file) for each filter on the route back to source.
- joins: list[tuple[str, str, str | None]]
(relation, condition, file) for each join on its route back to source.
- reach: dict[str, int]
Counts of what depends on it downstream (exposures, tasks, columns and approved terms).
- terms: list[str]
Business terms bound to it or to anything downstream.
- carried_into: list[str]
Columns of the same idea that are derived from this one, and so inherit its definition.
- property weight: int
- class konstruct.analysis.conflicts.Difference(kind: 'str', detail: 'str', side: 'str | None' = None, path: 'str | None' = None, witness: 'Witness | None' = None)[source]
Bases:
object- kind: str
formula,source,filter,joinoraggregation.
- detail: str
What differs, such as a condition, a source column or two formulas.
- side: str | None = None
Which column has it (
aorb) when only one does; None when both differ.
- path: str | None = None
The file the filter or join is in.
- witness: Witness | None = None
For a formula or aggregation not shown to be the same, values at which the two give different results (a proof only when it sets source columns alone; see
Witness), or the function they apply differently.
Changes
- class konstruct.analysis.diff.Diff(base: 'str', head: 'str', files: 'dict[str, str]', base_commit: 'str | None' = None, head_commit: 'str | None' = None, selection: 'Selection' = <factory>, relations: 'list[RelationChange]' = <factory>, dags: 'list[DagChange]' = <factory>, other: 'list[str]' = <factory>, yaml: 'list[YamlChange]' = <factory>, definitions: 'list[DefinitionsChange]' = <factory>, paths: 'list[str]' = <factory>)[source]
Bases:
object- base: str
The commit compared with.
- head: str
The commit compared, or
working tree.
- files: dict[str, str]
Every changed file.
- base_commit: str | None = None
The commit a date given as
basestands for.
- head_commit: str | None = None
The commit a date given as
headstands for.
- relations: list[RelationChange]
Changes to dbt models and SQL.
- other: list[str]
Changed files that were not analysed.
- yaml: list[YamlChange]
Changes to dbt YAML.
- definitions: list[DefinitionsChange]
Changes to definitions.yml.
- paths: list[str]
Every changed file inside the project, relative to it, generated ones included.
- property shown_base: str
base, with the commit a date stands for (2026-10-01 (abc1234)).
- property shown_head: str
head, with the commit a date stands for.
- property source_files: list[str]
Changed files, leaving out generated ones (target/, logs/, konstruct-out/, packages).
- property breaking: list[tuple[str, ColumnChange]]
- class konstruct.analysis.diff.RelationChange(path: 'str', label: 'str', kind: 'str', status: 'str', node_id: 'str | None' = None, columns: 'list[ColumnChange]' = <factory>, conditions_added: 'list[str]' = <factory>, conditions_removed: 'list[str]' = <factory>, rows_impact: 'dict[str, Any] | None'=None, note: 'str | None' = None)[source]
Bases:
object- path: str
The changed file.
- label: str
The relation it builds.
- kind: str
model,tableand so on.
- status: str
added,removedormodified.
- node_id: str | None = None
The graph node, when there is one.
- columns: list[ColumnChange]
Column changes.
- conditions_added: list[str]
Join, filter and grouping conditions only at the head.
- conditions_removed: list[str]
Conditions only at the base.
- rows_impact: dict[str, Any] | None = None
What is downstream of a relation whose rows changed.
- note: str | None = None
Why the relation could not be compared, when it could not.
- property changed: bool
- class konstruct.analysis.diff.ColumnChange(column: 'str', change: 'str', before: 'str | None' = None, after: 'str | None' = None, impact: 'dict[str, Any] | None'=None, breaks: 'list[str]' = <factory>)[source]
Bases:
object- column: str
The column name.
- change: str
added,removedorchanged.
- before: str | None = None
The canonical expression at the base (CTEs and aliases inlined).
- after: str | None = None
The canonical expression at the head.
- impact: dict[str, Any] | None = None
What is downstream of the column, as
Walk.to_dict().
- breaks: list[str]
Relations that still read a removed column, which makes the change breaking.
- class konstruct.analysis.diff.DagChange(path: 'str', dag: 'str', status: 'str', tasks_added: 'list[str]' = <factory>, tasks_removed: 'list[str]' = <factory>, dependencies_added: 'list[str]' = <factory>, dependencies_removed: 'list[str]' = <factory>, runs_changed: 'list[str]' = <factory>, sql_changed: 'list[str]' = <factory>)[source]
Bases:
object- path: str
The changed DAG file.
- dag: str
The DAG id.
- status: str
added,removedormodified.
- tasks_added: list[str]
Task ids only at the head.
- tasks_removed: list[str]
Task ids only at the base.
- dependencies_added: list[str]
Dependencies only at the head, as
a >> b.
- dependencies_removed: list[str]
Dependencies only at the base.
- runs_changed: list[str]
Tasks whose dbt selection changed.
- sql_changed: list[str]
Tasks whose SQL changed.
- class konstruct.analysis.diff.YamlChange(path, tests_added=<factory>, tests_removed=<factory>, descriptions=<factory>, sources_added=<factory>, sources_removed=<factory>, exposures=<factory>, note=None)[source]
Bases:
objectWhat changed in one dbt YAML file: tests, descriptions, sources and exposures.
- path: str
The changed dbt YAML file.
- tests_added: list[str]
Tests only at the head.
- tests_removed: list[str]
Tests only at the base.
- descriptions: list[str]
Descriptions that changed.
- sources_added: list[str]
Source tables only at the head.
- sources_removed: list[str]
Source tables only at the base.
- exposures: list[str]
Exposures added, removed or changed.
- note: str | None = None
Why the file could not be compared, when one of its versions cannot be read.
- property changed: bool
- class konstruct.analysis.diff.DefinitionsChange(path, added=<factory>, removed=<factory>, changed=<factory>, approved_changed=<factory>, marked=<factory>, unmarked=<factory>)[source]
Bases:
objectWhat changed in a definitions.yml: terms added or removed, and per term which fields.
- path: str
The changed definitions file.
- added: list[str]
Terms only at the head.
- removed: list[str]
Terms only at the base.
- changed: list[str]
One line per changed field, such as
Order amount: status agreed -> approved.
- approved_changed: list[str]
Approved terms whose meaning changed (definition, columns or filters) but stayed approved.
- marked: list[str]
those of a term that gains a
sensitivityor a new one, and those added to a sensitive term.- Type:
The columns, as the file names them, that the change marks sensitive
- unmarked: list[str]
a sensitive term removed, its
sensitivityremoved or changed, or a column taken out of it.- Type:
One line per sensitivity mark the change takes away
- property changed_any: bool
- class konstruct.analysis.diff.Selection(models=<factory>, tests=<factory>, modified_plus=<factory>)[source]
Bases:
objectThe dbt nodes a change actually needs rebuilt and tested, column by column, against what state:modified+ would build.
- models: list[str]
dbt models whose data the change can affect.
- tests: list[str]
dbt tests on those models.
- modified_plus: list[str]
What
state:modified+would select, for comparison.
- property selector: str
The Gate
- class konstruct.analysis.gate.Gate(base, findings=<factory>, changed=0, laws_from=None)[source]
Bases:
objectThe result of the gate on one change.
- base: str
The commit compared with, as given.
- changed: int = 0
Files the change touches.
- laws_from: str | None = None
Where the data laws the change is judged against came from (
konstruct-out/laws.json at main), or that there were none, so no data law was checked.
- property passed: bool
Whether nothing stops the change.
- property verdict: str
Gate passedorGate failed.
- class konstruct.analysis.gate.Finding(rule, message, path=None, fix='')[source]
Bases:
objectOne thing that stops a change, from a gate check.
- rule: str
breaking,approved-definition-changed,conflicting-definition,data-law-brokenorconsumer-pin-changedfor Konstruct’s own checks,<plugin>.<rule>for a plugin’s.- Type:
The rule
- message: str
What is wrong.
- path: str | None = None
The file to change, when one is known, relative to the project.
- fix: str = ''
What to do about it.
SQL Checks
- class konstruct.analysis.checksql.SqlCheck(sql, findings=<factory>, corrected=None, statements=0, terms=<factory>, dialect=None)[source]
Bases:
objectThe result of checking one piece of SQL against the project.
- sql: str
The SQL as given.
- corrected: str | None = None
The SQL with every safe fix applied, when there was one to apply.
- statements: int = 0
Statements checked.
- terms: list[str]
Business terms the query reads through their own columns.
- dialect: str | None = None
The SQL dialect the query was read in.
- property checked: bool
one that parses but cannot be analysed has an
uncheckednote, and makes this false.- Type:
Whether every statement was analysed
- property passed: bool
Whether every statement was checked and nothing breaks an approved definition (no finding of severity
error): a statement never analysed does not pass.
- failed(*, strict=False)[source]
Whether the check fails: on an error, or with
stricton a warning as well.
- class konstruct.analysis.checksql.Finding(rule, severity, message, fix, column=None, term=None, detail=None, rewrite=None, evidence=<factory>)[source]
Bases:
objectOne thing the query gets wrong, or may, with what to do about it.
- rule: str
One of
RULES.
- severity: str
error(an approved definition is broken),warningorinfo.
- message: str
What is wrong.
- fix: str
What to change.
- column: str | None = None
The output column it concerns, when there is one.
- term: str | None = None
The business term it concerns, when there is one.
- detail: str | None = None
what groups findings in history.
- Type:
The filter that is missing or extra, or the formula the query uses
- rewrite: str | None = None
The change made in the corrected SQL, when the fix was safe to make.
- class konstruct.analysis.checksql.HistoryCheck(queries=0, runs=0, unparsed=0, redacted=0, shadow=<factory>, adoption=<factory>, rules=<factory>, notes=<factory>)[source]
Bases:
objectThe checks run over query history: shadow metrics, and how widely each term’s own columns are read.
- queries: int = 0
Distinct query texts checked.
- runs: int = 0
Runs of those queries.
- unparsed: int = 0
Query texts that could not be parsed.
- redacted: int = 0
Runs whose text the warehouse withheld, which could not be checked.
- shadow: list[ShadowMetric]
Measures computed differently from their definitions, approved terms first, then by people.
- rules: dict[str, int]
Queries with a finding of each rule.
- notes: list[str]
What the warehouse withheld, and what would show it.
- property approved_shadow: list[ShadowMetric]
queries that compute an approved measure differently from its definition.
- Type:
The shadow metrics of approved terms
- class konstruct.analysis.checksql.ShadowMetric(term, status, rule, detail, fix, queries=0, runs=0, users=0, people=<factory>, example='')[source]
Bases:
objectOne way that queries in history compute a measure differently from its definition.
- term: str
The measure, or the output name it was computed under when no term holds it.
- status: str | None
The term’s status, or None for an idea with no term.
- rule: str
missing-filterorformula-differs.
- detail: str
the filter left out or added, or the formula used.
- Type:
What they do differently
- fix: str
What to do instead, from the first query found.
- queries: int = 0
Distinct query texts that compute it this way.
- runs: int = 0
Runs of those queries.
- users: int = 0
Distinct people who ran them.
- people: list[str]
Who ran them, by name; empty when the check pseudonymises.
- example: str = ''
The text of the query run most often; with its literals left out when the check pseudonymises, since they can identify a customer or a person.
- class konstruct.analysis.checksql.Adoption(term, queries=0, runs=0, users=0)[source]
Bases:
objectHow many queries read a term through its own columns.
- term: str
The business term.
- queries: int = 0
Distinct query texts that read it.
- runs: int = 0
Runs of those queries.
- users: int = 0
Distinct people who ran them.
Verification
- class konstruct.analysis.verify.Verification(warehouse: 'str', seed: 'int', max_rows: 'int', strategies: 'tuple[str, ...]', models: 'list[ModelResult]' = <factory>, seconds: 'float' = 0.0, keys: 'list[KeyCheck]' = <factory>, links: 'list[LinkCheck]' = <factory>, ontology_seconds: 'float' = 0.0, only: 'list[str]' = <factory>)[source]
Bases:
object- warehouse: str
The DuckDB file or Postgres database read.
- seed: int
The random seed, so a run can be repeated exactly.
- max_rows: int
The most rows sampled from any input.
- strategies: tuple[str, ...]
The perturbations applied (shift, null and shuffle).
- models: list[ModelResult]
One result per model or table checked.
- seconds: float = 0.0
Total time taken.
- ontology_seconds: float = 0.0
Time taken by the checks of the ontology’s keys and relationships.
- only: list[str]
The models and tables the run was limited to; empty for every one.
- property checked: list[ModelResult]
- class konstruct.analysis.verify.ModelResult(node_id: 'str', label: 'str', rows: 'int' = 0, claimed: 'int' = 0, confirmed: 'list[tuple[str, str]]'=<factory>, not_exercised: 'list[tuple[str, str]]'=<factory>, through_references: 'list[tuple[str, str]]'=<factory>, unexplained: 'list[tuple[str, str]]'=<factory>, skipped: 'str | None' = None, seconds: 'float' = 0.0)[source]
Bases:
object- node_id: str
The model or table checked.
- label: str
Its short name.
- rows: int = 0
Rows sampled from each input.
- claimed: int = 0
Column edges the graph claims, as (output column, input column).
- confirmed: list[tuple[str, str]]
Claimed edges whose output changed when the input was perturbed.
- not_exercised: list[tuple[str, str]]
Claimed edges no perturbation moved on this data.
- through_references: list[tuple[str, str]]
Observed influences the graph records as joins, filters or grouping.
- unexplained: list[tuple[str, str]]
Observed influences the graph does not record, which are gaps in the static analysis.
- skipped: str | None = None
Why the model was not checked, when it was not.
- seconds: float = 0.0
Time taken.
- class konstruct.analysis.integrity.KeyCheck(entity, column, state, rows=0, nulls=0, duplicates=0, reason=None, id='')[source]
Bases:
objectOne entity key column, checked for duplicate and null values.
- entity: str
The entity it is the key of.
- column: str
The key column, by label (
customers.customer_id).
- state: str
confirmed(no value repeats and none is null),failed, orskipped.
- rows: int = 0
The rows of its table.
- nulls: int = 0
Rows where the key is null.
- duplicates: int = 0
Values that a second row repeats.
- reason: str | None = None
Why it was skipped, when it was.
- id: str = ''
The column’s graph id, for tools; not written out.
- property detail: str
What the check found, in a sentence.
- class konstruct.analysis.integrity.LinkCheck(source, target, via, state, values=0, orphans=0, reason=None, one_to_one=None, columns=('', ''))[source]
Bases:
objectOne relationship, checked for values of the referring column that the key lacks.
- source: str
The entity that refers (
order).
- target: str
The entity referred to (
customer).
- via: str
The columns that join them (
orders.customer_id = customers.customer_id).
- state: str
confirmed(every value is found and the key is confirmed),failed, orskipped.
- values: int = 0
The non-null values of the referring column.
- orphans: int = 0
Those not found in the key.
- reason: str | None = None
Why it was skipped, or why a check that found no orphan still failed.
- one_to_one: str | None = None
For a relationship claimed one-to-one, whether the referring column is unique and never null:
confirmed,failed(it is many-to-one; every value may still be found), orskipped; left out for one claimed many-to-one.
- columns: tuple[str, str] = ('', '')
The graph ids of the referring column and the key, for tools; not written out.
- property detail: str
What the check found, in a sentence.
Mutation Testing
- class konstruct.analysis.mutation.MutationTesting(warehouse, seed, max_rows, max_mutants, operators, models=<factory>, seconds=0.0)[source]
Bases:
objectThe result of
mutate(): oneModelMutationper model, and the settings that make the run repeatable.- warehouse: str
The DuckDB file, Postgres database or sampled warehouse the mutants ran on.
- seed: int
The seed of the mutant sample, so a run can be repeated exactly.
- max_rows: int
The most rows sampled from any input.
- max_mutants: int
The most mutants run per model.
- operators: tuple[str, ...]
The kinds of mutation made.
- models: list[ModelMutation]
One result per model.
- seconds: float = 0.0
Total time taken.
- property checked: list[ModelMutation]
- property score: float | None
- class konstruct.analysis.mutation.ModelMutation(node_id, label, rows=0, sites=0, tests=<factory>, mutants=<factory>, skipped=None, seconds=0.0)[source]
Bases:
objectThe mutation testing of one dbt model: its tests, its mutants and its score.
- node_id: str
The dbt model.
- label: str
Its short name.
- rows: int = 0
Rows the original model returns on the sample.
- sites: int = 0
Every mutation its SQL admits, before sampling.
- mutants: list[MutantResult]
The sampled mutants and what became of each.
- skipped: str | None = None
Why the model was not mutation tested, when it was not.
- seconds: float = 0.0
Time taken.
- property score: float | None
the share of the changes the data shows that the tests notice. None when no mutant changed the output.
- Type:
Killed over killed and survived
- columns()[source]
The score of each output column (and
(row count)), over the mutants that changed it.
- property survivors: list[MutantResult]
Survivors, the ones with the most downstream reach first.
- class konstruct.analysis.mutation.MutantResult(mutant, status, rows=0, changed=<factory>, killed_by=<factory>, reach=0, suggestions=<factory>, error=None)[source]
Bases:
objectWhat became of one mutant: its status, what it changed and what killed it.
- status: str
One of
STATUSES.
- rows: int = 0
Rows the mutant returned.
- changed: list[str]
Output columns whose values changed, and
(row count)when the number of rows did.
- killed_by: list[str]
The tests that failed on the mutant.
- reach: int = 0
Nodes downstream of the changed columns, which ranks survivors by what they would break.
- suggestions: list[Suggestion]
For a survivor, the tests that would have killed it.
- error: str | None = None
Why an invalid mutant could not be judged.
- class konstruct.analysis.mutation.TestCheck(id, name, test, column=None, state='used', reason=None, kills=0)[source]
Bases:
objectOne dbt test of the model (a data test or a unit test) and whether it could be used.
- id: str
The dbt test’s unique id.
- name: str
Its name.
- test: str
not_null, relationships, a package test, singular, or unit_test.
- Type:
Its kind
- column: str | None = None
The column it tests, when it is a column test.
- state: str = 'used'
used;fails on the sample(a data test that fails before any mutation, so it cannot tell a mutant apart);fails on the original(a unit test the original model already fails); ornot run(it has no compiled SQL, its SQL will not run here, or its rows cannot be read).
- reason: str | None = None
Why it was not used.
- kills: int = 0
Mutants it killed.
- class konstruct.analysis.mutation.Suggestion(test, column, reason, yaml, where='')[source]
Bases:
objectA dbt test that passes on the original model and fails on a survivor.
- test: str
not_null, unique, accepted_values, relationships, dbt_utils.expression_is_true or unit_test.
- Type:
The dbt test
- column: str | None
The column it is on, for a column test.
- reason: str
Why it kills the mutant, from the two outputs.
- yaml: str
The test as a YAML list item, to paste where
wheresays. Empty when the test is one already suggested for another survivor.
- where: str = ''
Where the YAML goes in the project’s properties files.
- class konstruct.analysis.mutation.ColumnScore(killed=0, survived=0)[source]
Bases:
objectThe mutants that changed one output column, killed and survived.
- killed: int = 0
Mutants that changed the column and that a test killed.
- survived: int = 0
Mutants that changed the column and that every test let through.
- property score: float | None
- class konstruct.core.mutate.Mutant(id, operator, description, before, after, sql, scope=None, column=None)[source]
Bases:
objectOne mutation of a query: what changed, where, and the whole mutated SQL.
- id: str
the operator and the mutation’s position in the query.
- Type:
Stable within one query
- operator: str
One of
OPERATORS.
- description: str
What changed, in words.
- before: str
The SQL fragment that changed.
- after: str
What it became (empty when it was removed).
- sql: str
The whole mutated query, in the dialect it was read in.
- scope: str | None = None
The CTE the change is in, or None for the outer query.
- column: str | None = None
The outer query’s output column the change sits in, when it is inside one.
- konstruct.core.mutate.mutants(sql, dialect='duckdb', *, operators=('aggregate', 'filter', 'join_condition', 'join_type', 'boundary', 'comparison', 'date', 'coalesce', 'case'), limit=None, seed=0)[source]
The mutants of the query
sql: all of them, or a seeded sample of at mostlimit.The sample is reproducible for a given
seedand draws from each operator in turn. A mutation whose SQL is the original’s, or another mutant’s, is dropped as a duplicate.
The Rows Behind a Value
- class konstruct.analysis.why.Why(column, label, where, warehouse, value=None, matches=0, expression=None, composed=None, step=None, aggregates=<factory>, contributions=<factory>, contributors=0, excluded=<factory>, exclusions=0, limit=50, consistent=None, notes=<factory>, seconds=0.0, masked=<factory>)[source]
Bases:
objectThe rows behind one value of a column.
- column: str
The column id.
- label: str
Its short name.
- where: dict[str, str]
output column to value.
- Type:
The key that picks the row
- warehouse: str
Where it ran.
- value: Any = None
The value, computed by the model’s SQL in the sandbox.
- matches: int = 0
How many rows of the model the key matches, 1 when it picks one row.
- expression: str | None = None
The column’s own formula.
- composed: str | None = None
The formula in terms of source columns.
- step: str | None = None
The model whose step computes the value, which the contributions are rows of.
- contributions: list[Contribution]
The contributing rows, up to
limit, largest share first.
- contributors: int = 0
How many rows contribute, before the cap.
- exclusions: int = 0
How many related rows were left out, before the cap.
- limit: int = 50
The most rows listed in each table.
- consistent: bool | None = None
Whether the shares add up to the value (None where that cannot be checked).
- notes: list[str]
sampled inputs, untraced exclusions, approximate shares.
- Type:
Limits of this explanation
- seconds: float = 0.0
Time taken.
- masked: list[str]
The sensitive columns whose values are shown masked, by short name (see
konstruct.analysis.sensitivity).
- property capped: bool
Whether either table was cut at
limit.
- property decomposable: bool
- class konstruct.analysis.why.Contribution(rows, values, chosen=False)[source]
Bases:
objectOne input row of the computing step, with the source rows behind it and its share.
- rows: list[SourceRow]
The source rows that make up this input row, the one the formula reads first.
- values: list[Any]
the addend of a SUM or AVG, 1 or 0 for a COUNT, the candidate of a MIN or MAX, or the input value of an aggregate that cannot be split. For a value computed without an aggregate, the value itself.
- Type:
Per aggregate, this row’s share
- chosen: bool = False
this row holds the result.
- Type:
For MIN and MAX
- class konstruct.analysis.why.Exclusion(row, reason)[source]
Bases:
objectA source row related to the value by the key that a filter or a join left out.
- reason: str
The model and the WHERE condition, HAVING condition or join that removed it.
- class konstruct.analysis.why.SourceRow(relation, row_id, values=<factory>, shown=<factory>)[source]
Bases:
objectOne row of a source table.
- relation: str
The source’s short name.
- row_id: str
The row’s position in the sandbox copy of the source, which identifies it in this run.
- values: dict[str, Any]
The row’s columns.
- shown: list[str]
The columns that identify it or that the formula reads, shown in the table.
- class konstruct.analysis.why.Aggregate(sql, function, decomposable, total=None, count=0, addends=None)[source]
Bases:
objectOne aggregate in the computing step.
- sql: str
The aggregate as written, with the step’s names.
- function: str
SUM, COUNT, AVG, MIN, MAX, or another aggregate’s name.
- decomposable: bool
Whether the value splits into one share per row (not for DISTINCT, MEDIAN and the like).
- total: Any = None
The aggregate recomputed from the rows’ shares.
- count: int = 0
those with a non-null input.
- Type:
The rows it counts
- addends: Any = None
For AVG, the sum of the addends, which
countdivides; None for the others.
Data Laws
- konstruct.analysis.laws.propose(graph, root, *, models=None, sql=None, without_tests=())[source]
The laws the lineage implies, from every dbt model’s compiled SQL and every SQL-built table’s, without reading the warehouse.
Each numeric measure column (not a key) whose total follows from an input through safe joins gets a conservation or count law, followed upstream through every model that keeps the total; each model that maps an input one row to one row gets a count law; each aggregate measure gets an additivity law for
confirm()to classify. AUNION ALLgives a law whose total is the sum of its branches’. Columns with no law are inblocked.models(graph ids) limits the reading to those models, such as the ones the laws ofLaws.models()run through, which is allviolations()needs.sqlreplaces the compiled SQL of the models it names, such as a model edited since dbt last compiled it, askonstruct.analysis.diff.rendered_now()renders it; None for a model whose file is gone.without_tests(graph ids) are tests the change removes, which the graph may still hold.
- konstruct.analysis.laws.confirm(graph, root, warehouse, laws=None, *, tolerance=1e-09, max_rows=10000)[source]
Check
laws(proposed from the graph when not given) against the warehouse, read-only, in the engine verify uses: a DuckDB file attached read-only, a Postgres transaction that is rolled back, or a sample of a remote warehouse.Conservation and count laws compare totals per day within
tolerance(relative to the day’s sum of magnitudes; counts exactly) and need the full tables, so on a sampled warehouse they are left unchecked, as is a law with no rows to compare. A filter or partition that is not one expression over the relation’s columns, as a hand-edited laws.json could give, leaves its law unchecked. Additivity laws rerun the model’s SQL on up tomax_rowsrows of each input: with every row of the measure’s input twice, with one row twice, and on two halves of it split at the middle value of a date (or of the measure’s input column).
- konstruct.analysis.laws.violations(graph, laws, change, *, changed=None)[source]
The laws in
laws(as confirmed before a change) that held on every partition and that the changed project no longer satisfies.changeispropose()on the changed project andgraphits graph. A conservation or count law is violated when one of its steps no longer follows from that model’s SQL (a new join on a key no uniqueness test covers, a new filter, a removed test, a different factor) and the violation names the model, nearest the source first; an additivity law is violated when the SQL now suggests another class. Nothing is run.changed(the files a change touches, relative to the project) keeps only the violations the change makes: a step that differs although none of its files changed was broken before it, so a law broken on the base fails the change that broke it, not every change after it.
- konstruct.analysis.laws.load(path)[source]
Read a
laws.jsonwritten byLaws.write().
- konstruct.analysis.laws.recorded(root, out_dir, base=None)[source]
The laws as recorded before a change, and where they came from: in a git repository, the
laws.jsoncommitted atbase(default: the merge base with main) and nothing else, so a copy left in the output directory never stands in for the base’s; with no git base to read, the onekonstruct verify --lawslast wrote toout_dir.- Raises:
konstruct.QueryError – The file cannot be read.
- class konstruct.analysis.laws.Laws(laws=<factory>, blocked=<factory>, steps=<factory>, dialect=None, warehouse=None, tolerance=1e-09, seconds=0.0)[source]
Bases:
objectThe laws of a project, proposed and, once confirmed, checked.
- FORMAT_VERSION = 1
- blocked: list[Blocked]
Every column (and row count) of a model with no step, with the reason. laws.json keeps the measures among them.
- steps: dict[tuple[str, str | None], tuple[Step, ...]]
Every step the SQL proves, by model id and column (None for the row count), one for each branch of a
UNION ALL, whether or not a law starts at it. Kept in memory, not in laws.json.
- dialect: str | None = None
The SQL dialect of the filters and partitions.
- warehouse: str | None = None
What the laws were checked against, once confirmed.
- tolerance: float = 1e-09
The relative tolerance of conservation laws.
- seconds: float = 0.0
Time taken.
- class konstruct.analysis.laws.Law(id, kind, relation, label, column, terms=<factory>, partition=None, expected=None, status='proposed', held=0, checked=0, failures=<factory>, broken_at=None, classification=None, evidence=None, note=None, unprobed=None, files=<factory>)[source]
Bases:
objectAn equality the data must obey, with the result of checking it once checked.
- id: str
kind:relation id#column, orcount:relation idfor a row count.
- kind: str
conservation(some term sums),count(every term counts) oradditivity.
- relation: str
The relation the law is about, by graph id.
- label: str
Its short name.
- column: str | None
Its column, or None for its row count.
- terms: list[Term]
the input the probes change.
- Type:
What the total equals
- Type:
the sum of the terms. For additivity, one term
- partition: str | None = None
The day each row falls on, as SQL over the relation’s columns; each term has the same day over its source’s.
- expected: str | None = None
additive, semi-additive or non-additive.
- Type:
For additivity, what the SQL suggests
- status: str = 'proposed'
proposed,holds,brokenorunchecked.
- held: int = 0
Partitions (or, for additivity, probe sets) on which the law held.
- checked: int = 0
Partitions (or probe sets) checked.
- broken_at: str | None = None
For a broken law, the model where the totals first part, by graph id.
- classification: str | None = None
For additivity, what the probes found.
- evidence: str | None = None
For additivity, what the probes did and saw.
- note: str | None = None
Why the law was not checked, when it was not.
- unprobed: str | None = None
a model that keeps rows by comparing them with others (DISTINCT, QUALIFY, LIMIT, HAVING or a filter with a subquery) keeps different rows once its input is repeated or split.
- Type:
For additivity, why the probes cannot classify the column, read from its SQL
- files: list[str]
For additivity, the files the relation’s SQL follows from, relative to the project, as a step records them; the other kinds keep them on their steps.
- property relies_on: list[str]
Every test the law’s joins depend on.
- property detail: str | None
15 of 15 days, where the totals first part, or why the law was not checked.- Type:
What checking found, beside the statement
- property always_held: bool
Whether the law was checked and held on every partition.
- property statement: str
The law in one line.
- class konstruct.analysis.laws.Term(source, source_label, source_column=None, op='sum', factor=1.0, filter=None, partition=None, steps=())[source]
Bases:
objectOne source of a law’s total:
factortimes the total of a source column. A law over aUNION ALLhas one term for each branch; any other law has one.- source: str
The relation the total comes from (for additivity, the input the probes change), by graph id.
- source_label: str
Its short name.
- source_column: str | None = None
The source’s column, or None for its row count.
- op: str = 'sum'
it counts the column’s values (or, with no column, the rows). The branches of a
UNION ALLcan differ.- Type:
sum- Type:
the term totals the source column;
count
- factor: float = 1.0
The constant between the two totals.
- filter: str | None = None
The source rows that count, as SQL over the source’s columns.
- partition: str | None = None
The day each source row falls on, as SQL over the source’s columns, when the law has one.
- class konstruct.analysis.laws.Step(model, column, input, input_column, op, factor=1.0, filter=None, partition=None, relies_on=(), branch=0, files=())[source]
Bases:
objectOne model’s part in a law: how a total of its output follows from a total of one input.
- model: str
The model or table whose SQL makes the step, by graph id.
- column: str | None
Its column, or None for its row count.
- input: str
The relation the step reads, by graph id.
- input_column: str | None
The input’s column, or None for its row count.
- op: str
SUM(column) = factor * COUNT(input column, or rows);
rows: the output has one row for each input row.- Type:
sum- Type:
SUM(column) = factor * SUM(input column);
count
- factor: float = 1.0
The constant the SQL multiplies by, such as 0.01 for cents to units.
- filter: str | None = None
The input rows that count, as SQL over the input’s columns.
- partition: tuple[str, str] | None = None
The day each row falls on, as SQL over the output’s columns and over the input’s.
- relies_on: tuple[str, ...] = ()
The tests that make the step’s joins safe, by graph id.
- branch: int = 0
The branch of the model’s
UNION ALLthe step reads, from 0; 0 for any other query.
- files: tuple[str, ...] = ()
the model’s and those of the tests it relies on. A change that touches none of them leaves the step as it was.
- Type:
The files the step follows from, relative to the project
- class konstruct.analysis.laws.Violation(law, statement, model, label, message, path=None)[source]
Bases:
objectA law that held on every partition and that a change no longer satisfies.
- law: str
The law’s id.
- statement: str
The law in one line.
- model: str
The model the change breaks it in, by graph id.
- label: str
The model’s short name.
- message: str
What changed.
- path: str | None = None
The model’s file, relative to the project, when known.
- class konstruct.analysis.laws.Failure(partition, expected, actual)[source]
Bases:
objectOne partition on which a law did not hold.
- partition: str
The day (
2026-01-31),nullfor rows without one, orall.
- expected: float
The total the source gives.
- actual: float
The total the relation has.
- class konstruct.analysis.laws.Blocked(model, column, reason, measure=True)[source]
Bases:
objectA measure column that has no law, and why.
- model: str
The model, by graph id.
- column: str | None
The column, or None for the model’s row count.
- reason: str
What in the SQL stops the law, such as a join on a key no uniqueness test covers.
- measure: bool = True
Whether the column is a measure (numeric and not a key), which would have a law of its own.
Answer Receipts
- class konstruct.analysis.receipts.Receipt(id, issued, refs, build, citation, question=None, answer=None, canonical='konstruct-5/sqlglot-30.30', pins=<factory>, terms=<factory>, digest='')[source]
Bases:
objectWhat one answer rests on, as it was when the receipt was issued.
- id: str
r-and 12 hex digits.
- issued: str
When it was issued, in UTC, ISO 8601, to the second.
- refs: list[str]
columns, relations, exposures or business terms.
- Type:
What the answer rests on, as named
- citation: str
each column with its formula and verification state, each term with its status, and the receipt’s id.
- Type:
One paragraph to put under the answer
- question: str | None = None
The question answered, when given.
- answer: str | None = None
The answer given, when given.
- canonical: str = 'konstruct-5/sqlglot-30.30'
The canonical form the formulas and conditions were printed in, as the lockfile records it.
- pins: list[Pin]
Each column the answer rests on, pinned as the lineage lockfile pins it, sorted by column.
- terms: list[PinnedTerm]
Every business term named in
refs, and every term bound to the relation of a column the answer rests on, as they were, sorted by name.
- class konstruct.analysis.receipts.Build(graph, generated=None, konstruct=None, commit=None, uncommitted=None, changed_inputs=<factory>)[source]
Bases:
objectThe build a receipt answered from, and the project when it was issued.
- graph: str
sha256:and the SHA-256 ofgraph.json, which names the exact graph read.
- generated: str | None = None
When the graph was built.
- konstruct: str | None = None
The Konstruct release that built it.
- commit: str | None = None
The commit checked out when the receipt was issued; None outside git.
- uncommitted: bool | None = None
Whether tracked files had uncommitted changes then; None outside git.
- changed_inputs: list[str]
the answer came from a graph older than these.
- Type:
Inputs the build read that had changed since it ran, when the receipt was issued
- class konstruct.analysis.receipts.ReceiptCheck(directory, receipts=<factory>, changed_inputs=<factory>)[source]
Bases:
objectkonstruct receipts check: whether what each answer rests on still holds.- directory: str
The folder of receipts, relative to the project.
- receipts: list[CheckedReceipt]
Every receipt checked, in the order issued.
- changed_inputs: list[str]
while there are any, no receipt is said to hold.
- Type:
Inputs changed since the graph was built
- property passed: bool
True when every receipt checked holds.
- class konstruct.analysis.receipts.CheckedReceipt(id, status, file, line, issued=None, refs=<factory>, question=None, changes=<factory>, terms=<factory>, notes=<factory>, rebuilt=False, problem=None)[source]
Bases:
objectOne receipt, checked against the current graph.
- id: str
The receipt’s id, or for a line that is not a receipt, where it is.
- status: Literal['holds', 'stale', 'unchecked', 'tampered', 'unreadable']
holds(everything it rests on is as it was),stale(something changed),unchecked(the graph is older than its inputs, so it cannot say),tampered(the record does not match its digest) orunreadable.
- file: str
The file the receipt is in, relative to the project.
- line: int
Its line in the file.
- issued: str | None = None
When it was issued.
- refs: list[str]
What it rests on, as named.
- question: str | None = None
The question it answered, when it was given.
- changes: list[PinChange]
Each column it rests on that changed, with what changed and, from git history, who changed it.
- terms: list[str]
Each change to an approved term it rests on other than through a column’s pin.
- notes: list[str]
a verification lost, a draft term reworded.
- Type:
What changed without making it stale
- rebuilt: bool = False
Whether the graph is another build than the one it answered from.
- problem: str | None = None
For a receipt that is unchecked, tampered or unreadable, why.
The Lineage Lockfile
- konstruct.analysis.lock.lock(graph, root, reads=None, *, update=False, path=None, write=True)[source]
Pin the upstream columns this project reads in
konstruct.lock.readsnames what to pin (columns, relations, exposures or terms); None keeps what the lockfile already names, and an empty list (or no lockfile) pins every upstream column the project’s own models and exposures read. Columns already pinned keep their pin, as a package lockfile keeps its versions, unlessupdateis True (accept every change) or names them; what changed without failing a check is brought up to date.
- konstruct.analysis.lock.check(graph, root, *, path=None, consumer=None, history=True, only=None)[source]
Compare every pin in
konstruct.lockwith this build of the graph.consumernames the repository (one ofrepositories) whose lockfile it is, when it is not the project’s own: then only the pins on columns this build builds are judged, each composed as far as the consumer’s build saw it.historyFalse skips reading git history to say who changed what.only(graph ids) judges only the pins on those columns, such as the ones a change alters, and leaves the rest, and the reads not pinned, unremarked.
- konstruct.analysis.lock.check_consumers(graph, root, *, history=True, only=None)[source]
The lockfile of every repository in
repositoriesthat has one, checked against this graph: run in the producer, it names each consumer a change would break.onlyis as forcheck().
- class konstruct.analysis.lock.LockResult(path, lockfile, added=<factory>, removed=<factory>, updated=<factory>, kept=<factory>, stale=<factory>, missing=<factory>, written=True)[source]
Bases:
objectWhat
konstruct lockwrote.- path: str
The lockfile, relative to the project.
- added: list[str]
Columns pinned for the first time.
- removed: list[str]
Columns no longer read, or whose removal was accepted, so no longer pinned.
- updated: list[str]
Pinned columns whose change was accepted.
- kept: list[str]
Pinned columns left as they were locked, or brought up to date where nothing that fails a check changed (an approval date, a verification, a term that was not approved).
- stale: list[str]
Kept columns that fail
konstruct lock --checkagainst this build.
- missing: list[str]
Columns this project reads that the relation’s builder no longer builds.
- written: bool = True
Whether the lockfile was written.
- class konstruct.analysis.lock.LockCheck(path, pinned, changes=<factory>, notes=<factory>, consumer=None, canonical='konstruct-5/sqlglot-30.30', relock=0)[source]
Bases:
objectkonstruct lock --check: whether every pinned column still means what was locked.- path: str
The lockfile, relative to the project.
- pinned: int
How many columns the lockfile pins.
- notes: list[str]
a verification lost, a term re-approved, a new upstream read not yet pinned, a pin on a column this build does not build.
- Type:
What changed without failing
- consumer: str | None = None
The repository whose lockfile this is, when it is not the project’s own.
- canonical: str = 'konstruct-5/sqlglot-30.30'
The canonical form the lockfile was written with.
- relock: int = 0
they fail the check once, as a whole, rather than one by one.
- Type:
With a canonical form this Konstruct does not write, how many pins read differently
- property passed: bool
- relock_lines()[source]
With a canonical form this Konstruct does not write, what to do: review, then re-lock.
- class konstruct.analysis.lock.PinChange(column, label, reasons, repository=None, path=None, removed=None, formula_before=None, formula_after=None, conditions_added=<factory>, conditions_removed=<factory>, sources_added=<factory>, sources_removed=<factory>, files=<factory>, type_before=None, type_after=None, terms=<factory>, changed_by=<factory>, affected=<factory>)[source]
Bases:
objectOne pinned column that is not what was locked.
- column: str
The pin’s key.
- label: str
The column’s short name in this build.
- reasons: list[str]
removed,unknown(the formula could not be worked out now),formula,conditions,sources,files(a file that builds a column with no recorded expression),upstream(a column further up than a formula cut short shows),typeordefinition.- Type:
What changed
- repository: str | None = None
The repository that builds the column, when it is one listed in
repositories.
- path: str | None = None
The file that builds the column, relative to the project.
- removed: str | None = None
no such column in this buildorno longer built by ....- Type:
For a removed column, why
- formula_before: str | None = None
The locked formula, composed down to source, with short names.
- formula_after: str | None = None
The formula now.
- conditions_added: list[str]
Row conditions the column has now and did not have when locked.
- conditions_removed: list[str]
Row conditions it had when locked and no longer has.
- sources_added: list[str]
Source columns the formula reaches now and did not.
- sources_removed: list[str]
Source columns it reached and no longer does.
- files: list[str]
Relations whose file changed, for columns with no recorded expression.
- type_before: str | None = None
The locked type.
- type_after: str | None = None
The type now.
- terms: list[str]
One line per change to a bound approved term.
- changed_by: list[Attribution]
The commits (or uncommitted edits) that made the change, read from git history.
- affected: list[str]
This project’s columns and exposures that read the column, directly or further down.
- property accept: str
- unprint()[source]
Set aside what differs only as a canonical form can print it (a formula, a condition, a column upstream), keeping the rest; whether there was any.
- property keep: str | None
undo the change where it was made, or, for a commit in another team’s repository, ask them to.
- Type:
How to keep the locked meaning instead
- class konstruct.analysis.lock.Attribution(what, sha, author, date, subject, path=None, repository=None, before=None, after=None)[source]
Bases:
objectA commit (or uncommitted edit) that changed something a pin relies on.
- what: str
a column’s formula, a file, or a term.
- Type:
What it changed
- sha: str
Empty for uncommitted changes.
- author: str
Who made the change.
- date: str
When it was committed, in ISO 8601.
- subject: str
The commit’s subject line.
- path: str | None = None
The file changed, relative to the project.
- repository: str | None = None
The git repository it is in, relative to the project (
.for the project’s own).
- before: str | None = None
What it changed, before the change, where that can be shown.
- after: str | None = None
And after.
- property short: str
- class konstruct.analysis.lock.Lockfile(pins=<factory>, reads=<factory>, format=1, canonical='konstruct-5/sqlglot-30.30', _index=<factory>)[source]
Bases:
objectThe contents of
konstruct.lock.- reads: list[str]
columns, relations, exposures or terms, as given to
konstruct lock; empty for the default, every upstream column this project’s own models and exposures read.- Type:
What was locked
- format: int = 1
The layout of the file (see
FORMAT).
- canonical: str = 'konstruct-5/sqlglot-30.30'
The canonical form the pins were printed in (see
CANONICAL).
- stored()[source]
The pins, sorted, each with the names of the relations it relies on, and those relations’ conditions by name: once per relation, so pins that read the same relations share them. Pins kept as locked may rely on conditions a relation no longer has, which are stored beside the current ones as
name~2and so on.
- class konstruct.analysis.lock.Pin(column, id, fingerprint, formula=None, rows=<factory>, sources=<factory>, files=<factory>, type=None, complete=True, verified=None, repository=None, used_by=<factory>, terms=<factory>)[source]
Bases:
objectOne upstream column, pinned by what it means.
- column: str
the lock’s key.
- Type:
The column as
relation.columnwith the relation’s warehouse name
- id: str
The graph node, which finds the column again in a build from this project.
- fingerprint: str
sha256:and the 64 hex digits of a hash over the formula, conditions, sources and files, and, for a formula cut short, the expression of every column on its way.
- formula: str | None = None
The formula composed down to source columns, with full relation names, in canonical form; None for a column read straight from a source.
- rows: list[Rows]
The conditions of every relation on the formula’s way and of the relations they join, filter or group on, relation by relation.
- sources: list[str]
The source columns the formula reaches.
- files: list[str]
For each relation on the way whose columns have no recorded expression (a seed, a Python model, SQL that could not be read), the relation and a checksum of the file that builds it.
- type: str | None = None
The column’s type, when the build knows it.
- complete: bool = True
False when some column on the way, other than a seed’s, has no recorded expression.
- verified: str | None = None
confirmed,partly confirmedornot exercisedoncekonstruct verifyhas run.
- repository: str | None = None
The repository that builds the column, when it is one listed in
repositories.
- used_by: list[str]
This project’s columns and exposures that read the column directly.
- terms: list[PinnedTerm]
The business terms bound to the column, as they were when it was locked.
- property conditions: list[str]
The joins, filters and grouping that decide which rows feed the column, composed down to source columns the same way as the formula, each as it applies to the column.
- class konstruct.analysis.lock.Rows(relation, conditions, side=None, before=<factory>)[source]
Bases:
objectThe conditions on one relation’s rows that a pin relies on, and where they apply.
- relation: str
The relation’s warehouse name.
- conditions: list[str]
Its joins, filters and grouping, composed down to source columns in canonical form.
- side: str | None = None
LEFT,RIGHTorFULLwhen the pin reaches the relation through the side of an outer join that may find no row of it: its filters then decide which rows join.
- before: list[str]
The window functions (
WINDOW) and LIMITs (LIMIT) between the relation and the pinned column, nearest first: its conditions change what they see.
- property effective: list[str]
The conditions as they apply to the pinned column (see
in_context()).
- property reference: str
- class konstruct.analysis.lock.PinnedTerm(name, status, version, approved=None, owner=None, definition=None, grain=None, filters=<factory>, columns=<factory>, unit=None, currency=None, exceptions=None, formula=None)[source]
Bases:
objectA business term bound to a pinned column, as it was when the column was locked.
- name: str
The term’s name.
- status: str
draft,agreed,approvedorretired.
- version: str
its definition, grain, columns, filters, unit, currency, exceptions and, for a measure with one, its formula. Rewording any of them is a new version; a new approval date alone is not.
- Type:
A hash of the term’s meaning
- approved: str | None = None
The approval date.
- owner: str | None = None
Who owns the term.
- definition: str | None = None
What the term means, in words.
- grain: str | None = None
What one row of the term is.
- filters: list[str]
The filters the term requires.
- columns: list[str]
The columns the term names.
- unit: str | None = None
The unit of its values.
- currency: str | None = None
The currency of its values.
- exceptions: str | None = None
What the term leaves out.
- formula: str | None = None
For a measure defined by a formula (an Ossie metric), the formula.
- class konstruct.analysis.lock.LockError(message, *, suggestions=None, hint=None)[source]
Bases:
QueryErrorA lockfile that cannot be read or written, or a selection that names nothing to pin.
Builds
- class konstruct.engine.build.BuildResult(graph: 'Graph', outputs: 'list[Path]', seconds: 'float', cache_hits: 'int', cache_misses: 'int', inputs: 'dict[str, int]'=<factory>)[source]
Bases:
object- outputs: list[Path]
The files written (graph.json, REPORT.md, graph.html and ontology.yml).
- seconds: float
Time taken.
- cache_hits: int
Analyses reused from the previous build.
- cache_misses: int
Analyses computed afresh.
- inputs: dict[str, int]
Counts of what was read, such as dbt projects, warehouses, DAGs and terms.
The Graph
project.graph is the konstruct.Graph behind every answer, for anything the questions
above do not cover. Nodes and edges are dataclasses (dataclasses.fields, asdict and replace work on them; vars(node) is empty, as their fields are not in a __dict__); the edge kinds and their directions are
listed in Outputs and the Graph Format.
graph = project.graph
orders = graph.resolve('orders')[0]
for column in graph.columns_of(orders.id):
upstream = [graph.label(graph.nodes[e.target]) for e in graph.out_edges(column.id, 'derived_from')]
print(column.name, column.attrs.get('expression'), upstream)
- class konstruct.Graph
Bases:
objectThe graph.
- add_column(parent, name, *, data_type=None, description=None, origin=None, attrs=None)
- add_edge(source, target, kind, **attrs)
Add an edge, or merge attrs into an existing one of the same kind between the same nodes. Self-loops are ignored and return None.
- add_node(node)
Add a node, or merge it into an existing node with the same id.
- built(relation)
Whether something in the graph builds relation: a model, seed or snapshot, a file of its own, a task, or SQL that gives its columns expressions. A source, or a table the graph only reads, is not built.
- changed()
Note that nodes or edges were edited in place (a name, a relation key, an edge’s role), so the indexes queries use are made again.
- column(parent_id, name)
The column name of the relation parent_id, or None.
- columns_of(node_id)
The columns of a relation, in their order.
- downstream_edges(node_id)
Edges along which a change to node_id propagates, with the node they reach: feeds forwards, and derived_from, tests and runs backwards.
- edges
Every edge, in the order they were added.
- expand(node)
A relation stands for itself and all its columns; anything else for itself.
- fed_by_columns_of(consumer, relation)
Whether consumer (a Looker view, say) records which of relation’s columns it reads, so a column reaches it only through its own edge, and walks do not pass it every column of relation.
- classmethod from_dict(data, source='the graph')
The graph in data, as to_dict writes it in this or any earlier format.
- Raises:
konstruct.FormatError – A newer Konstruct wrote data (source names it).
- classmethod from_json(data, source='the graph')
The graph in data, the bytes of a graph.json, as read reads a file.
- Raises:
json.JSONDecodeError – data is not JSON; the message says where and why.
konstruct.FormatError – A newer Konstruct wrote it, it is not a graph, or it is larger than 2 GiB (source names it).
- guessed_relations()
The relations whose columns in the graph may not be all they have: the build marks a relation columns_guessed unless the warehouse or a dbt catalog lists its columns, or SQL that writes it selects them without a star it cannot expand.
- impact(node, max_depth=None)
Everything downstream of node.
A column reached through a join, filter or group-by (role = “reference”) is reported, but the walk does not continue from the whole relation it reached, so one join key does not mark every downstream column as affected. For a column, the exposures that use and the tasks that read or run each affected relation are reported too, and so are the tasks whose SQL an edge on the way came from (an Airflow SQL task, a Dagster asset). Tests and the tasks that run a node are reported, not walked through.
- in_edges(node_id, kind=None)
Edges arriving at node_id, optionally only of one kind.
- indexed(name, build, *, basis='graph')
The index name that build makes from the graph, kept until what it is made from changes: anything in the graph (‘graph’), a node (‘nodes’, for an index that reads no edges) or a relation (‘relations’).
- label(node)
Short human label: orders.amount for a column, orders for a relation.
- meta
its name, the inputs it read and so on.
- Type:
What the build records about the project
- node_for_reading(name)
The relation a name in canonical text stands for, whether the relation itself or one reading of it (see split_reading).
- node_for_relation(key)
The node SQL naming this relation refers to (see RELATION_PRIORITY).
- nodes
The nodes by id, in the order they were added.
- nodes_for_relation(key)
Every node that describes this relation, the preferred one first.
- out_edges(node_id, kind=None)
Edges leaving node_id, optionally only of one kind.
- parent_of(node)
A column’s relation, or None for anything else.
- path(start, end)
Shortest path from start to end, following data flow in either direction: downstream if there is a path that way, else upstream, else either way through contains and precedes too. Of several paths of the same length, each step takes the neighbour whose id sorts first, so the same path is found whatever order the build added the edges in.
- classmethod read(path)
Load a graph from a graph.json file, written by this or any earlier Konstruct.
- Raises:
json.JSONDecodeError – The file is not JSON; the message says where and why.
konstruct.FormatError – A newer Konstruct wrote it, it is not a graph, or it is larger than 2 GiB.
- relations()
- remove_edge(edge)
- resolve(ref)
Find the nodes a user reference such as orders.amount or stg_orders names.
- stats()
- to_dict()
The graph as plain data: format_version, meta, nodes, edges and warnings.
- trace(node, max_depth=None)
Everything upstream of node. For a column, the tasks that build each relation on the way (including the column’s own) are reported too.
- upstream_edges(node_id)
Edges along which node_id depends on something, with the node they reach: feeds and runs backwards, and derived_from forwards.
- warn(message)
- warnings
Notes about what the build could not read or resolve.
- write(path)
Write the graph to path as JSON (graph.json).
- class konstruct.Node(id, kind, name, parent=None, relation=None, description=None, data_type=None, path=None, origins=None, attrs=None)
Bases:
objectA node of the graph.
- attrs: dict[str, Any]
Everything else, such as expressions, conditions, schedules, usage and term fields.
- data_type: str | None
Column type.
- description: str | None
Description from dbt, the warehouse or definitions.yml.
- classmethod from_dict(data)
The node data describes, as to_dict writes it.
- id: str
A unique id, such as model.<package>.<name>, table.<relation>, <relation id>#<column>, task.<dag>.<task>, looker.view.<view>, powerbi.table.<model>.<table> or term.<slug>.
- kind: Literal['source', 'table', 'column', 'model', 'seed', 'snapshot', 'dag', 'task', 'test', 'exposure', 'term']
What the node is.
- name: str
Its own name.
- origins: list[str]
Inputs it came from.
- parent: str | None
For a column, its relation’s id.
- path: str | None
The file that defines it, relative to the project.
- relation: str | None
For a relation, its lower-case warehouse name, such as shop.main.orders.
- to_dict()
The node as plain data: id, kind and name, the fields that are set, its origins in order, and attrs when there are any.
- class konstruct.Edge(source, target, kind, attrs=None)
Bases:
objectAn edge of the graph: source -> target, with the meaning its kind gives (see konstruct.core.graph).
- attrs: dict[str, Any]
Details such as transform, role, verified and via.
- classmethod from_dict(data)
The edge data describes, as to_dict writes it.
- key
(source, target, kind), which names an edge in its graph.
- kind: Literal['contains', 'feeds', 'derived_from', 'runs', 'precedes', 'tests', 'defines', 'relates_to']
Edge kind.
- source: str
Source node id.
- target: str
Target node id.
- to_dict()
The edge as plain data: source, target and kind, and attrs when there are any.
Lower-Level Modules
These are stable enough to build on, but Project is the supported entry point.
SQL Lineage
- konstruct.core.sql.analyse(sql, *, dialect=None, schema=None, default_catalog=None, default_db=None, target=None, guessed=())[source]
Analyse every statement in
sql.targetnames the relation a bare SELECT writes (a dbt model’s relation). Relations created by earlier statements become part of the schema for later ones.guessednames the relations ofschemawhose columns are only those other queries were seen to read: they place a column, but are not taken to be all the columns the relation has.
- class konstruct.core.sql.StatementLineage(kind: 'str', target: 'str | None' = None, reads: 'list[str]' = <factory>, columns: 'dict[str, list[ColumnSource]]'=<factory>, references: 'list[tuple[str, str]]'=<factory>, errors: 'list[str]' = <factory>, expressions: 'dict[str, str]'=<factory>, conditions: 'list[str]' = <factory>, row_conditions: 'dict[str, list[int]]'=<factory>, aggregated: 'list[str]' = <factory>, readings: 'dict[str, str]'=<factory>, nullable: 'dict[str, str]'=<factory>, read_before: 'dict[str, list[str]]'=<factory>, star_reads: 'dict[str, list[str]]'=<factory>)[source]
Bases:
object- kind: str
- target: str | None = None
- reads: list[str]
- columns: dict[str, list[ColumnSource]]
- references: list[tuple[str, str]]
- errors: list[str]
- expressions: dict[str, str]
- conditions: list[str]
- row_conditions: dict[str, list[int]]
- aggregated: list[str]
- readings: dict[str, str]
- nullable: dict[str, str]
- read_before: dict[str, list[str]]
- star_reads: dict[str, list[str]]
Release Notes
- class konstruct.analysis.release_notes.ReleaseNotes(since, until, changes=<factory>, commits=0, notes=<factory>, warehouse=None)[source]
Bases:
objectThe changes to formulas, rows and business terms in a range of commits, newest first.
- since: str | None
a commit, tag or date, or None for the whole history.
- Type:
Where the range starts
- until: str
Where it ends.
- commits: int = 0
Commits read in the range.
- notes: list[str]
What could not be read, and why.
- warehouse: str | None = None
Where
konstruct verify --effectran the changes, when it did.
- class konstruct.analysis.release_notes.Change(commit, path, kind, status, relation=None, column=None, term=None, node_id=None, previous_path=None, before=None, after=None, before_english=None, after_english=None, added=<factory>, removed=<factory>, added_english=<factory>, removed_english=<factory>, fields=<factory>, affected=<factory>, approvals=<factory>, effect=None)[source]
Bases:
objectOne entry of the notes: a column whose formula changed, a relation whose rows changed, or a business term whose definition changed, in one commit.
- path: str
The file it changed, relative to the project.
- kind: str
formula(a column added, removed or changed),rows(joins, filters or grouping) orterm(a business term’s status, approval, owner or definition).
- status: str
For a formula,
added,removedorchanged; for rows and terms,changed.
- relation: str | None = None
The relation, by label; None for a term.
- column: str | None = None
The column, by label, for a formula.
- term: str | None = None
The term’s name, for a term.
- node_id: str | None = None
The relation’s or column’s graph id, when the graph has it.
- previous_path: str | None = None
Where the file was before the commit, relative to the project, when the commit moved it.
- before: str | None = None
The formula before, as SQL with short names.
- after: str | None = None
The formula after.
- before_english: str | None = None
beforein plain English.
- after_english: str | None = None
afterin plain English.
- added: list[str]
For rows, the conditions only after the commit, as SQL.
- removed: list[str]
For rows, the conditions only before it.
- added_english: list[str]
addedin plain English.
- removed_english: list[str]
removedin plain English.
- fields: dict[str, tuple[Any, Any]]
For a term, each changed field with its value before and after.
- approvals: list[Approval]
The business terms bound to the column or downstream of it, and whether each was approved after this change.
- property subject: str
- class konstruct.analysis.release_notes.Affected(columns=0, relations=0, exposures=<factory>, terms=<factory>)[source]
Bases:
objectWhat is downstream of a change.
- columns: int = 0
Columns downstream.
- relations: int = 0
Tables and models downstream, including those that join or filter on it.
- exposures: list[str]
Dashboards and other dbt exposures downstream, with their owners.
- terms: list[str]
Business terms bound to the column or downstream of it.
- class konstruct.analysis.release_notes.Approval(term, status, owner, approved=None, bound=True)[source]
Bases:
objectA business term bound to a changed column, or downstream of it, and whether it was approved after the change.
- term: str
The term’s name.
- status: str | None
Its status now.
- owner: str | None
Who owns it now.
- approved: Commit | None = None
The first commit at or after the change that approved it; None when none has.
- bound: bool = True
Whether the term is bound to the changed column itself (for a change to a relation’s rows, to one of its columns), rather than downstream of it.
- class konstruct.analysis.effect.Effect(rows_before=None, rows_after=None, columns=<factory>, every_column=False, key=<factory>, gained=None, only_before=None, only_after=None, lost=None, sampled=False, skipped=None)[source]
Bases:
objectWhat one change does on today’s data: the changed model’s SQL before and after the commit, run on the same rows.
- rows_before: int | None = None
Rows the model returns as it was before the commit.
- rows_after: int | None = None
Rows it returns as it was after.
- columns: list[ColumnEffect]
For a formula change, the changed column; for a change to the relation’s joins, filters or grouping, every column in both versions other than the key.
- every_column: bool = False
Whether every column was compared, for a change to the relation’s joins, filters or grouping; then only the columns that moved are named in the summary.
- key: list[str]
The key rows are matched by, when the relation has one.
- gained: int | None = None
Keys only after the commit.
- only_before: int | None = None
For a change to the rows of a relation with no key, the rows (over the columns both versions return) only the version before the commit gives, counted with repeats.
- only_after: int | None = None
The rows only the version after the commit gives.
- lost: int | None = None
Keys only before it.
- sampled: bool = False
Whether a copy reached the row limit, so the result may be for a sample.
- skipped: str | None = None
Why the effect could not be worked out, when it could not.
- class konstruct.analysis.effect.ColumnEffect(column, total_before=None, total_after=None, matched=None, changed=None, only_before=None, only_after=None)[source]
Bases:
objectWhat one change does to one column of the model on today’s data.
- column: str
The column.
- total_before: float | None = None
The sum of its values before the commit, when they are numbers.
- total_after: float | None = None
The sum after.
- matched: int | None = None
Rows of the same key in both versions, when the relation has a key.
- changed: int | None = None
Of those, the rows whose value of the column differs.
- only_before: int | None = None
For a formula change with no key to match rows by, the column’s values only the version before the commit gives, counted with repeats.
- only_after: int | None = None
The values only the version after the commit gives.
- property moved: bool
Whether the column’s sum or any of its values changed.
Timeline
- class konstruct.analysis.timeline.Timeline(since, until, paths=<factory>, entries=<factory>, commits=0, start=None, unknown=<factory>, notes=<factory>, warehouse=None)[source]
Bases:
objectHow the business’s meaning changed over a range of commits, oldest first.
- since: str | None
a commit, tag or date, or None for the whole history.
- Type:
Where the range starts
- until: str
Where it ends.
- paths: list[str]
Only commits that changed these files or directories, relative to the project.
- commits: int = 0
Commits read in the range.
- start: Commit | None = None
The commit whose world the first commit read is compared with, when it has a parent.
- notes: list[str]
What could not be read, and why.
- warehouse: str | None = None
Where
effects()ran the changes, when it did.
- class konstruct.analysis.timeline.Entry(commit, changes=<factory>, compared_with=None)[source]
Bases:
objectWhat one commit changed in the business’s meaning.
- changes: list[WorldChange]
What changed, by kind and then by name.
- class konstruct.semantics.world_facts.WorldChange(kind, name, status, before=None, after=None, fields=<factory>, renamed_from=None, english=<factory>, approval=None, effect=None, effect_column=None, worded=<factory>)[source]
Bases:
objectOne thing whose meaning changed between two worlds.
- kind: str
entity,relationship,propertyormetric.
- name: str
What names it in the later world (in the earlier one, for one removed).
- status: str
added,removed,renamed(and perhaps changed besides) orchanged.
- before: dict[str, Any] | None = None
Its facts in the earlier world; None when it was added.
- after: dict[str, Any] | None = None
Its facts in the later world; None when it was removed.
- fields: dict[str, tuple[Any, Any]]
For one renamed or changed, each fact that differs, with its value before and after.
- renamed_from: str | None = None
For one renamed, its name in the earlier world.
- english: list[str]
The change in plain English, one sentence for each part of it.
- approval: Commit | None = None
For a metric, the first commit that approved it at or after this change, by ancestry.
- effect: Any = None
For a metric whose formula or rows changed, what the change does on today’s data (
konstruct.analysis.effect.Effect), oncekonstruct verify --effectran it.
- effect_column: str | None = None
The column or relation whose change
effectis of.
- worded: dict[str, list[str]]
englishby the fact each sentence is about, and undernotethe release note’s words for a change of required filters.
- property facts: dict[str, Any]
Its facts in the later world, or in the earlier one when it was removed.
- class konstruct.semantics.world_facts.World(entities=<factory>, relationships=<factory>, properties=<factory>, metrics=<factory>, dialect=None, notes=<factory>, digests=<factory>, texts=<factory>)[source]
Bases:
objectThe facts of one world that two builds are compared by.
- entities: dict[str, dict[str, Any]]
- relationships: dict[str, dict[str, Any]]
- properties: dict[str, dict[str, Any]]
- metrics: dict[str, dict[str, Any]]
- dialect: str | None = None
The SQL dialect its formulas and conditions are written in.
- notes: list[str]
What the build of this world could not read, and why.
- digests: dict[str, str]
Each property column’s formula digest (see
Formulas), by the column’s label.
- texts: dict[str, str]
not compared, only worded.
- Type:
The composed formula of a digest, for those composed
- konstruct.semantics.world_facts.world_diff(a, b)[source]
What changed from world
ato worldb, by kind and then by name. Something that is gone and something new with the same signature (see_signature()), where exactly one of each has it, is one thing renamed. The diff of a world with itself is empty, andworld_diff(b, a)isworld_diff(a, b)with each change mirrored.
Search by Meaning
- konstruct.analysis.semantic.search(graph, text, limit=20, kinds=None, out_dir=None)[source]
Nodes that match
textby meaning or by name, best first: the word search and the vector search fused, an exact name first. Each result’smatchedsays what a match by meaning rests on most: the node’s ownwords, itslineage, itsformulaor itsstructure.
- konstruct.analysis.semantic.similar(graph, ref, limit=10, kinds=None, out_dir=None)[source]
The nodes most like
ref: named alike, near it in the lineage, computed by the same formula, or built from the same sources by the same path.
Formulas, Conflicts and Export
- class konstruct.analysis.explain.Explainer(graph, max_composed=4000, *, qualified=False, boundary=())[source]
Bases:
objectExplains columns of one graph, composing each column once however many ask for it.
- boundary
where another build’s view stopped.
- Type:
Columns composed as sources whatever their formula
- memo: dict[str, _Composed]
- active: set[str]
- texts: dict[str, _Text]
- writing: set[str]
- deep_memo: dict[tuple[str, int], _Composed]
- deep_texts: dict[tuple[str, int], _Text]
- upstream: dict[str, frozenset[str]]
- composed_text(node)[source]
The composed formula of
nodewith short names, rendered once per column.
- compose_text(node, depth=0)[source]
What
compose()gives, tidied and printed, built as text: each column’s own formula is printed once with its inputs left as placeholders, and their composed texts are put in their places, with the brackets the composed tree would keep. This gives the same text without copying and printing ever larger trees.
- compose(node, depth=0)[source]
node’s expression with every input column replaced by that column’s own composed expression; a column without an expression is a source (or, if it has inputs, a gap).
- compose_sql(tree, reads=None, owner=None)[source]
A free expression, such as one of a relation’s conditions, with every column in it replaced by that column’s composed formula, as
compose()replaces a column’s inputs, with brackets tidied.treeis rewritten in place;readsis called with each column of the graph it refers to;owneris the relation whose expression it is.
- inputs(node, upstream=None)[source]
How the columns in
node’s expression resolve: for each, the relation and column name it names, and the column node it reads when the graph has one.
- steps(column)[source]
Every column from
columnupstream, nearest first, with its formula and conditions.
- explain(column)[source]
The formula behind
columnand how it composes down to sources (seeexplain()).
- fingerprints()[source]
A hash per column of everything its summary depends on: its expression, its label and the fingerprints of the columns it is derived from (a Merkle hash over the lineage), so a cached summary is reused exactly when nothing upstream of the column changed.
- summaries(limit=50000, cache=None)[source]
Formulas for every column that has one, and conditions for every relation, compactly:
{"columns": {id: {"e": expression, "c": composed, "s": sources, "p": partial}}, "conditions": {relation id: [condition, ...]}}. Used by graph.html.With
cache(the build cache), each column’s summary is keyed by its fingerprint and each relation’s conditions by their text and the names in the graph, so an update only recomputes what changed.
- konstruct.analysis.explain.explain(graph, column)[source]
The formula behind
columnand how it composes down to sources.
- konstruct.analysis.conflicts.find_conflicts(graph, limit=None, *, touching=None)[source]
Pairs of columns that appear to be one business idea computed differently, ranked.
With
touching, a set of column ids, only the pairs with a column in it are compared: the conflicts are those of every pair that involve one of those columns, in the same order, at a cost that grows with how many columns it names rather than with the number of pairs.
- konstruct.analysis.conflicts.core(name)[source]
The idea a column name stands for: its words, as search by meaning reads them (
konstruct._core.terms()), without qualifiers, each folded to its concept.total_revenue,revenueandsales_amountall give {revenue}.
- konstruct.outputs.export.export(graph, fmt, *, select=None, columns=False, namespace=None, job_namespace='konstruct', event_time=None, plugins=None)[source]
The graph (or the lineage of
select) infmt; see the module docstring.pluginsis the registry to take the format from (default: Konstruct’s own formats).
Business Definitions
- konstruct.semantics.definitions.parse(text, path)[source]
Parse a definitions file. Problems are collected as
path:line: message, so one bad term does not hide the rest.
- konstruct.semantics.definitions.parse_file(path, relative=None)[source]
Parse a definitions file from disk;
relativeis the path reported in problems.
- konstruct.semantics.definitions.validate(terms)[source]
Problems that need every term at once: duplicate names, unknown related terms, and approved terms without an owner or an approval date. Column checks need the graph and are done by the build.
- class konstruct.semantics.definitions.Term(name: 'str', definition: 'str', path: 'str', line: 'int', synonyms: 'list[str]' = <factory>, owner: 'Owner | None' = None, status: 'str' = 'draft', approved: 'str | None' = None, grain: 'str | None' = None, columns: 'list[str]' = <factory>, filters: 'list[str]' = <factory>, unit: 'str | None' = None, currency: 'str | None' = None, related: 'list[str]' = <factory>, broader: 'list[str]' = <factory>, exceptions: 'str | None' = None, kind: 'str | None' = None, iri: 'str | None' = None, sensitivity: 'str | None' = None)[source]
Bases:
object- name: str
- definition: str
- path: str
- line: int
- synonyms: list[str]
- status: str = 'draft'
- approved: str | None = None
- grain: str | None = None
- columns: list[str]
- filters: list[str]
- unit: str | None = None
- currency: str | None = None
- broader: list[str]
a refund rate is a rate.
- Type:
The wider concepts this one is a kind of (SKOS
broader)
- exceptions: str | None = None
- kind: str | None = None
entity,measureorterm; unset, the ontology infers it from the bound columns.
- iri: str | None = None
For a term from a SKOS or OWL vocabulary, the concept’s IRI.
- sensitivity: str | None = None
each bound column is marked sensitive, and so is every column computed from it, and a relation the term is bound to is marked to hold such data (see
konstruct.analysis.sensitivity).- Type:
What kind of sensitive data the term’s columns hold, such as
personal
- property where: str
path:line, or for a vocabulary concept its file and IRI.- Type:
Where the term is defined
- property slug: str
- property names: list[str]
The name and every synonym.
Configuration, Usage and Builds
- konstruct.engine.config.load_config(root, out_dir=None, *, trusted=None)[source]
Read
konstruct.toml(or[tool.konstruct]inpyproject.toml) fromrootover the defaults.- Parameters:
root (Path) – The project.
out_dir (Path | None) – The output directory, over
out_dirin the configuration.trusted (bool | None) – Whether the project’s own connections and paths are trusted. By default, as
konstruct trustrecorded them (seekonstruct.engine.trust).
- Raises:
ConfigError – The configuration is invalid, or a path setting (see
PATH_SETTINGS) leads outside the project without being trusted.
- class konstruct.engine.trust.Trust(root, state, sha256, files=<factory>, variables=<factory>, dbt_projects=<factory>, record=<factory>, action='')[source]
Bases:
objectWhether a project’s own connection settings are trusted, and what that covers.
- root: Path
The project.
- state: str
trusted(recorded and unchanged),environment(KONSTRUCT_TRUST_CONFIG),changed(recorded, but the content has changed since) oruntrusted.
- sha256: str
The digest of the content that carries connections, as
konstruct trustrecords it.
- files: list[str]
The files that content comes from, relative to the project.
- variables: list[str]
The environment variables that content reads once it is trusted.
- dbt_projects: list[str]
The dbt projects Konstruct runs dbt in only once the project is trusted. The record does not cover their models and macros, which dbt runs with the user’s environment.
- record: Path
The user’s file that records trust.
- action: str = ''
recorded,unchanged(the same record was already there),revoked, or empty for a check.- Type:
What the call did
- property trusted: bool
Whether the build uses the project’s own connections and paths.
- property environment_variable: str
The environment variable that trusts every project.
- konstruct.engine.trust.check(root)[source]
Whether the project at
roothas its own connection settings trusted.- Raises:
konstruct.engine.config.ConfigError – The configuration cannot be read.
- konstruct.engine.trust.trust(root)[source]
Trust the project’s own connection settings as they are now, until they change.
- Raises:
konstruct.engine.config.ConfigError – The configuration cannot be read.
- konstruct.engine.trust.revoke(root)[source]
Remove the record that trusts the project’s own connection settings.
- Raises:
konstruct.engine.config.ConfigError – The configuration cannot be read.
- konstruct.engine.trust.trust_file()[source]
Where trust is recorded:
$XDG_CONFIG_HOME/konstruct/trusted.json, otherwise the platform’s user configuration directory (~/.config,~/Library/Application Supportor%APPDATA%).
- class konstruct.inputs.usage.UsageSettings(enabled: 'bool' = False, days: 'int' = 90, exclude_users: 'list[str]' = <factory>, objects: 'str' = 'direct', max_queries: 'int' = 50000)[source]
Bases:
object- enabled: bool = False
- days: int = 90
- exclude_users: list[str]
Service accounts to leave out, such as the one dbt and the orchestrator run as.
- objects: str = 'direct'
For Snowflake, count
directobjects (as queried) orbaseobjects (underlying tables).
- max_queries: int = 50000
For BigQuery, the most distinct (query text, user) pairs parsed.
- konstruct.engine.build.build(config, *, update=False)[source]
Build the graph for
configand writegraph.json,REPORT.md,graph.html,ontology.ymlandexplain.json;konstruct.Project.build()is the usual way in.
Plugins
The plugin API (see Writing a Plugin): what a plugin provides for each
entry-point group, and what Konstruct gives it. Everything here is imported from
konstruct.plugins.
- konstruct.plugins.api.API_VERSION: tuple[int, int] = (1, 2)
The version of the plugin API this release of Konstruct runs, as
(major, minor). The minor grows when the API gains something a plugin may use; the major only when a type or protocol here changes incompatibly. Konstruct runs a plugin that targets the same major and a minor no higher than its own; a plugin’sapi_version = 1means(1, 0).
- konstruct.plugins.api.RESERVED_NODE_ATTRS
The attribute names a plugin’s node may not set, which are every name Konstruct reads from attributes bar
PLUGIN_NODE_ATTRS;sorted(RESERVED_NODE_ATTRS)lists them.
- konstruct.plugins.api.PLUGIN_NODE_ATTRS = frozenset({'bash_command', 'label', 'maturity', 'operator', 'orchestrator', 'owner', 'schedule', 'severity', 'test', 'type', 'url'})
The attribute names Konstruct reads that a plugin’s node may set, each a string with the meaning Konstruct gives it, which are
testandseverityon a test node, naming the test (such asuniqueornot_null) and how much a failure counts,owner, who owns a DAG or task, and those the graph viewer shows:schedule,operator,orchestratorandbash_commandfor a DAG or task, andtype,maturity,labelandurlfor an exposure.
- konstruct.plugins.api.PLUGIN_LIST_ATTRS = frozenset({'fields'})
The attribute names Konstruct reads that a plugin’s node or edge may set as a list of strings, with the meaning Konstruct gives it:
fields, the fields a BI view or explore shows, which the Looker input sets and search by meaning reads.
- konstruct.plugins.api.PLUGIN_MAP_ATTRS = frozenset({'measure_filters', 'measures'})
The attribute names Konstruct reads that a plugin’s node may set as a map from strings to strings, with the meaning Konstruct gives them:
measures, each measure a BI view or model defines with its formula, andmeasure_filters, a measure’s filters as the tool writes them, which every answer from it applies. The Looker input sets both and the ontology’s metrics read them. Added in plugin API 1.1.
- konstruct.plugins.api.RESERVED_EDGE_ATTRS
The attribute names a plugin’s edge may not set, which are those Konstruct sets on every edge (
origin,source,target,kind) and every name Konstruct reads from attributes, such asverified,transform,expression,viaandrole, which carry its own lineage claims;sorted(RESERVED_EDGE_ATTRS)lists them.
- konstruct.plugins.api.MAX_DEPTH = 64
How deeply a node’s or edge’s attribute values, or a cached value, may nest lists and dicts.
- class konstruct.plugins.InputPlugin(*args, **kwargs)[source]
Bases:
Protocolkonstruct.inputs: a reader for a kind of file (an orchestrator, BI tool or loader).- api_version: tuple[int, int] | int
The plugin API version the input targets.
- class konstruct.plugins.InputFile(path, content)[source]
Bases:
objectOne file discovery found for an input.
- path: str
The file, relative to the project root, as node
pathvalues use it.
- content: bytes
Its bytes.
- property text: str
The content as UTF-8 text, with undecodable bytes replaced.
- class konstruct.plugins.InputContext(root, dialect, options, cached)[source]
Bases:
objectWhat Konstruct gives an input besides its files.
- dialect: str | None
The project’s SQL dialect, when one is known.
- options: Mapping[str, Any]
The input’s own table in
konstruct.toml([<name>]), or an empty mapping.
- cached: Callable[[tuple[Any, ...], Callable[[], Any]], Any]
Called as
cached(key, compute), gives the JSON-ready valuecompute()returns, kept forkonstruct build --updatewhilekey(a tuple of JSON-ready parts, such as a hash of a file’s content) and the plugin’s version are unchanged. A value that is not JSON-ready, or nests deeper thanMAX_DEPTH, raises an error and is not kept.
- class konstruct.plugins.Fragment(nodes=<factory>, edges=<factory>, sql=<factory>, warnings=<factory>, sources=<factory>, counts=<factory>)[source]
Bases:
objectWhat an input read: nodes, edges and SQL to merge into the graph. Konstruct validates the whole fragment first and merges nothing from one that is not valid.
- nodes: list[PluginNode]
Nodes to add.
- edges: list[PluginEdge]
Edges to add, in order.
- warnings: list[str]
Problems with the input, given as build warnings with the input’s name in front.
- counts: dict[str, int]
Counts for
konstruct.engine.build.BuildResult.inputs, each a 64-bit integer named<input>_<key>.
- class konstruct.plugins.PluginNode(id, kind, name, path=None, description=None, attrs=<factory>)[source]
Bases:
objectA node an input adds. Its id in the graph is the input’s name, a dot and
id.- id: str
An id unique within the fragment, such as
pipeline.daily_revenue; no#or line break.
- kind: str
One of
PLUGIN_NODE_KINDS.
- name: str
Its own name, as people know it.
- path: str | None = None
The file that defines it, relative to the project.
- description: str | None = None
What it is.
- attrs: dict[str, Any]
Anything else, as JSON-ready data, under names not in
RESERVED_NODE_ATTRS.
- class konstruct.plugins.PluginEdge(source, target, kind, attrs=<factory>, missing=None)[source]
Bases:
objectAn edge an input adds, in the graph’s directions (
source -> target).- attrs: dict[str, Any]
Details, as JSON-ready data, under names not in
RESERVED_EDGE_ATTRS;originis set by Konstruct.
- class konstruct.plugins.Relation(key)[source]
Bases:
objectA warehouse relation named by its key (
schema.tableorcatalog.schema.table), as SQL names it. It resolves to the node the build already has for that relation, or to a new inferred table.- key: str
The relation’s name, such as
analytics.orders.
- class konstruct.plugins.Column(relation, name)[source]
Bases:
objectA column of a warehouse relation. When the build knows the relation’s columns from something that defines it (the warehouse, dbt, or SQL that writes it) and this column is not one of them, the edge is left out with a warning; otherwise the column is created.
- name: str
The column’s name.
- class konstruct.plugins.Sql(sql, path, task=None, dialect=None)[source]
Bases:
objectSQL that Konstruct analyses for column lineage, as it does SQL files.
- sql: str
One or more statements.
- path: str
The file it comes from, relative to the project, for warnings and
via.
- task: str | None = None
the task then feeds the tables the SQL writes, and the tables it reads feed the task.
- Type:
The
idof the fragment’s task node that runs it
- dialect: str | None = None
Its SQL dialect, when it is not the project’s.
- class konstruct.plugins.Source(path, detail)[source]
Bases:
objectOne line of the build summary and of
graph.json’smeta.sources.- path: str
The file read.
- detail: str
What was found in it.
- class konstruct.plugins.ExportPlugin(*args, **kwargs)[source]
Bases:
Protocolkonstruct.exports: a format forkonstruct export, named by the entry point.- api_version: tuple[int, int] | int
The plugin API version the format targets.
- description: str
what the format is for.
- Type:
One line
- class konstruct.plugins.ExportRequest(select=None, keep=None, columns=False, namespace=None, job_namespace='konstruct', event_time=None)[source]
Bases:
objectThe options of one
konstruct export.- keep: frozenset[str] | None = None
With
select, the ids of the relations and tasks in its lineage, both directions.
- columns: bool = False
draw columns as well as relations.
- Type:
--columns
- namespace: str | None = None
a dataset namespace.
- Type:
--namespace
- job_namespace: str = 'konstruct'
The namespace of jobs, for formats that have one.
- event_time: str | None = None
A fixed event time, for formats that have one.
- class konstruct.plugins.CheckPlugin(*args, **kwargs)[source]
Bases:
Protocolkonstruct.checks: a rule ofkonstruct gate. EachFindingit returns fails the gate; a third-party plugin’s rules are named<entry point>.<rule>.- api_version: tuple[int, int] | int
The plugin API version the check targets.
- description: str
what the check stops.
- Type:
One line
- class konstruct.plugins.CheckContext(graph, diff, conflicts, laws=None)[source]
Bases:
objectWhat a gate check sees: the graph at the head and what the change does.
- class konstruct.plugins.ToolsPlugin(*args, **kwargs)[source]
Bases:
Protocolkonstruct.mcp_tools: tools forkonstruct mcp, each named<entry point>_<tool>.- api_version: tuple[int, int] | int
The plugin API version the tools target.
- class konstruct.plugins.Plugins(plugins, missing=())[source]
Bases:
objectThe registry for one project: every component, which of them run, and the problems found.
- plugins
Every component, Konstruct’s own first, then installed plugins by distribution.
- missing
Distributions
[plugins] enablednames that are not installed.
- off: str | None
Why plugins are turned off, when they are.
- property problems: list[str]
What stops a plugin the project enables from running, for build warnings.
- classmethod discover(enabled=(), *, off=False, environment=True)[source]
Konstruct’s own components and every installed plugin, running those whose distributions
enablednames unlessoff(--no-plugins) or, withenvironment,KONSTRUCT_NO_PLUGINSturns plugins off.
- classmethod for_config(config)[source]
The registry as
configsets it: its[plugins] enabledlist, unless plugins are turned off.
- group(group)[source]
The components of
groupthat run, loaded; one that cannot be loaded is left out, with a problem recorded.
- failed(group)[source]
The components of
groupthe project enables that could not be loaded or were refused.
- tools(project)[source]
The MCP tools of the plugins that run, each named
<plugin>_<tool>, bound toprojectand with the plugin it comes from. A plugin whosetoolsraises or returns no mapping, a tool named badly, and a tool whose joined name another plugin’s tool already has, are left out with a warning logged.
- load_all()[source]
Load every component that runs, so their API versions and load errors are known.
- class konstruct.plugins.Plugin(name, group, target, distribution, version, builtin, enabled, reason, api_version=None, error=None, loaded=None, requested=False)[source]
Bases:
objectOne component in the registry: Konstruct’s own, or one an installed plugin provides.
- name: str
an input’s name, an export format, a check’s rule prefix, a tool prefix or a pack directory’s name.
- Type:
The entry point’s name
- group: str
One of
GROUPS.
- target: str
module:attribute, or a package path for packs.- Type:
What the entry point names
- distribution: str
The distribution that provides it (
konstructfor Konstruct’s own).
- version: str
The distribution’s version.
- builtin: bool
Whether it is part of Konstruct.
- enabled: bool
Whether it runs for this project.
- reason: str
Why it does or does not run.
- api_version: tuple[int, int] | None = None
The plugin API version it targets, once loaded, as
(major, minor).
- error: str | None = None
Why it could not be loaded or was refused, when it could not.
- loaded: Any = None
The loaded object, or the directory for a pack.
- requested: bool = False
Whether the project enabled it before it was refused.
- property label: str
input 'pipelines' (konstruct-yaml-pipelines 0.1.0).- Type:
How messages name it
Errors
- exception konstruct.QueryError(message, *, suggestions=None, hint=None)[source]
Bases:
ExceptionA reference that matches nothing or several things, or a missing graph.
suggestionsholds the names the reference may have meant andhinta command that could help, for the CLI to show; the message alone says the same in one line.
- exception konstruct.FormatError[source]
Bases:
ExceptionA document whose format this Konstruct cannot read: written by a newer Konstruct, or not a Konstruct document at all.
- exception konstruct.engine.config.UntrustedConfig[source]
Bases:
ConfigErrorA connection or path from the repository’s own configuration that needs
konstruct trust.