sp-hydra-veil-core/core/models/manage/wrapper.py

102 lines
3.3 KiB
Python

# custom
from core.errors.logger import logger
from core.models.DatabaseOperation import DatabaseOperation, DBErrorType
from core.models.manage.migrations import migrate_sql
from core.models.manage.session_management import get_session
# generic
from sqlalchemy.orm import Session
from sqlalchemy.exc import (
OperationalError,
IntegrityError,
SQLAlchemyError
)
from typing import Type, Dict, Any, Callable, TypeVar
from functools import wraps
from enum import Enum
def safe_db_operation(func):
def wrapper(*args, **kwargs):
# get the Session for the function it's wrapping,
session = get_session()
try:
# passes the session to the function,
result = func(*args, session=session, **kwargs)
# If func already returns DatabaseOperation, pass it through
if isinstance(result, DatabaseOperation):
return result
# Otherwise, wrap success
return DatabaseOperation(valid=True, data=result)
except OperationalError as e:
logger.error(f"Schema mismatch in {func.__name__}: {e}")
session.rollback()
try:
logger.info("Attempting migration...")
migrate_sql()
# try again:
result = func(*args, session=session, **kwargs)
return DatabaseOperation(
valid=True,
data=result,
tried_migration=True,
message="Recovered via migration"
)
except Exception as retry_error: # migration failed:
logger.error(f"Migration failed with error: {str(retry_error)}", exc_info=True)
session.rollback()
return DatabaseOperation(
valid=False,
error_type=DBErrorType.MIGRATION_FAILED,
message=str(retry_error)
)
# This is really TypeErrors, passed from the insert function.
except WrapperRollback as e:
session.rollback()
return e.database_operation
except IntegrityError as e:
error_msg = f"IntegrityError caught: {e.orig}"
logger.error(error_msg)
session.rollback()
return DatabaseOperation(
valid=False,
error_type=DBErrorType.INTEGRITY_ERROR,
message=error_msg
)
except SQLAlchemyError as e:
error_msg = f"SQLAlchemyError caught: {e}"
logger.error(error_msg)
session.rollback()
return DatabaseOperation(
valid=False,
error_type=DBErrorType.UNKNOWN,
message=str(e)
)
except Exception as e:
# Catch-all for unexpected errors
session.rollback()
return DatabaseOperation(
valid=False,
error_type=DBErrorType.UNKNOWN,
message=str(e)
)
return wrapper
class WrapperRollback(Exception):
"""Raised by wrapped function to signal wrapper should rollback and return error."""
def __init__(self, database_operation: DatabaseOperation):
self.database_operation = database_operation