Skip to content

Commit 0bbca57

Browse files
Merge remote-tracking branch 'upstream/main' into feat/add-fabric-engine
2 parents 933a765 + 9a954d7 commit 0bbca57

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+1858
-464
lines changed

.circleci/continue_config.yml

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -201,39 +201,6 @@ jobs:
201201
name: Run tests
202202
command: npm --prefix web/client run test
203203

204-
trigger_private_tests:
205-
docker:
206-
- image: cimg/python:3.12.0
207-
resource_class: small
208-
steps:
209-
- checkout
210-
- run:
211-
name: Install setuptools scm
212-
command: pip install setuptools_scm
213-
- run:
214-
name: Trigger private tests
215-
command: |
216-
export COMMIT_MESSAGE="$(git log --format=%s -n 1 $CIRCLE_SHA1)"
217-
export FORMATTED_COMMIT_MESSAGE="${COMMIT_MESSAGE//\"/\\\"}"
218-
# returns a version string like 0.1.0.dev11
219-
export PACKAGE_VERSION="$(python ./.circleci/get_scm_version.py)"
220-
curl --request POST \
221-
--url $TOBIKO_PRIVATE_CIRCLECI_URL \
222-
--header "Circle-Token: $TOBIKO_PRIVATE_CIRCLECI_KEY" \
223-
--header "content-type: application/json" \
224-
--data '{
225-
"branch":"main",
226-
"parameters":{
227-
"run_main_pr":false,
228-
"run_sqlmesh_commit":true,
229-
"sqlmesh_branch":"'$CIRCLE_BRANCH'",
230-
"sqlmesh_commit_author":"'$CIRCLE_USERNAME'",
231-
"sqlmesh_commit_hash":"'$CIRCLE_SHA1'",
232-
"sqlmesh_commit_message":"'"$FORMATTED_COMMIT_MESSAGE"'",
233-
"sqlmesh_package_version":"'$PACKAGE_VERSION'"
234-
}
235-
}'
236-
237204
engine_tests_docker:
238205
parameters:
239206
engine:
@@ -340,13 +307,6 @@ workflows:
340307
branches:
341308
only:
342309
- main
343-
- trigger_private_tests:
344-
requires:
345-
- style_and_cicd_tests
346-
filters:
347-
branches:
348-
only:
349-
- main
350310
- ui_style
351311
- ui_test
352312
- vscode_test

