Edit on GitHub

sqlmesh.cli.project_init

  1import typing as t
  2from enum import Enum
  3from pathlib import Path
  4from dataclasses import dataclass
  5from rich.prompt import Prompt
  6from rich.console import Console
  7from sqlmesh.integrations.dlt import generate_dlt_models_and_settings
  8from sqlmesh.utils.date import yesterday_ds
  9from sqlmesh.utils.errors import SQLMeshError
 10from sqlmesh.core.config.common import VirtualEnvironmentMode
 11
 12from sqlmesh.core.config.common import DBT_PROJECT_FILENAME
 13from sqlmesh.core.config.connection import (
 14    CONNECTION_CONFIG_TO_TYPE,
 15    DIALECT_TO_TYPE,
 16    INIT_DISPLAY_INFO_TO_TYPE,
 17)
 18
 19
 20PRIMITIVES = (str, int, bool, float)
 21
 22
 23class ProjectTemplate(Enum):
 24    DEFAULT = "default"
 25    DBT = "dbt"
 26    EMPTY = "empty"
 27    DLT = "dlt"
 28
 29
 30class InitCliMode(Enum):
 31    DEFAULT = "default"
 32    FLOW = "flow"
 33
 34
 35def _gen_config(
 36    engine_type: t.Optional[str],
 37    settings: t.Optional[str],
 38    start: t.Optional[str],
 39    template: ProjectTemplate,
 40    cli_mode: InitCliMode,
 41    dialect: t.Optional[str] = None,
 42) -> str:
 43    project_dialect = dialect or DIALECT_TO_TYPE.get(engine_type)
 44
 45    connection_settings = (
 46        settings
 47        or """      type: duckdb
 48      database: db.db"""
 49    )
 50
 51    if not settings and template != ProjectTemplate.DBT:
 52        doc_link = "https://sqlmesh.readthedocs.io/en/stable/integrations/engines{engine_link}"
 53        engine_link = ""
 54
 55        if engine_type in CONNECTION_CONFIG_TO_TYPE:
 56            required_fields = []
 57            non_required_fields = []
 58
 59            for name, field in CONNECTION_CONFIG_TO_TYPE[engine_type].model_fields.items():
 60                field_name = field.alias or name
 61
 62                default_value = field.get_default()
 63
 64                if isinstance(default_value, Enum):
 65                    default_value = default_value.value
 66                elif not isinstance(default_value, PRIMITIVES):
 67                    default_value = ""
 68
 69                required = field.is_required() or field_name == "type"
 70                option_str = f"      {'# ' if not required else ''}{field_name}: {default_value}\n"
 71
 72                # specify the DuckDB database field so quickstart runs out of the box
 73                if engine_type == "duckdb" and field_name == "database":
 74                    option_str = "      database: db.db\n"
 75                    required = True
 76
 77                if required:
 78                    required_fields.append(option_str)
 79                else:
 80                    non_required_fields.append(option_str)
 81
 82            connection_settings = "".join(required_fields + non_required_fields)
 83
 84            engine_link = f"/{engine_type}/#connection-options"
 85
 86        connection_settings = (
 87            "      # For more information on configuring the connection to your execution engine, visit:\n"
 88            "      # https://sqlmesh.readthedocs.io/en/stable/reference/configuration/#connection\n"
 89            f"      # {doc_link.format(engine_link=engine_link)}\n{connection_settings}"
 90        )
 91
 92    default_configs = {
 93        ProjectTemplate.DEFAULT: f"""# --- Gateway Connection ---
 94gateways:
 95  {engine_type}:
 96    connection:
 97{connection_settings}
 98default_gateway: {engine_type}
 99
100# --- Model Defaults ---
101# https://sqlmesh.readthedocs.io/en/stable/reference/model_configuration/#model-defaults
102
103model_defaults:
104  dialect: {project_dialect}
105  start: {start or yesterday_ds()} # Start date for backfill history
106  cron: '@daily'    # Run models daily at 12am UTC (can override per model)
107
108# --- Linting Rules ---
109# Enforce standards for your team
110# https://sqlmesh.readthedocs.io/en/stable/guides/linter/
111
112linter:
113  enabled: true
114  rules:
115    - ambiguousorinvalidcolumn
116    - invalidselectstarexpansion
117    - noambiguousprojections
118""",
119        ProjectTemplate.DBT: f"""# --- DBT-specific options ---
120dbt:
121  # This configuration ensures that each dbt target gets its own isolated state.
122  # The inferred state schemas are named "sqlmesh_state_<profile name>_<target schema>", eg "sqlmesh_state_jaffle_shop_dev"
123  # If this is undesirable, you may manually configure the gateway to use a specific state schema name
124  # https://sqlmesh.readthedocs.io/en/stable/integrations/dbt/#selecting-a-different-state-connection
125  infer_state_schema_name: True
126
127# --- Virtual Data Environment Mode ---
128# Enable Virtual Data Environments (VDE) for *development* environments.
129# Note that the production environment in dbt projects is not virtual by default to maintain compatibility with existing tooling.
130# https://sqlmesh.readthedocs.io/en/stable/guides/configuration/#virtual-data-environment-modes
131virtual_environment_mode: {VirtualEnvironmentMode.DEV_ONLY.lower()}
132
133# --- Plan Defaults ---
134# https://sqlmesh.readthedocs.io/en/stable/reference/configuration/#plan
135plan:
136  # For Virtual Data Environments, this ensures that any changes are always considered against prod,
137  # rather than the previous state of that environment
138  always_recreate_environment: True
139
140# --- Model Defaults ---
141# https://sqlmesh.readthedocs.io/en/stable/reference/model_configuration/#model-defaults
142model_defaults:
143  start: {start or yesterday_ds()}
144""",
145    }
146
147    default_configs[ProjectTemplate.EMPTY] = default_configs[ProjectTemplate.DEFAULT]
148    default_configs[ProjectTemplate.DLT] = default_configs[ProjectTemplate.DEFAULT]
149
150    flow_cli_mode = """
151# FLOW: Minimal prompts, automatic changes, summary output
152# https://sqlmesh.readthedocs.io/en/stable/reference/configuration/#plan
153
154plan:
155  no_diff: true             # Hide detailed text differences for changed models
156  no_prompts: true          # No interactive prompts
157  auto_apply: true          # Apply changes automatically
158
159# --- Optional: Set a default target environment ---
160# This is intended for local development to prevent users from accidentally applying plans to the prod environment.
161# It is a development only config and should NOT be committed to your git repo.
162# https://sqlmesh.readthedocs.io/en/stable/guides/configuration/#default-target-environment
163
164# Uncomment the following line to use a default target environment derived from the logged in user's name.
165# default_target_environment: dev_{{ user() }}
166
167# Example usage:
168# sqlmesh plan            # Automatically resolves to: sqlmesh plan dev_yourname
169# sqlmesh plan prod       # Specify `prod` to apply changes to production
170"""
171
172    return default_configs[template] + (flow_cli_mode if cli_mode == InitCliMode.FLOW else "")
173
174
175@dataclass
176class ExampleObjects:
177    sql_models: t.Dict[str, str]
178    python_models: t.Dict[str, str]
179    seeds: t.Dict[str, str]
180    audits: t.Dict[str, str]
181    tests: t.Dict[str, str]
182    sql_macros: t.Dict[str, str]
183    python_macros: t.Dict[str, str]
184
185
186def _gen_example_objects(schema_name: str) -> ExampleObjects:
187    sql_models: t.Dict[str, str] = {}
188    python_models: t.Dict[str, str] = {}
189    seeds: t.Dict[str, str] = {}
190    audits: t.Dict[str, str] = {}
191    tests: t.Dict[str, str] = {}
192    sql_macros: t.Dict[str, str] = {}
193    python_macros: t.Dict[str, str] = {"__init__": ""}
194
195    full_model_name = f"{schema_name}.full_model"
196    incremental_model_name = f"{schema_name}.incremental_model"
197    seed_model_name = f"{schema_name}.seed_model"
198
199    sql_models[full_model_name] = f"""MODEL (
200  name {full_model_name},
201  kind FULL,
202  cron '@daily',
203  grain item_id,
204  audits (assert_positive_order_ids),
205);
206
207SELECT
208  item_id,
209  COUNT(DISTINCT id) AS num_orders,
210FROM
211  {incremental_model_name}
212GROUP BY item_id
213  """
214
215    sql_models[incremental_model_name] = f"""MODEL (
216  name {incremental_model_name},
217  kind INCREMENTAL_BY_TIME_RANGE (
218    time_column event_date
219  ),
220  start '2020-01-01',
221  cron '@daily',
222  grain (id, event_date)
223);
224
225SELECT
226  id,
227  item_id,
228  event_date,
229FROM
230  {seed_model_name}
231WHERE
232  event_date BETWEEN @start_date AND @end_date
233  """
234
235    sql_models[seed_model_name] = f"""MODEL (
236  name {seed_model_name},
237  kind SEED (
238    path '../seeds/seed_data.csv'
239  ),
240  columns (
241    id INTEGER,
242    item_id INTEGER,
243    event_date DATE
244  ),
245  grain (id, event_date)
246);
247  """
248
249    seeds["seed_data"] = """id,item_id,event_date
2501,2,2020-01-01
2512,1,2020-01-01
2523,3,2020-01-03
2534,1,2020-01-04
2545,1,2020-01-05
2556,1,2020-01-06
2567,1,2020-01-07
257"""
258
259    audits["assert_positive_order_ids"] = """AUDIT (
260  name assert_positive_order_ids,
261);
262
263SELECT *
264FROM @this_model
265WHERE
266  item_id < 0
267  """
268
269    tests["test_full_model"] = f"""test_example_full_model:
270  model: {full_model_name}
271  inputs:
272    {incremental_model_name}:
273      rows:
274      - id: 1
275        item_id: 1
276      - id: 2
277        item_id: 1
278      - id: 3
279        item_id: 2
280  outputs:
281    query:
282      rows:
283      - item_id: 1
284        num_orders: 2
285      - item_id: 2
286        num_orders: 1
287  """
288
289    return ExampleObjects(
290        sql_models=sql_models,
291        python_models=python_models,
292        seeds=seeds,
293        audits=audits,
294        tests=tests,
295        python_macros=python_macros,
296        sql_macros=sql_macros,
297    )
298
299
300def init_example_project(
301    path: t.Union[str, Path],
302    engine_type: t.Optional[str],
303    dialect: t.Optional[str] = None,
304    template: ProjectTemplate = ProjectTemplate.DEFAULT,
305    pipeline: t.Optional[str] = None,
306    dlt_path: t.Optional[str] = None,
307    schema_name: str = "sqlmesh_example",
308    cli_mode: InitCliMode = InitCliMode.DEFAULT,
309    start: t.Optional[str] = None,
310) -> Path:
311    root_path = Path(path)
312
313    config_path = root_path / "config.yaml"
314    if template == ProjectTemplate.DBT:
315        # name the config file `sqlmesh.yaml` to make it clear that within the context of all
316        # the existing yaml files DBT project, this one specifically relates to configuring the sqlmesh engine
317        config_path = root_path / "sqlmesh.yaml"
318
319    audits_path = root_path / "audits"
320    macros_path = root_path / "macros"
321    models_path = root_path / "models"
322    seeds_path = root_path / "seeds"
323    tests_path = root_path / "tests"
324
325    if config_path.exists():
326        raise SQLMeshError(
327            f"Found an existing config file '{config_path}'.\n\nPlease change to another directory or remove the existing file."
328        )
329
330    if template == ProjectTemplate.DBT and not Path(root_path, DBT_PROJECT_FILENAME).exists():
331        raise SQLMeshError(
332            "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."
333        )
334
335    engine_types = "', '".join(CONNECTION_CONFIG_TO_TYPE)
336    if engine_type is None and template != ProjectTemplate.DBT:
337        raise SQLMeshError(
338            f"Missing `engine` argument to `sqlmesh init` - please specify a SQL engine for your project. Options: '{engine_types}'."
339        )
340
341    if engine_type and engine_type not in CONNECTION_CONFIG_TO_TYPE:
342        raise SQLMeshError(
343            f"Invalid engine '{engine_type}'. Please specify one of '{engine_types}'."
344        )
345
346    models: t.Set[t.Tuple[str, str]] = set()
347    settings = None
348    if engine_type and template == ProjectTemplate.DLT:
349        project_dialect = dialect or DIALECT_TO_TYPE.get(engine_type)
350        if pipeline and project_dialect:
351            dlt_models, settings, start = generate_dlt_models_and_settings(
352                pipeline_name=pipeline, dialect=project_dialect, dlt_path=dlt_path
353            )
354        else:
355            raise SQLMeshError(
356                "Please provide a DLT pipeline with the `--dlt-pipeline` flag to generate a SQLMesh project from DLT."
357            )
358
359    _create_config(config_path, engine_type, dialect, settings, start, template, cli_mode)
360    if template == ProjectTemplate.DBT:
361        return config_path
362
363    _create_folders([audits_path, macros_path, models_path, seeds_path, tests_path])
364
365    if template == ProjectTemplate.DLT:
366        _create_object_files(
367            models_path, {model[0].split(".")[-1]: model[1] for model in dlt_models}, "sql"
368        )
369        return config_path
370
371    example_objects = _gen_example_objects(schema_name=schema_name)
372
373    if template != ProjectTemplate.EMPTY:
374        _create_object_files(models_path, example_objects.sql_models, "sql")
375        _create_object_files(models_path, example_objects.python_models, "py")
376        _create_object_files(seeds_path, example_objects.seeds, "csv")
377        _create_object_files(audits_path, example_objects.audits, "sql")
378        _create_object_files(tests_path, example_objects.tests, "yaml")
379        _create_object_files(macros_path, example_objects.python_macros, "py")
380        _create_object_files(macros_path, example_objects.sql_macros, "sql")
381
382    return config_path
383
384
385def _create_folders(target_folders: t.Sequence[Path]) -> None:
386    for folder_path in target_folders:
387        folder_path.mkdir(exist_ok=True)
388        (folder_path / ".gitkeep").touch()
389
390
391def _create_config(
392    config_path: Path,
393    engine_type: t.Optional[str],
394    dialect: t.Optional[str],
395    settings: t.Optional[str],
396    start: t.Optional[str],
397    template: ProjectTemplate,
398    cli_mode: InitCliMode,
399) -> None:
400    project_config = _gen_config(engine_type, settings, start, template, cli_mode, dialect)
401
402    _write_file(
403        config_path,
404        project_config,
405    )
406
407
408def _create_object_files(path: Path, object_dict: t.Dict[str, str], file_extension: str) -> None:
409    for object_name, object_def in object_dict.items():
410        # file name is table component of catalog.schema.table
411        _write_file(path / f"{object_name.split('.')[-1]}.{file_extension}", object_def)
412
413
414def _write_file(path: Path, payload: str) -> None:
415    with open(path, "w", encoding="utf-8") as fd:
416        fd.write(payload)
417
418
419def interactive_init(
420    path: Path,
421    console: Console,
422    project_template: t.Optional[ProjectTemplate] = None,
423) -> t.Tuple[ProjectTemplate, t.Optional[str], t.Optional[InitCliMode]]:
424    console.print("──────────────────────────────")
425    console.print("Welcome to SQLMesh!")
426
427    project_template = _init_template_prompt(console) if not project_template else project_template
428
429    if project_template == ProjectTemplate.DBT:
430        return (project_template, None, None)
431
432    engine_type = _init_engine_prompt(console)
433    cli_mode = _init_cli_mode_prompt(console)
434
435    return (project_template, engine_type, cli_mode)
436
437
438def _init_integer_prompt(
439    console: Console, err_msg_entity: str, num_options: int, retry_func: t.Callable[[t.Any], t.Any]
440) -> int:
441    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"
442    while True:
443        option_str = Prompt.ask("Enter a number", console=console)
444
445        value_error = False
446        try:
447            option_num = int(option_str)
448        except ValueError:
449            value_error = True
450
451        if value_error or option_num < 1 or option_num > num_options:
452            console.print(
453                err_msg.format(
454                    option_str=option_str, err_msg_entity=err_msg_entity, num_options=num_options
455                ),
456                style="red",
457            )
458            continue
459        console.print("")
460        return option_num
461
462
463def _init_display_choices(values_dict: t.Dict[str, str], console: Console) -> t.Dict[int, str]:
464    display_num_to_value = {}
465    for i, value_str in enumerate(values_dict.keys()):
466        console.print(f"    \\[{i + 1}] {' ' if i < 9 else ''}{value_str} {values_dict[value_str]}")
467        display_num_to_value[i + 1] = value_str
468    console.print("")
469    return display_num_to_value
470
471
472def _init_template_prompt(console: Console) -> ProjectTemplate:
473    console.print("──────────────────────────────\n")
474    console.print("What type of project do you want to set up?\n")
475
476    # These are ordered for user display - do not reorder
477    template_descriptions = {
478        ProjectTemplate.DEFAULT.name: "- Create SQLMesh example project models and files",
479        ProjectTemplate.DBT.value: "    - You have an existing dbt project and want to run it with SQLMesh",
480        ProjectTemplate.EMPTY.name: "  - Create a SQLMesh configuration file and project directories only",
481    }
482
483    display_num_to_template = _init_display_choices(template_descriptions, console)
484
485    template_num = _init_integer_prompt(
486        console, "project type", len(template_descriptions), _init_template_prompt
487    )
488
489    return ProjectTemplate(display_num_to_template[template_num].lower())
490
491
492def _init_engine_prompt(console: Console) -> str:
493    console.print("──────────────────────────────\n")
494    console.print("Choose your SQL engine:\n")
495
496    # INIT_DISPLAY_INFO_TO_TYPE is a dict of {engine_type: (display_order, display_name)}
497    DISPLAY_NAME_TO_TYPE = {v[1]: k for k, v in INIT_DISPLAY_INFO_TO_TYPE.items()}
498    ordered_engine_display_names = {
499        info[1]: "" for info in sorted(INIT_DISPLAY_INFO_TO_TYPE.values(), key=lambda x: x[0])
500    }
501    display_num_to_display_name = _init_display_choices(ordered_engine_display_names, console)
502
503    engine_num = _init_integer_prompt(
504        console, "engine", len(ordered_engine_display_names), _init_engine_prompt
505    )
506
507    return DISPLAY_NAME_TO_TYPE[display_num_to_display_name[engine_num]]
508
509
510def _init_cli_mode_prompt(console: Console) -> InitCliMode:
511    console.print("──────────────────────────────\n")
512    console.print("Choose your SQLMesh CLI experience:\n")
513
514    cli_mode_descriptions = {
515        InitCliMode.DEFAULT.name: "- See and control every detail",
516        InitCliMode.FLOW.name: "   - Automatically run changes and show summary output",
517    }
518
519    display_num_to_cli_mode = _init_display_choices(cli_mode_descriptions, console)
520
521    cli_mode_num = _init_integer_prompt(
522        console, "config", len(cli_mode_descriptions), _init_cli_mode_prompt
523    )
524
525    return InitCliMode(display_num_to_cli_mode[cli_mode_num].lower())
PRIMITIVES = (<class 'str'>, <class 'int'>, <class 'bool'>, <class 'float'>)
class ProjectTemplate(enum.Enum):
24class ProjectTemplate(Enum):
25    DEFAULT = "default"
26    DBT = "dbt"
27    EMPTY = "empty"
28    DLT = "dlt"

