"""POLITRAN API v1 — Python klient (requests) + postup zadavatele. pip install requests POLITRAN_API_TOKEN=... python politran_client.py """ from __future__ import annotations import os import time import uuid from typing import Any import requests class PolitranApiError(Exception): def __init__(self, status: int, code: str, message: str, details: dict | None = None, request_id: str | None = None): super().__init__(f"{status} {code}: {message} (request {request_id})") self.status, self.code, self.details, self.request_id = status, code, details or {}, request_id class PolitranClient: def __init__(self, base_url: str, token: str): self.base_url = base_url.rstrip("/") self.session = requests.Session() self.session.headers.update({"Authorization": f"Bearer {token}", "Accept": "application/json"}) def request(self, method: str, path: str, *, json: Any = None, params: dict | None = None, idempotency_key: str | None = None, if_match: str | None = None, files: dict | None = None) -> dict: headers = {} if idempotency_key: headers["Idempotency-Key"] = idempotency_key if if_match: headers["If-Match"] = f'"{if_match}"' for attempt in range(4): resp = self.session.request(method, f"{self.base_url}/{path.lstrip('/')}", json=json, params=params, headers=headers, files=files, timeout=30) if resp.status_code == 429 and attempt < 3: time.sleep(int(resp.headers.get("Retry-After", "5"))) continue break if resp.status_code >= 400: err = (resp.json() if resp.content else {}).get("error", {}) raise PolitranApiError(resp.status_code, err.get("code", f"HTTP_{resp.status_code}"), err.get("message", resp.reason), err.get("details"), err.get("request_id")) return resp.json() if resp.content else {} def get(self, path: str, **params: Any) -> dict: return self.request("GET", path, params=params or None) def post(self, path: str, body: dict | None = None, idempotency_key: str | None = None) -> dict: return self.request("POST", path, json=body, idempotency_key=idempotency_key) def patch(self, path: str, body: dict, if_match: str | None = None) -> dict: return self.request("PATCH", path, json=body, if_match=if_match) def upload(self, path: str, file_path: str) -> dict: with open(file_path, "rb") as fh: return self.request("POST", path, files={"file": (os.path.basename(file_path), fh)}) def iter_notices(self, **filters: Any): """Projde všechny stránky seznamu (např. updated_since=..., sort='updated_at').""" page = 1 while True: data = self.get("/notices", page=page, per_page=100, **filters) yield from data["data"] if page >= data["meta"]["last_page"]: return page += 1 def main() -> None: client = PolitranClient(os.environ.get("POLITRAN_API_BASE", "https://politran.cz/api/v1"), os.environ["POLITRAN_API_TOKEN"]) me = client.get("/me")["data"] assert me["side"] == "advertiser", "advertiser token required" print("Organisation:", me["organisation"]["name"], "scopes:", me["token"]["scopes"]) publisher_id = client.get("/publishers")["data"][0]["id"] fields = client.get("/notice-fields")["data"] required = [f["key"] for f in fields if f["required_before_send"] and f["condition"] is None] print("Required before send:", required) try: notice = client.post("/notices", { "publisher_org_id": publisher_id, "campaign_start_date": "2026-10-01", "campaign_end_date": "2026-10-31", "controlling_same_as_advertiser": True, "payer_source": "advertiser", "funding_finance": "private", "funding_source": "Dary členů", "complaint_mechanism": "https://example.com/stiznosti", "election_relation_type": "none", }, idempotency_key=f"example-{uuid.uuid4()}")["data"] print("Created", notice["short_code"], notice["allowed_actions"]) try: sent = client.post(f"/notices/{notice['id']}/send-to-publisher")["data"] print("Sent:", sent["status"], sent["public_url"]) except PolitranApiError as e: if e.code != "VALIDATION_FAILED": raise print("Missing fields:") for field, messages in e.details.items(): print(f" - {field}: {' '.join(messages)}") for n in client.iter_notices(updated_since="2026-01-01", sort="updated_at"): print(n["short_code"], n["status"], n["updated_at"]) except PolitranApiError as e: raise SystemExit(f"API error: {e}") if __name__ == "__main__": main()