Skip to content

Commit df0a0e4

Browse files
Merge pull request #19 from datacoves/DCV-3760
[DCV-3760][DCV-3765] Profiles.yml refresh + partial parse fallback
2 parents 103f84c + ecb5e01 commit df0a0e4

5 files changed

Lines changed: 76 additions & 14 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "dbt-core-interface"
3-
version = "1.1.6"
3+
version = "1.1.7"
44
dynamic = []
55
description = "Dbt Core Interface"
66
authors = [

src/dbt_core_interface/project.py

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -375,9 +375,9 @@ def args(self, value: DbtConfiguration | dict[str, t.Any]) -> None: # pyright:
375375
"""Set the args for the DbtProject instance and update runtime config."""
376376
if isinstance(value, dict):
377377
value = dc_replace(self._args, **value)
378+
self._args = value
378379
set_from_args(value, None) # pyright: ignore[reportArgumentType]
379380
self.parse_project(write_manifest=True, reparse_configuration=True)
380-
self._args = value
381381

382382
def set_args(self, **kwargs: t.Any) -> None:
383383
"""Set the args for the DbtProject instance."""
@@ -519,6 +519,20 @@ def parse_project(
519519
) -> None:
520520
"""Parse the dbt project and load manifest."""
521521
if reparse_configuration:
522+
current = Path(self._args.profiles_dir).resolve()
523+
standard_dirs = {
524+
self.project_root.resolve(),
525+
(Path.home() / ".dbt").resolve(),
526+
}
527+
env_dir = os.environ.get("DBT_PROFILES_DIR")
528+
if env_dir:
529+
standard_dirs.add(Path(env_dir).expanduser().resolve())
530+
if current in standard_dirs:
531+
self._args = dc_replace(
532+
self._args,
533+
profiles_dir=_get_profiles_dir(self.project_root),
534+
)
535+
set_from_args(self._args, None) # pyright: ignore[reportArgumentType]
522536
self.runtime_config = RuntimeConfig.from_args(self._args)
523537
self.__manifest_loader = ManifestLoader(
524538
self.runtime_config,
@@ -532,10 +546,28 @@ def parse_project(
532546
self.__manifest_loader.manifest.state_check = (
533547
self.__manifest_loader.build_manifest_state_check()
534548
)
535-
self._manifest = self.__manifest_loader.saved_manifest = self.__manifest_loader.load()
536-
if not self.__manifest_loader.skip_parsing:
537-
self._manifest.build_flat_graph()
538-
self._manifest.build_group_map()
549+
try:
550+
self._manifest = self.__manifest_loader.saved_manifest = (
551+
self.__manifest_loader.load()
552+
)
553+
if not self.__manifest_loader.skip_parsing:
554+
self._manifest.build_flat_graph()
555+
self._manifest.build_group_map()
556+
except Exception:
557+
if self.__manifest_loader.saved_manifest is not None:
558+
logger.warning("Partial parse failed, forcing full reparse")
559+
self.__manifest_loader = ManifestLoader(
560+
self.runtime_config,
561+
self.runtime_config.load_dependencies(),
562+
)
563+
self._manifest = self.__manifest_loader.saved_manifest = (
564+
self.__manifest_loader.load()
565+
)
566+
if not self.__manifest_loader.skip_parsing:
567+
self._manifest.build_flat_graph()
568+
self._manifest.build_group_map()
569+
else:
570+
raise
539571

540572
self._sql_parser = None
541573
self._macro_parser = None

src/dbt_core_interface/server.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
DbtConfiguration,
3939
DbtProject,
4040
ExecutionResult,
41+
_get_profiles_dir,
4142
)
4243
from dbt_core_interface.watcher import DbtProjectWatcher
4344

@@ -136,10 +137,26 @@ def _load_saved_state(runners: DbtProjectContainer) -> None:
136137
project_name = t.cast(str, entry.get("project"))
137138
if not project_name:
138139
continue
140+
project_dir = entry.get("project_dir")
141+
saved_profiles_dir = entry.get("profiles_dir")
142+
if saved_profiles_dir:
143+
saved_path = Path(saved_profiles_dir).resolve()
144+
standard_dirs: set[Path] = {(Path.home() / ".dbt").resolve()}
145+
if project_dir:
146+
standard_dirs.add(Path(project_dir).resolve())
147+
env_dir = os.environ.get("DBT_PROFILES_DIR")
148+
if env_dir:
149+
standard_dirs.add(Path(env_dir).expanduser().resolve())
150+
if saved_path in standard_dirs:
151+
profiles_dir = _get_profiles_dir(project_dir)
152+
else:
153+
profiles_dir = saved_profiles_dir
154+
else:
155+
profiles_dir = _get_profiles_dir(project_dir)
139156
kwargs: dict[str, t.Any] = {
140157
"target": entry.get("target"),
141-
"profiles_dir": entry.get("profiles_dir"),
142-
"project_dir": entry.get("project_dir"),
158+
"project_dir": project_dir,
159+
"profiles_dir": profiles_dir,
143160
"threads": entry.get("threads", 1),
144161
"vars": entry.get("vars", {}),
145162
}

src/dbt_core_interface/watcher.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from __future__ import annotations
55

66
import logging
7+
import os
78
import threading
89
import typing as t
910
import weakref
@@ -115,6 +116,7 @@ def _monitor_loop(self) -> None:
115116
try:
116117
change_level = self._check_for_changes()
117118
if change_level:
119+
logger.info(f"Reparsing project (change_level={change_level})")
118120
self._project.parse_project(
119121
write_manifest=True, reparse_configuration=change_level > 1
120122
)
@@ -123,6 +125,16 @@ def _monitor_loop(self) -> None:
123125

124126
_ = self._stop_event.wait(self.check_interval)
125127

128+
def _all_profiles_yml_paths(self) -> list[Path]:
129+
"""Return all potential profiles.yml paths in priority order."""
130+
paths = []
131+
env_dir = os.environ.get("DBT_PROFILES_DIR")
132+
if env_dir:
133+
paths.append(Path(env_dir).expanduser().resolve() / "profiles.yml")
134+
paths.append(self._project_or_raise.project_root / "profiles.yml")
135+
paths.append(Path.home() / ".dbt" / "profiles.yml")
136+
return paths
137+
126138
def _initialize_file_mtimes(self) -> None:
127139
"""Initialize the file modification time tracking."""
128140
for f_proxy in self._project_or_raise.manifest.files.values():
@@ -132,9 +144,8 @@ def _initialize_file_mtimes(self) -> None:
132144
self._mtimes[self._project_or_raise.dbt_project_yml] = (
133145
self._project_or_raise.dbt_project_yml.stat().st_mtime
134146
)
135-
self._mtimes[self._project_or_raise.profiles_yml] = (
136-
self._project_or_raise.profiles_yml.stat().st_mtime
137-
)
147+
for path in self._all_profiles_yml_paths():
148+
self._mtimes[path] = path.stat().st_mtime if path.exists() else 0.0
138149
logger.debug(f"Initialized tracking for {len(self._mtimes)} files")
139150

140151
def _check_for_changes(self) -> int:
@@ -143,12 +154,14 @@ def _check_for_changes(self) -> int:
143154
A return value of 0 means no changes, 1 means files were added/removed, and 2 means
144155
a configuration file was modified (dbt_project.yml or profiles.yml).
145156
"""
146-
for path in (self._project_or_raise.dbt_project_yml, self._project_or_raise.profiles_yml):
157+
config_paths = [self._project_or_raise.dbt_project_yml, *self._all_profiles_yml_paths()]
158+
for path in config_paths:
147159
try:
148160
current_mtime = path.stat().st_mtime if path.exists() else 0.0
149161
stamped_mtime = self._mtimes.get(path)
150-
if stamped_mtime is None or current_mtime > stamped_mtime:
162+
if stamped_mtime is None or current_mtime != stamped_mtime:
151163
self._mtimes[path] = current_mtime
164+
logger.info(f"Config change detected: {path}")
152165
return 2
153166
except OSError as e:
154167
logger.warning(f"Error checking file {path}: {e}")

uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)