Add granular DB error types with retry logic
New exceptions: DatabaseBusyError, DatabasePermissionDeniedError, DatabasePathInvalidError, DatabaseCorruptedError, DatabaseUnavailableError. open_db creates parent directory if missing. Catches all aiosqlite errors and maps to specific exception types. get_db retries up to 3x on locked database with backoff. Propagates specific exceptions instead of generic HTTPException. Tests for all new error types and retry behavior.
This commit is contained in:
@@ -473,6 +473,75 @@ class SetupAlreadyCompleteError(ConflictError):
|
||||
super().__init__("Setup has already been completed.")
|
||||
|
||||
|
||||
class DatabaseBusyError(ServiceUnavailableError):
|
||||
"""Raised when the SQLite database is locked or busy after all retries."""
|
||||
|
||||
error_code: str = "database_busy"
|
||||
|
||||
def __init__(self, database_path: str, retries: int) -> None:
|
||||
self.database_path = database_path
|
||||
self.retries = retries
|
||||
super().__init__(
|
||||
f"Database is temporarily busy after {retries} retries."
|
||||
)
|
||||
|
||||
def get_error_metadata(self) -> ErrorMetadata:
|
||||
return {"database_path": self.database_path, "retries": self.retries}
|
||||
|
||||
|
||||
class DatabasePermissionDeniedError(ServiceUnavailableError):
|
||||
"""Raised when the database file cannot be accessed due to insufficient permissions."""
|
||||
|
||||
error_code: str = "database_permission_denied"
|
||||
|
||||
def __init__(self, database_path: str) -> None:
|
||||
self.database_path = database_path
|
||||
super().__init__("Insufficient permissions to access the database file.")
|
||||
|
||||
def get_error_metadata(self) -> ErrorMetadata:
|
||||
return {"database_path": self.database_path}
|
||||
|
||||
|
||||
class DatabasePathInvalidError(ServiceUnavailableError):
|
||||
"""Raised when the database directory does not exist or the path is invalid."""
|
||||
|
||||
error_code: str = "database_path_invalid"
|
||||
|
||||
def __init__(self, database_path: str) -> None:
|
||||
self.database_path = database_path
|
||||
super().__init__("Database directory does not exist or path is invalid.")
|
||||
|
||||
def get_error_metadata(self) -> ErrorMetadata:
|
||||
return {"database_path": self.database_path}
|
||||
|
||||
|
||||
class DatabaseCorruptedError(ServiceUnavailableError):
|
||||
"""Raised when the database file is corrupted."""
|
||||
|
||||
error_code: str = "database_corrupted"
|
||||
|
||||
def __init__(self, database_path: str) -> None:
|
||||
self.database_path = database_path
|
||||
super().__init__("Database file is corrupted.")
|
||||
|
||||
def get_error_metadata(self) -> ErrorMetadata:
|
||||
return {"database_path": self.database_path}
|
||||
|
||||
|
||||
class DatabaseUnavailableError(ServiceUnavailableError):
|
||||
"""Raised for any other unexpected database error."""
|
||||
|
||||
error_code: str = "database_unavailable"
|
||||
|
||||
def __init__(self, database_path: str, error: str) -> None:
|
||||
self.database_path = database_path
|
||||
self.error = error
|
||||
super().__init__(f"Database is not available: {error}")
|
||||
|
||||
def get_error_metadata(self) -> ErrorMetadata:
|
||||
return {"database_path": self.database_path, "error": self.error}
|
||||
|
||||
|
||||
class BlocklistSourceNotFoundError(NotFoundError):
|
||||
"""Raised when a blocklist source is not found."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user