"""Calendar upstream helpers — HTTP to EMAIL_URL/CALENDAR_URL. Write paths (create / reschedule / cancel) follow the documented OpenAPI contract but were not exercised live when this package was written; treat a non-2xx as an upstream-contract finding before changing the request shape. """ from __future__ import annotations import os from urllib.parse import quote import httpx from dotenv import load_dotenv load_dotenv() CALENDAR_URL=os.getenv("CALENDAR_URL") or os.getenv("EMAIL_URL") CALENDAR_API_TOKEN=os.getenv("CALENDAR_API_TOKEN") or os.getenv("EMAIL_API_TOKEN") def _base_url(): if not CALENDAR_URL: raise RuntimeError("CALENDAR_URL or EMAIL_URL must be set") return CALENDAR_URL.rstrip("/") def _auth_headers(token=None): auth_token=token or CALENDAR_API_TOKEN if not auth_token: raise RuntimeError("CALENDAR_API_TOKEN or EMAIL_API_TOKEN must be set") return {"Authorization":f"Bearer {auth_token}"} async def get_event(event_id, token=None): """GET {base}/calendar/events/{id} -> event dict, or None on 404.""" encoded_id=quote(str(event_id),safe="") async with httpx.AsyncClient(timeout=30.0) as client: response=await client.get( f"{_base_url()}/calendar/events/{encoded_id}", headers={**_auth_headers(token),"accept":"application/json"}, ) if response.status_code==404: return None if response.status_code>=400: raise httpx.HTTPStatusError( response.text, request=response.request, response=response, ) if not response.content: return {} return response.json() async def create_event(payload, token=None): """POST {base}/calendar/events -> created event dict.""" async with httpx.AsyncClient(timeout=30.0) as client: response=await client.post( f"{_base_url()}/calendar/events", json=payload, headers=_auth_headers(token), ) if response.status_code>=400: raise httpx.HTTPStatusError( response.text, request=response.request, response=response, ) if not response.content: return {} return response.json() async def reschedule_event(event_id, payload, token=None): """PATCH {base}/calendar/events/{id}/reschedule -> updated event dict.""" encoded_id=quote(str(event_id),safe="") async with httpx.AsyncClient(timeout=30.0) as client: response=await client.patch( f"{_base_url()}/calendar/events/{encoded_id}/reschedule", json=payload, headers=_auth_headers(token), ) if response.status_code>=400: raise httpx.HTTPStatusError( response.text, request=response.request, response=response, ) if not response.content: return {} return response.json() async def cancel_event(event_id, comment=None, token=None): """POST {base}/calendar/events/{id}/cancel -> response body or empty dict.""" encoded_id=quote(str(event_id),safe="") body={"comment":comment} if comment is not None else {} async with httpx.AsyncClient(timeout=30.0) as client: response=await client.post( f"{_base_url()}/calendar/events/{encoded_id}/cancel", json=body, headers=_auth_headers(token), ) if response.status_code>=400: raise httpx.HTTPStatusError( response.text, request=response.request, response=response, ) if not response.content: return {} return response.json()