Additonal validation and better type hints

This commit is contained in:
Jonas Linter
2025-10-07 16:28:43 +02:00
parent e605af1231
commit a69816baa4
3 changed files with 86 additions and 87 deletions

View File

@@ -6,7 +6,14 @@ from enum import Enum
from typing import Any
from alpine_bits_python.db import Customer, Reservation
from alpine_bits_python.schemas import HotelReservationIdData
from alpine_bits_python.schemas import (
CommentData,
CommentListItemData,
CommentsData,
CustomerData,
HotelReservationIdData,
PhoneTechType,
)
# Import the generated classes
from .generated.alpinebits import (
@@ -68,13 +75,6 @@ NotifHotelReservation = OtaHotelResNotifRq.HotelReservations.HotelReservation
RetrieveHotelReservation = OtaResRetrieveRs.ReservationsList.HotelReservation
# phonetechtype enum 1,3,5 voice, fax, mobile
class PhoneTechType(Enum):
VOICE = "1"
FAX = "3"
MOBILE = "5"
# Enum to specify which OTA message type to use
class OtaMessageType(Enum):
NOTIF = "notification" # For OtaHotelResNotifRq
@@ -88,37 +88,6 @@ class KidsAgeData:
ages: list[int]
@dataclass
class CustomerData:
"""Simple data class to hold customer information without nested type constraints."""
given_name: str
surname: str
name_prefix: None | str = None
name_title: None | str = None
phone_numbers: list[tuple[str, None | PhoneTechType]] = (
None # (phone_number, phone_tech_type)
)
email_address: None | str = None
email_newsletter: None | bool = (
None # True for "yes", False for "no", None for not specified
)
address_line: None | str = None
city_name: None | str = None
postal_code: None | str = None
country_code: None | str = None # Two-letter country code
address_catalog: None | bool = (
None # True for "yes", False for "no", None for not specified
)
gender: None | str = None # "Unknown", "Male", "Female"
birth_date: None | str = None
language: None | str = None # Two-letter language code
def __post_init__(self):
if self.phone_numbers is None:
self.phone_numbers = []
class GuestCountsFactory:
"""Factory class to create GuestCounts instances for both OtaHotelResNotifRq and OtaResRetrieveRs."""
@@ -129,6 +98,7 @@ class GuestCountsFactory:
message_type: OtaMessageType = OtaMessageType.RETRIEVE,
) -> NotifGuestCounts:
"""Create a GuestCounts object for OtaHotelResNotifRq or OtaResRetrieveRs.
:param adults: Number of adults
:param kids: List of ages for each kid (optional)
:return: GuestCounts instance
@@ -147,7 +117,8 @@ class GuestCountsFactory:
def _create_guest_counts(
adults: int, kids: list[int] | None, guest_counts_class: type
) -> Any:
"""Internal method to create a GuestCounts object of the specified type.
"""Create a GuestCounts object of the specified type.
:param adults: Number of adults
:param kids: List of ages for each kid (optional)
:param guest_counts_class: The GuestCounts class to instantiate
@@ -173,7 +144,7 @@ class GuestCountsFactory:
class CustomerFactory:
"""Factory class to create Customer instances for both OtaHotelResNotifRq and OtaResRetrieveRs."""
"""Factory class to create Customer instances for both Retrieve and Notif."""
@staticmethod
def create_notif_customer(data: CustomerData) -> NotifCustomer:
@@ -186,8 +157,10 @@ class CustomerFactory:
return CustomerFactory._create_customer(RetrieveCustomer, data)
@staticmethod
def _create_customer(customer_class: type, data: CustomerData) -> Any:
"""Internal method to create a customer of the specified type."""
def _create_customer(
customer_class: type[RetrieveCustomer | NotifCustomer], data: CustomerData
) -> Any:
"""Create a customer of the specified type."""
# Create PersonName
person_name = customer_class.PersonName(
given_name=data.given_name,
@@ -260,19 +233,21 @@ class CustomerFactory:
@staticmethod
def _customer_to_data(customer: Any) -> CustomerData:
"""Internal method to convert any customer type to CustomerData."""
"""Convert any customer type to CustomerData."""
# Extract phone numbers
phone_numbers = []
if customer.telephone:
for tel in customer.telephone:
phone_numbers.append(
phone_numbers.extend(
[
(
tel.phone_number,
PhoneTechType(tel.phone_tech_type)
if tel.phone_tech_type
else None,
)
)
for tel in customer.telephone
]
)
# Extract email info
email_address = None
@@ -389,39 +364,6 @@ class HotelReservationIdFactory:
)
@dataclass
class CommentListItemData:
"""Simple data class to hold comment list item information."""
value: str # The text content of the list item
list_item: str # Numeric identifier (pattern: [0-9]+)
language: str # Two-letter language code (pattern: [a-z][a-z])
@dataclass
class CommentData:
"""Simple data class to hold comment information without nested type constraints."""
name: CommentName2 # Required: "included services", "customer comment", "additional info"
text: str | None = None # Optional text content
list_items: list[CommentListItemData] = None # Optional list items
def __post_init__(self):
if self.list_items is None:
self.list_items = []
@dataclass
class CommentsData:
"""Simple data class to hold multiple comments (1-3 max)."""
comments: list[CommentData] = None # 1-3 comments maximum
def __post_init__(self):
if self.comments is None:
self.comments = []
class CommentFactory:
"""Factory class to create Comment instances for both OtaHotelResNotifRq and OtaResRetrieveRs."""
@@ -494,11 +436,7 @@ class CommentFactory:
)
)
# Extract comment data
comment_data = CommentData(
name=comment.name, text=comment.text, list_items=list_items_data
)
comments_data_list.append(comment_data)
comments_data_list.append(comment)
return CommentsData(comments=comments_data_list)
@@ -527,7 +465,9 @@ class ResGuestFactory:
@staticmethod
def _create_res_guests(
res_guests_class: type, customer_class: type, customer_data: CustomerData
res_guests_class: type[RetrieveResGuests] | type[NotifResGuests],
customer_class: type[NotifCustomer | RetrieveCustomer],
customer_data: CustomerData,
) -> Any:
"""Create the complete ResGuests structure."""
# Create the customer using the existing CustomerFactory

View File

@@ -10,10 +10,18 @@ from XML generation (xsdata) follows clean architecture principles.
"""
from datetime import date
from enum import Enum
from pydantic import BaseModel, EmailStr, Field, field_validator, model_validator
# phonetechtype enum 1,3,5 voice, fax, mobile
class PhoneTechType(Enum):
VOICE = "1"
FAX = "3"
MOBILE = "5"
class PhoneNumber(BaseModel):
"""Phone number with optional type."""
@@ -34,7 +42,7 @@ class CustomerData(BaseModel):
surname: str = Field(..., min_length=1, max_length=100)
name_prefix: str | None = Field(None, max_length=20)
name_title: str | None = Field(None, max_length=20)
phone_numbers: list[PhoneNumber] = Field(default_factory=list)
phone_numbers: list[tuple[str, None | PhoneTechType]] = Field(default_factory=list)
email_address: EmailStr | None = None
email_newsletter: bool | None = None
address_line: str | None = Field(None, max_length=255)