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>
680 lines
17 KiB
Python
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 |