model2data 1.7.3__tar.gz → 1.9.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.
- {model2data-1.7.3/model2data.egg-info → model2data-1.9.0}/PKG-INFO +188 -25
- {model2data-1.7.3 → model2data-1.9.0}/README.md +268 -64
- {model2data-1.7.3 → model2data-1.9.0}/README_PYPI.md +185 -24
- model2data-1.9.0/model2data/_vendor/__init__.py +0 -0
- model2data-1.9.0/model2data/_vendor/pydbml/LICENSE +21 -0
- model2data-1.9.0/model2data/_vendor/pydbml/VENDORED.md +51 -0
- model2data-1.9.0/model2data/_vendor/pydbml/__init__.py +3 -0
- model2data-1.9.0/model2data/_vendor/pydbml/_classes/__init__.py +0 -0
- model2data-1.9.0/model2data/_vendor/pydbml/_classes/base.py +70 -0
- model2data-1.9.0/model2data/_vendor/pydbml/_classes/column.py +94 -0
- model2data-1.9.0/model2data/_vendor/pydbml/_classes/enum.py +89 -0
- model2data-1.9.0/model2data/_vendor/pydbml/_classes/expression.py +22 -0
- model2data-1.9.0/model2data/_vendor/pydbml/_classes/index.py +80 -0
- model2data-1.9.0/model2data/_vendor/pydbml/_classes/note.py +23 -0
- model2data-1.9.0/model2data/_vendor/pydbml/_classes/project.py +34 -0
- model2data-1.9.0/model2data/_vendor/pydbml/_classes/reference.py +120 -0
- model2data-1.9.0/model2data/_vendor/pydbml/_classes/sticky_note.py +24 -0
- model2data-1.9.0/model2data/_vendor/pydbml/_classes/table.py +158 -0
- model2data-1.9.0/model2data/_vendor/pydbml/_classes/table_group.py +49 -0
- model2data-1.9.0/model2data/_vendor/pydbml/classes/__init__.py +25 -0
- model2data-1.9.0/model2data/_vendor/pydbml/constants.py +4 -0
- model2data-1.9.0/model2data/_vendor/pydbml/database.py +207 -0
- model2data-1.9.0/model2data/_vendor/pydbml/definitions/__init__.py +0 -0
- model2data-1.9.0/model2data/_vendor/pydbml/definitions/column.py +158 -0
- model2data-1.9.0/model2data/_vendor/pydbml/definitions/common.py +39 -0
- model2data-1.9.0/model2data/_vendor/pydbml/definitions/enum.py +91 -0
- model2data-1.9.0/model2data/_vendor/pydbml/definitions/generic.py +43 -0
- model2data-1.9.0/model2data/_vendor/pydbml/definitions/index.py +110 -0
- model2data-1.9.0/model2data/_vendor/pydbml/definitions/project.py +53 -0
- model2data-1.9.0/model2data/_vendor/pydbml/definitions/records.py +80 -0
- model2data-1.9.0/model2data/_vendor/pydbml/definitions/reference.py +200 -0
- model2data-1.9.0/model2data/_vendor/pydbml/definitions/sticky_note.py +21 -0
- model2data-1.9.0/model2data/_vendor/pydbml/definitions/table.py +152 -0
- model2data-1.9.0/model2data/_vendor/pydbml/definitions/table_group.py +60 -0
- model2data-1.9.0/model2data/_vendor/pydbml/definitions/table_partial.py +48 -0
- model2data-1.9.0/model2data/_vendor/pydbml/exceptions.py +34 -0
- model2data-1.9.0/model2data/_vendor/pydbml/parser/__init__.py +1 -0
- model2data-1.9.0/model2data/_vendor/pydbml/parser/blueprints.py +372 -0
- model2data-1.9.0/model2data/_vendor/pydbml/parser/parser.py +360 -0
- model2data-1.9.0/model2data/_vendor/pydbml/renderer/__init__.py +0 -0
- model2data-1.9.0/model2data/_vendor/pydbml/renderer/base.py +38 -0
- model2data-1.9.0/model2data/_vendor/pydbml/renderer/dbml/__init__.py +0 -0
- model2data-1.9.0/model2data/_vendor/pydbml/renderer/dbml/default/__init__.py +11 -0
- model2data-1.9.0/model2data/_vendor/pydbml/renderer/dbml/default/column.py +55 -0
- model2data-1.9.0/model2data/_vendor/pydbml/renderer/dbml/default/enum.py +25 -0
- model2data-1.9.0/model2data/_vendor/pydbml/renderer/dbml/default/expression.py +7 -0
- model2data-1.9.0/model2data/_vendor/pydbml/renderer/dbml/default/index.py +49 -0
- model2data-1.9.0/model2data/_vendor/pydbml/renderer/dbml/default/note.py +14 -0
- model2data-1.9.0/model2data/_vendor/pydbml/renderer/dbml/default/project.py +29 -0
- model2data-1.9.0/model2data/_vendor/pydbml/renderer/dbml/default/reference.py +68 -0
- model2data-1.9.0/model2data/_vendor/pydbml/renderer/dbml/default/renderer.py +19 -0
- model2data-1.9.0/model2data/_vendor/pydbml/renderer/dbml/default/sticky_note.py +15 -0
- model2data-1.9.0/model2data/_vendor/pydbml/renderer/dbml/default/table.py +58 -0
- model2data-1.9.0/model2data/_vendor/pydbml/renderer/dbml/default/table_group.py +23 -0
- model2data-1.9.0/model2data/_vendor/pydbml/renderer/dbml/default/utils.py +31 -0
- model2data-1.9.0/model2data/_vendor/pydbml/renderer/sql/__init__.py +0 -0
- model2data-1.9.0/model2data/_vendor/pydbml/renderer/sql/default/__init__.py +8 -0
- model2data-1.9.0/model2data/_vendor/pydbml/renderer/sql/default/column.py +45 -0
- model2data-1.9.0/model2data/_vendor/pydbml/renderer/sql/default/enum.py +34 -0
- model2data-1.9.0/model2data/_vendor/pydbml/renderer/sql/default/expression.py +7 -0
- model2data-1.9.0/model2data/_vendor/pydbml/renderer/sql/default/index.py +65 -0
- model2data-1.9.0/model2data/_vendor/pydbml/renderer/sql/default/note.py +43 -0
- model2data-1.9.0/model2data/_vendor/pydbml/renderer/sql/default/reference.py +82 -0
- model2data-1.9.0/model2data/_vendor/pydbml/renderer/sql/default/renderer.py +24 -0
- model2data-1.9.0/model2data/_vendor/pydbml/renderer/sql/default/table.py +97 -0
- model2data-1.9.0/model2data/_vendor/pydbml/renderer/sql/default/utils.py +37 -0
- model2data-1.9.0/model2data/_vendor/pydbml/tools.py +54 -0
- model2data-1.9.0/model2data/cli.py +817 -0
- model2data-1.9.0/model2data/dbt/hint_tests.py +180 -0
- model2data-1.9.0/model2data/dbt/naming.py +61 -0
- model2data-1.9.0/model2data/dbt/templates/hint_macros/model2data_hint_tests.sql +64 -0
- model2data-1.9.0/model2data/dbt/templates/history_macros/model2data_history_tests.sql +38 -0
- model2data-1.9.0/model2data/dbt/tests.py +529 -0
- model2data-1.9.0/model2data/defects/__init__.py +59 -0
- model2data-1.9.0/model2data/defects/apply.py +1064 -0
- model2data-1.9.0/model2data/defects/checks.py +179 -0
- model2data-1.9.0/model2data/defects/presets.py +311 -0
- model2data-1.9.0/model2data/defects/report.py +245 -0
- model2data-1.9.0/model2data/generate/days.py +817 -0
- {model2data-1.7.3 → model2data-1.9.0}/model2data/generate/faker.py +76 -22
- {model2data-1.7.3 → model2data-1.9.0}/model2data/generate/hints.py +14 -17
- model2data-1.9.0/model2data/generate/history.py +152 -0
- model2data-1.9.0/model2data/generate/kinds.py +62 -0
- {model2data-1.7.3 → model2data-1.9.0}/model2data/generate/relationships.py +25 -8
- {model2data-1.7.3 → model2data-1.9.0}/model2data/generate/timeline.py +5 -9
- model2data-1.9.0/model2data/model/__init__.py +70 -0
- model2data-1.9.0/model2data/model/_yaml.py +284 -0
- model2data-1.9.0/model2data/model/dbml.py +375 -0
- model2data-1.9.0/model2data/model/document.py +321 -0
- model2data-1.9.0/model2data/model/dump.py +245 -0
- model2data-1.9.0/model2data/model/engine.py +184 -0
- model2data-1.9.0/model2data/model/errors.py +60 -0
- model2data-1.9.0/model2data/model/reader.py +105 -0
- model2data-1.9.0/model2data/model/types.py +210 -0
- model2data-1.9.0/model2data/model/validate.py +1173 -0
- model2data-1.9.0/model2data/output.py +113 -0
- model2data-1.9.0/model2data/parse/dbml.py +78 -0
- model2data-1.9.0/model2data/spec/PUBLISHING.md +75 -0
- model2data-1.9.0/model2data/spec/README.md +419 -0
- model2data-1.9.0/model2data/spec/examples/coffee_webshop.model2data.yml +126 -0
- model2data-1.9.0/model2data/spec/model.schema.json +583 -0
- {model2data-1.7.3 → model2data-1.9.0/model2data.egg-info}/PKG-INFO +188 -25
- model2data-1.9.0/model2data.egg-info/SOURCES.txt +157 -0
- {model2data-1.7.3 → model2data-1.9.0}/model2data.egg-info/requires.txt +2 -0
- {model2data-1.7.3 → model2data-1.9.0}/pyproject.toml +17 -1
- {model2data-1.7.3 → model2data-1.9.0}/tests/test_cli.py +9 -7
- model2data-1.9.0/tests/test_cli_model.py +248 -0
- {model2data-1.7.3 → model2data-1.9.0}/tests/test_coverage_gaps.py +10 -12
- model2data-1.9.0/tests/test_days.py +539 -0
- {model2data-1.7.3 → model2data-1.9.0}/tests/test_dbml_parser.py +107 -325
- {model2data-1.7.3 → model2data-1.9.0}/tests/test_dbml_parser_fuzz.py +34 -30
- {model2data-1.7.3 → model2data-1.9.0}/tests/test_dbt_integration.py +17 -0
- {model2data-1.7.3 → model2data-1.9.0}/tests/test_dbt_tests.py +44 -0
- model2data-1.9.0/tests/test_defects.py +772 -0
- model2data-1.9.0/tests/test_defects_dbt.py +238 -0
- model2data-1.9.0/tests/test_defects_replay.py +138 -0
- model2data-1.9.0/tests/test_defects_report.py +370 -0
- model2data-1.9.0/tests/test_defects_review.py +302 -0
- model2data-1.9.0/tests/test_examples_model.py +61 -0
- {model2data-1.7.3 → model2data-1.9.0}/tests/test_generation.py +6 -5
- model2data-1.9.0/tests/test_hint_tests.py +421 -0
- model2data-1.9.0/tests/test_history.py +254 -0
- model2data-1.9.0/tests/test_model.py +790 -0
- model2data-1.9.0/tests/test_model_dbml.py +362 -0
- model2data-1.9.0/tests/test_model_edges.py +226 -0
- model2data-1.9.0/tests/test_model_messages.py +603 -0
- model2data-1.9.0/tests/test_model_vocabulary.py +172 -0
- model2data-1.9.0/tests/test_reproducibility.py +116 -0
- {model2data-1.7.3 → model2data-1.9.0}/tests/test_shaping.py +23 -2
- model2data-1.9.0/tests/test_spec_alignment.py +126 -0
- model2data-1.9.0/tests/test_spec_schema.py +108 -0
- model2data-1.7.3/model2data/cli.py +0 -456
- model2data-1.7.3/model2data/dbt/tests.py +0 -305
- model2data-1.7.3/model2data/parse/dbml.py +0 -698
- model2data-1.7.3/model2data.egg-info/SOURCES.txt +0 -48
- {model2data-1.7.3 → model2data-1.9.0}/LICENSE +0 -0
- {model2data-1.7.3 → model2data-1.9.0}/model2data/__init__.py +0 -0
- {model2data-1.7.3 → model2data-1.9.0}/model2data/dbt/__init__.py +0 -0
- {model2data-1.7.3 → model2data-1.9.0}/model2data/dbt/project.py +0 -0
- {model2data-1.7.3 → model2data-1.9.0}/model2data/dbt/templates/dbt_project.yml.jinja +0 -0
- {model2data-1.7.3 → model2data-1.9.0}/model2data/dbt/templates/macros/generate_schema_name.sql +0 -0
- {model2data-1.7.3 → model2data-1.9.0}/model2data/dbt/templates/profiles.yml.jinja +0 -0
- {model2data-1.7.3 → model2data-1.9.0}/model2data/generate/__init__.py +0 -0
- {model2data-1.7.3 → model2data-1.9.0}/model2data/generate/core.py +0 -0
- {model2data-1.7.3 → model2data-1.9.0}/model2data/generate/options.py +0 -0
- {model2data-1.7.3 → model2data-1.9.0}/model2data/parse/__init__.py +0 -0
- {model2data-1.7.3 → model2data-1.9.0}/model2data/utils.py +0 -0
- {model2data-1.7.3 → model2data-1.9.0}/model2data.egg-info/dependency_links.txt +0 -0
- {model2data-1.7.3 → model2data-1.9.0}/model2data.egg-info/entry_points.txt +0 -0
- {model2data-1.7.3 → model2data-1.9.0}/model2data.egg-info/top_level.txt +0 -0
- {model2data-1.7.3 → model2data-1.9.0}/setup.cfg +0 -0
- {model2data-1.7.3 → model2data-1.9.0}/tests/test_as_of_anchor.py +0 -0
- {model2data-1.7.3 → model2data-1.9.0}/tests/test_column_time_hints.py +0 -0
- {model2data-1.7.3 → model2data-1.9.0}/tests/test_dbt_naming.py +0 -0
- {model2data-1.7.3 → model2data-1.9.0}/tests/test_dbt_project.py +0 -0
- {model2data-1.7.3 → model2data-1.9.0}/tests/test_distributions.py +0 -0
- {model2data-1.7.3 → model2data-1.9.0}/tests/test_faker_name_inference.py +0 -0
- {model2data-1.7.3 → model2data-1.9.0}/tests/test_lone_country.py +0 -0
- {model2data-1.7.3 → model2data-1.9.0}/tests/test_options.py +0 -0
- {model2data-1.7.3 → model2data-1.9.0}/tests/test_release_stress.py +0 -0
- {model2data-1.7.3 → model2data-1.9.0}/tests/test_row_identity.py +0 -0
- {model2data-1.7.3 → model2data-1.9.0}/tests/test_table_seeds.py +0 -0
- {model2data-1.7.3 → model2data-1.9.0}/tests/test_timeline.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: model2data
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.9.0
|
|
4
4
|
Summary: Generate analytics-ready datasets from DBML models
|
|
5
5
|
Author: JB Analytica
|
|
6
6
|
License-Expression: MIT
|
|
@@ -28,7 +28,9 @@ License-File: LICENSE
|
|
|
28
28
|
Requires-Dist: dbt-core>=1.11
|
|
29
29
|
Requires-Dist: dbt-duckdb>=1.11
|
|
30
30
|
Requires-Dist: faker>=37.12.0
|
|
31
|
+
Requires-Dist: jsonschema>=4.18
|
|
31
32
|
Requires-Dist: pandas>=2.3.3
|
|
33
|
+
Requires-Dist: pyparsing<4,>=3.1
|
|
32
34
|
Requires-Dist: pyyaml>=6.0.3
|
|
33
35
|
Requires-Dist: typer>=0.20.0
|
|
34
36
|
Provides-Extra: postgres
|
|
@@ -52,15 +54,16 @@ Dynamic: license-file
|
|
|
52
54
|
|
|
53
55
|
**Turn a data model into a running analytics stack in one command.**
|
|
54
56
|
|
|
55
|
-
Give `model2data` a [DBML](https://dbml.dbdiagram.io/docs/)
|
|
56
|
-
from an existing database — and it generates realistic,
|
|
57
|
+
Give `model2data` a data model — a `.model2data.yml` file, or a [DBML](https://dbml.dbdiagram.io/docs/)
|
|
58
|
+
schema hand-written or exported from an existing database — and it generates realistic,
|
|
59
|
+
relationship-preserving synthetic data
|
|
57
60
|
*and* a complete, runnable dbt project around it: seeds, staging models, tests, and a
|
|
58
61
|
DuckDB or Postgres profile. No sample data to hunt down, no dbt boilerplate to hand-write, no
|
|
59
62
|
production data to risk exposing.
|
|
60
63
|
|
|
61
64
|
```bash
|
|
62
65
|
pip install model2data
|
|
63
|
-
model2data --file examples/ecommerce.
|
|
66
|
+
model2data --file examples/ecommerce.model2data.yml --rows 200 --seed 42
|
|
64
67
|
cd dbt_ecommerce && dbt build
|
|
65
68
|
```
|
|
66
69
|
|
|
@@ -112,15 +115,72 @@ access required.
|
|
|
112
115
|
|
|
113
116
|
## How it works
|
|
114
117
|
|
|
115
|
-
1. **
|
|
118
|
+
1. **Read.** Reads the model — its tables, columns, keys, references and generation hints — from
|
|
119
|
+
a `.model2data.yml` document, checking it against [the spec](https://github.com/JB-Analytica/model2data/blob/main/model2data/spec/README.md), or
|
|
120
|
+
from DBML, which it converts to the same model first.
|
|
116
121
|
2. **Generate.** Produces synthetic values per column — typed generation for known SQL types
|
|
117
122
|
(int, date, timestamp, ...), name-aware inference for everything else (`email`, `phone`,
|
|
118
123
|
`city`, ...), foreign keys resolved against already-generated parent rows.
|
|
119
124
|
3. **Scaffold.** Writes a complete dbt project around that data: CSV seeds, staging models that
|
|
120
125
|
`ref()` those seeds, `not_null`/`unique`/`relationships` tests, `accepted_values` tests for
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
126
|
+
enum-typed columns, singular SQL tests for composite primary/unique keys, table and column
|
|
127
|
+
`description:` fields from the model, and a profile for DuckDB (zero-config, file-based) or
|
|
128
|
+
Postgres.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## The model file
|
|
133
|
+
|
|
134
|
+
A model is one YAML document, `<name>.model2data.yml` — or the same document as JSON. Its format
|
|
135
|
+
is [spec 0.3.0](https://github.com/JB-Analytica/model2data/blob/main/model2data/spec/README.md), with a JSON Schema your editor can autocomplete and
|
|
136
|
+
check against:
|
|
137
|
+
|
|
138
|
+
```yaml
|
|
139
|
+
# yaml-language-server: $schema=https://www.jbanalytica.com/model2data/spec/0.2.0/model.schema.json
|
|
140
|
+
model2data: 0.2.0
|
|
141
|
+
name: coffee_webshop
|
|
142
|
+
|
|
143
|
+
enums:
|
|
144
|
+
order_status: [pending, paid, shipped, delivered, cancelled]
|
|
145
|
+
|
|
146
|
+
tables:
|
|
147
|
+
customers:
|
|
148
|
+
columns:
|
|
149
|
+
id: {type: bigint, pk: true}
|
|
150
|
+
email: {type: email, unique: true, not_null: true}
|
|
151
|
+
|
|
152
|
+
orders:
|
|
153
|
+
columns:
|
|
154
|
+
id: {type: bigint, pk: true}
|
|
155
|
+
customer_id:
|
|
156
|
+
type: bigint
|
|
157
|
+
not_null: true
|
|
158
|
+
references: customers.id
|
|
159
|
+
generate: {skew: 0.8}
|
|
160
|
+
total_amount:
|
|
161
|
+
type: numeric
|
|
162
|
+
generate:
|
|
163
|
+
min: 10
|
|
164
|
+
max: 5000
|
|
165
|
+
distribution: {kind: lognormal, median: 120, spread: 0.7}
|
|
166
|
+
status:
|
|
167
|
+
type: order_status
|
|
168
|
+
generate:
|
|
169
|
+
weights: {delivered: 20, cancelled: 1}
|
|
170
|
+
|
|
171
|
+
run:
|
|
172
|
+
seed: 1
|
|
173
|
+
as_of: 2026-01-01
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
`generate` holds a column's hints; `run` saves the generation settings with the model, and every
|
|
177
|
+
CLI option overrides the one it names. `model2data validate FILE` checks a model and prints every
|
|
178
|
+
issue with its path in the document.
|
|
179
|
+
|
|
180
|
+
**DBML is supported input.** `--file` also takes a `.dbml` file, converted to the same model
|
|
181
|
+
before generating; a JSON note on a column (`[note: '{"min": 1}']`) becomes its `generate`, any
|
|
182
|
+
other note its description. `model2data convert schema.dbml -o schema.model2data.yml` writes the
|
|
183
|
+
model a DBML file converts to.
|
|
124
184
|
|
|
125
185
|
---
|
|
126
186
|
|
|
@@ -158,12 +218,12 @@ pip install model2data
|
|
|
158
218
|
## Quick start
|
|
159
219
|
|
|
160
220
|
We bundle several example schemas in `examples/` — this walkthrough uses the e-commerce one
|
|
161
|
-
(`examples/ecommerce.
|
|
221
|
+
(`examples/ecommerce.model2data.yml`: customers, products, orders, order items, and reviews).
|
|
162
222
|
|
|
163
223
|
Generate a project with synthetic data:
|
|
164
224
|
|
|
165
225
|
```bash
|
|
166
|
-
model2data --file examples/ecommerce.
|
|
226
|
+
model2data --file examples/ecommerce.model2data.yml --rows 200 --seed 42
|
|
167
227
|
```
|
|
168
228
|
|
|
169
229
|
This creates a `dbt_ecommerce/` folder with your data and dbt setup.
|
|
@@ -176,7 +236,7 @@ re-rolls just that table, leaving every other table's seed CSV byte-identical. `
|
|
|
176
236
|
the country every generated person and address comes from:
|
|
177
237
|
|
|
178
238
|
```bash
|
|
179
|
-
model2data --file examples/ecommerce.
|
|
239
|
+
model2data --file examples/ecommerce.model2data.yml --rows 200 --seed 42 \
|
|
180
240
|
--as-of 2026-01-31 --table-seed orders=7 --locale nl_BE
|
|
181
241
|
```
|
|
182
242
|
|
|
@@ -199,24 +259,126 @@ To target Postgres instead, install the extra and pass `--adapter postgres`:
|
|
|
199
259
|
|
|
200
260
|
```bash
|
|
201
261
|
pip install "model2data[postgres]"
|
|
202
|
-
model2data --file examples/ecommerce.
|
|
262
|
+
model2data --file examples/ecommerce.model2data.yml --rows 200 --seed 42 --adapter postgres
|
|
203
263
|
```
|
|
204
264
|
|
|
205
265
|
Connection details are read from environment variables (`MODEL2DATA_PG_HOST`, `MODEL2DATA_PG_PORT`, `MODEL2DATA_PG_USER`, `MODEL2DATA_PG_PASSWORD`, `MODEL2DATA_PG_DATABASE`), defaulting to `localhost:5432` with a `postgres`/`postgres` user for local development.
|
|
206
266
|
|
|
207
|
-
After generation, the CLI prints a short summary — tables and rows generated, relationships found in the
|
|
267
|
+
After generation, the CLI prints a short summary — tables and rows generated, relationships found in the model, and any columns that fell back to generic placeholder text because neither their type nor name could be matched.
|
|
208
268
|
|
|
209
269
|
Pass `--unit-tests` to also generate deterministic dbt unit test fixtures (`models/staging/ut_stg_<table>.yml`) from the actually-generated seed rows:
|
|
210
270
|
|
|
211
271
|
```bash
|
|
212
|
-
model2data --file examples/ecommerce.
|
|
272
|
+
model2data --file examples/ecommerce.model2data.yml --rows 200 --seed 42 --unit-tests
|
|
213
273
|
```
|
|
214
274
|
|
|
215
275
|
This targets dbt-core's native unit testing feature, which works out of the box with the base
|
|
216
276
|
install — see [dbt-core versions](#dbt-core-versions) below.
|
|
217
277
|
|
|
278
|
+
Your model's hints also write **data tests** for the dbt project, so the model that generates the
|
|
279
|
+
fixtures guards the real pipeline too. Every hint that states a constraint becomes a generic test
|
|
280
|
+
on the staging model, shipped as a self-contained macro in `macros/model2data_hint_tests.sql` (no
|
|
281
|
+
dbt package, so `dbt build` still works offline):
|
|
282
|
+
|
|
283
|
+
| In the model | dbt test | Tolerance |
|
|
284
|
+
|---|---|---|
|
|
285
|
+
| `generate: {min, max}` on a numeric column | `model2data_between` | none |
|
|
286
|
+
| `generate: {after: other}` | `model2data_not_before` (where both are not null) | none |
|
|
287
|
+
| `generate: {null_rate}` | `model2data_max_null_share`: nulls at most `null_rate` + tolerance | `--test-tolerance`, default 0.1 |
|
|
288
|
+
| `generate: {distinct: n}` | `model2data_max_distinct`: at most `n` distinct values | none |
|
|
289
|
+
| `grain` on a table | `model2data_unique_combination` | none |
|
|
290
|
+
| an enum-typed column | `accepted_values` (always written) | none |
|
|
291
|
+
|
|
292
|
+
`true_rate`, `weights`, `skew`, `distribution` and the temporal shape hints (`business_hours`,
|
|
293
|
+
`growth`, `seasonality`) describe a statistical shape rather than a constraint a row can break, so
|
|
294
|
+
they write no test. `--hint-tests {error,warn,off}` sets these tests' severity (default `warn`:
|
|
295
|
+
they describe intent and should not break a first `dbt build` on real data); the structural tests
|
|
296
|
+
(`not_null`, `unique`, `relationships`) are unaffected. A model with no such hints generates the
|
|
297
|
+
same YAML as before. To call the mapping from Python, `model2data.dbt.hint_tests.hint_tests_for(model)`
|
|
298
|
+
returns the tests as data.
|
|
299
|
+
|
|
218
300
|
---
|
|
219
301
|
|
|
302
|
+
### Generate the next days
|
|
303
|
+
|
|
304
|
+
A table with `incremental` in the model moves on day by day: each day adds `new_per_day` rows
|
|
305
|
+
(their dates and timestamps falling on that day) and updates `update_rate` of the rows it already
|
|
306
|
+
holds, and an enum column with `transitions` moves from a state to one of the states allowed to
|
|
307
|
+
follow it. See `examples/ecommerce_daily.model2data.yml` and "Days after the first" in the
|
|
308
|
+
[spec](https://github.com/JB-Analytica/model2data/blob/main/model2data/spec/README.md).
|
|
309
|
+
|
|
310
|
+
```bash
|
|
311
|
+
model2data --file examples/ecommerce_daily.model2data.yml --seed 42 --as-of 2026-01-31 --days 7
|
|
312
|
+
model2data --file examples/ecommerce_daily.model2data.yml --seed 42 --as-of 2026-01-31 --next
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
`--days N` generates day 0, the run you would get without it, and N days after it (`--next` is
|
|
316
|
+
`--days 1`). The dbt seeds hold the state after the last day. `--days-format` picks what else is
|
|
317
|
+
written beside the project, outside `seeds/` so dbt does not load it: `batches` (the default),
|
|
318
|
+
`days/<table>/day_000.csv` whole and then one file per day with the rows inserted and updated that
|
|
319
|
+
day; `changelog`, `changelog/<table>.csv` with a `_day` and an `_op` (`insert` or `update`) column
|
|
320
|
+
on every row; or `final`, nothing more. A day depends only on the seed, `--as-of`, the day and the
|
|
321
|
+
model, so day *n* never changes when you generate more days or add an unrelated table. From Python:
|
|
322
|
+
|
|
323
|
+
```python
|
|
324
|
+
from model2data.generate.days import generate_days
|
|
325
|
+
from model2data.model import load
|
|
326
|
+
|
|
327
|
+
days = generate_days(load("examples/ecommerce_daily.model2data.yml"), 7, seed=42)
|
|
328
|
+
days[3].tables["orders"].inserted # rows day 3 added
|
|
329
|
+
days[3].tables["orders"].updated # rows day 3 changed, with their new values
|
|
330
|
+
days[3].tables["orders"].state # the table after day 3
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### Break the data on purpose
|
|
334
|
+
|
|
335
|
+
A dbt test you have never seen fail is a test you hope works. `--defects` puts deliberate,
|
|
336
|
+
counted defects in the generated data, so `dbt build` fails exactly the tests it should — for
|
|
337
|
+
teaching dbt, or for proving a project's tests fire:
|
|
338
|
+
|
|
339
|
+
```bash
|
|
340
|
+
model2data --file examples/ecommerce.model2data.yml --seed 42 --defects training
|
|
341
|
+
cd dbt_ecommerce && dbt build # fails unique, not_null, relationships, accepted_values once each
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
`training` breaks each kind of standard dbt test once; `messy` puts a small share of every defect
|
|
345
|
+
on every table; `clean` (the default) is the data as it always was, byte for byte; `none` also
|
|
346
|
+
ignores the defects a model's tables list, for the clean data of a training model. A table can
|
|
347
|
+
also list its own defects in the model (spec 0.3.0):
|
|
348
|
+
|
|
349
|
+
```yaml
|
|
350
|
+
orders:
|
|
351
|
+
defects:
|
|
352
|
+
- {type: duplicate_keys, count: 3} # fails unique
|
|
353
|
+
- {type: orphan_foreign_keys, column: customer_id, share: 0.02} # fails relationships
|
|
354
|
+
- {type: nulls, column: order_date, count: 5} # fails not_null
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
The types are `duplicate_keys`, `orphan_foreign_keys`, `nulls`, `invalid_values` (fails
|
|
358
|
+
`accepted_values`), `messy_text` (whitespace and casing to clean in staging), and, with `--days`,
|
|
359
|
+
`late_arriving` (rows an incremental model filtering on `updated_at > max(updated_at)` skips) and
|
|
360
|
+
`late_updates` (versions a `strategy: timestamp` snapshot misses) and `overlapping_history` (see
|
|
361
|
+
below). Every run with defects writes
|
|
362
|
+
`defects_report.json` (each defect, the rows it broke, the tests it breaks) and
|
|
363
|
+
`EXPECTED_FAILURES.md` into the project, so a test that does not fire is visible. Defects are applied
|
|
364
|
+
after the clean data, from their own random stream: the same seed gives the same bytes, and a
|
|
365
|
+
defect on one table moves nothing in another. From Python, `model2data.defects.planned_defects`
|
|
366
|
+
says what a run will break, `model2data.output.finish_run` applies it (and builds the histories)
|
|
367
|
+
after `generate_days`, and `write_defects_report` / `write_expected_failures` write the two files.
|
|
368
|
+
|
|
369
|
+
### A source that keeps its history
|
|
370
|
+
|
|
371
|
+
`history: true` in a table's `incremental` (spec 0.3.0) also writes `<table>_history`: every
|
|
372
|
+
version of every row over the generated days, with `valid_from`, `valid_to` (null for the current
|
|
373
|
+
version) and `is_current` — an SCD type 2 source to build snapshots and point-in-time joins
|
|
374
|
+
against. The project tests it with two package-free tests, one current row per key and no
|
|
375
|
+
overlapping versions; the `overlapping_history` defect breaks the second.
|
|
376
|
+
|
|
377
|
+
```yaml
|
|
378
|
+
orders:
|
|
379
|
+
incremental: {new_per_day: 20, update_rate: 0.1, updated_at: updated_at, history: true}
|
|
380
|
+
```
|
|
381
|
+
|
|
220
382
|
## Generated dbt project structure
|
|
221
383
|
|
|
222
384
|
The generated dbt project includes:
|
|
@@ -237,22 +399,23 @@ dbt_{project_name}/
|
|
|
237
399
|
├── data-tests/
|
|
238
400
|
│ └── unique_combination_stg_table1_col_a_col_b.sql # only for composite pk/unique keys
|
|
239
401
|
├── macros/
|
|
240
|
-
│
|
|
402
|
+
│ ├── generate_schema_name.sql
|
|
403
|
+
│ └── model2data_hint_tests.sql # only when the model's hints write tests
|
|
241
404
|
├── dbt_project.yml
|
|
242
405
|
├── profiles.yml # DuckDB or Postgres config, depending on --adapter
|
|
243
406
|
└── {project_name}_profile.duckdb # DuckDB adapter only
|
|
244
407
|
```
|
|
245
408
|
|
|
246
409
|
- **Seeds**: CSV files with generated synthetic data, plus `__seed_config.yml` — each seed's
|
|
247
|
-
`description:` (
|
|
410
|
+
`description:` (the table's `description` in the model) and the column-type overrides that keep
|
|
248
411
|
all-digit text columns (barcodes, zero-padded postcodes, ...) from being loaded as integers.
|
|
249
412
|
- **Staging Models**: Basic dbt models that `ref()` their seed. Using `ref()` rather than
|
|
250
413
|
declaring the seeds as dbt `sources` is what gives each model a real DAG edge to the seed
|
|
251
414
|
behind it, so one `dbt build` orders seeds before models on a fresh database.
|
|
252
415
|
- **Tests**: A YAML per staging model with column tests (`not_null`, `unique`, `relationships`,
|
|
253
|
-
and `accepted_values` for
|
|
254
|
-
`description:`
|
|
255
|
-
- **Composite key tests**: Composite primary/unique keys
|
|
416
|
+
and `accepted_values` for enum-typed columns), plus the hint tests above. A column's `description` in the model becomes
|
|
417
|
+
its `description:` field.
|
|
418
|
+
- **Composite key tests**: Composite primary/unique keys (`keys`, or several `pk: true` columns) get
|
|
256
419
|
a singular SQL test under `data-tests/`, dbt's configured `test-paths`.
|
|
257
420
|
- **Profiles**: Pre-configured for DuckDB (file-based) or Postgres (via env vars), with schema handling.
|
|
258
421
|
- **Unit tests** (opt-in via `--unit-tests`): `models/staging/ut_stg_<table>.yml` fixtures built
|
|
@@ -265,8 +428,8 @@ dbt_{project_name}/
|
|
|
265
428
|
|
|
266
429
|
If you want to go from a plain-English description of a data model straight to a running,
|
|
267
430
|
demo-ready dbt project, [LLMS.md](https://github.com/JB-Analytica/model2data/blob/main/LLMS.md) is written for an LLM/agent to read: it covers the
|
|
268
|
-
|
|
269
|
-
relationship syntaxes, self-references) and the exact command sequence to run. Point an
|
|
431
|
+
DBML feature set model2data understands (enums, notes, defaults, composite keys, both
|
|
432
|
+
relationship syntaxes, self-references), which it converts to a spec 0.2.0 model and the exact command sequence to run. Point an
|
|
270
433
|
LLM-backed coding assistant at it and describe your data model — it can author the DBML and run
|
|
271
434
|
model2data for you.
|
|
272
435
|
|
|
@@ -298,15 +461,15 @@ If you're pinned to an older dbt-core, use model2data 0.5.x, which supported dow
|
|
|
298
461
|
- Synthetic data generation is heuristic-based (typed generation, name-aware inference, enum/default awareness) and may not perfectly mimic real-world distributions or edge cases.
|
|
299
462
|
- DuckDB and Postgres are supported today; other databases require manual profile adjustments.
|
|
300
463
|
- No support for incremental models or advanced dbt features in generated projects.
|
|
301
|
-
- Composite foreign keys (across a bridge/join table) are generated as independent single-column FKs — each column's values are individually valid, but the *combination* isn't guaranteed to match a real parent composite key unless that key is separately enforced
|
|
302
|
-
-
|
|
464
|
+
- Composite foreign keys (across a bridge/join table) are generated as independent single-column FKs — each column's values are individually valid, but the *combination* isn't guaranteed to match a real parent composite key unless that key is separately enforced as a key of the child (`keys`).
|
|
465
|
+
- A model that doesn't conform to the spec — or DBML that can't be read, or converts to such a model — is refused with every issue and where it is, rather than generated from in part. A reference onto a column that is not a key is allowed with a warning: its values are drawn from the ones the parent column holds.
|
|
303
466
|
|
|
304
467
|
---
|
|
305
468
|
|
|
306
469
|
## Project status
|
|
307
470
|
|
|
308
471
|
As of `1.0.0`, model2data is considered **feature-complete for its intended use case**: turning a
|
|
309
|
-
|
|
472
|
+
data model into realistic synthetic data and a runnable dbt project, reliably. There's no active
|
|
310
473
|
roadmap of new capabilities planned — the focus from here is maintenance: bug fixes, keeping pace
|
|
311
474
|
with new dbt-core releases, and reviewing community contributions.
|
|
312
475
|
|
|
@@ -328,7 +491,7 @@ See [CONTRIBUTING.md](https://github.com/JB-Analytica/model2data/blob/main/CONTR
|
|
|
328
491
|
We welcome contributions!
|
|
329
492
|
|
|
330
493
|
- Open issues for bugs or feature requests.
|
|
331
|
-
- Submit PRs to add new
|
|
494
|
+
- Submit PRs to add new example models, custom data generators, or improvements.
|
|
332
495
|
- Ensure all new features include tests if possible.
|
|
333
496
|
|
|
334
497
|
See [CONTRIBUTING.md](https://github.com/JB-Analytica/model2data/blob/main/CONTRIBUTING.md) for detailed guidelines, and [DEVELOPMENT.md](https://github.com/JB-Analytica/model2data/blob/main/DEVELOPMENT.md) for the local dev setup and release process.
|