model2data 1.7.1__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 (148) hide show
  1. {model2data-1.7.1/model2data.egg-info → model2data-1.8.0}/PKG-INFO +170 -25
  2. {model2data-1.7.1 → model2data-1.8.0}/README.md +250 -64
  3. {model2data-1.7.1 → model2data-1.8.0}/README_PYPI.md +166 -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.1 → model2data-1.8.0}/model2data/dbt/tests.py +32 -6
  72. {model2data-1.7.1 → model2data-1.8.0}/model2data/generate/core.py +8 -4
  73. model2data-1.8.0/model2data/generate/days.py +812 -0
  74. {model2data-1.7.1 → model2data-1.8.0}/model2data/generate/faker.py +76 -22
  75. {model2data-1.7.1 → model2data-1.8.0}/model2data/generate/hints.py +14 -17
  76. model2data-1.8.0/model2data/generate/kinds.py +62 -0
  77. model2data-1.8.0/model2data/generate/relationships.py +112 -0
  78. {model2data-1.7.1 → model2data-1.8.0}/model2data/generate/timeline.py +5 -9
  79. model2data-1.8.0/model2data/model/__init__.py +64 -0
  80. model2data-1.8.0/model2data/model/_yaml.py +284 -0
  81. model2data-1.8.0/model2data/model/dbml.py +375 -0
  82. model2data-1.8.0/model2data/model/document.py +272 -0
  83. model2data-1.8.0/model2data/model/dump.py +233 -0
  84. model2data-1.8.0/model2data/model/engine.py +184 -0
  85. model2data-1.8.0/model2data/model/errors.py +60 -0
  86. model2data-1.8.0/model2data/model/reader.py +105 -0
  87. model2data-1.8.0/model2data/model/types.py +172 -0
  88. model2data-1.8.0/model2data/model/validate.py +878 -0
  89. model2data-1.8.0/model2data/parse/dbml.py +78 -0
  90. model2data-1.8.0/model2data/spec/PUBLISHING.md +71 -0
  91. model2data-1.8.0/model2data/spec/README.md +330 -0
  92. model2data-1.8.0/model2data/spec/examples/coffee_webshop.model2data.yml +126 -0
  93. model2data-1.8.0/model2data/spec/model.schema.json +529 -0
  94. {model2data-1.7.1 → model2data-1.8.0/model2data.egg-info}/PKG-INFO +170 -25
  95. model2data-1.8.0/model2data.egg-info/SOURCES.txt +142 -0
  96. {model2data-1.7.1 → model2data-1.8.0}/model2data.egg-info/requires.txt +2 -0
  97. {model2data-1.7.1 → model2data-1.8.0}/pyproject.toml +18 -1
  98. {model2data-1.7.1 → model2data-1.8.0}/tests/test_cli.py +9 -7
  99. model2data-1.8.0/tests/test_cli_model.py +248 -0
  100. {model2data-1.7.1 → model2data-1.8.0}/tests/test_coverage_gaps.py +10 -12
  101. model2data-1.8.0/tests/test_days.py +539 -0
  102. {model2data-1.7.1 → model2data-1.8.0}/tests/test_dbml_parser.py +107 -325
  103. {model2data-1.7.1 → model2data-1.8.0}/tests/test_dbml_parser_fuzz.py +34 -30
  104. {model2data-1.7.1 → model2data-1.8.0}/tests/test_dbt_integration.py +17 -0
  105. {model2data-1.7.1 → model2data-1.8.0}/tests/test_dbt_tests.py +38 -0
  106. model2data-1.8.0/tests/test_examples_model.py +61 -0
  107. {model2data-1.7.1 → model2data-1.8.0}/tests/test_generation.py +166 -0
  108. model2data-1.8.0/tests/test_hint_tests.py +410 -0
  109. model2data-1.8.0/tests/test_model.py +790 -0
  110. model2data-1.8.0/tests/test_model_dbml.py +362 -0
  111. model2data-1.8.0/tests/test_model_edges.py +225 -0
  112. model2data-1.8.0/tests/test_model_messages.py +250 -0
  113. model2data-1.8.0/tests/test_model_vocabulary.py +172 -0
  114. model2data-1.8.0/tests/test_reproducibility.py +116 -0
  115. {model2data-1.7.1 → model2data-1.8.0}/tests/test_shaping.py +23 -2
  116. model2data-1.8.0/tests/test_spec_alignment.py +126 -0
  117. model2data-1.8.0/tests/test_spec_schema.py +108 -0
  118. model2data-1.7.1/model2data/cli.py +0 -456
  119. model2data-1.7.1/model2data/generate/relationships.py +0 -52
  120. model2data-1.7.1/model2data/parse/dbml.py +0 -698
  121. model2data-1.7.1/model2data.egg-info/SOURCES.txt +0 -48
  122. {model2data-1.7.1 → model2data-1.8.0}/LICENSE +0 -0
  123. {model2data-1.7.1 → model2data-1.8.0}/model2data/__init__.py +0 -0
  124. {model2data-1.7.1 → model2data-1.8.0}/model2data/dbt/__init__.py +0 -0
  125. {model2data-1.7.1 → model2data-1.8.0}/model2data/dbt/project.py +0 -0
  126. {model2data-1.7.1 → model2data-1.8.0}/model2data/dbt/templates/dbt_project.yml.jinja +0 -0
  127. {model2data-1.7.1 → model2data-1.8.0}/model2data/dbt/templates/macros/generate_schema_name.sql +0 -0
  128. {model2data-1.7.1 → model2data-1.8.0}/model2data/dbt/templates/profiles.yml.jinja +0 -0
  129. {model2data-1.7.1 → model2data-1.8.0}/model2data/generate/__init__.py +0 -0
  130. {model2data-1.7.1 → model2data-1.8.0}/model2data/generate/options.py +0 -0
  131. {model2data-1.7.1 → model2data-1.8.0}/model2data/parse/__init__.py +0 -0
  132. {model2data-1.7.1 → model2data-1.8.0}/model2data/utils.py +0 -0
  133. {model2data-1.7.1 → model2data-1.8.0}/model2data.egg-info/dependency_links.txt +0 -0
  134. {model2data-1.7.1 → model2data-1.8.0}/model2data.egg-info/entry_points.txt +0 -0
  135. {model2data-1.7.1 → model2data-1.8.0}/model2data.egg-info/top_level.txt +0 -0
  136. {model2data-1.7.1 → model2data-1.8.0}/setup.cfg +0 -0
  137. {model2data-1.7.1 → model2data-1.8.0}/tests/test_as_of_anchor.py +0 -0
  138. {model2data-1.7.1 → model2data-1.8.0}/tests/test_column_time_hints.py +0 -0
  139. {model2data-1.7.1 → model2data-1.8.0}/tests/test_dbt_naming.py +0 -0
  140. {model2data-1.7.1 → model2data-1.8.0}/tests/test_dbt_project.py +0 -0
  141. {model2data-1.7.1 → model2data-1.8.0}/tests/test_distributions.py +0 -0
  142. {model2data-1.7.1 → model2data-1.8.0}/tests/test_faker_name_inference.py +0 -0
  143. {model2data-1.7.1 → model2data-1.8.0}/tests/test_lone_country.py +0 -0
  144. {model2data-1.7.1 → model2data-1.8.0}/tests/test_options.py +0 -0
  145. {model2data-1.7.1 → model2data-1.8.0}/tests/test_release_stress.py +0 -0
  146. {model2data-1.7.1 → model2data-1.8.0}/tests/test_row_identity.py +0 -0
  147. {model2data-1.7.1 → model2data-1.8.0}/tests/test_table_seeds.py +0 -0
  148. {model2data-1.7.1 → model2data-1.8.0}/tests/test_timeline.py +0 -0
