sqlmesh 0.195.1.dev3__py3-none-any.whl → 0.195.1.dev5__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.
Files changed (59) hide show
  1. sqlmesh/_version.py +2 -2
  2. sqlmesh/cli/main.py +75 -11
  3. sqlmesh/cli/project_init.py +497 -0
  4. sqlmesh/core/config/connection.py +73 -0
  5. sqlmesh/integrations/dlt.py +6 -2
  6. sqlmesh/lsp/main.py +106 -35
  7. sqlmesh/magics.py +11 -5
  8. sqlmesh/utils/yaml.py +2 -0
  9. {sqlmesh-0.195.1.dev3.dist-info → sqlmesh-0.195.1.dev5.dist-info}/METADATA +1 -1
  10. {sqlmesh-0.195.1.dev3.dist-info → sqlmesh-0.195.1.dev5.dist-info}/RECORD +57 -57
  11. web/client/dist/assets/{Audits-sYYNn2gO.js → Audits-DrNQaTQA.js} +1 -1
  12. web/client/dist/assets/{Banner-CIUVdc-z.js → Banner-DsG8aGIL.js} +1 -1
  13. web/client/dist/assets/{ChevronDownIcon-uQGmwNOL.js → ChevronDownIcon-CpQjfO01.js} +1 -1
  14. web/client/dist/assets/{ChevronRightIcon-B65l6fcy.js → ChevronRightIcon-B-9lnqGr.js} +1 -1
  15. web/client/dist/assets/{Content-CPscdsPQ.js → Content-CSMzf1cU.js} +1 -1
  16. web/client/dist/assets/{Content-CAmZChZo.js → Content-vfltDlhj.js} +1 -1
  17. web/client/dist/assets/{Data-CexnpfbT.js → Data-k1Dz6qdS.js} +1 -1
  18. web/client/dist/assets/{DataCatalog-zqv8aDld.js → DataCatalog-DlbWZyBp.js} +1 -1
  19. web/client/dist/assets/{Editor-EsVsPMR8.js → Editor--iBTA9xH.js} +1 -1
  20. web/client/dist/assets/{Editor-BPOAPXLv.js → Editor-C2pcRDlN.js} +1 -1
  21. web/client/dist/assets/{Errors-0iATm2JG.js → Errors-CQW3lhsM.js} +1 -1
  22. web/client/dist/assets/{FileExplorer-PTjWgl3B.js → FileExplorer-IAIPoTxM.js} +1 -1
  23. web/client/dist/assets/{Footer-MSdMRGiG.js → Footer-D77vCxKH.js} +1 -1
  24. web/client/dist/assets/{Header-SKoNqU5i.js → Header-Ccb81kaP.js} +1 -1
  25. web/client/dist/assets/{Input-Cl0rlyUj.js → Input-D_Fp4W_B.js} +1 -1
  26. web/client/dist/assets/Lineage-BHLfUAmC.js +1 -0
  27. web/client/dist/assets/{ListboxShow-mv_m9Xqu.js → ListboxShow-BIPQ-cqE.js} +1 -1
  28. web/client/dist/assets/{ModelLineage-JG9oPMA2.js → ModelLineage-BrTZ2lm3.js} +1 -1
  29. web/client/dist/assets/{Models-Bq6zaOiV.js → Models-B6cHeIlA.js} +1 -1
  30. web/client/dist/assets/{Page-D8EIuqN3.js → Page-S1ykkm5T.js} +1 -1
  31. web/client/dist/assets/{Plan-Cr31O86c.js → Plan-DCD4m0nD.js} +1 -1
  32. web/client/dist/assets/{PlusCircleIcon-BA3W9uiU.js → PlusCircleIcon-C40sAC-m.js} +1 -1
  33. web/client/dist/assets/{ReportErrors-CGhLWJSn.js → ReportErrors-CxqgDcYY.js} +1 -1
  34. web/client/dist/assets/{Root-Cm9f5yLR.js → Root-DtUrFfE_.js} +1 -1
  35. web/client/dist/assets/{SearchList-KwGRUK3b.js → SearchList-DoRflby9.js} +1 -1
  36. web/client/dist/assets/{SelectEnvironment-Fdveg7Dn.js → SelectEnvironment-equ86mF1.js} +1 -1
  37. web/client/dist/assets/{SourceList-CrxTdTD0.js → SourceList-DT-sBCAQ.js} +1 -1
  38. web/client/dist/assets/{SourceListItem-CbL1dN69.js → SourceListItem-MFS9vpg9.js} +1 -1
  39. web/client/dist/assets/{SplitPane-Di60F7Gk.js → SplitPane-D2cB3Qkv.js} +1 -1
  40. web/client/dist/assets/{Tests-Cjw3dIQ9.js → Tests-Duzk1K8j.js} +1 -1
  41. web/client/dist/assets/{Welcome-Cwxy-j_l.js → Welcome-BgAHQe0m.js} +1 -1
  42. web/client/dist/assets/{context-DKh7qNl9.js → context-CXV8eAaM.js} +1 -1
  43. web/client/dist/assets/{context-MffevtK7.js → context-g_ECqgnp.js} +1 -1
  44. web/client/dist/assets/{editor-BYglYCad.js → editor-BddPKujl.js} +1 -1
  45. web/client/dist/assets/{file-BATQOjrJ.js → file-DUr_Gx7F.js} +1 -1
  46. web/client/dist/assets/{floating-ui.react-dom-DHc129jH.js → floating-ui.react-dom-DTKAUIBI.js} +1 -1
  47. web/client/dist/assets/{help-iFVHtKo0.js → help-LQUMs6nO.js} +1 -1
  48. web/client/dist/assets/{index-DA7iL93d.js → index-BsS6H8XQ.js} +1 -1
  49. web/client/dist/assets/{index-DZ2vKQ1l.js → index-DvxjegO0.js} +2 -2
  50. web/client/dist/assets/{plan-8UAMXVjx.js → plan-DS237yuz.js} +1 -1
  51. web/client/dist/assets/{popover-DE6_QtGZ.js → popover-CiiY17g7.js} +1 -1
  52. web/client/dist/assets/{project-CfZrmqZ7.js → project-DUHOhQT2.js} +1 -1
  53. web/client/dist/index.html +1 -1
  54. sqlmesh/cli/example_project.py +0 -338
  55. web/client/dist/assets/Lineage-VxUfloHd.js +0 -1
  56. {sqlmesh-0.195.1.dev3.dist-info → sqlmesh-0.195.1.dev5.dist-info}/WHEEL +0 -0
  57. {sqlmesh-0.195.1.dev3.dist-info → sqlmesh-0.195.1.dev5.dist-info}/entry_points.txt +0 -0
  58. {sqlmesh-0.195.1.dev3.dist-info → sqlmesh-0.195.1.dev5.dist-info}/licenses/LICENSE +0 -0
  59. {sqlmesh-0.195.1.dev3.dist-info → sqlmesh-0.195.1.dev5.dist-info}/top_level.txt +0 -0
