Skip to content

Commit cfb43cb

Browse files
authored
Merge branch 'main' into fix/remove-intervals-empty-guard
2 parents cccd4f6 + c0ae2db commit cfb43cb

7 files changed

Lines changed: 45 additions & 16 deletions

File tree

‎.devcontainer/devcontainer-lock.json‎

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,6 @@
1010
"resolved": "ghcr.io/devcontainers/features/docker-in-docker@sha256:4fa87399214366e320d489991769c4f3f461e1ffe461f54eea78a41b34945bb5",
1111
"integrity": "sha256:4fa87399214366e320d489991769c4f3f461e1ffe461f54eea78a41b34945bb5"
1212
},
13-
"ghcr.io/devcontainers/features/java:1": {
14-
"version": "1.8.1",
15-
"resolved": "ghcr.io/devcontainers/features/java@sha256:8157bab2d8d71e40b2f3128c162fab763e2b11038fdd33784549290a5d386b48",
16-
"integrity": "sha256:8157bab2d8d71e40b2f3128c162fab763e2b11038fdd33784549290a5d386b48"
17-
},
1813
"ghcr.io/devcontainers/features/python:1": {
1914
"version": "1.8.0",
2015
"resolved": "ghcr.io/devcontainers/features/python@sha256:fbcad6955caeecc5ad3f7886baf652e25cba5225a6c4c2287c536de2e5607511",

‎.devcontainer/devcontainer.json‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,6 @@
66
"ghcr.io/devcontainers/features/python:1": {
77
"version": "3.12"
88
},
9-
"ghcr.io/devcontainers/features/java:1": {
10-
"version": "21"
11-
},
129
"ghcr.io/devcontainers/features/docker-in-docker:4": {}
1310
},
1411
"postCreateCommand": "bash .devcontainer/post-create-command.sh",

‎.github/workflows/pr.yaml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,7 @@ jobs:
457457
strategy:
458458
fail-fast: false
459459
matrix:
460-
dbt-version: ['1.3', '1.4', '1.5', '1.6', '1.7', '1.8', '1.9', '1.10', '1.11']
460+
dbt-version: ['1.3', '1.4', '1.5', '1.6', '1.7', '1.8', '1.9', '1.10', '1.11', '1.12']
461461
steps:
462462
- uses: actions/checkout@v7
463463
- name: Set up Python

‎Makefile‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ install-dev-dbt-%:
3333
echo "Installing dbt version: $$version"; \
3434
cp pyproject.toml pyproject.toml.backup; \
3535
$(SED_INPLACE) 's/"pydantic>=2.0.0"/"pydantic"/g' pyproject.toml; \
36-
if [ "$$version" = "1.10.0" ] || [ "$$version" = "1.11.0" ]; then \
36+
if [ "$$version" = "1.10.0" ] || [ "$$version" = "1.11.0" ] || [ "$$version" = "1.12.0" ]; then \
3737
echo "Applying special handling for dbt $$version"; \
3838
$(SED_INPLACE) -E 's/"(dbt-core)[^"]*"/"\1~='"$$version"'"/g' pyproject.toml; \
3939
$(SED_INPLACE) -E 's/"(dbt-(bigquery|duckdb|snowflake|athena-community|clickhouse|redshift|trino))[^"]*"/"\1"/g' pyproject.toml; \
@@ -132,13 +132,13 @@ engine-up: engine-clickhouse-up engine-mssql-up engine-mysql-up engine-postgres-
132132
engine-down: engine-clickhouse-down engine-mssql-down engine-mysql-down engine-postgres-down engine-spark-down engine-trino-down
133133

134134
fast-test:
135-
pytest -n auto -m "fast and not cicdonly" --junitxml=test-results/junit-fast-test.xml && pytest -m "isolated" && pytest -m "registry_isolation" && pytest -m "dialect_isolated"
135+
pytest -n auto -m "fast and not cicdonly and not isolated" --junitxml=test-results/junit-fast-test.xml && pytest -m "isolated and not slow" && pytest -m "registry_isolation" && pytest -m "dialect_isolated"
136136

137137
slow-test:
138-
pytest -n auto -m "(fast or slow) and not cicdonly" && pytest -m "isolated" && pytest -m "registry_isolation" && pytest -m "dialect_isolated"
138+
pytest -n auto -m "(fast or slow) and not cicdonly and not isolated" && pytest -m "isolated" && pytest -m "registry_isolation" && pytest -m "dialect_isolated"
139139

