model2data 0.2.3__tar.gz → 0.4.1__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 (42) hide show
  1. model2data-0.4.1/PKG-INFO +279 -0
  2. model2data-0.4.1/README.md +254 -0
  3. {model2data-0.2.3 → model2data-0.4.1}/model2data/cli.py +52 -3
  4. {model2data-0.2.3 → model2data-0.4.1}/model2data/dbt/project.py +3 -3
  5. model2data-0.4.1/model2data/dbt/templates/profiles.yml.jinja +18 -0
  6. model2data-0.4.1/model2data/dbt/tests.py +215 -0
  7. {model2data-0.2.3 → model2data-0.4.1}/model2data/generate/core.py +41 -0
  8. model2data-0.4.1/model2data/generate/faker.py +247 -0
  9. model2data-0.4.1/model2data/parse/dbml.py +393 -0
  10. model2data-0.4.1/model2data.egg-info/PKG-INFO +279 -0
  11. {model2data-0.2.3 → model2data-0.4.1}/model2data.egg-info/SOURCES.txt +1 -0
  12. {model2data-0.2.3 → model2data-0.4.1}/model2data.egg-info/requires.txt +3 -1
  13. {model2data-0.2.3 → model2data-0.4.1}/pyproject.toml +5 -8
  14. {model2data-0.2.3 → model2data-0.4.1}/tests/test_cli.py +148 -0
  15. {model2data-0.2.3 → model2data-0.4.1}/tests/test_dbml_parser.py +391 -7
  16. {model2data-0.2.3 → model2data-0.4.1}/tests/test_dbt_project.py +55 -0
  17. model2data-0.4.1/tests/test_dbt_tests.py +267 -0
  18. model2data-0.4.1/tests/test_faker_name_inference.py +191 -0
  19. {model2data-0.2.3 → model2data-0.4.1}/tests/test_generation.py +73 -0
  20. model2data-0.2.3/PKG-INFO +0 -161
  21. model2data-0.2.3/README.md +0 -137
  22. model2data-0.2.3/model2data/dbt/templates/profiles.yml.jinja +0 -7
  23. model2data-0.2.3/model2data/dbt/tests.py +0 -92
  24. model2data-0.2.3/model2data/generate/faker.py +0 -122
  25. model2data-0.2.3/model2data/parse/dbml.py +0 -218
  26. model2data-0.2.3/model2data.egg-info/PKG-INFO +0 -161
  27. model2data-0.2.3/tests/test_dbt_tests.py +0 -63
  28. {model2data-0.2.3 → model2data-0.4.1}/LICENSE +0 -0
  29. {model2data-0.2.3 → model2data-0.4.1}/model2data/__init__.py +0 -0
  30. {model2data-0.2.3 → model2data-0.4.1}/model2data/dbt/__init__.py +0 -0
  31. {model2data-0.2.3 → model2data-0.4.1}/model2data/dbt/templates/dbt_project.yml.jinja +0 -0
  32. {model2data-0.2.3 → model2data-0.4.1}/model2data/dbt/templates/macros/generate_schema_name.sql +0 -0
  33. {model2data-0.2.3 → model2data-0.4.1}/model2data/generate/__init__.py +0 -0
  34. {model2data-0.2.3 → model2data-0.4.1}/model2data/generate/relationships.py +0 -0
  35. {model2data-0.2.3 → model2data-0.4.1}/model2data/parse/__init__.py +0 -0
  36. {model2data-0.2.3 → model2data-0.4.1}/model2data/utils.py +0 -0
  37. {model2data-0.2.3 → model2data-0.4.1}/model2data.egg-info/dependency_links.txt +0 -0
  38. {model2data-0.2.3 → model2data-0.4.1}/model2data.egg-info/entry_points.txt +0 -0
  39. {model2data-0.2.3 → model2data-0.4.1}/model2data.egg-info/top_level.txt +0 -0
  40. {model2data-0.2.3 → model2data-0.4.1}/setup.cfg +0 -0
  41. {model2data-0.2.3 → model2data-0.4.1}/tests/test_coverage_gaps.py +0 -0
  42. {model2data-0.2.3 → model2data-0.4.1}/tests/test_dbt_naming.py +0 -0
