-
Notifications
You must be signed in to change notification settings - Fork 226
Support dbt 2.0 / Fusion in the edr CLI #2333
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
base: master
Are you sure you want to change the base?
Changes from 4 commits
7b2dffe
970e91e
121dcbf
b0f2fb7
1208689
544e51b
d1c4330
0ccb0b6
fa0d2b6
ba5bdc9
f9675d3
0f4c38d
7b668ae
02d3963
341f80f
77900c0
bf732d6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| from elementary.clients.dbt.dbt_installation import get_dbt2_binary_path | ||
| from elementary.clients.dbt.subprocess_dbt_runner import SubprocessDbtRunner | ||
|
|
||
|
|
||
| class Dbt2Runner(SubprocessDbtRunner): | ||
| """Runner for dbt 2.0 (the Fusion engine), which is distributed as a | ||
| standalone binary (via the `dbt` PyPI package, the `dbt-core` 2.x package | ||
| or the standalone installer) and has no importable Python API.""" | ||
|
|
||
| def _get_dbt_command_name(self) -> str: | ||
| return get_dbt2_binary_path() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,14 +1,4 @@ | ||
| import os | ||
| # Kept for backward compatibility; use Dbt2Runner instead. | ||
| from elementary.clients.dbt.dbt2_runner import Dbt2Runner as DbtFusionRunner | ||
|
|
||
| from elementary.clients.dbt.subprocess_dbt_runner import SubprocessDbtRunner | ||
|
|
||
| DBT_FUSION_PATH = os.getenv("DBT_FUSION_PATH", "~/.local/bin/dbt") | ||
|
|
||
|
|
||
| class DbtFusionRunner(SubprocessDbtRunner): | ||
| def _get_dbt_command_name(self) -> str: | ||
| return os.path.expanduser(DBT_FUSION_PATH) | ||
|
|
||
| def _run_deps_if_needed(self): | ||
| # Currently we don't support auto-updating deps for dbt fusion | ||
| return | ||
| __all__ = ["DbtFusionRunner"] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| import os | ||
| import shutil | ||
| from importlib import metadata | ||
| from typing import Optional | ||
|
|
||
| from packaging import version | ||
|
|
||
| DBT_FUSION_PATH_ENV_VAR = "DBT_FUSION_PATH" | ||
| DEFAULT_DBT_FUSION_PATH = "~/.local/bin/dbt" | ||
|
|
||
|
|
||
| def _get_package_version(package_name: str) -> Optional[version.Version]: | ||
| try: | ||
| return version.Version(metadata.version(package_name)) | ||
| except (metadata.PackageNotFoundError, version.InvalidVersion): | ||
| return None | ||
|
|
||
|
|
||
| def get_dbt_core_version() -> Optional[version.Version]: | ||
| """Version of the installed `dbt-core` package, or None if not installed.""" | ||
| return _get_package_version("dbt-core") | ||
|
|
||
|
|
||
| def get_dbt_package_version() -> Optional[version.Version]: | ||
| """Version of the installed `dbt` package, or None if not installed. | ||
|
|
||
| From 2.0, the `dbt` package on PyPI ships the dbt (Fusion) binary as a | ||
| platform wheel with no importable Python module. | ||
| """ | ||
| return _get_package_version("dbt") | ||
|
|
||
|
|
||
| def is_dbt2_binary_available() -> bool: | ||
| env_path = os.getenv(DBT_FUSION_PATH_ENV_VAR) | ||
| if env_path and os.path.exists(os.path.expanduser(env_path)): | ||
| return True | ||
|
|
||
| dbt_package_version = get_dbt_package_version() | ||
| if dbt_package_version is not None and dbt_package_version.major >= 2: | ||
| return True | ||
| return os.path.exists(os.path.expanduser(DEFAULT_DBT_FUSION_PATH)) | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
|
|
||
| def get_dbt2_binary_path() -> str: | ||
| env_path = os.getenv(DBT_FUSION_PATH_ENV_VAR) | ||
| if env_path: | ||
| return os.path.expanduser(env_path) | ||
|
|
||
| # When only dbt-core 1.x is installed, the `dbt` executable on PATH is its | ||
| # entrypoint, so it can't be trusted to be the dbt 2.0 binary. | ||
| dbt_core_version = get_dbt_core_version() | ||
| dbt_package_version = get_dbt_package_version() | ||
| dbt2_installed_via_pip = ( | ||
| dbt_package_version is not None and dbt_package_version.major >= 2 | ||
| ) or (dbt_core_version is not None and dbt_core_version.major >= 2) | ||
| if dbt2_installed_via_pip or dbt_core_version is None: | ||
| which_path = shutil.which("dbt") | ||
| if which_path: | ||
| return which_path | ||
|
|
||
| return os.path.expanduser(DEFAULT_DBT_FUSION_PATH) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,7 +20,9 @@ snapshot-paths: ["snapshots"] | |
|
|
||
| packages-install-path: "{{ env_var('DBT_PACKAGES_FOLDER', 'dbt_packages') }}" | ||
|
|
||
| target-path: "{{ env_var('EDR_INTERNAL_TARGET_PATH', 'target') }}" # directory which will store compiled SQL files | ||
| # NOTE: 'target-path' is intentionally not set here since dbt 2.0 (Fusion) rejects it. | ||
| # The EDR_INTERNAL_TARGET_PATH env var is instead translated to DBT_TARGET_PATH by the CLI | ||
| # when running this project. | ||
|
Comment on lines
+23
to
+25
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Find every writer and reader of the internal target path env var.
rg -n 'EDR_INTERNAL_TARGET_PATH|DBT_TARGET_PATH|target-path|target_path' --glob '!**/node_modules/**'Repository: elementary-data/elementary Length of output: 164 🏁 Script executed: #!/bin/bash
set -u
printf '%s\n' '--- tracked files ---'
git ls-files | sed -n '1,120p'
printf '%s\n' '--- project file ---'
if [ -f elementary/monitor/dbt_project/dbt_project.yml ]; then
cat -n elementary/monitor/dbt_project/dbt_project.yml | sed -n '1,45p'
else
printf '%s\n' 'project file not found'
fi
printf '%s\n' '--- tracked references ---'
git grep -n -E 'EDR_INTERNAL_TARGET_PATH|DBT_TARGET_PATH|target-path|target_path|_add_internal_target_path_env_var' -- ':!**/node_modules/**' || true
printf '%s\n' '--- filename matches ---'
find . -type f \( -name '*.py' -o -name '*.yml' -o -name '*.yaml' -o -name '*.js' -o -name '*.ts' \) -print | sed -n '1,200p'Repository: elementary-data/elementary Length of output: 22378 🏁 Script executed: #!/bin/bash
set -u
printf '%s\n' '--- runner implementation ---'
cat -n elementary/clients/dbt/command_line_dbt_runner.py | sed -n '1,145p'
printf '%s\n' '--- runner call sites and environment construction ---'
git grep -n -E 'CommandLineDbtRunner|env_vars|EDR_INTERNAL_TARGET_PATH|DBT_TARGET_PATH' -- '*.py' '*.yml' '*.yaml' '*.mdx' ':!**/node_modules/**' || true
printf '%s\n' '--- monitor project helpers ---'
cat -n elementary/monitor/dbt_project_utils.py | sed -n '1,220p'
printf '%s\n' '--- relevant runner tests ---'
cat -n tests/unit/clients/dbt_runner/test_dbt_runner.py | sed -n '1,260p'Repository: elementary-data/elementary Length of output: 16380 🏁 Script executed: #!/bin/bash
set -u
printf '%s\n' '--- subprocess runner ---'
cat -n elementary/clients/dbt/subprocess_dbt_runner.py | sed -n '1,120p'
printf '%s\n' '--- API runner ---'
cat -n elementary/clients/dbt/api_dbt_runner.py | sed -n '1,100p'
printf '%s\n' '--- runner factory ---'
cat -n elementary/clients/dbt/factory.py | sed -n '1,100p'
printf '%s\n' '--- internal runner construction ---'
git grep -n -E 'CLI_DBT_PROJECT_PATH|dbt_project_dir|project_dir=.*dbt_project|project_dir.*CLI|create_dbt_runner|get_dbt_runner' -- '*.py' || true
printf '%s\n' '--- all environment assignments involving target paths ---'
git grep -n -E 'os\.(environ|putenv)|env_vars\s*=|DBT_TARGET_PATH|EDR_INTERNAL_TARGET_PATH' -- '*.py' '*.yml' '*.yaml' '*.mdx' || trueRepository: elementary-data/elementary Length of output: 18998 🏁 Script executed: #!/bin/bash
python3 - <<'PY'
import ast
from pathlib import Path
runner_path = Path("elementary/clients/dbt/command_line_dbt_runner.py")
project_path = Path("elementary/monitor/dbt_project/dbt_project.yml")
runner = ast.parse(runner_path.read_text(), filename=str(runner_path))
method = next(
node for node in ast.walk(runner)
if isinstance(node, ast.FunctionDef)
and node.name == "_add_internal_target_path_env_var"
)
getenv_calls = [
ast.unparse(node)
for node in ast.walk(method)
if isinstance(node, ast.Call)
and isinstance(node.func, ast.Attribute)
and isinstance(node.func.value, ast.Name)
and node.func.value.id == "os"
and node.func.attr == "getenv"
]
return_text = ast.get_source_segment(runner_path.read_text(), method) or ""
print("runner_getenv_calls:", getenv_calls)
print("runner_requires_truthy_value:", "if internal_target_path" in return_text)
print("runner_sets_dbt_target_path:", '"DBT_TARGET_PATH": internal_target_path' in return_text)
writers = []
for path_text in __import__("subprocess").check_output(
["git", "ls-files", "*.py"], text=True
).splitlines():
path = Path(path_text)
try:
tree = ast.parse(path.read_text(), filename=path_text)
except SyntaxError:
continue
for node in ast.walk(tree):
if isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute):
if (
isinstance(node.func.value, ast.Name)
and node.func.value.id == "os"
and node.func.attr in {"putenv"}
):
if any("EDR_INTERNAL_TARGET_PATH" in ast.unparse(arg) for arg in node.args):
writers.append(f"{path_text}:{node.lineno}:{ast.unparse(node)}")
if isinstance(node, ast.Subscript) and isinstance(node.value, ast.Attribute):
if (
isinstance(node.value.value, ast.Name)
and node.value.value.id == "os"
and node.value.attr == "environ"
and "EDR_INTERNAL_TARGET_PATH" in ast.unparse(node.slice)
):
writers.append(f"{path_text}:{node.lineno}:{ast.unparse(node)}")
print("in_repo_python_writers:", writers)
project_text = project_path.read_text()
print("project_default_target:", "'target'" in project_text and "env_var('EDR_INTERNAL_TARGET_PATH', 'target')" in project_text)
PYRepository: elementary-data/elementary Length of output: 358 Set 🤖 Prompt for AI Agents
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This preserves the pre-PR behavior exactly: the removed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| clean-targets: # directories to be removed by `dbt clean` | ||
| - "{{ env_var('EDR_INTERNAL_TARGET_PATH', 'target') }}" | ||
| - "{{ env_var('DBT_PACKAGES_FOLDER', 'dbt_packages') }}" | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.