Skip to content

Commit fe35723

Browse files
committed
Fix static_analysis: off interpreted as boolean false (#12015)
YAML interprets unquoted `on`/`off` as booleans. Add `static_analysis` as a typed field to config dataclasses with __post_init__ coercion so the manifest serializes the string value, not a boolean.
1 parent 0168c62 commit fe35723

File tree

5 files changed

+65
-0
lines changed

5 files changed

+65
-0
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
kind: Fixes
2+
body: 'Fix `static_analysis: off` being interpreted as boolean `false` instead of string `"off"` in manifest.json'
3+
time: 2026-02-19T12:00:00.000000-05:00
4+
custom:
5+
Author: b-per
6+
Issue: "12015"

core/dbt/artifacts/resources/v1/config.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,13 @@ class NodeAndTestConfig(BaseConfig):
7272
default=None,
7373
metadata=CompareBehavior.Exclude.meta(),
7474
)
75+
static_analysis: Optional[str] = None
76+
77+
def __post_init__(self):
78+
# YAML interprets unquoted `on`/`off` as booleans; coerce back to strings
79+
# so that `static_analysis: off` serializes as "off", not false.
80+
if isinstance(self.static_analysis, bool):
81+
self.static_analysis = "on" if self.static_analysis else "off"
7582

7683

7784
@dataclass
@@ -129,6 +136,7 @@ class NodeConfig(NodeAndTestConfig):
129136
concurrent_batches: Any = None
130137

131138
def __post_init__(self):
139+
super().__post_init__()
132140
# we validate that node_color has a suitable value to prevent dbt-docs from crashing
133141
if self.docs.node_color:
134142
node_color = self.docs.node_color

core/dbt/artifacts/resources/v1/source_definition.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ class SourceConfig(BaseConfig):
2525
loaded_at_query: Optional[str] = None
2626
meta: Dict[str, Any] = field(default_factory=dict, metadata=MergeBehavior.Update.meta())
2727
tags: List[str] = field(default_factory=list)
28+
static_analysis: Optional[str] = None
29+
30+
def __post_init__(self):
31+
if isinstance(self.static_analysis, bool):
32+
self.static_analysis = "on" if self.static_analysis else "off"
2833

2934

3035
@dataclass

core/dbt/artifacts/resources/v1/unit_test_definition.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ class UnitTestConfig(BaseConfig):
2121
metadata=MergeBehavior.Update.meta(),
2222
)
2323
enabled: bool = True
24+
static_analysis: Optional[str] = None
25+
26+
def __post_init__(self):
27+
if isinstance(self.static_analysis, bool):
28+
self.static_analysis = "on" if self.static_analysis else "off"
2429

2530

2631
class UnitTestFormat(StrEnum):

tests/unit/contracts/graph/test_nodes_parsed.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,47 @@ def test_config_same(unrendered_node_config_dict, func):
152152
assert ModelConfig.same_contents(unrendered_node_config_dict, value)
153153

154154

155+
class TestStaticAnalysisBoolCoercion:
156+
"""YAML interprets unquoted on/off as booleans. Ensure static_analysis
157+
is always coerced to a string so manifest.json contains "off"/"on"
158+
rather than false/true. See: https://github.com/dbt-labs/dbt-core/issues/12015"""
159+
160+
def test_false_coerced_to_off(self):
161+
cfg = ModelConfig(static_analysis=False)
162+
assert cfg.static_analysis == "off"
163+
164+
def test_true_coerced_to_on(self):
165+
cfg = ModelConfig(static_analysis=True)
166+
assert cfg.static_analysis == "on"
167+
168+
def test_string_off_unchanged(self):
169+
cfg = ModelConfig(static_analysis="off")
170+
assert cfg.static_analysis == "off"
171+
172+
def test_string_on_unchanged(self):
173+
cfg = ModelConfig(static_analysis="on")
174+
assert cfg.static_analysis == "on"
175+
176+
def test_string_unsafe_unchanged(self):
177+
cfg = ModelConfig(static_analysis="unsafe")
178+
assert cfg.static_analysis == "unsafe"
179+
180+
def test_none_default(self):
181+
cfg = ModelConfig()
182+
assert cfg.static_analysis is None
183+
184+
def test_serializes_as_string(self):
185+
cfg = ModelConfig(static_analysis=False)
186+
d = cfg.to_dict(omit_none=True)
187+
assert d["static_analysis"] == "off"
188+
189+
def test_source_config_coercion(self):
190+
cfg = SourceConfig(static_analysis=False)
191+
assert cfg.static_analysis == "off"
192+
cfg2 = SourceConfig(static_analysis=True)
193+
assert cfg2.static_analysis == "on"
194+
195+
155196
@pytest.fixture
156197
def base_parsed_model_dict():
157198
return {

0 commit comments

Comments
 (0)