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>
This commit is contained in:
680
infrastructure/repositories/interfaces.py
Normal file
680
infrastructure/repositories/interfaces.py
Normal file
@@ -0,0 +1,680 @@
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user