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
4 changes: 0 additions & 4 deletions scripts/kg_rebuild.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,6 @@
# Add src to path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "src"))

from dotenv import load_dotenv

load_dotenv(Path(__file__).resolve().parent.parent / ".env")

from brainlayer.paths import get_db_path

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve the configured database before rebuilding

When a developer keeps BRAINLAYER_DB in the repo .env, removing this load makes main() silently fall back to the canonical database via get_db_path(). Running either rebuild tier then writes entities and relations to the live canonical DB instead of the configured sandbox/custom DB; replace the dotenv dependency with dependency-free loading that preserves the prior override, or require an explicit DB argument rather than silently changing targets.

AGENTS.md reference: AGENTS.md:L5-L8

Useful? React with 👍 / 👎.

from brainlayer.pipeline.batch_extraction import DEFAULT_SEED_ENTITIES
from brainlayer.pipeline.entity_extraction import (
Expand Down
20 changes: 20 additions & 0 deletions tests/test_kg_rebuild.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,3 +309,23 @@ def test_groq_rebuild_entity_payload_preserves_source_subtype():
assert entity.entity_type == "source"
assert entity.entity_subtype == "channel"
assert entity.start == 6


def test_kg_rebuild_module_import_does_not_require_python_dotenv(monkeypatch):
import builtins
import importlib
import sys

real_import = builtins.__import__

def import_without_dotenv(name, *args, **kwargs):
if name == "dotenv" or name.startswith("dotenv."):
raise ModuleNotFoundError("No module named 'dotenv'")
return real_import(name, *args, **kwargs)

monkeypatch.setattr(builtins, "__import__", import_without_dotenv)
sys.modules.pop("scripts.kg_rebuild", None)

module = importlib.import_module("scripts.kg_rebuild")
Comment on lines +327 to +329

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files | rg '(^|/)tests/test_kg_rebuild\.py$|(^|/)scripts/kg_rebuild\.py$|(^|/)pytest|\.ini$|pyproject\.toml' || true

echo "== relevant test section =="
if [ -f tests/test_kg_rebuild.py ]; then
  nl -ba tests/test_kg_rebuild.py | sed -n '280,360p'
fi

echo "== references to monkeypatch/sys.modules/import hook and scripts.kg_rebuild =="
rg -n "builtins\.__import__|sys\.modules\['scripts\.kg_rebuild'\]|sys\.modules\.pop|importlib\.import_module|scripts\.kg_rebuild|BRAINLAYER_BACKUP_LOG_PATH|pytest" tests/test_kg_rebuild.py | sed -n '1,220p'

Repository: EtanHey/brainlayer

Length of output: 298


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import os, re
for path in ["tests/test_kg_rebuild.py", "scripts/kg_rebuild.py"]:
    print(f"== {path} size/exists ==")
    print(f"exists={os.path.exists(path)}")
    if os.path.exists(path):
        with open(path, encoding="utf-8") as f:
            lines = f.read().splitlines()
        print(f"lines={len(lines)}")
        start=max(1,280) if path=="tests/test_kg_rebuild.py" else 1
        end=min(len(lines),360) if path=="tests/test_kg_rebuild.py" else 170
        for i in range(start,min(end+1,len(lines))+1):
            print(f"{i:04d}\t{lines[i-1]}")
PY

echo "== focused references =="
rg -n "builtins\.__import__|sys\.modules\['scripts\.kg_rebuild'\]|sys\.modules\.pop|importlib\.import_module|scripts\.kg_rebuild|BRAINLAYER_BACKUP_LOG_PATH|pytest|monkeypatch" tests/test_kg_rebuild.py | head -n 220 || true

echo "== pytest config =="
for f in pyproject.toml pytest.ini; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    sed -n '1,220p' "$f"
  fi
done

Repository: EtanHey/brainlayer

Length of output: 14148


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import ast
from pathlib import Path

path = Path("tests/test_kg_rebuild.py")
src = path.read_text(encoding="utf-8")
tree = ast.parse(src)

for node in ast.walk(tree):
    if isinstance(node, ast.ClassDef) or isinstance(node, ast.FunctionDef) or isinstance(node, ast.AsyncFunctionDef):
        if "test_" in str(node.name):
            print(f"Function {node.name} at lines {node.lineno}-{getattr(node, 'end_lineno', '?')}")

test = next(n for n in tree.body if isinstance(n, ast.FunctionDef) and n.name == "test_kg_rebuild_module_import_does_not_require_python_dotenv")

print("\nBody nodes:")
for n in test.body:
    print(type(n).__name__, getattr(n, "lineno", "?"), getattr(n, "end_lineno", "?"), ast.unparse(n)[:260])

print("\nsys.modules.pop occurrences:")
for n in ast.walk(test):
    if isinstance(n, ast.Call):
        func = n.func
        if isinstance(getattr(func, "value", None), ast.Attribute) and getattr(func, "attr", None) == "pop":
            print(ast.unparse(n))

print("\nsys.modules.pop with cleanup occurrences:")
for n in ast.walk(test):
    if isinstance(n, ast.Try):
        for stmt in n.body:
            print("try body first:", ast.unparse(stmt)[:240])
        cleanup = []
        for h in n.handlers:
            for stmt in h.body:
                cleanup.append(ast.unparse(stmt)[:240])
        for stmt in n.finalbody:
            cleanup.append(ast.unparse(stmt)[:240])
        if any("sys.modules" in s or "builtins" in s for s in cleanup):
            print("cleanup:")
            for s in cleanup:
                print("  " + s)
PY

Repository: EtanHey/brainlayer

Length of output: 2051


Restore scripts.kg_rebuild after checking the import hook.

monkeypatch restores builtins.__import__, but it does not restore the direct sys.modules.pop("scripts.kg_rebuild", None). If another test loaded scripts.kg_rebuild first, this test replaces it process-wide; an import failure then leaves it removed. Restore the previous module entry and parent package attribute in a finally block, or run this import check in a separate process.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_kg_rebuild.py` around lines 327 - 329, Update the import-check
test around the direct sys.modules.pop and importlib.import_module calls to save
the existing scripts.kg_rebuild module and parent-package attribute, then
restore both in a finally block even when the import fails. Preserve the
import-hook validation while preventing this test from leaving process-wide
module state altered.


assert callable(module.extracted_entity_from_groq_payload)
Loading