mirror of
https://github.com/basicmachines-co/basic-memory
synced 2026-06-21 13:47:35 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a80a7aec63 |
@@ -10,6 +10,31 @@ from basic_memory.schemas.directory import DirectoryNode
|
||||
router = APIRouter(prefix="/directory", tags=["directory"])
|
||||
|
||||
|
||||
@router.get("/structure", response_model=DirectoryNode)
|
||||
async def get_directory_structure(
|
||||
directory_service: DirectoryServiceDep,
|
||||
project_id: ProjectIdDep,
|
||||
):
|
||||
"""Get optimized directory structure with folders only (no file metadata).
|
||||
|
||||
This endpoint is optimized for folder navigation and tree UI rendering.
|
||||
It only returns the folder structure without loading full file metadata,
|
||||
resulting in significant performance improvements for large knowledge bases.
|
||||
|
||||
Args:
|
||||
directory_service: Service for directory operations
|
||||
project_id: ID of the current project
|
||||
|
||||
Returns:
|
||||
DirectoryNode representing the root with only folder nodes (no file nodes)
|
||||
"""
|
||||
# Get optimized directory structure for folder navigation
|
||||
structure = await directory_service.get_directory_structure()
|
||||
|
||||
# Return the folder structure
|
||||
return structure
|
||||
|
||||
|
||||
@router.get("/tree", response_model=DirectoryNode)
|
||||
async def get_directory_tree(
|
||||
directory_service: DirectoryServiceDep,
|
||||
|
||||
@@ -199,3 +199,19 @@ class EntityRepository(Repository[Entity]):
|
||||
session.add(entity)
|
||||
await session.flush()
|
||||
return entity
|
||||
|
||||
async def get_all_file_paths(self) -> List[str]:
|
||||
"""Get all file paths from entities without loading full entities.
|
||||
|
||||
This is optimized for directory structure building where we only need
|
||||
the file paths and not the full entity objects or relationships.
|
||||
|
||||
Returns:
|
||||
List of file path strings
|
||||
"""
|
||||
async with db.scoped_session(self.session_maker) as session:
|
||||
# Only select the file_path column for better performance
|
||||
query = self.select().with_only_columns(Entity.file_path)
|
||||
result = await session.execute(query)
|
||||
# Return list of file path strings
|
||||
return list(result.scalars().all())
|
||||
|
||||
@@ -22,6 +22,54 @@ class DirectoryService:
|
||||
"""
|
||||
self.entity_repository = entity_repository
|
||||
|
||||
async def get_directory_structure(self) -> DirectoryNode:
|
||||
"""Build a hierarchical directory structure with folders only (no file metadata).
|
||||
|
||||
This is optimized for folder navigation and tree UI rendering. Unlike get_directory_tree(),
|
||||
this method only loads file paths from the database without eager loading full entity
|
||||
objects and their relationships, resulting in 10-100x performance improvement for
|
||||
large knowledge bases.
|
||||
|
||||
Returns:
|
||||
DirectoryNode representing the root with only folder nodes (no file nodes)
|
||||
"""
|
||||
# Get only file paths from DB (much faster than loading full entities)
|
||||
file_paths = await self.entity_repository.get_all_file_paths()
|
||||
|
||||
# Create a root directory node
|
||||
root_node = DirectoryNode(name="Root", directory_path="/", type="directory")
|
||||
|
||||
# Map to store directory nodes by path for easy lookup
|
||||
dir_map: Dict[str, DirectoryNode] = {root_node.directory_path: root_node}
|
||||
|
||||
# Build directory structure from file paths
|
||||
for file_path in file_paths:
|
||||
# Process directory path components
|
||||
parts = [p for p in file_path.split("/") if p]
|
||||
|
||||
# Create directory structure (skip the filename)
|
||||
current_path = "/"
|
||||
for part in parts[:-1]:
|
||||
parent_path = current_path
|
||||
# Build the directory path
|
||||
current_path = (
|
||||
f"{current_path}{part}" if current_path == "/" else f"{current_path}/{part}"
|
||||
)
|
||||
|
||||
# Create directory node if it doesn't exist
|
||||
if current_path not in dir_map:
|
||||
dir_node = DirectoryNode(
|
||||
name=part, directory_path=current_path, type="directory"
|
||||
)
|
||||
dir_map[current_path] = dir_node
|
||||
|
||||
# Add to parent's children
|
||||
if parent_path in dir_map:
|
||||
dir_map[parent_path].children.append(dir_node)
|
||||
|
||||
# Return the root node with its children (folders only, no files)
|
||||
return root_node
|
||||
|
||||
async def get_directory_tree(self) -> DirectoryNode:
|
||||
"""Build a hierarchical directory tree from indexed files."""
|
||||
|
||||
|
||||
@@ -7,6 +7,51 @@ import pytest
|
||||
from basic_memory.schemas.directory import DirectoryNode
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_directory_structure_endpoint(test_graph, client, project_url):
|
||||
"""Test the get_directory_structure endpoint returns folders only (optimized)."""
|
||||
# Call the endpoint
|
||||
response = await client.get(f"{project_url}/directory/structure")
|
||||
|
||||
# Verify response
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
||||
# Check that the response is a valid directory structure
|
||||
assert "name" in data
|
||||
assert "directory_path" in data
|
||||
assert "children" in data
|
||||
assert "type" in data
|
||||
|
||||
# Should have the test directory
|
||||
assert isinstance(data["children"], list)
|
||||
assert len(data["children"]) == 1
|
||||
|
||||
test_dir = data["children"][0]
|
||||
assert test_dir["name"] == "test"
|
||||
assert test_dir["type"] == "directory"
|
||||
|
||||
# Should have NO file nodes (folders only)
|
||||
assert len(test_dir["children"]) == 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_directory_structure_empty(client, project_url):
|
||||
"""Test the get_directory_structure endpoint with no entities."""
|
||||
# Call the endpoint
|
||||
response = await client.get(f"{project_url}/directory/structure")
|
||||
|
||||
# Verify response
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
||||
# Should return root with no children
|
||||
assert data["name"] == "Root"
|
||||
assert data["directory_path"] == "/"
|
||||
assert data["type"] == "directory"
|
||||
assert len(data["children"]) == 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_directory_tree_endpoint(test_graph, client, project_url):
|
||||
"""Test the get_directory_tree endpoint returns correctly structured data."""
|
||||
|
||||
@@ -5,6 +5,52 @@ import pytest
|
||||
from basic_memory.services.directory_service import DirectoryService
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_directory_structure_empty(directory_service: DirectoryService):
|
||||
"""Test getting empty directory structure."""
|
||||
|
||||
# When no entities exist, result should just be the root
|
||||
result = await directory_service.get_directory_structure()
|
||||
assert result is not None
|
||||
assert len(result.children) == 0
|
||||
|
||||
assert result.name == "Root"
|
||||
assert result.directory_path == "/"
|
||||
assert result.has_children is False
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_directory_structure(directory_service: DirectoryService, test_graph):
|
||||
"""Test getting directory structure with folders only (no file metadata)."""
|
||||
# test_graph files:
|
||||
# /
|
||||
# ├── test
|
||||
# │ ├── Connected Entity 1.md
|
||||
# │ ├── Connected Entity 2.md
|
||||
# │ ├── Deep Entity.md
|
||||
# │ ├── Deeper Entity.md
|
||||
# │ └── Root.md
|
||||
|
||||
result = await directory_service.get_directory_structure()
|
||||
assert result is not None
|
||||
assert len(result.children) == 1
|
||||
|
||||
# Should have the "test" directory
|
||||
node_0 = result.children[0]
|
||||
assert node_0.name == "test"
|
||||
assert node_0.type == "directory"
|
||||
assert node_0.directory_path == "/test"
|
||||
assert node_0.has_children is False # No file nodes included
|
||||
assert len(node_0.children) == 0 # Folders only, no files
|
||||
|
||||
# Verify no file metadata is included (all fields should be None/empty)
|
||||
assert node_0.content_type is None
|
||||
assert node_0.entity_id is None
|
||||
assert node_0.entity_type is None
|
||||
assert node_0.title is None
|
||||
assert node_0.permalink is None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_directory_tree_empty(directory_service: DirectoryService):
|
||||
"""Test getting empty directory tree."""
|
||||
|
||||
Reference in New Issue
Block a user