implement sync cli

This commit is contained in:
phernandez
2025-01-03 16:41:12 -06:00
parent de1cbf29c3
commit ecbe612fdc
+74 -16
View File
@@ -1,22 +1,43 @@
"""Command module for basic-memory sync operations."""
from pathlib import Path
from typing import Optional
from typing import Optional, List, Tuple
from dataclasses import dataclass
import typer
import asyncio
from loguru import logger
from rich.console import Console
from basic_memory.cli.app import app
from basic_memory import db
from basic_memory.config import config
from basic_memory.db import DatabaseType
from basic_memory.repository import DocumentRepository, EntityRepository
from basic_memory.repository import DocumentRepository, EntityRepository, ObservationRepository, RelationRepository
from basic_memory.services import (
DocumentService,
EntityService,
EntityService, ObservationService, RelationService,
)
from basic_memory.markdown import KnowledgeParser
from basic_memory.services.sync import SyncService, FileChangeScanner, KnowledgeSyncService
from basic_memory.utils.file_utils import ParseError
console = Console()
@dataclass
class ValidationIssue:
file_path: str
error: str
def display_validation_errors(issues: List[ValidationIssue]):
"""Display validation errors in a clear, git-like format."""
console.print("\n[red]Error: Invalid frontmatter in knowledge files[/red]\n")
for issue in issues:
console.print(f"{issue.file_path}")
console.print(f" {issue.error}\n")
console.print("To fix, add required frontmatter fields to each file")
console.print("Once fixed, run 'basic-memory sync' again")
async def get_sync_service(db_type=DatabaseType.FILESYSTEM):
@@ -28,14 +49,19 @@ async def get_sync_service(db_type=DatabaseType.FILESYSTEM):
# Initialize repositories
document_repository = DocumentRepository(session_maker)
entity_repository = EntityRepository(session_maker)
observation_repository = ObservationRepository(session_maker)
relation_repository = RelationRepository(session_maker)
# Initialize scanner
file_change_scanner = FileChangeScanner(document_repository, entity_repository)
# Initialize services
document_service = DocumentService(document_repository)
document_service = DocumentService(document_repository, config.documents_dir)
entity_service = EntityService(entity_repository)
knowledge_sync_service = KnowledgeSyncService(entity_service)
observation_service = ObservationService(observation_repository)
relation_service = RelationService(relation_repository)
knowledge_sync_service = KnowledgeSyncService(entity_service, observation_service, relation_service)
knowledge_parser = KnowledgeParser()
# Create sync service
@@ -49,14 +75,46 @@ async def get_sync_service(db_type=DatabaseType.FILESYSTEM):
return sync_service
async def run_sync(root_dir: Path):
async def validate_knowledge_files(sync_service: SyncService, directory: Path) -> List[ValidationIssue]:
"""Pre-validate knowledge files and collect all issues."""
issues = []
changes = await sync_service.scanner.find_knowledge_changes(directory)
for file_path in [*changes.new, *changes.modified]:
try:
await sync_service.knowledge_parser.parse_file(directory / file_path)
except ParseError as e:
issues.append(ValidationIssue(file_path=file_path, error=str(e)))
return issues
async def run_sync():
"""Run sync operation."""
sync_service = await get_sync_service()
await sync_service.sync(root_dir)
# Sync documents first
await sync_service.sync_documents(config.documents_dir)
# Validate knowledge files before attempting sync
issues = await validate_knowledge_files(sync_service, config.knowledge_dir)
if issues:
display_validation_errors(issues)
raise typer.Exit(1)
# If validation passes, sync knowledge files
await sync_service.sync_knowledge(config.knowledge_dir)
@app.command()
def sync(
path: Optional[Path] = typer.Argument(
None,
help="Path to sync. Defaults to current project directory.",
exists=True,
dir_okay=True,
file_okay=False,
),
verbose: bool = typer.Option(
False,
"--verbose",
@@ -66,23 +124,23 @@ def sync(
) -> None:
"""Sync knowledge files with the database.
This command syncs both documents and knowledge files with the database,
using a two-pass strategy for knowledge files to handle relations correctly.
Use 'basic-memory status' to preview changes before syncing.
This command syncs both documents and knowledge files with the database.
Knowledge files must have required frontmatter fields: type, id, created, modified.
"""
try:
# Get project directory
project_dir = config.home
project_dir = path or Path.cwd()
logger.info(f"Syncing directory: {project_dir}")
# Run sync
asyncio.run(run_sync(project_dir))
asyncio.run(run_sync())
if verbose:
logger.info("Sync completed successfully")
except Exception as e:
logger.exception(f"Sync failed: {e}")
typer.echo(f"Error during sync: {e}", err=True)
raise typer.Exit(1)
if not isinstance(e, typer.Exit):
logger.exception("Sync failed")
typer.echo(f"Error during sync: {e}", err=True)
raise typer.Exit(1)
raise