diff --git a/.changes/unreleased/Fixes-20230919-140514.yaml b/.changes/unreleased/Fixes-20230919-140514.yaml new file mode 100644 index 00000000000..7990e1fabf8 --- /dev/null +++ b/.changes/unreleased/Fixes-20230919-140514.yaml @@ -0,0 +1,6 @@ +kind: Fixes +body: Support global flags passed in after subcommands +time: 2023-09-19T14:05:14.600303+01:00 +custom: + Author: aranke + Issue: "6497" diff --git a/core/dbt/cli/main.py b/core/dbt/cli/main.py index 9b6e8459cb0..ab501e015f4 100644 --- a/core/dbt/cli/main.py +++ b/core/dbt/cli/main.py @@ -1,3 +1,4 @@ +import functools from copy import copy from dataclasses import dataclass from typing import Callable, List, Optional, Union @@ -118,6 +119,44 @@ def invoke(self, args: List[str], **kwargs) -> dbtRunnerResult: ) +# approach from https://github.com/pallets/click/issues/108#issuecomment-280489786 +def global_flags(func): + @p.cache_selected_only + @p.debug + @p.deprecated_print + @p.enable_legacy_logger + @p.fail_fast + @p.log_cache_events + @p.log_file_max_bytes + @p.log_format_file + @p.log_level + @p.log_level_file + @p.log_path + @p.macro_debugging + @p.partial_parse + @p.partial_parse_file_path + @p.partial_parse_file_diff + @p.populate_cache + @p.print + @p.printer_width + @p.quiet + @p.record_timing_info + @p.send_anonymous_usage_stats + @p.single_threaded + @p.static_parser + @p.use_colors + @p.use_colors_file + @p.use_experimental_parser + @p.version + @p.version_check + @p.write_json + @functools.wraps(func) + def wrapper(*args, **kwargs): + return func(*args, **kwargs) + + return wrapper + + # dbt @click.group( context_settings={"help_option_names": ["-h", "--help"]}, @@ -126,38 +165,10 @@ def invoke(self, args: List[str], **kwargs) -> dbtRunnerResult: epilog="Specify one of these sub-commands and you can find more help from there.", ) @click.pass_context -@p.cache_selected_only -@p.debug -@p.deprecated_print -@p.enable_legacy_logger -@p.fail_fast -@p.log_cache_events -@p.log_file_max_bytes -@p.log_format -@p.log_format_file -@p.log_level -@p.log_level_file -@p.log_path -@p.macro_debugging -@p.partial_parse -@p.partial_parse_file_path -@p.partial_parse_file_diff -@p.populate_cache -@p.print -@p.printer_width -@p.quiet -@p.record_timing_info -@p.send_anonymous_usage_stats -@p.single_threaded -@p.static_parser -@p.use_colors -@p.use_colors_file -@p.use_experimental_parser -@p.version -@p.version_check +@global_flags @p.warn_error @p.warn_error_options -@p.write_json +@p.log_format def cli(ctx, **kwargs): """An ELT tool for managing your SQL transformations and data models. For more documentation on these commands, visit: docs.getdbt.com @@ -167,10 +178,10 @@ def cli(ctx, **kwargs): # dbt build @cli.command("build") @click.pass_context +@global_flags @p.defer @p.deprecated_defer @p.exclude -@p.fail_fast @p.favor_state @p.deprecated_favor_state @p.full_refresh @@ -190,7 +201,6 @@ def cli(ctx, **kwargs): @p.target_path @p.threads @p.vars -@p.version_check @requires.postflight @requires.preflight @requires.profile @@ -213,6 +223,7 @@ def build(ctx, **kwargs): # dbt clean @cli.command("clean") @click.pass_context +@global_flags @p.profile @p.profiles_dir @p.project_dir @@ -235,6 +246,7 @@ def clean(ctx, **kwargs): # dbt docs @cli.group() @click.pass_context +@global_flags def docs(ctx, **kwargs): """Generate or serve the documentation website for your project""" @@ -242,6 +254,7 @@ def docs(ctx, **kwargs): # dbt docs generate @docs.command("generate") @click.pass_context +@global_flags @p.compile_docs @p.defer @p.deprecated_defer @@ -261,7 +274,6 @@ def docs(ctx, **kwargs): @p.target_path @p.threads @p.vars -@p.version_check @requires.postflight @requires.preflight @requires.profile @@ -284,6 +296,7 @@ def docs_generate(ctx, **kwargs): # dbt docs serve @docs.command("serve") @click.pass_context +@global_flags @p.browser @p.port @p.profile @@ -312,6 +325,7 @@ def docs_serve(ctx, **kwargs): # dbt compile @cli.command("compile") @click.pass_context +@global_flags @p.defer @p.deprecated_defer @p.exclude @@ -335,7 +349,6 @@ def docs_serve(ctx, **kwargs): @p.target_path @p.threads @p.vars -@p.version_check @requires.postflight @requires.preflight @requires.profile @@ -359,6 +372,7 @@ def compile(ctx, **kwargs): # dbt show @cli.command("show") @click.pass_context +@global_flags @p.defer @p.deprecated_defer @p.exclude @@ -382,7 +396,6 @@ def compile(ctx, **kwargs): @p.target_path @p.threads @p.vars -@p.version_check @requires.postflight @requires.preflight @requires.profile @@ -406,6 +419,7 @@ def show(ctx, **kwargs): # dbt debug @cli.command("debug") @click.pass_context +@global_flags @p.debug_connection @p.config_dir @p.profile @@ -413,7 +427,6 @@ def show(ctx, **kwargs): @p.project_dir @p.target @p.vars -@p.version_check @requires.postflight @requires.preflight def debug(ctx, **kwargs): @@ -432,6 +445,7 @@ def debug(ctx, **kwargs): # dbt deps @cli.command("deps") @click.pass_context +@global_flags @p.profile @p.profiles_dir_exists_false @p.project_dir @@ -452,6 +466,7 @@ def deps(ctx, **kwargs): # dbt init @cli.command("init") @click.pass_context +@global_flags # for backwards compatibility, accept 'project_name' as an optional positional argument @click.argument("project_name", required=False) @p.profile @@ -474,6 +489,7 @@ def init(ctx, **kwargs): # dbt list @cli.command("list") @click.pass_context +@global_flags @p.exclude @p.indirect_selection @p.models @@ -519,6 +535,7 @@ def list(ctx, **kwargs): # dbt parse @cli.command("parse") @click.pass_context +@global_flags @p.profile @p.profiles_dir @p.project_dir @@ -526,7 +543,6 @@ def list(ctx, **kwargs): @p.target_path @p.threads @p.vars -@p.version_check @requires.postflight @requires.preflight @requires.profile @@ -543,12 +559,12 @@ def parse(ctx, **kwargs): # dbt run @cli.command("run") @click.pass_context +@global_flags @p.defer @p.deprecated_defer @p.favor_state @p.deprecated_favor_state @p.exclude -@p.fail_fast @p.full_refresh @p.profile @p.profiles_dir @@ -562,7 +578,6 @@ def parse(ctx, **kwargs): @p.target_path @p.threads @p.vars -@p.version_check @requires.postflight @requires.preflight @requires.profile @@ -585,6 +600,7 @@ def run(ctx, **kwargs): # dbt retry @cli.command("retry") @click.pass_context +@global_flags @p.project_dir @p.profiles_dir @p.vars @@ -592,7 +608,6 @@ def run(ctx, **kwargs): @p.target @p.state @p.threads -@p.fail_fast @requires.postflight @requires.preflight @requires.profile @@ -615,6 +630,7 @@ def retry(ctx, **kwargs): # dbt clone @cli.command("clone") @click.pass_context +@global_flags @p.defer_state @p.exclude @p.full_refresh @@ -629,7 +645,6 @@ def retry(ctx, **kwargs): @p.target_path @p.threads @p.vars -@p.version_check @requires.preflight @requires.profile @requires.project @@ -652,6 +667,7 @@ def clone(ctx, **kwargs): # dbt run operation @cli.command("run-operation") @click.pass_context +@global_flags @click.argument("macro") @p.args @p.profile @@ -683,6 +699,7 @@ def run_operation(ctx, **kwargs): # dbt seed @cli.command("seed") @click.pass_context +@global_flags @p.exclude @p.full_refresh @p.profile @@ -698,7 +715,6 @@ def run_operation(ctx, **kwargs): @p.target_path @p.threads @p.vars -@p.version_check @requires.postflight @requires.preflight @requires.profile @@ -720,6 +736,7 @@ def seed(ctx, **kwargs): # dbt snapshot @cli.command("snapshot") @click.pass_context +@global_flags @p.defer @p.deprecated_defer @p.exclude @@ -759,6 +776,7 @@ def snapshot(ctx, **kwargs): # dbt source @cli.group() @click.pass_context +@global_flags def source(ctx, **kwargs): """Manage your project's sources""" @@ -766,6 +784,7 @@ def source(ctx, **kwargs): # dbt source freshness @source.command("freshness") @click.pass_context +@global_flags @p.exclude @p.output_path # TODO: Is this ok to re-use? We have three different output params, how much can we consolidate? @p.profile @@ -808,10 +827,10 @@ def freshness(ctx, **kwargs): # dbt test @cli.command("test") @click.pass_context +@global_flags @p.defer @p.deprecated_defer @p.exclude -@p.fail_fast @p.favor_state @p.deprecated_favor_state @p.indirect_selection @@ -828,7 +847,6 @@ def freshness(ctx, **kwargs): @p.target_path @p.threads @p.vars -@p.version_check @requires.postflight @requires.preflight @requires.profile diff --git a/tests/unit/test_cli_flags.py b/tests/unit/test_cli_flags.py index 5c2bcc8e99b..83c0e251deb 100644 --- a/tests/unit/test_cli_flags.py +++ b/tests/unit/test_cli_flags.py @@ -1,10 +1,10 @@ -import pytest - -import click from multiprocessing import get_context from pathlib import Path from typing import List, Optional +import click +import pytest + from dbt.cli.exceptions import DbtUsageException from dbt.cli.flags import Flags from dbt.cli.main import cli @@ -356,6 +356,19 @@ def test_duplicate_flags_raises_error(self): with pytest.raises(DbtUsageException): Flags(context) + def test_global_flag_at_child_context(self): + parent_context_a = self.make_dbt_context("parent_context_a", ["--no-use-colors"]) + child_context_a = self.make_dbt_context("child_context_a", ["run"], parent_context_a) + flags_a = Flags(child_context_a) + + parent_context_b = self.make_dbt_context("parent_context_b", ["run"]) + child_context_b = self.make_dbt_context( + "child_context_b", ["--no-use-colors"], parent_context_b + ) + flags_b = Flags(child_context_b) + + assert flags_a.USE_COLORS == flags_b.USE_COLORS + def _create_flags_from_dict(self, cmd, d): write_file("", "profiles.yml") result = Flags.from_dict(cmd, d)