model2data 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- model2data-0.1.0/LICENSE +21 -0
- model2data-0.1.0/PKG-INFO +154 -0
- model2data-0.1.0/README.md +137 -0
- model2data-0.1.0/model2data/cli.py +160 -0
- model2data-0.1.0/model2data/dbt/project.py +74 -0
- model2data-0.1.0/model2data/generate/core.py +171 -0
- model2data-0.1.0/model2data/generate/faker.py +113 -0
- model2data-0.1.0/model2data/generate/relationships.py +49 -0
- model2data-0.1.0/model2data/parse/dbml.py +162 -0
- model2data-0.1.0/model2data/utils.py +9 -0
- model2data-0.1.0/model2data.egg-info/PKG-INFO +154 -0
- model2data-0.1.0/model2data.egg-info/SOURCES.txt +21 -0
- model2data-0.1.0/model2data.egg-info/dependency_links.txt +1 -0
- model2data-0.1.0/model2data.egg-info/entry_points.txt +2 -0
- model2data-0.1.0/model2data.egg-info/requires.txt +9 -0
- model2data-0.1.0/model2data.egg-info/top_level.txt +1 -0
- model2data-0.1.0/pyproject.toml +31 -0
- model2data-0.1.0/setup.cfg +4 -0
- model2data-0.1.0/tests/test_cli_smoke.py +41 -0
- model2data-0.1.0/tests/test_dbml_parser.py +29 -0
- model2data-0.1.0/tests/test_dbt_naming.py +56 -0
- model2data-0.1.0/tests/test_dbt_tests.py +31 -0
- model2data-0.1.0/tests/test_generation.py +47 -0
model2data-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 JB Analytica
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: model2data
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Generate analytics-ready datasets from DBML models
|
|
5
|
+
Requires-Python: >=3.9
|
|
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: dev
|
|
15
|
+
Requires-Dist: pytest; extra == "dev"
|
|
16
|
+
Dynamic: license-file
|
|
17
|
+
|
|
18
|
+
# model2data
|
|
19
|
+
|
|
20
|
+
[](https://pypi.org/project/model2data/)
|
|
21
|
+
[](https://github.com/JB-Analytica/model2data/actions/workflows/ci.yml)
|
|
22
|
+
[](https://codecov.io/gh/JB-Analytica/model2data)
|
|
23
|
+
[](LICENSE)
|
|
24
|
+
|
|
25
|
+
`model2data` turns **data models into analytics-ready datasets** in seconds.
|
|
26
|
+
|
|
27
|
+
Given a **DBML file**, it generates synthetic but realistic data, a complete dbt project scaffold, and everything you need to start analyzing or testing data pipelines.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## What problem does it solve?
|
|
32
|
+
|
|
33
|
+
Building analytics or testing dbt pipelines often requires realistic data, but using real data raises privacy concerns, and creating mock data manually is time-consuming. `model2data` automates this by generating synthetic datasets from your data model definitions, ensuring privacy-safe, deterministic, and relationship-preserving data for development and testing.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## How it works (high level)
|
|
38
|
+
|
|
39
|
+
1. **Parse DBML**: Reads your database schema from a DBML file, extracting tables, columns, types, and relationships.
|
|
40
|
+
2. **Generate Data**: Uses Faker and custom logic to create realistic synthetic data, respecting foreign keys and constraints.
|
|
41
|
+
3. **Scaffold dbt Project**: Creates a dbt project with seeds (CSV files), staging models, profiles, and tests, ready to run with DuckDB.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install model2data
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Quick start
|
|
54
|
+
|
|
55
|
+
We provide an example Hacker News dataset in `examples/hackernews.dbml`.
|
|
56
|
+
|
|
57
|
+
Generate a project with synthetic data:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
model2data generate --file examples/hackernews.dbml --rows 200 --seed 42
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
This creates a `dbt_hackernews/` folder with your data and dbt setup.
|
|
64
|
+
|
|
65
|
+
Run dbt to load and transform the data:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
cd dbt_hackernews
|
|
69
|
+
dbt deps
|
|
70
|
+
dbt seed
|
|
71
|
+
dbt run
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Your analytics-ready dataset is now in DuckDB!
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Generated dbt project structure
|
|
79
|
+
|
|
80
|
+
The generated dbt project includes:
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
dbt_{project_name}/
|
|
84
|
+
โโโ seeds/
|
|
85
|
+
โ โโโ {project_name}/
|
|
86
|
+
โ โโโ table1.csv
|
|
87
|
+
โ โโโ table2.csv
|
|
88
|
+
โโโ models/
|
|
89
|
+
โ โโโ {project_name}/
|
|
90
|
+
โ โโโ staging/
|
|
91
|
+
โ โโโ __sources.yml
|
|
92
|
+
โ โโโ stg_table1.sql
|
|
93
|
+
โ โโโ stg_table1.yml
|
|
94
|
+
โ โโโ ...
|
|
95
|
+
โโโ macros/
|
|
96
|
+
โ โโโ generate_schema_name.sql
|
|
97
|
+
โโโ dbt_project.yml
|
|
98
|
+
โโโ profiles.yml # DuckDB config
|
|
99
|
+
โโโ {project_name}.duckdb
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
- **Seeds**: CSV files with generated synthetic data.
|
|
103
|
+
- **Staging Models**: Basic dbt models that load from seeds.
|
|
104
|
+
- **Sources & Tests**: YAML configs defining sources and basic tests (not_null, unique).
|
|
105
|
+
- **Profiles**: Pre-configured for DuckDB with schema handling.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Design decisions / non-goals
|
|
110
|
+
|
|
111
|
+
- **DuckDB Default**: Chosen for its zero-config, file-based nature, making it easy to get started without database setup. Other adapters can be configured manually.
|
|
112
|
+
- **dbt Integration**: Leverages dbt's transformation capabilities for a familiar workflow in analytics engineering.
|
|
113
|
+
- **Synthetic Data**: Uses deterministic generation for reproducibility; not intended for production use or as a replacement for real data.
|
|
114
|
+
- **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.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Limitations
|
|
119
|
+
|
|
120
|
+
- Supports basic DBML features; complex constraints or advanced SQL types may not be fully handled.
|
|
121
|
+
- Synthetic data generation is heuristic-based and may not perfectly mimic real-world distributions or edge cases.
|
|
122
|
+
- Currently optimized for DuckDB; other databases require manual profile adjustments.
|
|
123
|
+
- No support for incremental models or advanced dbt features in generated projects.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Roadmap
|
|
128
|
+
|
|
129
|
+
- Support for additional database adapters (e.g., Snowflake, BigQuery).
|
|
130
|
+
- Enhanced data type handling and custom generators.
|
|
131
|
+
- Integration with more dbt features like incremental models.
|
|
132
|
+
- Web-based DBML editor and data preview.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Contributing
|
|
137
|
+
|
|
138
|
+
We welcome contributions!
|
|
139
|
+
|
|
140
|
+
- Open issues for bugs or feature requests.
|
|
141
|
+
- Submit PRs to add new DBML examples, custom data generators, or improvements.
|
|
142
|
+
- Ensure all new features include tests if possible.
|
|
143
|
+
|
|
144
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines.
|
|
145
|
+
|
|
146
|
+
## Code of Conduct
|
|
147
|
+
|
|
148
|
+
Please read our [Code of Conduct](CODE_OF_CONDUCT.md) to understand our community standards.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## License
|
|
153
|
+
|
|
154
|
+
MIT License. See LICENSE for details.
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# model2data
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/model2data/)
|
|
4
|
+
[](https://github.com/JB-Analytica/model2data/actions/workflows/ci.yml)
|
|
5
|
+
[](https://codecov.io/gh/JB-Analytica/model2data)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
`model2data` turns **data models into analytics-ready datasets** in seconds.
|
|
9
|
+
|
|
10
|
+
Given a **DBML file**, it generates synthetic but realistic data, a complete dbt project scaffold, and everything you need to start analyzing or testing data pipelines.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## What problem does it solve?
|
|
15
|
+
|
|
16
|
+
Building analytics or testing dbt pipelines often requires realistic data, but using real data raises privacy concerns, and creating mock data manually is time-consuming. `model2data` automates this by generating synthetic datasets from your data model definitions, ensuring privacy-safe, deterministic, and relationship-preserving data for development and testing.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## How it works (high level)
|
|
21
|
+
|
|
22
|
+
1. **Parse DBML**: Reads your database schema from a DBML file, extracting tables, columns, types, and relationships.
|
|
23
|
+
2. **Generate Data**: Uses Faker and custom logic to create realistic synthetic data, respecting foreign keys and constraints.
|
|
24
|
+
3. **Scaffold dbt Project**: Creates a dbt project with seeds (CSV files), staging models, profiles, and tests, ready to run with DuckDB.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install model2data
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Quick start
|
|
37
|
+
|
|
38
|
+
We provide an example Hacker News dataset in `examples/hackernews.dbml`.
|
|
39
|
+
|
|
40
|
+
Generate a project with synthetic data:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
model2data generate --file examples/hackernews.dbml --rows 200 --seed 42
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
This creates a `dbt_hackernews/` folder with your data and dbt setup.
|
|
47
|
+
|
|
48
|
+
Run dbt to load and transform the data:
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
cd dbt_hackernews
|
|
52
|
+
dbt deps
|
|
53
|
+
dbt seed
|
|
54
|
+
dbt run
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Your analytics-ready dataset is now in DuckDB!
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Generated dbt project structure
|
|
62
|
+
|
|
63
|
+
The generated dbt project includes:
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
dbt_{project_name}/
|
|
67
|
+
โโโ seeds/
|
|
68
|
+
โ โโโ {project_name}/
|
|
69
|
+
โ โโโ table1.csv
|
|
70
|
+
โ โโโ table2.csv
|
|
71
|
+
โโโ models/
|
|
72
|
+
โ โโโ {project_name}/
|
|
73
|
+
โ โโโ staging/
|
|
74
|
+
โ โโโ __sources.yml
|
|
75
|
+
โ โโโ stg_table1.sql
|
|
76
|
+
โ โโโ stg_table1.yml
|
|
77
|
+
โ โโโ ...
|
|
78
|
+
โโโ macros/
|
|
79
|
+
โ โโโ generate_schema_name.sql
|
|
80
|
+
โโโ dbt_project.yml
|
|
81
|
+
โโโ profiles.yml # DuckDB config
|
|
82
|
+
โโโ {project_name}.duckdb
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
- **Seeds**: CSV files with generated synthetic data.
|
|
86
|
+
- **Staging Models**: Basic dbt models that load from seeds.
|
|
87
|
+
- **Sources & Tests**: YAML configs defining sources and basic tests (not_null, unique).
|
|
88
|
+
- **Profiles**: Pre-configured for DuckDB with schema handling.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Design decisions / non-goals
|
|
93
|
+
|
|
94
|
+
- **DuckDB Default**: Chosen for its zero-config, file-based nature, making it easy to get started without database setup. Other adapters can be configured manually.
|
|
95
|
+
- **dbt Integration**: Leverages dbt's transformation capabilities for a familiar workflow in analytics engineering.
|
|
96
|
+
- **Synthetic Data**: Uses deterministic generation for reproducibility; not intended for production use or as a replacement for real data.
|
|
97
|
+
- **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.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Limitations
|
|
102
|
+
|
|
103
|
+
- Supports basic DBML features; complex constraints or advanced SQL types may not be fully handled.
|
|
104
|
+
- Synthetic data generation is heuristic-based and may not perfectly mimic real-world distributions or edge cases.
|
|
105
|
+
- Currently optimized for DuckDB; other databases require manual profile adjustments.
|
|
106
|
+
- No support for incremental models or advanced dbt features in generated projects.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Roadmap
|
|
111
|
+
|
|
112
|
+
- Support for additional database adapters (e.g., Snowflake, BigQuery).
|
|
113
|
+
- Enhanced data type handling and custom generators.
|
|
114
|
+
- Integration with more dbt features like incremental models.
|
|
115
|
+
- Web-based DBML editor and data preview.
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Contributing
|
|
120
|
+
|
|
121
|
+
We welcome contributions!
|
|
122
|
+
|
|
123
|
+
- Open issues for bugs or feature requests.
|
|
124
|
+
- Submit PRs to add new DBML examples, custom data generators, or improvements.
|
|
125
|
+
- Ensure all new features include tests if possible.
|
|
126
|
+
|
|
127
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed guidelines.
|
|
128
|
+
|
|
129
|
+
## Code of Conduct
|
|
130
|
+
|
|
131
|
+
Please read our [Code of Conduct](CODE_OF_CONDUCT.md) to understand our community standards.
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## License
|
|
136
|
+
|
|
137
|
+
MIT License. See LICENSE for details.
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Optional
|
|
3
|
+
import random
|
|
4
|
+
import shutil
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
from faker import Faker
|
|
8
|
+
|
|
9
|
+
from model2data.parse.dbml import parse_dbml
|
|
10
|
+
from model2data.generate.core import generate_data_from_dbml
|
|
11
|
+
from model2data.dbt.project import (
|
|
12
|
+
create_project_scaffold,
|
|
13
|
+
create_profiles_yml,
|
|
14
|
+
create_staging_models,
|
|
15
|
+
)
|
|
16
|
+
from model2data.dbt.tests import generate_dbt_yml
|
|
17
|
+
from model2data.utils import normalize_identifier
|
|
18
|
+
|
|
19
|
+
app = typer.Typer(
|
|
20
|
+
help=(
|
|
21
|
+
"model2data: Generate analytics-ready datasets from DBML models.\n\n"
|
|
22
|
+
"Given a DBML file, this tool produces:\n"
|
|
23
|
+
"โข Synthetic but realistic data\n"
|
|
24
|
+
"โข A runnable dbt project scaffold\n"
|
|
25
|
+
"โข dbt seeds, staging models, and profiles\n"
|
|
26
|
+
),
|
|
27
|
+
add_completion=False,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@app.command(help="Generate synthetic data and a dbt project from a DBML model.")
|
|
32
|
+
def main(
|
|
33
|
+
file: Path = typer.Option(
|
|
34
|
+
...,
|
|
35
|
+
"--file",
|
|
36
|
+
"-f",
|
|
37
|
+
exists=True,
|
|
38
|
+
file_okay=True,
|
|
39
|
+
dir_okay=False,
|
|
40
|
+
readable=True,
|
|
41
|
+
resolve_path=True,
|
|
42
|
+
help="Path to the DBML file to generate data from.",
|
|
43
|
+
),
|
|
44
|
+
rows: int = typer.Option(
|
|
45
|
+
100,
|
|
46
|
+
"--rows",
|
|
47
|
+
"-r",
|
|
48
|
+
min=10,
|
|
49
|
+
help="Number of rows to generate per table.",
|
|
50
|
+
),
|
|
51
|
+
seed: Optional[int] = typer.Option(
|
|
52
|
+
None,
|
|
53
|
+
"--seed",
|
|
54
|
+
help=(
|
|
55
|
+
"Optional random seed for deterministic generation.\n"
|
|
56
|
+
"Using the same seed will always produce identical datasets."
|
|
57
|
+
),
|
|
58
|
+
),
|
|
59
|
+
name: Optional[str] = typer.Option(
|
|
60
|
+
None,
|
|
61
|
+
"--name",
|
|
62
|
+
"-n",
|
|
63
|
+
help="Optional override for the generated dbt project's name.",
|
|
64
|
+
),
|
|
65
|
+
force: bool = typer.Option(
|
|
66
|
+
False,
|
|
67
|
+
"--force",
|
|
68
|
+
help="Overwrite the destination directory if it already exists.",
|
|
69
|
+
),
|
|
70
|
+
):
|
|
71
|
+
"""
|
|
72
|
+
Generate synthetic data and a dbt project from a DBML model.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
# -------------------------
|
|
76
|
+
# Deterministic seed
|
|
77
|
+
# -------------------------
|
|
78
|
+
if seed is not None:
|
|
79
|
+
random.seed(seed)
|
|
80
|
+
Faker.seed(seed)
|
|
81
|
+
typer.echo(f"๐ Using deterministic seed: {seed}")
|
|
82
|
+
|
|
83
|
+
# -------------------------
|
|
84
|
+
# Parse DBML (names untouched)
|
|
85
|
+
# -------------------------
|
|
86
|
+
tables, refs = parse_dbml(file)
|
|
87
|
+
if not tables:
|
|
88
|
+
typer.echo("โ No tables found in the provided DBML file.")
|
|
89
|
+
raise typer.Exit(1)
|
|
90
|
+
|
|
91
|
+
# -------------------------
|
|
92
|
+
# DBML โ dbt name mapping
|
|
93
|
+
# -------------------------
|
|
94
|
+
dbt_name_map = {
|
|
95
|
+
table_name: normalize_identifier(table_name)
|
|
96
|
+
for table_name in tables.keys()
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
project_name = normalize_identifier(name or file.stem)
|
|
100
|
+
dest = Path.cwd() / f"dbt_{project_name}"
|
|
101
|
+
profile_name = f"{project_name}_profile"
|
|
102
|
+
|
|
103
|
+
if dest.exists():
|
|
104
|
+
if not force:
|
|
105
|
+
typer.echo(
|
|
106
|
+
f"โ Destination {dest} already exists.\n"
|
|
107
|
+
"Use --force to overwrite."
|
|
108
|
+
)
|
|
109
|
+
raise typer.Exit(1)
|
|
110
|
+
shutil.rmtree(dest)
|
|
111
|
+
|
|
112
|
+
# -------------------------
|
|
113
|
+
# dbt project scaffold
|
|
114
|
+
# -------------------------
|
|
115
|
+
typer.echo(f"๐ฆ Creating dbt project scaffold at {dest}")
|
|
116
|
+
create_project_scaffold(dest, project_name, profile_name)
|
|
117
|
+
|
|
118
|
+
# -------------------------
|
|
119
|
+
# Generate synthetic data
|
|
120
|
+
# -------------------------
|
|
121
|
+
typer.echo("๐งฎ Generating synthetic datasets from DBML definitions...")
|
|
122
|
+
generated_tables = generate_data_from_dbml(
|
|
123
|
+
tables=tables,
|
|
124
|
+
refs=refs,
|
|
125
|
+
base_rows=rows,
|
|
126
|
+
seed=seed,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
# -------------------------
|
|
130
|
+
# Write dbt seeds (normalized names)
|
|
131
|
+
# -------------------------
|
|
132
|
+
seeds_path = dest / "seeds/raw"
|
|
133
|
+
for table_key, df in generated_tables.items():
|
|
134
|
+
csv_path = seeds_path / f"{table_key}.csv"
|
|
135
|
+
df.to_csv(csv_path, index=False)
|
|
136
|
+
|
|
137
|
+
# -------------------------
|
|
138
|
+
# Build dbt assets
|
|
139
|
+
# -------------------------
|
|
140
|
+
typer.echo("๐๏ธ Building staging models for generated seeds...")
|
|
141
|
+
create_staging_models(dest, project_name)
|
|
142
|
+
|
|
143
|
+
typer.echo("๐งช Generating dbt yml with tests...")
|
|
144
|
+
generate_dbt_yml(dest, tables, refs, project_name)
|
|
145
|
+
|
|
146
|
+
typer.echo("๐ชช Ensuring dbt profile exists...")
|
|
147
|
+
create_profiles_yml(dest, profile_name)
|
|
148
|
+
|
|
149
|
+
# Keep original DBML for reference
|
|
150
|
+
shutil.copy(file, dest / file.name)
|
|
151
|
+
|
|
152
|
+
# -------------------------
|
|
153
|
+
# Done
|
|
154
|
+
# -------------------------
|
|
155
|
+
typer.echo("\n๐ model2data generation complete!\n")
|
|
156
|
+
typer.echo("Next steps:")
|
|
157
|
+
typer.echo(f" cd {dest}")
|
|
158
|
+
typer.echo(" dbt deps")
|
|
159
|
+
typer.echo(" dbt seed")
|
|
160
|
+
typer.echo(" dbt run")
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import shutil
|
|
3
|
+
import jinja2
|
|
4
|
+
|
|
5
|
+
TEMPLATES_DIR = Path(__file__).parent / "templates"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def create_project_scaffold(dest: Path, project_name: str, profile_name: str) -> None:
|
|
9
|
+
dest.mkdir(parents=True, exist_ok=True)
|
|
10
|
+
|
|
11
|
+
# dbt folders
|
|
12
|
+
(dest / "models" / "staging").mkdir(parents=True, exist_ok=True)
|
|
13
|
+
(dest / "seeds" / "raw").mkdir(parents=True, exist_ok=True)
|
|
14
|
+
(dest / "analysis").mkdir(exist_ok=True)
|
|
15
|
+
(dest / "macros").mkdir(exist_ok=True)
|
|
16
|
+
(dest / "tests").mkdir(exist_ok=True)
|
|
17
|
+
(dest / "snapshots").mkdir(exist_ok=True)
|
|
18
|
+
|
|
19
|
+
# dbt_project.yml
|
|
20
|
+
_render_template(
|
|
21
|
+
template_name="dbt_project.yml.jinja",
|
|
22
|
+
output_path=dest / "dbt_project.yml",
|
|
23
|
+
context={"project_name": project_name, "profile_name": profile_name},
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
# Copy over any macros from templates
|
|
27
|
+
template_macros_dir = Path("model2data/dbt/templates/macros")
|
|
28
|
+
if template_macros_dir.exists():
|
|
29
|
+
for macro_file in template_macros_dir.glob("*.sql"):
|
|
30
|
+
target_file = dest / "macros" / macro_file.name
|
|
31
|
+
if not target_file.exists():
|
|
32
|
+
target_file.write_text(macro_file.read_text())
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def create_staging_models(dest: Path, project_name: str) -> None:
|
|
36
|
+
"""
|
|
37
|
+
Creates staging models in models/staging/ folder that reference raw seed tables as sources.
|
|
38
|
+
"""
|
|
39
|
+
seeds_path = dest / "seeds" / "raw"
|
|
40
|
+
models_path = dest / "models" / "staging"
|
|
41
|
+
models_path.mkdir(parents=True, exist_ok=True)
|
|
42
|
+
|
|
43
|
+
for csv_file in seeds_path.glob("*.csv"):
|
|
44
|
+
table_name = csv_file.stem # keep full seed name, e.g., raw_stories
|
|
45
|
+
model_file = models_path / f"stg_{table_name}.sql"
|
|
46
|
+
|
|
47
|
+
if not model_file.exists():
|
|
48
|
+
sql_content = f"""\
|
|
49
|
+
-- Auto-generated staging model for {table_name}
|
|
50
|
+
select *
|
|
51
|
+
from {{{{ source('raw', '{table_name}') }}}}
|
|
52
|
+
"""
|
|
53
|
+
model_file.write_text(sql_content)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def create_profiles_yml(dest: Path, profile_name: str) -> None:
|
|
57
|
+
profiles_file = dest / "profiles.yml"
|
|
58
|
+
if profiles_file.exists():
|
|
59
|
+
content = profiles_file.read_text()
|
|
60
|
+
if profile_name in content:
|
|
61
|
+
return
|
|
62
|
+
_render_template(
|
|
63
|
+
template_name="profiles.yml.jinja",
|
|
64
|
+
output_path=profiles_file,
|
|
65
|
+
context={"profile_name": profile_name},
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _render_template(template_name: str, output_path: Path, context: dict) -> None:
|
|
70
|
+
template_path = TEMPLATES_DIR / template_name
|
|
71
|
+
if not template_path.exists():
|
|
72
|
+
raise FileNotFoundError(f"Template not found: {template_path}")
|
|
73
|
+
template = jinja2.Template(template_path.read_text())
|
|
74
|
+
output_path.write_text(template.render(**context))
|