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: 2 additions & 6 deletions scripts/commands/graph_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from typing import Any, Dict, Optional

from commands import register_command
from utils import default_db_path


def add_args(parser):
Expand All @@ -26,11 +27,6 @@ def add_args(parser):
help="Custom path for SQLite database file")


def _default_db_path(workspace: str) -> str:
"""Return the default SQLite db path for a workspace."""
return os.path.join(workspace, ".codelens", "codelens.db")


def get_graph_schema(workspace: str, db_path: Optional[str] = None) -> Dict[str, Any]:
"""Return the shape of the graph (node/edge counts + type distribution).

Expand All @@ -48,7 +44,7 @@ def get_graph_schema(workspace: str, db_path: Optional[str] = None) -> Dict[str,
``indexes``, ``status``, and ``workspace``.
"""
workspace = os.path.abspath(workspace)
db_path = db_path or _default_db_path(workspace)
db_path = db_path or default_db_path(workspace)

schema: Dict[str, Any] = {
"status": "ok",
Expand Down
14 changes: 8 additions & 6 deletions scripts/graph_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
from collections import deque
from typing import Any, Dict, Iterable, List, Optional, Set, Tuple

from utils import logger
from utils import default_db_path, logger


# ─── Schema Constants ─────────────────────────────────────────
Expand Down Expand Up @@ -158,9 +158,11 @@ def init_graph_schema(conn: sqlite3.Connection) -> None:

# ─── Population ───────────────────────────────────────────────

def _default_db_path(workspace: str) -> str:
"""Return the default SQLite db path for a workspace."""
return os.path.join(workspace, ".codelens", "codelens.db")
# Single source of truth for the default db path lives in utils.default_db_path
# (see issue #40). The private alias below is kept for backward compatibility
# with tests and callers that import ``graph_model._default_db_path`` directly;
# it delegates to the canonical helper so logic never drifts.
_default_db_path = default_db_path


def _parse_file_line_from_node_id(node_id: str) -> Tuple[str, int]:
Expand Down Expand Up @@ -244,7 +246,7 @@ def populate_graph_tables(workspace: str, db_path: Optional[str] = None) -> Dict
Dict with keys 'nodes' and 'edges' giving the number of rows inserted.
"""
workspace = os.path.abspath(workspace)
db_path = db_path or _default_db_path(workspace)
db_path = db_path or default_db_path(workspace)

# Lazy import to avoid circular dependency at module load time.
from registry import load_backend_registry
Expand Down Expand Up @@ -430,7 +432,7 @@ def incremental_graph_update(
unresolved after the post-pass.
"""
workspace = os.path.abspath(workspace)
db_path = db_path or _default_db_path(workspace)
db_path = db_path or default_db_path(workspace)

# Normalize changed_files to workspace-relative paths. We accept any
# iterable (list, set, tuple) and de-duplicate via a set.
Expand Down
11 changes: 6 additions & 5 deletions scripts/persistent_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,15 @@
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional, Set, Tuple

from utils import logger
from utils import default_db_path, logger


# ─── Schema Version ────────────────────────────────────────────

SCHEMA_VERSION = 1
# Module-level constant kept for backwards-compat with external callers/plugins
# that may import ``DB_FILENAME``. Internal path construction now goes through
# ``utils.default_db_path`` (single source of truth — see issue #40).
DB_FILENAME = "codelens.db"

# ─── SQL Statements ────────────────────────────────────────────
Expand Down Expand Up @@ -129,9 +132,7 @@ def __init__(self, workspace: str, db_path: Optional[str] = None):
Defaults to .codelens/codelens.db
"""
self.workspace = workspace
self._db_path = db_path or os.path.join(
workspace, ".codelens", DB_FILENAME
)
self._db_path = db_path or default_db_path(workspace)
self._local = threading.local() # Thread-local storage for connections
self._initialized = False
self._init_lock = threading.Lock()
Expand Down Expand Up @@ -855,7 +856,7 @@ def vacuum(self) -> None:

def db_exists(workspace: str, db_path: Optional[str] = None) -> bool:
"""Check if a CodeLens SQLite database exists for the workspace."""
path = db_path or os.path.join(workspace, ".codelens", DB_FILENAME)
path = db_path or default_db_path(workspace)
return os.path.exists(path)


Expand Down
23 changes: 23 additions & 0 deletions scripts/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,29 @@ def get_logger(name: str = "codelens") -> logging.Logger:
'.chunk.js', '.d.ts', # declaration files
})

# ─── Storage Path Resolution ──────────────────────────────────


def default_db_path(workspace: str) -> str:
"""Return the default SQLite database path for a workspace.

Single source of truth for the default CodeLens database location.
Previously triplicated across ``scripts/commands/graph_schema.py``,
``scripts/graph_model.py``, and ``scripts/persistent_registry.py``
(see issue #40). Any future change to the default path — e.g.,
honoring a ``CODELENS_DB_PATH`` environment variable or moving the
database out of ``.codelens/`` — only needs to be made here.

Args:
workspace: Absolute or relative path to the workspace root.

Returns:
Path to the default SQLite database file:
``<workspace>/.codelens/codelens.db``.
"""
return os.path.join(workspace, ".codelens", "codelens.db")


# ─── Output File Generation ─────────────────────────────────

def write_output_files(workspace: str, scan_result, max_files: int = 3000) -> dict:
Expand Down
76 changes: 76 additions & 0 deletions tests/test_graph_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -514,3 +514,79 @@ def test_use_graph_false_forces_flat(self, scanned_clean_app):
)

assert self._chain_set(forced_flat["chains"]["up"]) == self._chain_set(flat["chains"]["up"])


# ─── 7. Default DB Path Consistency (issue #40) ─────────────


class TestDefaultDbPathConsistency:
"""Verify all three call sites resolve the default db path identically.

Issue #40: ``_default_db_path`` was duplicated in ``graph_schema.py``,
``graph_model.py``, and ``persistent_registry.py``. After consolidation
to ``utils.default_db_path``, this test guards against future drift by
asserting that all three sites (plus the canonical helper) produce the
same path for the same workspace.
"""

def test_default_db_path_consistency(self):
"""All three call sites must resolve to the same db path."""
workspace = os.path.join(os.path.sep, "tmp", "codelens_path_test")

# 1. Canonical helper — the single source of truth.
from utils import default_db_path
canonical = default_db_path(workspace)

# 2. graph_model._default_db_path (kept as a backward-compat alias).
from graph_model import _default_db_path as graph_model_path
# 3. graph_schema used to define its own helper; it now imports
# utils.default_db_path, so we exercise the public path through
# the module's get_graph_schema function indirectly by checking
# that the import resolves to the same callable.
from commands import graph_schema as graph_schema_mod

assert canonical == graph_model_path(workspace), (
"graph_model._default_db_path must match utils.default_db_path; "
f"got graph_model={graph_model_path(workspace)!r} "
f"vs utils={canonical!r}"
)

# graph_schema.py no longer defines its own _default_db_path; the
# module must import default_db_path from utils so its callers
# resolve through the same single source of truth.
assert hasattr(graph_schema_mod, "default_db_path"), (
"commands.graph_schema must import default_db_path from utils "
"(issue #40 consolidation)"
)
assert graph_schema_mod.default_db_path is default_db_path, (
"commands.graph_schema.default_db_path must be the same callable "
"as utils.default_db_path (not a re-definition)"
)
assert graph_schema_mod.default_db_path(workspace) == canonical, (
"commands.graph_schema must resolve to the same path as "
f"utils.default_db_path; got "
f"{graph_schema_mod.default_db_path(workspace)!r} "
f"vs {canonical!r}"
)

# 4. PersistentRegistry must construct the same default path when no
# explicit db_path is supplied.
from persistent_registry import PersistentRegistry, db_exists
registry = PersistentRegistry(workspace)
assert registry.db_path == canonical, (
"PersistentRegistry default db_path must match utils.default_db_path; "
f"got registry={registry.db_path!r} vs utils={canonical!r}"
)
# db_exists() helper must also use the same default path.
# (We can't easily assert the path it computes without filesystem
# side effects, but we can assert the function does not raise and
# returns False for a non-existent workspace — proving it computes
# a path rather than crashing on the old hardcoded join.)
assert db_exists(workspace) is False, (
"db_exists must return False for a workspace with no database"
)

# Sanity: the canonical path has the expected shape.
assert canonical.endswith(os.path.join(".codelens", "codelens.db")), (
f"canonical path {canonical!r} must end with .codelens/codelens.db"
)
Loading