loom-dbt 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- loom_dbt-0.1.0/LICENSE +21 -0
- loom_dbt-0.1.0/PKG-INFO +192 -0
- loom_dbt-0.1.0/README.md +162 -0
- loom_dbt-0.1.0/pyproject.toml +84 -0
- loom_dbt-0.1.0/pyproject.toml.orig +84 -0
- loom_dbt-0.1.0/src/loom/.agents/skills/loom-adoption/SKILL.md +399 -0
- loom_dbt-0.1.0/src/loom/.agents/skills/loom-adoption/references/adoption-playbook.md +158 -0
- loom_dbt-0.1.0/src/loom/.agents/skills/loom-adoption/references/first-project/local/registrations/customer_order_counts.yml +12 -0
- loom_dbt-0.1.0/src/loom/.agents/skills/loom-adoption/references/first-project/metadata/instances/hubs.yml +7 -0
- loom_dbt-0.1.0/src/loom/.agents/skills/loom-adoption/references/first-project/metadata/instances/staging.yml +29 -0
- loom_dbt-0.1.0/src/loom/.agents/skills/loom-adoption/references/first-project/metadata/patterns/current_hub.yml +8 -0
- loom_dbt-0.1.0/src/loom/.agents/skills/loom-adoption/references/first-project/metadata/patterns/staging.yml +8 -0
- loom_dbt-0.1.0/src/loom/.agents/skills/loom-adoption/references/first-project/models/sources.yml +25 -0
- loom_dbt-0.1.0/src/loom/.agents/skills/loom-adoption/references/metadata-reference.md +347 -0
- loom_dbt-0.1.0/src/loom/.agents/skills/loom-ask/SKILL.md +241 -0
- loom_dbt-0.1.0/src/loom/.agents/skills/loom-authoring/SKILL.md +125 -0
- loom_dbt-0.1.0/src/loom/.agents/skills/loom-authoring/references/metadata-reference.md +347 -0
- loom_dbt-0.1.0/src/loom/.agents/skills/loom-bootstrap/SKILL.md +136 -0
- loom_dbt-0.1.0/src/loom/.agents/skills/loom-bootstrap/references/first-project/local/registrations/customer_order_counts.yml +12 -0
- loom_dbt-0.1.0/src/loom/.agents/skills/loom-bootstrap/references/first-project/metadata/instances/hubs.yml +7 -0
- loom_dbt-0.1.0/src/loom/.agents/skills/loom-bootstrap/references/first-project/metadata/instances/staging.yml +29 -0
- loom_dbt-0.1.0/src/loom/.agents/skills/loom-bootstrap/references/first-project/metadata/patterns/current_hub.yml +8 -0
- loom_dbt-0.1.0/src/loom/.agents/skills/loom-bootstrap/references/first-project/metadata/patterns/staging.yml +8 -0
- loom_dbt-0.1.0/src/loom/.agents/skills/loom-bootstrap/references/first-project/models/sources.yml +25 -0
- loom_dbt-0.1.0/src/loom/.agents/skills/loom-core/SKILL.md +260 -0
- loom_dbt-0.1.0/src/loom/.agents/skills/loom-core/references/commands.md +378 -0
- loom_dbt-0.1.0/src/loom/.agents/skills/loom-core/references/terminology.md +88 -0
- loom_dbt-0.1.0/src/loom/.agents/skills/loom-maintenance/SKILL.md +100 -0
- loom_dbt-0.1.0/src/loom/__init__.py +27 -0
- loom_dbt-0.1.0/src/loom/__main__.py +5 -0
- loom_dbt-0.1.0/src/loom/check.py +411 -0
- loom_dbt-0.1.0/src/loom/cli.py +3865 -0
- loom_dbt-0.1.0/src/loom/contracts.py +723 -0
- loom_dbt-0.1.0/src/loom/declared_generic_tests.py +319 -0
- loom_dbt-0.1.0/src/loom/declared_unit_tests.py +387 -0
- loom_dbt-0.1.0/src/loom/dialects.py +545 -0
- loom_dbt-0.1.0/src/loom/equivalence.py +490 -0
- loom_dbt-0.1.0/src/loom/errors.py +2 -0
- loom_dbt-0.1.0/src/loom/generator_registry.py +129 -0
- loom_dbt-0.1.0/src/loom/inventory.py +1021 -0
- loom_dbt-0.1.0/src/loom/inventory_corpus.py +1182 -0
- loom_dbt-0.1.0/src/loom/inventory_patterns.py +371 -0
- loom_dbt-0.1.0/src/loom/inventory_records.py +2676 -0
- loom_dbt-0.1.0/src/loom/inventory_relationships.py +1559 -0
- loom_dbt-0.1.0/src/loom/manifest.py +597 -0
- loom_dbt-0.1.0/src/loom/migration_assessment.py +5794 -0
- loom_dbt-0.1.0/src/loom/migration_plan.py +327 -0
- loom_dbt-0.1.0/src/loom/output_columns.py +624 -0
- loom_dbt-0.1.0/src/loom/output_layout.py +172 -0
- loom_dbt-0.1.0/src/loom/project_files.py +260 -0
- loom_dbt-0.1.0/src/loom/properties.py +308 -0
- loom_dbt-0.1.0/src/loom/renderers/__init__.py +53 -0
- loom_dbt-0.1.0/src/loom/renderers/base.py +491 -0
- loom_dbt-0.1.0/src/loom/renderers/bridge.py +115 -0
- loom_dbt-0.1.0/src/loom/renderers/bronze.py +104 -0
- loom_dbt-0.1.0/src/loom/renderers/dimension.py +163 -0
- loom_dbt-0.1.0/src/loom/renderers/fact.py +211 -0
- loom_dbt-0.1.0/src/loom/renderers/gold.py +284 -0
- loom_dbt-0.1.0/src/loom/renderers/hub.py +304 -0
- loom_dbt-0.1.0/src/loom/renderers/link.py +351 -0
- loom_dbt-0.1.0/src/loom/renderers/mart.py +92 -0
- loom_dbt-0.1.0/src/loom/renderers/normalized_table.py +118 -0
- loom_dbt-0.1.0/src/loom/renderers/obt.py +116 -0
- loom_dbt-0.1.0/src/loom/renderers/pit.py +107 -0
- loom_dbt-0.1.0/src/loom/renderers/reference.py +220 -0
- loom_dbt-0.1.0/src/loom/renderers/satellite.py +692 -0
- loom_dbt-0.1.0/src/loom/renderers/scd2_dimension.py +341 -0
- loom_dbt-0.1.0/src/loom/renderers/silver.py +204 -0
- loom_dbt-0.1.0/src/loom/renderers/staging.py +217 -0
- loom_dbt-0.1.0/src/loom/renderers/view.py +87 -0
- loom_dbt-0.1.0/src/loom/reporting.py +2862 -0
- loom_dbt-0.1.0/src/loom/selection.py +1544 -0
- loom_dbt-0.1.0/src/loom/similarity.py +452 -0
- loom_dbt-0.1.0/src/loom/source_structure.py +481 -0
loom_dbt-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lincoln Phillips
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
loom_dbt-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: loom-dbt
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Authoring-time dbt generation and model inventory coverage
|
|
5
|
+
Keywords: dbt,data-vault,kimball,medallion,code-generation,data-modeling
|
|
6
|
+
Author: Lincoln Phillips
|
|
7
|
+
Author-email: Lincoln Phillips <lincolnp.projects@gmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
18
|
+
Classifier: Topic :: Database
|
|
19
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
20
|
+
Requires-Dist: pyyaml>=6.0,<7
|
|
21
|
+
Requires-Dist: wcwidth>=0.2,<1
|
|
22
|
+
Maintainer: Lincoln Phillips
|
|
23
|
+
Maintainer-email: Lincoln Phillips <lincolnp.projects@gmail.com>
|
|
24
|
+
Requires-Python: >=3.11, <3.15
|
|
25
|
+
Project-URL: Repository, https://github.com/lincolnp-oss/loom-dbt
|
|
26
|
+
Project-URL: Documentation, https://github.com/lincolnp-oss/loom-dbt/blob/main/docs/commands.md
|
|
27
|
+
Project-URL: Issues, https://github.com/lincolnp-oss/loom-dbt/issues
|
|
28
|
+
Project-URL: Changelog, https://github.com/lincolnp-oss/loom-dbt/blob/main/CHANGELOG.md
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# Loom
|
|
32
|
+
|
|
33
|
+
[](https://github.com/lincolnp-oss/loom-dbt/actions/workflows/ci.yml) [](https://pypi.org/project/loom-dbt/) [](https://pypi.org/project/loom-dbt/) [](LICENSE)
|
|
34
|
+
|
|
35
|
+
Loom is built and maintained by one person; if it saves your team time, consider [sponsoring its development](https://github.com/sponsors/lincolnp-oss).
|
|
36
|
+
|
|
37
|
+
Loom is an authoring-time CLI for dbt projects: declare each repeated transformation once, and Loom generates the plain dbt models, tests and properties for it, proves they still match what you declared, and lets you search the whole project before you build something that already exists.
|
|
38
|
+
|
|
39
|
+
## The 30-second example
|
|
40
|
+
|
|
41
|
+
One small declaration is the whole idea. A **pattern** is a named, reusable shape; an **instance** is one concrete model declared against it. This declaration:
|
|
42
|
+
|
|
43
|
+
```yaml
|
|
44
|
+
pattern: current_hub
|
|
45
|
+
instances:
|
|
46
|
+
- name: hub_customer
|
|
47
|
+
params:
|
|
48
|
+
description: Stable customer identity table.
|
|
49
|
+
input_models: [stg_customers]
|
|
50
|
+
business_key: customer_id
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
generates this model:
|
|
54
|
+
|
|
55
|
+
```sql
|
|
56
|
+
-- Generated by Loom
|
|
57
|
+
-- Generator: hub
|
|
58
|
+
-- Hash function: sha256
|
|
59
|
+
{{ config(materialized='table') }}
|
|
60
|
+
SELECT DISTINCT
|
|
61
|
+
LOWER(sha256(UPPER(TRIM(CAST(customer_id AS VARCHAR))))) AS customer_hk,
|
|
62
|
+
UPPER(TRIM(CAST(customer_id AS VARCHAR))) AS customer_id
|
|
63
|
+
FROM {{ ref('stg_customers') }}
|
|
64
|
+
WHERE customer_id IS NOT NULL
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
That is the loop in miniature: the shape is declared once, the model is generated as plain dbt with no Loom macros to call at build time, and the same declaration is what validation, search and coverage read. The [first project](examples/first-project/README.md) walks the loop end to end.
|
|
68
|
+
|
|
69
|
+
## Why Loom?
|
|
70
|
+
|
|
71
|
+
Most dbt projects repeat the same modelling shapes until consistency becomes the problem: copies drift, generators demand a permanent place in your build, and nobody can say with confidence what already exists. Loom's answer, in one line per problem:
|
|
72
|
+
|
|
73
|
+
| Problem | Loom's answer |
|
|
74
|
+
| --- | --- |
|
|
75
|
+
| The same shapes copied model after model, drifting apart | Declare the shape once as a pattern plus one small instance per model; regenerate all of them from the declaration, and let `generate --check` prove none of them drifted |
|
|
76
|
+
| Generators that become permanent runtime dependencies | Loom runs at authoring time only; the generated SQL/YAML is plain dbt with no Loom macros, so dbt runs your project without Loom installed |
|
|
77
|
+
| Tooling that covers one paradigm and bends the rest to fit | Generators for Data Vault, Kimball, medallion, 3NF and more under one contract, mixed freely in one project, never converted into each other |
|
|
78
|
+
| Migrations that demand everything convert at once | Convert first, then register the rest: drafts where a generator fits (an equivalence proof gates any swap), authoring drafts with one named hole for layered models that fit none, and registration last, because registering a model withdraws it from the drafting pool |
|
|
79
|
+
| "Does this already exist?" answered by grepping and guessing | One inventory of every model in the project, generated and hand-written, searchable in the terminal before you build |
|
|
80
|
+
| Model knowledge scattered across YAML, docs and heads | One declaration in your git is the authored source; properties, validation, search and coverage all derive from it |
|
|
81
|
+
|
|
82
|
+
And the guarantee the table rests on: **what Loom generates is plain dbt, so you can delete Loom and the project still builds.** Adoption is reversible by construction, not by promise.
|
|
83
|
+
|
|
84
|
+
## What Loom is
|
|
85
|
+
|
|
86
|
+
Loom is three small ideas and four verbs. A **generator** is the machinery that knows how to build one kind of model (a hub, a staging model, a fact). A **pattern** is a named, reusable configuration of a generator: the shape your project repeats. An **instance set** is a collection of **instances** - concrete models declared against a pattern, supplying only what differs: names, inputs, keys. Hand-written models are not second-class: a **registration** describes one in the same terms, and it joins the same inventory. The [terminology](docs/terminology.md) draws the exact boundary between Loom authoring and dbt resources.
|
|
87
|
+
|
|
88
|
+
**Declare, generate, prove, ask.** *Declare* the project once, as patterns, instances and registrations held in your own git. *Generate* the models, tests and properties from that declaration. *Prove* the generated files still match the declaration, and that the inventory covers the project. *Ask* the whole project questions (what exists, what is keyed on this, what would this change touch) before building anything. Generation is episodic; asking is the daily habit.
|
|
89
|
+
|
|
90
|
+
## What generation adds
|
|
91
|
+
|
|
92
|
+
A generated model is more than its SELECT statement. The generators also produce, from the same declaration and without being asked, the parts of a well-kept model that projects otherwise remember inconsistently or not at all:
|
|
93
|
+
|
|
94
|
+
- **Columns**: the hash and surrogate keys a paradigm runs on (hub and link hashes, dimension surrogate keys), the `__loaded_at` load timestamp on generated models, and record-source provenance where the generator carries it.
|
|
95
|
+
- **Tests**: each generator emits the dbt data tests its own guarantees rest on, default-on and not suppressible: `unique` and `not_null` on hub and link hashes, `relationships` from every link endpoint hash to its hub, `not_null` on fact dimension keys and measures, the dedup key's `unique` and `not_null` on silver, `not_null` on every gold and mart measure, and their counterparts for the other generators (the full table is in the [architecture document](docs/architecture.md#generated-dbt-tests)). Links additionally carry a guard inside their SQL that fails the build on null, blank or unmatched keys rather than letting a bad join through quietly.
|
|
96
|
+
- **Properties and declarations**: a properties document per generated model carrying its description and column documentation from the declaration, so the catalogue dbt shows is written from the same source as the SQL; the dbt source declaration for any source declared inline in metadata; and, for satellites, the generated current-state view alongside the history table.
|
|
97
|
+
|
|
98
|
+
The boundary is deliberate: Loom originates only the tests its generators' guarantees require. There are no inferred tests on staging models or raw sources, and no business-specific tests: those stay yours to declare, and tests you declare on an instance (generic tests, and dbt unit tests via the `unit_tests` parameter) are carried into the generated documents verbatim.
|
|
99
|
+
|
|
100
|
+
## What Loom isn't
|
|
101
|
+
|
|
102
|
+
- **Not a dbt replacement, and not in your build.** Loom runs at authoring time on your machine; the project it leaves behind is plain dbt that builds without it. There are no Loom macros to install into a dbt project and no runtime component at all.
|
|
103
|
+
- **Not an orchestrator or a warehouse tool.** It schedules nothing, connects to no warehouse to generate (generation needs no warehouse connection), and never runs your models: dbt and your platform do that.
|
|
104
|
+
- **Not an ingestion tool.** It moves no data. Sources, seeds and loading are yours; staging binds the sources you declare.
|
|
105
|
+
- **Not a general-purpose SQL generator.** It generates the shapes its eighteen generators know. Models outside those shapes stay hand-written and are registered, not forced into a fit, and the metadata vocabulary never evaluates arbitrary SQL or Jinja on your behalf.
|
|
106
|
+
- **Not a migration machine.** Conversion is conservative by design: it drafts what provably fits, leaves a named hole where a person must author, and never swaps a model in without an equivalence proof. It will not restructure your project to make it convertible.
|
|
107
|
+
- **Not a data-quality platform.** The tests it emits are paradigm guarantees (above). Business rules, anomaly detection and observability remain your tooling's job, alongside the tests you declare yourself.
|
|
108
|
+
- **Not a hosted service.** It is a local CLI working on files in your git. There is no account, no server and nothing to phone home to.
|
|
109
|
+
|
|
110
|
+
## Status and scope
|
|
111
|
+
|
|
112
|
+
Loom is **Alpha** software, and this section is the honest boundary of what that means today, stated here in full rather than by reference. Its interfaces (the CLI, the metadata format, the generated output) may change between releases, and changes are recorded in the changelog. What does not change is the exit guarantee above: generated output is plain dbt, so no Loom change can strand a built project, and if a second metadata format version is ever introduced it ships only with its migration helper in the same release, so declared metadata is never left without a migration path.
|
|
113
|
+
|
|
114
|
+
Dialect support has exactly two states. **Executed and supported**: DuckDB. The full lifecycle (generate, seed, build, runtime tests) runs on DuckDB, in CI. **Generation only (parse-checked, not executed)**: PostgreSQL, ClickHouse, Databricks, Snowflake, BigQuery, Redshift, Azure Synapse. Loom generates dialect-flavoured SQL for these dialects and the test suite parse-checks every generator's output under each dialect's grammar; nothing is executed on them. That is a syntax guarantee, not adapter compatibility or a support claim, and no other dialect statement anywhere in this documentation should be read as widening it.
|
|
115
|
+
|
|
116
|
+
## Install
|
|
117
|
+
|
|
118
|
+
Loom needs Python 3.11 to 3.14 and installs from PyPI:
|
|
119
|
+
|
|
120
|
+
```sh
|
|
121
|
+
python -m pip install loom-dbt
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Loom does not depend on dbt or on any adapter: install those the way you already do for your project (the examples use `dbt-duckdb`, the executed dialect). Check the install with `loom --version`, then take one of the two doors below.
|
|
125
|
+
|
|
126
|
+
## Two doors
|
|
127
|
+
|
|
128
|
+
There are two ways in, depending on whether the project exists yet. Start from the question you actually have, and take the matching door.
|
|
129
|
+
|
|
130
|
+
**New project:** work through the [first project](examples/first-project/README.md), a complete project in fourteen authored files (one source, two staging models, one hub, one hand-written model) whose README walks the loop step by step: what to run, what you should see, and what just happened. When the loop makes sense, the [quickstart showcase](examples/quickstart/README.md) is the second project: every paradigm composed in one project, with the add-a-model recipe for day-two work. Both examples run locally with no credentials, external data or dbt packages.
|
|
131
|
+
|
|
132
|
+
**Existing project:** the [adoption playbook](docs/adoption-playbook.md) converts first and registers the rest, and the order is load-bearing. `loom convert` drafts a conversion for every model unknown to Loom that fits a generator, and an authoring draft (inputs pre-wired, the transform left as one named hole) for layered models that fit none; nothing is swapped in until an equivalence proof passes. Then `loom register` inventories the models you are keeping hand-written, making them searchable and coverage-checked, and withdraws them from convert's drafting pool: that withdrawal is why registration comes second, and `--include-registered` re-opens a registered model when its circumstances change. A project may stay hybrid forever, and many should. The [jaffle-shop example](examples/jaffle-shop/README.md) shows the whole pathway end to end on dbt's canonical example project: what converted, what was registered instead and why, and a verified remodel of the models that stayed hand-written, for teams weighing that trade.
|
|
133
|
+
|
|
134
|
+
## What you can model
|
|
135
|
+
|
|
136
|
+
If a shape is in this table, a generator ships for it today. The architecture links are the per-generator contracts and guarantees.
|
|
137
|
+
|
|
138
|
+
| What you need | Current capability |
|
|
139
|
+
| --- | --- |
|
|
140
|
+
| Project and rename raw columns | [Staging](docs/architecture.md#staging), using an authored native dbt source; optional project-authored column macros |
|
|
141
|
+
| Represent identities and relationships | [Hubs](docs/architecture.md#hubs), including multi-source hubs (`input_models` with per-source provenance, the mapping form of `record_source`), opt-in persistent retention (incremental append on every dialect; merge on Databricks) and first-insert provenance (`record_source`, `__loaded_at`, with semantics per generator in [Provenance](docs/architecture.md#provenance)); [links](docs/architecture.md#links) join on hub hashes across two or more endpoints, fail the build on null, blank or unmatched keys, and are runtime-verified non-empty on DuckDB, plus dependent-child keys and a non-historised event-link variant carrying payload |
|
|
142
|
+
| Store descriptive vault history | [Satellites](docs/architecture.md#satellites) (standard, non-historised, record-tracking, multi-active and effectivity variants over hub or link parents, with generated current-state views), [point-in-time tables](docs/architecture.md#point-in-time-tables), [bridge tables](docs/architecture.md#bridge-tables) and [reference data](docs/architecture.md#reference-data) constructs |
|
|
143
|
+
| Track dimension history | [SCD2 snapshot and view](docs/architecture.md#scd2-dimensions), executed on DuckDB, not detached MERGE |
|
|
144
|
+
| Build lakehouse pipelines | [Medallion bronze](docs/architecture.md#medallion-bronze) (raw load), [silver](docs/architecture.md#medallion-silver) (deduplicated), [gold](docs/architecture.md#medallion-gold) (aggregates) |
|
|
145
|
+
| Build dimensional models | [Kimball facts](docs/architecture.md#kimball-facts) with measures and dimension-key columns, [dimensions](docs/architecture.md#kimball-dimensions) with surrogate keys |
|
|
146
|
+
| Build normalized tables and views | [3NF normalized tables](docs/architecture.md#3nf-normalized-tables) with entity/attribute separation, [reporting views](docs/architecture.md#3nf-reporting-views) over normalized inputs |
|
|
147
|
+
| Keep discovery current | [Validation, generation, manifest-backed coverage and search](docs/architecture.md#daily-authoring-workflow), including hand-written models |
|
|
148
|
+
| Review more advanced changes | [Generated dbt tests](docs/architecture.md#generated-dbt-tests), [source-structure inspection/baselines](docs/architecture.md#source-structure-inspection) and [migration planning](docs/architecture.md#consumer-migration) |
|
|
149
|
+
|
|
150
|
+
## The daily workflow
|
|
151
|
+
|
|
152
|
+
Day to day, Loom is four commands against one declaration. **Declare:** edit a pattern, instance or registration (small YAML files under `metadata/` and `local/registrations/`), then run `loom validate`, the gate every other step builds on. **Generate:** `loom generate` previews every file it would write, change or leave alone; add `--write` to apply the preview, and `--select <selector>` to scope it to a set of models. **Prove:** `loom generate --check` fails when any generated file no longer matches the declaration, and `loom check` runs validation plus manifest-backed coverage in one gate; both belong in CI. **Ask:** `loom search` finds what already exists before you build it: with no query it prints a project overview, and a query searches the one inventory (declared metadata and the dbt manifest as a single corpus) by name, key, column, input, generator, paradigm, owner or status, with faceted counts over the result set and a detail view when exactly one object matches. Search and the action commands share one selector grammar, so the string that finds a set in search is the same string you pass to `generate` or `check` with `--select`; search may add clearly labelled close matches by shape overlap (and `--similar-to <model>` searches by example), while `--select` on an action command always takes exact matches only.
|
|
153
|
+
|
|
154
|
+
## Authoring with an AI assistant
|
|
155
|
+
|
|
156
|
+
Loom is designed to be worked by an AI assistant as much as by hand. The authored surface is small, structured YAML that diffs cleanly; every command reports as JSON as well as text; and generation is deterministic, so an assistant can draft and propose while the same gates a human uses (`validate`, `generate --check`, the equivalence proofs) arbitrate what lands. To make that practical, **six agent skills ship inside the package**: task guidance that teaches an assistant Loom's model, its commands and its working rules, version-matched to the CLI by construction.
|
|
157
|
+
|
|
158
|
+
| Skill | What it covers |
|
|
159
|
+
| --- | --- |
|
|
160
|
+
| `loom-core` | The shared foundation: the mental model, the responsibility boundary (an assistant drafts and proposes; the practitioner decides), write-safety and the selector grammar |
|
|
161
|
+
| `loom-bootstrap` | Starting a brand-new Loom project from zero |
|
|
162
|
+
| `loom-adoption` | Assessing an existing dbt project: convert what fits, register the rest, prove equivalence before any swap |
|
|
163
|
+
| `loom-authoring` | Extending a project already on Loom: new models, new patterns, tests on existing models |
|
|
164
|
+
| `loom-ask` | Read-only questions over the inventory: what exists, what depends on what, whether something like it already exists |
|
|
165
|
+
| `loom-maintenance` | Keeping an adopted project healthy: drift, coverage findings, diagnosing validate and generate failures |
|
|
166
|
+
|
|
167
|
+
**Installing and using them.** The skills are ordinary Agent Skills (the open `SKILL.md` format). They are already on disk wherever Loom is installed, inside the package at `loom/.agents/skills/`, and are mirrored at [`skills/`](skills/) in this repository. Install them from there with your agent harness's skill mechanism (for example `npx skills add lincolnp-oss/loom-dbt`), or copy the six directories into your project's `.agents/skills/` folder; then just ask your assistant for the task ("adopt this project", "add a hub for orders", "what already exists like this") and the matching skill loads its procedure. The working rules the skills enforce are the review-first protocol: the assistant reads the project through Loom's JSON outputs rather than guessing its structure, drafts metadata YAML only and never writes generated files, gates every draft on `loom validate`, and leaves `generate` and any overwrite of a practitioner file to you. The full protocol is in the architecture document's [Migration and AI-assisted authoring](docs/architecture.md#migration-and-ai-assisted-authoring) section.
|
|
168
|
+
|
|
169
|
+
## Learn more
|
|
170
|
+
|
|
171
|
+
| Task | Start here |
|
|
172
|
+
| --- | --- |
|
|
173
|
+
| Start a new project | The [first project](examples/first-project/README.md), the guided on-ramp |
|
|
174
|
+
| Add a model to a Loom project | The [add-a-model recipe](examples/quickstart/README.md#add-a-model-the-recipe) |
|
|
175
|
+
| Look up a metadata format or option | The [metadata reference](docs/metadata-reference.md): a pattern definition and one composite instance as annotated working examples |
|
|
176
|
+
| Migrate an existing dbt project | The [adoption playbook](docs/adoption-playbook.md) |
|
|
177
|
+
| Prove a project still matches its declaration | [`loom generate --check`](docs/commands.md#loom-generate) and [`loom check`](docs/commands.md#loom-check) |
|
|
178
|
+
| Look up a command or the selector grammar | The [command reference](docs/commands.md) |
|
|
179
|
+
| Work with Loom through an AI assistant | The bundled agent skills - `loom-core`, `loom-bootstrap`, `loom-adoption`, `loom-authoring`, `loom-ask` and `loom-maintenance` - shipped inside the package at `src/loom/.agents/skills/` and mirrored at [`skills/`](skills/) in this repository |
|
|
180
|
+
| Understand the design | The [architecture document](docs/architecture.md) and the [decision records](docs/adr/README.md) |
|
|
181
|
+
| Diagnose a failure | [Diagnostics and recovery](docs/architecture.md#diagnostics-and-recovery) |
|
|
182
|
+
| Report a problem | [SUPPORT.md](SUPPORT.md) |
|
|
183
|
+
|
|
184
|
+
## A note on how Loom was built
|
|
185
|
+
|
|
186
|
+
In the spirit of the section above: Loom itself was developed with substantial AI assistance. Design exploration, implementation, tests and documentation were drafted and iterated with AI agents, under human direction and review, using much the same discipline the bundled skills teach an assistant working in a Loom project: small reviewable changes, explicit decision points, and automated gates that a change must pass before it lands.
|
|
187
|
+
|
|
188
|
+
The appropriate caveat is that AI involvement is a fact about the process, not a quality claim in either direction. The evidence you can judge is the same evidence any project offers: a test suite of over 1,700 tests runs in CI on Linux and Windows across Python 3.11 to 3.14, together with DuckDB runtime integration tests, an installed-consumer test and an end-to-end quickstart run on every change; the release tree was additionally put through an independent readiness assessment and exercised end to end by a separate local AI agent before publication. And the structural protection does not depend on trusting any of that: what Loom generates is plain dbt, reviewable like any other code, and your project keeps building if you delete Loom entirely. If you find a defect, an issue with a reproduction is the most useful contribution there is.
|
|
189
|
+
|
|
190
|
+
## Contributing and licence
|
|
191
|
+
|
|
192
|
+
Report problems via GitHub Issues; [SUPPORT.md](SUPPORT.md) has what to check first and what a useful report contains, and no support response is promised. To contribute, see [CONTRIBUTING.md](CONTRIBUTING.md) for setup, checks and change expectations. Loom is released under the [MIT License](LICENSE); files Loom generates into your project are yours under your own terms, and Loom claims no additional rights in them.
|
loom_dbt-0.1.0/README.md
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# Loom
|
|
2
|
+
|
|
3
|
+
[](https://github.com/lincolnp-oss/loom-dbt/actions/workflows/ci.yml) [](https://pypi.org/project/loom-dbt/) [](https://pypi.org/project/loom-dbt/) [](LICENSE)
|
|
4
|
+
|
|
5
|
+
Loom is built and maintained by one person; if it saves your team time, consider [sponsoring its development](https://github.com/sponsors/lincolnp-oss).
|
|
6
|
+
|
|
7
|
+
Loom is an authoring-time CLI for dbt projects: declare each repeated transformation once, and Loom generates the plain dbt models, tests and properties for it, proves they still match what you declared, and lets you search the whole project before you build something that already exists.
|
|
8
|
+
|
|
9
|
+
## The 30-second example
|
|
10
|
+
|
|
11
|
+
One small declaration is the whole idea. A **pattern** is a named, reusable shape; an **instance** is one concrete model declared against it. This declaration:
|
|
12
|
+
|
|
13
|
+
```yaml
|
|
14
|
+
pattern: current_hub
|
|
15
|
+
instances:
|
|
16
|
+
- name: hub_customer
|
|
17
|
+
params:
|
|
18
|
+
description: Stable customer identity table.
|
|
19
|
+
input_models: [stg_customers]
|
|
20
|
+
business_key: customer_id
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
generates this model:
|
|
24
|
+
|
|
25
|
+
```sql
|
|
26
|
+
-- Generated by Loom
|
|
27
|
+
-- Generator: hub
|
|
28
|
+
-- Hash function: sha256
|
|
29
|
+
{{ config(materialized='table') }}
|
|
30
|
+
SELECT DISTINCT
|
|
31
|
+
LOWER(sha256(UPPER(TRIM(CAST(customer_id AS VARCHAR))))) AS customer_hk,
|
|
32
|
+
UPPER(TRIM(CAST(customer_id AS VARCHAR))) AS customer_id
|
|
33
|
+
FROM {{ ref('stg_customers') }}
|
|
34
|
+
WHERE customer_id IS NOT NULL
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
That is the loop in miniature: the shape is declared once, the model is generated as plain dbt with no Loom macros to call at build time, and the same declaration is what validation, search and coverage read. The [first project](examples/first-project/README.md) walks the loop end to end.
|
|
38
|
+
|
|
39
|
+
## Why Loom?
|
|
40
|
+
|
|
41
|
+
Most dbt projects repeat the same modelling shapes until consistency becomes the problem: copies drift, generators demand a permanent place in your build, and nobody can say with confidence what already exists. Loom's answer, in one line per problem:
|
|
42
|
+
|
|
43
|
+
| Problem | Loom's answer |
|
|
44
|
+
| --- | --- |
|
|
45
|
+
| The same shapes copied model after model, drifting apart | Declare the shape once as a pattern plus one small instance per model; regenerate all of them from the declaration, and let `generate --check` prove none of them drifted |
|
|
46
|
+
| Generators that become permanent runtime dependencies | Loom runs at authoring time only; the generated SQL/YAML is plain dbt with no Loom macros, so dbt runs your project without Loom installed |
|
|
47
|
+
| Tooling that covers one paradigm and bends the rest to fit | Generators for Data Vault, Kimball, medallion, 3NF and more under one contract, mixed freely in one project, never converted into each other |
|
|
48
|
+
| Migrations that demand everything convert at once | Convert first, then register the rest: drafts where a generator fits (an equivalence proof gates any swap), authoring drafts with one named hole for layered models that fit none, and registration last, because registering a model withdraws it from the drafting pool |
|
|
49
|
+
| "Does this already exist?" answered by grepping and guessing | One inventory of every model in the project, generated and hand-written, searchable in the terminal before you build |
|
|
50
|
+
| Model knowledge scattered across YAML, docs and heads | One declaration in your git is the authored source; properties, validation, search and coverage all derive from it |
|
|
51
|
+
|
|
52
|
+
And the guarantee the table rests on: **what Loom generates is plain dbt, so you can delete Loom and the project still builds.** Adoption is reversible by construction, not by promise.
|
|
53
|
+
|
|
54
|
+
## What Loom is
|
|
55
|
+
|
|
56
|
+
Loom is three small ideas and four verbs. A **generator** is the machinery that knows how to build one kind of model (a hub, a staging model, a fact). A **pattern** is a named, reusable configuration of a generator: the shape your project repeats. An **instance set** is a collection of **instances** - concrete models declared against a pattern, supplying only what differs: names, inputs, keys. Hand-written models are not second-class: a **registration** describes one in the same terms, and it joins the same inventory. The [terminology](docs/terminology.md) draws the exact boundary between Loom authoring and dbt resources.
|
|
57
|
+
|
|
58
|
+
**Declare, generate, prove, ask.** *Declare* the project once, as patterns, instances and registrations held in your own git. *Generate* the models, tests and properties from that declaration. *Prove* the generated files still match the declaration, and that the inventory covers the project. *Ask* the whole project questions (what exists, what is keyed on this, what would this change touch) before building anything. Generation is episodic; asking is the daily habit.
|
|
59
|
+
|
|
60
|
+
## What generation adds
|
|
61
|
+
|
|
62
|
+
A generated model is more than its SELECT statement. The generators also produce, from the same declaration and without being asked, the parts of a well-kept model that projects otherwise remember inconsistently or not at all:
|
|
63
|
+
|
|
64
|
+
- **Columns**: the hash and surrogate keys a paradigm runs on (hub and link hashes, dimension surrogate keys), the `__loaded_at` load timestamp on generated models, and record-source provenance where the generator carries it.
|
|
65
|
+
- **Tests**: each generator emits the dbt data tests its own guarantees rest on, default-on and not suppressible: `unique` and `not_null` on hub and link hashes, `relationships` from every link endpoint hash to its hub, `not_null` on fact dimension keys and measures, the dedup key's `unique` and `not_null` on silver, `not_null` on every gold and mart measure, and their counterparts for the other generators (the full table is in the [architecture document](docs/architecture.md#generated-dbt-tests)). Links additionally carry a guard inside their SQL that fails the build on null, blank or unmatched keys rather than letting a bad join through quietly.
|
|
66
|
+
- **Properties and declarations**: a properties document per generated model carrying its description and column documentation from the declaration, so the catalogue dbt shows is written from the same source as the SQL; the dbt source declaration for any source declared inline in metadata; and, for satellites, the generated current-state view alongside the history table.
|
|
67
|
+
|
|
68
|
+
The boundary is deliberate: Loom originates only the tests its generators' guarantees require. There are no inferred tests on staging models or raw sources, and no business-specific tests: those stay yours to declare, and tests you declare on an instance (generic tests, and dbt unit tests via the `unit_tests` parameter) are carried into the generated documents verbatim.
|
|
69
|
+
|
|
70
|
+
## What Loom isn't
|
|
71
|
+
|
|
72
|
+
- **Not a dbt replacement, and not in your build.** Loom runs at authoring time on your machine; the project it leaves behind is plain dbt that builds without it. There are no Loom macros to install into a dbt project and no runtime component at all.
|
|
73
|
+
- **Not an orchestrator or a warehouse tool.** It schedules nothing, connects to no warehouse to generate (generation needs no warehouse connection), and never runs your models: dbt and your platform do that.
|
|
74
|
+
- **Not an ingestion tool.** It moves no data. Sources, seeds and loading are yours; staging binds the sources you declare.
|
|
75
|
+
- **Not a general-purpose SQL generator.** It generates the shapes its eighteen generators know. Models outside those shapes stay hand-written and are registered, not forced into a fit, and the metadata vocabulary never evaluates arbitrary SQL or Jinja on your behalf.
|
|
76
|
+
- **Not a migration machine.** Conversion is conservative by design: it drafts what provably fits, leaves a named hole where a person must author, and never swaps a model in without an equivalence proof. It will not restructure your project to make it convertible.
|
|
77
|
+
- **Not a data-quality platform.** The tests it emits are paradigm guarantees (above). Business rules, anomaly detection and observability remain your tooling's job, alongside the tests you declare yourself.
|
|
78
|
+
- **Not a hosted service.** It is a local CLI working on files in your git. There is no account, no server and nothing to phone home to.
|
|
79
|
+
|
|
80
|
+
## Status and scope
|
|
81
|
+
|
|
82
|
+
Loom is **Alpha** software, and this section is the honest boundary of what that means today, stated here in full rather than by reference. Its interfaces (the CLI, the metadata format, the generated output) may change between releases, and changes are recorded in the changelog. What does not change is the exit guarantee above: generated output is plain dbt, so no Loom change can strand a built project, and if a second metadata format version is ever introduced it ships only with its migration helper in the same release, so declared metadata is never left without a migration path.
|
|
83
|
+
|
|
84
|
+
Dialect support has exactly two states. **Executed and supported**: DuckDB. The full lifecycle (generate, seed, build, runtime tests) runs on DuckDB, in CI. **Generation only (parse-checked, not executed)**: PostgreSQL, ClickHouse, Databricks, Snowflake, BigQuery, Redshift, Azure Synapse. Loom generates dialect-flavoured SQL for these dialects and the test suite parse-checks every generator's output under each dialect's grammar; nothing is executed on them. That is a syntax guarantee, not adapter compatibility or a support claim, and no other dialect statement anywhere in this documentation should be read as widening it.
|
|
85
|
+
|
|
86
|
+
## Install
|
|
87
|
+
|
|
88
|
+
Loom needs Python 3.11 to 3.14 and installs from PyPI:
|
|
89
|
+
|
|
90
|
+
```sh
|
|
91
|
+
python -m pip install loom-dbt
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Loom does not depend on dbt or on any adapter: install those the way you already do for your project (the examples use `dbt-duckdb`, the executed dialect). Check the install with `loom --version`, then take one of the two doors below.
|
|
95
|
+
|
|
96
|
+
## Two doors
|
|
97
|
+
|
|
98
|
+
There are two ways in, depending on whether the project exists yet. Start from the question you actually have, and take the matching door.
|
|
99
|
+
|
|
100
|
+
**New project:** work through the [first project](examples/first-project/README.md), a complete project in fourteen authored files (one source, two staging models, one hub, one hand-written model) whose README walks the loop step by step: what to run, what you should see, and what just happened. When the loop makes sense, the [quickstart showcase](examples/quickstart/README.md) is the second project: every paradigm composed in one project, with the add-a-model recipe for day-two work. Both examples run locally with no credentials, external data or dbt packages.
|
|
101
|
+
|
|
102
|
+
**Existing project:** the [adoption playbook](docs/adoption-playbook.md) converts first and registers the rest, and the order is load-bearing. `loom convert` drafts a conversion for every model unknown to Loom that fits a generator, and an authoring draft (inputs pre-wired, the transform left as one named hole) for layered models that fit none; nothing is swapped in until an equivalence proof passes. Then `loom register` inventories the models you are keeping hand-written, making them searchable and coverage-checked, and withdraws them from convert's drafting pool: that withdrawal is why registration comes second, and `--include-registered` re-opens a registered model when its circumstances change. A project may stay hybrid forever, and many should. The [jaffle-shop example](examples/jaffle-shop/README.md) shows the whole pathway end to end on dbt's canonical example project: what converted, what was registered instead and why, and a verified remodel of the models that stayed hand-written, for teams weighing that trade.
|
|
103
|
+
|
|
104
|
+
## What you can model
|
|
105
|
+
|
|
106
|
+
If a shape is in this table, a generator ships for it today. The architecture links are the per-generator contracts and guarantees.
|
|
107
|
+
|
|
108
|
+
| What you need | Current capability |
|
|
109
|
+
| --- | --- |
|
|
110
|
+
| Project and rename raw columns | [Staging](docs/architecture.md#staging), using an authored native dbt source; optional project-authored column macros |
|
|
111
|
+
| Represent identities and relationships | [Hubs](docs/architecture.md#hubs), including multi-source hubs (`input_models` with per-source provenance, the mapping form of `record_source`), opt-in persistent retention (incremental append on every dialect; merge on Databricks) and first-insert provenance (`record_source`, `__loaded_at`, with semantics per generator in [Provenance](docs/architecture.md#provenance)); [links](docs/architecture.md#links) join on hub hashes across two or more endpoints, fail the build on null, blank or unmatched keys, and are runtime-verified non-empty on DuckDB, plus dependent-child keys and a non-historised event-link variant carrying payload |
|
|
112
|
+
| Store descriptive vault history | [Satellites](docs/architecture.md#satellites) (standard, non-historised, record-tracking, multi-active and effectivity variants over hub or link parents, with generated current-state views), [point-in-time tables](docs/architecture.md#point-in-time-tables), [bridge tables](docs/architecture.md#bridge-tables) and [reference data](docs/architecture.md#reference-data) constructs |
|
|
113
|
+
| Track dimension history | [SCD2 snapshot and view](docs/architecture.md#scd2-dimensions), executed on DuckDB, not detached MERGE |
|
|
114
|
+
| Build lakehouse pipelines | [Medallion bronze](docs/architecture.md#medallion-bronze) (raw load), [silver](docs/architecture.md#medallion-silver) (deduplicated), [gold](docs/architecture.md#medallion-gold) (aggregates) |
|
|
115
|
+
| Build dimensional models | [Kimball facts](docs/architecture.md#kimball-facts) with measures and dimension-key columns, [dimensions](docs/architecture.md#kimball-dimensions) with surrogate keys |
|
|
116
|
+
| Build normalized tables and views | [3NF normalized tables](docs/architecture.md#3nf-normalized-tables) with entity/attribute separation, [reporting views](docs/architecture.md#3nf-reporting-views) over normalized inputs |
|
|
117
|
+
| Keep discovery current | [Validation, generation, manifest-backed coverage and search](docs/architecture.md#daily-authoring-workflow), including hand-written models |
|
|
118
|
+
| Review more advanced changes | [Generated dbt tests](docs/architecture.md#generated-dbt-tests), [source-structure inspection/baselines](docs/architecture.md#source-structure-inspection) and [migration planning](docs/architecture.md#consumer-migration) |
|
|
119
|
+
|
|
120
|
+
## The daily workflow
|
|
121
|
+
|
|
122
|
+
Day to day, Loom is four commands against one declaration. **Declare:** edit a pattern, instance or registration (small YAML files under `metadata/` and `local/registrations/`), then run `loom validate`, the gate every other step builds on. **Generate:** `loom generate` previews every file it would write, change or leave alone; add `--write` to apply the preview, and `--select <selector>` to scope it to a set of models. **Prove:** `loom generate --check` fails when any generated file no longer matches the declaration, and `loom check` runs validation plus manifest-backed coverage in one gate; both belong in CI. **Ask:** `loom search` finds what already exists before you build it: with no query it prints a project overview, and a query searches the one inventory (declared metadata and the dbt manifest as a single corpus) by name, key, column, input, generator, paradigm, owner or status, with faceted counts over the result set and a detail view when exactly one object matches. Search and the action commands share one selector grammar, so the string that finds a set in search is the same string you pass to `generate` or `check` with `--select`; search may add clearly labelled close matches by shape overlap (and `--similar-to <model>` searches by example), while `--select` on an action command always takes exact matches only.
|
|
123
|
+
|
|
124
|
+
## Authoring with an AI assistant
|
|
125
|
+
|
|
126
|
+
Loom is designed to be worked by an AI assistant as much as by hand. The authored surface is small, structured YAML that diffs cleanly; every command reports as JSON as well as text; and generation is deterministic, so an assistant can draft and propose while the same gates a human uses (`validate`, `generate --check`, the equivalence proofs) arbitrate what lands. To make that practical, **six agent skills ship inside the package**: task guidance that teaches an assistant Loom's model, its commands and its working rules, version-matched to the CLI by construction.
|
|
127
|
+
|
|
128
|
+
| Skill | What it covers |
|
|
129
|
+
| --- | --- |
|
|
130
|
+
| `loom-core` | The shared foundation: the mental model, the responsibility boundary (an assistant drafts and proposes; the practitioner decides), write-safety and the selector grammar |
|
|
131
|
+
| `loom-bootstrap` | Starting a brand-new Loom project from zero |
|
|
132
|
+
| `loom-adoption` | Assessing an existing dbt project: convert what fits, register the rest, prove equivalence before any swap |
|
|
133
|
+
| `loom-authoring` | Extending a project already on Loom: new models, new patterns, tests on existing models |
|
|
134
|
+
| `loom-ask` | Read-only questions over the inventory: what exists, what depends on what, whether something like it already exists |
|
|
135
|
+
| `loom-maintenance` | Keeping an adopted project healthy: drift, coverage findings, diagnosing validate and generate failures |
|
|
136
|
+
|
|
137
|
+
**Installing and using them.** The skills are ordinary Agent Skills (the open `SKILL.md` format). They are already on disk wherever Loom is installed, inside the package at `loom/.agents/skills/`, and are mirrored at [`skills/`](skills/) in this repository. Install them from there with your agent harness's skill mechanism (for example `npx skills add lincolnp-oss/loom-dbt`), or copy the six directories into your project's `.agents/skills/` folder; then just ask your assistant for the task ("adopt this project", "add a hub for orders", "what already exists like this") and the matching skill loads its procedure. The working rules the skills enforce are the review-first protocol: the assistant reads the project through Loom's JSON outputs rather than guessing its structure, drafts metadata YAML only and never writes generated files, gates every draft on `loom validate`, and leaves `generate` and any overwrite of a practitioner file to you. The full protocol is in the architecture document's [Migration and AI-assisted authoring](docs/architecture.md#migration-and-ai-assisted-authoring) section.
|
|
138
|
+
|
|
139
|
+
## Learn more
|
|
140
|
+
|
|
141
|
+
| Task | Start here |
|
|
142
|
+
| --- | --- |
|
|
143
|
+
| Start a new project | The [first project](examples/first-project/README.md), the guided on-ramp |
|
|
144
|
+
| Add a model to a Loom project | The [add-a-model recipe](examples/quickstart/README.md#add-a-model-the-recipe) |
|
|
145
|
+
| Look up a metadata format or option | The [metadata reference](docs/metadata-reference.md): a pattern definition and one composite instance as annotated working examples |
|
|
146
|
+
| Migrate an existing dbt project | The [adoption playbook](docs/adoption-playbook.md) |
|
|
147
|
+
| Prove a project still matches its declaration | [`loom generate --check`](docs/commands.md#loom-generate) and [`loom check`](docs/commands.md#loom-check) |
|
|
148
|
+
| Look up a command or the selector grammar | The [command reference](docs/commands.md) |
|
|
149
|
+
| Work with Loom through an AI assistant | The bundled agent skills - `loom-core`, `loom-bootstrap`, `loom-adoption`, `loom-authoring`, `loom-ask` and `loom-maintenance` - shipped inside the package at `src/loom/.agents/skills/` and mirrored at [`skills/`](skills/) in this repository |
|
|
150
|
+
| Understand the design | The [architecture document](docs/architecture.md) and the [decision records](docs/adr/README.md) |
|
|
151
|
+
| Diagnose a failure | [Diagnostics and recovery](docs/architecture.md#diagnostics-and-recovery) |
|
|
152
|
+
| Report a problem | [SUPPORT.md](SUPPORT.md) |
|
|
153
|
+
|
|
154
|
+
## A note on how Loom was built
|
|
155
|
+
|
|
156
|
+
In the spirit of the section above: Loom itself was developed with substantial AI assistance. Design exploration, implementation, tests and documentation were drafted and iterated with AI agents, under human direction and review, using much the same discipline the bundled skills teach an assistant working in a Loom project: small reviewable changes, explicit decision points, and automated gates that a change must pass before it lands.
|
|
157
|
+
|
|
158
|
+
The appropriate caveat is that AI involvement is a fact about the process, not a quality claim in either direction. The evidence you can judge is the same evidence any project offers: a test suite of over 1,700 tests runs in CI on Linux and Windows across Python 3.11 to 3.14, together with DuckDB runtime integration tests, an installed-consumer test and an end-to-end quickstart run on every change; the release tree was additionally put through an independent readiness assessment and exercised end to end by a separate local AI agent before publication. And the structural protection does not depend on trusting any of that: what Loom generates is plain dbt, reviewable like any other code, and your project keeps building if you delete Loom entirely. If you find a defect, an issue with a reproduction is the most useful contribution there is.
|
|
159
|
+
|
|
160
|
+
## Contributing and licence
|
|
161
|
+
|
|
162
|
+
Report problems via GitHub Issues; [SUPPORT.md](SUPPORT.md) has what to check first and what a useful report contains, and no support response is promised. To contribute, see [CONTRIBUTING.md](CONTRIBUTING.md) for setup, checks and change expectations. Loom is released under the [MIT License](LICENSE); files Loom generates into your project are yours under your own terms, and Loom claims no additional rights in them.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "loom-dbt"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Authoring-time dbt generation and model inventory coverage"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
keywords = [
|
|
9
|
+
"dbt",
|
|
10
|
+
"data-vault",
|
|
11
|
+
"kimball",
|
|
12
|
+
"medallion",
|
|
13
|
+
"code-generation",
|
|
14
|
+
"data-modeling",
|
|
15
|
+
]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
24
|
+
"Programming Language :: Python :: 3.14",
|
|
25
|
+
"Topic :: Database",
|
|
26
|
+
"Topic :: Software Development :: Code Generators",
|
|
27
|
+
]
|
|
28
|
+
requires-python = ">=3.11,<3.15"
|
|
29
|
+
dependencies = [
|
|
30
|
+
"PyYAML>=6.0,<7",
|
|
31
|
+
"wcwidth>=0.2,<1",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[[project.authors]]
|
|
35
|
+
name = "Lincoln Phillips"
|
|
36
|
+
email = "lincolnp.projects@gmail.com"
|
|
37
|
+
|
|
38
|
+
[[project.maintainers]]
|
|
39
|
+
name = "Lincoln Phillips"
|
|
40
|
+
email = "lincolnp.projects@gmail.com"
|
|
41
|
+
|
|
42
|
+
[project.urls]
|
|
43
|
+
Repository = "https://github.com/lincolnp-oss/loom-dbt"
|
|
44
|
+
Documentation = "https://github.com/lincolnp-oss/loom-dbt/blob/main/docs/commands.md"
|
|
45
|
+
Issues = "https://github.com/lincolnp-oss/loom-dbt/issues"
|
|
46
|
+
Changelog = "https://github.com/lincolnp-oss/loom-dbt/blob/main/CHANGELOG.md"
|
|
47
|
+
|
|
48
|
+
[project.scripts]
|
|
49
|
+
loom = "loom.cli:main"
|
|
50
|
+
|
|
51
|
+
[dependency-groups]
|
|
52
|
+
dev = [
|
|
53
|
+
"duckdb>=1.2,<2",
|
|
54
|
+
"jinja2>=3.1,<4",
|
|
55
|
+
"pytest>=8.0",
|
|
56
|
+
"pytest-cov>=7.1.0",
|
|
57
|
+
"pytest-xdist>=3.5,<4",
|
|
58
|
+
"sqlglot>=25,<30",
|
|
59
|
+
]
|
|
60
|
+
integration = [
|
|
61
|
+
"dbt-clickhouse>=1.8,<2",
|
|
62
|
+
"dbt-duckdb>=1.9,<2",
|
|
63
|
+
"dbt-postgres>=1.8,<2",
|
|
64
|
+
{ include-group = "dev" },
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
[build-system]
|
|
68
|
+
requires = ["uv_build>=0.11.6,<0.14"]
|
|
69
|
+
build-backend = "uv_build"
|
|
70
|
+
|
|
71
|
+
[tool.uv.build-backend]
|
|
72
|
+
module-name = "loom"
|
|
73
|
+
|
|
74
|
+
[tool.pytest.ini_options]
|
|
75
|
+
addopts = "-m 'not integration'"
|
|
76
|
+
pythonpath = ["src"]
|
|
77
|
+
markers = ["integration: optional generated-SQL runtime and installed-consumer smoke tests"]
|
|
78
|
+
|
|
79
|
+
[tool.coverage.run]
|
|
80
|
+
source = ["loom"]
|
|
81
|
+
|
|
82
|
+
[tool.coverage.report]
|
|
83
|
+
fail_under = 80
|
|
84
|
+
show_missing = true
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "loom-dbt"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Authoring-time dbt generation and model inventory coverage"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
authors = [{ name = "Lincoln Phillips", email = "lincolnp.projects@gmail.com" }]
|
|
9
|
+
maintainers = [{ name = "Lincoln Phillips", email = "lincolnp.projects@gmail.com" }]
|
|
10
|
+
keywords = [
|
|
11
|
+
"dbt",
|
|
12
|
+
"data-vault",
|
|
13
|
+
"kimball",
|
|
14
|
+
"medallion",
|
|
15
|
+
"code-generation",
|
|
16
|
+
"data-modeling",
|
|
17
|
+
]
|
|
18
|
+
classifiers = [
|
|
19
|
+
"Development Status :: 3 - Alpha",
|
|
20
|
+
"Intended Audience :: Developers",
|
|
21
|
+
"License :: OSI Approved :: MIT License",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Programming Language :: Python :: 3.11",
|
|
24
|
+
"Programming Language :: Python :: 3.12",
|
|
25
|
+
"Programming Language :: Python :: 3.13",
|
|
26
|
+
"Programming Language :: Python :: 3.14",
|
|
27
|
+
"Topic :: Database",
|
|
28
|
+
"Topic :: Software Development :: Code Generators",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
requires-python = ">=3.11,<3.15"
|
|
32
|
+
dependencies = [
|
|
33
|
+
"PyYAML>=6.0,<7",
|
|
34
|
+
"wcwidth>=0.2,<1",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.urls]
|
|
38
|
+
Repository = "https://github.com/lincolnp-oss/loom-dbt"
|
|
39
|
+
Documentation = "https://github.com/lincolnp-oss/loom-dbt/blob/main/docs/commands.md"
|
|
40
|
+
Issues = "https://github.com/lincolnp-oss/loom-dbt/issues"
|
|
41
|
+
Changelog = "https://github.com/lincolnp-oss/loom-dbt/blob/main/CHANGELOG.md"
|
|
42
|
+
|
|
43
|
+
[project.scripts]
|
|
44
|
+
loom = "loom.cli:main"
|
|
45
|
+
|
|
46
|
+
[dependency-groups]
|
|
47
|
+
dev = [
|
|
48
|
+
"duckdb>=1.2,<2",
|
|
49
|
+
"jinja2>=3.1,<4",
|
|
50
|
+
"pytest>=8.0",
|
|
51
|
+
"pytest-cov>=7.1.0",
|
|
52
|
+
"pytest-xdist>=3.5,<4",
|
|
53
|
+
"sqlglot>=25,<30",
|
|
54
|
+
]
|
|
55
|
+
integration = [
|
|
56
|
+
"dbt-clickhouse>=1.8,<2",
|
|
57
|
+
"dbt-duckdb>=1.9,<2",
|
|
58
|
+
"dbt-postgres>=1.8,<2",
|
|
59
|
+
{ include-group = "dev" },
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
[build-system]
|
|
63
|
+
requires = ["uv_build>=0.11.6,<0.14"]
|
|
64
|
+
build-backend = "uv_build"
|
|
65
|
+
|
|
66
|
+
# The distribution is loom-dbt; the import package stays loom.
|
|
67
|
+
[tool.uv.build-backend]
|
|
68
|
+
module-name = "loom"
|
|
69
|
+
|
|
70
|
+
[tool.pytest.ini_options]
|
|
71
|
+
addopts = "-m 'not integration'"
|
|
72
|
+
pythonpath = ["src"]
|
|
73
|
+
markers = ["integration: optional generated-SQL runtime and installed-consumer smoke tests"]
|
|
74
|
+
|
|
75
|
+
# Coverage is measured on the full default suite with
|
|
76
|
+
# `python -m pytest --cov -q` (see README's contributing section).
|
|
77
|
+
# The floor applies to that command; subset runs are expected to fall
|
|
78
|
+
# below it and should be run without --cov.
|
|
79
|
+
[tool.coverage.run]
|
|
80
|
+
source = ["loom"]
|
|
81
|
+
|
|
82
|
+
[tool.coverage.report]
|
|
83
|
+
fail_under = 80
|
|
84
|
+
show_missing = true
|