@@ -1,10 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: model2data
3
- Version: 1.7.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
7
7
  Project-URL: Homepage, https://github.com/JB-Analytica/model2data
8
+ Project-URL: Studio, https://studio.jbanalytica.com
8
9
  Project-URL: Repository, https://github.com/JB-Analytica/model2data
9
10
  Project-URL: Issues, https://github.com/JB-Analytica/model2data/issues
10
11
  Project-URL: Changelog, https://github.com/JB-Analytica/model2data/blob/main/CHANGELOG.md
@@ -27,7 +28,9 @@ License-File: LICENSE
27
28
  Requires-Dist: dbt-core>=1.11
28
29
  Requires-Dist: dbt-duckdb>=1.11
29
30
  Requires-Dist: faker>=37.12.0
31
+ Requires-Dist: jsonschema>=4.18
30
32
  Requires-Dist: pandas>=2.3.3
33
+ Requires-Dist: pyparsing<4,>=3.1
31
34
  Requires-Dist: pyyaml>=6.0.3
32
35
  Requires-Dist: typer>=0.20.0
33
36
  Provides-Extra: postgres
@@ -51,18 +54,24 @@ Dynamic: license-file
51
54
 
52
55
  **Turn a data model into a running analytics stack in one command.**
53
56
 
54
- Give `model2data` a [DBML](https://dbml.dbdiagram.io/docs/) schema — hand-written or exported
55
- 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
56
60
  *and* a complete, runnable dbt project around it: seeds, staging models, tests, and a
57
61
  DuckDB or Postgres profile. No sample data to hunt down, no dbt boilerplate to hand-write, no
58
62
  production data to risk exposing.
59
63
 
60
64
  ```bash
61
65
  pip install model2data
62
- model2data --file examples/ecommerce.dbml --rows 200 --seed 42
66
+ model2data --file examples/ecommerce.model2data.yml --rows 200 --seed 42
63
67
  cd dbt_ecommerce && dbt build
64
68
  ```
65
69
 
70
+ > **Prefer a browser?** [model2data studio](https://studio.jbanalytica.com) is the same
71
+ > engine as a web app: write DBML, watch the entity diagram redraw as you type, see what every
72
+ > column will generate before you generate it, then export CSVs or a runnable dbt project.
73
+ > Nothing to install, free to start.
74
+
66
75
  That's a working analytics stack — real (synthetic) data, tested dbt models, queryable in
67
76
  DuckDB — from a schema file, in seconds:
68
77
 
@@ -106,15 +115,95 @@ access required.
106
115
 
107
116
  ## How it works
108
117
 
109
- 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.
110
121
  2. **Generate.** Produces synthetic values per column — typed generation for known SQL types
111
122
  (int, date, timestamp, ...), name-aware inference for everything else (`email`, `phone`,
112
123
  `city`, ...), foreign keys resolved against already-generated parent rows.
113
124
  3. **Scaffold.** Writes a complete dbt project around that data: CSV seeds, staging models that
114
125
  `ref()` those seeds, `not_null`/`unique`/`relationships` tests, `accepted_values` tests for
115
- DBML `Enum`-typed columns, singular SQL tests for composite primary/unique keys, table and
116
- column `description:` fields pulled from DBML notes, and a profile for DuckDB (zero-config,
117
- 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.
184
+
185
+ ---
186
+
187
+ ## model2data studio — the same engine, in the browser
188
+
189
+ [**model2data studio**](https://studio.jbanalytica.com) puts everything on this page behind a
190
+ web UI. It is built by JB Analytica on top of this library, it's the fastest way to try
191
+ model2data, and it's the better fit while a schema is still being designed:
192
+
193
+ - **Type DBML, see the diagram.** Syntax highlighting, autocomplete and live error checking; the
194
+ entity diagram redraws as you type. Click a column to trace what actually joins to it.
195
+ - **See what you'll get before you generate.** Every column shows an example of the value it will
196
+ produce, and columns nothing recognises are marked — so placeholder data is visible rather than
197
+ silent.
198
+ - **Generate and export.** Per-table row counts, then CSVs or a complete dbt project: the same
199
+ seeds, staging models, tests and DuckDB profile this CLI produces, reproducing the exact rows
200
+ you previewed.
201
+ - **Share the model.** A share link that also embeds as a chrome-free diagram in a Notion,
202
+ Confluence or wiki page.
203
+
204
+ Free to start, nothing to install: [studio.jbanalytica.com](https://studio.jbanalytica.com).
205
+ The CLI stays the right tool for scripting, CI and fixtures you commit; the studio is where a
206
+ model gets designed and shown.
118
207
 
119
208
  ---
120
209
 
@@ -129,12 +218,12 @@ pip install model2data
129
218
  ## Quick start
130
219
 
131
220
  We bundle several example schemas in `examples/` — this walkthrough uses the e-commerce one
132
- (`examples/ecommerce.dbml`: customers, products, orders, order items, and reviews).
221
+ (`examples/ecommerce.model2data.yml`: customers, products, orders, order items, and reviews).
133
222
 
134
223
  Generate a project with synthetic data:
135
224
 
136
225
  ```bash
137
- model2data --file examples/ecommerce.dbml --rows 200 --seed 42
226
+ model2data --file examples/ecommerce.model2data.yml --rows 200 --seed 42
138
227
  ```
139
228
 
140
229
  This creates a `dbt_ecommerce/` folder with your data and dbt setup.
@@ -147,7 +236,7 @@ re-rolls just that table, leaving every other table's seed CSV byte-identical. `
147
236
  the country every generated person and address comes from:
148
237
 
149
238
  ```bash
150
- model2data --file examples/ecommerce.dbml --rows 200 --seed 42 \
239
+ model2data --file examples/ecommerce.model2data.yml --rows 200 --seed 42 \
151
240
  --as-of 2026-01-31 --table-seed orders=7 --locale nl_BE
152
241
  ```
153
242
 
@@ -170,24 +259,77 @@ To target Postgres instead, install the extra and pass `--adapter postgres`:
170
259
 
171
260
  ```bash
172
261
  pip install "model2data[postgres]"
173
- model2data --file examples/ecommerce.dbml --rows 200 --seed 42 --adapter postgres
262
+ model2data --file examples/ecommerce.model2data.yml --rows 200 --seed 42 --adapter postgres
174
263
  ```
175
264
 
176
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.
177
266
 
178
- 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.
179
268
 
180
269
  Pass `--unit-tests` to also generate deterministic dbt unit test fixtures (`models/staging/ut_stg_<table>.yml`) from the actually-generated seed rows:
181
270
 
182
271
  ```bash
183
- model2data --file examples/ecommerce.dbml --rows 200 --seed 42 --unit-tests
272
+ model2data --file examples/ecommerce.model2data.yml --rows 200 --seed 42 --unit-tests
184
273
  ```
185
274
 
186
275
  This targets dbt-core's native unit testing feature, which works out of the box with the base
187
276
  install — see [dbt-core versions](#dbt-core-versions) below.
188
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
+
189
300
  ---
190
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
+
191
333
  ## Generated dbt project structure
192
334
 
193
335
  The generated dbt project includes:
@@ -208,22 +350,23 @@ dbt_{project_name}/
208
350
  ├── data-tests/
209
351
  │ └── unique_combination_stg_table1_col_a_col_b.sql # only for composite pk/unique keys
210
352
  ├── macros/
211
- │ └── generate_schema_name.sql
353
+ │ ├── generate_schema_name.sql
354
+ │ └── model2data_hint_tests.sql # only when the model's hints write tests
212
355
  ├── dbt_project.yml
213
356
  ├── profiles.yml # DuckDB or Postgres config, depending on --adapter
214
357
  └── {project_name}_profile.duckdb # DuckDB adapter only
215
358
  ```
216
359
 
217
360
  - **Seeds**: CSV files with generated synthetic data, plus `__seed_config.yml` — each seed's
218
- `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
219
362
  all-digit text columns (barcodes, zero-padded postcodes, ...) from being loaded as integers.
220
363
  - **Staging Models**: Basic dbt models that `ref()` their seed. Using `ref()` rather than
221
364
  declaring the seeds as dbt `sources` is what gives each model a real DAG edge to the seed
222
365
  behind it, so one `dbt build` orders seeds before models on a fresh database.
223
366
  - **Tests**: A YAML per staging model with column tests (`not_null`, `unique`, `relationships`,
224
- and `accepted_values` for DBML `Enum`-typed columns). Column `Note` text from the DBML becomes
225
- `description:` fields.
226
- - **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
227
370
  a singular SQL test under `data-tests/`, dbt's configured `test-paths`.
228
371
  - **Profiles**: Pre-configured for DuckDB (file-based) or Postgres (via env vars), with schema handling.
229
372
  - **Unit tests** (opt-in via `--unit-tests`): `models/staging/ut_stg_<table>.yml` fixtures built
@@ -236,8 +379,8 @@ dbt_{project_name}/
236
379
 
237
380
  If you want to go from a plain-English description of a data model straight to a running,
238
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
239
- full DBML feature set model2data understands (enums, notes, defaults, composite keys, both
240
- 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
241
384
  LLM-backed coding assistant at it and describe your data model — it can author the DBML and run
242
385
  model2data for you.
243
386
 
@@ -269,15 +412,15 @@ If you're pinned to an older dbt-core, use model2data 0.5.x, which supported dow
269
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.
270
413
  - DuckDB and Postgres are supported today; other databases require manual profile adjustments.
271
414
  - No support for incremental models or advanced dbt features in generated projects.
272
- - 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 { }`.
273
- - 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.
274
417
 
275
418
  ---
276
419
 
277
420
  ## Project status
278
421
 
279
422
  As of `1.0.0`, model2data is considered **feature-complete for its intended use case**: turning a
280
- 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
281
424
  roadmap of new capabilities planned — the focus from here is maintenance: bug fixes, keeping pace
282
425
  with new dbt-core releases, and reviewing community contributions.
283
426
 
@@ -299,7 +442,7 @@ See [CONTRIBUTING.md](https://github.com/JB-Analytica/model2data/blob/main/CONTR
299
442
  We welcome contributions!
300
443
 
301
444
  - Open issues for bugs or feature requests.
302
- - 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.
303
446
  - Ensure all new features include tests if possible.
304
447
 
305
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.
@@ -323,4 +466,6 @@ MIT License. See LICENSE for details.
323
466
  <br>
324
467
  Built and maintained by <a href="https://www.jbanalytica.com"><strong>JB Analytica</strong></a> —
325
468
  Data & Analytics Engineering · Data Platform Architecture · Modern BI.
469
+ <br>
470
+ Try <a href="https://studio.jbanalytica.com"><strong>model2data studio</strong></a> — model2data in the browser, nothing to install.
326
471
  </p>