Skip to content

Commit 58c2544

Browse files
authored
fix(refresh): use pydantic v1-compatible API so adapter imports on DBR 15.4 (#1461)
### Description PR #1434 added `from pydantic import model_validator` to `refresh.py`, a v2-only symbol. This breaks adapter import on Databricks Jobs DBR 15.4 LTS, which ships pydantic v1.10.x. This PR rewrites the three v2-only call sites to v1-compatible equivalents that also work in v2 via the deprecation shim: | Before (v2 only) | After (v1 + v2) | |---|---| | `@model_validator(mode="after")` | `@root_validator(skip_on_failure=True)` | | `self.model_construct(**self.model_dump(), ...)` | `self.copy(update=...)` | ### Verification - Unit tests: 1081 passed, 6 skipped - Local smoke (pydantic v2): 56 PASS / 0 ERROR - Databricks Jobs verify on DBR 15.4 LTS (pydantic v1.10.x) - Reproduced the post-merge `Min-Deps Parse` workflow (#1460) locally against this branch: `uv pip install --resolution lowest-direct .` resolved pydantic to 1.10.0; `dbt parse --project-dir tests/min_deps_smoke --profiles-dir tests/min_deps_smoke` succeeds, confirming the fix clears the same import path that the post-merge gate exercises. ### Checklist - [x] Added changes to CHANGELOG.md - [x] Unit tests pass - [x] Functional/smoke tests pass on Databricks Jobs (DBR 15.4 LTS)
1 parent f15ddfd commit 58c2544

2 files changed

Lines changed: 22 additions & 19 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## dbt-databricks 1.12.1 (TBD)
2+
3+
### Fixes
4+
5+
- Use pydantic v1-compatible API in `refresh.py` so the adapter works on environments shipping pydantic v1.
6+
17
## dbt-databricks 1.12.0 (May 14, 2026)
28

39
### Features

dbt/adapters/databricks/relation_configs/refresh.py

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from dbt.adapters.contracts.relation import RelationConfig
66
from dbt.adapters.relation_configs.config_base import RelationResults
77
from dbt_common.exceptions import DbtRuntimeError
8-
from pydantic import model_validator
8+
from pydantic import root_validator
99

1010
from dbt.adapters.databricks.relation_configs import base
1111
from dbt.adapters.databricks.relation_configs.base import (
@@ -41,6 +41,9 @@ class RefreshMode(str, Enum):
4141
# these all canonicalize to the same UTC for equality.
4242
_UTC_ALIASES = {"UTC", "ETC/UTC"}
4343

44+
# Mutually-exclusive mode discriminators on RefreshConfig.
45+
_MODE_FIELDS = ("cron", "every", "on_update")
46+
4447

4548
def _canonical_tz(tz: Optional[str]) -> str:
4649
s = (tz or "UTC").upper()
@@ -98,33 +101,27 @@ class RefreshConfig(DatabricksComponentConfig):
98101
# affect identity.
99102
is_altered: bool = False
100103

101-
@model_validator(mode="after")
102-
def _validate_mode_fields(self) -> "RefreshConfig":
103-
modes_set = [name for name, value in self._mode_signals() if value]
104+
@root_validator(skip_on_failure=True)
105+
def _validate_mode_fields(cls, values: dict) -> dict:
106+
modes_set = [name for name in _MODE_FIELDS if values.get(name)]
104107
if len(modes_set) > 1:
105108
raise DbtRuntimeError(
106109
f"Refresh schedule must specify at most one of cron / every / on_update;"
107110
f" got {modes_set}."
108111
)
109-
if self.time_zone_value is not None and self.cron is None:
112+
if values.get("time_zone_value") is not None and values.get("cron") is None:
110113
raise DbtRuntimeError("`time_zone_value` is only valid when `cron` is set.")
111-
if self.at_most_every is not None:
112-
if not self.on_update:
114+
at_most_every = values.get("at_most_every")
115+
if at_most_every is not None:
116+
if not values.get("on_update"):
113117
raise DbtRuntimeError("`at_most_every` is only valid when `on_update` is True.")
114-
seconds = _interval_seconds(self.at_most_every)
118+
seconds = _interval_seconds(at_most_every)
115119
if seconds < 60:
116120
raise DbtRuntimeError(
117121
f"`at_most_every` must be at least 60 seconds (1 minute);"
118-
f" got {self.at_most_every!r} ({seconds}s)."
122+
f" got {at_most_every!r} ({seconds}s)."
119123
)
120-
return self
121-
122-
def _mode_signals(self) -> tuple[tuple[str, Any], ...]:
123-
return (
124-
("cron", self.cron),
125-
("every", self.every),
126-
("on_update", self.on_update),
127-
)
124+
return values
128125

129126
@property
130127
def mode(self) -> RefreshMode:
@@ -176,8 +173,8 @@ def get_diff(self, other: "RefreshConfig") -> Optional["RefreshConfig"]:
176173
if self == other:
177174
return None
178175
is_altered = self.mode != RefreshMode.MANUAL and other.mode != RefreshMode.MANUAL
179-
# model_construct skips re-validation; only is_altered changes, other fields stay valid.
180-
return self.model_construct(**{**self.model_dump(), "is_altered": is_altered})
176+
# copy() skips re-validation; only is_altered changes, other fields stay valid.
177+
return self.copy(update={"is_altered": is_altered})
181178

182179

183180
class RefreshProcessor(DatabricksComponentProcessor[RefreshConfig]):

0 commit comments

Comments
 (0)