Using pydantic instead of dataclasses
This commit is contained in:
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<OTA_HotelResNotifRQ xmlns="http://www.opentravel.org/OTA/2003/05" Version="7.000">
|
||||
<HotelReservations>
|
||||
<HotelReservation CreateDateTime="2025-10-07T14:05:37.563674+00:00" ResStatus="Requested" RoomStayReservation="true">
|
||||
<UniqueID Type="14" ID="c52702c9-55b9-44e1-b158-ec9544c7"/>
|
||||
<RoomStays>
|
||||
<RoomStay>
|
||||
<GuestCounts>
|
||||
<GuestCount Count="3"/>
|
||||
<GuestCount Count="1" Age="12"/>
|
||||
</GuestCounts>
|
||||
<TimeSpan Start="2026-01-02" End="2026-01-07"/>
|
||||
</RoomStay>
|
||||
</RoomStays>
|
||||
<ResGuests>
|
||||
<ResGuest>
|
||||
<Profiles>
|
||||
<ProfileInfo>
|
||||
<Profile>
|
||||
<Customer Language="it">
|
||||
<PersonName>
|
||||
<NamePrefix>Frau</NamePrefix>
|
||||
<GivenName>Genesia</GivenName>
|
||||
<Surname>Supino</Surname>
|
||||
</PersonName>
|
||||
<Telephone PhoneTechType="5" PhoneNumber="+393406259979"/>
|
||||
<Email Remark="newsletter:yes">supinogenesia@gmail.com</Email>
|
||||
</Customer>
|
||||
</Profile>
|
||||
</ProfileInfo>
|
||||
</Profiles>
|
||||
</ResGuest>
|
||||
</ResGuests>
|
||||
<ResGlobalInfo>
|
||||
<HotelReservationIDs>
|
||||
<HotelReservationID ResID_Type="13" ResID_Value="IwZXh0bgNhZW0BMABhZGlkAassWPh1b8QBHoRc2S24gMktdNKiPwEvGYMK3rB-mn" ResID_Source="Facebook_Mobile_Feed" ResID_SourceContext="99tales"/>
|
||||
</HotelReservationIDs>
|
||||
<Profiles>
|
||||
<ProfileInfo>
|
||||
<Profile ProfileType="4">
|
||||
<CompanyInfo>
|
||||
<CompanyName Code="who knows?" CodeContext="who knows?">99tales GmbH</CompanyName>
|
||||
</CompanyInfo>
|
||||
</Profile>
|
||||
</ProfileInfo>
|
||||
</Profiles>
|
||||
<BasicPropertyInfo HotelCode="12345" HotelName="Bemelmans Post"/>
|
||||
</ResGlobalInfo>
|
||||
</HotelReservation>
|
||||
</HotelReservations>
|
||||
</OTA_HotelResNotifRQ>
|
||||
257
logs/wix_test_data_20251007_160537.json
Normal file
257
logs/wix_test_data_20251007_160537.json
Normal file
@@ -0,0 +1,257 @@
|
||||
{
|
||||
"timestamp": "2025-10-07T16:05:37.531417",
|
||||
"client_ip": "127.0.0.1",
|
||||
"headers": {
|
||||
"host": "localhost:8080",
|
||||
"content-type": "application/json",
|
||||
"user-agent": "insomnia/2023.5.8",
|
||||
"accept": "*/*",
|
||||
"content-length": "7335"
|
||||
},
|
||||
"data": {
|
||||
"data": {
|
||||
"formName": "Contact us",
|
||||
"submissions": [
|
||||
{
|
||||
"label": "Anreisedatum",
|
||||
"value": "2026-01-02"
|
||||
},
|
||||
{
|
||||
"label": "Abreisedatum",
|
||||
"value": "2026-01-07"
|
||||
},
|
||||
{
|
||||
"label": "Anzahl Erwachsene",
|
||||
"value": "3"
|
||||
},
|
||||
{
|
||||
"label": "Anzahl Kinder",
|
||||
"value": "1"
|
||||
},
|
||||
{
|
||||
"label": "Alter Kind 1",
|
||||
"value": "12"
|
||||
},
|
||||
{
|
||||
"label": "Anrede",
|
||||
"value": "Frau"
|
||||
},
|
||||
{
|
||||
"label": "Vorname",
|
||||
"value": "Genesia "
|
||||
},
|
||||
{
|
||||
"label": "Nachname",
|
||||
"value": "Supino "
|
||||
},
|
||||
{
|
||||
"label": "Email",
|
||||
"value": "supinogenesia@gmail.com"
|
||||
},
|
||||
{
|
||||
"label": "Phone",
|
||||
"value": "+39 340 625 9979"
|
||||
},
|
||||
{
|
||||
"label": "Einwilligung Marketing",
|
||||
"value": "Selezionato"
|
||||
},
|
||||
{
|
||||
"label": "utm_Source",
|
||||
"value": "fb"
|
||||
},
|
||||
{
|
||||
"label": "utm_Medium",
|
||||
"value": "Facebook_Mobile_Feed"
|
||||
},
|
||||
{
|
||||
"label": "utm_Campaign",
|
||||
"value": "Conversions_Hotel_Bemelmans_ITA"
|
||||
},
|
||||
{
|
||||
"label": "utm_Term",
|
||||
"value": "Cold_Traffic_Conversions_Hotel_Bemelmans_ITA"
|
||||
},
|
||||
{
|
||||
"label": "utm_Content",
|
||||
"value": "Grafik_AuszeitDezember_9.12_23.12"
|
||||
},
|
||||
{
|
||||
"label": "utm_term_id",
|
||||
"value": "120238574626400196"
|
||||
},
|
||||
{
|
||||
"label": "utm_content_id",
|
||||
"value": "120238574626400196"
|
||||
},
|
||||
{
|
||||
"label": "gad_source",
|
||||
"value": ""
|
||||
},
|
||||
{
|
||||
"label": "gad_campaignid",
|
||||
"value": ""
|
||||
},
|
||||
{
|
||||
"label": "gbraid",
|
||||
"value": ""
|
||||
},
|
||||
{
|
||||
"label": "gclid",
|
||||
"value": ""
|
||||
},
|
||||
{
|
||||
"label": "fbclid",
|
||||
"value": "IwZXh0bgNhZW0BMABhZGlkAassWPh1b8QBHoRc2S24gMktdNKiPwEvGYMK3rB-mne_0IJQvQRIGH60wLvLfOm0XWP8wJ9s_aem_rbpAFMODwOh4UnF5UVxwWg"
|
||||
},
|
||||
{
|
||||
"label": "hotelid",
|
||||
"value": "12345"
|
||||
},
|
||||
{
|
||||
"label": "hotelname",
|
||||
"value": "Bemelmans Post"
|
||||
}
|
||||
],
|
||||
"field:date_picker_7e65": "2026-01-07",
|
||||
"field:number_7cf5": "3",
|
||||
"field:utm_source": "fb",
|
||||
"submissionTime": "2025-10-07T05:48:41.855Z",
|
||||
"field:alter_kind_3": "12",
|
||||
"field:gad_source": "",
|
||||
"field:form_field_5a7b": "Selezionato",
|
||||
"field:gad_campaignid": "",
|
||||
"field:utm_medium": "Facebook_Mobile_Feed",
|
||||
"field:utm_term_id": "120238574626400196",
|
||||
"context": {
|
||||
"metaSiteId": "1dea821c-8168-4736-96e4-4b92e8b364cf",
|
||||
"activationId": "2421c9cd-6565-49ba-b60f-165d3dacccba"
|
||||
},
|
||||
"field:email_5139": "supinogenesia@gmail.com",
|
||||
"field:phone_4c77": "+39 340 625 9979",
|
||||
"_context": {
|
||||
"activation": {
|
||||
"id": "2421c9cd-6565-49ba-b60f-165d3dacccba"
|
||||
},
|
||||
"configuration": {
|
||||
"id": "a976f18c-fa86-495d-be1e-676df188eeae"
|
||||
},
|
||||
"app": {
|
||||
"id": "225dd912-7dea-4738-8688-4b8c6955ffc2"
|
||||
},
|
||||
"action": {
|
||||
"id": "152db4d7-5263-40c4-be2b-1c81476318b7"
|
||||
},
|
||||
"trigger": {
|
||||
"key": "wix_form_app-form_submitted"
|
||||
}
|
||||
},
|
||||
"field:gclid": "",
|
||||
"formFieldMask": [
|
||||
"field:",
|
||||
"field:",
|
||||
"field:angebot_auswaehlen",
|
||||
"field:date_picker_a7c8",
|
||||
"field:date_picker_7e65",
|
||||
"field:",
|
||||
"field:number_7cf5",
|
||||
"field:anzahl_kinder",
|
||||
"field:alter_kind_3",
|
||||
"field:alter_kind_25",
|
||||
"field:alter_kind_4",
|
||||
"field:alter_kind_5",
|
||||
"field:alter_kind_6",
|
||||
"field:alter_kind_7",
|
||||
"field:alter_kind_8",
|
||||
"field:alter_kind_9",
|
||||
"field:alter_kind_10",
|
||||
"field:alter_kind_11",
|
||||
"field:",
|
||||
"field:anrede",
|
||||
"field:first_name_abae",
|
||||
"field:last_name_d97c",
|
||||
"field:email_5139",
|
||||
"field:phone_4c77",
|
||||
"field:long_answer_3524",
|
||||
"field:form_field_5a7b",
|
||||
"field:",
|
||||
"field:utm_source",
|
||||
"field:utm_medium",
|
||||
"field:utm_campaign",
|
||||
"field:utm_term",
|
||||
"field:utm_content",
|
||||
"field:utm_term_id",
|
||||
"field:utm_content_id",
|
||||
"field:gad_source",
|
||||
"field:gad_campaignid",
|
||||
"field:gbraid",
|
||||
"field:gclid",
|
||||
"field:fbclid",
|
||||
"field:hotelid",
|
||||
"field:hotelname",
|
||||
"field:",
|
||||
"metaSiteId"
|
||||
],
|
||||
"contact": {
|
||||
"name": {
|
||||
"first": "Genesia",
|
||||
"last": "Supino"
|
||||
},
|
||||
"email": "supinogenesia@gmail.com",
|
||||
"locale": "it-it",
|
||||
"phones": [
|
||||
{
|
||||
"tag": "UNTAGGED",
|
||||
"formattedPhone": "+39 340 625 9979",
|
||||
"id": "198f04fb-5b2c-4a7b-b7ea-adc150ec4212",
|
||||
"countryCode": "IT",
|
||||
"e164Phone": "+393406259979",
|
||||
"primary": true,
|
||||
"phone": "340 625 9979"
|
||||
}
|
||||
],
|
||||
"contactId": "4d695011-36c1-4480-b225-ae9c6eef9e83",
|
||||
"emails": [
|
||||
{
|
||||
"id": "e09d7bab-1f11-4b5d-b3c5-32d43c1dc584",
|
||||
"tag": "UNTAGGED",
|
||||
"email": "supinogenesia@gmail.com",
|
||||
"primary": true
|
||||
}
|
||||
],
|
||||
"updatedDate": "2025-10-07T05:48:44.764Z",
|
||||
"phone": "+393406259979",
|
||||
"createdDate": "2025-10-07T05:48:43.567Z"
|
||||
},
|
||||
"submissionId": "c52702c9-55b9-44e1-b158-ec9544c73cc7",
|
||||
"field:anzahl_kinder": "1",
|
||||
"field:first_name_abae": "Genesia ",
|
||||
"field:utm_content_id": "120238574626400196",
|
||||
"field:utm_campaign": "Conversions_Hotel_Bemelmans_ITA",
|
||||
"field:utm_term": "Cold_Traffic_Conversions_Hotel_Bemelmans_ITA",
|
||||
"contactId": "4d695011-36c1-4480-b225-ae9c6eef9e83",
|
||||
"field:date_picker_a7c8": "2026-01-02",
|
||||
"field:hotelname": "Bemelmans Post",
|
||||
"field:utm_content": "Grafik_AuszeitDezember_9.12_23.12",
|
||||
"field:last_name_d97c": "Supino ",
|
||||
"field:hotelid": "12345",
|
||||
"submissionsLink": "https://manage.wix.app/forms/submissions/1dea821c-8168-4736-96e4-4b92e8b364cf/e084006b-ae83-4e4d-b2f5-074118cdb3b1?d=https%3A%2F%2Fmanage.wix.com%2Fdashboard%2F1dea821c-8168-4736-96e4-4b92e8b364cf%2Fwix-forms%2Fform%2Fe084006b-ae83-4e4d-b2f5-074118cdb3b1%2Fsubmissions&s=true",
|
||||
"field:gbraid": "",
|
||||
"field:fbclid": "IwZXh0bgNhZW0BMABhZGlkAassWPh1b8QBHoRc2S24gMktdNKiPwEvGYMK3rB-mne_0IJQvQRIGH60wLvLfOm0XWP8wJ9s_aem_rbpAFMODwOh4UnF5UVxwWg",
|
||||
"submissionPdf": {
|
||||
"fileName": "c52702c9-55b9-44e1-b158-ec9544c73cc7.pdf",
|
||||
"downloadUrl": "https://manage.wix.com/_api/form-submission-service/v4/submissions/c52702c9-55b9-44e1-b158-ec9544c73cc7/download?accessToken=JWS.eyJraWQiOiJWLVNuLWhwZSIsImFsZyI6IkhTMjU2In0.eyJkYXRhIjoie1wibWV0YVNpdGVJZFwiOlwiMWRlYTgyMWMtODE2OC00NzM2LTk2ZTQtNGI5MmU4YjM2NGNmXCJ9IiwiaWF0IjoxNzU5ODE2MTI0LCJleHAiOjE3NTk4MTY3MjR9.quBfp9UL9Ddqb2CWERXoVkh9OdmHlIBvlLAyhoXElaY"
|
||||
},
|
||||
"field:anrede": "Frau",
|
||||
"formId": "e084006b-ae83-4e4d-b2f5-074118cdb3b1"
|
||||
}
|
||||
},
|
||||
"origin_header": null,
|
||||
"all_headers": {
|
||||
"host": "localhost:8080",
|
||||
"content-type": "application/json",
|
||||
"user-agent": "insomnia/2023.5.8",
|
||||
"accept": "*/*",
|
||||
"content-length": "7335"
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,7 @@ from enum import Enum
|
||||
from typing import Any
|
||||
|
||||
from alpine_bits_python.db import Customer, Reservation
|
||||
from alpine_bits_python.schemas import (
|
||||
HotelReservationIdData as HotelReservationIdDataValidated,
|
||||
)
|
||||
from alpine_bits_python.schemas import HotelReservationIdData
|
||||
|
||||
# Import the generated classes
|
||||
from .generated.alpinebits import (
|
||||
@@ -327,16 +325,6 @@ class CustomerFactory:
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class HotelReservationIdData:
|
||||
"""Hold hotel reservation ID information without nested type constraints."""
|
||||
|
||||
res_id_type: str # Required field - pattern: [0-9]+
|
||||
res_id_value: None | str = None # Max 64 characters
|
||||
res_id_source: None | str = None # Max 64 characters
|
||||
res_id_source_context: None | str = None # Max 64 characters
|
||||
|
||||
|
||||
class HotelReservationIdFactory:
|
||||
"""Factory class to create HotelReservationId instances for both OtaHotelResNotifRq and OtaResRetrieveRs."""
|
||||
|
||||
@@ -770,21 +758,13 @@ def _process_single_reservation(
|
||||
# - Trim whitespace
|
||||
# - Truncate to 64 characters if needed
|
||||
# - Convert empty strings to None
|
||||
hotel_res_id_data_validated = HotelReservationIdDataValidated(
|
||||
hotel_res_id_data = HotelReservationIdData(
|
||||
res_id_type="13",
|
||||
res_id_value=klick_id,
|
||||
res_id_source=res_id_source,
|
||||
res_id_source_context="99tales",
|
||||
)
|
||||
|
||||
# Convert back to dataclass for the factory
|
||||
hotel_res_id_data = HotelReservationIdData(
|
||||
res_id_type=hotel_res_id_data_validated.res_id_type,
|
||||
res_id_value=hotel_res_id_data_validated.res_id_value,
|
||||
res_id_source=hotel_res_id_data_validated.res_id_source,
|
||||
res_id_source_context=hotel_res_id_data_validated.res_id_source_context,
|
||||
)
|
||||
|
||||
hotel_res_id = alpine_bits_factory.create(hotel_res_id_data, message_type)
|
||||
hotel_res_ids = HotelReservation.ResGlobalInfo.HotelReservationIds(
|
||||
hotel_reservation_id=[hotel_res_id]
|
||||
|
||||
@@ -75,9 +75,9 @@ class HotelReservationIdData(BaseModel):
|
||||
"""Validated hotel reservation ID data."""
|
||||
|
||||
res_id_type: str = Field(..., pattern=r"^[0-9]+$") # Must be numeric string
|
||||
res_id_value: str | None = None
|
||||
res_id_source: str | None = None
|
||||
res_id_source_context: str | None = None
|
||||
res_id_value: str | None = Field(None, min_length=1, max_length=64)
|
||||
res_id_source: str | None = Field(None, min_length=1, max_length=64)
|
||||
res_id_source_context: str | None = Field(None, min_length=1, max_length=64)
|
||||
|
||||
@field_validator(
|
||||
"res_id_value", "res_id_source", "res_id_source_context", mode="before"
|
||||
|
||||
@@ -17,15 +17,11 @@ def extract_relevant_sections(xml_string):
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_ping_action_response_matches_expected():
|
||||
with open("test/test_data/Handshake-OTA_PingRQ.xml", encoding="utf-8") as f:
|
||||
with open("tests/test_data/Handshake-OTA_PingRQ.xml", encoding="utf-8") as f:
|
||||
server = AlpineBitsServer()
|
||||
with open(
|
||||
"test/test_data/Handshake-OTA_PingRQ.xml", encoding="utf-8"
|
||||
) as f:
|
||||
with open("tests/test_data/Handshake-OTA_PingRQ.xml", encoding="utf-8") as f:
|
||||
request_xml = f.read()
|
||||
with open(
|
||||
"test/test_data/Handshake-OTA_PingRS.xml", encoding="utf-8"
|
||||
) as f:
|
||||
with open("tests/test_data/Handshake-OTA_PingRS.xml", encoding="utf-8") as f:
|
||||
expected_xml = f.read()
|
||||
client_info = AlpineBitsClientInfo(username="irrelevant", password="irrelevant")
|
||||
response = await server.handle_request(
|
||||
@@ -56,7 +52,7 @@ async def test_ping_action_response_matches_expected():
|
||||
@pytest.mark.asyncio
|
||||
async def test_ping_action_response_success():
|
||||
server = AlpineBitsServer()
|
||||
with open("test/test_data/Handshake-OTA_PingRQ.xml", encoding="utf-8") as f:
|
||||
with open("tests/test_data/Handshake-OTA_PingRQ.xml", encoding="utf-8") as f:
|
||||
request_xml = f.read()
|
||||
client_info = AlpineBitsClientInfo(username="irrelevant", password="irrelevant")
|
||||
response = await server.handle_request(
|
||||
@@ -74,7 +70,7 @@ async def test_ping_action_response_success():
|
||||
@pytest.mark.asyncio
|
||||
async def test_ping_action_response_version_arbitrary():
|
||||
server = AlpineBitsServer()
|
||||
with open("test/test_data/Handshake-OTA_PingRQ.xml", encoding="utf-8") as f:
|
||||
with open("tests/test_data/Handshake-OTA_PingRQ.xml", encoding="utf-8") as f:
|
||||
request_xml = f.read()
|
||||
client_info = AlpineBitsClientInfo(username="irrelevant", password="irrelevant")
|
||||
response = await server.handle_request(
|
||||
@@ -91,7 +87,7 @@ async def test_ping_action_response_version_arbitrary():
|
||||
@pytest.mark.asyncio
|
||||
async def test_ping_action_response_invalid_action():
|
||||
server = AlpineBitsServer()
|
||||
with open("test/test_data/Handshake-OTA_PingRQ.xml", encoding="utf-8") as f:
|
||||
with open("tests/test_data/Handshake-OTA_PingRQ.xml", encoding="utf-8") as f:
|
||||
request_xml = f.read()
|
||||
client_info = AlpineBitsClientInfo(username="irrelevant", password="irrelevant")
|
||||
response = await server.handle_request(
|
||||
|
||||
Reference in New Issue
Block a user