feat(spaces): implement Phase 0-1 of Information Space Service
Phase 0 - Project Organization: - Create docs/PROJECT_STRUCTURE.md documenting codebase layout - Create markitect/core/ with parser, serializer, document_manager, workspace - Create markitect/schema/ consolidating 6 schema_*.py modules - Create markitect/storage/ with database module - Maintain backward compatibility via re-exports from original locations - Add docs/roadmap/information-space-service/ with README and WORKPLAN Phase 1 - Foundation (Weeks 1-3): - Week 1: Core domain models (InformationSpace, SpaceDocument, SpaceConfig, SpaceMetadata, SpaceVariable, TransclusionReference, SpaceStatus) - Week 2: Repository layer with interfaces (ISpaceRepository, IDocumentAssociationRepository, IVariableRepository, IReferenceRepository) and SQLite implementations with foreign key cascade deletes - Week 3: SpaceService orchestration layer with full CRUD, document, variable, and reference tracking operations Test coverage: 124 tests (25 model + 63 repository + 36 integration) Capabilities delivered: - CAP-001: InformationSpace entity with lifecycle management - CAP-002: SpaceRepository CRUD with SQLite backing - CAP-003: Document-Space associations with path-based organization - CAP-004: Space metadata and configuration schemas - CAP-005: Database schema with migrations Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
409
markitect/spaces/repositories/interfaces.py
Normal file
409
markitect/spaces/repositories/interfaces.py
Normal file
@@ -0,0 +1,409 @@
|
||||
"""
|
||||
Repository interfaces for Information Spaces.
|
||||
|
||||
This module defines abstract base classes for space data access,
|
||||
following the repository pattern for clean separation of concerns.
|
||||
"""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import List, Optional, Dict, Any
|
||||
from ..models import (
|
||||
InformationSpace,
|
||||
SpaceDocument,
|
||||
SpaceVariable,
|
||||
TransclusionReference,
|
||||
)
|
||||
|
||||
|
||||
class ISpaceRepository(ABC):
|
||||
"""
|
||||
Abstract repository interface for InformationSpace persistence.
|
||||
|
||||
Implementations should handle CRUD operations for spaces,
|
||||
including proper transaction management and error handling.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def create(self, space: InformationSpace) -> InformationSpace:
|
||||
"""
|
||||
Create a new space in the repository.
|
||||
|
||||
Args:
|
||||
space: The space to create
|
||||
|
||||
Returns:
|
||||
The created space with any generated fields populated
|
||||
|
||||
Raises:
|
||||
ValueError: If space with same name already exists
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_by_id(self, space_id: str) -> Optional[InformationSpace]:
|
||||
"""
|
||||
Retrieve a space by its ID.
|
||||
|
||||
Args:
|
||||
space_id: The unique space identifier
|
||||
|
||||
Returns:
|
||||
The space if found, None otherwise
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_by_name(self, name: str) -> Optional[InformationSpace]:
|
||||
"""
|
||||
Retrieve a space by its unique name.
|
||||
|
||||
Args:
|
||||
name: The space name
|
||||
|
||||
Returns:
|
||||
The space if found, None otherwise
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def list_all(self, include_archived: bool = False) -> List[InformationSpace]:
|
||||
"""
|
||||
List all spaces in the repository.
|
||||
|
||||
Args:
|
||||
include_archived: Whether to include archived spaces
|
||||
|
||||
Returns:
|
||||
List of all spaces
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def update(self, space: InformationSpace) -> InformationSpace:
|
||||
"""
|
||||
Update an existing space.
|
||||
|
||||
Args:
|
||||
space: The space with updated values
|
||||
|
||||
Returns:
|
||||
The updated space
|
||||
|
||||
Raises:
|
||||
ValueError: If space does not exist
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def delete(self, space_id: str) -> bool:
|
||||
"""
|
||||
Delete a space by ID.
|
||||
|
||||
Args:
|
||||
space_id: The space ID to delete
|
||||
|
||||
Returns:
|
||||
True if deleted, False if not found
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def exists(self, space_id: str) -> bool:
|
||||
"""
|
||||
Check if a space exists.
|
||||
|
||||
Args:
|
||||
space_id: The space ID to check
|
||||
|
||||
Returns:
|
||||
True if exists, False otherwise
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_children(self, parent_space_id: str) -> List[InformationSpace]:
|
||||
"""
|
||||
Get all child spaces of a parent space.
|
||||
|
||||
Args:
|
||||
parent_space_id: The parent space ID
|
||||
|
||||
Returns:
|
||||
List of child spaces
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class IDocumentAssociationRepository(ABC):
|
||||
"""
|
||||
Abstract repository interface for SpaceDocument associations.
|
||||
|
||||
Manages the relationship between documents and spaces.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def add_document(self, document: SpaceDocument) -> SpaceDocument:
|
||||
"""
|
||||
Add a document to a space.
|
||||
|
||||
Args:
|
||||
document: The document association to create
|
||||
|
||||
Returns:
|
||||
The created document association
|
||||
|
||||
Raises:
|
||||
ValueError: If document path already exists in space
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_document(self, document_id: str) -> Optional[SpaceDocument]:
|
||||
"""
|
||||
Get a document association by ID.
|
||||
|
||||
Args:
|
||||
document_id: The document association ID
|
||||
|
||||
Returns:
|
||||
The document if found, None otherwise
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_by_space_path(self, space_id: str, space_path: str) -> Optional[SpaceDocument]:
|
||||
"""
|
||||
Get a document by its path within a space.
|
||||
|
||||
Args:
|
||||
space_id: The space ID
|
||||
space_path: The path within the space (e.g., "/intro.md")
|
||||
|
||||
Returns:
|
||||
The document if found, None otherwise
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def list_by_space(self, space_id: str) -> List[SpaceDocument]:
|
||||
"""
|
||||
List all documents in a space.
|
||||
|
||||
Args:
|
||||
space_id: The space ID
|
||||
|
||||
Returns:
|
||||
List of documents in the space, ordered by order_index
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def update_document(self, document: SpaceDocument) -> SpaceDocument:
|
||||
"""
|
||||
Update a document association.
|
||||
|
||||
Args:
|
||||
document: The document with updated values
|
||||
|
||||
Returns:
|
||||
The updated document
|
||||
|
||||
Raises:
|
||||
ValueError: If document does not exist
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def remove_document(self, document_id: str) -> bool:
|
||||
"""
|
||||
Remove a document from a space.
|
||||
|
||||
Args:
|
||||
document_id: The document association ID
|
||||
|
||||
Returns:
|
||||
True if removed, False if not found
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def move_document(self, document_id: str, new_space_path: str) -> SpaceDocument:
|
||||
"""
|
||||
Move a document to a new path within the space.
|
||||
|
||||
Args:
|
||||
document_id: The document association ID
|
||||
new_space_path: The new path within the space
|
||||
|
||||
Returns:
|
||||
The updated document
|
||||
|
||||
Raises:
|
||||
ValueError: If new path already exists
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def reorder_documents(self, space_id: str, document_ids: List[str]) -> None:
|
||||
"""
|
||||
Reorder documents within a space.
|
||||
|
||||
Args:
|
||||
space_id: The space ID
|
||||
document_ids: Ordered list of document IDs
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def update_content_hash(self, document_id: str, content_hash: str) -> None:
|
||||
"""
|
||||
Update the content hash for change detection.
|
||||
|
||||
Args:
|
||||
document_id: The document association ID
|
||||
content_hash: New content hash
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class IVariableRepository(ABC):
|
||||
"""
|
||||
Abstract repository interface for SpaceVariable storage.
|
||||
|
||||
Manages space-level variables for transclusion context.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def set_variable(self, variable: SpaceVariable) -> SpaceVariable:
|
||||
"""
|
||||
Set a variable value.
|
||||
|
||||
Args:
|
||||
variable: The variable to set
|
||||
|
||||
Returns:
|
||||
The saved variable
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_variable(self, space_id: str, name: str) -> Optional[SpaceVariable]:
|
||||
"""
|
||||
Get a variable by name.
|
||||
|
||||
Args:
|
||||
space_id: The space ID
|
||||
name: Variable name
|
||||
|
||||
Returns:
|
||||
The variable if found, None otherwise
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def list_variables(self, space_id: str, scope: Optional[str] = None) -> List[SpaceVariable]:
|
||||
"""
|
||||
List all variables in a space.
|
||||
|
||||
Args:
|
||||
space_id: The space ID
|
||||
scope: Optional scope filter
|
||||
|
||||
Returns:
|
||||
List of variables
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def delete_variable(self, space_id: str, name: str) -> bool:
|
||||
"""
|
||||
Delete a variable.
|
||||
|
||||
Args:
|
||||
space_id: The space ID
|
||||
name: Variable name
|
||||
|
||||
Returns:
|
||||
True if deleted, False if not found
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class IReferenceRepository(ABC):
|
||||
"""
|
||||
Abstract repository interface for TransclusionReference tracking.
|
||||
|
||||
Manages the dependency graph for cache invalidation.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def add_reference(self, reference: TransclusionReference) -> TransclusionReference:
|
||||
"""
|
||||
Add a transclusion reference.
|
||||
|
||||
Args:
|
||||
reference: The reference to add
|
||||
|
||||
Returns:
|
||||
The saved reference
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_references_from(self, source_doc_id: str, space_id: str) -> List[TransclusionReference]:
|
||||
"""
|
||||
Get all references from a source document.
|
||||
|
||||
Args:
|
||||
source_doc_id: The source document ID
|
||||
space_id: The space ID
|
||||
|
||||
Returns:
|
||||
List of references from this document
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_references_to(self, target_doc_id: str, space_id: str) -> List[TransclusionReference]:
|
||||
"""
|
||||
Get all references to a target document.
|
||||
|
||||
Args:
|
||||
target_doc_id: The target document ID
|
||||
space_id: The space ID
|
||||
|
||||
Returns:
|
||||
List of references to this document
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def clear_references_from(self, source_doc_id: str, space_id: str) -> int:
|
||||
"""
|
||||
Clear all references from a source document.
|
||||
|
||||
Args:
|
||||
source_doc_id: The source document ID
|
||||
space_id: The space ID
|
||||
|
||||
Returns:
|
||||
Number of references deleted
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_dependents(self, document_id: str, space_id: str) -> List[str]:
|
||||
"""
|
||||
Get all documents that depend on a given document.
|
||||
|
||||
Used for cache invalidation - returns documents that need
|
||||
to be re-rendered when the target document changes.
|
||||
|
||||
Args:
|
||||
document_id: The document ID
|
||||
space_id: The space ID
|
||||
|
||||
Returns:
|
||||
List of dependent document IDs
|
||||
"""
|
||||
pass
|
||||
Reference in New Issue
Block a user