docs/integrations/github.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ Below is an example of how to define the default config for the bot in either YA
300300
| `run_on_deploy_to_prod` | Indicates whether to run latest intervals when deploying to prod. If set to false, the deployment will backfill only the changed models up to the existing latest interval in production, ignoring any missing intervals beyond this point. Default: `False` | bool | N |
301301
| `pr_environment_name` | The name of the PR environment to create for which a PR number will be appended to. Defaults to the repo name if not provided. Note: The name will be normalized to alphanumeric + underscore and lowercase. | str | N |
302302
| `prod_branch_name` | The name of the git branch associated with production. Ex: `prod`. Default: `main` or `master` is considered prod | str | N |
303+
| `forward_only_branch_suffix` | If the git branch has this suffix, trigger a [forward-only](../concepts/plans.md#forward-only-plans) plan instead of a normal plan. Default: `-forward-only` | str | N |
303304

304305
Example with all properties defined:
305306

docs/reference/cli.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ Options:
149149
```
150150
Usage: sqlmesh destroy
151151

152-
Removes all project resources, including warehouse objects, state tables, the SQLMesh cache and any build artifacts.
152+
Removes all state tables, the SQLMesh cache and all project resources, including warehouse objects. This includes all tables, views and schemas managed by SQLMesh, as well as any external resources that may have been created by other tools within those schemas.
153153

154154
Options:
155155
--help Show this message and exit.

docs/reference/notebook.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ options:
250250
```
251251
%destroy
252252

253-
Removes all project resources, including warehouse objects, state tables, the SQLMesh cache and any build artifacts.
253+
Removes all state tables, the SQLMesh cache, and other project resources, including warehouse objects. This includes all tables, views, and schemas managed by SQLMesh, as well as any external resources that may have been created by other tools within those schemas.
254254
```
255255

256256
#### dlt_refresh

sqlmesh/cli/options.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ def format_options(func: t.Callable) -> t.Callable:
6363
"--normalize",
6464
is_flag=True,
6565
help="Whether or not to normalize identifiers to lowercase.",
66+
default=None,
6667
)(func)
6768
func = click.option(
6869
"--pad",
@@ -82,6 +83,7 @@ def format_options(func: t.Callable) -> t.Callable:
8283
func = click.option(
8384
"--leading-comma",
8485
is_flag=True,
86+
default=None,
8587
help="Determines whether or not the comma is leading or trailing in select expressions. Default is trailing.",
8688
)(func)
8789
func = click.option(

sqlmesh/core/config/connection.py

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -354,11 +354,28 @@ def init(cursor: duckdb.DuckDBPyConnection) -> None:
354354
except Exception as e:
355355
raise ConfigError(f"Failed to load extension {extension['name']}: {e}")
356356

357-
for field, setting in self.connector_config.items():
358-
try:
359-
cursor.execute(f"SET {field} = '{setting}'")
360-
except Exception as e:
361-
raise ConfigError(f"Failed to set connector config {field} to {setting}: {e}")
357+
if self.connector_config:
358+
option_names = list(self.connector_config)
359+
in_part = ",".join("?" for _ in range(len(option_names)))
360+
361+
cursor.execute(
362+
f"SELECT name, value FROM duckdb_settings() WHERE name IN ({in_part})",
363+
option_names,
364+
)
365+
366+
existing_values = {field: setting for field, setting in cursor.fetchall()}
367+
368+
# only set connector_config items if the values differ from what is already set
369+
# trying to set options like 'temp_directory' even to the same value can throw errors like:
370+
# Not implemented Error: Cannot switch temporary directory after the current one has been used
371+
for field, setting in self.connector_config.items():
372+
if existing_values.get(field) != setting:
373+
try:
374+
cursor.execute(f"SET {field} = '{setting}'")
375+
except Exception as e:
376+
raise ConfigError(
377+
f"Failed to set connector config {field} to {setting}: {e}"
378+
)
362379

363380
if self.secrets:
364381
duckdb_version = duckdb.__version__
@@ -386,7 +403,9 @@ def init(cursor: duckdb.DuckDBPyConnection) -> None:
386403
if secret_settings:
387404
secret_clause = ", ".join(secret_settings)
388405
try:
389-
cursor.execute(f"CREATE SECRET {secret_name} ({secret_clause});")
406+
cursor.execute(
407+
f"CREATE OR REPLACE SECRET {secret_name} ({secret_clause});"
408+
)
390409
except Exception as e:
391410
raise ConfigError(f"Failed to create secret: {e}")
392411

sqlmesh/core/config/loader.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,8 +184,14 @@ def load_config_from_python_module(
184184
module_path: Path,
185185
config_name: str = "config",
186186
) -> C:
187-
with sys_path(module_path.parent):
188-
config_module = import_python_file(module_path, module_path.parent)
187+
try:
188+
with sys_path(module_path.parent):
189+
config_module = import_python_file(module_path, module_path.parent)
190+
except Exception as e:
191+
raise ConfigError(
192+
f"Failed to load config file: {e}",
193+
location=module_path,
194+
)
189195

190196
try:
191197
config_obj = getattr(config_module, config_name)

sqlmesh/core/console.py

Lines changed: 99 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -186,9 +186,19 @@ class DestroyConsole(abc.ABC):
186186
"""Console for describing a destroy operation"""
187187

188188
@abc.abstractmethod
189-
def start_destroy(self) -> bool:
189+
def start_destroy(
190+
self,
191+
schemas_to_delete: t.Optional[t.Set[str]] = None,
192+
views_to_delete: t.Optional[t.Set[str]] = None,
193+
tables_to_delete: t.Optional[t.Set[str]] = None,
194+
) -> bool:
190195
"""Start a destroy operation.
191196

197+
Args:
198+
schemas_to_delete: Set of schemas that will be deleted
199+
views_to_delete: Set of views that will be deleted
200+
tables_to_delete: Set of tables that will be deleted
201+
192202
Returns:
193203
Whether or not the destroy operation should proceed
194204
"""
@@ -289,11 +299,17 @@ def show_row_diff(
289299

290300
class BaseConsole(abc.ABC):
291301
@abc.abstractmethod
292-
def log_error(self, message: str) -> None:
302+
def log_error(self, message: str, *args: t.Any, **kwargs: t.Any) -> None:
293303
"""Display error info to the user."""
294304

295305
@abc.abstractmethod
296-
def log_warning(self, short_message: str, long_message: t.Optional[str] = None) -> None:
306+
def log_warning(
307+
self,
308+
short_message: str,
309+
long_message: t.Optional[str] = None,
310+
*args: t.Any,
311+
**kwargs: t.Any,
312+
) -> None:
297313
"""Display warning info to the user.
298314

299315
Args:
@@ -824,7 +840,12 @@ def print_connection_config(
824840
) -> None:
825841
pass
826842

827-
def start_destroy(self) -> bool:
843+
def start_destroy(
844+
self,
845+
schemas_to_delete: t.Optional[t.Set[str]] = None,
846+
views_to_delete: t.Optional[t.Set[str]] = None,
847+
tables_to_delete: t.Optional[t.Set[str]] = None,
848+
) -> bool:
828849
return True
829850

830851
def stop_destroy(self, success: bool = True) -> None:
@@ -1276,16 +1297,40 @@ def stop_cleanup(self, success: bool = False) -> None:
12761297
else:
12771298
self.log_error("Cleanup failed!")
12781299

1279-
def start_destroy(self) -> bool:
1300+
def start_destroy(
1301+
self,
1302+
schemas_to_delete: t.Optional[t.Set[str]] = None,
1303+
views_to_delete: t.Optional[t.Set[str]] = None,
1304+
tables_to_delete: t.Optional[t.Set[str]] = None,
1305+
) -> bool:
12801306
self.log_warning(
1281-
(
1282-
"This will permanently delete all engine-managed objects, state tables and SQLMesh cache.\n"
1283-
"The operation is irreversible and may disrupt any currently running or scheduled plans.\n"
1284-
"Use this command only when you intend to fully reset the project."
1285-
)
1307+
"This will permanently delete all engine-managed objects, state tables and SQLMesh cache.\n"
1308+
"The operation may disrupt any currently running or scheduled plans.\n"
12861309
)
1287-
if not self._confirm("Proceed?"):
1288-
self.log_error("Destroy aborted!")
1310+
1311+
if schemas_to_delete or views_to_delete or tables_to_delete:
1312+
if schemas_to_delete:
1313+
self.log_error("Schemas to be deleted:")
1314+
for schema in sorted(schemas_to_delete):
1315+
self.log_error(f" • {schema}")
1316+
1317+
if views_to_delete:
1318+
self.log_error("\nEnvironment views to be deleted:")
1319+
for view in sorted(views_to_delete):
1320+
self.log_error(f" • {view}")
1321+
1322+
if tables_to_delete:
1323+
self.log_error("\nSnapshot tables to be deleted:")
1324+
for table in sorted(tables_to_delete):
1325+
self.log_error(f" • {table}")
1326+
1327+
self.log_error(
1328+
"\nThis action will DELETE ALL the above resources managed by SQLMesh AND\n"
1329+
"potentially external resources created by other tools in these schemas.\n"
1330+
)
1331+
1332+
if not self._confirm("Are you ABSOLUTELY SURE you want to proceed with deletion?"):
1333+
self.log_error("Destroy operation cancelled.")
12891334
return False
12901335
return True
12911336

@@ -2711,8 +2756,16 @@ def _cells_match(x: t.Any, y: t.Any) -> bool:
27112756
def _normalize(val: t.Any) -> t.Any:
27122757
# Convert Pandas null to Python null for the purposes of comparison to prevent errors like the following on boolean fields:
27132758
# - TypeError: boolean value of NA is ambiguous
2714-
if pd.isnull(val):
2759+
# note pd.isnull() returns either a bool or a ndarray[bool] depending on if the input
2760+
# is scalar or an array
2761+
isnull = pd.isnull(val)
2762+
2763+
if isinstance(isnull, bool): # scalar
2764+
if isnull:
2765+
val = None
2766+
elif all(isnull): # array
27152767
val = None
2768+
27162769
return list(val) if isinstance(val, (pd.Series, np.ndarray)) else val
27172770

27182771
return _normalize(x) == _normalize(y)
@@ -3082,15 +3135,23 @@ def consume_captured_errors(self) -> str:
30823135
finally:
30833136
self._errors = []
30843137

3085-
def log_warning(self, short_message: str, long_message: t.Optional[str] = None) -> None:
3138+
def log_warning(
3139+
self,
3140+
short_message: str,
3141+
long_message: t.Optional[str] = None,
3142+
*args: t.Any,
3143+
**kwargs: t.Any,
3144+
) -> None:
30863145
if short_message not in self._warnings:
30873146
self._warnings.append(short_message)
3088-
super().log_warning(short_message, long_message)
3147+
if kwargs.pop("print", True):
3148+
super().log_warning(short_message, long_message)
30893149

3090-
def log_error(self, message: str) -> None:
3150+
def log_error(self, message: str, *args: t.Any, **kwargs: t.Any) -> None:
30913151
if message not in self._errors:
30923152
self._errors.append(message)
3093-
super().log_error(message)
3153+
if kwargs.pop("print", True):
3154+
super().log_error(message)
30943155

30953156
def log_skipped_models(self, snapshot_names: t.Set[str]) -> None:
30963157
if snapshot_names:
@@ -3127,6 +3188,11 @@ def __init__(self, **kwargs: t.Any) -> None:
31273188
kwargs.pop("alert_block_collapsible_threshold", 200)
31283189
)
31293190

3191+
# capture_only = True: capture but dont print to console
3192+
# capture_only = False: capture and also print to console
3193+
self.warning_capture_only = kwargs.pop("warning_capture_only", False)
3194+
self.error_capture_only = kwargs.pop("error_capture_only", False)
3195+
31303196
super().__init__(
31313197
**{**kwargs, "console": RichConsole(no_color=True, width=kwargs.pop("width", None))}
31323198
)
@@ -3401,6 +3467,12 @@ def stop_promotion_progress(self, success: bool = True) -> None:
34013467
super().stop_promotion_progress(success)
34023468
self._print("\n")
34033469

3470+
def log_warning(self, short_message: str, long_message: t.Optional[str] = None) -> None:
3471+
super().log_warning(short_message, long_message, print=not self.warning_capture_only)
3472+
3473+
def log_error(self, message: str) -> None:
3474+
super().log_error(message, print=not self.error_capture_only)
3475+
34043476
def log_success(self, message: str) -> None:
34053477
self._print(message)
34063478

@@ -3427,19 +3499,24 @@ def log_test_results(self, result: ModelTextTestResult, target_dialect: str) ->
34273499

34283500
def log_skipped_models(self, snapshot_names: t.Set[str]) -> None:
34293501
if snapshot_names:
3430-
msg = " " + "\n ".join(snapshot_names)
3431-
self._print(f"**Skipped models**\n\n{msg}")
3502+
self._print(f"**Skipped models**")
3503+
for snapshot_name in snapshot_names:
3504+
self._print(f"* `{snapshot_name}`")
3505+
self._print("")
34323506

34333507
def log_failed_models(self, errors: t.List[NodeExecutionFailedError]) -> None:
34343508
if errors:
3435-
self._print("\n```\nFailed models\n")
3509+
self._print("**Failed models**")
34363510

34373511
error_messages = _format_node_errors(errors)
34383512

34393513
for node_name, msg in error_messages.items():
3440-
self._print(f" **{node_name}**\n\n{msg}")
3514+
self._print(f"* `{node_name}`\n")
3515+
self._print(" ```")
3516+
self._print(msg)
3517+
self._print(" ```")
34413518

3442-
self._print("```\n")
3519+
self._print("")
34433520

34443521
def show_linter_violations(
34453522
self, violations: t.List[RuleViolation], model: Model, is_error: bool = False

0 commit comments

Comments
 (0)