Spaces:
Running
Running
| """ | |
| Luuna Autoflow Core - Memory Layer | |
| ================================= | |
| Simple local storage for execution records. | |
| Uses JSON file storage (dev-safe, no production DB). | |
| Storage location: backend/.autoflow/executions.json | |
| This path is gitignored to prevent committing execution data. | |
| """ | |
| import json | |
| import logging | |
| from pathlib import Path | |
| from typing import Dict, List, Optional | |
| from datetime import datetime | |
| from .models import ExecutionRecord | |
| logger = logging.getLogger(__name__) | |
| # Default storage directory (gitignored, dev-safe) | |
| DEFAULT_STORAGE_DIR = Path(__file__).parent.parent / ".autoflow" | |
| DEFAULT_STORAGE_FILE = "executions.json" | |
| class MemoryStore: | |
| """ | |
| Simple local JSON storage for execution records. | |
| For development only - production should use proper DB. | |
| Storage path: backend/.autoflow/executions.json | |
| This is intentionally outside repo tracking to keep execution data safe. | |
| """ | |
| def __init__(self, storage_path: Optional[str] = None): | |
| """ | |
| Initialize memory store. | |
| Args: | |
| storage_path: Optional custom path to JSON file for storage. | |
| If not provided, uses backend/.autoflow/executions.json | |
| """ | |
| if storage_path: | |
| self.storage_path = Path(storage_path) | |
| else: | |
| # Default to backend/.autoflow/executions.json (gitignored, dev-safe) | |
| self.storage_path = DEFAULT_STORAGE_DIR / DEFAULT_STORAGE_FILE | |
| # Ensure directory exists | |
| self.storage_path.parent.mkdir(parents=True, exist_ok=True) | |
| # Initialize storage | |
| self._records: Dict[str, dict] = {} | |
| self._load() | |
| logger.info(f"[MemoryStore] Initialized at: {self.storage_path}") | |
| def _load(self) -> None: | |
| """Load records from disk.""" | |
| if self.storage_path.exists(): | |
| try: | |
| with open(self.storage_path, "r", encoding="utf-8") as f: | |
| self._records = json.load(f) | |
| logger.debug(f"Loaded {len(self._records)} execution records") | |
| except (json.JSONDecodeError, IOError) as e: | |
| logger.warning(f"Failed to load records: {e}") | |
| self._records = {} | |
| def _save(self) -> None: | |
| """Save records to disk.""" | |
| try: | |
| with open(self.storage_path, "w", encoding="utf-8") as f: | |
| json.dump(self._records, f, indent=2, default=str, ensure_ascii=False) | |
| logger.debug(f"Saved {len(self._records)} execution records") | |
| except IOError as e: | |
| logger.error(f"Failed to save records: {e}") | |
| def store(self, record: ExecutionRecord) -> None: | |
| """Store an execution record.""" | |
| self._records[record.execution_id] = record.model_dump() | |
| self._save() | |
| logger.debug(f"Stored execution record: {record.execution_id}") | |
| def get(self, execution_id: str) -> Optional[ExecutionRecord]: | |
| """Retrieve an execution record by ID.""" | |
| data = self._records.get(execution_id) | |
| if data: | |
| return ExecutionRecord(**data) | |
| return None | |
| def update(self, record: ExecutionRecord) -> None: | |
| """Update an existing record.""" | |
| record.updated_at = datetime.utcnow() | |
| self._records[record.execution_id] = record.model_dump() | |
| self._save() | |
| logger.debug(f"Updated execution record: {record.execution_id}") | |
| def delete(self, execution_id: str) -> bool: | |
| """Delete a record by ID.""" | |
| if execution_id in self._records: | |
| del self._records[execution_id] | |
| self._save() | |
| return True | |
| return False | |
| def list_all(self) -> list: | |
| """List all execution records.""" | |
| return [ExecutionRecord(**data) for data in self._records.values()] | |
| def list_by_status(self, status: str) -> List[ExecutionRecord]: | |
| """List records by status.""" | |
| return [ | |
| ExecutionRecord(**data) | |
| for data in self._records.values() | |
| if data.get("status") == status | |
| ] | |
| def clear(self) -> None: | |
| """Clear all records.""" | |
| self._records = {} | |
| self._save() | |
| def get_storage_path(self) -> str: | |
| """Get the current storage path for reporting.""" | |
| return str(self.storage_path) | |
| def count(self) -> int: | |
| """Return total number of stored records.""" | |
| return len(self._records) | |