Files
alpinebits_python/.github/copilot-instructions.md
2025-10-07 17:16:41 +02:00

6.6 KiB

AlpineBits Python Server - AI Agent Instructions

Project Overview

This is an AlpineBits 2024-10 server that bridges booking requests from Wix landing pages to hotel partners. It's a dual-purpose system:

  1. FastAPI webhook receiver - accepts booking forms from wix.com landing pages via /api/webhook/wix-form
  2. AlpineBits OTA server - exposes hotel reservation data at /api/alpinebits/server-2024-10 using OpenTravel Alliance XML protocol

Data flows: Wix form → Database → AlpineBits XML → Hotel systems (pull or push)

Architecture Patterns

XML Generation with xsdata

  • Never manually construct XML strings. Use xsdata-generated Pydantic dataclasses from src/alpine_bits_python/generated/alpinebits.py
  • Parse XML: XmlParser().from_string(xml_string, OtaPingRq)
  • Serialize XML: XmlSerializer(config=SerializerConfig(...)).render(ota_object)
  • Factory pattern: Use classes in alpine_bits_helpers.py (e.g., CustomerFactory, GuestCountsFactory) to build complex OTA objects from DB models
  • Example: create_res_retrieve_response() builds OTA_ResRetrieveRS from (Reservation, Customer) tuples
  • Regenerating XML classes: Run xsdata on AlpineBits-HotelData-2024-10/files/schema-xsd/alpinebits.xsd to regenerate generated/alpinebits.py (only if XSD spec changes)

Configuration System

  • Config loaded from YAML with secret injection via !secret tags (see config_loader.py)
  • Default config location: config/config.yaml + config/secrets.yaml
  • Override via ALPINEBITS_CONFIG_DIR environment variable
  • Multi-hotel support: Each hotel in alpine_bits_auth array gets own credentials and optional push_endpoint

Database Layer

  • Async-only SQLAlchemy with AsyncSession (see db.py)
  • Three core tables: Customer, Reservation, AckedRequest (tracks which clients acknowledged which reservations)
  • DB URL configurable: SQLite for dev (sqlite+aiosqlite:///alpinebits.db), PostgreSQL for prod
  • Database auto-created on startup in api.py:create_app()

Event-Driven Push System

  • EventDispatcher in api.py enables hotel-specific listeners: event_dispatcher.register_hotel_listener("reservation:created", hotel_code, push_listener)
  • Push listener sends OTA_HotelResNotif XML to hotel's configured push_endpoint.url with Bearer token auth
  • Push requests logged to logs/push_requests/ with timestamp and unique ID
  • Note: Push endpoint support is currently dormant - configured but not actively used by partners

AlpineBits Action Pattern

  • Each OTA action is a class inheriting AlpineBitsActionHandler (see alpinebits_server.py)
  • Actions: PingAction, ReadAction, NotifReportAction, PushAction
  • Request flow: Parse XML → Call handle() → Return AlpineBitsActionResult with XML response + HTTP status
  • AlpineBitsActionName enum maps capability names to request names (e.g., OTA_READ"OTA_Read:GuestRequests")
  • Server supports multiple AlpineBits versions (2024-10, 2022-10) when actions are identical across versions

Acknowledgment System

  • AckedRequest table tracks which clients acknowledged which reservations via OTA_NotifReport:GuestRequests
  • Read requests filter out acknowledged reservations for clients with client_id
  • Prevents duplicate reservation sends: once acknowledged, data won't appear in subsequent reads for that client

Critical Workflows

Running Locally

uv sync                                    # Install dependencies (uses uv, not pip!)
uv run python -m alpine_bits_python.run_api  # Start server on port 8080, clears DB on startup

Testing

uv run pytest                              # Run all tests
uv run pytest tests/test_alpine_bits_server_read.py  # Specific test file
  • Tests use in-memory SQLite via test_db_engine fixture (see tests/test_alpine_bits_server_read.py)
  • Test data fixtures in tests/test_data/ directory

Building for Deployment

uv sync
docker build . -t gitea.linter-home.com/jonas/asa_api:master
  • Multi-stage Dockerfile: builder stage installs deps with uv, production stage copies .venv
  • Runs as non-root user (UID 1000) for security
  • Requires ALPINEBITS_CONFIG_DIR=/config volume mount for config files
  • Deployment: Docker build pipeline exists and works; can also build manually on target system

Project-Specific Conventions

Naming Patterns

  • OTA message types use full AlpineBits names: OtaReadRq, OtaResRetrieveRs, OtaHotelResNotifRq
  • Factory classes suffix with Factory: CustomerFactory, HotelReservationIdFactory
  • DB models in db.py, validation schemas in schemas.py, OTA helpers in alpine_bits_helpers.py

Data Validation Flow

  1. API Layer → Pydantic schemas (schemas.py) validate incoming data
  2. DB Layer → SQLAlchemy models (db.py) persist validated data
  3. XML Layer → xsdata classes (generated/alpinebits.py) + factories (alpine_bits_helpers.py) generate OTA XML

This separation prevents mixing concerns (validation ≠ persistence ≠ XML generation).

Unique ID Generation

  • Reservation IDs: 35-char max, format {hotel_code}_{uuid4}_{timestamp}
  • Generated via generate_unique_id() in auth.py

Rate Limiting

  • Uses slowapi with Redis backend
  • Three tiers: DEFAULT_RATE_LIMIT (100/hour), WEBHOOK_RATE_LIMIT (300/hour), BURST_RATE_LIMIT (10/minute)
  • Applied via decorators: @limiter.limit(DEFAULT_RATE_LIMIT)

Common Pitfalls

  1. Don't use synchronous SQLAlchemy calls - Always await session.execute(), never session.query()
  2. Don't hardcode XML namespaces - Let xsdata handle them via generated classes
  3. Don't skip config validation - Voluptuous schemas in config_loader.py catch config errors early
  4. Auth is per-hotel - HTTP Basic Auth credentials from alpine_bits_auth config array
  5. AlpineBits version matters - Server implements 2024-10 spec (see AlpineBits-HotelData-2024-10/ directory)

Key Files Reference

  • api.py - FastAPI app, all endpoints, event dispatcher
  • alpinebits_server.py - AlpineBits action handlers (Ping, Read, NotifReport)
  • alpine_bits_helpers.py - Factory classes for building OTA XML from DB models
  • config_loader.py - YAML config loading with secret injection
  • db.py - SQLAlchemy async models (Customer, Reservation, AckedRequest)
  • schemas.py - Pydantic validation schemas
  • generated/alpinebits.py - xsdata-generated OTA XML classes (DO NOT EDIT - regenerate from XSD)

Testing Strategy

  • Fixtures create isolated in-memory databases per test
  • Use test_config() fixture for test configuration
  • XML serialization/parsing tested via xsdata round-trips
  • Push endpoint mocking via httpx in tests