Files
markitect-main/infrastructure/repositories/interfaces.py
tegwick f782ac1f69 fix: Add missing infrastructure files from data access improvements
Add infrastructure components that were created during issue #24
but not properly committed:

- Data access repositories and interfaces
- Connection management infrastructure
- Exception handling framework
- Configuration management
- Documentation from data access pattern improvements

These files are essential infrastructure components that enable
the repository pattern and improved data access strategies.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-27 08:35:34 +02:00

680 lines
17 KiB
Python

"""
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