Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions src/sql_safe/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,22 @@
)

__all__ = [
"mysql_with_timeout",
"query",
"read_only_query",
"mysql_with_timeout",
"query",
"read_only_query",
"get_engine",
# Exceptions
"SqlSafeError",
"ValidationError",
"ConfigurationError",
"ResourceError",
"ResourceError",
"OperationError",
"DatabaseConnectionError",
"QueryExecutionError",
"QueryTimeoutError",
# Validation
"validate_not_none",
"validate_string",
"validate_string",
"validate_number",
"validate_path",
"validate_dict",
Expand Down
103 changes: 44 additions & 59 deletions src/sql_safe/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,16 @@

class IcatSqlSafeError(Exception):
"""Base exception for all icat-sql-safe errors."""

def __init__(
self,
message: str,
self,
message: str,
details: Optional[Dict[str, Any]] = None,
suggestion: Optional[str] = None
suggestion: Optional[str] = None,
) -> None:
"""
Initialize the exception with detailed error information.

Args:
message: Human-readable error message
details: Additional error context and debugging information
Expand All @@ -34,34 +34,30 @@ def __init__(
self.message = message
self.details = details or {}
self.suggestion = suggestion

def __str__(self) -> str:
"""Return formatted error message with details and suggestions."""
result = self.message

if self.details:
details_str = ", ".join(f"{k}={v}" for k, v in self.details.items())
result += f" (Details: {details_str})"

if self.suggestion:
result += f" Suggestion: {self.suggestion}"

return result


class ValidationError(IcatSqlSafeError):
"""Raised when input validation fails."""

def __init__(
self,
field: str,
value: Any,
expected: str,
suggestion: Optional[str] = None
self, field: str, value: Any, expected: str, suggestion: Optional[str] = None
) -> None:
"""
Initialize validation error with field-specific information.

Args:
field: Name of the field that failed validation
value: The invalid value that was provided
Expand All @@ -78,16 +74,11 @@ def __init__(

class ConfigurationError(IcatSqlSafeError):
"""Raised when configuration is invalid or missing."""

def __init__(
self,
config_key: str,
issue: str,
suggestion: Optional[str] = None
) -> None:

def __init__(self, config_key: str, issue: str, suggestion: Optional[str] = None) -> None:
"""
Initialize configuration error.

Args:
config_key: The configuration key that has an issue
issue: Description of the configuration problem
Expand All @@ -102,17 +93,17 @@ def __init__(

class ResourceError(IcatSqlSafeError):
"""Raised when system resources are unavailable or exhausted."""

def __init__(
self,
resource: str,
self,
resource: str,
issue: str,
current_usage: Optional[str] = None,
suggestion: Optional[str] = None
suggestion: Optional[str] = None,
) -> None:
"""
Initialize resource error.

Args:
resource: The resource that is unavailable (memory, disk, network, etc.)
issue: Description of the resource problem
Expand All @@ -131,17 +122,17 @@ def __init__(

class OperationError(IcatSqlSafeError):
"""Raised when an operation fails due to business logic or external factors."""

def __init__(
self,
operation: str,
self,
operation: str,
reason: str,
retry_possible: bool = False,
suggestion: Optional[str] = None
suggestion: Optional[str] = None,
) -> None:
"""
Initialize operation error.

Args:
operation: The operation that failed
reason: Why the operation failed
Expand All @@ -158,54 +149,48 @@ def __init__(

class DatabaseConnectionError(ResourceError):
"""Raised when database connection fails."""

def __init__(
self,
self,
database_url: str,
original_error: Optional[str] = None,
suggestion: Optional[str] = None
suggestion: Optional[str] = None,
) -> None:
issue = f"Failed to connect to database: {original_error}" if original_error else "Connection failed"
issue = (
f"Failed to connect to database: {original_error}"
if original_error
else "Connection failed"
)
super().__init__(
"database_connection",
issue,
suggestion=suggestion or "Check database URL, credentials, and network connectivity"
"database_connection",
issue,
suggestion=suggestion or "Check database URL, credentials, and network connectivity",
)
self.database_url = database_url
self.original_error = original_error


class QueryExecutionError(OperationError):
"""Raised when SQL query execution fails."""

def __init__(
self,
query: str,
error_message: str,
suggestion: Optional[str] = None
) -> None:

def __init__(self, query: str, error_message: str, suggestion: Optional[str] = None) -> None:
super().__init__(
"query_execution",
"query_execution",
f"Query failed: {error_message}",
suggestion=suggestion or "Check query syntax and database permissions"
suggestion=suggestion or "Check query syntax and database permissions",
)
self.query = query
self.error_message = error_message


class QueryTimeoutError(TimeoutError):
"""Raised when SQL query times out."""

def __init__(
self,
query: str,
timeout_ms: int,
suggestion: Optional[str] = None
) -> None:

def __init__(self, query: str, timeout_ms: int, suggestion: Optional[str] = None) -> None:
super().__init__(
timeout_ms / 1000,
timeout_ms / 1000,
"query_execution",
suggestion or "Try optimizing the query or increasing timeout_ms"
suggestion or "Try optimizing the query or increasing timeout_ms",
)
self.query = query
self.timeout_ms = timeout_ms
4 changes: 1 addition & 3 deletions src/sql_safe/safe_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,7 @@ def wrapper(*args: Any, **kwargs: Any) -> Any:
elapsed_ms = (time.perf_counter() - start_time) * 1000

if elapsed_ms > ms:
logger.warning(
f"🐢 Slow query detected: {elapsed_ms:.1f}ms (threshold: {ms}ms)"
)
logger.warning(f"🐢 Slow query detected: {elapsed_ms:.1f}ms (threshold: {ms}ms)")
else:
logger.debug(f"Query completed in {elapsed_ms:.1f}ms")

Expand Down
Loading
Loading