trellis-datamodel 0.3.3__py3-none-any.whl
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.
- trellis_datamodel/__init__.py +8 -0
- trellis_datamodel/adapters/__init__.py +41 -0
- trellis_datamodel/adapters/base.py +147 -0
- trellis_datamodel/adapters/dbt_core.py +975 -0
- trellis_datamodel/cli.py +292 -0
- trellis_datamodel/config.py +239 -0
- trellis_datamodel/models/__init__.py +13 -0
- trellis_datamodel/models/schemas.py +28 -0
- trellis_datamodel/routes/__init__.py +11 -0
- trellis_datamodel/routes/data_model.py +221 -0
- trellis_datamodel/routes/manifest.py +110 -0
- trellis_datamodel/routes/schema.py +183 -0
- trellis_datamodel/server.py +101 -0
- trellis_datamodel/static/_app/env.js +1 -0
- trellis_datamodel/static/_app/immutable/assets/0.ByDwyx3a.css +1 -0
- trellis_datamodel/static/_app/immutable/assets/2.DLAp_5AW.css +1 -0
- trellis_datamodel/static/_app/immutable/assets/trellis_squared.CTOnsdDx.svg +127 -0
- trellis_datamodel/static/_app/immutable/chunks/8ZaN1sxc.js +1 -0
- trellis_datamodel/static/_app/immutable/chunks/BfBfOTnK.js +1 -0
- trellis_datamodel/static/_app/immutable/chunks/C3yhlRfZ.js +2 -0
- trellis_datamodel/static/_app/immutable/chunks/CK3bXPEX.js +1 -0
- trellis_datamodel/static/_app/immutable/chunks/CXDUumOQ.js +1 -0
- trellis_datamodel/static/_app/immutable/chunks/DDNfEvut.js +1 -0
- trellis_datamodel/static/_app/immutable/chunks/DUdVct7e.js +1 -0
- trellis_datamodel/static/_app/immutable/chunks/QRltG_J6.js +2 -0
- trellis_datamodel/static/_app/immutable/chunks/zXDdy2c_.js +1 -0
- trellis_datamodel/static/_app/immutable/entry/app.abCkWeAJ.js +2 -0
- trellis_datamodel/static/_app/immutable/entry/start.B7CjH6Z7.js +1 -0
- trellis_datamodel/static/_app/immutable/nodes/0.bFI_DI3G.js +1 -0
- trellis_datamodel/static/_app/immutable/nodes/1.J_r941Qf.js +1 -0
- trellis_datamodel/static/_app/immutable/nodes/2.WqbMkq6o.js +27 -0
- trellis_datamodel/static/_app/version.json +1 -0
- trellis_datamodel/static/index.html +40 -0
- trellis_datamodel/static/robots.txt +3 -0
- trellis_datamodel/static/trellis_squared.svg +127 -0
- trellis_datamodel/tests/__init__.py +2 -0
- trellis_datamodel/tests/conftest.py +132 -0
- trellis_datamodel/tests/test_cli.py +526 -0
- trellis_datamodel/tests/test_data_model.py +151 -0
- trellis_datamodel/tests/test_dbt_schema.py +892 -0
- trellis_datamodel/tests/test_manifest.py +72 -0
- trellis_datamodel/tests/test_server_static.py +44 -0
- trellis_datamodel/tests/test_yaml_handler.py +228 -0
- trellis_datamodel/utils/__init__.py +2 -0
- trellis_datamodel/utils/yaml_handler.py +365 -0
- trellis_datamodel-0.3.3.dist-info/METADATA +333 -0
- trellis_datamodel-0.3.3.dist-info/RECORD +52 -0
- trellis_datamodel-0.3.3.dist-info/WHEEL +5 -0
- trellis_datamodel-0.3.3.dist-info/entry_points.txt +2 -0
- trellis_datamodel-0.3.3.dist-info/licenses/LICENSE +661 -0
- trellis_datamodel-0.3.3.dist-info/licenses/NOTICE +6 -0
- trellis_datamodel-0.3.3.dist-info/top_level.txt +1 -0
trellis_datamodel/cli.py
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Trellis CLI
|
|
3
|
+
|
|
4
|
+
Command-line interface for Trellis - visual data model editor for dbt projects.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
import uvicorn
|
|
9
|
+
import webbrowser
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
from trellis_datamodel import __version__
|
|
14
|
+
from trellis_datamodel import config as cfg
|
|
15
|
+
from trellis_datamodel.config import (
|
|
16
|
+
load_config,
|
|
17
|
+
find_config_file,
|
|
18
|
+
print_config,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
app = typer.Typer(
|
|
22
|
+
name="trellis",
|
|
23
|
+
help="Trellis - Visual data model editor for dbt projects",
|
|
24
|
+
add_completion=False,
|
|
25
|
+
no_args_is_help=True,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@app.callback(invoke_without_command=True)
|
|
30
|
+
def main(
|
|
31
|
+
ctx: typer.Context,
|
|
32
|
+
version: bool = typer.Option(False, "--version", help="Show version and exit"),
|
|
33
|
+
):
|
|
34
|
+
"""Trellis - Visual data model editor for dbt projects."""
|
|
35
|
+
if version:
|
|
36
|
+
typer.echo(__version__)
|
|
37
|
+
raise typer.Exit()
|
|
38
|
+
if ctx.invoked_subcommand is None:
|
|
39
|
+
typer.echo(ctx.get_help())
|
|
40
|
+
raise typer.Exit()
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@app.command()
|
|
44
|
+
def run(
|
|
45
|
+
port: int = typer.Option(8089, "--port", "-p", help="Port to run the server on"),
|
|
46
|
+
config: Optional[str] = typer.Option(
|
|
47
|
+
None, "--config", "-c", help="Path to config file (trellis.yml or config.yml)"
|
|
48
|
+
),
|
|
49
|
+
no_browser: bool = typer.Option(
|
|
50
|
+
False, "--no-browser", help="Don't open browser automatically"
|
|
51
|
+
),
|
|
52
|
+
):
|
|
53
|
+
"""
|
|
54
|
+
Start the Trellis server.
|
|
55
|
+
|
|
56
|
+
Looks for trellis.yml or config.yml in the current directory.
|
|
57
|
+
"""
|
|
58
|
+
# Load configuration
|
|
59
|
+
import os
|
|
60
|
+
|
|
61
|
+
# Allow running without config file if test environment variables are set
|
|
62
|
+
is_test_mode = bool(
|
|
63
|
+
os.environ.get("DATAMODEL_DATA_MODEL_PATH")
|
|
64
|
+
or os.environ.get("DATAMODEL_TEST_DIR")
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
config_path = config
|
|
68
|
+
if not config_path:
|
|
69
|
+
found_config = find_config_file()
|
|
70
|
+
if not found_config:
|
|
71
|
+
if is_test_mode:
|
|
72
|
+
# Test mode: allow running without config file
|
|
73
|
+
config_path = None
|
|
74
|
+
else:
|
|
75
|
+
cwd = os.getcwd()
|
|
76
|
+
expected_path = os.path.join(cwd, "trellis.yml")
|
|
77
|
+
typer.echo(
|
|
78
|
+
typer.style(
|
|
79
|
+
"Error: No config file found.",
|
|
80
|
+
fg=typer.colors.RED,
|
|
81
|
+
)
|
|
82
|
+
)
|
|
83
|
+
typer.echo(f" Expected location: {expected_path}")
|
|
84
|
+
typer.echo()
|
|
85
|
+
typer.echo(" Run 'trellis init' to create a starter config file.")
|
|
86
|
+
raise typer.Exit(1)
|
|
87
|
+
else:
|
|
88
|
+
config_path = found_config
|
|
89
|
+
|
|
90
|
+
if config_path:
|
|
91
|
+
load_config(config_path)
|
|
92
|
+
elif is_test_mode:
|
|
93
|
+
# Test mode: config will be loaded from environment variables
|
|
94
|
+
load_config(None)
|
|
95
|
+
|
|
96
|
+
# Print startup info
|
|
97
|
+
typer.echo(
|
|
98
|
+
typer.style(f"🌿 Trellis Data v{__version__}", fg=typer.colors.GREEN, bold=True)
|
|
99
|
+
)
|
|
100
|
+
typer.echo(f" Loading config from {config_path}")
|
|
101
|
+
print_config()
|
|
102
|
+
typer.echo()
|
|
103
|
+
|
|
104
|
+
# Start server
|
|
105
|
+
url = f"http://localhost:{port}"
|
|
106
|
+
typer.echo(typer.style(f" Server running at {url}", fg=typer.colors.CYAN))
|
|
107
|
+
typer.echo(" Press Ctrl+C to stop")
|
|
108
|
+
typer.echo()
|
|
109
|
+
|
|
110
|
+
# Open browser
|
|
111
|
+
if not no_browser:
|
|
112
|
+
try:
|
|
113
|
+
webbrowser.open(url)
|
|
114
|
+
except Exception:
|
|
115
|
+
pass # Ignore browser errors
|
|
116
|
+
|
|
117
|
+
# Run server
|
|
118
|
+
uvicorn.run(
|
|
119
|
+
"trellis_datamodel.server:app",
|
|
120
|
+
host="0.0.0.0",
|
|
121
|
+
port=port,
|
|
122
|
+
reload=False, # Disable reload in production
|
|
123
|
+
log_level="info",
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@app.command(name="serve")
|
|
128
|
+
def serve(
|
|
129
|
+
port: int = typer.Option(8089, "--port", "-p", help="Port to run the server on"),
|
|
130
|
+
config: Optional[str] = typer.Option(
|
|
131
|
+
None, "--config", "-c", help="Path to config file (trellis.yml or config.yml)"
|
|
132
|
+
),
|
|
133
|
+
no_browser: bool = typer.Option(
|
|
134
|
+
False, "--no-browser", help="Don't open browser automatically"
|
|
135
|
+
),
|
|
136
|
+
):
|
|
137
|
+
"""
|
|
138
|
+
Start the Trellis server (alias for 'run').
|
|
139
|
+
|
|
140
|
+
Looks for trellis.yml or config.yml in the current directory.
|
|
141
|
+
"""
|
|
142
|
+
# Delegate to run function
|
|
143
|
+
run(port=port, config=config, no_browser=no_browser)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@app.command()
|
|
147
|
+
def init():
|
|
148
|
+
"""
|
|
149
|
+
Initialize a new trellis.yml config file in the current directory.
|
|
150
|
+
"""
|
|
151
|
+
config_file = Path("trellis.yml")
|
|
152
|
+
if config_file.exists():
|
|
153
|
+
typer.echo(
|
|
154
|
+
typer.style(
|
|
155
|
+
"trellis.yml already exists in this directory.", fg=typer.colors.YELLOW
|
|
156
|
+
)
|
|
157
|
+
)
|
|
158
|
+
raise typer.Exit(1)
|
|
159
|
+
|
|
160
|
+
default_config = """\
|
|
161
|
+
# Trellis configuration
|
|
162
|
+
framework: dbt-core
|
|
163
|
+
dbt_project_path: "."
|
|
164
|
+
dbt_manifest_path: "target/manifest.json"
|
|
165
|
+
dbt_catalog_path: "target/catalog.json"
|
|
166
|
+
data_model_file: "data_model.yml"
|
|
167
|
+
dbt_model_paths: [] # Empty = include all models
|
|
168
|
+
"""
|
|
169
|
+
config_file.write_text(default_config)
|
|
170
|
+
typer.echo(typer.style("✓ Created trellis.yml", fg=typer.colors.GREEN))
|
|
171
|
+
typer.echo(" Edit the file to configure your dbt project paths, then run:")
|
|
172
|
+
typer.echo(typer.style(" trellis run", fg=typer.colors.CYAN))
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
@app.command(name="generate-company-data")
|
|
176
|
+
def generate_company_data(
|
|
177
|
+
config: Optional[str] = typer.Option(
|
|
178
|
+
None, "--config", "-c", help="Path to config file (trellis.yml or config.yml)"
|
|
179
|
+
),
|
|
180
|
+
):
|
|
181
|
+
"""
|
|
182
|
+
Generate mock commercial company data for modeling exercises.
|
|
183
|
+
|
|
184
|
+
Creates CSV files in dbt_company_dummy/data/ directory with realistic
|
|
185
|
+
commercial company data including departments, employees, leads, customers,
|
|
186
|
+
products, orders, and order items.
|
|
187
|
+
|
|
188
|
+
The path to the dbt company dummy project can be configured in trellis.yml
|
|
189
|
+
via the 'dbt_company_dummy_path' option (defaults to './dbt_company_dummy').
|
|
190
|
+
"""
|
|
191
|
+
import sys
|
|
192
|
+
import importlib.util
|
|
193
|
+
|
|
194
|
+
# Load config to get dbt_company_dummy_path
|
|
195
|
+
config_path = config
|
|
196
|
+
if not config_path:
|
|
197
|
+
found_config = find_config_file()
|
|
198
|
+
if found_config:
|
|
199
|
+
config_path = found_config
|
|
200
|
+
|
|
201
|
+
if config_path:
|
|
202
|
+
load_config(config_path)
|
|
203
|
+
|
|
204
|
+
# Use configured path or fallback to default relative to current working directory
|
|
205
|
+
import os
|
|
206
|
+
|
|
207
|
+
if cfg.DBT_COMPANY_DUMMY_PATH:
|
|
208
|
+
generator_path = Path(cfg.DBT_COMPANY_DUMMY_PATH) / "generate_data.py"
|
|
209
|
+
else:
|
|
210
|
+
# Fallback: check current working directory first (for installed packages)
|
|
211
|
+
# This is the most common case when running from repo root
|
|
212
|
+
cwd_dummy_path = Path(os.getcwd()) / "dbt_company_dummy" / "generate_data.py"
|
|
213
|
+
if cwd_dummy_path.exists():
|
|
214
|
+
generator_path = cwd_dummy_path
|
|
215
|
+
else:
|
|
216
|
+
# Try repo root (for development when running from source)
|
|
217
|
+
project_root = Path(__file__).parent.parent
|
|
218
|
+
repo_dummy_path = project_root / "dbt_company_dummy" / "generate_data.py"
|
|
219
|
+
if repo_dummy_path.exists():
|
|
220
|
+
generator_path = repo_dummy_path
|
|
221
|
+
else:
|
|
222
|
+
# Last resort: use current working directory even if it doesn't exist yet
|
|
223
|
+
# (will show helpful error message)
|
|
224
|
+
generator_path = cwd_dummy_path
|
|
225
|
+
|
|
226
|
+
if not generator_path.exists():
|
|
227
|
+
import os
|
|
228
|
+
|
|
229
|
+
typer.echo(
|
|
230
|
+
typer.style(
|
|
231
|
+
f"Error: Generator script not found at {generator_path}",
|
|
232
|
+
fg=typer.colors.RED,
|
|
233
|
+
)
|
|
234
|
+
)
|
|
235
|
+
typer.echo()
|
|
236
|
+
typer.echo("The generator script should be located at:")
|
|
237
|
+
if cfg.DBT_COMPANY_DUMMY_PATH:
|
|
238
|
+
typer.echo(f" {cfg.DBT_COMPANY_DUMMY_PATH}/generate_data.py")
|
|
239
|
+
typer.echo()
|
|
240
|
+
typer.echo("This path is configured in your trellis.yml file.")
|
|
241
|
+
else:
|
|
242
|
+
typer.echo(f" {os.getcwd()}/dbt_company_dummy/generate_data.py")
|
|
243
|
+
typer.echo()
|
|
244
|
+
typer.echo(
|
|
245
|
+
"Make sure you're running this command from the repository root,"
|
|
246
|
+
)
|
|
247
|
+
typer.echo(
|
|
248
|
+
"or configure 'dbt_company_dummy_path' in your trellis.yml file."
|
|
249
|
+
)
|
|
250
|
+
raise typer.Exit(1)
|
|
251
|
+
|
|
252
|
+
# Load and run the generator module
|
|
253
|
+
try:
|
|
254
|
+
spec = importlib.util.spec_from_file_location("generate_data", generator_path)
|
|
255
|
+
generator_module = importlib.util.module_from_spec(spec)
|
|
256
|
+
sys.modules["generate_data"] = generator_module
|
|
257
|
+
spec.loader.exec_module(generator_module)
|
|
258
|
+
|
|
259
|
+
# Call the main function
|
|
260
|
+
generator_module.main()
|
|
261
|
+
|
|
262
|
+
typer.echo()
|
|
263
|
+
typer.echo(
|
|
264
|
+
typer.style(
|
|
265
|
+
"✓ Company dummy data generation completed successfully!",
|
|
266
|
+
fg=typer.colors.GREEN,
|
|
267
|
+
)
|
|
268
|
+
)
|
|
269
|
+
except ImportError as e:
|
|
270
|
+
typer.echo(
|
|
271
|
+
typer.style(
|
|
272
|
+
f"Error: Missing dependency - {e}",
|
|
273
|
+
fg=typer.colors.RED,
|
|
274
|
+
)
|
|
275
|
+
)
|
|
276
|
+
typer.echo(
|
|
277
|
+
" Install required dependencies with: pip install trellis-datamodel[dbt-example]"
|
|
278
|
+
)
|
|
279
|
+
typer.echo(" Or install directly: pip install pandas faker")
|
|
280
|
+
raise typer.Exit(1)
|
|
281
|
+
except Exception as e:
|
|
282
|
+
typer.echo(
|
|
283
|
+
typer.style(
|
|
284
|
+
f"Error generating data: {e}",
|
|
285
|
+
fg=typer.colors.RED,
|
|
286
|
+
)
|
|
287
|
+
)
|
|
288
|
+
raise typer.Exit(1)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
if __name__ == "__main__":
|
|
292
|
+
app()
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration loading for Trellis Data.
|
|
3
|
+
Centralizes all path resolution logic.
|
|
4
|
+
|
|
5
|
+
For testing, set environment variable DATAMODEL_TEST_DIR to a temp directory path.
|
|
6
|
+
This will override all paths to use that directory.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
import yaml
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import Optional
|
|
13
|
+
|
|
14
|
+
# Check for test mode - allows overriding config via environment
|
|
15
|
+
_TEST_DIR = os.environ.get("DATAMODEL_TEST_DIR", "")
|
|
16
|
+
|
|
17
|
+
if _TEST_DIR:
|
|
18
|
+
# Test mode: use temp directory paths
|
|
19
|
+
CONFIG_PATH = os.path.join(_TEST_DIR, "config.yml")
|
|
20
|
+
FRAMEWORK: str = os.environ.get("DATAMODEL_FRAMEWORK", "dbt-core")
|
|
21
|
+
MANIFEST_PATH: str = os.environ.get(
|
|
22
|
+
"DATAMODEL_MANIFEST_PATH", os.path.join(_TEST_DIR, "manifest.json")
|
|
23
|
+
)
|
|
24
|
+
CATALOG_PATH: str = os.environ.get(
|
|
25
|
+
"DATAMODEL_CATALOG_PATH", os.path.join(_TEST_DIR, "catalog.json")
|
|
26
|
+
)
|
|
27
|
+
DATA_MODEL_PATH: str = os.environ.get(
|
|
28
|
+
"DATAMODEL_DATA_MODEL_PATH", os.path.join(_TEST_DIR, "data_model.yml")
|
|
29
|
+
)
|
|
30
|
+
CANVAS_LAYOUT_PATH: str = os.environ.get(
|
|
31
|
+
"DATAMODEL_CANVAS_LAYOUT_PATH", os.path.join(_TEST_DIR, "canvas_layout.yml")
|
|
32
|
+
)
|
|
33
|
+
CANVAS_LAYOUT_VERSION_CONTROL: bool = (
|
|
34
|
+
os.environ.get("DATAMODEL_CANVAS_LAYOUT_VERSION_CONTROL", "true").lower()
|
|
35
|
+
== "true"
|
|
36
|
+
)
|
|
37
|
+
DBT_PROJECT_PATH: str = _TEST_DIR
|
|
38
|
+
DBT_MODEL_PATHS: list[str] = ["3_core"]
|
|
39
|
+
FRONTEND_BUILD_DIR: str = os.path.join(_TEST_DIR, "frontend/build")
|
|
40
|
+
DBT_COMPANY_DUMMY_PATH: str = os.path.join(_TEST_DIR, "dbt_company_dummy")
|
|
41
|
+
else:
|
|
42
|
+
# Production mode: will be set by load_config()
|
|
43
|
+
CONFIG_PATH: str = ""
|
|
44
|
+
FRAMEWORK: str = "dbt-core"
|
|
45
|
+
MANIFEST_PATH: str = ""
|
|
46
|
+
CATALOG_PATH: str = ""
|
|
47
|
+
DATA_MODEL_PATH: str = ""
|
|
48
|
+
CANVAS_LAYOUT_PATH: str = ""
|
|
49
|
+
CANVAS_LAYOUT_VERSION_CONTROL: bool = True
|
|
50
|
+
DBT_PROJECT_PATH: str = ""
|
|
51
|
+
DBT_MODEL_PATHS: list[str] = []
|
|
52
|
+
FRONTEND_BUILD_DIR: str = ""
|
|
53
|
+
DBT_COMPANY_DUMMY_PATH: str = ""
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def find_config_file(config_override: Optional[str] = None) -> Optional[str]:
|
|
57
|
+
"""
|
|
58
|
+
Find config file in order of priority:
|
|
59
|
+
1. CLI override (--config)
|
|
60
|
+
2. trellis.yml in current directory
|
|
61
|
+
3. config.yml in current directory (fallback)
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
Path to config file or None if not found
|
|
65
|
+
"""
|
|
66
|
+
if config_override:
|
|
67
|
+
if os.path.exists(config_override):
|
|
68
|
+
return os.path.abspath(config_override)
|
|
69
|
+
return None
|
|
70
|
+
|
|
71
|
+
cwd = os.getcwd()
|
|
72
|
+
|
|
73
|
+
# Try trellis.yml first
|
|
74
|
+
trellis_yml = os.path.join(cwd, "trellis.yml")
|
|
75
|
+
if os.path.exists(trellis_yml):
|
|
76
|
+
return os.path.abspath(trellis_yml)
|
|
77
|
+
|
|
78
|
+
# Fallback to config.yml
|
|
79
|
+
config_yml = os.path.join(cwd, "config.yml")
|
|
80
|
+
if os.path.exists(config_yml):
|
|
81
|
+
return os.path.abspath(config_yml)
|
|
82
|
+
|
|
83
|
+
return None
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def load_config(config_path: Optional[str] = None) -> None:
|
|
87
|
+
"""Load and resolve all paths from config file."""
|
|
88
|
+
global FRAMEWORK, MANIFEST_PATH, DATA_MODEL_PATH, DBT_MODEL_PATHS, CATALOG_PATH, DBT_PROJECT_PATH, CANVAS_LAYOUT_PATH, CANVAS_LAYOUT_VERSION_CONTROL, CONFIG_PATH, FRONTEND_BUILD_DIR, DBT_COMPANY_DUMMY_PATH
|
|
89
|
+
|
|
90
|
+
# Skip loading config file in test mode (paths already set via environment)
|
|
91
|
+
if _TEST_DIR:
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
# Find config file
|
|
95
|
+
if config_path:
|
|
96
|
+
CONFIG_PATH = config_path
|
|
97
|
+
else:
|
|
98
|
+
found_config = find_config_file()
|
|
99
|
+
if not found_config:
|
|
100
|
+
# No config found - use defaults
|
|
101
|
+
CONFIG_PATH = ""
|
|
102
|
+
return
|
|
103
|
+
CONFIG_PATH = found_config
|
|
104
|
+
|
|
105
|
+
if not os.path.exists(CONFIG_PATH):
|
|
106
|
+
return
|
|
107
|
+
|
|
108
|
+
try:
|
|
109
|
+
with open(CONFIG_PATH, "r") as f:
|
|
110
|
+
config = yaml.safe_load(f) or {}
|
|
111
|
+
|
|
112
|
+
# 0. Get framework (defaults to dbt-core)
|
|
113
|
+
FRAMEWORK = config.get("framework", "dbt-core")
|
|
114
|
+
|
|
115
|
+
# 1. Get dbt_project_path (Required for resolving other paths)
|
|
116
|
+
if "dbt_project_path" in config:
|
|
117
|
+
p = config["dbt_project_path"]
|
|
118
|
+
if not os.path.isabs(p):
|
|
119
|
+
# Resolve relative to config file location
|
|
120
|
+
DBT_PROJECT_PATH = os.path.abspath(
|
|
121
|
+
os.path.join(os.path.dirname(CONFIG_PATH), p)
|
|
122
|
+
)
|
|
123
|
+
else:
|
|
124
|
+
DBT_PROJECT_PATH = p
|
|
125
|
+
else:
|
|
126
|
+
DBT_PROJECT_PATH = ""
|
|
127
|
+
|
|
128
|
+
# 2. Resolve Manifest
|
|
129
|
+
if "dbt_manifest_path" in config:
|
|
130
|
+
p = config["dbt_manifest_path"]
|
|
131
|
+
if not os.path.isabs(p) and DBT_PROJECT_PATH:
|
|
132
|
+
MANIFEST_PATH = os.path.abspath(os.path.join(DBT_PROJECT_PATH, p))
|
|
133
|
+
elif os.path.isabs(p):
|
|
134
|
+
MANIFEST_PATH = p
|
|
135
|
+
else:
|
|
136
|
+
MANIFEST_PATH = os.path.abspath(
|
|
137
|
+
os.path.join(os.path.dirname(CONFIG_PATH), p)
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# 3. Resolve Catalog
|
|
141
|
+
if "dbt_catalog_path" in config:
|
|
142
|
+
p = config["dbt_catalog_path"]
|
|
143
|
+
if not os.path.isabs(p) and DBT_PROJECT_PATH:
|
|
144
|
+
CATALOG_PATH = os.path.abspath(os.path.join(DBT_PROJECT_PATH, p))
|
|
145
|
+
elif os.path.isabs(p):
|
|
146
|
+
CATALOG_PATH = p
|
|
147
|
+
else:
|
|
148
|
+
CATALOG_PATH = os.path.abspath(
|
|
149
|
+
os.path.join(os.path.dirname(CONFIG_PATH), p)
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
# 4. Resolve Data Model (env var takes precedence)
|
|
153
|
+
if (
|
|
154
|
+
"DATAMODEL_DATA_MODEL_PATH" not in os.environ
|
|
155
|
+
and "data_model_file" in config
|
|
156
|
+
):
|
|
157
|
+
p = config["data_model_file"]
|
|
158
|
+
if not os.path.isabs(p):
|
|
159
|
+
base_path = DBT_PROJECT_PATH or os.path.dirname(CONFIG_PATH)
|
|
160
|
+
DATA_MODEL_PATH = os.path.abspath(os.path.join(base_path, p))
|
|
161
|
+
else:
|
|
162
|
+
DATA_MODEL_PATH = p
|
|
163
|
+
|
|
164
|
+
# 5. Model path filters
|
|
165
|
+
if "dbt_model_paths" in config:
|
|
166
|
+
DBT_MODEL_PATHS = config["dbt_model_paths"]
|
|
167
|
+
|
|
168
|
+
# 6. Resolve Canvas Layout (defaults to canvas_layout.yml next to data model)
|
|
169
|
+
if "canvas_layout_file" in config:
|
|
170
|
+
p = config["canvas_layout_file"]
|
|
171
|
+
if not os.path.isabs(p):
|
|
172
|
+
base_path = DBT_PROJECT_PATH or os.path.dirname(CONFIG_PATH)
|
|
173
|
+
CANVAS_LAYOUT_PATH = os.path.abspath(os.path.join(base_path, p))
|
|
174
|
+
else:
|
|
175
|
+
CANVAS_LAYOUT_PATH = p
|
|
176
|
+
else:
|
|
177
|
+
# Default: canvas_layout.yml next to data_model.yml
|
|
178
|
+
if DATA_MODEL_PATH:
|
|
179
|
+
data_model_dir = os.path.dirname(DATA_MODEL_PATH)
|
|
180
|
+
CANVAS_LAYOUT_PATH = os.path.abspath(
|
|
181
|
+
os.path.join(data_model_dir, "canvas_layout.yml")
|
|
182
|
+
)
|
|
183
|
+
else:
|
|
184
|
+
CANVAS_LAYOUT_PATH = os.path.abspath(
|
|
185
|
+
os.path.join(os.path.dirname(CONFIG_PATH), "canvas_layout.yml")
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
# 7. Canvas layout version control setting
|
|
189
|
+
if "canvas_layout_version_control" in config:
|
|
190
|
+
CANVAS_LAYOUT_VERSION_CONTROL = config["canvas_layout_version_control"]
|
|
191
|
+
|
|
192
|
+
# 8. Frontend build directory
|
|
193
|
+
fe_env = os.environ.get("DATAMODEL_FRONTEND_BUILD_DIR")
|
|
194
|
+
if fe_env:
|
|
195
|
+
FRONTEND_BUILD_DIR = fe_env
|
|
196
|
+
elif "frontend_build_dir" in config:
|
|
197
|
+
p = config["frontend_build_dir"]
|
|
198
|
+
if not os.path.isabs(p):
|
|
199
|
+
FRONTEND_BUILD_DIR = os.path.abspath(
|
|
200
|
+
os.path.join(os.path.dirname(CONFIG_PATH), p)
|
|
201
|
+
)
|
|
202
|
+
else:
|
|
203
|
+
FRONTEND_BUILD_DIR = p
|
|
204
|
+
else:
|
|
205
|
+
# Default: frontend/build next to config file (repo root)
|
|
206
|
+
FRONTEND_BUILD_DIR = os.path.abspath(
|
|
207
|
+
os.path.join(os.path.dirname(CONFIG_PATH), "frontend", "build")
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
# 9. Resolve dbt company dummy path (only if explicitly configured)
|
|
211
|
+
# If not configured, leave empty so CLI can use smart fallback logic
|
|
212
|
+
if "dbt_company_dummy_path" in config:
|
|
213
|
+
p = config["dbt_company_dummy_path"]
|
|
214
|
+
if not os.path.isabs(p):
|
|
215
|
+
DBT_COMPANY_DUMMY_PATH = os.path.abspath(
|
|
216
|
+
os.path.join(os.path.dirname(CONFIG_PATH), p)
|
|
217
|
+
)
|
|
218
|
+
else:
|
|
219
|
+
DBT_COMPANY_DUMMY_PATH = p
|
|
220
|
+
# Note: No default set here - CLI handles fallback to cwd/dbt_company_dummy
|
|
221
|
+
|
|
222
|
+
except Exception as e:
|
|
223
|
+
print(f"Error loading config: {e}")
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def print_config() -> None:
|
|
227
|
+
"""Print current configuration for debugging."""
|
|
228
|
+
print(f"Using Config: {CONFIG_PATH}")
|
|
229
|
+
print(f"Framework: {FRAMEWORK}")
|
|
230
|
+
print(f"Project Path: {DBT_PROJECT_PATH}")
|
|
231
|
+
print(f"Frontend build dir: {FRONTEND_BUILD_DIR}")
|
|
232
|
+
print(f"Looking for manifest at: {MANIFEST_PATH}")
|
|
233
|
+
print(f"Looking for catalog at: {CATALOG_PATH}")
|
|
234
|
+
print(f"Looking for data model at: {DATA_MODEL_PATH}")
|
|
235
|
+
print(f"Looking for canvas layout at: {CANVAS_LAYOUT_PATH}")
|
|
236
|
+
print(f"Canvas layout version control: {CANVAS_LAYOUT_VERSION_CONTROL}")
|
|
237
|
+
print(f"Filtering models by paths: {DBT_MODEL_PATHS}")
|
|
238
|
+
if DBT_COMPANY_DUMMY_PATH:
|
|
239
|
+
print(f"dbt company dummy path: {DBT_COMPANY_DUMMY_PATH}")
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Pydantic models for API request/response schemas."""
|
|
2
|
+
from pydantic import BaseModel
|
|
3
|
+
from typing import List, Optional, Dict, Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class DataModelUpdate(BaseModel):
|
|
7
|
+
"""Schema for updating the data model."""
|
|
8
|
+
version: float = 0.1
|
|
9
|
+
entities: List[Dict[str, Any]]
|
|
10
|
+
relationships: List[Dict[str, Any]]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DbtSchemaRequest(BaseModel):
|
|
14
|
+
"""Schema for creating a new dbt schema file."""
|
|
15
|
+
entity_id: str
|
|
16
|
+
model_name: str
|
|
17
|
+
fields: List[Dict[str, str]]
|
|
18
|
+
description: Optional[str] = None
|
|
19
|
+
tags: Optional[List[str]] = None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ModelSchemaRequest(BaseModel):
|
|
23
|
+
"""Schema for updating model columns and metadata."""
|
|
24
|
+
columns: List[Dict[str, Any]]
|
|
25
|
+
description: Optional[str] = None
|
|
26
|
+
tags: Optional[List[str]] = None
|
|
27
|
+
version: Optional[int] = None
|
|
28
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""API route modules."""
|
|
2
|
+
from .manifest import router as manifest_router
|
|
3
|
+
from .data_model import router as data_model_router
|
|
4
|
+
from .schema import router as schema_router
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"manifest_router",
|
|
8
|
+
"data_model_router",
|
|
9
|
+
"schema_router",
|
|
10
|
+
]
|
|
11
|
+
|