An enumeration.

DEFAULT = <ProjectTemplate.DEFAULT: 'default'>
DBT = <ProjectTemplate.DBT: 'dbt'>
EMPTY = <ProjectTemplate.EMPTY: 'empty'>
DLT = <ProjectTemplate.DLT: 'dlt'>
Inherited Members
enum.Enum
name
value
class InitCliMode(enum.Enum):
31class InitCliMode(Enum):
32    DEFAULT = "default"
33    FLOW = "flow"

An enumeration.

DEFAULT = <InitCliMode.DEFAULT: 'default'>
FLOW = <InitCliMode.FLOW: 'flow'>
Inherited Members
enum.Enum
name
value
@dataclass
class ExampleObjects:
176@dataclass
177class ExampleObjects:
178    sql_models: t.Dict[str, str]
179    python_models: t.Dict[str, str]
180    seeds: t.Dict[str, str]
181    audits: t.Dict[str, str]
182    tests: t.Dict[str, str]
183    sql_macros: t.Dict[str, str]
184    python_macros: t.Dict[str, str]
ExampleObjects( sql_models: Dict[str, str], python_models: Dict[str, str], seeds: Dict[str, str], audits: Dict[str, str], tests: Dict[str, str], sql_macros: Dict[str, str], python_macros: Dict[str, str])
sql_models: Dict[str, str]
python_models: Dict[str, str]
seeds: Dict[str, str]
audits: Dict[str, str]
tests: Dict[str, str]
sql_macros: Dict[str, str]
python_macros: Dict[str, str]
def init_example_project( path: Union[str, pathlib.Path], engine_type: Optional[str], dialect: Optional[str] = None, template: ProjectTemplate = <ProjectTemplate.DEFAULT: 'default'>, pipeline: Optional[str] = None, dlt_path: Optional[str] = None, schema_name: str = 'sqlmesh_example', cli_mode: InitCliMode = <InitCliMode.DEFAULT: 'default'>, start: Optional[str] = None) -> pathlib.Path:
301def init_example_project(
302    path: t.Union[str, Path],
303    engine_type: t.Optional[str],
304    dialect: t.Optional[str] = None,
305    template: ProjectTemplate = ProjectTemplate.DEFAULT,
306    pipeline: t.Optional[str] = None,
307    dlt_path: t.Optional[str] = None,
308    schema_name: str = "sqlmesh_example",
309    cli_mode: InitCliMode = InitCliMode.DEFAULT,
310    start: t.Optional[str] = None,
311) -> Path:
312    root_path = Path(path)
313
314    config_path = root_path / "config.yaml"
315    if template == ProjectTemplate.DBT:
316        # name the config file `sqlmesh.yaml` to make it clear that within the context of all
317        # the existing yaml files DBT project, this one specifically relates to configuring the sqlmesh engine
318        config_path = root_path / "sqlmesh.yaml"
319
320    audits_path = root_path / "audits"
321    macros_path = root_path / "macros"
322    models_path = root_path / "models"
323    seeds_path = root_path / "seeds"
324    tests_path = root_path / "tests"
325
326    if config_path.exists():
327        raise SQLMeshError(
328            f"Found an existing config file '{config_path}'.\n\nPlease change to another directory or remove the existing file."
329        )
330
331    if template == ProjectTemplate.DBT and not Path(root_path, DBT_PROJECT_FILENAME).exists():
332        raise SQLMeshError(
333            "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."
334        )
335
336    engine_types = "', '".join(CONNECTION_CONFIG_TO_TYPE)
337    if engine_type is None and template != ProjectTemplate.DBT:
338        raise SQLMeshError(
339            f"Missing `engine` argument to `sqlmesh init` - please specify a SQL engine for your project. Options: '{engine_types}'."
340        )
341
342    if engine_type and engine_type not in CONNECTION_CONFIG_TO_TYPE:
343        raise SQLMeshError(
344            f"Invalid engine '{engine_type}'. Please specify one of '{engine_types}'."
345        )
346
347    models: t.Set[t.Tuple[str, str]] = set()
348    settings = None
349    if engine_type and template == ProjectTemplate.DLT:
350        project_dialect = dialect or DIALECT_TO_TYPE.get(engine_type)
351        if pipeline and project_dialect:
352            dlt_models, settings, start = generate_dlt_models_and_settings(
353                pipeline_name=pipeline, dialect=project_dialect, dlt_path=dlt_path
354            )
355        else:
356            raise SQLMeshError(
357                "Please provide a DLT pipeline with the `--dlt-pipeline` flag to generate a SQLMesh project from DLT."
358            )
359
360    _create_config(config_path, engine_type, dialect, settings, start, template, cli_mode)
361    if template == ProjectTemplate.DBT:
362        return config_path
363
364    _create_folders([audits_path, macros_path, models_path, seeds_path, tests_path])
365
366    if template == ProjectTemplate.DLT:
367        _create_object_files(
368            models_path, {model[0].split(".")[-1]: model[1] for model in dlt_models}, "sql"
369        )
370        return config_path
371
372    example_objects = _gen_example_objects(schema_name=schema_name)
373
374    if template != ProjectTemplate.EMPTY:
375        _create_object_files(models_path, example_objects.sql_models, "sql")
376        _create_object_files(models_path, example_objects.python_models, "py")
377        _create_object_files(seeds_path, example_objects.seeds, "csv")
378        _create_object_files(audits_path, example_objects.audits, "sql")
379        _create_object_files(tests_path, example_objects.tests, "yaml")
380        _create_object_files(macros_path, example_objects.python_macros, "py")
381        _create_object_files(macros_path, example_objects.sql_macros, "sql")
382
383    return config_path
def interactive_init( path: pathlib.Path, console: rich.console.Console, project_template: Optional[ProjectTemplate] = None) -> Tuple[ProjectTemplate, Optional[str], Optional[InitCliMode]]:
420def interactive_init(
421    path: Path,
422    console: Console,
423    project_template: t.Optional[ProjectTemplate] = None,
424) -> t.Tuple[ProjectTemplate, t.Optional[str], t.Optional[InitCliMode]]:
425    console.print("──────────────────────────────")
426    console.print("Welcome to SQLMesh!")
427
428    project_template = _init_template_prompt(console) if not project_template else project_template
429
430    if project_template == ProjectTemplate.DBT:
431        return (project_template, None, None)
432
433    engine_type = _init_engine_prompt(console)
434    cli_mode = _init_cli_mode_prompt(console)
435
436    return (project_template, engine_type, cli_mode)