Skip to content

Commit ab82fb0

Browse files
Gabe PescoGabe Pesco
authored andcommitted
Merge branch 'fix/multi-repo-create-external-models' of https://github.com/gabepesco/sqlmesh into fix/multi-repo-create-external-models
2 parents 7db3b9f + e59bc10 commit ab82fb0

40 files changed

Lines changed: 598 additions & 129 deletions

.github/workflows/pr.yaml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -188,15 +188,15 @@ jobs:
188188

189189
ui-style:
190190
needs: [changes]
191-
if:
192-
needs.changes.outputs.client == 'true' || needs.changes.outputs.ci ==
193-
'true' || github.ref == 'refs/heads/main'
191+
if: false
192+
# needs.changes.outputs.client == 'true' || needs.changes.outputs.ci ==
193+
# 'true' || github.ref == 'refs/heads/main'
194194
runs-on: ubuntu-latest
195195
steps:
196196
- uses: actions/checkout@v5
197197
- uses: actions/setup-node@v6
198198
with:
199-
node-version: '20'
199+
node-version: '22'
200200
- uses: pnpm/action-setup@v4
201201
with:
202202
version: latest
@@ -396,6 +396,7 @@ jobs:
396396
env:
397397
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
398398
runs-on: ubuntu-latest
399+
if: false
399400
steps:
400401
- uses: actions/checkout@v5
401402
- uses: actions/setup-node@v6

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ dependencies = [
2424
"requests",
2525
"rich[jupyter]",
2626
"ruamel.yaml",
27-
"sqlglot~=30.0.1",
27+
"sqlglot~=30.4.2",
2828
"tenacity",
2929
"time-machine",
3030
"json-stream"

sqlmesh/core/config/connection.py

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
ValidationInfo,
3535
field_validator,
3636
model_validator,
37+
validation_data,
3738
validation_error_message,
3839
get_concrete_types_from_typehint,
3940
)
@@ -813,6 +814,8 @@ class DatabricksConnectionConfig(ConnectionConfig):
813814
DISPLAY_NAME: t.ClassVar[t.Literal["Databricks"]] = "Databricks"
814815
DISPLAY_ORDER: t.ClassVar[t.Literal[3]] = 3
815816

817+
shared_connection: t.ClassVar[bool] = True
818+
816819
_concurrent_tasks_validator = concurrent_tasks_validator
817820
_http_headers_validator = http_headers_validator
818821

@@ -1081,7 +1084,7 @@ def validate_execution_project(
10811084
v: t.Optional[str],
10821085
info: ValidationInfo,
10831086
) -> t.Optional[str]:
1084-
if v and not info.data.get("project"):
1087+
if v and not validation_data(info).get("project"):
10851088
raise ConfigError(
10861089
"If the `execution_project` field is specified, you must also specify the `project` field to provide a default object location."
10871090
)
@@ -1093,7 +1096,7 @@ def validate_quota_project(
10931096
v: t.Optional[str],
10941097
info: ValidationInfo,
10951098
) -> t.Optional[str]:
1096-
if v and not info.data.get("project"):
1099+
if v and not validation_data(info).get("project"):
10971100
raise ConfigError(
10981101
"If the `quota_project` field is specified, you must also specify the `project` field to provide a default object location."
10991102
)
@@ -2341,35 +2344,28 @@ def init(cursor: t.Any) -> None:
23412344
return init
23422345

23432346

2347+
_CONNECTION_CONFIG_EXCLUDE: t.Set[t.Type[ConnectionConfig]] = {
2348+
ConnectionConfig, # type: ignore[type-abstract]
2349+
BaseDuckDBConnectionConfig, # type: ignore[type-abstract]
2350+
}
2351+
23442352
CONNECTION_CONFIG_TO_TYPE = {
23452353
# Map all subclasses of ConnectionConfig to the value of their `type_` field.
23462354
tpe.all_field_infos()["type_"].default: tpe
2347-
for tpe in subclasses(
2348-
__name__,
2349-
ConnectionConfig,
2350-
exclude={ConnectionConfig, BaseDuckDBConnectionConfig},
2351-
)
2355+
for tpe in subclasses(__name__, ConnectionConfig, exclude=_CONNECTION_CONFIG_EXCLUDE)
23522356
}
23532357

23542358
DIALECT_TO_TYPE = {
23552359
tpe.all_field_infos()["type_"].default: tpe.DIALECT
2356-
for tpe in subclasses(
2357-
__name__,
2358-
ConnectionConfig,
2359-
exclude={ConnectionConfig, BaseDuckDBConnectionConfig},
2360-
)
2360+
for tpe in subclasses(__name__, ConnectionConfig, exclude=_CONNECTION_CONFIG_EXCLUDE)
23612361
}
23622362

23632363
INIT_DISPLAY_INFO_TO_TYPE = {
23642364
tpe.all_field_infos()["type_"].default: (
23652365
tpe.DISPLAY_ORDER,
23662366
tpe.DISPLAY_NAME,
23672367
)
2368-
for tpe in subclasses(
2369-
__name__,
2370-
ConnectionConfig,
2371-
exclude={ConnectionConfig, BaseDuckDBConnectionConfig},
2372-
)
2368+
for tpe in subclasses(__name__, ConnectionConfig, exclude=_CONNECTION_CONFIG_EXCLUDE)
23732369
}
23742370

23752371

