Skip to content

Commit b717a9d

Browse files
authored
Merge branch 'main' into lint-scope-schema-resolution
2 parents 038f6d6 + 86cd3b7 commit b717a9d

20 files changed

Lines changed: 684 additions & 23 deletions

File tree

.github/workflows/pr.yaml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -514,7 +514,12 @@ jobs:
514514
echo "DBT version is ${{ matrix.dbt-version }} (>= 1.6.0), keeping semantic_models and metrics sections"
515515
fi
516516
517-
make dbt-fast-test
517+
# dbt 1.6 installs without web/lsp (pydantic conflict); skip collecting those tests
518+
if [[ "${{ matrix.dbt-version }}" == "1.6" ]]; then
519+
pytest -n auto -m "dbt and fast" --reruns 3 --ignore=tests/lsp --ignore=tests/web
520+
else
521+
make dbt-fast-test
522+
fi
518523
- name: Test SQLMesh info in sushi_dbt
519524
working-directory: ./examples/sushi_dbt
520525
run: |

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ Initial TSC voting members are the project's Maintainers:
1414
|---------------------|---------------|----------------|------------|
1515
| Alexander Butler | z3z1ma | Harness | TSC Member |
1616
| Alexander Filipchik | afilipchik | Cloud Kitchens | TSC Member |
17-
| Reid Hooper | rhooper9711 | Benzinga | TSC Member |
17+
| Cortland Goffena | cmgoffena13 | Benzinga | TSC Member |
1818
| Yuki Kakegawa | StuffbyYuki | Jump.ai | TSC Member |
1919
| Toby Mao | tobymao | Fivetran | TSC Chair |
2020
| Alex Wilde | alexminerv | Minerva | TSC Member |

Makefile

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,12 @@ install-dev-dbt-%:
4646
echo "Applying numpy<2 constraint for dbt $$version"; \
4747
$(SED_INPLACE) 's/"numpy"/"numpy<2"/g' pyproject.toml; \
4848
fi; \
49-
$(MAKE) install-dev; \
49+
if [ "$$version" = "1.6.0" ]; then \
50+
echo "Installing without web/lsp for dbt 1.6.0 (fastapi>=0.136 needs pydantic v2; dbt 1.6 needs pydantic v1)"; \
51+
$(PIP) install -e ".[dev,slack,dlt]" ./examples/custom_materializations; \
52+
else \
53+
$(MAKE) install-dev; \
54+
fi; \
5055
if [ "$$version" = "1.6.0" ]; then \
5156
echo "Applying overrides for dbt 1.6.0"; \
5257
$(PIP) install 'pydantic>=2.0.0' 'google-cloud-bigquery==3.30.0' 'databricks-sdk==0.28.0' \