140140
cicd-test:
141-
pytest -n auto -m "(fast or slow) and not pyspark" --junitxml=test-results/junit-cicd.xml && pytest -m "pyspark" && pytest -m "isolated" && pytest -m "registry_isolation" && pytest -m "dialect_isolated"
141+
pytest -n auto -m "(fast or slow) and not pyspark and not isolated" --junitxml=test-results/junit-cicd.xml && pytest -m "pyspark" && pytest -m "isolated and not pyspark" && pytest -m "registry_isolation" && pytest -m "dialect_isolated"
142142

143143
core-fast-test:
144144
pytest -n auto -m "fast and not web and not github and not dbt and not jupyter"

‎sqlmesh/utils/cache.py‎

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
import gzip
44
import logging
5+
import os
56
import pickle
67
import shutil
8+
import tempfile
79
import typing as t
810
from pathlib import Path
911

@@ -125,8 +127,25 @@ def put(self, name: str, entry_id: str = "", *, value: T) -> None:
125127
if not self._path.is_dir():
126128
raise SQLMeshError(f"Cache path '{self._path}' is not a directory.")
127129

128-
with gzip.open(self._cache_entry_path(name, entry_id), "wb", compresslevel=1) as fd:
129-
pickle.dump(value, fd)
130+
# Write to a temporary file and then atomically move it into place. Writing the
131+
# entry in place ("wb" truncates the target file first) means that a concurrent
132+
# reader (e.g. another pytest-xdist worker) could observe a partially written
133+
# entry and fail to unpickle it.
134+
tmp_fd, tmp_name = tempfile.mkstemp(dir=self._path, prefix=f"{self._cache_version}__tmp")
135+
try:
136+
with os.fdopen(tmp_fd, "wb") as raw_fd:
137+
with gzip.open(raw_fd, "wb", compresslevel=1) as fd:
138+
pickle.dump(value, fd)
139+
try:
140+
os.replace(tmp_name, self._cache_entry_path(name, entry_id))
141+
except OSError as ex:
142+
# Windows os.replace fails if a concurrent reader still has the target file open.
143+
logger.warning("Failed to store a cache entry '%s': %s", name, ex)
144+
finally:
145+
try:
146+
os.unlink(tmp_name)
147+
except OSError:
148+
pass
130149

131150
def exists(self, name: str, entry_id: str = "") -> bool:
132151
"""Returns true if the cache entry with the given name and ID exists, false otherwise.

‎tests/conftest.py‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,11 +212,13 @@ def pytest_collection_modifyitems(items, *args, **kwargs):
212212
"dialect_isolated",
213213
}
214214
for item in items:
215+
if "capsys" in item.fixturenames:
216+
# capsys is not threadsafe, so the test must be isolated.
217+
item.add_marker("isolated")
215218
for marker in item.iter_markers():
216219
if marker.name in test_type_markers:
217220
break
218221
else:
219-
# if no test type marker is found, assume fast test
220222
item.add_marker("fast")
221223

222224

‎tests/utils/test_cache.py‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,22 @@ def test_file_cache(tmp_path: Path, mocker: MockerFixture):
4242
assert "客户数据" in cache._cache_entry_path("客户数据").name
4343

4444

45+
def test_file_cache_put_is_atomic(tmp_path: Path, mocker: MockerFixture) -> None:
46+
cache: FileCache[_TestEntry] = FileCache(tmp_path)
47+
48+
old_entry = _TestEntry(value="old")
49+
cache.put("test_name", value=old_entry)
50+
51+
# Simulate os.replace failing, e.g. on Windows when a concurrent reader still has the
52+
# target file open. The existing entry must never be truncated / partially overwritten.
53+
mocker.patch("sqlmesh.utils.cache.os.replace", side_effect=PermissionError("file in use"))
54+
cache.put("test_name", value=_TestEntry(value="new"))
55+
56+
assert cache.get("test_name") == old_entry
57+
# The temporary file should have been cleaned up.
58+
assert len(list(tmp_path.glob("*"))) == 1
59+
60+
4561
def test_optimized_query_cache(tmp_path: Path, mocker: MockerFixture):
4662
model = SqlModel(
4763
name="test_model",

0 commit comments

Comments
 (0)