From 99d1ed1732928242b1c87579803eb19b326a738f Mon Sep 17 00:00:00 2001 From: Jonas Linter <{email_address}> Date: Tue, 14 Oct 2025 08:46:16 +0200 Subject: [PATCH] Email validation no longer breaks customer retrieval --- src/alpine_bits_python/alpine_bits_helpers.py | 31 +++++++++++++++++++ src/alpine_bits_python/alpinebits_server.py | 8 ++--- 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/alpine_bits_python/alpine_bits_helpers.py b/src/alpine_bits_python/alpine_bits_helpers.py index 0e0adf4..a8a71de 100644 --- a/src/alpine_bits_python/alpine_bits_helpers.py +++ b/src/alpine_bits_python/alpine_bits_helpers.py @@ -1,9 +1,12 @@ +import re import traceback from dataclasses import dataclass from datetime import UTC from enum import Enum from typing import Any +from email_validator import EmailNotValidError, validate_email + from alpine_bits_python.db import Customer, Reservation from alpine_bits_python.logging_config import get_logger from alpine_bits_python.schemas import ( @@ -618,6 +621,34 @@ def create_res_notif_push_message( return _create_xml_from_db(list, OtaMessageType.NOTIF, config) +def _validate_and_repair_email(email: str | None) -> str | None: + """Validate and repair email addresses with common typos. + + Attempts to fix trailing digits in TLDs (e.g., .de1 -> .de) + before validation. If the email is still invalid, returns None + instead of raising an exception. + + Args: + email: Email address to validate and repair + + Returns: + Normalized email address or None if invalid + """ + if email is None: + return None + + # Remove trailing digits from TLD (e.g., .de1 -> .de, .com2 -> .com) + # This matches a dot followed by letters, then trailing digits at the end + email = re.sub(r'(\.[a-zA-Z]+)\d+$', r'\1', email) + + try: + email_info = validate_email(email) + except EmailNotValidError as e: + _LOGGER.warning("invalid email address: %s", e) + return None + return email_info.normalized + + def _process_single_reservation( reservation: Reservation, customer: Customer, diff --git a/src/alpine_bits_python/alpinebits_server.py b/src/alpine_bits_python/alpinebits_server.py index ab0e41a..80c07e3 100644 --- a/src/alpine_bits_python/alpinebits_server.py +++ b/src/alpine_bits_python/alpinebits_server.py @@ -13,7 +13,6 @@ from dataclasses import dataclass from datetime import datetime from enum import Enum, IntEnum from typing import Any, Optional, override -from zoneinfo import ZoneInfo from xsdata.formats.dataclass.serializers.config import SerializerConfig from xsdata_pydantic.bindings import XmlParser, XmlSerializer @@ -24,8 +23,7 @@ from alpine_bits_python.alpine_bits_helpers import ( ) from alpine_bits_python.logging_config import get_logger -from .db import AckedRequest, Customer, Reservation -from .reservation_service import ReservationService +from .db import Customer, Reservation from .generated.alpinebits import ( OtaNotifReportRq, OtaNotifReportRs, @@ -34,6 +32,7 @@ from .generated.alpinebits import ( OtaReadRq, WarningStatus, ) +from .reservation_service import ReservationService # Configure logging _LOGGER = get_logger(__name__) @@ -147,7 +146,8 @@ class AlpineBitsResponse: """Validate that status code is one of the allowed values.""" if self.status_code not in [200, 400, 401, 500]: raise ValueError( - f"Invalid status code {self.status_code}. Must be 200, 400, 401, or 500" + "Invalid status code %s. Must be 200, 400, 401, or 500", + self.status_code, )