Skip to content

Commit 1fcce44

Browse files
authored
raise MissingPlusPrefixDeprecation when missing plus-prefix, otherwise skip type-related issues in dbt_project.yml (#11825)
1 parent de03d6f commit 1fcce44

File tree

7 files changed

+127
-16
lines changed

7 files changed

+127
-16
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: raise MissingPlusPrefixDeprecation instead of GenericJSONSchemaValidationDeprecation when config missing plus prefix in dbt_project.yml
3+
time: 2025-07-14T18:41:31.322137-04:00
4+
custom:
5+
Author: michelleark
6+
Issue: "11826"

core/dbt/deprecations.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,11 @@ class EnvironmentVariableNamespaceDeprecation(DBTDeprecation):
210210
_event = "EnvironmentVariableNamespaceDeprecation"
211211

212212

213+
class MissingPlusPrefixDeprecation(DBTDeprecation):
214+
_name = "missing-plus-prefix-in-config-deprecation"
215+
_event = "MissingPlusPrefixDeprecation"
216+
217+
213218
def renamed_env_var(old_name: str, new_name: str):
214219
class EnvironmentVariableRenamed(DBTDeprecation):
215220
_name = f"environment-variable-renamed:{old_name}"
@@ -292,6 +297,7 @@ def show_deprecations_summary() -> None:
292297
WEOInlcudeExcludeDeprecation(),
293298
SourceOverrideDeprecation(),
294299
EnvironmentVariableNamespaceDeprecation(),
300+
MissingPlusPrefixDeprecation(),
295301
]
296302

297303
deprecations: Dict[str, DBTDeprecation] = {d.name: d for d in deprecations_list}

core/dbt/events/types.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,15 @@ def message(self) -> str:
733733
return line_wrap_message(deprecation_tag(description))
734734

735735

736+
class MissingPlusPrefixDeprecation(WarnLevel):
737+
def code(self) -> str:
738+
return "D037"
739+
740+
def message(self) -> str:
741+
description = f"Missing '+' prefix on `{self.key}` found at `{self.key_path}` in file `{self.file}`. Hierarchical config values without a '+' prefix are deprecated in dbt_project.yml."
742+
return line_wrap_message(deprecation_tag(description, self.__class__.__name__))
743+
744+
736745
# =======================================================
737746
# I - Project parsing
738747
# =======================================================

core/dbt/jsonschemas.py

Lines changed: 48 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,20 @@
2424
"snowflake",
2525
}
2626

27+
_HIERARCHICAL_CONFIG_KEYS = {
28+
"seeds",
29+
"sources",
30+
"models",
31+
"snapshots",
32+
"tests",
33+
"exposures",
34+
"data_tests",
35+
"metrics",
36+
"saved_queries",
37+
"semantic_models",
38+
"unit_tests",
39+
}
40+
2741

2842
def load_json_from_package(jsonschema_type: str, filename: str) -> Dict[str, Any]:
2943
"""Loads a JSON file from within a package."""
@@ -135,24 +149,43 @@ def jsonschema_validate(schema: Dict[str, Any], json: Dict[str, Any], file_path:
135149
file=file_path,
136150
key_path=key_path,
137151
)
152+
elif error.validator == "anyOf" and len(error_path) > 0:
153+
sub_errors = error.context or []
154+
# schema yaml resource configs
155+
if error_path[-1] == "config":
156+
for sub_error in sub_errors:
157+
if (
158+
isinstance(sub_error, ValidationError)
159+
and sub_error.validator == "additionalProperties"
160+
):
161+
keys = _additional_properties_violation_keys(sub_error)
162+
key_path = error_path_to_string(error)
163+
for key in keys:
164+
deprecations.warn(
165+
"custom-key-in-config-deprecation",
166+
key=key,
167+
file=file_path,
168+
key_path=key_path,
169+
)
170+
# dbt_project.yml configs
171+
elif "dbt_project.yml" in file_path and error_path[0] in _HIERARCHICAL_CONFIG_KEYS:
172+
for sub_error in sub_errors:
173+
if isinstance(sub_error, ValidationError) and sub_error.validator == "type":
174+
# Only raise type-errors if they are indicating leaf config without a plus prefix
175+
if (
176+
len(sub_error.path) > 0
177+
and isinstance(sub_error.path[-1], str)
178+
and not sub_error.path[-1].startswith("+")
179+
):
180+
deprecations.warn(
181+
"missing-plus-prefix-in-config-deprecation",
182+
key=sub_error.path[-1],
183+
file=file_path,
184+
key_path=error_path_to_string(sub_error),
185+
)
138186
elif error.validator == "type":
139187
# Not deprecating invalid types yet
140188
pass
141-
elif error.validator == "anyOf" and len(error_path) > 0 and error_path[-1] == "config":
142-
for sub_error in error.context or []:
143-
if (
144-
isinstance(sub_error, ValidationError)
145-
and sub_error.validator == "additionalProperties"
146-
):
147-
keys = _additional_properties_violation_keys(sub_error)
148-
key_path = error_path_to_string(error)
149-
for key in keys:
150-
deprecations.warn(
151-
"custom-key-in-config-deprecation",
152-
key=key,
153-
file=file_path,
154-
key_path=key_path,
155-
)
156189
else:
157190
deprecations.warn(
158191
"generic-json-schema-validation-deprecation",

core/setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@
7474
# Minor versions for these are expected to be backwards-compatible
7575
"dbt-common>=1.27.0,<2.0",
7676
"dbt-adapters>=1.15.2,<2.0",
77-
"dbt-protos>=1.0.337,<2.0",
77+
"dbt-protos>=1.0.339,<2.0",
7878
"pydantic<3",
7979
# ----
8080
# Expect compatibility with all new versions of these packages, so lower bounds only.

tests/functional/deprecations/test_deprecations.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
DuplicateYAMLKeysDeprecation,
2020
EnvironmentVariableNamespaceDeprecation,
2121
GenericJSONSchemaValidationDeprecation,
22+
MissingPlusPrefixDeprecation,
2223
ModelParamUsageDeprecation,
2324
PackageRedirectDeprecation,
2425
WEOIncludeExcludeDeprecation,
@@ -614,6 +615,61 @@ def test_environment_variable_namespace_deprecation(self):
614615
)
615616

