Skip to content

Commit 7abcc6b

Browse files
committed
feat: Configure flags from dbt project file when possible
1 parent 2b515e8 commit 7abcc6b

2 files changed

Lines changed: 111 additions & 13 deletions

File tree

airflow_dbt_python/utils/configs.py

Lines changed: 56 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -184,23 +184,26 @@ class BaseConfig:
184184
)
185185

186186
# legacy behaviors - https://github.com/dbt-labs/dbt-core/blob/main/docs/guides/behavior-change-flags.md
187-
require_batched_execution_for_custom_microbatch_strategy: bool = False
188-
require_event_names_in_deprecations: bool = False
189-
require_explicit_package_overrides_for_builtin_materializations: bool = True
190-
require_resource_names_without_spaces: bool = True
191-
source_freshness_run_project_hooks: bool = True
192-
skip_nodes_if_on_run_start_fails: bool = False
193-
state_modified_compare_more_unrendered_values: bool = False
194-
state_modified_compare_vars: bool = False
195-
require_yaml_configuration_for_mf_time_spines: bool = False
196-
require_nested_cumulative_type_params: bool = False
197-
validate_macro_args: bool = False
198-
require_all_warnings_handled_by_warn_error: bool = False
199-
require_generic_test_arguments_property: bool = False
187+
require_batched_execution_for_custom_microbatch_strategy: Optional[bool] = None
188+
require_event_names_in_deprecations: Optional[bool] = None
189+
require_explicit_package_overrides_for_builtin_materializations: Optional[bool] = (
190+
None
191+
)
192+
require_resource_names_without_spaces: Optional[bool] = None
193+
source_freshness_run_project_hooks: Optional[bool] = None
194+
skip_nodes_if_on_run_start_fails: Optional[bool] = None
195+
state_modified_compare_more_unrendered_values: Optional[bool] = None
196+
state_modified_compare_vars: Optional[bool] = None
197+
require_yaml_configuration_for_mf_time_spines: Optional[bool] = None
198+
require_nested_cumulative_type_params: Optional[bool] = None
199+
validate_macro_args: Optional[bool] = None
200+
require_all_warnings_handled_by_warn_error: Optional[bool] = None
201+
require_generic_test_arguments_property: Optional[bool] = None
200202

201203
def __post_init__(self):
202204
"""Post initialization actions for a dbt configuration."""
203205
self.vars = parse_yaml_args(self.vars)
206+
self.set_flags_from_dbt_project()
204207
self.set_mutually_exclusive_attributes()
205208

206209
def set_mutually_exclusive_attributes(self):
@@ -257,6 +260,46 @@ def set_mutually_exclusive_attributes(self):
257260
else:
258261
setattr(self, attr, not negative_value)
259262

263+
def set_flags_from_dbt_project(self):
264+
"""Attempt to load configured flags from a project configuration file.
265+
266+
Dbt allows flags to be set in the configuration file. Since we create a project
267+
here, we must attempt to load them when they are set.
268+
269+
Important to keep in mind dbt's precedence rules and not override anything
270+
passed as an argument or set in an environment variable.
271+
"""
272+
if not self.project_dir:
273+
return
274+
275+
dbt_project_path = Path(self.project_dir) / "dbt_project.yml"
276+
if dbt_project_path.exists() is False:
277+
dbt_project_path = Path(self.project_dir) / "dbt_project.yaml"
278+
if dbt_project_path.exists() is False:
279+
return
280+
281+
try:
282+
with open(dbt_project_path) as dbt_project_yaml:
283+
yaml = dbt_project_yaml.read()
284+
contents = yaml_helper.load_yaml_text(yaml) or {}
285+
except Exception:
286+
return
287+
288+
if "flags" not in contents:
289+
return
290+
291+
for flag_name, flag_value in contents["flags"].items():
292+
current_value = getattr(self, flag_name, None)
293+
env_value = os.getenv(f"DBT_{flag_name.upper()}", None)
294+
295+
if current_value is not None or env_value is not None:
296+
# According to dbt config precedence rules, a value passed as argument
297+
# or in the environment wins over values set in dbt project config.
298+
# https://docs.getdbt.com/reference/global-configs/project-flags#config-precedence
299+
continue
300+
301+
setattr(self, flag_name, flag_value)
302+
260303
def __getattribute__(self, item: str):
261304
"""Dbt 1.5+ uses uppercase attributes, let's handle this."""
262305
if item.isupper():

tests/utils/test_configs.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -451,3 +451,58 @@ def test_base_config_create_dbt_project_and_profile_with_no_profile(
451451
target = profile.to_target_dict()
452452
assert target["name"] == conn_id
453453
assert target["type"] == "postgres"
454+
455+
456+
@pytest.fixture(scope="function")
457+
def dbt_project_file_with_flags(dbt_project_dir, logs_dir, request):
458+
"""Create a test dbt_project.yml file."""
459+
p = dbt_project_dir / "dbt_project.yml"
460+
contents = """
461+
name: test
462+
profile: default
463+
config-version: 2
464+
version: 1.0.0
465+
flags:
466+
fail_fast: false
467+
require_generic_test_arguments_property: true
468+
"""
469+
p.write_text(contents)
470+
471+
return p
472+
473+
474+
def test_base_config_sets_flag_from_dbt_project_file(dbt_project_file_with_flags):
475+
"""Test the configuration reads flags from dbt project file."""
476+
config = BaseConfig(
477+
project_dir=dbt_project_file_with_flags.parent,
478+
)
479+
assert config.fail_fast is False
480+
assert config.require_generic_test_arguments_property is True
481+
482+
483+
def test_base_config_does_not_override_when_value_passed(dbt_project_file_with_flags):
484+
"""Test flags in dbt project file do not override any values passed."""
485+
config = BaseConfig(
486+
project_dir=dbt_project_file_with_flags.parent,
487+
fail_fast=True,
488+
require_generic_test_arguments_property=False,
489+
)
490+
assert config.fail_fast is True
491+
assert config.require_generic_test_arguments_property is False
492+
493+
494+
def test_base_config_does_not_override_when_value_in_environment(
495+
dbt_project_file_with_flags,
496+
):
497+
"""Test flags in dbt project file do not override when environment values set."""
498+
env = {
499+
"DBT_FAIL_FAST": "1",
500+
"DBT_REQUIRE_GENERIC_TEST_ARGUMENTS_PROPERTY": "0",
501+
}
502+
503+
with patch.dict(os.environ, env):
504+
config = BaseConfig(
505+
project_dir=dbt_project_file_with_flags.parent,
506+
)
507+
assert config.fail_fast is None
508+
assert config.require_generic_test_arguments_property is None

0 commit comments

Comments
 (0)