New pydantic model for ConversionGuest
This commit is contained in:
@@ -455,6 +455,82 @@ class WebhookRequestData(BaseModel):
|
||||
|
||||
|
||||
# Example usage in a service layer
|
||||
class ConversionGuestData(BaseModel):
|
||||
"""Validated conversion guest data from PMS XML.
|
||||
|
||||
Handles validation and hashing for guest records extracted from
|
||||
hotel PMS conversion XML files.
|
||||
"""
|
||||
|
||||
hotel_id: str = Field(..., min_length=1, max_length=50)
|
||||
guest_id: int = Field(..., gt=0)
|
||||
guest_first_name: str | None = Field(None, max_length=100)
|
||||
guest_last_name: str | None = Field(None, max_length=100)
|
||||
guest_email: str | None = Field(None, max_length=200)
|
||||
guest_country_code: str | None = Field(None, max_length=10)
|
||||
guest_birth_date: date | None = None
|
||||
|
||||
# Auto-calculated hashed fields
|
||||
hashed_first_name: str | None = Field(None, max_length=64)
|
||||
hashed_last_name: str | None = Field(None, max_length=64)
|
||||
hashed_email: str | None = Field(None, max_length=64)
|
||||
hashed_country_code: str | None = Field(None, max_length=64)
|
||||
hashed_birth_date: str | None = Field(None, max_length=64)
|
||||
|
||||
# Timestamps
|
||||
first_seen: datetime = Field(default_factory=lambda: datetime.now(UTC))
|
||||
last_seen: datetime = Field(default_factory=lambda: datetime.now(UTC))
|
||||
|
||||
@staticmethod
|
||||
def _normalize_and_hash(value: str | None) -> str | None:
|
||||
"""Normalize and hash a value for privacy-preserving matching.
|
||||
|
||||
Uses the same logic as ConversionGuest._normalize_and_hash.
|
||||
"""
|
||||
if value is None or value == "":
|
||||
return None
|
||||
# Normalize: lowercase, strip whitespace
|
||||
normalized = value.lower().strip()
|
||||
if not normalized:
|
||||
return None
|
||||
# Hash with SHA256
|
||||
return hashlib.sha256(normalized.encode("utf-8")).hexdigest()
|
||||
|
||||
@model_validator(mode="after")
|
||||
def calculate_hashes(self) -> "ConversionGuestData":
|
||||
"""Auto-calculate hashed fields from plain text fields."""
|
||||
if self.hashed_first_name is None:
|
||||
self.hashed_first_name = self._normalize_and_hash(self.guest_first_name)
|
||||
if self.hashed_last_name is None:
|
||||
self.hashed_last_name = self._normalize_and_hash(self.guest_last_name)
|
||||
if self.hashed_email is None:
|
||||
self.hashed_email = self._normalize_and_hash(self.guest_email)
|
||||
if self.hashed_country_code is None:
|
||||
self.hashed_country_code = self._normalize_and_hash(self.guest_country_code)
|
||||
if self.hashed_birth_date is None and self.guest_birth_date is not None:
|
||||
self.hashed_birth_date = self._normalize_and_hash(
|
||||
self.guest_birth_date.isoformat()
|
||||
)
|
||||
return self
|
||||
|
||||
@field_validator("guest_id", mode="before")
|
||||
@classmethod
|
||||
def convert_guest_id_to_int(cls, v: Any) -> int:
|
||||
"""Convert guest_id to integer (handles string input from XML)."""
|
||||
if v is None:
|
||||
raise ValueError("guest_id cannot be None")
|
||||
if isinstance(v, int):
|
||||
return v
|
||||
if isinstance(v, str):
|
||||
try:
|
||||
return int(v)
|
||||
except ValueError as e:
|
||||
raise ValueError(f"guest_id must be a valid integer, got: {v}") from e
|
||||
raise ValueError(f"guest_id must be int or str, got: {type(v)}")
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class ReservationService:
|
||||
"""Example service showing how to use Pydantic models with SQLAlchemy."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user