616617

618+
class TestMissingPlusPrefixDeprecation:
619+
@pytest.fixture(scope="class")
620+
def project_config_update(self):
621+
return {"seeds": {"path": {"enabled": True}}}
622+
623+
@mock.patch.dict(os.environ, {"DBT_ENV_PRIVATE_RUN_JSONSCHEMA_VALIDATIONS": "True"})
624+
@mock.patch("dbt.jsonschemas._JSONSCHEMA_SUPPORTED_ADAPTERS", {"postgres"})
625+
def test_missing_plus_prefix_deprecation(self, project):
626+
event_catcher = EventCatcher(MissingPlusPrefixDeprecation)
627+
run_dbt(["parse", "--no-partial-parse"], callbacks=[event_catcher.catch])
628+
assert len(event_catcher.caught_events) == 1
629+
assert "Missing '+' prefix on `enabled`" in event_catcher.caught_events[0].info.msg
630+
631+
632+
class TestMissingPlusPrefixDeprecationSubPath:
633+
@pytest.fixture(scope="class")
634+
def project_config_update(self):
635+
return {"seeds": {"path": {"+enabled": True, "sub_path": {"enabled": True}}}}
636+
637+
@mock.patch.dict(os.environ, {"DBT_ENV_PRIVATE_RUN_JSONSCHEMA_VALIDATIONS": "True"})
638+
@mock.patch("dbt.jsonschemas._JSONSCHEMA_SUPPORTED_ADAPTERS", {"postgres"})
639+
def test_missing_plus_prefix_deprecation_sub_path(self, project):
640+
event_catcher = EventCatcher(MissingPlusPrefixDeprecation)
641+
run_dbt(["parse", "--no-partial-parse"], callbacks=[event_catcher.catch])
642+
assert len(event_catcher.caught_events) == 1
643+
assert "Missing '+' prefix on `enabled`" in event_catcher.caught_events[0].info.msg
644+
645+
646+
class TestMissingPlusPrefixDeprecationCustomConfig:
647+
@pytest.fixture(scope="class")
648+
def project_config_update(self):
649+
return {"seeds": {"path": {"custom_config": True, "sub_path": {"+enabled": True}}}}
650+
651+
@mock.patch.dict(os.environ, {"DBT_ENV_PRIVATE_RUN_JSONSCHEMA_VALIDATIONS": "True"})
652+
@mock.patch("dbt.jsonschemas._JSONSCHEMA_SUPPORTED_ADAPTERS", {"postgres"})
653+
def test_missing_plus_prefix_deprecation_sub_path(self, project):
654+
event_catcher = EventCatcher(MissingPlusPrefixDeprecation)
655+
run_dbt(["parse", "--no-partial-parse"], callbacks=[event_catcher.catch])
656+
assert len(event_catcher.caught_events) == 1
657+
assert "Missing '+' prefix on `custom_config`" in event_catcher.caught_events[0].info.msg
658+
659+
660+
class TestCustomConfigInDbtProjectYmlNoDeprecation:
661+
@pytest.fixture(scope="class")
662+
def project_config_update(self):
663+
return {"seeds": {"path": {"+custom_config": True}}}
664+
665+
@mock.patch.dict(os.environ, {"DBT_ENV_PRIVATE_RUN_JSONSCHEMA_VALIDATIONS": "True"})
666+
@mock.patch("dbt.jsonschemas._JSONSCHEMA_SUPPORTED_ADAPTERS", {"postgres"})
667+
def test_missing_plus_prefix_deprecation_sub_path(self, project):
668+
note_catcher = EventCatcher(Note)
669+
run_dbt(["parse", "--no-partial-parse"], callbacks=[note_catcher.catch])
670+
assert len(note_catcher.caught_events) == 0
671+
672+
617673
class TestJsonSchemaValidationGating:
618674
@pytest.fixture(scope="class")
619675
def models(self):

tests/unit/test_events.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ def test_event_codes(self):
172172
core_types.WEOIncludeExcludeDeprecation(found_include=True, found_exclude=True),
173173
core_types.ModelParamUsageDeprecation(),
174174
core_types.EnvironmentVariableNamespaceDeprecation(env_var="", reserved_prefix=""),
175+
core_types.MissingPlusPrefixDeprecation(key="", key_path="", file=""),
175176
# E - DB Adapter ======================
176177
adapter_types.AdapterEventDebug(),
177178
adapter_types.AdapterEventInfo(),

0 commit comments

Comments
 (0)