@@ -0,0 +1,279 @@
1
+ Metadata-Version: 2.4
2
+ Name: model2data
3
+ Version: 0.4.1
4
+ Summary: Generate analytics-ready datasets from DBML models
5
+ Requires-Python: >=3.10
6
+ Description-Content-Type: text/markdown
7
+ License-File: LICENSE
8
+ Requires-Dist: dbt-core>=1.5.0
9
+ Requires-Dist: dbt-duckdb>=1.5.0
10
+ Requires-Dist: faker>=37.12.0
11
+ Requires-Dist: pandas>=2.3.3
12
+ Requires-Dist: pyyaml>=6.0.3
13
+ Requires-Dist: typer>=0.20.0
14
+ Provides-Extra: postgres
15
+ Requires-Dist: dbt-postgres>=1.5.0; extra == "postgres"
16
+ Provides-Extra: dev
17
+ Requires-Dist: pytest; extra == "dev"
18
+ Requires-Dist: pytest-cov; extra == "dev"
19
+ Requires-Dist: pre-commit; extra == "dev"
20
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
21
+ Requires-Dist: ty>=0.0.4; extra == "dev"
22
+ Requires-Dist: types-pyyaml; extra == "dev"
23
+ Requires-Dist: poethepoet>=0.38.0; extra == "dev"
24
+ Dynamic: license-file
25
+
26
+ # model2data
27
+
28
+ [![PyPI](https://img.shields.io/pypi/v/model2data)](https://pypi.org/project/model2data/)
29
+ [![CI](https://github.com/JB-Analytica/model2data/actions/workflows/ci.yml/badge.svg)](https://github.com/JB-Analytica/model2data/actions/workflows/ci.yml)
30
+ [![codecov](https://codecov.io/gh/JB-Analytica/model2data/branch/main/graph/badge.svg)](https://codecov.io/gh/JB-Analytica/model2data)
31
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
32
+
33
+ **Turn a data model into a running analytics stack in one command.**
34
+
35
+ Give `model2data` a [DBML](https://dbml.dbdiagram.io/docs/) schema — hand-written or exported
36
+ from an existing database — and it generates realistic, relationship-preserving synthetic data
37
+ *and* a complete, runnable dbt project around it: seeds, staging models, sources, tests, and a
38
+ DuckDB or Postgres profile. No sample data to hunt down, no dbt boilerplate to hand-write, no
39
+ production data to risk exposing.
40
+
41
+ ```bash
42
+ pip install model2data
43
+ model2data --file examples/hackernews.dbml --rows 200 --seed 42
44
+ cd dbt_hackernews && dbt seed && dbt run
45
+ ```
46
+
47
+ That's a working analytics stack — real (synthetic) data, tested dbt models, queryable in
48
+ DuckDB — from a schema file, in seconds:
49
+
50
+ ![model2data generating a project and running it with dbt](assets/demo.gif)
51
+
52
+ ---
53
+
54
+ ## Why this exists
55
+
56
+ Analytics engineers hit the same wall constantly: you need realistic data to build or test a
57
+ pipeline, but production data is off-limits (privacy, access, scale), and hand-rolling mock CSVs
58
+ is tedious and doesn't scale past two tables. `model2data` closes that gap — from a schema
59
+ definition to a seeded, tested dbt project you can actually run, with no database or production
60
+ access required.
61
+
62
+ - **Privacy-safe.** Nothing but a schema definition goes in; nothing but synthetic data comes out.
63
+ - **Realistic, not random.** Column names are matched against ~35 common patterns — `email`,
64
+ `first_name`, `city`, `phone`, `company`, ... — so a column called `email` gets real-looking
65
+ emails, not `Lorem ipsum` text.
66
+ - **Relationship-preserving.** Foreign keys resolve to real parent rows; tables are generated in
67
+ dependency order.
68
+ - **Deterministic.** Pass `--seed` and the same schema always produces the same data — safe to
69
+ commit fixtures, safe to diff across CI runs.
70
+ - **A real dbt project, not just CSVs.** Seeds, staging models, sources, schema tests, and a
71
+ ready-to-use profile — the thing you'd otherwise spend an afternoon scaffolding by hand.
72
+
73
+ ## Who is model2data for?
74
+
75
+ - **Analytics engineers** — generate realistic datasets and a working dbt project without
76
+ waiting on production access.
77
+ - **Data engineers** — produce deterministic test data from an existing schema for pipeline and
78
+ migration testing.
79
+ - **Software & data teams** — prototype integrations and analytics workflows without exposing
80
+ production data.
81
+ - **Consultants & architects** — spin up realistic environments for demos, workshops, and
82
+ architecture validation in minutes, not hours.
83
+
84
+ ## How it works
85
+
86
+ ```mermaid
87
+ flowchart LR
88
+ subgraph input [" "]
89
+ A["📄 DBML schema"]
90
+ end
91
+
92
+ subgraph m2d ["model2data"]
93
+ direction LR
94
+ B["Parse\ntables, columns,\nrelationships"] --> C["Generate\nFaker + name-aware\ninference, FK-aware"]
95
+ C --> D["Scaffold\nseeds · staging models\ntests · profile"]
96
+ end
97
+
98
+ subgraph output ["Generated dbt project"]
99
+ direction TB
100
+ E["seeds/*.csv"]
101
+ F["models/staging/*.sql + *.yml"]
102
+ G["profiles.yml\n(DuckDB or Postgres)"]
103
+ end
104
+
105
+ A --> B
106
+ D --> E
107
+ D --> F
108
+ D --> G
109
+ E & F & G --> H["dbt seed && dbt run"]
110
+ H --> I[("Analytics-ready\ndataset")]
111
+
112
+ classDef m2dStyle fill:#0A3866,stroke:#2196F0,color:#F6F8FB
113
+ classDef outStyle fill:#182333,stroke:#A8C9EE,color:#F6F8FB
114
+ classDef endStyle fill:#FA9306,stroke:#FA9306,color:#182333
115
+ class B,C,D m2dStyle
116
+ class E,F,G outStyle
117
+ class H,I endStyle
118
+ ```
119
+
120
+ 1. **Parse.** Reads tables, columns, types, and `Ref` relationships from a DBML file.
121
+ 2. **Generate.** Produces synthetic values per column — typed generation for known SQL types
122
+ (int, date, timestamp, ...), name-aware inference for everything else (`email`, `phone`,
123
+ `city`, ...), foreign keys resolved against already-generated parent rows.
124
+ 3. **Scaffold.** Writes a complete dbt project around that data: CSV seeds, staging models with
125
+ `source`/`not_null`/`unique`/`relationships` tests, and a profile for DuckDB (zero-config,
126
+ file-based) or Postgres.
127
+
128
+ ---
129
+
130
+ ## Installation
131
+
132
+ ```bash
133
+ pip install model2data
134
+ ```
135
+
136
+ ---
137
+
138
+ ## Quick start
139
+
140
+ We provide an example Hacker News dataset in `examples/hackernews.dbml`.
141
+
142
+ Generate a project with synthetic data:
143
+
144
+ ```bash
145
+ model2data --file examples/hackernews.dbml --rows 200 --seed 42
146
+ ```
147
+
148
+ This creates a `dbt_hackernews/` folder with your data and dbt setup.
149
+
150
+ Run dbt to load and transform the data:
151
+
152
+ ```bash
153
+ cd dbt_hackernews
154
+ dbt deps
155
+ dbt seed
156
+ dbt run
157
+ ```
158
+
159
+ Your analytics-ready dataset is now in DuckDB!
160
+
161
+ To target Postgres instead, install the extra and pass `--adapter postgres`:
162
+
163
+ ```bash
164
+ pip install "model2data[postgres]"
165
+ model2data --file examples/hackernews.dbml --rows 200 --seed 42 --adapter postgres
166
+ ```
167
+
168
+ 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.
169
+
170
+ 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.
171
+
172
+ Pass `--unit-tests` to also generate deterministic dbt unit test fixtures (`tests/unit/test_stg_<table>.yml`) from the actually-generated seed rows:
173
+
174
+ ```bash
175
+ model2data --file examples/hackernews.dbml --rows 200 --seed 42 --unit-tests
176
+ ```
177
+
178
+ This targets dbt-core's native unit testing feature, which requires **dbt-core >= 1.8**. Since this project only requires `dbt-core>=1.5.0` by default, unit test generation is opt-in — leave the flag off if your dbt-core version is older.
179
+
180
+ ---
181
+
182
+ ## Generated dbt project structure
183
+
184
+ The generated dbt project includes:
185
+
186
+ ```
187
+ dbt_{project_name}/
188
+ ├── seeds/
189
+ │ └── {project_name}/
190
+ │ ├── table1.csv
191
+ │ └── table2.csv
192
+ ├── models/
193
+ │ └── {project_name}/
194
+ │ └── staging/
195
+ │ ├── __sources.yml
196
+ │ ├── stg_table1.sql
197
+ │ ├── stg_table1.yml
198
+ │ └── ...
199
+ ├── macros/
200
+ │ └── generate_schema_name.sql
201
+ ├── dbt_project.yml
202
+ ├── profiles.yml # DuckDB or Postgres config, depending on --adapter
203
+ └── {project_name}.duckdb # DuckDB adapter only
204
+ ```
205
+
206
+ - **Seeds**: CSV files with generated synthetic data.
207
+ - **Staging Models**: Basic dbt models that load from seeds.
208
+ - **Sources & Tests**: YAML configs defining sources and column tests (`not_null`, `unique`,
209
+ `relationships`, and `accepted_values` for DBML `Enum`-typed columns). Table and column
210
+ `Note` text from the DBML becomes `description:` fields. Composite primary/unique keys
211
+ declared in an `indexes { }` block get a singular SQL test under `tests/`.
212
+ - **Profiles**: Pre-configured for DuckDB (file-based) or Postgres (via env vars), with schema handling.
213
+ - **Unit tests** (opt-in via `--unit-tests`): `tests/unit/test_stg_<table>.yml` fixtures built
214
+ from real generated rows. Requires dbt-core >= 1.8.
215
+
216
+ ---
217
+
218
+ ## Design decisions / non-goals
219
+
220
+ - **DuckDB Default**: Chosen for its zero-config, file-based nature, making it easy to get started without database setup. Postgres is supported via `--adapter postgres`; other adapters can be configured manually.
221
+ - **dbt Integration**: Leverages dbt's transformation capabilities for a familiar workflow in analytics engineering.
222
+ - **Synthetic Data**: Uses deterministic generation for reproducibility; not intended for production use or as a replacement for real data.
223
+ - **Non-goals**: This is not a data migration tool, ETL pipeline, or real-time data generator. It focuses on static, synthetic datasets for testing and prototyping.
224
+
225
+ ---
226
+
227
+ ## Limitations
228
+
229
+ - Supports basic DBML features; complex constraints or advanced SQL types may not be fully handled.
230
+ - Synthetic data generation is heuristic-based and may not perfectly mimic real-world distributions or edge cases.
231
+ - DuckDB and Postgres are supported today; other databases require manual profile adjustments.
232
+ - No support for incremental models or advanced dbt features in generated projects.
233
+
234
+ ---
235
+
236
+ ## Roadmap
237
+
238
+ - [x] Postgres adapter support (`--adapter postgres`)
239
+ - [x] Name-aware synthetic data (email, name, address, phone, etc. instead of generic text)
240
+ - [x] Post-run generation summary (tables, rows, relationships, unmapped columns)
241
+ - [x] DBML `Enum` support, real table/column notes as dbt descriptions, composite keys from
242
+ `indexes { }`, `default:` values, and opt-in deterministic dbt unit test scaffolding
243
+ (`--unit-tests`)
244
+ - [ ] Additional database adapters (e.g., Snowflake, BigQuery).
245
+ - [ ] Enhanced data type handling and custom generators.
246
+ - [ ] Improved schema exploration and developer tooling.
247
+
248
+ ---
249
+
250
+ ## Contributing
251
+
252
+ We welcome contributions!
253
+
254
+ - Open issues for bugs or feature requests.
255
+ - Submit PRs to add new DBML examples, custom data generators, or improvements.
256
+ - Ensure all new features include tests if possible.
257
+
258
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines, and [DEVELOPMENT.md](DEVELOPMENT.md) for the local dev setup and release process.
259
+
260
+ ## Code of Conduct
261
+
262
+ Please read our [Code of Conduct](CODE_OF_CONDUCT.md) to understand our community standards.
263
+
264
+ ---
265
+
266
+ ## License
267
+
268
+ MIT License. See LICENSE for details.
269
+
270
+ ---
271
+
272
+ <p align="center">
273
+ <a href="https://www.jbanalytica.com">
274
+ <img src="assets/jba-icon-dark-bg.svg" alt="JB Analytica" height="40">
275
+ </a>
276
+ <br>
277
+ Built and maintained by <a href="https://www.jbanalytica.com"><strong>JB Analytica</strong></a> —
278
+ Data & Analytics Engineering · Data Platform Architecture · Modern BI.
279
+ </p>
@@ -0,0 +1,254 @@
1
+ # model2data
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/model2data)](https://pypi.org/project/model2data/)
4
+ [![CI](https://github.com/JB-Analytica/model2data/actions/workflows/ci.yml/badge.svg)](https://github.com/JB-Analytica/model2data/actions/workflows/ci.yml)
5
+ [![codecov](https://codecov.io/gh/JB-Analytica/model2data/branch/main/graph/badge.svg)](https://codecov.io/gh/JB-Analytica/model2data)
6
+ [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
7
+
8
+ **Turn a data model into a running analytics stack in one command.**
9
+
10
+ Give `model2data` a [DBML](https://dbml.dbdiagram.io/docs/) schema — hand-written or exported
11
+ from an existing database — and it generates realistic, relationship-preserving synthetic data
12
+ *and* a complete, runnable dbt project around it: seeds, staging models, sources, tests, and a
13
+ DuckDB or Postgres profile. No sample data to hunt down, no dbt boilerplate to hand-write, no
14
+ production data to risk exposing.
15
+
16
+ ```bash
17
+ pip install model2data
18
+ model2data --file examples/hackernews.dbml --rows 200 --seed 42
19
+ cd dbt_hackernews && dbt seed && dbt run
20
+ ```
21
+
22
+ That's a working analytics stack — real (synthetic) data, tested dbt models, queryable in
23
+ DuckDB — from a schema file, in seconds:
24
+
25
+ ![model2data generating a project and running it with dbt](assets/demo.gif)
26
+
27
+ ---
28
+
29
+ ## Why this exists
30
+
31
+ Analytics engineers hit the same wall constantly: you need realistic data to build or test a
32
+ pipeline, but production data is off-limits (privacy, access, scale), and hand-rolling mock CSVs
33
+ is tedious and doesn't scale past two tables. `model2data` closes that gap — from a schema
34
+ definition to a seeded, tested dbt project you can actually run, with no database or production
35
+ access required.
36
+
37
+ - **Privacy-safe.** Nothing but a schema definition goes in; nothing but synthetic data comes out.
38
+ - **Realistic, not random.** Column names are matched against ~35 common patterns — `email`,
39
+ `first_name`, `city`, `phone`, `company`, ... — so a column called `email` gets real-looking
40
+ emails, not `Lorem ipsum` text.
41
+ - **Relationship-preserving.** Foreign keys resolve to real parent rows; tables are generated in
42
+ dependency order.
43
+ - **Deterministic.** Pass `--seed` and the same schema always produces the same data — safe to
44
+ commit fixtures, safe to diff across CI runs.
45
+ - **A real dbt project, not just CSVs.** Seeds, staging models, sources, schema tests, and a
46
+ ready-to-use profile — the thing you'd otherwise spend an afternoon scaffolding by hand.
47
+
48
+ ## Who is model2data for?
49
+
50
+ - **Analytics engineers** — generate realistic datasets and a working dbt project without
51
+ waiting on production access.
52
+ - **Data engineers** — produce deterministic test data from an existing schema for pipeline and
53
+ migration testing.
54
+ - **Software & data teams** — prototype integrations and analytics workflows without exposing
55
+ production data.
56
+ - **Consultants & architects** — spin up realistic environments for demos, workshops, and
57
+ architecture validation in minutes, not hours.
58
+
59
+ ## How it works
60
+
61
+ ```mermaid
62
+ flowchart LR
63
+ subgraph input [" "]
64
+ A["📄 DBML schema"]
65
+ end
66
+
67
+ subgraph m2d ["model2data"]
68
+ direction LR
69
+ B["Parse\ntables, columns,\nrelationships"] --> C["Generate\nFaker + name-aware\ninference, FK-aware"]
70
+ C --> D["Scaffold\nseeds · staging models\ntests · profile"]
71
+ end
72
+
73
+ subgraph output ["Generated dbt project"]
74
+ direction TB
75
+ E["seeds/*.csv"]
76
+ F["models/staging/*.sql + *.yml"]
77
+ G["profiles.yml\n(DuckDB or Postgres)"]
78
+ end
79
+
80
+ A --> B
81
+ D --> E
82
+ D --> F
83
+ D --> G
84
+ E & F & G --> H["dbt seed && dbt run"]
85
+ H --> I[("Analytics-ready\ndataset")]
86
+
87
+ classDef m2dStyle fill:#0A3866,stroke:#2196F0,color:#F6F8FB
88
+ classDef outStyle fill:#182333,stroke:#A8C9EE,color:#F6F8FB
89
+ classDef endStyle fill:#FA9306,stroke:#FA9306,color:#182333
90
+ class B,C,D m2dStyle
91
+ class E,F,G outStyle
92
+ class H,I endStyle
93
+ ```
94
+
95
+ 1. **Parse.** Reads tables, columns, types, and `Ref` relationships from a DBML file.
96
+ 2. **Generate.** Produces synthetic values per column — typed generation for known SQL types
97
+ (int, date, timestamp, ...), name-aware inference for everything else (`email`, `phone`,
98
+ `city`, ...), foreign keys resolved against already-generated parent rows.
99
+ 3. **Scaffold.** Writes a complete dbt project around that data: CSV seeds, staging models with
100
+ `source`/`not_null`/`unique`/`relationships` tests, and a profile for DuckDB (zero-config,
101
+ file-based) or Postgres.
102
+
103
+ ---
104
+
105
+ ## Installation
106
+
107
+ ```bash
108
+ pip install model2data
109
+ ```
110
+
111
+ ---
112
+
113
+ ## Quick start
114
+
115
+ We provide an example Hacker News dataset in `examples/hackernews.dbml`.
116
+
117
+ Generate a project with synthetic data:
118
+
119
+ ```bash
120
+ model2data --file examples/hackernews.dbml --rows 200 --seed 42
121
+ ```
122
+
123
+ This creates a `dbt_hackernews/` folder with your data and dbt setup.
124
+
125
+ Run dbt to load and transform the data:
126
+
127
+ ```bash
128
+ cd dbt_hackernews
129
+ dbt deps
130
+ dbt seed
131
+ dbt run
132
+ ```
133
+
134
+ Your analytics-ready dataset is now in DuckDB!
135
+
136
+ To target Postgres instead, install the extra and pass `--adapter postgres`:
137
+
138
+ ```bash
139
+ pip install "model2data[postgres]"
140
+ model2data --file examples/hackernews.dbml --rows 200 --seed 42 --adapter postgres
141
+ ```
142
+
143
+ 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.
144
+
145
+ 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.
146
+
147
+ Pass `--unit-tests` to also generate deterministic dbt unit test fixtures (`tests/unit/test_stg_<table>.yml`) from the actually-generated seed rows:
148
+
149
+ ```bash
150
+ model2data --file examples/hackernews.dbml --rows 200 --seed 42 --unit-tests
151
+ ```
152
+
153
+ This targets dbt-core's native unit testing feature, which requires **dbt-core >= 1.8**. Since this project only requires `dbt-core>=1.5.0` by default, unit test generation is opt-in — leave the flag off if your dbt-core version is older.
154
+
155
+ ---
156
+
157
+ ## Generated dbt project structure
158
+
159
+ The generated dbt project includes:
160
+
161
+ ```
162
+ dbt_{project_name}/
163
+ ├── seeds/
164
+ │ └── {project_name}/
165
+ │ ├── table1.csv
166
+ │ └── table2.csv
167
+ ├── models/
168
+ │ └── {project_name}/
169
+ │ └── staging/
170
+ │ ├── __sources.yml
171
+ │ ├── stg_table1.sql
172
+ │ ├── stg_table1.yml
173
+ │ └── ...
174
+ ├── macros/
175
+ │ └── generate_schema_name.sql
176
+ ├── dbt_project.yml
177
+ ├── profiles.yml # DuckDB or Postgres config, depending on --adapter
178
+ └── {project_name}.duckdb # DuckDB adapter only
179
+ ```
180
+
181
+ - **Seeds**: CSV files with generated synthetic data.
182
+ - **Staging Models**: Basic dbt models that load from seeds.
183
+ - **Sources & Tests**: YAML configs defining sources and column tests (`not_null`, `unique`,
184
+ `relationships`, and `accepted_values` for DBML `Enum`-typed columns). Table and column
185
+ `Note` text from the DBML becomes `description:` fields. Composite primary/unique keys
186
+ declared in an `indexes { }` block get a singular SQL test under `tests/`.
187
+ - **Profiles**: Pre-configured for DuckDB (file-based) or Postgres (via env vars), with schema handling.
188
+ - **Unit tests** (opt-in via `--unit-tests`): `tests/unit/test_stg_<table>.yml` fixtures built
189
+ from real generated rows. Requires dbt-core >= 1.8.
190
+
191
+ ---
192
+
193
+ ## Design decisions / non-goals
194
+
195
+ - **DuckDB Default**: Chosen for its zero-config, file-based nature, making it easy to get started without database setup. Postgres is supported via `--adapter postgres`; other adapters can be configured manually.
196
+ - **dbt Integration**: Leverages dbt's transformation capabilities for a familiar workflow in analytics engineering.
197
+ - **Synthetic Data**: Uses deterministic generation for reproducibility; not intended for production use or as a replacement for real data.
198
+ - **Non-goals**: This is not a data migration tool, ETL pipeline, or real-time data generator. It focuses on static, synthetic datasets for testing and prototyping.
199
+
200
+ ---
201
+
202
+ ## Limitations
203
+
204
+ - Supports basic DBML features; complex constraints or advanced SQL types may not be fully handled.
205
+ - Synthetic data generation is heuristic-based and may not perfectly mimic real-world distributions or edge cases.
206
+ - DuckDB and Postgres are supported today; other databases require manual profile adjustments.
207
+ - No support for incremental models or advanced dbt features in generated projects.
208
+
209
+ ---
210
+
211
+ ## Roadmap
212
+
213
+ - [x] Postgres adapter support (`--adapter postgres`)
214
+ - [x] Name-aware synthetic data (email, name, address, phone, etc. instead of generic text)
215
+ - [x] Post-run generation summary (tables, rows, relationships, unmapped columns)
216
+ - [x] DBML `Enum` support, real table/column notes as dbt descriptions, composite keys from
217
+ `indexes { }`, `default:` values, and opt-in deterministic dbt unit test scaffolding
218
+ (`--unit-tests`)
219
+ - [ ] Additional database adapters (e.g., Snowflake, BigQuery).
220
+ - [ ] Enhanced data type handling and custom generators.
221
+ - [ ] Improved schema exploration and developer tooling.
222
+
223
+ ---
224
+
225
+ ## Contributing
226
+
227
+ We welcome contributions!
228
+
229
+ - Open issues for bugs or feature requests.
230
+ - Submit PRs to add new DBML examples, custom data generators, or improvements.
231
+ - Ensure all new features include tests if possible.
232
+
233
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines, and [DEVELOPMENT.md](DEVELOPMENT.md) for the local dev setup and release process.
234
+
235
+ ## Code of Conduct
236
+
237
+ Please read our [Code of Conduct](CODE_OF_CONDUCT.md) to understand our community standards.
238
+
239
+ ---
240
+
241
+ ## License
242
+
243
+ MIT License. See LICENSE for details.
244
+
245
+ ---
246
+
247
+ <p align="center">
248
+ <a href="https://www.jbanalytica.com">
249
+ <img src="assets/jba-icon-dark-bg.svg" alt="JB Analytica" height="40">
250
+ </a>
251
+ <br>
252
+ Built and maintained by <a href="https://www.jbanalytica.com"><strong>JB Analytica</strong></a> —
253
+ Data & Analytics Engineering · Data Platform Architecture · Modern BI.
254
+ </p>
@@ -11,11 +11,14 @@ from model2data.dbt.project import (
11
11
  create_project_scaffold,
12
12
  create_staging_models,
13
13
  )
14
- from model2data.dbt.tests import generate_dbt_yml
14
+ from model2data.dbt.tests import generate_dbt_yml, generate_unit_tests
15
15
  from model2data.generate.core import generate_data_from_dbml
16
+ from model2data.generate.faker import get_unmapped_columns, reset_stats
16
17
  from model2data.parse.dbml import parse_dbml
17
18
  from model2data.utils import normalize_identifier
18
19
 
20
+ SUPPORTED_ADAPTERS = ("duckdb", "postgres")
21
+
19
22
  app = typer.Typer(
20
23
  help=(
21
24
  "model2data: Generate analytics-ready datasets from DBML models.\n\n"
@@ -67,11 +70,35 @@ def main(
67
70
  "--force",
68
71
  help="Overwrite the destination directory if it already exists.",
69
72
  ),
73
+ adapter: str = typer.Option(
74
+ "duckdb",
75
+ "--adapter",
76
+ "-a",
77
+ help=f"dbt warehouse adapter to target. One of: {', '.join(SUPPORTED_ADAPTERS)}.",
78
+ ),
79
+ unit_tests: bool = typer.Option(
80
+ False,
81
+ "--unit-tests",
82
+ help=(
83
+ "Also generate deterministic dbt unit test fixtures (tests/unit/) from the "
84
+ "generated seed rows. Requires dbt-core >= 1.8 to run."
85
+ ),
86
+ ),
70
87
  ):
71
88
  """
72
89
  Generate synthetic data and a dbt project from a DBML model.
73
90
  """
74
91
 
92
+ # -------------------------
93
+ # Validate adapter
94
+ # -------------------------
95
+ adapter = adapter.lower()
96
+ if adapter not in SUPPORTED_ADAPTERS:
97
+ typer.echo(
98
+ f"❌ Unsupported adapter '{adapter}'. Choose one of: {', '.join(SUPPORTED_ADAPTERS)}."
99
+ )
100
+ raise typer.Exit(1)
101
+
75
102
  # -------------------------
76
103
  # Deterministic seed
77
104
  # -------------------------
@@ -108,6 +135,7 @@ def main(
108
135
  # Generate synthetic data
109
136
  # -------------------------
110
137
  typer.echo("🧮 Generating synthetic datasets from DBML definitions...")
138
+ reset_stats()
111
139
  generated_tables = generate_data_from_dbml(
112
140
  tables=tables,
113
141
  refs=refs,
@@ -132,12 +160,33 @@ def main(
132
160
  typer.echo("🧪 Generating dbt yml with tests...")
133
161
  generate_dbt_yml(dest, tables, refs, project_name)
134
162
 
135
- typer.echo("🪪 Ensuring dbt profile exists...")
136
- create_profiles_yml(dest, profile_name)
163
+ if unit_tests:
164
+ typer.echo("🔬 Generating dbt unit test fixtures (requires dbt-core >= 1.8)...")
165
+ generate_unit_tests(dest, tables, generated_tables)
166
+
167
+ typer.echo(f"🪪 Ensuring dbt profile exists ({adapter})...")
168
+ create_profiles_yml(dest, profile_name, adapter=adapter)
137
169
 
138
170
  # Keep original DBML for reference
139
171
  shutil.copy(file, dest / file.name)
140
172
 
173
+ # -------------------------
174
+ # Summary
175
+ # -------------------------
176
+ total_rows = sum(len(df) for df in generated_tables.values())
177
+ unmapped = get_unmapped_columns()
178
+
179
+ typer.echo("\n📊 Summary")
180
+ typer.echo(f" Tables generated: {len(generated_tables)}")
181
+ typer.echo(f" Rows generated: {total_rows}")
182
+ typer.echo(f" Relationships in DBML: {len(refs)}")
183
+ if unmapped:
184
+ typer.echo(f" Columns using generic fallback text: {len(unmapped)}")
185
+ for col_name, data_type in unmapped:
186
+ typer.echo(f" - {col_name} ({data_type})")
187
+ else:
188
+ typer.echo(" Columns using generic fallback text: 0")
189
+
141
190
  # -------------------------
142
191
  # Done
143
192
  # -------------------------
@@ -24,7 +24,7 @@ def create_project_scaffold(dest: Path, project_name: str, profile_name: str) ->
24
24
  )
25
25
 
26
26
  # Copy over any macros from templates
27
- template_macros_dir = Path("model2data/dbt/templates/macros")
27
+ template_macros_dir = TEMPLATES_DIR / "macros"
28
28
  if template_macros_dir.exists():
29
29
  for macro_file in template_macros_dir.glob("*.sql"):
30
30
  target_file = dest / "macros" / macro_file.name
@@ -53,7 +53,7 @@ from {{{{ source('raw', '{table_name}') }}}}
53
53
  model_file.write_text(sql_content)
54
54
 
55
55
 
56
- def create_profiles_yml(dest: Path, profile_name: str) -> None:
56
+ def create_profiles_yml(dest: Path, profile_name: str, adapter: str = "duckdb") -> None:
57
57
  profiles_file = dest / "profiles.yml"
58
58
  if profiles_file.exists():
59
59
  content = profiles_file.read_text()
@@ -62,7 +62,7 @@ def create_profiles_yml(dest: Path, profile_name: str) -> None:
62
62
  _render_template(
63
63
  template_name="profiles.yml.jinja",
64
64
  output_path=profiles_file,
65
- context={"profile_name": profile_name},
65
+ context={"profile_name": profile_name, "adapter": adapter},
66
66
  )
67
67
 
68
68
 
@@ -0,0 +1,18 @@
1
+ {{ profile_name }}:
2
+ target: dev
3
+ outputs:
4
+ dev:
5
+ {% if adapter == "postgres" %}
6
+ type: postgres
7
+ threads: 4
8
+ host: "{% raw %}{{ env_var('MODEL2DATA_PG_HOST', 'localhost') }}{% endraw %}"
9
+ port: "{% raw %}{{ env_var('MODEL2DATA_PG_PORT', '5432') | as_number }}{% endraw %}"
10
+ user: "{% raw %}{{ env_var('MODEL2DATA_PG_USER', 'postgres') }}{% endraw %}"
11
+ pass: "{% raw %}{{ env_var('MODEL2DATA_PG_PASSWORD', 'postgres') }}{% endraw %}"
12
+ dbname: "{% raw %}{{ env_var('MODEL2DATA_PG_DATABASE', 'postgres') }}{% endraw %}"
13
+ schema: "{{ profile_name }}"
14
+ {% else %}
15
+ type: duckdb
16
+ threads: 1
17
+ path: "{{ profile_name }}.duckdb"
18
+ {% endif %}