docs/concepts/macros/sqlmesh_macros.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1608,7 +1608,7 @@ def add_args(
16081608
return argument_1 + argument_2 + argument_3
16091609
```
16101610

1611-
An `@add_args` call providing values for all arguments accepts positional arguments like this: `@add_args(5, 6, 7)` (which returns 5 + 6 + 7 = `18`). A call omitting and using the default value for the the final `argument_3` can also use positional arguments: `@add_args(5, 6)` (which returns 5 + 6 + 3 = `14`).
1611+
An `@add_args` call providing values for all arguments accepts positional arguments like this: `@add_args(5, 6, 7)` (which returns 5 + 6 + 7 = `18`). A call omitting and using the default value for the final `argument_3` can also use positional arguments: `@add_args(5, 6)` (which returns 5 + 6 + 3 = `14`).
16121612

16131613
However, skipping an argument requires specifying the names of subsequent arguments (i.e., using "keyword arguments"). For example, skipping the second argument above by just omitting it - `@add_args(5, , 7)` - results in an error.
16141614

docs/concepts/models/model_kinds.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -583,7 +583,7 @@ MODEL (
583583

584584
### When Matched Expression
585585

586-
The logic to use when updating columns when a match occurs (the source and target match on the given keys) by default updates all the columns. This can be overriden with custom logic like below:
586+
The logic to use when updating columns when a match occurs (the source and target match on the given keys) by default updates all the columns. This can be overridden with custom logic like below:
587587

588588
```sql linenums="1" hl_lines="5"
589589
MODEL (
@@ -1437,7 +1437,7 @@ GROUP BY
14371437

14381438
SCD Type 2 models are designed by default to protect the data that has been captured because it is not possible to recreate the history once it has been lost.
14391439
However, there are cases where you may want to clear the history and start fresh.
1440-
For this use use case you will want to start by setting `disable_restatement` to `false` in the model definition.
1440+
For this use case you will want to start by setting `disable_restatement` to `false` in the model definition.
14411441

14421442
```sql linenums="1" hl_lines="5"
14431443
MODEL (

docs/guides/ui.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ You may include all a project's models by clicking `All` in the Show drop-down o
225225

226226
![Lineage module - all models](./ui/ui-guide_lineage-all.png){ loading=lazy }
227227

228-
Click `Connected` in the Show drop-down menu to highlight edges between upstream parents and downstream children in blue. This may be helpful when when a project contains many models:
228+
Click `Connected` in the Show drop-down menu to highlight edges between upstream parents and downstream children in blue. This may be helpful when a project contains many models:
229229

230230
![Lineage module - all models, connected edges](./ui/ui-guide_lineage-all-connected.png){ loading=lazy }
231231

docs/quickstart/notebook.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ You've now created a new production environment with all of history backfilled.
248248

249249
## 3. Update a model
250250

251-
Now that we have have populated the `prod` environment, let's modify one of the SQL models.
251+
Now that we have populated the `prod` environment, let's modify one of the SQL models.
252252

253253
We can modify the incremental SQL model using the `%model` *line* notebook magic (note the single `%`) and the model name:
254254

pyproject.toml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,15 +134,19 @@ snowflake = [
134134
starrocks = ["pymysql"]
135135
trino = ["trino"]
136136
web = [
137-
"fastapi==0.120.1",
137+
"fastapi>=0.136.3; python_version >= '3.10'",
138+
"starlette>=1.0.1; python_version >= '3.10'",
139+
"fastapi==0.120.1; python_version < '3.10'",
138140
"watchfiles>=0.19.0",
139141
"uvicorn[standard]==0.22.0",
140142
"sse-starlette>=0.2.2",
141143
"pyarrow",
142144
]
143145
lsp = [
144146
# Duplicate of web
145-
"fastapi==0.120.1",
147+
"fastapi>=0.136.3; python_version >= '3.10'",
148+
"starlette>=1.0.1; python_version >= '3.10'",
149+
"fastapi==0.120.1; python_version < '3.10'",
146150
"watchfiles>=0.19.0",
147151
# "uvicorn[standard]==0.22.0",
148152
"sse-starlette>=0.2.2",

sqlmesh/cli/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -645,7 +645,7 @@ def run(ctx: click.Context, environment: t.Optional[str] = None, **kwargs: t.Any
645645
def invalidate(ctx: click.Context, environment: str, **kwargs: t.Any) -> None:
646646
"""Invalidate the target environment, forcing its removal during the next run of the janitor process."""
647647
context = ctx.obj
648-
context.invalidate_environment(environment, **kwargs)
648+
context.invalidate_environment(environment, must_exist=True, **kwargs)
649649

650650

651651
@cli.command("janitor")

sqlmesh/core/context.py

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1938,15 +1938,24 @@ def apply(
19381938
)
19391939

19401940
@python_api_analytics
1941-
def invalidate_environment(self, name: str, sync: bool = False) -> None:
1941+
def invalidate_environment(
1942+
self, name: str, sync: bool = False, must_exist: bool = False
1943+
) -> None:
19421944
"""Invalidates the target environment by setting its expiration timestamp to now.
19431945
19441946
Args:
19451947
name: The name of the environment to invalidate.
19461948
sync: If True, the call blocks until the environment is deleted. Otherwise, the environment will
19471949
be deleted asynchronously by the janitor process.
1950+
must_exist: If True, raise if the environment doesn't exist instead of silently doing nothing.
1951+
Used by the user-facing entry points, where a mistyped name should be reported rather than
1952+
look like it succeeded. Internal callers such as
1953+
`GithubController.try_invalidate_pr_environment` rely on the default no-op behavior, since
1954+
a PR environment may never have been created.
19481955
"""
19491956
name = Environment.sanitize_name(name)
1957+
if must_exist and self.state_sync.get_environment(name) is None:
1958+
raise SQLMeshError(f"Environment '{name}' was not found.")
19501959
self.state_sync.invalidate_environment(name)
19511960
if sync:
19521961
self._cleanup_environments(name=name)
@@ -3138,10 +3147,17 @@ def _cleanup_environments(
31383147
expired_env = self.state_reader.get_environment(expired_env_summary.name)
31393148

31403149
if expired_env:
3150+
cleanup_default_adapter, cleanup_engine_adapters, failure = (
3151+
self._cleanup_adapters_for_environment(expired_env)
3152+
)
3153+
if failure:
3154+
logger.warning(failure)
3155+
failures.append(failure)
3156+
continue
31413157
failures.extend(
31423158
cleanup_expired_views(
3143-
default_adapter=self.engine_adapter,
3144-
engine_adapters=self.engine_adapters,
3159+
default_adapter=cleanup_default_adapter,
3160+
engine_adapters=cleanup_engine_adapters,
31453161
environments=[expired_env],
31463162
console=self.console,
31473163
)
@@ -3153,6 +3169,62 @@ def _cleanup_environments(
31533169
self.state_sync.delete_expired_environments(current_ts=current_ts, name=name)
31543170
return failures
31553171

3172+
def _cleanup_adapters_for_environment(
3173+
self, environment: Environment
3174+
) -> t.Tuple[EngineAdapter, t.Dict[str, EngineAdapter], t.Optional[str]]:
3175+
"""Create cleanup-scoped adapters for an expired environment.
3176+
3177+
Persisted catalog-qualified view names indicate that virtual catalog injection was active,
3178+
so cleanup can clone only the selected adapters with the historical catalog and leave the
3179+
context's adapters unchanged.
3180+
"""
3181+
engine_adapters = self.engine_adapters
3182+
default_adapter = self.engine_adapter
3183+
catalogs_by_gateway: t.Dict[str, t.Set[str]] = collections.defaultdict(set)
3184+
3185+
for snapshot in environment.snapshots:
3186+
if not snapshot.is_model or snapshot.is_symbolic:
3187+
continue
3188+
3189+
gateway = (
3190+
snapshot.model_gateway
3191+
if environment.gateway_managed and snapshot.model_gateway in engine_adapters
3192+
else self.selected_gateway
3193+
)
3194+
adapter = engine_adapters.get(gateway, default_adapter)
3195+
catalog = snapshot.qualified_view_name.catalog_for_environment(
3196+
environment.naming_info, dialect=adapter.dialect
3197+
)
3198+
if catalog and adapter.supports_virtual_catalog() is True:
3199+
catalogs_by_gateway[gateway].add(catalog)
3200+
3201+
for gateway, catalogs in catalogs_by_gateway.items():
3202+
if len(catalogs) > 1:
3203+
catalogs_description = ", ".join(f"'{catalog}'" for catalog in sorted(catalogs))
3204+
return (
3205+
default_adapter,
3206+
engine_adapters,
3207+
(
3208+
f"Failed to clean up expired environment '{environment.name}': gateway "
3209+
f"'{gateway}' references multiple virtual catalogs: {catalogs_description}"
3210+
),
3211+
)
3212+
3213+
cleanup_engine_adapters = engine_adapters.copy()
3214+
cleanup_default_adapter = default_adapter
3215+
for gateway, catalogs in catalogs_by_gateway.items():
3216+
cleanup_adapter = engine_adapters.get(gateway, default_adapter).with_settings()
3217+
cleanup_adapter.inject_virtual_catalog(gateway)
3218+
# inject_virtual_catalog() may initialize adapter-specific state in addition to
3219+
# _default_catalog. Override only the cleanup clone with the catalog persisted in the
3220+
# expired environment so historical names pass SINGLE_CATALOG_ONLY validation.
3221+
cleanup_adapter._default_catalog = next(iter(catalogs))
3222+
cleanup_engine_adapters[gateway] = cleanup_adapter
3223+
if gateway == self.selected_gateway:
3224+
cleanup_default_adapter = cleanup_adapter
3225+
3226+
return cleanup_default_adapter, cleanup_engine_adapters, None
3227+
31563228
def _try_connection(self, connection_name: str, validator: t.Callable[[], None]) -> None:
31573229
connection_name = connection_name.capitalize()
31583230
try:

0 commit comments

Comments
 (0)