sqlmesh/core/config/scheduler.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,9 +144,10 @@ def get_default_catalog_per_gateway(self, context: GenericContext) -> t.Dict[str
144144
return default_catalogs_per_gateway
145145

146146

147-
SCHEDULER_CONFIG_TO_TYPE = {
147+
SCHEDULER_CONFIG_TO_TYPE: t.Dict[str, t.Type[SchedulerConfig]] = {
148148
tpe.all_field_infos()["type_"].default: tpe
149149
for tpe in subclasses(__name__, BaseConfig, exclude={BaseConfig})
150+
if issubclass(tpe, SchedulerConfig)
150151
}
151152

152153

sqlmesh/core/context.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1605,9 +1605,11 @@ def plan_builder(
16051605
backfill_models = None
16061606

16071607
models_override: t.Optional[UniqueKeyDict[str, Model]] = None
1608+
selected_fqns: t.Set[str] = set()
1609+
selected_deletion_fqns: t.Set[str] = set()
16081610
if select_models:
16091611
try:
1610-
models_override = model_selector.select_models(
1612+
models_override, selected_fqns = model_selector.select_models(
16111613
select_models,
16121614
environment,
16131615
fallback_env_name=create_from or c.PROD,
@@ -1622,12 +1624,17 @@ def plan_builder(
16221624
# Only backfill selected models unless explicitly specified.
16231625
backfill_models = model_selector.expand_model_selections(select_models)
16241626

1627+
if not backfill_models:
1628+
# The selection matched nothing locally. Check whether it matched models
1629+
# in the deployed environment that were deleted locally.
1630+
selected_deletion_fqns = selected_fqns - set(self._models)
1631+
16251632
expanded_restate_models = None
16261633
if restate_models is not None:
16271634
expanded_restate_models = model_selector.expand_model_selections(restate_models)
16281635

16291636
if (restate_models is not None and not expanded_restate_models) or (
1630-
backfill_models is not None and not backfill_models
1637+
backfill_models is not None and not backfill_models and not selected_deletion_fqns
16311638
):
16321639
raise PlanError(
16331640
"Selector did not return any models. Please check your model selection and try again."
@@ -1636,7 +1643,7 @@ def plan_builder(
16361643
if always_include_local_changes is None:
16371644
# default behaviour - if restatements are detected; we operate entirely out of state and ignore local changes
16381645
force_no_diff = restate_models is not None or (
1639-
backfill_models is not None and not backfill_models
1646+
backfill_models is not None and not backfill_models and not selected_deletion_fqns
16401647
)
16411648
else:
16421649
force_no_diff = not always_include_local_changes

sqlmesh/core/dialect.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from sqlglot.dialects.dialect import DialectType
1515
from sqlglot.dialects import DuckDB, Snowflake, TSQL
1616
import sqlglot.dialects.athena as athena
17+
import sqlglot.generators.athena as athena_generators
1718
from sqlglot.parsers.athena import AthenaTrinoParser
1819
from sqlglot.helper import seq_get
1920
from sqlglot.optimizer.normalize_identifiers import normalize_identifiers
@@ -1048,8 +1049,8 @@ def extend_sqlglot() -> None:
10481049
if dialect == athena.Athena:
10491050
tokenizers.add(athena._TrinoTokenizer)
10501051
parsers.add(AthenaTrinoParser)
1051-
generators.add(athena._TrinoGenerator)
1052-
generators.add(athena._HiveGenerator)
1052+
generators.add(athena_generators.AthenaTrinoGenerator)
1053+
generators.add(athena_generators._HiveGenerator)
10531054

10541055
if hasattr(dialect, "Tokenizer"):
10551056
tokenizers.add(dialect.Tokenizer)

sqlmesh/core/engine_adapter/base.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2920,7 +2920,11 @@ def _replace_by_key(
29202920
target_columns_to_types = self.columns(target_table)
29212921

29222922
temp_table = self._get_temp_table(target_table)
2923-
key_exp = exp.func("CONCAT_WS", "'__SQLMESH_DELIM__'", *key) if len(key) > 1 else key[0]
2923+
key_exp = (
2924+
exp.func("CONCAT_WS", "'__SQLMESH_DELIM__'", *key, dialect=self.dialect)
2925+
if len(key) > 1
2926+
else key[0]
2927+
)
29242928
column_names = list(target_columns_to_types or [])
29252929

29262930
with self.transaction():

sqlmesh/core/engine_adapter/clickhouse.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,11 @@ def _replace_by_key(
423423
source_columns=source_columns,
424424
)
425425

426-
key_exp = exp.func("CONCAT_WS", "'__SQLMESH_DELIM__'", *key) if len(key) > 1 else key[0]
426+
key_exp = (
427+
exp.func("CONCAT_WS", "'__SQLMESH_DELIM__'", *key, dialect=self.dialect)
428+
if len(key) > 1
429+
else key[0]
430+
)
427431

428432
self._insert_overwrite_by_condition(
429433
target_table,

sqlmesh/core/environment.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ def _sanitize_name(cls, v: str) -> str:
5656
@classmethod
5757
def _validate_boolean_field(cls, v: t.Any, info: ValidationInfo) -> bool:
5858
if v is None:
59-
return info.field_name == "normalize_name"
59+
# Pydantic 2.13+ sets field_name to None during model_validate_json()
60+
return (info.field_name or "") == "normalize_name"
6061
return bool(v)
6162

6263
@t.overload

sqlmesh/core/linter/rules/builtin.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,4 +318,5 @@ def check_model(self, model: Model) -> t.Optional[RuleViolation]:
318318
return None
319319

320320

321-
BUILTIN_RULES = RuleSet(subclasses(__name__, Rule, exclude={Rule}))
321+
_RULE_EXCLUDE: t.Set[t.Type[Rule]] = {Rule} # type: ignore[type-abstract]
322+
BUILTIN_RULES = RuleSet(subclasses(__name__, Rule, exclude=_RULE_EXCLUDE))

0 commit comments

Comments
 (0)