Skip to content

Commit 0d6bdde

Browse files
committed
Fix(dbt_cli): Make multiple --exclude's actually work
1 parent ff9305c commit 0d6bdde

File tree

5 files changed

+52
-14
lines changed

5 files changed

+52
-14
lines changed

sqlmesh_dbt/cli.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,19 @@ def _cleanup() -> None:
4646
@click.group(invoke_without_command=True)
4747
@click.option("--profile", help="Which existing profile to load. Overrides output.profile")
4848
@click.option("-t", "--target", help="Which target to load for the given profile")
49+
@click.option(
50+
"-d",
51+
"--debug/--no-debug",
52+
default=False,
53+
help="Display debug logging during dbt execution. Useful for debugging and making bug reports events to help when debugging.",
54+
)
4955
@click.pass_context
5056
@cli_global_error_handler
5157
def dbt(
52-
ctx: click.Context, profile: t.Optional[str] = None, target: t.Optional[str] = None
58+
ctx: click.Context,
59+
profile: t.Optional[str] = None,
60+
target: t.Optional[str] = None,
61+
debug: bool = False,
5362
) -> None:
5463
"""
5564
An ELT tool for managing your SQL transformations and data models, powered by the SQLMesh engine.
@@ -61,7 +70,9 @@ def dbt(
6170

6271
# we have a partially applied function here because subcommands might set extra options like --vars
6372
# that need to be known before we attempt to load the project
64-
ctx.obj = functools.partial(create, project_dir=Path.cwd(), profile=profile, target=target)
73+
ctx.obj = functools.partial(
74+
create, project_dir=Path.cwd(), profile=profile, target=target, debug=debug
75+
)
6576

6677
if not ctx.invoked_subcommand:
6778
if profile or target:

sqlmesh_dbt/operations.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,10 @@
1616

1717

1818
class DbtOperations:
19-
def __init__(self, sqlmesh_context: Context, dbt_project: Project):
19+
def __init__(self, sqlmesh_context: Context, dbt_project: Project, debug: bool = False):
2020
self.context = sqlmesh_context
2121
self.project = dbt_project
22+
self.debug = debug
2223

2324
def list_(
2425
self,
@@ -55,6 +56,10 @@ def _selected_models(
5556
self, select: t.Optional[t.List[str]] = None, exclude: t.Optional[t.List[str]] = None
5657
) -> t.Dict[str, Model]:
5758
if sqlmesh_selector := selectors.to_sqlmesh(select or [], exclude or []):
59+
if self.debug:
60+
self.console.print(f"dbt --select: {select}")
61+
self.console.print(f"dbt --exclude: {exclude}")
62+
self.console.print(f"sqlmesh equivalent: '{sqlmesh_selector}'")
5863
model_selector = self.context._new_selector()
5964
selected_models = {
6065
fqn: model
@@ -119,7 +124,7 @@ def create(
119124
# so that DbtOperations can query information from the DBT project files in order to invoke SQLMesh correctly
120125
dbt_project = dbt_loader._projects[0]
121126

122-
return DbtOperations(sqlmesh_context, dbt_project)
127+
return DbtOperations(sqlmesh_context, dbt_project, debug=debug)
123128

124129

125130
def init_project_if_required(project_dir: Path) -> None:

sqlmesh_dbt/selectors.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,24 @@ def to_sqlmesh(dbt_select: t.Collection[str], dbt_exclude: t.Collection[str]) ->
3737
-> "+main.model_a & ^(raw.src_data)"
3838
--select "+main.model_a" --select "main.*b+" --exclude "raw.src_data"
3939
-> "(+main.model_a | main.*b+) & ^(raw.src_data)"
40+
--select "+main.model_a" --select "main.*b+" --exclude "raw.src_data" --exclude "main.model_c"
41+
-> "(+main.model_a | main.*b+) & ^(raw.src_data | main.model_c)"
42+
--select "+main.model_a main.*b+" --exclude "raw.src_data main.model_c"
43+
-> "(+main.model_a | main.*b+) & ^(raw.src_data | main.model_c)"
4044
"""
4145
if not dbt_select and not dbt_exclude:
4246
return None
4347

4448
select_expr = " | ".join(_to_sqlmesh(expr) for expr in dbt_select)
4549
select_expr = _wrap(select_expr) if dbt_exclude and len(dbt_select) > 1 else select_expr
4650

