diff --git a/.changes/unreleased/Fixes-20240709-172440.yaml b/.changes/unreleased/Fixes-20240709-172440.yaml new file mode 100644 index 00000000000..4931b2f80f0 --- /dev/null +++ b/.changes/unreleased/Fixes-20240709-172440.yaml @@ -0,0 +1,6 @@ +kind: Fixes +body: CLI flags should take precedence over env var flags +time: 2024-07-09T17:24:40.918977-04:00 +custom: + Author: gshank + Issue: "10304" diff --git a/core/dbt/cli/flags.py b/core/dbt/cli/flags.py index 709525894e7..1deaa0e7073 100644 --- a/core/dbt/cli/flags.py +++ b/core/dbt/cli/flags.py @@ -90,6 +90,8 @@ def __init__( # Set the default flags. for key, value in FLAGS_DEFAULTS.items(): object.__setattr__(self, key, value) + # Use to handle duplicate params in _assign_params + flags_defaults_list = list(FLAGS_DEFAULTS.keys()) if ctx is None: ctx = get_current_context() @@ -171,13 +173,29 @@ def _assign_params( old_name=dep_param.envvar, new_name=new_param.envvar, ) + # end deprecated_params # Set the flag value. - is_duplicate = hasattr(self, param_name.upper()) + is_duplicate = ( + hasattr(self, param_name.upper()) + and param_name.upper() not in flags_defaults_list + ) + # First time through, set as though FLAGS_DEFAULTS hasn't been set, so not a duplicate. + # Subsequent pass (to process "parent" params) should be treated as duplicates. + if param_name.upper() in flags_defaults_list: + flags_defaults_list.remove(param_name.upper()) + # Note: the following determines whether parameter came from click default, + # not from FLAGS_DEFAULTS in __init__. is_default = ctx.get_parameter_source(param_name) == ParameterSource.DEFAULT + is_envvar = ctx.get_parameter_source(param_name) == ParameterSource.ENVIRONMENT + flag_name = (new_name or param_name).upper() - if (is_duplicate and not is_default) or not is_duplicate: + # envvar flags are assigned in either parent or child context if there + # isn't an overriding cli command flag. + # If the flag has been encountered as a child cli flag, we don't + # want to overwrite with parent envvar, since the commandline flag takes precedence. + if (is_duplicate and not (is_default or is_envvar)) or not is_duplicate: object.__setattr__(self, flag_name, param_value) # Track default assigned params. diff --git a/tests/unit/cli/test_flags.py b/tests/unit/cli/test_flags.py index 6bf9d692e0e..f80f60d96db 100644 --- a/tests/unit/cli/test_flags.py +++ b/tests/unit/cli/test_flags.py @@ -380,6 +380,22 @@ def test_global_flag_at_child_context(self): assert flags_a.USE_COLORS == flags_b.USE_COLORS + def test_global_flag_with_env_var(self, monkeypatch): + # The environment variable is used for whichever parent or child + # does not have a cli command. + # Test that "child" global flag overrides env var + monkeypatch.setenv("DBT_QUIET", "0") + parent_context = self.make_dbt_context("parent", ["--no-use-colors"]) + child_context = self.make_dbt_context("child", ["--quiet"], parent_context) + flags = Flags(child_context) + assert flags.QUIET is True + + # Test that "parent" global flag overrides env var + parent_context = self.make_dbt_context("parent", ["--quiet"]) + child_context = self.make_dbt_context("child", ["--no-use-colors"], parent_context) + flags = Flags(child_context) + assert flags.QUIET is True + def test_set_project_only_flags(self, project_flags, run_context): flags = Flags(run_context, project_flags)