sqlmesh/_version.py CHANGED
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '0.195.1.dev3'
21
- __version_tuple__ = version_tuple = (0, 195, 1, 'dev3')
20
+ __version__ = version = '0.195.1.dev5'
21
+ __version_tuple__ = version_tuple = (0, 195, 1, 'dev5')
sqlmesh/cli/main.py CHANGED
@@ -6,11 +6,15 @@ import sys
6
6
  import typing as t
7
7
 
8
8
  import click
9
-
10
9
  from sqlmesh import configure_logging, remove_excess_logs
11
10
  from sqlmesh.cli import error_handler
12
11
  from sqlmesh.cli import options as opt
13
- from sqlmesh.cli.example_project import ProjectTemplate, init_example_project
12
+ from sqlmesh.cli.project_init import (
13
+ ProjectTemplate,
14
+ init_example_project,
15
+ InitCliMode,
16
+ interactive_init,
17
+ )
14
18
  from sqlmesh.core.analytics import cli_analytics
15
19
  from sqlmesh.core.console import configure_console, get_console
16
20
  from sqlmesh.utils import Verbosity
@@ -22,6 +26,7 @@ from pathlib import Path
22
26
 
23
27
  logger = logging.getLogger(__name__)
24
28
 
29
+
25
30
  SKIP_LOAD_COMMANDS = (
26
31
  "clean",
27
32
  "create_external_models",
@@ -138,7 +143,7 @@ def cli(
138
143
 
139
144
 
140
145
  @cli.command("init")
141
- @click.argument("sql_dialect", required=False)
146
+ @click.argument("engine", required=False)
142
147
  @click.option(
143
148
  "-t",
144
149
  "--template",
@@ -160,24 +165,83 @@ def cli(
160
165
  @cli_analytics
161
166
  def init(
162
167
  ctx: click.Context,
163
- sql_dialect: t.Optional[str] = None,
168
+ engine: t.Optional[str] = None,
164
169
  template: t.Optional[str] = None,
165
170
  dlt_pipeline: t.Optional[str] = None,
166
171
  dlt_path: t.Optional[str] = None,
167
172
  ) -> None:
168
173
  """Create a new SQLMesh repository."""
169
- try:
170
- project_template = ProjectTemplate(template.lower() if template else "default")
171
- except ValueError:
172
- raise click.ClickException(f"Invalid project template '{template}'")
173
- init_example_project(
174
- ctx.obj,
175
- dialect=sql_dialect,
174
+ project_template = None
175
+ if template:
176
+ try:
177
+ project_template = ProjectTemplate(template.lower())
178
+ except ValueError:
179
+ template_strings = "', '".join([template.value for template in ProjectTemplate])
180
+ raise click.ClickException(
181
+ f"Invalid project template '{template}'. Please specify one of '{template_strings}'."
182
+ )
183
+
184
+ if engine or project_template == ProjectTemplate.DBT:
185
+ init_example_project(
186
+ path=ctx.obj,
187
+ template=project_template or ProjectTemplate.DEFAULT,
188
+ engine_type=engine,
189
+ pipeline=dlt_pipeline,
190
+ dlt_path=dlt_path,
191
+ )
192
+ return
193
+
194
+ import sqlmesh.utils.rich as srich
195
+
196
+ console = srich.console
197
+
198
+ project_template, engine_type, cli_mode = interactive_init(ctx.obj, console, project_template)
199
+
200
+ config_path = init_example_project(
201
+ path=ctx.obj,
176
202
  template=project_template,
203
+ engine_type=engine_type,
204
+ cli_mode=cli_mode or InitCliMode.DEFAULT,
177
205
  pipeline=dlt_pipeline,
178
206
  dlt_path=dlt_path,
179
207
  )
180
208
 
209
+ engine_install_text = ""
210
+ if engine_type and engine_type not in ("duckdb", "motherduck"):
211
+ install_text = (
212
+ "pyspark" if engine_type == "spark" else f"sqlmesh\\[{engine_type.replace('_', '')}]"
213
+ )
214
+ engine_install_text = f'• Run command in CLI to install your SQL engine\'s Python dependencies: pip install "{install_text}"\n'
215
+ # interactive init does not support DLT template
216
+ next_step_text = {
217
+ ProjectTemplate.DEFAULT: f"{engine_install_text}• Update your gateway connection settings (e.g., username/password) in the project configuration file:\n {config_path}",
218
+ ProjectTemplate.DBT: "",
219
+ }
220
+ next_step_text[ProjectTemplate.EMPTY] = next_step_text[ProjectTemplate.DEFAULT]
221
+
222
+ quickstart_text = {
223
+ ProjectTemplate.DEFAULT: "Quickstart guide:\nhttps://sqlmesh.readthedocs.io/en/stable/quickstart/cli/",
224
+ ProjectTemplate.DBT: "dbt guide:\nhttps://sqlmesh.readthedocs.io/en/stable/integrations/dbt/",
225
+ }
226
+ quickstart_text[ProjectTemplate.EMPTY] = quickstart_text[ProjectTemplate.DEFAULT]
227
+
228
+ console.print(f"""──────────────────────────────
229
+
230
+ Your SQLMesh project is ready!
231
+
232
+ Next steps:
233
+ {next_step_text[project_template]}
234
+ • Run command in CLI: sqlmesh plan
235
+ • (Optional) Explain a plan: sqlmesh plan --explain
236
+
237
+ {quickstart_text[project_template]}
238
+
239
+ Need help?
240
+ • Docs: https://sqlmesh.readthedocs.io
241
+ • Slack: https://www.tobikodata.com/slack
242
+ • GitHub: https://github.com/TobikoData/sqlmesh/issues
243
+ """)
244
+
181
245
 
182
246
  @cli.command("render")
183
247
  @click.argument("model")
@@ -0,0 +1,497 @@
1
+ import typing as t
2
+ from enum import Enum
3
+ from pathlib import Path
4
+ from dataclasses import dataclass
5
+ from rich.prompt import Prompt
6
+ from rich.console import Console
7
+ from sqlmesh.integrations.dlt import generate_dlt_models_and_settings
8
+ from sqlmesh.utils.date import yesterday_ds
9
+ from sqlmesh.utils.errors import SQLMeshError
10
+
11
+ from sqlmesh.core.config.connection import (
12
+ CONNECTION_CONFIG_TO_TYPE,
13
+ DIALECT_TO_TYPE,
14
+ INIT_DISPLAY_INFO_TO_TYPE,
15
+ )
16
+
17
+
18
+ PRIMITIVES = (str, int, bool, float)
19
+
20
+
21
+ class ProjectTemplate(Enum):
22
+ DEFAULT = "default"
23
+ DBT = "dbt"
24
+ EMPTY = "empty"
25
+ DLT = "dlt"
26
+
27
+
28
+ class InitCliMode(Enum):
29
+ DEFAULT = "default"
30
+ FLOW = "flow"
31
+
32
+
33
+ def _gen_config(
34
+ engine_type: t.Optional[str],
35
+ settings: t.Optional[str],
36
+ start: t.Optional[str],
37
+ template: ProjectTemplate,
38
+ cli_mode: InitCliMode,
39
+ dialect: t.Optional[str] = None,
40
+ ) -> str:
41
+ project_dialect = dialect or DIALECT_TO_TYPE.get(engine_type)
42
+
43
+ connection_settings = (
44
+ settings
45
+ or """ type: duckdb
46
+ database: db.db"""
47
+ )
48
+
49
+ if not settings and template != ProjectTemplate.DBT:
50
+ doc_link = "https://sqlmesh.readthedocs.io/en/stable/integrations/engines{engine_link}"
51
+ engine_link = ""
52
+
53
+ if engine_type in CONNECTION_CONFIG_TO_TYPE:
54
+ required_fields = []
55
+ non_required_fields = []
56
+
57
+ for name, field in CONNECTION_CONFIG_TO_TYPE[engine_type].model_fields.items():
58
+ field_name = field.alias or name
59
+
60
+ default_value = field.get_default()
61
+
62
+ if isinstance(default_value, Enum):
63
+ default_value = default_value.value
64
+ elif not isinstance(default_value, PRIMITIVES):
65
+ default_value = ""
66
+
67
+ required = field.is_required() or field_name == "type"
68
+ option_str = f" {'# ' if not required else ''}{field_name}: {default_value}\n"
69
+
70
+ # specify the DuckDB database field so quickstart runs out of the box
71
+ if engine_type == "duckdb" and field_name == "database":
72
+ option_str = " database: db.db\n"
73
+ required = True
74
+
75
+ if required:
76
+ required_fields.append(option_str)
77
+ else:
78
+ non_required_fields.append(option_str)
79
+
80
+ connection_settings = "".join(required_fields + non_required_fields)
81
+
82
+ engine_link = f"/{engine_type}/#connection-options"
83
+
84
+ connection_settings = (
85
+ " # For more information on configuring the connection to your execution engine, visit:\n"
86
+ " # https://sqlmesh.readthedocs.io/en/stable/reference/configuration/#connection\n"
87
+ f" # {doc_link.format(engine_link=engine_link)}\n{connection_settings}"
88
+ )
89
+
90
+ default_configs = {
91
+ ProjectTemplate.DEFAULT: f"""# --- Gateway Connection ---
92
+ gateways:
93
+ {engine_type}:
94
+ connection:
95
+ {connection_settings}
96
+ default_gateway: {engine_type}
97
+
98
+ # --- Model Defaults ---
99
+ # https://sqlmesh.readthedocs.io/en/stable/reference/model_configuration/#model-defaults
100
+
101
+ model_defaults:
102
+ dialect: {project_dialect}
103
+ start: {start or yesterday_ds()} # Start date for backfill history
104
+ cron: '@daily' # Run models daily at 12am UTC (can override per model)
105
+
106
+ # --- Linting Rules ---
107
+ # Enforce standards for your team
108
+ # https://sqlmesh.readthedocs.io/en/stable/guides/linter/
109
+
110
+ linter:
111
+ enabled: true
112
+ rules:
113
+ - ambiguousorinvalidcolumn
114
+ - invalidselectstarexpansion
115
+ """,
116
+ ProjectTemplate.DBT: """from pathlib import Path
117
+
118
+ from sqlmesh.dbt.loader import sqlmesh_config
119
+
120
+ config = sqlmesh_config(Path(__file__).parent)
121
+ """,
122
+ }
123
+
124
+ default_configs[ProjectTemplate.EMPTY] = default_configs[ProjectTemplate.DEFAULT]
125
+ default_configs[ProjectTemplate.DLT] = default_configs[ProjectTemplate.DEFAULT]
126
+
127
+ flow_cli_mode = """
128
+ # FLOW: Minimal prompts, automatic changes, summary output
129
+ # https://sqlmesh.readthedocs.io/en/stable/reference/configuration/#plan
130
+
131
+ plan:
132
+ no_diff: true # Hide detailed text differences for changed models
133
+ no_prompts: true # No interactive prompts
134
+ auto_apply: true # Apply changes automatically
135
+
136
+ # --- Optional: Set a default target environment ---
137
+ # This is intended for local development to prevent users from accidentally applying plans to the prod environment.
138
+ # It is a development only config and should NOT be committed to your git repo.
139
+ # https://sqlmesh.readthedocs.io/en/stable/guides/configuration/#default-target-environment
140
+
141
+ # Uncomment the following line to use a default target environment derived from the logged in user's name.
142
+ # default_target_environment: dev_{{ user() }}
143
+
144
+ # Example usage:
145
+ # sqlmesh plan # Automatically resolves to: sqlmesh plan dev_yourname
146
+ # sqlmesh plan prod # Specify `prod` to apply changes to production
147
+ """
148
+
149
+ return default_configs[template] + (flow_cli_mode if cli_mode == InitCliMode.FLOW else "")
150
+
151
+
152
+ @dataclass
153
+ class ExampleObjects:
154
+ sql_models: t.Dict[str, str]
155
+ python_models: t.Dict[str, str]
156
+ seeds: t.Dict[str, str]
157
+ audits: t.Dict[str, str]
158
+ tests: t.Dict[str, str]
159
+ sql_macros: t.Dict[str, str]
160
+ python_macros: t.Dict[str, str]
161
+
162
+
163
+ def _gen_example_objects(schema_name: str) -> ExampleObjects:
164
+ sql_models: t.Dict[str, str] = {}
165
+ python_models: t.Dict[str, str] = {}
166
+ seeds: t.Dict[str, str] = {}
167
+ audits: t.Dict[str, str] = {}
168
+ tests: t.Dict[str, str] = {}
169
+ sql_macros: t.Dict[str, str] = {}
170
+ python_macros: t.Dict[str, str] = {"__init__": ""}
171
+
172
+ full_model_name = f"{schema_name}.full_model"
173
+ incremental_model_name = f"{schema_name}.incremental_model"
174
+ seed_model_name = f"{schema_name}.seed_model"
175
+
176
+ sql_models[full_model_name] = f"""MODEL (
177
+ name {full_model_name},
178
+ kind FULL,
179
+ cron '@daily',
180
+ grain item_id,
181
+ audits (assert_positive_order_ids),
182
+ );
183
+
184
+ SELECT
185
+ item_id,
186
+ COUNT(DISTINCT id) AS num_orders,
187
+ FROM
188
+ {incremental_model_name}
189
+ GROUP BY item_id
190
+ """
191
+
192
+ sql_models[incremental_model_name] = f"""MODEL (
193
+ name {incremental_model_name},
194
+ kind INCREMENTAL_BY_TIME_RANGE (
195
+ time_column event_date
196
+ ),
197
+ start '2020-01-01',
198
+ cron '@daily',
199
+ grain (id, event_date)
200
+ );
201
+
202
+ SELECT
203
+ id,
204
+ item_id,
205
+ event_date,
206
+ FROM
207
+ {seed_model_name}
208
+ WHERE
209
+ event_date BETWEEN @start_date AND @end_date
210
+ """
211
+
212
+ sql_models[seed_model_name] = f"""MODEL (
213
+ name {seed_model_name},
214
+ kind SEED (
215
+ path '../seeds/seed_data.csv'
216
+ ),
217
+ columns (
218
+ id INTEGER,
219
+ item_id INTEGER,
220
+ event_date DATE
221
+ ),
222
+ grain (id, event_date)
223
+ );
224
+ """
225
+
226
+ seeds["seed_data"] = """id,item_id,event_date
227
+ 1,2,2020-01-01
228
+ 2,1,2020-01-01
229
+ 3,3,2020-01-03
230
+ 4,1,2020-01-04
231
+ 5,1,2020-01-05
232
+ 6,1,2020-01-06
233
+ 7,1,2020-01-07
234
+ """
235
+
236
+ audits["assert_positive_order_ids"] = """AUDIT (
237
+ name assert_positive_order_ids,
238
+ );
239
+
240
+ SELECT *
241
+ FROM @this_model
242
+ WHERE
243
+ item_id < 0
244
+ """
245
+
246
+ tests["test_full_model"] = f"""test_example_full_model:
247
+ model: {full_model_name}
248
+ inputs:
249
+ {incremental_model_name}:
250
+ rows:
251
+ - id: 1
252
+ item_id: 1
253
+ - id: 2
254
+ item_id: 1
255
+ - id: 3
256
+ item_id: 2
257
+ outputs:
258
+ query:
259
+ rows:
260
+ - item_id: 1
261
+ num_orders: 2
262
+ - item_id: 2
263
+ num_orders: 1
264
+ """
265
+
266
+ return ExampleObjects(
267
+ sql_models=sql_models,
268
+ python_models=python_models,
269
+ seeds=seeds,
270
+ audits=audits,
271
+ tests=tests,
272
+ python_macros=python_macros,
273
+ sql_macros=sql_macros,
274
+ )
275
+
276
+
277
+ def init_example_project(
278
+ path: t.Union[str, Path],
279
+ engine_type: t.Optional[str],
280
+ dialect: t.Optional[str] = None,
281
+ template: ProjectTemplate = ProjectTemplate.DEFAULT,
282
+ pipeline: t.Optional[str] = None,
283
+ dlt_path: t.Optional[str] = None,
284
+ schema_name: str = "sqlmesh_example",
285
+ cli_mode: InitCliMode = InitCliMode.DEFAULT,
286
+ ) -> Path:
287
+ root_path = Path(path)
288
+ config_extension = "py" if template == ProjectTemplate.DBT else "yaml"
289
+ config_path = root_path / f"config.{config_extension}"
290
+ audits_path = root_path / "audits"
291
+ macros_path = root_path / "macros"
292
+ models_path = root_path / "models"
293
+ seeds_path = root_path / "seeds"
294
+ tests_path = root_path / "tests"
295
+
296
+ if config_path.exists():
297
+ raise SQLMeshError(
298
+ f"Found an existing config file '{config_path}'.\n\nPlease change to another directory or remove the existing file."
299
+ )
300
+
301
+ if template == ProjectTemplate.DBT and not Path(root_path, "dbt_project.yml").exists():
302
+ raise SQLMeshError(
303
+ "Required dbt project file 'dbt_project.yml' not found in the current directory.\n\nPlease add it or change directories before running `sqlmesh init` to set up your project."
304
+ )
305
+
306
+ engine_types = "', '".join(CONNECTION_CONFIG_TO_TYPE)
307
+ if engine_type is None and template != ProjectTemplate.DBT:
308
+ raise SQLMeshError(
309
+ f"Missing `engine` argument to `sqlmesh init` - please specify a SQL engine for your project. Options: '{engine_types}'."
310
+ )
311
+
312
+ if engine_type and engine_type not in CONNECTION_CONFIG_TO_TYPE:
313
+ raise SQLMeshError(
314
+ f"Invalid engine '{engine_type}'. Please specify one of '{engine_types}'."
315
+ )
316
+
317
+ models: t.Set[t.Tuple[str, str]] = set()
318
+ settings = None
319
+ start = None
320
+ if engine_type and template == ProjectTemplate.DLT:
321
+ project_dialect = dialect or DIALECT_TO_TYPE.get(engine_type)
322
+ if pipeline and project_dialect:
323
+ dlt_models, settings, start = generate_dlt_models_and_settings(
324
+ pipeline_name=pipeline, dialect=project_dialect, dlt_path=dlt_path
325
+ )
326
+ else:
327
+ raise SQLMeshError(
328
+ "Please provide a DLT pipeline with the `--dlt-pipeline` flag to generate a SQLMesh project from DLT."
329
+ )
330
+
331
+ _create_config(config_path, engine_type, dialect, settings, start, template, cli_mode)
332
+ if template == ProjectTemplate.DBT:
333
+ return config_path
334
+
335
+ _create_folders([audits_path, macros_path, models_path, seeds_path, tests_path])
336
+
337
+ if template == ProjectTemplate.DLT:
338
+ _create_object_files(
339
+ models_path, {model[0].split(".")[-1]: model[1] for model in dlt_models}, "sql"
340
+ )
341
+ return config_path
342
+
343
+ example_objects = _gen_example_objects(schema_name=schema_name)
344
+
345
+ if template != ProjectTemplate.EMPTY:
346
+ _create_object_files(models_path, example_objects.sql_models, "sql")
347
+ _create_object_files(models_path, example_objects.python_models, "py")
348
+ _create_object_files(seeds_path, example_objects.seeds, "csv")
349
+ _create_object_files(audits_path, example_objects.audits, "sql")
350
+ _create_object_files(tests_path, example_objects.tests, "yaml")
351
+ _create_object_files(macros_path, example_objects.python_macros, "py")
352
+ _create_object_files(macros_path, example_objects.sql_macros, "sql")
353
+
354
+ return config_path
355
+
356
+
357
+ def _create_folders(target_folders: t.Sequence[Path]) -> None:
358
+ for folder_path in target_folders:
359
+ folder_path.mkdir(exist_ok=True)
360
+ (folder_path / ".gitkeep").touch()
361
+
362
+
363
+ def _create_config(
364
+ config_path: Path,
365
+ engine_type: t.Optional[str],
366
+ dialect: t.Optional[str],
367
+ settings: t.Optional[str],
368
+ start: t.Optional[str],
369
+ template: ProjectTemplate,
370
+ cli_mode: InitCliMode,
371
+ ) -> None:
372
+ project_config = _gen_config(engine_type, settings, start, template, cli_mode, dialect)
373
+
374
+ _write_file(
375
+ config_path,
376
+ project_config,
377
+ )
378
+
379
+
380
+ def _create_object_files(path: Path, object_dict: t.Dict[str, str], file_extension: str) -> None:
381
+ for object_name, object_def in object_dict.items():
382
+ # file name is table component of catalog.schema.table
383
+ _write_file(path / f"{object_name.split('.')[-1]}.{file_extension}", object_def)
384
+
385
+
386
+ def _write_file(path: Path, payload: str) -> None:
387
+ with open(path, "w", encoding="utf-8") as fd:
388
+ fd.write(payload)
389
+
390
+
391
+ def interactive_init(
392
+ path: Path,
393
+ console: Console,
394
+ project_template: t.Optional[ProjectTemplate] = None,
395
+ ) -> t.Tuple[ProjectTemplate, t.Optional[str], t.Optional[InitCliMode]]:
396
+ console.print("──────────────────────────────")
397
+ console.print("Welcome to SQLMesh!")
398
+
399
+ project_template = _init_template_prompt(console) if not project_template else project_template
400
+
401
+ if project_template == ProjectTemplate.DBT:
402
+ return (project_template, None, None)
403
+
404
+ engine_type = _init_engine_prompt(console)
405
+ cli_mode = _init_cli_mode_prompt(console)
406
+
407
+ return (project_template, engine_type, cli_mode)
408
+
409
+
410
+ def _init_integer_prompt(
411
+ console: Console, err_msg_entity: str, num_options: int, retry_func: t.Callable[[t.Any], t.Any]
412
+ ) -> int:
413
+ err_msg = "\nERROR: '{option_str}' is not a valid {err_msg_entity} number - please enter a number between 1 and {num_options} or exit with control+c\n"
414
+ while True:
415
+ option_str = Prompt.ask("Enter a number", console=console)
416
+
417
+ value_error = False
418
+ try:
419
+ option_num = int(option_str)
420
+ except ValueError:
421
+ value_error = True
422
+
423
+ if value_error or option_num < 1 or option_num > num_options:
424
+ console.print(
425
+ err_msg.format(
426
+ option_str=option_str, err_msg_entity=err_msg_entity, num_options=num_options
427
+ ),
428
+ style="red",
429
+ )
430
+ continue
431
+ console.print("")
432
+ return option_num
433
+
434
+
435
+ def _init_display_choices(values_dict: t.Dict[str, str], console: Console) -> t.Dict[int, str]:
436
+ display_num_to_value = {}
437
+ for i, value_str in enumerate(values_dict.keys()):
438
+ console.print(f" \[{i + 1}] {' ' if i < 9 else ''}{value_str} {values_dict[value_str]}")
439
+ display_num_to_value[i + 1] = value_str
440
+ console.print("")
441
+ return display_num_to_value
442
+
443
+
444
+ def _init_template_prompt(console: Console) -> ProjectTemplate:
445
+ console.print("──────────────────────────────\n")
446
+ console.print("What type of project do you want to set up?\n")
447
+
448
+ # These are ordered for user display - do not reorder
449
+ template_descriptions = {
450
+ ProjectTemplate.DEFAULT.name: "- Create SQLMesh example project models and files",
451
+ ProjectTemplate.DBT.value: " - You have an existing dbt project and want to run it with SQLMesh",
452
+ ProjectTemplate.EMPTY.name: " - Create a SQLMesh configuration file and project directories only",
453
+ }
454
+
455
+ display_num_to_template = _init_display_choices(template_descriptions, console)
456
+
457
+ template_num = _init_integer_prompt(
458
+ console, "project type", len(template_descriptions), _init_template_prompt
459
+ )
460
+
461
+ return ProjectTemplate(display_num_to_template[template_num].lower())
462
+
463
+
464
+ def _init_engine_prompt(console: Console) -> str:
465
+ console.print("──────────────────────────────\n")
466
+ console.print("Choose your SQL engine:\n")
467
+
468
+ # INIT_DISPLAY_INFO_TO_TYPE is a dict of {engine_type: (display_order, display_name)}
469
+ DISPLAY_NAME_TO_TYPE = {v[1]: k for k, v in INIT_DISPLAY_INFO_TO_TYPE.items()}
470
+ ordered_engine_display_names = {
471
+ info[1]: "" for info in sorted(INIT_DISPLAY_INFO_TO_TYPE.values(), key=lambda x: x[0])
472
+ }
473
+ display_num_to_display_name = _init_display_choices(ordered_engine_display_names, console)
474
+
475
+ engine_num = _init_integer_prompt(
476
+ console, "engine", len(ordered_engine_display_names), _init_engine_prompt
477
+ )
478
+
479
+ return DISPLAY_NAME_TO_TYPE[display_num_to_display_name[engine_num]]
480
+
481
+
482
+ def _init_cli_mode_prompt(console: Console) -> InitCliMode:
483
+ console.print("──────────────────────────────\n")
484
+ console.print("Choose your SQLMesh CLI experience:\n")
485
+
486
+ cli_mode_descriptions = {
487
+ InitCliMode.DEFAULT.name: "- See and control every detail",
488
+ InitCliMode.FLOW.name: " - Automatically run changes and show summary output",
489
+ }
490
+
491
+ display_num_to_cli_mode = _init_display_choices(cli_mode_descriptions, console)
492
+
493
+ cli_mode_num = _init_integer_prompt(
494
+ console, "config", len(cli_mode_descriptions), _init_cli_mode_prompt
495
+ )
496
+
497
+ return InitCliMode(display_num_to_cli_mode[cli_mode_num].lower())