diff --git a/core/dbt/cli/flags.py b/core/dbt/cli/flags.py index e7f8f06d036..3506049ba4b 100644 --- a/core/dbt/cli/flags.py +++ b/core/dbt/cli/flags.py @@ -13,8 +13,8 @@ from dbt.config.profile import read_user_config from dbt.contracts.project import UserConfig from dbt.helper_types import WarnErrorOptions -from dbt.config.project import PartialProject -from dbt.exceptions import DbtProjectError +from dbt.cli.resolvers import default_project_dir, default_log_path + if os.name != "nt": # https://bugs.python.org/issue41567 @@ -132,23 +132,9 @@ def assign_params(ctx, params_assigned_from_default): # Default LOG_PATH from PROJECT_DIR, if available. if getattr(self, "LOG_PATH", None) is None: - log_path = "logs" - project_dir = getattr(self, "PROJECT_DIR", None) - # If available, set LOG_PATH from log-path in dbt_project.yml - # Known limitations: - # 1. Using PartialProject here, so no jinja rendering of log-path. - # 2. Programmatic invocations of the cli via dbtRunner may pass a Project object directly, - # which is not being used here to extract log-path. - if project_dir: - try: - partial = PartialProject.from_project_root( - project_dir, verify_version=getattr(self, "VERSION_CHECK", True) - ) - log_path = str(partial.project_dict.get("log-path", log_path)) - except DbtProjectError: - pass - - object.__setattr__(self, "LOG_PATH", log_path) + project_dir = getattr(self, "PROJECT_DIR", default_project_dir()) + version_check = getattr(self, "VERSION_CHECK", True) + object.__setattr__(self, "LOG_PATH", default_log_path(project_dir, version_check)) # Support console DO NOT TRACK initiave if os.getenv("DO_NOT_TRACK", "").lower() in ("1", "t", "true", "y", "yes"): diff --git a/core/dbt/cli/resolvers.py b/core/dbt/cli/resolvers.py index a1ba148fc27..48ba92c365a 100644 --- a/core/dbt/cli/resolvers.py +++ b/core/dbt/cli/resolvers.py @@ -1,11 +1,31 @@ from pathlib import Path +from dbt.config.project import PartialProject +from dbt.exceptions import DbtProjectError -def default_project_dir(): +def default_project_dir() -> Path: paths = list(Path.cwd().parents) paths.insert(0, Path.cwd()) return next((x for x in paths if (x / "dbt_project.yml").exists()), Path.cwd()) -def default_profiles_dir(): +def default_profiles_dir() -> Path: return Path.cwd() if (Path.cwd() / "profiles.yml").exists() else Path.home() / ".dbt" + + +def default_log_path(project_dir: Path, verify_version: bool = False) -> Path: + """If available, derive a default log path from dbt_project.yml. Otherwise, default to "logs". + Known limitations: + 1. Using PartialProject here, so no jinja rendering of log-path. + 2. Programmatic invocations of the cli via dbtRunner may pass a Project object directly, + which is not being taken into consideration here to extract a log-path. + """ + default_log_path = Path("logs") + try: + partial = PartialProject.from_project_root(str(project_dir), verify_version=verify_version) + partial_log_path = partial.project_dict.get("log-path") or default_log_path + default_log_path = Path(project_dir) / partial_log_path + except DbtProjectError: + pass + + return default_log_path diff --git a/core/dbt/docs/build/doctrees/environment.pickle b/core/dbt/docs/build/doctrees/environment.pickle index 082c8168167..24041fc88a2 100644 Binary files a/core/dbt/docs/build/doctrees/environment.pickle and b/core/dbt/docs/build/doctrees/environment.pickle differ diff --git a/tests/functional/cli/test_resolvers.py b/tests/functional/cli/test_resolvers.py new file mode 100644 index 00000000000..2ce8e17ceba --- /dev/null +++ b/tests/functional/cli/test_resolvers.py @@ -0,0 +1,35 @@ +import pytest +from dbt.cli.resolvers import default_log_path +from pathlib import Path + + +class TestDefaultLogPathNoProject: + def test_default_log_path_no_project(self): + expected_log_path = Path("logs") + actual_log_path = default_log_path("nonexistent_project_dir") + + assert actual_log_path == expected_log_path + + +class TestDefaultLogPathWithProject: + @pytest.fixture(scope="class") + def project_config_update(self): + return {"log-path": "test_default_log_path"} + + def test_default_log_path_with_project(self, project, project_config_update): + expected_log_path = Path(project.project_root) / "test_default_log_path" + actual_log_path = default_log_path(project.project_root) + + assert actual_log_path == expected_log_path + + +class TestDefaultLogPathWithProjectNoConfiguredLogPath: + @pytest.fixture(scope="class") + def project_config_update(self): + return {"log-path": None} + + def test_default_log_path_with_project(self, project, project_config_update): + expected_log_path = Path(project.project_root) / "logs" + actual_log_path = default_log_path(project.project_root) + + assert actual_log_path == expected_log_path diff --git a/tests/unit/test_cli_flags.py b/tests/unit/test_cli_flags.py index 5b10b3f6f08..5df2ab24a88 100644 --- a/tests/unit/test_cli_flags.py +++ b/tests/unit/test_cli_flags.py @@ -2,6 +2,7 @@ import click from multiprocessing import get_context +from pathlib import Path from typing import List from dbt.cli.main import cli @@ -42,7 +43,7 @@ def test_cli_group_flags_from_params(self, run_context, param): def test_log_path_default(self, run_context): flags = Flags(run_context) assert hasattr(flags, "LOG_PATH") - assert getattr(flags, "LOG_PATH") == "logs" + assert getattr(flags, "LOG_PATH") == Path("logs") @pytest.mark.parametrize( "set_stats_param,do_not_track,expected_anonymous_usage_stats",