model2data 1.7.3__tar.gz → 1.8.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.8.0}/PKG-INFO +139 -25
- {model2data-1.7.3 → model2data-1.8.0}/README.md +219 -64
- {model2data-1.7.3 → model2data-1.8.0}/README_PYPI.md +136 -24
- model2data-1.8.0/model2data/_vendor/__init__.py +0 -0
- model2data-1.8.0/model2data/_vendor/pydbml/LICENSE +21 -0
- model2data-1.8.0/model2data/_vendor/pydbml/VENDORED.md +51 -0
- model2data-1.8.0/model2data/_vendor/pydbml/__init__.py +3 -0
- model2data-1.8.0/model2data/_vendor/pydbml/_classes/__init__.py +0 -0
- model2data-1.8.0/model2data/_vendor/pydbml/_classes/base.py +70 -0
- model2data-1.8.0/model2data/_vendor/pydbml/_classes/column.py +94 -0
- model2data-1.8.0/model2data/_vendor/pydbml/_classes/enum.py +89 -0
- model2data-1.8.0/model2data/_vendor/pydbml/_classes/expression.py +22 -0
- model2data-1.8.0/model2data/_vendor/pydbml/_classes/index.py +80 -0
- model2data-1.8.0/model2data/_vendor/pydbml/_classes/note.py +23 -0
- model2data-1.8.0/model2data/_vendor/pydbml/_classes/project.py +34 -0
- model2data-1.8.0/model2data/_vendor/pydbml/_classes/reference.py +120 -0
- model2data-1.8.0/model2data/_vendor/pydbml/_classes/sticky_note.py +24 -0
- model2data-1.8.0/model2data/_vendor/pydbml/_classes/table.py +158 -0
- model2data-1.8.0/model2data/_vendor/pydbml/_classes/table_group.py +49 -0
- model2data-1.8.0/model2data/_vendor/pydbml/classes/__init__.py +25 -0
- model2data-1.8.0/model2data/_vendor/pydbml/constants.py +4 -0
- model2data-1.8.0/model2data/_vendor/pydbml/database.py +207 -0
- model2data-1.8.0/model2data/_vendor/pydbml/definitions/__init__.py +0 -0
- model2data-1.8.0/model2data/_vendor/pydbml/definitions/column.py +158 -0
- model2data-1.8.0/model2data/_vendor/pydbml/definitions/common.py +39 -0
- model2data-1.8.0/model2data/_vendor/pydbml/definitions/enum.py +91 -0
- model2data-1.8.0/model2data/_vendor/pydbml/definitions/generic.py +43 -0
- model2data-1.8.0/model2data/_vendor/pydbml/definitions/index.py +110 -0
- model2data-1.8.0/model2data/_vendor/pydbml/definitions/project.py +53 -0
- model2data-1.8.0/model2data/_vendor/pydbml/definitions/records.py +80 -0
- model2data-1.8.0/model2data/_vendor/pydbml/definitions/reference.py +200 -0
- model2data-1.8.0/model2data/_vendor/pydbml/definitions/sticky_note.py +21 -0
- model2data-1.8.0/model2data/_vendor/pydbml/definitions/table.py +152 -0
- model2data-1.8.0/model2data/_vendor/pydbml/definitions/table_group.py +60 -0
- model2data-1.8.0/model2data/_vendor/pydbml/definitions/table_partial.py +48 -0
- model2data-1.8.0/model2data/_vendor/pydbml/exceptions.py +34 -0
- model2data-1.8.0/model2data/_vendor/pydbml/parser/__init__.py +1 -0
- model2data-1.8.0/model2data/_vendor/pydbml/parser/blueprints.py +372 -0
- model2data-1.8.0/model2data/_vendor/pydbml/parser/parser.py +360 -0
- model2data-1.8.0/model2data/_vendor/pydbml/renderer/__init__.py +0 -0
- model2data-1.8.0/model2data/_vendor/pydbml/renderer/base.py +38 -0
- model2data-1.8.0/model2data/_vendor/pydbml/renderer/dbml/__init__.py +0 -0
- model2data-1.8.0/model2data/_vendor/pydbml/renderer/dbml/default/__init__.py +11 -0
- model2data-1.8.0/model2data/_vendor/pydbml/renderer/dbml/default/column.py +55 -0
- model2data-1.8.0/model2data/_vendor/pydbml/renderer/dbml/default/enum.py +25 -0
- model2data-1.8.0/model2data/_vendor/pydbml/renderer/dbml/default/expression.py +7 -0
- model2data-1.8.0/model2data/_vendor/pydbml/renderer/dbml/default/index.py +49 -0
- model2data-1.8.0/model2data/_vendor/pydbml/renderer/dbml/default/note.py +14 -0
- model2data-1.8.0/model2data/_vendor/pydbml/renderer/dbml/default/project.py +29 -0
- model2data-1.8.0/model2data/_vendor/pydbml/renderer/dbml/default/reference.py +68 -0
- model2data-1.8.0/model2data/_vendor/pydbml/renderer/dbml/default/renderer.py +19 -0
- model2data-1.8.0/model2data/_vendor/pydbml/renderer/dbml/default/sticky_note.py +15 -0
- model2data-1.8.0/model2data/_vendor/pydbml/renderer/dbml/default/table.py +58 -0
- model2data-1.8.0/model2data/_vendor/pydbml/renderer/dbml/default/table_group.py +23 -0
- model2data-1.8.0/model2data/_vendor/pydbml/renderer/dbml/default/utils.py +31 -0
- model2data-1.8.0/model2data/_vendor/pydbml/renderer/sql/__init__.py +0 -0
- model2data-1.8.0/model2data/_vendor/pydbml/renderer/sql/default/__init__.py +8 -0
- model2data-1.8.0/model2data/_vendor/pydbml/renderer/sql/default/column.py +45 -0
- model2data-1.8.0/model2data/_vendor/pydbml/renderer/sql/default/enum.py +34 -0
- model2data-1.8.0/model2data/_vendor/pydbml/renderer/sql/default/expression.py +7 -0
- model2data-1.8.0/model2data/_vendor/pydbml/renderer/sql/default/index.py +65 -0
- model2data-1.8.0/model2data/_vendor/pydbml/renderer/sql/default/note.py +43 -0
- model2data-1.8.0/model2data/_vendor/pydbml/renderer/sql/default/reference.py +82 -0
- model2data-1.8.0/model2data/_vendor/pydbml/renderer/sql/default/renderer.py +24 -0
- model2data-1.8.0/model2data/_vendor/pydbml/renderer/sql/default/table.py +97 -0
- model2data-1.8.0/model2data/_vendor/pydbml/renderer/sql/default/utils.py +37 -0
- model2data-1.8.0/model2data/_vendor/pydbml/tools.py +54 -0
- model2data-1.8.0/model2data/cli.py +767 -0
- model2data-1.8.0/model2data/dbt/hint_tests.py +180 -0
- model2data-1.8.0/model2data/dbt/templates/hint_macros/model2data_hint_tests.sql +64 -0
- {model2data-1.7.3 → model2data-1.8.0}/model2data/dbt/tests.py +32 -6
- model2data-1.8.0/model2data/generate/days.py +812 -0
- {model2data-1.7.3 → model2data-1.8.0}/model2data/generate/faker.py +76 -22
- {model2data-1.7.3 → model2data-1.8.0}/model2data/generate/hints.py +14 -17
- model2data-1.8.0/model2data/generate/kinds.py +62 -0
- {model2data-1.7.3 → model2data-1.8.0}/model2data/generate/relationships.py +25 -8
- {model2data-1.7.3 → model2data-1.8.0}/model2data/generate/timeline.py +5 -9
- model2data-1.8.0/model2data/model/__init__.py +64 -0
- model2data-1.8.0/model2data/model/_yaml.py +284 -0
- model2data-1.8.0/model2data/model/dbml.py +375 -0
- model2data-1.8.0/model2data/model/document.py +272 -0
- model2data-1.8.0/model2data/model/dump.py +233 -0
- model2data-1.8.0/model2data/model/engine.py +184 -0
- model2data-1.8.0/model2data/model/errors.py +60 -0
- model2data-1.8.0/model2data/model/reader.py +105 -0
- model2data-1.8.0/model2data/model/types.py +172 -0
- model2data-1.8.0/model2data/model/validate.py +878 -0
- model2data-1.8.0/model2data/parse/dbml.py +78 -0
- model2data-1.8.0/model2data/spec/PUBLISHING.md +71 -0
- model2data-1.8.0/model2data/spec/README.md +330 -0
- model2data-1.8.0/model2data/spec/examples/coffee_webshop.model2data.yml +126 -0
- model2data-1.8.0/model2data/spec/model.schema.json +529 -0
- {model2data-1.7.3 → model2data-1.8.0/model2data.egg-info}/PKG-INFO +139 -25
- model2data-1.8.0/model2data.egg-info/SOURCES.txt +142 -0
- {model2data-1.7.3 → model2data-1.8.0}/model2data.egg-info/requires.txt +2 -0
- {model2data-1.7.3 → model2data-1.8.0}/pyproject.toml +17 -1
- {model2data-1.7.3 → model2data-1.8.0}/tests/test_cli.py +9 -7
- model2data-1.8.0/tests/test_cli_model.py +248 -0
- {model2data-1.7.3 → model2data-1.8.0}/tests/test_coverage_gaps.py +10 -12
- model2data-1.8.0/tests/test_days.py +539 -0
- {model2data-1.7.3 → model2data-1.8.0}/tests/test_dbml_parser.py +107 -325
- {model2data-1.7.3 → model2data-1.8.0}/tests/test_dbml_parser_fuzz.py +34 -30
- {model2data-1.7.3 → model2data-1.8.0}/tests/test_dbt_integration.py +17 -0
- model2data-1.8.0/tests/test_examples_model.py +61 -0
- {model2data-1.7.3 → model2data-1.8.0}/tests/test_generation.py +6 -5
- model2data-1.8.0/tests/test_hint_tests.py +410 -0
- model2data-1.8.0/tests/test_model.py +790 -0
- model2data-1.8.0/tests/test_model_dbml.py +362 -0
- model2data-1.8.0/tests/test_model_edges.py +225 -0
- model2data-1.8.0/tests/test_model_messages.py +250 -0
- model2data-1.8.0/tests/test_model_vocabulary.py +172 -0
- model2data-1.8.0/tests/test_reproducibility.py +116 -0
- {model2data-1.7.3 → model2data-1.8.0}/tests/test_shaping.py +23 -2
- model2data-1.8.0/tests/test_spec_alignment.py +126 -0
- model2data-1.8.0/tests/test_spec_schema.py +108 -0
- model2data-1.7.3/model2data/cli.py +0 -456
- 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.8.0}/LICENSE +0 -0
- {model2data-1.7.3 → model2data-1.8.0}/model2data/__init__.py +0 -0
- {model2data-1.7.3 → model2data-1.8.0}/model2data/dbt/__init__.py +0 -0
- {model2data-1.7.3 → model2data-1.8.0}/model2data/dbt/project.py +0 -0
- {model2data-1.7.3 → model2data-1.8.0}/model2data/dbt/templates/dbt_project.yml.jinja +0 -0
- {model2data-1.7.3 → model2data-1.8.0}/model2data/dbt/templates/macros/generate_schema_name.sql +0 -0
- {model2data-1.7.3 → model2data-1.8.0}/model2data/dbt/templates/profiles.yml.jinja +0 -0
- {model2data-1.7.3 → model2data-1.8.0}/model2data/generate/__init__.py +0 -0
- {model2data-1.7.3 → model2data-1.8.0}/model2data/generate/core.py +0 -0
- {model2data-1.7.3 → model2data-1.8.0}/model2data/generate/options.py +0 -0
- {model2data-1.7.3 → model2data-1.8.0}/model2data/parse/__init__.py +0 -0
- {model2data-1.7.3 → model2data-1.8.0}/model2data/utils.py +0 -0
- {model2data-1.7.3 → model2data-1.8.0}/model2data.egg-info/dependency_links.txt +0 -0
- {model2data-1.7.3 → model2data-1.8.0}/model2data.egg-info/entry_points.txt +0 -0
- {model2data-1.7.3 → model2data-1.8.0}/model2data.egg-info/top_level.txt +0 -0
- {model2data-1.7.3 → model2data-1.8.0}/setup.cfg +0 -0
- {model2data-1.7.3 → model2data-1.8.0}/tests/test_as_of_anchor.py +0 -0
- {model2data-1.7.3 → model2data-1.8.0}/tests/test_column_time_hints.py +0 -0
- {model2data-1.7.3 → model2data-1.8.0}/tests/test_dbt_naming.py +0 -0
- {model2data-1.7.3 → model2data-1.8.0}/tests/test_dbt_project.py +0 -0
- {model2data-1.7.3 → model2data-1.8.0}/tests/test_dbt_tests.py +0 -0
- {model2data-1.7.3 → model2data-1.8.0}/tests/test_distributions.py +0 -0
- {model2data-1.7.3 → model2data-1.8.0}/tests/test_faker_name_inference.py +0 -0
- {model2data-1.7.3 → model2data-1.8.0}/tests/test_lone_country.py +0 -0
- {model2data-1.7.3 → model2data-1.8.0}/tests/test_options.py +0 -0
- {model2data-1.7.3 → model2data-1.8.0}/tests/test_release_stress.py +0 -0
- {model2data-1.7.3 → model2data-1.8.0}/tests/test_row_identity.py +0 -0
- {model2data-1.7.3 → model2data-1.8.0}/tests/test_table_seeds.py +0 -0
- {model2data-1.7.3 → model2data-1.8.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.8.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.2.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,77 @@ 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
|
+
|
|
220
333
|
## Generated dbt project structure
|
|
221
334
|
|
|
222
335
|
The generated dbt project includes:
|
|
@@ -237,22 +350,23 @@ dbt_{project_name}/
|
|
|
237
350
|
├── data-tests/
|
|
238
351
|
│ └── unique_combination_stg_table1_col_a_col_b.sql # only for composite pk/unique keys
|
|
239
352
|
├── macros/
|
|
240
|
-
│
|
|
353
|
+
│ ├── generate_schema_name.sql
|
|
354
|
+
│ └── model2data_hint_tests.sql # only when the model's hints write tests
|
|
241
355
|
├── dbt_project.yml
|
|
242
356
|
├── profiles.yml # DuckDB or Postgres config, depending on --adapter
|
|
243
357
|
└── {project_name}_profile.duckdb # DuckDB adapter only
|
|
244
358
|
```
|
|
245
359
|
|
|
246
360
|
- **Seeds**: CSV files with generated synthetic data, plus `__seed_config.yml` — each seed's
|
|
247
|
-
`description:` (
|
|
361
|
+
`description:` (the table's `description` in the model) and the column-type overrides that keep
|
|
248
362
|
all-digit text columns (barcodes, zero-padded postcodes, ...) from being loaded as integers.
|
|
249
363
|
- **Staging Models**: Basic dbt models that `ref()` their seed. Using `ref()` rather than
|
|
250
364
|
declaring the seeds as dbt `sources` is what gives each model a real DAG edge to the seed
|
|
251
365
|
behind it, so one `dbt build` orders seeds before models on a fresh database.
|
|
252
366
|
- **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
|
|
367
|
+
and `accepted_values` for enum-typed columns), plus the hint tests above. A column's `description` in the model becomes
|
|
368
|
+
its `description:` field.
|
|
369
|
+
- **Composite key tests**: Composite primary/unique keys (`keys`, or several `pk: true` columns) get
|
|
256
370
|
a singular SQL test under `data-tests/`, dbt's configured `test-paths`.
|
|
257
371
|
- **Profiles**: Pre-configured for DuckDB (file-based) or Postgres (via env vars), with schema handling.
|
|
258
372
|
- **Unit tests** (opt-in via `--unit-tests`): `models/staging/ut_stg_<table>.yml` fixtures built
|
|
@@ -265,8 +379,8 @@ dbt_{project_name}/
|
|
|
265
379
|
|
|
266
380
|
If you want to go from a plain-English description of a data model straight to a running,
|
|
267
381
|
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
|
|
382
|
+
DBML feature set model2data understands (enums, notes, defaults, composite keys, both
|
|
383
|
+
relationship syntaxes, self-references), which it converts to a spec 0.2.0 model and the exact command sequence to run. Point an
|
|
270
384
|
LLM-backed coding assistant at it and describe your data model — it can author the DBML and run
|
|
271
385
|
model2data for you.
|
|
272
386
|
|
|
@@ -298,15 +412,15 @@ If you're pinned to an older dbt-core, use model2data 0.5.x, which supported dow
|
|
|
298
412
|
- 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
413
|
- DuckDB and Postgres are supported today; other databases require manual profile adjustments.
|
|
300
414
|
- 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
|
-
-
|
|
415
|
+
- 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`).
|
|
416
|
+
- 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
417
|
|
|
304
418
|
---
|
|
305
419
|
|
|
306
420
|
## Project status
|
|
307
421
|
|
|
308
422
|
As of `1.0.0`, model2data is considered **feature-complete for its intended use case**: turning a
|
|
309
|
-
|
|
423
|
+
data model into realistic synthetic data and a runnable dbt project, reliably. There's no active
|
|
310
424
|
roadmap of new capabilities planned — the focus from here is maintenance: bug fixes, keeping pace
|
|
311
425
|
with new dbt-core releases, and reviewing community contributions.
|
|
312
426
|
|
|
@@ -328,7 +442,7 @@ See [CONTRIBUTING.md](https://github.com/JB-Analytica/model2data/blob/main/CONTR
|
|
|
328
442
|
We welcome contributions!
|
|
329
443
|
|
|
330
444
|
- Open issues for bugs or feature requests.
|
|
331
|
-
- Submit PRs to add new
|
|
445
|
+
- Submit PRs to add new example models, custom data generators, or improvements.
|
|
332
446
|
- Ensure all new features include tests if possible.
|
|
333
447
|
|
|
334
448
|
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.
|