"""One atomic Supply ARI write with readback, using the Python standard library.""" import json import os import re import uuid from urllib.error import HTTPError from urllib.parse import urlencode from urllib.request import Request, urlopen required = ( "OUTRESERVE_SUPPLY_BASE_URL", "OUTRESERVE_SUPPLY_API_KEY", "OUTRESERVE_PROPERTY_ID", "OUTRESERVE_UNIT_ID", "OUTRESERVE_DATE_FROM", "OUTRESERVE_DATE_TO", "OUTRESERVE_INCORPORATED_RESERVATION_SEQUENCE", ) missing = [name for name in required if not os.environ.get(name)] if missing: raise SystemExit(f"Set these environment variables first: {', '.join(missing)}") base = os.environ["OUTRESERVE_SUPPLY_BASE_URL"].rstrip("/") url = f'{base}/properties/{os.environ["OUTRESERVE_PROPERTY_ID"]}/ari' query = urlencode( { "date_from": os.environ["OUTRESERVE_DATE_FROM"], "date_to": os.environ["OUTRESERVE_DATE_TO"], } ) auth = f'Bearer {os.environ["OUTRESERVE_SUPPLY_API_KEY"]}' sequence_text = os.environ["OUTRESERVE_INCORPORATED_RESERVATION_SEQUENCE"] if not re.fullmatch(r"0|[1-9][0-9]*", sequence_text) or int(sequence_text) > 2**53 - 1: raise SystemExit("Set OUTRESERVE_INCORPORATED_RESERVATION_SEQUENCE to a nonnegative safe JSON integer") def request(path, method="GET", body=None, headers=None): """Perform one request and report structured and non-JSON errors.""" payload = json.dumps(body).encode() if body is not None else None request_headers = {"Authorization": auth, **(headers or {})} if payload is not None: request_headers["Content-Type"] = "application/json" operation = Request(path, data=payload, headers=request_headers, method=method) try: with urlopen(operation, timeout=30) as response: return json.load(response) except HTTPError as error: raw = error.read().decode("utf-8", errors="replace") try: detail = json.loads(raw) except json.JSONDecodeError: detail = raw raise SystemExit(f"Supply API {error.code}: {detail}") from error current = request(f"{url}?{query}") revision = current.get("data", {}).get("calendar_revision") if revision is None: raise SystemExit("GET ARI did not return data.calendar_revision; inspect before writing") update = { "updates": [ { "scope": "unit", "unit_id": os.environ["OUTRESERVE_UNIT_ID"], "date_from": os.environ["OUTRESERVE_DATE_FROM"], "date_to": os.environ["OUTRESERVE_DATE_TO"], "available_count": 1, "incorporated_reservation_sequence": int(sequence_text), "rate_minor": 18500, "stop_sell": False, } ] } result = request( url, method="POST", body=update, headers={"If-Match": f'"{revision}"', "Idempotency-Key": str(uuid.uuid4())}, ) print(json.dumps({"applied": result, "readback": request(f"{url}?{query}")}, indent=2))