Renamed table

This commit is contained in:
Jonas Linter
2025-11-18 10:09:06 +01:00
parent a181e41172
commit 10dcbae5ad

View File

@@ -1,11 +1,26 @@
import asyncio import asyncio
import hashlib import hashlib
import os import os
from typing import Any, AsyncGenerator, Callable, TypeVar from collections.abc import AsyncGenerator, Callable
from typing import TypeVar
from sqlalchemy import Boolean, Column, Date, DateTime, ForeignKey, Integer, String, JSON from sqlalchemy import (
JSON,
Boolean,
Column,
Date,
DateTime,
ForeignKey,
Integer,
String,
)
from sqlalchemy.exc import DBAPIError from sqlalchemy.exc import DBAPIError
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, create_async_engine, async_sessionmaker from sqlalchemy.ext.asyncio import (
AsyncEngine,
AsyncSession,
async_sessionmaker,
create_async_engine,
)
from sqlalchemy.orm import declarative_base, relationship from sqlalchemy.orm import declarative_base, relationship
from .logging_config import get_logger from .logging_config import get_logger
@@ -95,9 +110,7 @@ def create_database_engine(config=None, echo=False) -> AsyncEngine:
# Create engine with connect_args to set search_path for PostgreSQL # Create engine with connect_args to set search_path for PostgreSQL
connect_args = {} connect_args = {}
if schema_name and "postgresql" in database_url: if schema_name and "postgresql" in database_url:
connect_args = { connect_args = {"server_settings": {"search_path": f"{schema_name},public"}}
"server_settings": {"search_path": f"{schema_name},public"}
}
_LOGGER.info("Setting PostgreSQL search_path to: %s,public", schema_name) _LOGGER.info("Setting PostgreSQL search_path to: %s,public", schema_name)
return create_async_engine(database_url, echo=echo, connect_args=connect_args) return create_async_engine(database_url, echo=echo, connect_args=connect_args)
@@ -120,13 +133,12 @@ class ResilientAsyncSession:
Args: Args:
async_sessionmaker_: Factory for creating async sessions async_sessionmaker_: Factory for creating async sessions
engine: The SQLAlchemy async engine for connection recovery engine: The SQLAlchemy async engine for connection recovery
""" """
self.async_sessionmaker = async_sessionmaker_ self.async_sessionmaker = async_sessionmaker_
self.engine = engine self.engine = engine
async def execute_with_retry( async def execute_with_retry(self, func: Callable[..., T], *args, **kwargs) -> T:
self, func: Callable[..., T], *args, **kwargs
) -> T:
"""Execute a function with automatic retry on connection errors. """Execute a function with automatic retry on connection errors.
Args: Args:
@@ -139,6 +151,7 @@ class ResilientAsyncSession:
Raises: Raises:
The original exception if all retries are exhausted The original exception if all retries are exhausted
""" """
last_error = None last_error = None
@@ -169,7 +182,7 @@ class ResilientAsyncSession:
# Wait before retry (exponential backoff) # Wait before retry (exponential backoff)
if attempt < MAX_RETRIES - 1: if attempt < MAX_RETRIES - 1:
wait_time = RETRY_DELAY * (2 ** attempt) wait_time = RETRY_DELAY * (2**attempt)
await asyncio.sleep(wait_time) await asyncio.sleep(wait_time)
else: else:
# Not a connection-related error, re-raise immediately # Not a connection-related error, re-raise immediately
@@ -201,6 +214,7 @@ class SessionMaker:
Args: Args:
async_sessionmaker_: SQLAlchemy async_sessionmaker factory async_sessionmaker_: SQLAlchemy async_sessionmaker factory
""" """
self.async_sessionmaker = async_sessionmaker_ self.async_sessionmaker = async_sessionmaker_
@@ -210,13 +224,14 @@ class SessionMaker:
Returns: Returns:
A new AsyncSession instance ready for use. Caller is responsible A new AsyncSession instance ready for use. Caller is responsible
for managing the session lifecycle (closing when done). for managing the session lifecycle (closing when done).
""" """
return self.async_sessionmaker() return self.async_sessionmaker()
async def get_resilient_session( async def get_resilient_session(
resilient_session: "ResilientAsyncSession", resilient_session: "ResilientAsyncSession",
) -> AsyncGenerator[AsyncSession, None]: ) -> AsyncGenerator[AsyncSession]:
"""Dependency for FastAPI that provides a resilient async session. """Dependency for FastAPI that provides a resilient async session.
This generator creates a new session with automatic retry capability This generator creates a new session with automatic retry capability
@@ -227,6 +242,7 @@ async def get_resilient_session(
Yields: Yields:
AsyncSession instance for database operations AsyncSession instance for database operations
""" """
async with resilient_session.async_sessionmaker() as session: async with resilient_session.async_sessionmaker() as session:
yield session yield session
@@ -356,10 +372,19 @@ class Reservation(Base):
# Table for tracking acknowledged requests by client # Table for tracking acknowledged requests by client
class AckedRequest(Base): class AckedRequest(Base):
"""Tracks which Reservations the Client has already seen via ReadAction.
Clients can report successfull transfers via ReportNotifAction. This gets stored in this table.
This prevents re-sending the same reservation multiple times to the client.
"""
__tablename__ = "acked_requests" __tablename__ = "acked_requests"
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
client_id = Column(String, index=True) client_id = Column(String, index=True)
username = Column(String, index=True, nullable=True) # Username of the client making the request username = Column(
String, index=True, nullable=True
) # Username of the client making the request
unique_id = Column( unique_id = Column(
String, index=True String, index=True
) # Should match Reservation.form_id or another unique field ) # Should match Reservation.form_id or another unique field
@@ -371,10 +396,13 @@ class Conversion(Base):
Represents a single reservation event from the PMS XML with all its metadata. Represents a single reservation event from the PMS XML with all its metadata.
Each row links to one reservation from the PMS system. A reservation can have Each row links to one reservation from the PMS system. A reservation can have
multiple room reservations (stored in RoomReservation table). multiple room reservations (stored in ConversionRoom table).
Linked to reservations via advertising tracking data (fbclid, gclid, etc) Linked to reservations via advertising tracking data (fbclid, gclid, etc)
stored in advertisingCampagne field. stored in advertisingCampagne field.
The tracking data transferered by the PMS is however somewhat shorter.
We therefore also need to match on guest name/email and other metadata.
""" """
__tablename__ = "conversions" __tablename__ = "conversions"
@@ -423,12 +451,12 @@ class Conversion(Base):
reservation = relationship("Reservation", backref="conversions") reservation = relationship("Reservation", backref="conversions")
customer = relationship("Customer", backref="conversions") customer = relationship("Customer", backref="conversions")
hashed_customer = relationship("HashedCustomer", backref="conversions") hashed_customer = relationship("HashedCustomer", backref="conversions")
room_reservations = relationship( conversion_rooms = relationship(
"RoomReservation", back_populates="conversion", cascade="all, delete-orphan" "ConversionRoom", back_populates="conversion", cascade="all, delete-orphan"
) )
class RoomReservation(Base): class ConversionRoom(Base):
"""Room reservation data from hotel PMS. """Room reservation data from hotel PMS.
Represents a single room reservation within a conversion/PMS reservation. Represents a single room reservation within a conversion/PMS reservation.
@@ -438,7 +466,7 @@ class RoomReservation(Base):
for efficient querying. for efficient querying.
""" """
__tablename__ = "room_reservations" __tablename__ = "conversion_rooms"
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
# Link to the parent conversion/PMS reservation # Link to the parent conversion/PMS reservation
@@ -478,4 +506,4 @@ class RoomReservation(Base):
updated_at = Column(DateTime(timezone=True)) # When this record was last updated updated_at = Column(DateTime(timezone=True)) # When this record was last updated
# Relationships # Relationships
conversion = relationship("Conversion", back_populates="room_reservations") conversion = relationship("Conversion", back_populates="conversion_rooms")