Skip to content

Commit 64b70cd

Browse files
committed
Differentiate dialect and engine_type, add error handling
1 parent 93c1014 commit 64b70cd

3 files changed

Lines changed: 104 additions & 54 deletions

File tree

sqlmesh/cli/example_project.py

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,12 @@ def _gen_config(
3939
database: db.db"""
4040
)
4141

42+
engine = "mssql" if dialect == "tsql" else dialect
43+
4244
if not settings and template != ProjectTemplate.DBT:
4345
doc_link = "https://sqlmesh.readthedocs.io/en/stable/integrations/engines{engine_link}"
4446
engine_link = ""
4547

46-
engine = "mssql" if dialect == "tsql" else dialect
47-
4848
if engine in CONNECTION_CONFIG_TO_TYPE:
4949
required_fields = []
5050
non_required_fields = []
@@ -84,17 +84,18 @@ def _gen_config(
8484
default_configs = {
8585
ProjectTemplate.DEFAULT: f"""# --- Gateway Connection ---
8686
gateways:
87-
{dialect}:
87+
{engine}:
8888
connection:
8989
{connection_settings}
90-
default_gateway: {dialect}
90+
default_gateway: {engine}
9191
9292
# --- Model Defaults ---
9393
# https://sqlmesh.readthedocs.io/en/stable/reference/model_configuration/#model-defaults
9494
9595
model_defaults:
9696
dialect: {dialect}
97-
start: {start or yesterday_ds()}
97+
start: {start or yesterday_ds()} # Start date for backfill history
98+
cron: '@daily' # Run models daily at 12am UTC (can override per model)
9899
99100
# --- Linting Rules ---
100101
# Enforce standards for your team
@@ -123,7 +124,6 @@ def _gen_config(
123124
# https://sqlmesh.readthedocs.io/en/stable/reference/configuration/#plan
124125
125126
plan:
126-
enable_preview: true # Enable preview for forward-only models when targeting a development environment
127127
no_diff: true # Hide detailed text differences for changed models
128128
use_finalized_state: true # Compare only against finalized snapshots
129129
no_prompts: true # No interactive prompts
@@ -274,6 +274,7 @@ def _gen_example_objects(schema_name: str) -> ExampleObjects:
274274
def init_example_project(
275275
path: t.Union[str, Path],
276276
dialect: t.Optional[str],
277+
engine_type: t.Optional[str],
277278
template: ProjectTemplate = ProjectTemplate.DEFAULT,
278279
pipeline: t.Optional[str] = None,
279280
dlt_path: t.Optional[str] = None,
@@ -294,8 +295,10 @@ def init_example_project(
294295
f"Found an existing config file '{config_path}'.\n\nPlease change to another directory or remove the existing file."
295296
)
296297

297-
if not dialect and template != ProjectTemplate.DBT:
298-
raise SQLMeshError("Please provide a default SQL dialect for your project's models.")
298+
if not engine_type and template != ProjectTemplate.DBT:
299+
if not dialect:
300+
raise SQLMeshError("Please provide a default SQL dialect for your project's models.")
301+
Dialect.get_or_raise(dialect)
299302

300303
models: t.Set[t.Tuple[str, str]] = set()
301304
settings = None
@@ -310,7 +313,11 @@ def init_example_project(
310313
"Please provide a DLT pipeline with the `--dlt-pipeline` flag to generate a SQLMesh project from DLT."
311314
)
312315

313-
_create_config(config_path, dialect, settings, start, template, cli_mode)
316+
# config generation chooses engine based on ConnectionConfig.type_
317+
# - if user passes a SQL dialect, we always generate the engine whose type_ == dialect
318+
# - example: if users passes `postgres` we will always choose `postgres` engine and never `gcp_postgres`
319+
# - if user interactively chooses an engine, we will pass the correct ConnectionConfig.type_
320+
_create_config(config_path, engine_type or dialect, settings, start, template, cli_mode)
314321
if template == ProjectTemplate.DBT:
315322
return config_path
316323

@@ -340,16 +347,13 @@ def _create_folders(target_folders: t.Sequence[Path]) -> None:
340347

341348
def _create_config(
342349
config_path: Path,
343-
dialect: t.Optional[str],
350+
engine_type: t.Optional[str],
344351
settings: t.Optional[str],
345352
start: t.Optional[str],
346353
template: ProjectTemplate,
347354
cli_mode: InitCliMode,
348355
) -> None:
349-
if dialect:
350-
Dialect.get_or_raise(dialect)
351-
352-
project_config = _gen_config(dialect, settings, start, template, cli_mode)
356+
project_config = _gen_config(engine_type, settings, start, template, cli_mode)
353357

354358
_write_file(
355359
config_path,

sqlmesh/cli/main.py

Lines changed: 80 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from sqlmesh.core.console import configure_console, get_console
1616
from sqlmesh.utils import Verbosity
1717
from sqlmesh.core.config import load_configs
18+
from sqlmesh.core.config.connection import CONNECTION_CONFIG_TO_TYPE
1819
from sqlmesh.core.context import Context
1920
from sqlmesh.utils.date import TimeLike
2021
from sqlmesh.utils.errors import MissingDependencyError, SQLMeshError
@@ -39,23 +40,24 @@
3940
)
4041
SKIP_CONTEXT_COMMANDS = ("init", "ui")
4142

42-
ENGINE_DISPLAY_NAME_TO_DIALECT = {
43-
"Athena": "athena",
44-
"Azure SQL": "tsql",
45-
"BigQuery": "bigquery",
46-
"ClickHouse": "clickhouse",
47-
"Databricks": "databricks",
43+
# These are ordered for user display - do not reorder
44+
ENGINE_DISPLAY_NAME_TO_CONNECTION_TYPE = {
4845
"DuckDB": "duckdb",
49-
"GCP Postgres": "postgres",
46+
"Snowflake": "snowflake",
47+
"Databricks": "databricks",
48+
"BigQuery": "bigquery",
5049
"MotherDuck": "duckdb",
51-
"MSSQL": "tsql",
52-
"MySQL": "mysql",
53-
"Postgres": "postgres",
50+
"ClickHouse": "clickhouse",
5451
"Redshift": "redshift",
55-
"RisingWave": "postgres",
56-
"Snowflake": "snowflake",
5752
"Spark": "spark",
5853
"Trino": "trino",
54+
"Azure SQL": "azuresql",
55+
"MSSQL": "tsql",
56+
"Postgres": "postgres",
57+
"GCP Postgres": "gcp_postgres",
58+
"MySQL": "mysql",
59+
"Athena": "athena",
60+
"RisingWave": "risingwave",
5961
}
6062

6163

@@ -201,38 +203,55 @@ def init(
201203

202204
if sql_dialect or project_template == ProjectTemplate.DBT:
203205
init_example_project(
204-
ctx.obj,
206+
path=ctx.obj,
205207
dialect=sql_dialect,
208+
engine_type=None,
206209
template=project_template or ProjectTemplate.DEFAULT,
207210
pipeline=dlt_pipeline,
208211
dlt_path=dlt_path,
209212
)
210-
else:
211-
import sqlmesh.utils.rich as srich
213+
return
212214

213-
console = srich.console
215+
import sqlmesh.utils.rich as srich
214216

215-
project_template, sql_dialect, cli_mode = _interactive_init(console, project_template)
216-
config_path = init_example_project(
217-
ctx.obj,
218-
template=project_template,
219-
dialect=sql_dialect,
220-
cli_mode=cli_mode,
221-
pipeline=dlt_pipeline,
222-
dlt_path=dlt_path,
223-
)
224-
console.print(f"""──────────────────────────────
217+
console = srich.console
218+
219+
project_template, engine_type, cli_mode = _interactive_init(console, project_template)
220+
if project_template != ProjectTemplate.DBT:
221+
_check_engine_installed(console, engine_type)
222+
223+
config_path = init_example_project(
224+
path=ctx.obj,
225+
dialect=None,
226+
template=project_template,
227+
engine_type=engine_type,
228+
cli_mode=cli_mode or InitCliMode.DEFAULT,
229+
pipeline=dlt_pipeline,
230+
dlt_path=dlt_path,
231+
)
232+
233+
next_step_text = {
234+
ProjectTemplate.DEFAULT: f"• Update your gateway connection settings (e.g., username/password) in the project configuration file:\n {config_path}\nRun command in CLI: sqlmesh plan\n(Optional) Explain a plan: sqlmesh plan --explain",
235+
ProjectTemplate.DBT: "",
236+
}
237+
next_step_text[ProjectTemplate.EMPTY] = next_step_text[ProjectTemplate.DEFAULT]
238+
239+
quickstart_text = {
240+
ProjectTemplate.DEFAULT: "Quickstart guide:\nhttps://sqlmesh.readthedocs.io/en/stable/quickstart/cli/",
241+
ProjectTemplate.DBT: "dbt guide:\nhttps://sqlmesh.readthedocs.io/en/stable/integrations/dbt/",
242+
}
243+
quickstart_text[ProjectTemplate.EMPTY] = quickstart_text[ProjectTemplate.DEFAULT]
244+
245+
console.print(f"""──────────────────────────────
225246
226247
Your SQLMesh project is ready!
227248
228249
Next steps:
229-
• Update your gateway connection settings (e.g., username/password) in the project configuration file:
230-
{config_path}
250+
{next_step_text[project_template]}
231251
• Run command in CLI: sqlmesh plan
232252
• (Optional) Explain a plan: sqlmesh plan --explain
233253
234-
Quickstart guide:
235-
https://sqlmesh.readthedocs.io/en/stable/quickstart/cli/
254+
{quickstart_text[project_template]}
236255
237256
Need help?
238257
• Docs: https://sqlmesh.readthedocs.io
@@ -1286,21 +1305,29 @@ def state_import(obj: Context, input_file: Path, replace: bool, no_confirm: bool
12861305

12871306
def _interactive_init(
12881307
console: Console, project_template: t.Optional[ProjectTemplate] = None
1289-
) -> t.Tuple[ProjectTemplate, str, InitCliMode]:
1308+
) -> t.Tuple[ProjectTemplate, t.Optional[str], t.Optional[InitCliMode]]:
12901309
console.print("──────────────────────────────")
12911310
console.print("Welcome to SQLMesh!")
12921311

12931312
project_template = _init_template_prompt(console) if not project_template else project_template
1294-
dialect = _init_engine_prompt(console)
1313+
1314+
if project_template == ProjectTemplate.DBT:
1315+
if not Path("dbt_project.yml").exists():
1316+
raise SQLMeshError(
1317+
"Required file 'dbt_project.yml' not found in the current directory.\n\n Please add it or change directories before running `sqlmesh init` to set up your dbt project with SQLMesh."
1318+
)
1319+
return (project_template, None, None)
1320+
1321+
engine_type = _init_engine_prompt(console)
12951322
cli_mode = _init_cli_mode_prompt(console)
12961323

1297-
return (project_template, dialect, cli_mode)
1324+
return (project_template, engine_type, cli_mode)
12981325

12991326

13001327
def _init_integer_prompt(
13011328
console: Console, err_msg_entity: str, num_options: int, retry_func: t.Callable[[t.Any], t.Any]
13021329
) -> int:
1303-
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 Ctrl+C"
1330+
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"
13041331
option_str = Prompt.ask("Enter a number", console=console)
13051332
try:
13061333
option_num = int(option_str)
@@ -1321,11 +1348,11 @@ def _init_integer_prompt(
13211348

13221349

13231350
def _init_template_prompt(console: Console) -> ProjectTemplate:
1351+
# These are ordered for user display - do not reorder
13241352
template_descriptions = {
13251353
ProjectTemplate.DEFAULT.name: "- Create SQLMesh example project models and files",
1326-
ProjectTemplate.EMPTY.name: " - Create a configuration file and project file directories",
13271354
ProjectTemplate.DBT.value: " - You have an existing dbt project and want to run it with SQLMesh",
1328-
ProjectTemplate.DLT.name: " - You have an existing DLT pipeline and want to load it with SQLMesh",
1355+
ProjectTemplate.EMPTY.name: " - Create a SQLMesh configuration file and project directories only",
13291356
}
13301357

13311358
console.print("──────────────────────────────\n")
@@ -1349,7 +1376,7 @@ def _init_engine_prompt(console: Console) -> str:
13491376
console.print("Choose your SQL engine:\n")
13501377

13511378
display_num_to_engine = {}
1352-
for i, engine in enumerate(ENGINE_DISPLAY_NAME_TO_DIALECT.keys()):
1379+
for i, engine in enumerate(ENGINE_DISPLAY_NAME_TO_CONNECTION_TYPE.keys()):
13531380
console.print(f" \\[{i + 1}] {' ' if i < 9 else ''}{engine}")
13541381
display_num_to_engine[i + 1] = engine
13551382
console.print("")
@@ -1361,10 +1388,10 @@ def _init_engine_prompt(console: Console) -> str:
13611388
# """)
13621389

13631390
engine_num = _init_integer_prompt(
1364-
console, "engine", len(ENGINE_DISPLAY_NAME_TO_DIALECT), _init_engine_prompt
1391+
console, "engine", len(ENGINE_DISPLAY_NAME_TO_CONNECTION_TYPE), _init_engine_prompt
13651392
)
13661393

1367-
return ENGINE_DISPLAY_NAME_TO_DIALECT[display_num_to_engine[engine_num]]
1394+
return ENGINE_DISPLAY_NAME_TO_CONNECTION_TYPE[display_num_to_engine[engine_num]]
13681395

13691396

13701397
def _init_cli_mode_prompt(console: Console) -> InitCliMode:
@@ -1387,3 +1414,17 @@ def _init_cli_mode_prompt(console: Console) -> InitCliMode:
13871414
)
13881415

13891416
return InitCliMode(display_num_to_cli_mode[cli_mode_num].lower())
1417+
1418+
1419+
def _check_engine_installed(console: Console, engine_type: t.Optional[str] = None) -> None:
1420+
if not engine_type:
1421+
return
1422+
connection_config = CONNECTION_CONFIG_TO_TYPE[engine_type]
1423+
1424+
try:
1425+
connection_config._connection_factory.fget(None)
1426+
except ModuleNotFoundError:
1427+
install_command = f'pip install "sqlmesh[{engine_type}]"'
1428+
raise SQLMeshError(
1429+
f"Unable to load required Python dependencies for the {engine_type.upper()} engine.\n\nPlease run `{install_command}` to install them before running `sqlmesh init` again."
1430+
)

sqlmesh/magics.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,12 @@ def init(self, line: str) -> None:
190190
except ValueError:
191191
raise MagicError(f"Invalid project template '{args.template}'")
192192
init_example_project(
193-
args.path, args.sql_dialect, project_template, args.dlt_pipeline, args.dlt_path
193+
path=args.path,
194+
dialect=args.sql_dialect,
195+
engine_type=None,
196+
template=project_template,
197+
pipeline=args.dlt_pipeline,
198+
dlt_path=args.dlt_path,
194199
)
195200
html = str(
196201
h(

0 commit comments

Comments
 (0)