diff --git a/reme2/component/file_graph/base_file_graph.py b/reme2/component/file_graph/base_file_graph.py index add28ecb..0ea21cdb 100644 --- a/reme2/component/file_graph/base_file_graph.py +++ b/reme2/component/file_graph/base_file_graph.py @@ -15,9 +15,7 @@ Contract — six abstract methods, two blocks: from __future__ import annotations -import re from abc import abstractmethod -from collections.abc import AsyncIterator from pathlib import Path from ..base_component import BaseComponent @@ -30,16 +28,11 @@ class BaseFileGraph(BaseComponent): component_type = ComponentEnum.FILE_GRAPH - def __init__(self, store_name: str = "default", **kwargs): + def __init__(self, graph_name: str = "default", **kwargs): super().__init__(**kwargs) - if not re.match(r"^[a-zA-Z0-9_]+$", store_name): - raise ValueError( - f"Invalid store name '{store_name}'. " f"Only alphanumeric and underscores allowed.", - ) - self.store_name: str = store_name - self.working_dir: str = self.app_context.app_config.working_dir if self.app_context else "" - self.store_path: Path = Path(self.working_dir) / "file_graph" / store_name - self.store_path.mkdir(parents=True, exist_ok=True) + self.graph_name: str = graph_name or self.name + self.graph_path: Path = self.working_path / self.component_type.value / graph_name + self.graph_path.mkdir(parents=True, exist_ok=True) # -- Node CRUD --------------------------------------------------------- @@ -58,15 +51,9 @@ class BaseFileGraph(BaseComponent): # -- Link access ------------------------------------------------------- @abstractmethod - async def get_outlinks( - self, - path: str, - ) -> list[tuple[FileNode, FileLink]]: + async def get_outlinks(self, path: str) -> list[FileLink]: """Resolved outgoing links from ``path``.""" @abstractmethod - async def get_inlinks( - self, - path: str, - ) -> list[tuple[FileNode, FileLink]]: + async def get_inlinks(self, path: str) -> list[FileLink]: """Resolved incoming links to ``path``.""" diff --git a/reme2/component/file_graph/local_file_graph.py b/reme2/component/file_graph/local_file_graph.py index 6a093746..12f63659 100644 --- a/reme2/component/file_graph/local_file_graph.py +++ b/reme2/component/file_graph/local_file_graph.py @@ -26,7 +26,7 @@ from pathlib import Path from .base_file_graph import BaseFileGraph from ..component_registry import R from ...schema import FileLink, FileNode - +import networkx as nx @R.register("local") class LocalFileGraph(BaseFileGraph): @@ -35,21 +35,15 @@ class LocalFileGraph(BaseFileGraph): def __init__(self, **kwargs): super().__init__(**kwargs) self._graph = None # networkx.MultiDiGraph; set in _start - self._graph_file: Path = self.store_path / "graph.pkl" + self._graph_file: Path = self.graph_path / f"{self.graph_name}.pkl" # -- Lifecycle --------------------------------------------------------- async def _start(self) -> None: await super()._start() - try: - import networkx as nx - except ImportError as e: - raise ImportError( - "LocalFileGraph requires networkx. Install with `pip install networkx`.", - ) from e self._graph = self._load_graph(nx) or nx.MultiDiGraph() self.logger.info( - f"LocalFileGraph '{self.store_name}' ready: " + f"LocalFileGraph '{self.graph_name}' ready: " f"{self._graph.number_of_nodes()} nodes, " f"{self._graph.number_of_edges()} edges", ) diff --git a/reme2/component/file_graph/neo4j_file_graph.py b/reme2/component/file_graph/neo4j_file_graph.py index d7eb613b..61fafc2e 100644 --- a/reme2/component/file_graph/neo4j_file_graph.py +++ b/reme2/component/file_graph/neo4j_file_graph.py @@ -83,7 +83,7 @@ class Neo4jFileGraph(BaseFileGraph): "CREATE CONSTRAINT file_path_unique IF NOT EXISTS " "FOR (f:File) REQUIRE f.path IS UNIQUE", ) self.logger.info( - f"Neo4jFileGraph '{self.store_name}' connected at " f"{self._uri}/{self._database}", + f"Neo4jFileGraph '{self.graph_name}' connected at " f"{self._uri}/{self._database}", ) async def _close(self) -> None: diff --git a/reme2/component/file_parser/linked_file_parser.py b/reme2/component/file_parser/linked_file_parser.py index 265e2405..1052c130 100644 --- a/reme2/component/file_parser/linked_file_parser.py +++ b/reme2/component/file_parser/linked_file_parser.py @@ -577,9 +577,8 @@ class LinkedFileParser(BaseFileParser): when ``embed_toc``, otherwise just ``content``.""" text = _toc_join(before, content, after) if self.embed_toc else content return FileChunk( - id=hash_text(text), path=path, start_line=start_line, end_line=end_line, text=text, - ) + ).set_hash_id() diff --git a/reme2/component/file_store/base_file_store.py b/reme2/component/file_store/base_file_store.py index 84bde311..e545fe1b 100644 --- a/reme2/component/file_store/base_file_store.py +++ b/reme2/component/file_store/base_file_store.py @@ -1,4 +1,3 @@ -import re from abc import abstractmethod from ..base_component import BaseComponent @@ -12,15 +11,13 @@ class BaseFileStore(BaseComponent): component_type = ComponentEnum.FILE_STORE def __init__( - self, - store_name: str, - embedding_model: str = "default", - keyword_index: str = "default", - **kwargs, + self, + store_name: str, + embedding_model: str = "default", + keyword_index: str = "default", + **kwargs, ): super().__init__(**kwargs) - if not re.match(r"^[a-zA-Z0-9_]+$", store_name): - raise ValueError(f"Invalid store name '{store_name}'. Only alphanumeric and underscores allowed.") self.store_name = store_name or self.name self._embedding_model_name = embedding_model self._keyword_index_name = keyword_index @@ -46,13 +43,15 @@ class BaseFileStore(BaseComponent): self.keyword_index = None await self.dump_file_nodes() - async def load_file_nodes(self): ... + async def load_file_nodes(self): + ... - async def dump_file_nodes(self): ... + async def dump_file_nodes(self): + ... async def upsert_file( - self, - file: tuple[FileNode, list[FileChunk]] | list[tuple[FileNode, list[FileChunk]]], + self, + file: tuple[FileNode, list[FileChunk]] | list[tuple[FileNode, list[FileChunk]]], ) -> None: """Upsert a file and its chunks into the store.""" @@ -69,6 +68,3 @@ class BaseFileStore(BaseComponent): @abstractmethod async def keyword_search(self, query: str, limit: int, search_filter: dict) -> list[FileChunk]: """Perform full-text keyword search.""" - - async def graph_search(self, query: str, limit: int, search_filter: dict) -> list[FileChunk] | None: - """Perform graph search."""