db.py 3.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. from sqlalchemy import create_engine, event
  2. from sqlalchemy.orm import Session, declarative_base, sessionmaker
  3. from .config import settings
  4. from .logging_setup import db_operation, db_query, log_query_error, query_origin
  5. def install_engine_logging(engine):
  6. if getattr(engine, '_gc_logging', False): return
  7. engine._gc_logging = True
  8. @event.listens_for(engine, 'before_cursor_execute')
  9. def executing(conn, cursor, statement, parameters, context, executemany):
  10. context._gc_operation = 'execute'
  11. @event.listens_for(engine, 'after_cursor_execute')
  12. def executed(conn, cursor, statement, parameters, context, executemany):
  13. context._gc_operation = 'fetch'
  14. @event.listens_for(engine, 'handle_error')
  15. def query_failed(context):
  16. # Observational only: never replace exceptions or affect connection invalidation.
  17. error = context.sqlalchemy_exception or context.original_exception
  18. options = getattr(context.execution_context, 'execution_options', {}) or {}
  19. active = db_query.get() or {}
  20. statement = context.statement if context.statement is not None else active.get('statement')
  21. log_query_error(error, statement, options.get('query_name') or active.get('query_name'),
  22. db_operation.get() or getattr(context.execution_context, '_gc_operation', 'connect_or_transaction'),
  23. parameters=context.parameters)
  24. class LoggingSession(Session):
  25. def execute(self, statement, params=None, *, execution_options=None, **kwargs):
  26. options = dict(execution_options or {})
  27. origin = options.setdefault('query_name', getattr(statement, '_execution_options', {}).get('query_name') or query_origin())
  28. token = db_operation.set('execute')
  29. query_token = db_query.set({'statement': statement, 'query_name': origin})
  30. try:
  31. return super().execute(statement, params, execution_options=options, **kwargs)
  32. except Exception as error:
  33. log_query_error(error, statement, origin, 'execute', parameters=params)
  34. raise
  35. finally:
  36. db_query.reset(query_token)
  37. db_operation.reset(token)
  38. def _transaction_operation(self, operation):
  39. token = db_operation.set(operation)
  40. try:
  41. return getattr(super(), operation)()
  42. except Exception as error:
  43. log_query_error(error, operation=operation)
  44. raise
  45. finally:
  46. db_operation.reset(token)
  47. def commit(self): return self._transaction_operation('commit')
  48. def rollback(self): return self._transaction_operation('rollback')
  49. def flush(self, objects=None):
  50. token = db_operation.set('flush')
  51. try:
  52. return super().flush(objects)
  53. except Exception as error:
  54. log_query_error(error, operation='flush')
  55. raise
  56. finally:
  57. db_operation.reset(token)
  58. engine = create_engine(
  59. settings.DATABASE_URL,
  60. connect_args={"check_same_thread": False} if settings.DATABASE_URL.startswith("sqlite") else {},
  61. hide_parameters=True,
  62. )
  63. install_engine_logging(engine)
  64. SessionLocal = sessionmaker(bind=engine, class_=LoggingSession, autoflush=False, autocommit=False)
  65. Base = declarative_base()
  66. def get_session():
  67. db = SessionLocal()
  68. try:
  69. yield db
  70. finally:
  71. db.close()