""" Abstract repository interfaces for data access patterns. Defines the contracts for data access operations across different data sources, enabling clean separation between business logic and infrastructure concerns. """ from abc import ABC, abstractmethod from typing import List, Optional, Dict, Any, AsyncContextManager from pathlib import Path from domain.issues.models import Issue from domain.projects.models import Project, Milestone from infrastructure.exceptions import ErrorContext class IssueRepository(ABC): """Abstract repository for issue-related operations.""" @abstractmethod async def get_issue(self, issue_number: int, context: Optional[ErrorContext] = None) -> Issue: """ Retrieve an issue by its number. Args: issue_number: The issue number to retrieve context: Error context for tracking operations Returns: Issue domain object Raises: ResourceNotFoundError: If issue doesn't exist GiteaApiError: If API request fails NetworkError: If network connectivity fails """ pass @abstractmethod async def get_issues( self, project_id: Optional[str] = None, state: Optional[str] = None, labels: Optional[List[str]] = None, limit: int = 100, offset: int = 0, context: Optional[ErrorContext] = None ) -> List[Issue]: """ Retrieve multiple issues with filtering and pagination. Args: project_id: Filter by project ID state: Filter by issue state (open, closed) labels: Filter by labels limit: Maximum number of issues to return offset: Number of issues to skip context: Error context for tracking operations Returns: List of Issue domain objects Raises: GiteaApiError: If API request fails NetworkError: If network connectivity fails """ pass @abstractmethod async def create_issue( self, title: str, body: str, labels: Optional[List[str]] = None, assignees: Optional[List[str]] = None, context: Optional[ErrorContext] = None ) -> Issue: """ Create a new issue. Args: title: Issue title body: Issue description labels: List of label names assignees: List of assignee usernames context: Error context for tracking operations Returns: Created Issue domain object Raises: ValidationError: If input data is invalid GiteaApiError: If API request fails NetworkError: If network connectivity fails """ pass @abstractmethod async def update_issue( self, issue_number: int, title: Optional[str] = None, body: Optional[str] = None, state: Optional[str] = None, labels: Optional[List[str]] = None, context: Optional[ErrorContext] = None ) -> Issue: """ Update an existing issue. Args: issue_number: Issue number to update title: New title (if provided) body: New body (if provided) state: New state (if provided) labels: New labels (if provided) context: Error context for tracking operations Returns: Updated Issue domain object Raises: ResourceNotFoundError: If issue doesn't exist ValidationError: If input data is invalid GiteaApiError: If API request fails ConcurrencyError: If issue was modified concurrently """ pass @abstractmethod async def get_issue_project_info( self, issue_number: int, context: Optional[ErrorContext] = None ) -> Dict[str, Any]: """ Get project-related information for an issue. Args: issue_number: Issue number context: Error context for tracking operations Returns: Project information dictionary Raises: ResourceNotFoundError: If issue doesn't exist GiteaApiError: If API request fails """ pass class ProjectRepository(ABC): """Abstract repository for project-related operations.""" @abstractmethod async def get_project(self, project_id: str, context: Optional[ErrorContext] = None) -> Project: """ Retrieve a project by its ID. Args: project_id: Project identifier context: Error context for tracking operations Returns: Project domain object Raises: ResourceNotFoundError: If project doesn't exist GiteaApiError: If API request fails """ pass @abstractmethod async def get_projects( self, organization: Optional[str] = None, limit: int = 100, offset: int = 0, context: Optional[ErrorContext] = None ) -> List[Project]: """ Retrieve multiple projects with pagination. Args: organization: Filter by organization limit: Maximum number of projects to return offset: Number of projects to skip context: Error context for tracking operations Returns: List of Project domain objects Raises: GiteaApiError: If API request fails """ pass @abstractmethod async def get_milestones( self, project_id: str, state: Optional[str] = None, context: Optional[ErrorContext] = None ) -> List[Milestone]: """ Retrieve milestones for a project. Args: project_id: Project identifier state: Filter by milestone state context: Error context for tracking operations Returns: List of Milestone domain objects Raises: ResourceNotFoundError: If project doesn't exist GiteaApiError: If API request fails """ pass @abstractmethod async def create_milestone( self, project_id: str, title: str, description: str, due_date: Optional[str] = None, context: Optional[ErrorContext] = None ) -> Milestone: """ Create a new milestone for a project. Args: project_id: Project identifier title: Milestone title description: Milestone description due_date: Due date (ISO format) context: Error context for tracking operations Returns: Created Milestone domain object Raises: ResourceNotFoundError: If project doesn't exist ValidationError: If input data is invalid GiteaApiError: If API request fails """ pass class DocumentRepository(ABC): """Abstract repository for document storage and retrieval.""" @abstractmethod async def store_document( self, filename: str, content: str, ast: Dict[str, Any], context: Optional[ErrorContext] = None ) -> str: """ Store a document with its AST representation. Args: filename: Document filename content: Document content ast: Parsed AST representation context: Error context for tracking operations Returns: Document ID Raises: ValidationError: If input data is invalid DatabaseError: If storage operation fails DuplicateResourceError: If document already exists """ pass @abstractmethod async def get_document( self, document_id: str, context: Optional[ErrorContext] = None ) -> Dict[str, Any]: """ Retrieve a document by its ID. Args: document_id: Document identifier context: Error context for tracking operations Returns: Document data dictionary Raises: ResourceNotFoundError: If document doesn't exist DatabaseError: If retrieval operation fails """ pass @abstractmethod async def get_documents( self, filename_pattern: Optional[str] = None, limit: int = 100, offset: int = 0, context: Optional[ErrorContext] = None ) -> List[Dict[str, Any]]: """ Retrieve multiple documents with filtering and pagination. Args: filename_pattern: Filter by filename pattern limit: Maximum number of documents to return offset: Number of documents to skip context: Error context for tracking operations Returns: List of document data dictionaries Raises: DatabaseError: If retrieval operation fails """ pass @abstractmethod async def update_document( self, document_id: str, content: Optional[str] = None, ast: Optional[Dict[str, Any]] = None, context: Optional[ErrorContext] = None ) -> Dict[str, Any]: """ Update an existing document. Args: document_id: Document identifier content: New content (if provided) ast: New AST (if provided) context: Error context for tracking operations Returns: Updated document data Raises: ResourceNotFoundError: If document doesn't exist ValidationError: If input data is invalid DatabaseError: If update operation fails """ pass @abstractmethod async def delete_document( self, document_id: str, context: Optional[ErrorContext] = None ) -> bool: """ Delete a document. Args: document_id: Document identifier context: Error context for tracking operations Returns: True if document was deleted Raises: ResourceNotFoundError: If document doesn't exist DatabaseError: If deletion operation fails """ pass @abstractmethod async def get_cache_path( self, document_id: str, context: Optional[ErrorContext] = None ) -> Path: """ Get the cache file path for a document. Args: document_id: Document identifier context: Error context for tracking operations Returns: Path to cache file Raises: ResourceNotFoundError: If document doesn't exist """ pass class WorkspaceRepository(ABC): """Abstract repository for workspace file operations.""" @abstractmethod async def create_workspace( self, workspace_id: str, base_path: Path, context: Optional[ErrorContext] = None ) -> Path: """ Create a new workspace directory. Args: workspace_id: Workspace identifier base_path: Base directory for workspaces context: Error context for tracking operations Returns: Path to created workspace Raises: DuplicateResourceError: If workspace already exists ValidationError: If paths are invalid FileSystemError: If directory creation fails """ pass @abstractmethod async def get_workspace_path( self, workspace_id: str, context: Optional[ErrorContext] = None ) -> Path: """ Get the path to a workspace. Args: workspace_id: Workspace identifier context: Error context for tracking operations Returns: Path to workspace directory Raises: ResourceNotFoundError: If workspace doesn't exist """ pass @abstractmethod async def list_workspaces( self, context: Optional[ErrorContext] = None ) -> List[str]: """ List all available workspaces. Args: context: Error context for tracking operations Returns: List of workspace identifiers Raises: FileSystemError: If directory listing fails """ pass @abstractmethod async def write_file( self, workspace_id: str, file_path: str, content: str, context: Optional[ErrorContext] = None ) -> Path: """ Write content to a file in the workspace. Args: workspace_id: Workspace identifier file_path: Relative path within workspace content: File content context: Error context for tracking operations Returns: Full path to written file Raises: ResourceNotFoundError: If workspace doesn't exist ValidationError: If file path is invalid FileSystemError: If write operation fails """ pass @abstractmethod async def read_file( self, workspace_id: str, file_path: str, context: Optional[ErrorContext] = None ) -> str: """ Read content from a file in the workspace. Args: workspace_id: Workspace identifier file_path: Relative path within workspace context: Error context for tracking operations Returns: File content Raises: ResourceNotFoundError: If workspace or file doesn't exist FileSystemError: If read operation fails """ pass @abstractmethod async def delete_workspace( self, workspace_id: str, context: Optional[ErrorContext] = None ) -> bool: """ Delete a workspace and all its contents. Args: workspace_id: Workspace identifier context: Error context for tracking operations Returns: True if workspace was deleted Raises: ResourceNotFoundError: If workspace doesn't exist FileSystemError: If deletion fails """ pass @abstractmethod async def list_files( self, workspace_id: str, pattern: Optional[str] = None, context: Optional[ErrorContext] = None ) -> List[str]: """ List files in a workspace. Args: workspace_id: Workspace identifier pattern: File pattern to match context: Error context for tracking operations Returns: List of relative file paths Raises: ResourceNotFoundError: If workspace doesn't exist FileSystemError: If listing fails """ pass class CacheRepository(ABC): """Abstract repository for caching operations.""" @abstractmethod async def get( self, key: str, context: Optional[ErrorContext] = None ) -> Optional[Any]: """ Retrieve a value from cache. Args: key: Cache key context: Error context for tracking operations Returns: Cached value or None if not found Raises: CacheError: If cache operation fails """ pass @abstractmethod async def set( self, key: str, value: Any, ttl: Optional[int] = None, context: Optional[ErrorContext] = None ) -> bool: """ Store a value in cache. Args: key: Cache key value: Value to cache ttl: Time to live in seconds context: Error context for tracking operations Returns: True if value was stored Raises: CacheError: If cache operation fails """ pass @abstractmethod async def delete( self, key: str, context: Optional[ErrorContext] = None ) -> bool: """ Delete a value from cache. Args: key: Cache key context: Error context for tracking operations Returns: True if value was deleted Raises: CacheError: If cache operation fails """ pass @abstractmethod async def invalidate_pattern( self, pattern: str, context: Optional[ErrorContext] = None ) -> int: """ Invalidate cache entries matching a pattern. Args: pattern: Pattern to match (e.g., "user:*") context: Error context for tracking operations Returns: Number of invalidated entries Raises: CacheInvalidationError: If invalidation fails """ pass @abstractmethod async def store_ast_cache( self, document_id: str, ast: Dict[str, Any], context: Optional[ErrorContext] = None ) -> bool: """ Store AST cache for a document. Args: document_id: Document identifier ast: AST representation context: Error context for tracking operations Returns: True if cache was stored Raises: CacheError: If cache operation fails """ pass