Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #7753: Fix regression in run-operation to not require the name of the package to run #7811

Merged
merged 7 commits into from
Jun 20, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changes/unreleased/Fixes-20230606-145217.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
kind: Fixes
body: Fix regression in `run-operation` to not require the name of the package to
run
time: 2023-06-06T14:52:17.38538-07:00
custom:
Author: aranke
Issue: "7753"
16 changes: 13 additions & 3 deletions core/dbt/task/run_operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
RunningOperationUncaughtError,
LogDebugStackTrace,
)
from dbt.exceptions import DbtRuntimeError
from dbt.node_types import NodeType
from dbt.task.base import ConfiguredTask

Expand All @@ -28,7 +29,7 @@ def _get_macro_parts(self):
if "." in macro_name:
package_name, macro_name = macro_name.split(".", 1)
else:
package_name = self.config.project_name
package_name = None

return package_name, macro_name

Expand Down Expand Up @@ -66,8 +67,17 @@ def run(self) -> RunResultsArtifact:
end = datetime.utcnow()

package_name, macro_name = self._get_macro_parts()
aranke marked this conversation as resolved.
Show resolved Hide resolved
fqn = [NodeType.Operation, package_name, macro_name]
unique_id = ".".join(fqn)
macro = self.manifest.find_macro_by_name( # type: ignore[union-attr]
macro_name, self.config.project_name, package_name
)

try:
unique_id = macro.unique_id # type: ignore[union-attr]
fqn = unique_id.split(".")
except AttributeError:
raise DbtRuntimeError(
aranke marked this conversation as resolved.
Show resolved Hide resolved
f"dbt could not find a macro with the name '{macro_name}' in any package"
)
Copy link
Contributor

@MichelleArk MichelleArk Jun 7, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section could be refactored as following to avoid the #type: ignore workarounds and make it more clear what's happening with the macro.unique_id access. Catching the error on the None value of the returned macro feels like an indirect way of checking whether the macro was found or not.

Additionally, we could avoid making a breaking change to the RunResult unique id (from operation. to macro.) if we use the attributes on the resolved macro:

        macro = (
            self.manifest.find_macro_by_name(macro_name, self.config.project_name, package_name)
            if self.manifest
            else None
        )

        if macro:
            fqn = [NodeType.Operation, macro.package_name, macro.name]
            # for downstream usage -- could be inlined instead
            unique_id = macro.unique_id
        else:
            raise DbtRuntimeError(
                f"dbt could not find a macro with the name '{macro_name}' in any package"
            )

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additionally, we could avoid making a breaking change to the RunResult unique id (from operation. to macro.)

FWIW - I believe this is new in v1.6 (#7655), so it's not a breaking change for existing functionality in prior versions. Whether this node is better considered a macro or an operation is a fair question. IIRC, we use the operation node type for on-run-* hooks ("project-level" hooks). Whereas the resource run by run-operation is really just a macro... but of course, the command is named run-operation.


run_result = RunResult(
adapter_response={},
Expand Down
2 changes: 1 addition & 1 deletion tests/functional/artifacts/test_artifacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -689,7 +689,7 @@ def test_run_operation(self, project):
results, log_output = run_dbt_and_capture(["run-operation", "alter_timezone"])
assert len(results) == 1
assert results[0].status == RunStatus.Success
assert results[0].unique_id == "operation.test.alter_timezone"
assert results[0].unique_id == "macro.test.alter_timezone"
assert "Timezone set to: America/Los_Angeles" in log_output

def test_run_model_with_operation(self, project):
Expand Down
2 changes: 1 addition & 1 deletion tests/functional/retry/test_retry.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ def test_run_operation(self, project):
)

expected_statuses = {
"operation.test.alter_timezone": RunStatus.Error,
"macro.test.alter_timezone": RunStatus.Error,
MichelleArk marked this conversation as resolved.
Show resolved Hide resolved
}

assert {n.unique_id: n.status for n in results.results} == expected_statuses
Expand Down
52 changes: 50 additions & 2 deletions tests/functional/run_operations/test_run_operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,16 @@
import pytest
import yaml

from dbt.tests.util import check_table_does_exist, run_dbt
from dbt.exceptions import DbtRuntimeError
from dbt.tests.util import (
check_table_does_exist,
run_dbt,
write_file,
mkdir,
run_dbt_and_capture,
rm_dir,
rm_file,
)
from tests.functional.run_operations.fixtures import happy_macros_sql, sad_macros_sql, model_sql


Expand Down Expand Up @@ -66,7 +75,11 @@ def test_macro_exception(self, project):
self.run_operation("syntax_error", False)

def test_macro_missing(self, project):
self.run_operation("this_macro_does_not_exist", False)
with pytest.raises(
DbtRuntimeError,
match="dbt could not find a macro with the name 'this_macro_does_not_exist' in any package",
):
self.run_operation("this_macro_does_not_exist", False)

def test_cannot_connect(self, project):
self.run_operation("no_args", extra_args=["--target", "noaccess"], expect_pass=False)
Expand All @@ -90,3 +103,38 @@ def test_access_graph(self, project):
def test_print(self, project):
# Tests that calling the `print()` macro does not cause an exception
self.run_operation("print_something")

def test_run_operation_local_macro(self, project):
pkg_macro = """
{% macro something_cool() %}
{{ log("something cool", info=true) }}
{% endmacro %}
"""

mkdir("pkg/macros")

write_file(pkg_macro, "pkg/macros/something_cool.sql")

pkg_yaml = """
packages:
- local: pkg
"""

write_file(pkg_yaml, "packages.yml")

pkg_dbt_project = """
name: 'pkg'
"""

write_file(pkg_dbt_project, "pkg/dbt_project.yml")

run_dbt(["deps"])

results, log_output = run_dbt_and_capture(["run-operation", "something_cool"])
assert "something cool" in log_output

results, log_output = run_dbt_and_capture(["run-operation", "pkg.something_cool"])
assert "something cool" in log_output

rm_dir("pkg")
rm_file("packages.yml")