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.
Files changed (147) hide show
  1. {model2data-1.7.3/model2data.egg-info → model2data-1.8.0}/PKG-INFO +139 -25
  2. {model2data-1.7.3 → model2data-1.8.0}/README.md +219 -64
  3. {model2data-1.7.3 → model2data-1.8.0}/README_PYPI.md +136 -24
  4. model2data-1.8.0/model2data/_vendor/__init__.py +0 -0
  5. model2data-1.8.0/model2data/_vendor/pydbml/LICENSE +21 -0
  6. model2data-1.8.0/model2data/_vendor/pydbml/VENDORED.md +51 -0
  7. model2data-1.8.0/model2data/_vendor/pydbml/__init__.py +3 -0
  8. model2data-1.8.0/model2data/_vendor/pydbml/_classes/__init__.py +0 -0
  9. model2data-1.8.0/model2data/_vendor/pydbml/_classes/base.py +70 -0
  10. model2data-1.8.0/model2data/_vendor/pydbml/_classes/column.py +94 -0
  11. model2data-1.8.0/model2data/_vendor/pydbml/_classes/enum.py +89 -0
  12. model2data-1.8.0/model2data/_vendor/pydbml/_classes/expression.py +22 -0
  13. model2data-1.8.0/model2data/_vendor/pydbml/_classes/index.py +80 -0
  14. model2data-1.8.0/model2data/_vendor/pydbml/_classes/note.py +23 -0
  15. model2data-1.8.0/model2data/_vendor/pydbml/_classes/project.py +34 -0
  16. model2data-1.8.0/model2data/_vendor/pydbml/_classes/reference.py +120 -0
  17. model2data-1.8.0/model2data/_vendor/pydbml/_classes/sticky_note.py +24 -0
  18. model2data-1.8.0/model2data/_vendor/pydbml/_classes/table.py +158 -0
  19. model2data-1.8.0/model2data/_vendor/pydbml/_classes/table_group.py +49 -0
  20. model2data-1.8.0/model2data/_vendor/pydbml/classes/__init__.py +25 -0
  21. model2data-1.8.0/model2data/_vendor/pydbml/constants.py +4 -0
  22. model2data-1.8.0/model2data/_vendor/pydbml/database.py +207 -0
  23. model2data-1.8.0/model2data/_vendor/pydbml/definitions/__init__.py +0 -0
  24. model2data-1.8.0/model2data/_vendor/pydbml/definitions/column.py +158 -0
  25. model2data-1.8.0/model2data/_vendor/pydbml/definitions/common.py +39 -0
  26. model2data-1.8.0/model2data/_vendor/pydbml/definitions/enum.py +91 -0
  27. model2data-1.8.0/model2data/_vendor/pydbml/definitions/generic.py +43 -0
  28. model2data-1.8.0/model2data/_vendor/pydbml/definitions/index.py +110 -0
  29. model2data-1.8.0/model2data/_vendor/pydbml/definitions/project.py +53 -0
  30. model2data-1.8.0/model2data/_vendor/pydbml/definitions/records.py +80 -0
  31. model2data-1.8.0/model2data/_vendor/pydbml/definitions/reference.py +200 -0
  32. model2data-1.8.0/model2data/_vendor/pydbml/definitions/sticky_note.py +21 -0
  33. model2data-1.8.0/model2data/_vendor/pydbml/definitions/table.py +152 -0
  34. model2data-1.8.0/model2data/_vendor/pydbml/definitions/table_group.py +60 -0
  35. model2data-1.8.0/model2data/_vendor/pydbml/definitions/table_partial.py +48 -0
  36. model2data-1.8.0/model2data/_vendor/pydbml/exceptions.py +34 -0
  37. model2data-1.8.0/model2data/_vendor/pydbml/parser/__init__.py +1 -0
  38. model2data-1.8.0/model2data/_vendor/pydbml/parser/blueprints.py +372 -0
  39. model2data-1.8.0/model2data/_vendor/pydbml/parser/parser.py +360 -0
  40. model2data-1.8.0/model2data/_vendor/pydbml/renderer/__init__.py +0 -0
  41. model2data-1.8.0/model2data/_vendor/pydbml/renderer/base.py +38 -0
  42. model2data-1.8.0/model2data/_vendor/pydbml/renderer/dbml/__init__.py +0 -0
  43. model2data-1.8.0/model2data/_vendor/pydbml/renderer/dbml/default/__init__.py +11 -0
  44. model2data-1.8.0/model2data/_vendor/pydbml/renderer/dbml/default/column.py +55 -0
  45. model2data-1.8.0/model2data/_vendor/pydbml/renderer/dbml/default/enum.py +25 -0
  46. model2data-1.8.0/model2data/_vendor/pydbml/renderer/dbml/default/expression.py +7 -0
  47. model2data-1.8.0/model2data/_vendor/pydbml/renderer/dbml/default/index.py +49 -0
  48. model2data-1.8.0/model2data/_vendor/pydbml/renderer/dbml/default/note.py +14 -0
  49. model2data-1.8.0/model2data/_vendor/pydbml/renderer/dbml/default/project.py +29 -0
  50. model2data-1.8.0/model2data/_vendor/pydbml/renderer/dbml/default/reference.py +68 -0
  51. model2data-1.8.0/model2data/_vendor/pydbml/renderer/dbml/default/renderer.py +19 -0
  52. model2data-1.8.0/model2data/_vendor/pydbml/renderer/dbml/default/sticky_note.py +15 -0
  53. model2data-1.8.0/model2data/_vendor/pydbml/renderer/dbml/default/table.py +58 -0
  54. model2data-1.8.0/model2data/_vendor/pydbml/renderer/dbml/default/table_group.py +23 -0
  55. model2data-1.8.0/model2data/_vendor/pydbml/renderer/dbml/default/utils.py +31 -0
  56. model2data-1.8.0/model2data/_vendor/pydbml/renderer/sql/__init__.py +0 -0
  57. model2data-1.8.0/model2data/_vendor/pydbml/renderer/sql/default/__init__.py +8 -0
  58. model2data-1.8.0/model2data/_vendor/pydbml/renderer/sql/default/column.py +45 -0
  59. model2data-1.8.0/model2data/_vendor/pydbml/renderer/sql/default/enum.py +34 -0
  60. model2data-1.8.0/model2data/_vendor/pydbml/renderer/sql/default/expression.py +7 -0
  61. model2data-1.8.0/model2data/_vendor/pydbml/renderer/sql/default/index.py +65 -0
  62. model2data-1.8.0/model2data/_vendor/pydbml/renderer/sql/default/note.py +43 -0
  63. model2data-1.8.0/model2data/_vendor/pydbml/renderer/sql/default/reference.py +82 -0
  64. model2data-1.8.0/model2data/_vendor/pydbml/renderer/sql/default/renderer.py +24 -0
  65. model2data-1.8.0/model2data/_vendor/pydbml/renderer/sql/default/table.py +97 -0
  66. model2data-1.8.0/model2data/_vendor/pydbml/renderer/sql/default/utils.py +37 -0
  67. model2data-1.8.0/model2data/_vendor/pydbml/tools.py +54 -0
  68. model2data-1.8.0/model2data/cli.py +767 -0
  69. model2data-1.8.0/model2data/dbt/hint_tests.py +180 -0
  70. model2data-1.8.0/model2data/dbt/templates/hint_macros/model2data_hint_tests.sql +64 -0
  71. {model2data-1.7.3 → model2data-1.8.0}/model2data/dbt/tests.py +32 -6
  72. model2data-1.8.0/model2data/generate/days.py +812 -0
  73. {model2data-1.7.3 → model2data-1.8.0}/model2data/generate/faker.py +76 -22
  74. {model2data-1.7.3 → model2data-1.8.0}/model2data/generate/hints.py +14 -17
  75. model2data-1.8.0/model2data/generate/kinds.py +62 -0
  76. {model2data-1.7.3 → model2data-1.8.0}/model2data/generate/relationships.py +25 -8
  77. {model2data-1.7.3 → model2data-1.8.0}/model2data/generate/timeline.py +5 -9
  78. model2data-1.8.0/model2data/model/__init__.py +64 -0
  79. model2data-1.8.0/model2data/model/_yaml.py +284 -0
  80. model2data-1.8.0/model2data/model/dbml.py +375 -0
  81. model2data-1.8.0/model2data/model/document.py +272 -0
  82. model2data-1.8.0/model2data/model/dump.py +233 -0
  83. model2data-1.8.0/model2data/model/engine.py +184 -0
  84. model2data-1.8.0/model2data/model/errors.py +60 -0
  85. model2data-1.8.0/model2data/model/reader.py +105 -0
  86. model2data-1.8.0/model2data/model/types.py +172 -0
  87. model2data-1.8.0/model2data/model/validate.py +878 -0
  88. model2data-1.8.0/model2data/parse/dbml.py +78 -0
  89. model2data-1.8.0/model2data/spec/PUBLISHING.md +71 -0
  90. model2data-1.8.0/model2data/spec/README.md +330 -0
  91. model2data-1.8.0/model2data/spec/examples/coffee_webshop.model2data.yml +126 -0
  92. model2data-1.8.0/model2data/spec/model.schema.json +529 -0
  93. {model2data-1.7.3 → model2data-1.8.0/model2data.egg-info}/PKG-INFO +139 -25
  94. model2data-1.8.0/model2data.egg-info/SOURCES.txt +142 -0
  95. {model2data-1.7.3 → model2data-1.8.0}/model2data.egg-info/requires.txt +2 -0
  96. {model2data-1.7.3 → model2data-1.8.0}/pyproject.toml +17 -1
  97. {model2data-1.7.3 → model2data-1.8.0}/tests/test_cli.py +9 -7
  98. model2data-1.8.0/tests/test_cli_model.py +248 -0
  99. {model2data-1.7.3 → model2data-1.8.0}/tests/test_coverage_gaps.py +10 -12
  100. model2data-1.8.0/tests/test_days.py +539 -0
  101. {model2data-1.7.3 → model2data-1.8.0}/tests/test_dbml_parser.py +107 -325
  102. {model2data-1.7.3 → model2data-1.8.0}/tests/test_dbml_parser_fuzz.py +34 -30
  103. {model2data-1.7.3 → model2data-1.8.0}/tests/test_dbt_integration.py +17 -0
  104. model2data-1.8.0/tests/test_examples_model.py +61 -0
  105. {model2data-1.7.3 → model2data-1.8.0}/tests/test_generation.py +6 -5
  106. model2data-1.8.0/tests/test_hint_tests.py +410 -0
  107. model2data-1.8.0/tests/test_model.py +790 -0
  108. model2data-1.8.0/tests/test_model_dbml.py +362 -0
  109. model2data-1.8.0/tests/test_model_edges.py +225 -0
  110. model2data-1.8.0/tests/test_model_messages.py +250 -0
  111. model2data-1.8.0/tests/test_model_vocabulary.py +172 -0
  112. model2data-1.8.0/tests/test_reproducibility.py +116 -0
  113. {model2data-1.7.3 → model2data-1.8.0}/tests/test_shaping.py +23 -2
  114. model2data-1.8.0/tests/test_spec_alignment.py +126 -0
  115. model2data-1.8.0/tests/test_spec_schema.py +108 -0
  116. model2data-1.7.3/model2data/cli.py +0 -456
  117. model2data-1.7.3/model2data/parse/dbml.py +0 -698
  118. model2data-1.7.3/model2data.egg-info/SOURCES.txt +0 -48
  119. {model2data-1.7.3 → model2data-1.8.0}/LICENSE +0 -0
  120. {model2data-1.7.3 → model2data-1.8.0}/model2data/__init__.py +0 -0
  121. {model2data-1.7.3 → model2data-1.8.0}/model2data/dbt/__init__.py +0 -0
  122. {model2data-1.7.3 → model2data-1.8.0}/model2data/dbt/project.py +0 -0
  123. {model2data-1.7.3 → model2data-1.8.0}/model2data/dbt/templates/dbt_project.yml.jinja +0 -0
  124. {model2data-1.7.3 → model2data-1.8.0}/model2data/dbt/templates/macros/generate_schema_name.sql +0 -0
  125. {model2data-1.7.3 → model2data-1.8.0}/model2data/dbt/templates/profiles.yml.jinja +0 -0
  126. {model2data-1.7.3 → model2data-1.8.0}/model2data/generate/__init__.py +0 -0
  127. {model2data-1.7.3 → model2data-1.8.0}/model2data/generate/core.py +0 -0
  128. {model2data-1.7.3 → model2data-1.8.0}/model2data/generate/options.py +0 -0
  129. {model2data-1.7.3 → model2data-1.8.0}/model2data/parse/__init__.py +0 -0
  130. {model2data-1.7.3 → model2data-1.8.0}/model2data/utils.py +0 -0
  131. {model2data-1.7.3 → model2data-1.8.0}/model2data.egg-info/dependency_links.txt +0 -0
  132. {model2data-1.7.3 → model2data-1.8.0}/model2data.egg-info/entry_points.txt +0 -0
  133. {model2data-1.7.3 → model2data-1.8.0}/model2data.egg-info/top_level.txt +0 -0
  134. {model2data-1.7.3 → model2data-1.8.0}/setup.cfg +0 -0
  135. {model2data-1.7.3 → model2data-1.8.0}/tests/test_as_of_anchor.py +0 -0
  136. {model2data-1.7.3 → model2data-1.8.0}/tests/test_column_time_hints.py +0 -0
  137. {model2data-1.7.3 → model2data-1.8.0}/tests/test_dbt_naming.py +0 -0
  138. {model2data-1.7.3 → model2data-1.8.0}/tests/test_dbt_project.py +0 -0
  139. {model2data-1.7.3 → model2data-1.8.0}/tests/test_dbt_tests.py +0 -0
  140. {model2data-1.7.3 → model2data-1.8.0}/tests/test_distributions.py +0 -0
  141. {model2data-1.7.3 → model2data-1.8.0}/tests/test_faker_name_inference.py +0 -0
  142. {model2data-1.7.3 → model2data-1.8.0}/tests/test_lone_country.py +0 -0
  143. {model2data-1.7.3 → model2data-1.8.0}/tests/test_options.py +0 -0
  144. {model2data-1.7.3 → model2data-1.8.0}/tests/test_release_stress.py +0 -0
  145. {model2data-1.7.3 → model2data-1.8.0}/tests/test_row_identity.py +0 -0
  146. {model2data-1.7.3 → model2data-1.8.0}/tests/test_table_seeds.py +0 -0
  147. {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.7.3
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/) schema — hand-written or exported
56
- from an existing database — and it generates realistic, relationship-preserving synthetic data
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.dbml --rows 200 --seed 42
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. **Parse.** Reads tables, columns, types, and `Ref` relationships from a DBML file.
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
- DBML `Enum`-typed columns, singular SQL tests for composite primary/unique keys, table and
122
- column `description:` fields pulled from DBML notes, and a profile for DuckDB (zero-config,
123
- file-based) or Postgres.
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.dbml`: customers, products, orders, order items, and reviews).
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.dbml --rows 200 --seed 42
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.dbml --rows 200 --seed 42 \
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.dbml --rows 200 --seed 42 --adapter postgres
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 DBML, and any columns that fell back to generic placeholder text because neither their type nor name could be matched.
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.dbml --rows 200 --seed 42 --unit-tests
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
- │ └── generate_schema_name.sql
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:` (from the table's DBML `Note`) and the column-type overrides that keep
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 DBML `Enum`-typed columns). Column `Note` text from the DBML becomes
254
- `description:` fields.
255
- - **Composite key tests**: Composite primary/unique keys declared in an `indexes { }` block get
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
- full DBML feature set model2data understands (enums, notes, defaults, composite keys, both
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 via `indexes { }`.
302
- - Any DBML the parser can't fully make sense of (a malformed line, a ref pointing at an unknown table, an unrecognized column definition) is reported as a warning in the CLI's summary rather than silently dropped — check that summary after generating from a schema you didn't author yourself.
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
- DBML schema into realistic synthetic data and a runnable dbt project, reliably. There's no active
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 DBML examples, custom data generators, or improvements.
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.