Added an elegant simplified creation method for customers
This commit is contained in:
Binary file not shown.
@@ -7,6 +7,7 @@ requires-python = ">=3.13"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"generateds>=2.44.3",
|
"generateds>=2.44.3",
|
||||||
"lxml>=6.0.1",
|
"lxml>=6.0.1",
|
||||||
|
"ruff>=0.13.1",
|
||||||
"xsdata-pydantic[cli,lxml,soap]>=24.5",
|
"xsdata-pydantic[cli,lxml,soap]>=24.5",
|
||||||
"xsdata[cli,lxml,soap]>=25.7",
|
"xsdata[cli,lxml,soap]>=25.7",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -3,12 +3,16 @@ from datetime import datetime, timezone
|
|||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# TimeSpan class according to XSD: <TimeSpan Start="..." End="..." Duration="..." StartWindow="..." EndWindow="..."/>
|
# TimeSpan class according to XSD: <TimeSpan Start="..." End="..." Duration="..." StartWindow="..." EndWindow="..."/>
|
||||||
class TimeSpan:
|
class TimeSpan:
|
||||||
def __init__(self, start: str, end: str = None, duration: str = None, start_window: str = None, end_window: str = None):
|
def __init__(
|
||||||
|
self,
|
||||||
|
start: str,
|
||||||
|
end: str = None,
|
||||||
|
duration: str = None,
|
||||||
|
start_window: str = None,
|
||||||
|
end_window: str = None,
|
||||||
|
):
|
||||||
self.start = start
|
self.start = start
|
||||||
self.end = end
|
self.end = end
|
||||||
self.duration = duration
|
self.duration = duration
|
||||||
@@ -29,16 +33,27 @@ class TimeSpan:
|
|||||||
|
|
||||||
|
|
||||||
NAMESPACE = "http://www.opentravel.org/OTA/2003/05"
|
NAMESPACE = "http://www.opentravel.org/OTA/2003/05"
|
||||||
ET.register_namespace('', NAMESPACE)
|
ET.register_namespace("", NAMESPACE)
|
||||||
|
|
||||||
|
|
||||||
def _ns(tag):
|
def _ns(tag):
|
||||||
return f"{{{NAMESPACE}}}{tag}"
|
return f"{{{NAMESPACE}}}{tag}"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class ResGuest:
|
class ResGuest:
|
||||||
def __init__(self, given_name: str, surname: str, gender: Optional[str] = None, birth_date: Optional[str] = None, language: Optional[str] = None, name_prefix: Optional[str] = None, name_title: Optional[str] = None, email: Optional[str] = None, address: Optional[dict] = None, telephones: Optional[list] = None):
|
def __init__(
|
||||||
|
self,
|
||||||
|
given_name: str,
|
||||||
|
surname: str,
|
||||||
|
gender: Optional[str] = None,
|
||||||
|
birth_date: Optional[str] = None,
|
||||||
|
language: Optional[str] = None,
|
||||||
|
name_prefix: Optional[str] = None,
|
||||||
|
name_title: Optional[str] = None,
|
||||||
|
email: Optional[str] = None,
|
||||||
|
address: Optional[dict] = None,
|
||||||
|
telephones: Optional[list] = None,
|
||||||
|
):
|
||||||
self.given_name = given_name
|
self.given_name = given_name
|
||||||
self.surname = surname
|
self.surname = surname
|
||||||
self.gender = gender
|
self.gender = gender
|
||||||
@@ -91,6 +106,7 @@ class ResGuest:
|
|||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
elem = self.to_xml()
|
elem = self.to_xml()
|
||||||
xml_bytes = ET.tostring(elem, encoding="utf-8")
|
xml_bytes = ET.tostring(elem, encoding="utf-8")
|
||||||
parser = etree.XMLParser(remove_blank_text=True)
|
parser = etree.XMLParser(remove_blank_text=True)
|
||||||
@@ -98,14 +114,6 @@ class ResGuest:
|
|||||||
return etree.tostring(lxml_elem, pretty_print=True, encoding="unicode")
|
return etree.tostring(lxml_elem, pretty_print=True, encoding="unicode")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class RoomStay:
|
class RoomStay:
|
||||||
def __init__(self, room_type: str, timespan: TimeSpan, guests: List[ResGuest]):
|
def __init__(self, room_type: str, timespan: TimeSpan, guests: List[ResGuest]):
|
||||||
self.room_type = room_type
|
self.room_type = room_type
|
||||||
@@ -114,7 +122,9 @@ class RoomStay:
|
|||||||
|
|
||||||
def to_xml(self):
|
def to_xml(self):
|
||||||
roomstay_elem = ET.Element(_ns("RoomStay"))
|
roomstay_elem = ET.Element(_ns("RoomStay"))
|
||||||
ET.SubElement(roomstay_elem, _ns("RoomType")).set("RoomTypeCode", self.room_type)
|
ET.SubElement(roomstay_elem, _ns("RoomType")).set(
|
||||||
|
"RoomTypeCode", self.room_type
|
||||||
|
)
|
||||||
roomstay_elem.append(self.timespan.to_xml())
|
roomstay_elem.append(self.timespan.to_xml())
|
||||||
guests_elem = ET.SubElement(roomstay_elem, _ns("Guests"))
|
guests_elem = ET.SubElement(roomstay_elem, _ns("Guests"))
|
||||||
for guest in self.guests:
|
for guest in self.guests:
|
||||||
@@ -123,6 +133,7 @@ class RoomStay:
|
|||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
elem = self.to_xml()
|
elem = self.to_xml()
|
||||||
xml_bytes = ET.tostring(elem, encoding="utf-8")
|
xml_bytes = ET.tostring(elem, encoding="utf-8")
|
||||||
parser = etree.XMLParser(remove_blank_text=True)
|
parser = etree.XMLParser(remove_blank_text=True)
|
||||||
@@ -131,7 +142,13 @@ class RoomStay:
|
|||||||
|
|
||||||
|
|
||||||
class Reservation:
|
class Reservation:
|
||||||
def __init__(self, reservation_id: str, hotel_code: str, roomstays: List[RoomStay], create_time: Optional[str] = None):
|
def __init__(
|
||||||
|
self,
|
||||||
|
reservation_id: str,
|
||||||
|
hotel_code: str,
|
||||||
|
roomstays: List[RoomStay],
|
||||||
|
create_time: Optional[str] = None,
|
||||||
|
):
|
||||||
self.reservation_id = reservation_id
|
self.reservation_id = reservation_id
|
||||||
self.hotel_code = hotel_code
|
self.hotel_code = hotel_code
|
||||||
self.roomstays = roomstays
|
self.roomstays = roomstays
|
||||||
@@ -151,10 +168,10 @@ class Reservation:
|
|||||||
return res_elem
|
return res_elem
|
||||||
|
|
||||||
def to_xml_string(self):
|
def to_xml_string(self):
|
||||||
root = ET.Element(_ns("OTA_ResRetrieveRS"), {
|
root = ET.Element(
|
||||||
"Version": "2024-10",
|
_ns("OTA_ResRetrieveRS"),
|
||||||
"TimeStamp": datetime.now(timezone.utc).isoformat()
|
{"Version": "2024-10", "TimeStamp": datetime.now(timezone.utc).isoformat()},
|
||||||
})
|
)
|
||||||
success_elem = ET.SubElement(root, _ns("Success"))
|
success_elem = ET.SubElement(root, _ns("Success"))
|
||||||
reservations_list = ET.SubElement(root, _ns("ReservationsList"))
|
reservations_list = ET.SubElement(root, _ns("ReservationsList"))
|
||||||
reservations_list.append(self.to_xml())
|
reservations_list.append(self.to_xml())
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
145
src/main.py
145
src/main.py
@@ -6,26 +6,9 @@ from datetime import datetime, timezone
|
|||||||
import re
|
import re
|
||||||
from xsdata_pydantic.bindings import XmlSerializer
|
from xsdata_pydantic.bindings import XmlSerializer
|
||||||
|
|
||||||
def validate_gender(gender: str) -> str:
|
from simplified_access import CustomerData, CustomerFactory, PhoneTechType
|
||||||
"""Validate gender field against the pattern (Unknown|Male|Female)"""
|
|
||||||
if gender and not re.match(r"^(Unknown|Male|Female)$", gender):
|
|
||||||
raise ValueError(f"Invalid gender value: '{gender}'. Must be one of: Unknown, Male, Female")
|
|
||||||
return gender
|
|
||||||
|
|
||||||
def validate_dataclass_patterns(obj) -> None:
|
|
||||||
"""Custom validation function to check pattern constraints"""
|
|
||||||
# Add validation for known pattern fields
|
|
||||||
if hasattr(obj, 'gender') and obj.gender:
|
|
||||||
validate_gender(obj.gender)
|
|
||||||
|
|
||||||
# Recursively validate nested objects
|
|
||||||
for field_name, field_value in vars(obj).items():
|
|
||||||
if hasattr(field_value, '__dataclass_fields__'):
|
|
||||||
validate_dataclass_patterns(field_value)
|
|
||||||
elif isinstance(field_value, list):
|
|
||||||
for item in field_value:
|
|
||||||
if hasattr(item, '__dataclass_fields__'):
|
|
||||||
validate_dataclass_patterns(item)
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
# Success - use None instead of object() for cleaner XML output
|
# Success - use None instead of object() for cleaner XML output
|
||||||
@@ -33,41 +16,70 @@ def main():
|
|||||||
|
|
||||||
# UniqueID
|
# UniqueID
|
||||||
unique_id = ab.OtaResRetrieveRs.ReservationsList.HotelReservation.UniqueId(
|
unique_id = ab.OtaResRetrieveRs.ReservationsList.HotelReservation.UniqueId(
|
||||||
type_value=ab.UniqueIdType2.VALUE_14,
|
type_value=ab.UniqueIdType2.VALUE_14, id="6b34fe24ac2ff811"
|
||||||
id="6b34fe24ac2ff811"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# TimeSpan - use the actual nested class
|
# TimeSpan - use the actual nested class
|
||||||
time_span = ab.OtaResRetrieveRs.ReservationsList.HotelReservation.RoomStays.RoomStay.TimeSpan()
|
|
||||||
|
start_date_window = ab.OtaResRetrieveRs.ReservationsList.HotelReservation.RoomStays.RoomStay.TimeSpan.StartDateWindow(
|
||||||
|
earliest_date="2024-10-01", latest_date="2024-10-02"
|
||||||
|
)
|
||||||
|
|
||||||
|
time_span = ab.OtaResRetrieveRs.ReservationsList.HotelReservation.RoomStays.RoomStay.TimeSpan(
|
||||||
|
start_date_window=start_date_window
|
||||||
|
)
|
||||||
|
|
||||||
# RoomStay with TimeSpan
|
# RoomStay with TimeSpan
|
||||||
room_stay = ab.OtaResRetrieveRs.ReservationsList.HotelReservation.RoomStays.RoomStay(
|
room_stay = (
|
||||||
time_span=time_span
|
ab.OtaResRetrieveRs.ReservationsList.HotelReservation.RoomStays.RoomStay(
|
||||||
|
time_span=time_span
|
||||||
|
)
|
||||||
)
|
)
|
||||||
room_stays = ab.OtaResRetrieveRs.ReservationsList.HotelReservation.RoomStays(
|
room_stays = ab.OtaResRetrieveRs.ReservationsList.HotelReservation.RoomStays(
|
||||||
room_stay=[room_stay]
|
room_stay=[room_stay]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
customer_data = CustomerData(
|
||||||
|
given_name="John",
|
||||||
|
surname="Doe",
|
||||||
|
name_prefix="Mr.",
|
||||||
|
phone_numbers=[
|
||||||
|
("+1234567890", PhoneTechType.MOBILE), # Phone number with type
|
||||||
|
("+0987654321", None) # Phone number without type
|
||||||
|
],
|
||||||
|
email_address="john.doe@example.com",
|
||||||
|
email_newsletter=True,
|
||||||
|
address_line="123 Main Street",
|
||||||
|
city_name="Anytown",
|
||||||
|
postal_code="12345",
|
||||||
|
country_code="US",
|
||||||
|
address_catalog=False,
|
||||||
|
gender="Male",
|
||||||
|
birth_date="1980-01-01",
|
||||||
|
language="en"
|
||||||
|
)
|
||||||
|
|
||||||
|
retrieve_customer = CustomerFactory.create_retrieve_customer(customer_data)
|
||||||
|
|
||||||
profile = ab.OtaResRetrieveRs.ReservationsList.HotelReservation.ResGuests.ResGuest.Profiles.ProfileInfo.Profile(
|
profile = ab.OtaResRetrieveRs.ReservationsList.HotelReservation.ResGuests.ResGuest.Profiles.ProfileInfo.Profile(
|
||||||
customer=ab.OtaResRetrieveRs.ReservationsList.HotelReservation.ResGuests.ResGuest.Profiles.ProfileInfo.Profile.Customer(
|
customer=retrieve_customer
|
||||||
person_name=ab.OtaResRetrieveRs.ReservationsList.HotelReservation.ResGuests.ResGuest.Profiles.ProfileInfo.Profile.Customer.PersonName(
|
|
||||||
given_name="John",
|
|
||||||
surname="Doe"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Use the actual nested Profiles class
|
# Use the actual nested Profiles class
|
||||||
|
|
||||||
|
|
||||||
profile_info = ab.OtaResRetrieveRs.ReservationsList.HotelReservation.ResGuests.ResGuest.Profiles.ProfileInfo(
|
profile_info = ab.OtaResRetrieveRs.ReservationsList.HotelReservation.ResGuests.ResGuest.Profiles.ProfileInfo(
|
||||||
profile=profile)
|
profile=profile
|
||||||
|
)
|
||||||
|
|
||||||
profiles = ab.OtaResRetrieveRs.ReservationsList.HotelReservation.ResGuests.ResGuest.Profiles(profile_info=profile_info)
|
profiles = ab.OtaResRetrieveRs.ReservationsList.HotelReservation.ResGuests.ResGuest.Profiles(
|
||||||
|
profile_info=profile_info
|
||||||
|
)
|
||||||
|
|
||||||
# ResGuest
|
# ResGuest
|
||||||
res_guest = ab.OtaResRetrieveRs.ReservationsList.HotelReservation.ResGuests.ResGuest(
|
res_guest = (
|
||||||
profiles=profiles
|
ab.OtaResRetrieveRs.ReservationsList.HotelReservation.ResGuests.ResGuest(
|
||||||
|
profiles=profiles
|
||||||
|
)
|
||||||
)
|
)
|
||||||
res_guests = ab.OtaResRetrieveRs.ReservationsList.HotelReservation.ResGuests(
|
res_guests = ab.OtaResRetrieveRs.ReservationsList.HotelReservation.ResGuests(
|
||||||
res_guest=res_guest
|
res_guest=res_guest
|
||||||
@@ -75,19 +87,23 @@ def main():
|
|||||||
|
|
||||||
# Use the actual nested HotelReservationIds class
|
# Use the actual nested HotelReservationIds class
|
||||||
hotel_res_ids = ab.OtaResRetrieveRs.ReservationsList.HotelReservation.ResGlobalInfo.HotelReservationIds(
|
hotel_res_ids = ab.OtaResRetrieveRs.ReservationsList.HotelReservation.ResGlobalInfo.HotelReservationIds(
|
||||||
hotel_reservation_id=[]
|
hotel_reservation_id = [
|
||||||
|
ab.OtaResRetrieveRs.ReservationsList.HotelReservation.ResGlobalInfo.HotelReservationIds.HotelReservationId(
|
||||||
|
res_id_type="13", res_id_source_context="99tales"
|
||||||
|
)
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
# Basic property info
|
# Basic property info
|
||||||
basic_property_info = ab.OtaResRetrieveRs.ReservationsList.HotelReservation.ResGlobalInfo.BasicPropertyInfo(
|
basic_property_info = ab.OtaResRetrieveRs.ReservationsList.HotelReservation.ResGlobalInfo.BasicPropertyInfo(
|
||||||
hotel_code="123",
|
hotel_code="123", hotel_name="Frangart Inn"
|
||||||
hotel_name="Frangart Inn"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# ResGlobalInfo
|
# ResGlobalInfo
|
||||||
res_global_info = ab.OtaResRetrieveRs.ReservationsList.HotelReservation.ResGlobalInfo(
|
res_global_info = (
|
||||||
hotel_reservation_ids=hotel_res_ids,
|
ab.OtaResRetrieveRs.ReservationsList.HotelReservation.ResGlobalInfo(
|
||||||
basic_property_info=basic_property_info
|
hotel_reservation_ids=hotel_res_ids, basic_property_info=basic_property_info
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Hotel Reservation
|
# Hotel Reservation
|
||||||
@@ -98,16 +114,16 @@ def main():
|
|||||||
unique_id=unique_id,
|
unique_id=unique_id,
|
||||||
room_stays=room_stays,
|
room_stays=room_stays,
|
||||||
res_guests=res_guests,
|
res_guests=res_guests,
|
||||||
res_global_info=res_global_info
|
res_global_info=res_global_info,
|
||||||
)
|
)
|
||||||
|
|
||||||
reservations_list = ab.OtaResRetrieveRs.ReservationsList(hotel_reservation=[hotel_reservation])
|
reservations_list = ab.OtaResRetrieveRs.ReservationsList(
|
||||||
|
hotel_reservation=[hotel_reservation]
|
||||||
|
)
|
||||||
|
|
||||||
# Root element
|
# Root element
|
||||||
ota_res_retrieve_rs = ab.OtaResRetrieveRs(
|
ota_res_retrieve_rs = ab.OtaResRetrieveRs(
|
||||||
version="7.000",
|
version="7.000", success=success, reservations_list=reservations_list
|
||||||
success=success,
|
|
||||||
reservations_list=reservations_list
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Serialize using Pydantic's model_dump and convert to XML
|
# Serialize using Pydantic's model_dump and convert to XML
|
||||||
@@ -115,46 +131,49 @@ def main():
|
|||||||
# First validate the model
|
# First validate the model
|
||||||
ota_res_retrieve_rs.model_validate(ota_res_retrieve_rs.model_dump())
|
ota_res_retrieve_rs.model_validate(ota_res_retrieve_rs.model_dump())
|
||||||
print("✅ Pydantic validation successful!")
|
print("✅ Pydantic validation successful!")
|
||||||
|
|
||||||
# For XML serialization with Pydantic models, we need to use xsdata-pydantic serializer
|
# For XML serialization with Pydantic models, we need to use xsdata-pydantic serializer
|
||||||
from xsdata.formats.dataclass.serializers.config import SerializerConfig
|
from xsdata.formats.dataclass.serializers.config import SerializerConfig
|
||||||
|
|
||||||
config = SerializerConfig(
|
config = SerializerConfig(
|
||||||
pretty_print=True,
|
pretty_print=True, xml_declaration=True, encoding="UTF-8"
|
||||||
xml_declaration=True,
|
|
||||||
encoding="UTF-8"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
serializer = XmlSerializer(config=config)
|
serializer = XmlSerializer(config=config)
|
||||||
|
|
||||||
# Use ns_map to control namespace prefixes - set default namespace
|
# Use ns_map to control namespace prefixes - set default namespace
|
||||||
ns_map = {None: "http://www.opentravel.org/OTA/2003/05"}
|
ns_map = {None: "http://www.opentravel.org/OTA/2003/05"}
|
||||||
xml_string = serializer.render(ota_res_retrieve_rs, ns_map=ns_map)
|
xml_string = serializer.render(ota_res_retrieve_rs, ns_map=ns_map)
|
||||||
|
|
||||||
with open('output.xml', 'w', encoding='utf-8') as outfile:
|
with open("output.xml", "w", encoding="utf-8") as outfile:
|
||||||
outfile.write(xml_string)
|
outfile.write(xml_string)
|
||||||
|
|
||||||
print("✅ XML serialization successful!")
|
print("✅ XML serialization successful!")
|
||||||
print(f"Generated XML written to output.xml")
|
print(f"Generated XML written to output.xml")
|
||||||
|
|
||||||
# Also print the pretty formatted XML to console
|
# Also print the pretty formatted XML to console
|
||||||
print("\n📄 Generated XML:")
|
print("\n📄 Generated XML:")
|
||||||
print(xml_string)
|
print(xml_string)
|
||||||
|
|
||||||
# Test parsing back
|
# Test parsing back
|
||||||
from xsdata_pydantic.bindings import XmlParser
|
from xsdata_pydantic.bindings import XmlParser
|
||||||
|
|
||||||
parser = XmlParser()
|
parser = XmlParser()
|
||||||
|
|
||||||
with open('output.xml', 'r', encoding='utf-8') as infile:
|
with open("output.xml", "r", encoding="utf-8") as infile:
|
||||||
xml_content = infile.read()
|
xml_content = infile.read()
|
||||||
|
|
||||||
parsed_result = parser.from_string(xml_content, ab.OtaResRetrieveRs)
|
parsed_result = parser.from_string(xml_content, ab.OtaResRetrieveRs)
|
||||||
print("✅ Round-trip validation successful!")
|
|
||||||
print(f"Parsed reservation status: {parsed_result.reservations_list.hotel_reservation[0].res_status}")
|
|
||||||
|
|
||||||
|
print("✅ Round-trip validation successful!")
|
||||||
|
print(
|
||||||
|
f"Parsed reservation status: {parsed_result.reservations_list.hotel_reservation[0].res_status}"
|
||||||
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"❌ Validation/Serialization failed: {e}")
|
print(f"❌ Validation/Serialization failed: {e}")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<OTA_ResRetrieveRS xmlns="http://www.opentravel.org/OTA/2003/05" Version="7.000">
|
<OTA_ResRetrieveRS xmlns="http://www.opentravel.org/OTA/2003/05" Version="7.000">
|
||||||
<ReservationsList>
|
<ReservationsList>
|
||||||
<HotelReservation CreateDateTime="2025-09-24T09:16:59.563720+00:00" ResStatus="Requested" RoomStayReservation="true">
|
<HotelReservation CreateDateTime="2025-09-24T12:47:54.728936+00:00" ResStatus="Requested" RoomStayReservation="true">
|
||||||
<UniqueID Type="14" ID="6b34fe24ac2ff811"/>
|
<UniqueID Type="14" ID="6b34fe24ac2ff811"/>
|
||||||
<RoomStays>
|
<RoomStays>
|
||||||
<RoomStay>
|
<RoomStay>
|
||||||
<TimeSpan/>
|
<TimeSpan>
|
||||||
|
<StartDateWindow EarliestDate="2024-10-01" LatestDate="2024-10-02"/>
|
||||||
|
</TimeSpan>
|
||||||
</RoomStay>
|
</RoomStay>
|
||||||
</RoomStays>
|
</RoomStays>
|
||||||
<ResGuests>
|
<ResGuests>
|
||||||
@@ -13,11 +15,21 @@
|
|||||||
<Profiles>
|
<Profiles>
|
||||||
<ProfileInfo>
|
<ProfileInfo>
|
||||||
<Profile>
|
<Profile>
|
||||||
<Customer>
|
<Customer Gender="Male" BirthDate="1980-01-01" Language="en">
|
||||||
<PersonName>
|
<PersonName>
|
||||||
|
<NamePrefix>Mr.</NamePrefix>
|
||||||
<GivenName>John</GivenName>
|
<GivenName>John</GivenName>
|
||||||
<Surname>Doe</Surname>
|
<Surname>Doe</Surname>
|
||||||
</PersonName>
|
</PersonName>
|
||||||
|
<Telephone PhoneTechType="5" PhoneNumber="+1234567890"/>
|
||||||
|
<Telephone PhoneNumber="+0987654321"/>
|
||||||
|
<Email Remark="newsletter:yes">john.doe@example.com</Email>
|
||||||
|
<Address Remark="catalog:no">
|
||||||
|
<AddressLine>123 Main Street</AddressLine>
|
||||||
|
<CityName>Anytown</CityName>
|
||||||
|
<PostalCode>12345</PostalCode>
|
||||||
|
<CountryName Code="US"/>
|
||||||
|
</Address>
|
||||||
</Customer>
|
</Customer>
|
||||||
</Profile>
|
</Profile>
|
||||||
</ProfileInfo>
|
</ProfileInfo>
|
||||||
@@ -25,7 +37,9 @@
|
|||||||
</ResGuest>
|
</ResGuest>
|
||||||
</ResGuests>
|
</ResGuests>
|
||||||
<ResGlobalInfo>
|
<ResGlobalInfo>
|
||||||
<HotelReservationIDs/>
|
<HotelReservationIDs>
|
||||||
|
<HotelReservationID ResID_Type="13" ResID_SourceContext="99tales"/>
|
||||||
|
</HotelReservationIDs>
|
||||||
<BasicPropertyInfo HotelCode="123" HotelName="Frangart Inn"/>
|
<BasicPropertyInfo HotelCode="123" HotelName="Frangart Inn"/>
|
||||||
</ResGlobalInfo>
|
</ResGlobalInfo>
|
||||||
</HotelReservation>
|
</HotelReservation>
|
||||||
|
|||||||
230
src/simplified_access.py
Normal file
230
src/simplified_access.py
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
from typing import Union, Optional, Any, TypeVar
|
||||||
|
from pydantic import BaseModel, ConfigDict, Field
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
# Import the generated classes
|
||||||
|
from generated.alpinebits import OtaHotelResNotifRq, OtaResRetrieveRs
|
||||||
|
|
||||||
|
# Define type aliases for the two Customer types
|
||||||
|
NotifCustomer = OtaHotelResNotifRq.HotelReservations.HotelReservation.ResGuests.ResGuest.Profiles.ProfileInfo.Profile.Customer
|
||||||
|
RetrieveCustomer = OtaResRetrieveRs.ReservationsList.HotelReservation.ResGuests.ResGuest.Profiles.ProfileInfo.Profile.Customer
|
||||||
|
|
||||||
|
|
||||||
|
# phonetechtype enum 1,3,5 voice, fax, mobile
|
||||||
|
class PhoneTechType(Enum):
|
||||||
|
VOICE = "1"
|
||||||
|
FAX = "3"
|
||||||
|
MOBILE = "5"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@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 CustomerFactory:
|
||||||
|
"""Factory class to create Customer instances for both OtaHotelResNotifRq and OtaResRetrieveRs."""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_notif_customer(data: CustomerData) -> NotifCustomer:
|
||||||
|
"""Create a Customer for OtaHotelResNotifRq."""
|
||||||
|
return CustomerFactory._create_customer(NotifCustomer, data)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_retrieve_customer(data: CustomerData) -> RetrieveCustomer:
|
||||||
|
"""Create a Customer for OtaResRetrieveRs."""
|
||||||
|
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."""
|
||||||
|
|
||||||
|
# Create PersonName
|
||||||
|
person_name = customer_class.PersonName(
|
||||||
|
given_name=data.given_name,
|
||||||
|
surname=data.surname,
|
||||||
|
name_prefix=data.name_prefix,
|
||||||
|
name_title=data.name_title
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create telephone list
|
||||||
|
telephones = []
|
||||||
|
for phone_number, phone_tech_type in data.phone_numbers:
|
||||||
|
telephone = customer_class.Telephone(
|
||||||
|
phone_number=phone_number,
|
||||||
|
phone_tech_type=phone_tech_type.value if phone_tech_type else None
|
||||||
|
)
|
||||||
|
telephones.append(telephone)
|
||||||
|
|
||||||
|
# Create email if provided
|
||||||
|
email = None
|
||||||
|
if data.email_address:
|
||||||
|
remark = None
|
||||||
|
if data.email_newsletter is not None:
|
||||||
|
remark = f"newsletter:{'yes' if data.email_newsletter else 'no'}"
|
||||||
|
|
||||||
|
email = customer_class.Email(
|
||||||
|
value=data.email_address,
|
||||||
|
remark=remark
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create address if any address fields are provided
|
||||||
|
address = None
|
||||||
|
if any([data.address_line, data.city_name, data.postal_code, data.country_code]):
|
||||||
|
country_name = None
|
||||||
|
if data.country_code:
|
||||||
|
country_name = customer_class.Address.CountryName(code=data.country_code)
|
||||||
|
|
||||||
|
address_remark = None
|
||||||
|
if data.address_catalog is not None:
|
||||||
|
address_remark = f"catalog:{'yes' if data.address_catalog else 'no'}"
|
||||||
|
|
||||||
|
address = customer_class.Address(
|
||||||
|
address_line=data.address_line,
|
||||||
|
city_name=data.city_name,
|
||||||
|
postal_code=data.postal_code,
|
||||||
|
country_name=country_name,
|
||||||
|
remark=address_remark
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create the customer
|
||||||
|
return customer_class(
|
||||||
|
person_name=person_name,
|
||||||
|
telephone=telephones,
|
||||||
|
email=email,
|
||||||
|
address=address,
|
||||||
|
gender=data.gender,
|
||||||
|
birth_date=data.birth_date,
|
||||||
|
language=data.language
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_notif_customer(customer: NotifCustomer) -> CustomerData:
|
||||||
|
"""Convert a NotifCustomer back to CustomerData."""
|
||||||
|
return CustomerFactory._customer_to_data(customer)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_retrieve_customer(customer: RetrieveCustomer) -> CustomerData:
|
||||||
|
"""Convert a RetrieveCustomer back to CustomerData."""
|
||||||
|
return CustomerFactory._customer_to_data(customer)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _customer_to_data(customer: Any) -> CustomerData:
|
||||||
|
"""Internal method to convert any customer type to CustomerData."""
|
||||||
|
|
||||||
|
# Extract phone numbers
|
||||||
|
phone_numbers = []
|
||||||
|
if customer.telephone:
|
||||||
|
for tel in customer.telephone:
|
||||||
|
phone_numbers.append((tel.phone_number, tel.phone_tech_type))
|
||||||
|
|
||||||
|
# Extract email info
|
||||||
|
email_address = None
|
||||||
|
email_newsletter = None
|
||||||
|
if customer.email:
|
||||||
|
email_address = customer.email.value
|
||||||
|
if customer.email.remark:
|
||||||
|
if "newsletter:yes" in customer.email.remark:
|
||||||
|
email_newsletter = True
|
||||||
|
elif "newsletter:no" in customer.email.remark:
|
||||||
|
email_newsletter = False
|
||||||
|
|
||||||
|
# Extract address info
|
||||||
|
address_line = None
|
||||||
|
city_name = None
|
||||||
|
postal_code = None
|
||||||
|
country_code = None
|
||||||
|
address_catalog = None
|
||||||
|
|
||||||
|
if customer.address:
|
||||||
|
address_line = customer.address.address_line
|
||||||
|
city_name = customer.address.city_name
|
||||||
|
postal_code = customer.address.postal_code
|
||||||
|
|
||||||
|
if customer.address.country_name:
|
||||||
|
country_code = customer.address.country_name.code
|
||||||
|
|
||||||
|
if customer.address.remark:
|
||||||
|
if "catalog:yes" in customer.address.remark:
|
||||||
|
address_catalog = True
|
||||||
|
elif "catalog:no" in customer.address.remark:
|
||||||
|
address_catalog = False
|
||||||
|
|
||||||
|
return CustomerData(
|
||||||
|
given_name=customer.person_name.given_name,
|
||||||
|
surname=customer.person_name.surname,
|
||||||
|
name_prefix=customer.person_name.name_prefix,
|
||||||
|
name_title=customer.person_name.name_title,
|
||||||
|
phone_numbers=phone_numbers,
|
||||||
|
email_address=email_address,
|
||||||
|
email_newsletter=email_newsletter,
|
||||||
|
address_line=address_line,
|
||||||
|
city_name=city_name,
|
||||||
|
postal_code=postal_code,
|
||||||
|
country_code=country_code,
|
||||||
|
address_catalog=address_catalog,
|
||||||
|
gender=customer.gender,
|
||||||
|
birth_date=customer.birth_date,
|
||||||
|
language=customer.language
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Usage examples
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Create customer data using simple data class
|
||||||
|
customer_data = CustomerData(
|
||||||
|
given_name="John",
|
||||||
|
surname="Doe",
|
||||||
|
name_prefix="Mr.",
|
||||||
|
phone_numbers=[
|
||||||
|
("+1234567890", PhoneTechType.MOBILE), # Phone number with type
|
||||||
|
("+0987654321", None) # Phone number without type
|
||||||
|
],
|
||||||
|
email_address="john.doe@example.com",
|
||||||
|
email_newsletter=True,
|
||||||
|
address_line="123 Main Street",
|
||||||
|
city_name="Anytown",
|
||||||
|
postal_code="12345",
|
||||||
|
country_code="US",
|
||||||
|
address_catalog=False,
|
||||||
|
gender="Male",
|
||||||
|
birth_date="1980-01-01",
|
||||||
|
language="en"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create customer for OtaHotelResNotifRq
|
||||||
|
notif_customer = CustomerFactory.create_notif_customer(customer_data)
|
||||||
|
print("Created NotifCustomer:", notif_customer.person_name.given_name, notif_customer.person_name.surname)
|
||||||
|
|
||||||
|
# Create customer for OtaResRetrieveRs
|
||||||
|
retrieve_customer = CustomerFactory.create_retrieve_customer(customer_data)
|
||||||
|
print("Created RetrieveCustomer:", retrieve_customer.person_name.given_name, retrieve_customer.person_name.surname)
|
||||||
|
|
||||||
|
# Convert back to data class
|
||||||
|
converted_data = CustomerFactory.from_notif_customer(notif_customer)
|
||||||
|
print("Converted back to data:", converted_data.given_name, converted_data.surname)
|
||||||
|
|
||||||
|
# Verify they contain the same information
|
||||||
|
print("Original and converted data match:", customer_data == converted_data)
|
||||||
2
uv.lock
generated
2
uv.lock
generated
@@ -9,6 +9,7 @@ source = { virtual = "." }
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "generateds" },
|
{ name = "generateds" },
|
||||||
{ name = "lxml" },
|
{ name = "lxml" },
|
||||||
|
{ name = "ruff" },
|
||||||
{ name = "xsdata", extra = ["cli", "lxml", "soap"] },
|
{ name = "xsdata", extra = ["cli", "lxml", "soap"] },
|
||||||
{ name = "xsdata-pydantic", extra = ["cli", "lxml", "soap"] },
|
{ name = "xsdata-pydantic", extra = ["cli", "lxml", "soap"] },
|
||||||
]
|
]
|
||||||
@@ -17,6 +18,7 @@ dependencies = [
|
|||||||
requires-dist = [
|
requires-dist = [
|
||||||
{ name = "generateds", specifier = ">=2.44.3" },
|
{ name = "generateds", specifier = ">=2.44.3" },
|
||||||
{ name = "lxml", specifier = ">=6.0.1" },
|
{ name = "lxml", specifier = ">=6.0.1" },
|
||||||
|
{ name = "ruff", specifier = ">=0.13.1" },
|
||||||
{ name = "xsdata", extras = ["cli", "lxml", "soap"], specifier = ">=25.7" },
|
{ name = "xsdata", extras = ["cli", "lxml", "soap"], specifier = ">=25.7" },
|
||||||
{ name = "xsdata-pydantic", extras = ["cli", "lxml", "soap"], specifier = ">=24.5" },
|
{ name = "xsdata-pydantic", extras = ["cli", "lxml", "soap"], specifier = ">=24.5" },
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user