Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
8 changes: 4 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "dbt-core-interface"
version = "1.1.8"
version = "1.2.0"
dynamic = []
description = "Dbt Core Interface"
authors = [
Expand Down Expand Up @@ -31,7 +31,7 @@ Changelog = "https://github.com/z3z1ma/dbt-core-interface/releases"

[project.optional-dependencies]
sqlfluff = [
"sqlfluff>=2.3.2,<4"
"sqlfluff>=2.3.2,<5"
]
server = [
"fastapi~=0.115.14",
Expand All @@ -42,13 +42,13 @@ dev = [
"ruff~=0.12.0",
"coverage[toml]>=6.2,<7",
"pytest>=8.0.0,<9",
"sqlfluff>=2.3.2,<4"
"sqlfluff>=2.3.2,<5"
]
test = [
"dbt-postgres~=1.8.0",
"dbt-core~=1.8.0",
"psycopg2-binary>=2.8,<3.0",
"sqlfluff>=2.3.2,<4"
"sqlfluff>=2.3.2,<5"
]

[[tool.uv.index]]
Expand Down
2 changes: 1 addition & 1 deletion src/dbt_core_interface/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -529,7 +529,7 @@ def status(runner: DbtProject = Depends(_get_runner)) -> dict[str, t.Any]:
@app.get("/api/v1/heartbeat")
def heartbeat() -> dict[str, t.Any]:
"""Heartbeat endpoint to check server availability."""
return {"result": {"status": "ready"}}
return {"result": {"status": "ready", **DbtProjectWatcher.reload_status()}}


class ServerLintResult(BaseModel):
Expand Down
22 changes: 22 additions & 0 deletions src/dbt_core_interface/watcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import logging
import os
import threading
import time
import typing as t
import weakref
from pathlib import Path
Expand Down Expand Up @@ -50,6 +51,9 @@ def __init__(
self.reader = project.create_reader()

self._mtimes: dict[Path, float] = {}
self.reload_count: int = 0
self.last_reload_at: float = 0.0
self.last_reload_path: str = ""
self._running = False
self._thread: threading.Thread | None = None
self._stop_event = threading.Event()
Expand Down Expand Up @@ -120,6 +124,9 @@ def _monitor_loop(self) -> None:
self._project.parse_project(
write_manifest=True, reparse_configuration=change_level > 1
)
if change_level > 1:
self.reload_count += 1
self.last_reload_at = time.time()
except Exception as e:
logger.error(f"Error in project watcher loop: {e}")

Expand Down Expand Up @@ -161,6 +168,7 @@ def _check_for_changes(self) -> int:
stamped_mtime = self._mtimes.get(path)
if stamped_mtime is None or current_mtime != stamped_mtime:
self._mtimes[path] = current_mtime
self.last_reload_path = str(path)
logger.info(f"Config change detected: {path}")
return 2
except OSError as e:
Expand Down Expand Up @@ -235,6 +243,20 @@ def stop_path(cls, path: Path | str) -> None:
else:
logger.warning(f"No watcher found for project at {path}")

@classmethod
def reload_status(cls) -> dict[str, t.Any]:
"""Aggregate configuration reload counters across every active watcher."""
with cls._instance_lock:
watchers = list(cls._instances.values())
if not watchers:
return {"config_reload_count": 0, "config_reload_at": 0.0, "config_reload_path": ""}
latest = max(watchers, key=lambda w: w.last_reload_at)
return {
"config_reload_count": sum(w.reload_count for w in watchers),
"config_reload_at": latest.last_reload_at,
"config_reload_path": latest.last_reload_path,
}

@classmethod
def active_watchers(cls) -> list[DbtProjectWatcher]:
"""Return a list of currently active project paths being watched."""
Expand Down
8 changes: 4 additions & 4 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading