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.
- sqlmesh/_version.py +2 -2
- sqlmesh/cli/main.py +75 -11
- sqlmesh/cli/project_init.py +497 -0
- sqlmesh/core/config/connection.py +73 -0
- sqlmesh/integrations/dlt.py +6 -2
- sqlmesh/lsp/main.py +106 -35
- sqlmesh/magics.py +11 -5
- sqlmesh/utils/yaml.py +2 -0
- {sqlmesh-0.195.1.dev3.dist-info → sqlmesh-0.195.1.dev5.dist-info}/METADATA +1 -1
- {sqlmesh-0.195.1.dev3.dist-info → sqlmesh-0.195.1.dev5.dist-info}/RECORD +57 -57
- web/client/dist/assets/{Audits-sYYNn2gO.js → Audits-DrNQaTQA.js} +1 -1
- web/client/dist/assets/{Banner-CIUVdc-z.js → Banner-DsG8aGIL.js} +1 -1
- web/client/dist/assets/{ChevronDownIcon-uQGmwNOL.js → ChevronDownIcon-CpQjfO01.js} +1 -1
- web/client/dist/assets/{ChevronRightIcon-B65l6fcy.js → ChevronRightIcon-B-9lnqGr.js} +1 -1
- web/client/dist/assets/{Content-CPscdsPQ.js → Content-CSMzf1cU.js} +1 -1
- web/client/dist/assets/{Content-CAmZChZo.js → Content-vfltDlhj.js} +1 -1
- web/client/dist/assets/{Data-CexnpfbT.js → Data-k1Dz6qdS.js} +1 -1
- web/client/dist/assets/{DataCatalog-zqv8aDld.js → DataCatalog-DlbWZyBp.js} +1 -1
- web/client/dist/assets/{Editor-EsVsPMR8.js → Editor--iBTA9xH.js} +1 -1
- web/client/dist/assets/{Editor-BPOAPXLv.js → Editor-C2pcRDlN.js} +1 -1
- web/client/dist/assets/{Errors-0iATm2JG.js → Errors-CQW3lhsM.js} +1 -1
- web/client/dist/assets/{FileExplorer-PTjWgl3B.js → FileExplorer-IAIPoTxM.js} +1 -1
- web/client/dist/assets/{Footer-MSdMRGiG.js → Footer-D77vCxKH.js} +1 -1
- web/client/dist/assets/{Header-SKoNqU5i.js → Header-Ccb81kaP.js} +1 -1
- web/client/dist/assets/{Input-Cl0rlyUj.js → Input-D_Fp4W_B.js} +1 -1
- web/client/dist/assets/Lineage-BHLfUAmC.js +1 -0
- web/client/dist/assets/{ListboxShow-mv_m9Xqu.js → ListboxShow-BIPQ-cqE.js} +1 -1
- web/client/dist/assets/{ModelLineage-JG9oPMA2.js → ModelLineage-BrTZ2lm3.js} +1 -1
- web/client/dist/assets/{Models-Bq6zaOiV.js → Models-B6cHeIlA.js} +1 -1
- web/client/dist/assets/{Page-D8EIuqN3.js → Page-S1ykkm5T.js} +1 -1
- web/client/dist/assets/{Plan-Cr31O86c.js → Plan-DCD4m0nD.js} +1 -1
- web/client/dist/assets/{PlusCircleIcon-BA3W9uiU.js → PlusCircleIcon-C40sAC-m.js} +1 -1
- web/client/dist/assets/{ReportErrors-CGhLWJSn.js → ReportErrors-CxqgDcYY.js} +1 -1
- web/client/dist/assets/{Root-Cm9f5yLR.js → Root-DtUrFfE_.js} +1 -1
- web/client/dist/assets/{SearchList-KwGRUK3b.js → SearchList-DoRflby9.js} +1 -1
- web/client/dist/assets/{SelectEnvironment-Fdveg7Dn.js → SelectEnvironment-equ86mF1.js} +1 -1
- web/client/dist/assets/{SourceList-CrxTdTD0.js → SourceList-DT-sBCAQ.js} +1 -1
- web/client/dist/assets/{SourceListItem-CbL1dN69.js → SourceListItem-MFS9vpg9.js} +1 -1
- web/client/dist/assets/{SplitPane-Di60F7Gk.js → SplitPane-D2cB3Qkv.js} +1 -1
- web/client/dist/assets/{Tests-Cjw3dIQ9.js → Tests-Duzk1K8j.js} +1 -1
- web/client/dist/assets/{Welcome-Cwxy-j_l.js → Welcome-BgAHQe0m.js} +1 -1
- web/client/dist/assets/{context-DKh7qNl9.js → context-CXV8eAaM.js} +1 -1
- web/client/dist/assets/{context-MffevtK7.js → context-g_ECqgnp.js} +1 -1
- web/client/dist/assets/{editor-BYglYCad.js → editor-BddPKujl.js} +1 -1
- web/client/dist/assets/{file-BATQOjrJ.js → file-DUr_Gx7F.js} +1 -1
- web/client/dist/assets/{floating-ui.react-dom-DHc129jH.js → floating-ui.react-dom-DTKAUIBI.js} +1 -1
- web/client/dist/assets/{help-iFVHtKo0.js → help-LQUMs6nO.js} +1 -1
- web/client/dist/assets/{index-DA7iL93d.js → index-BsS6H8XQ.js} +1 -1
- web/client/dist/assets/{index-DZ2vKQ1l.js → index-DvxjegO0.js} +2 -2
- web/client/dist/assets/{plan-8UAMXVjx.js → plan-DS237yuz.js} +1 -1
- web/client/dist/assets/{popover-DE6_QtGZ.js → popover-CiiY17g7.js} +1 -1
- web/client/dist/assets/{project-CfZrmqZ7.js → project-DUHOhQT2.js} +1 -1
- web/client/dist/index.html +1 -1
- sqlmesh/cli/example_project.py +0 -338
- web/client/dist/assets/Lineage-VxUfloHd.js +0 -1
- {sqlmesh-0.195.1.dev3.dist-info → sqlmesh-0.195.1.dev5.dist-info}/WHEEL +0 -0
- {sqlmesh-0.195.1.dev3.dist-info → sqlmesh-0.195.1.dev5.dist-info}/entry_points.txt +0 -0
- {sqlmesh-0.195.1.dev3.dist-info → sqlmesh-0.195.1.dev5.dist-info}/licenses/LICENSE +0 -0
- {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.
|
|
21
|
-
__version_tuple__ = version_tuple = (0, 195, 1, '
|
|
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.
|
|
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("
|
|
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
|
-
|
|
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
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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())
|