47-
exclude_expr = " | ".join(_to_sqlmesh(expr, negate=True) for expr in dbt_exclude)
48-
exclude_expr = _wrap(exclude_expr) if dbt_select and len(dbt_exclude) > 1 else exclude_expr
51+
exclude_expr = ""
52+
53+
if dbt_exclude:
54+
exclude_expr = " | ".join(_to_sqlmesh(expr) for expr in dbt_exclude)
55+
exclude_expr = _negate(
56+
_wrap(exclude_expr) if dbt_select and len(dbt_exclude) > 1 else exclude_expr
57+
)
4958

5059
main_expr = " & ".join([expr for expr in [select_expr, exclude_expr] if expr])
5160

@@ -56,13 +65,9 @@ def to_sqlmesh(dbt_select: t.Collection[str], dbt_exclude: t.Collection[str]) ->
5665
return main_expr
5766

5867

59-
def _to_sqlmesh(selector_str: str, negate: bool = False) -> str:
68+
def _to_sqlmesh(selector_str: str) -> str:
6069
unions, intersections = _split_unions_and_intersections(selector_str)
6170

62-
if negate:
63-
unions = [_negate(u) for u in unions]
64-
intersections = [_negate(i) for i in intersections]
65-
6671
union_expr = " | ".join(unions)
6772
intersection_expr = " & ".join(intersections)
6873

@@ -79,6 +84,7 @@ def _split_unions_and_intersections(selector_str: str) -> t.Tuple[t.List[str], t
7984
# break space-separated items like: "my_first_model my_second_model" into a list of selectors to union
8085
# and comma-separated items like: "my_first_model,my_second_model" into a list of selectors to intersect
8186
# but, take into account brackets, eg "(my_first_model & my_second_model)" should not be split
87+
# also take into account both types in the same string, eg "my_first_model my_second_model model_3,model_4,model_5"
8288

8389
def _split_by(input: str, delimiter: str) -> t.Iterator[str]:
8490
buf = ""

tests/dbt/cli/test_list.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ def test_list_select(jaffle_shop_duckdb: Path, invoke_cli: t.Callable[..., Resul
3434

3535

3636
def test_list_select_exclude(jaffle_shop_duckdb: Path, invoke_cli: t.Callable[..., Result]):
37+
# single exclude
3738
result = invoke_cli(["list", "--select", "main.raw_customers+", "--exclude", "main.orders"])
3839

3940
assert result.exit_code == 0
@@ -47,6 +48,20 @@ def test_list_select_exclude(jaffle_shop_duckdb: Path, invoke_cli: t.Callable[..
4748
assert "main.stg_payments" not in result.output
4849
assert "main.raw_orders" not in result.output
4950

51+
# multiple exclude
52+
for args in (
53+
["--select", "main.stg_orders+", "--exclude", "main.customers", "--exclude", "main.orders"],
54+
["--select", "main.stg_orders+", "--exclude", "main.customers main.orders"],
55+
):
56+
result = invoke_cli(["list", *args])
57+
assert result.exit_code == 0
58+
assert not result.exception
59+
60+
assert "main.stg_orders" in result.output
61+
62+
assert "main.customers" not in result.output
63+
assert "main.orders" not in result.output
64+
5065

5166
def test_list_with_vars(jaffle_shop_duckdb: Path, invoke_cli: t.Callable[..., Result]):
5267
(jaffle_shop_duckdb / "models" / "aliased_model.sql").write_text("""

tests/dbt/cli/test_selectors.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,11 @@ def test_selection(dbt_select: t.List[str], expected: t.Optional[str]):
2727
([], None),
2828
(["main.model_a"], "^(main.model_a)"),
2929
(["(main.model_a & main.model_b)"], "^(main.model_a & main.model_b)"),
30-
(["main.model_a +main.model_b"], "^(main.model_a) | ^(+main.model_b)"),
30+
(["main.model_a,main.model_b"], "^(main.model_a & main.model_b)"),
31+
(["main.model_a +main.model_b"], "^(main.model_a | +main.model_b)"),
3132
(
3233
["(+main.model_a & ^main.model_b)", "main.model_c"],
33-
"^(+main.model_a & ^main.model_b) | ^(main.model_c)",
34+
"^((+main.model_a & ^main.model_b) | main.model_c)",
3435
),
3536
],
3637
)
@@ -51,7 +52,7 @@ def test_exclusion(dbt_exclude: t.List[str], expected: t.Optional[str]):
5152
(
5253
["+main.model_a", "main.*b+"],
5354
["raw.src_data", "tag:disabled"],
54-
"(+main.model_a | main.*b+) & (^(raw.src_data) | ^(tag:disabled))",
55+
"(+main.model_a | main.*b+) & ^(raw.src_data | tag:disabled)",
5556
),
5657
],
5758
)

0 commit comments

Comments
 (0)