Files
apim-apps/apim_apps/client.py
T
2026-06-29 14:22:37 +02:00

123 lines
4.9 KiB
Python

from typing import Optional
import requests
from .models import AppSpec, EnvConfig, OrgSpec
class _AppendOnlySession(requests.Session):
"""HTTP session that refuses DELETE requests — this tool is append-only."""
def request(self, method: str, url, **kwargs):
if method.upper() == "DELETE":
raise RuntimeError(
f"DELETE is not allowed: this tool never removes existing resources ({url})"
)
return super().request(method, url, **kwargs)
class AxwayClient:
"""Axway API Manager REST client. Endpoint: /api/portal/v1.4"""
def __init__(self, env: EnvConfig):
self.env = env
self.base = env.url.rstrip("/") + "/api/portal/v1.4"
self.session = _AppendOnlySession()
self.session.auth = (env.username, env.password)
self.session.verify = env.verify_ssl
self.session.headers.update({"Accept": "application/json"})
# ── Helpers ───────────────────────────────────────────────────────────────
def _get_all(self, path: str, params: dict = None) -> list:
url = f"{self.base}{path}"
p = dict(params or {})
p.setdefault("count", 100)
p["startIndex"] = 0
items = []
while True:
r = self.session.get(url, params=p, timeout=30)
r.raise_for_status()
data = r.json()
if isinstance(data, list):
items.extend(data)
break
chunk = data.get("result", data.get("items", []))
if not isinstance(chunk, list):
return [data]
items.extend(chunk)
total = data.get("total", len(chunk))
p["startIndex"] += len(chunk)
if p["startIndex"] >= total or not chunk:
break
return items
def _post(self, path: str, payload: dict) -> dict:
r = self.session.post(
f"{self.base}{path}",
json=payload,
headers={"Content-Type": "application/json"},
timeout=30,
)
r.raise_for_status()
return r.json()
# ── Organisations ─────────────────────────────────────────────────────────
def list_orgs(self) -> dict[str, dict]:
return {o["name"]: o for o in self._get_all("/organizations")}
def create_org(self, spec: OrgSpec) -> dict:
return self._post("/organizations", {
"name": spec.name,
"description": spec.description,
"email": spec.email,
"phone": spec.phone,
"enabled": spec.enabled,
"development": spec.development,
})
# ── Applications ──────────────────────────────────────────────────────────
def list_apps(self) -> dict[tuple, dict]:
apps = self._get_all("/applications")
orgs_by_id = {o["id"]: o["name"] for o in self._get_all("/organizations")}
return {
(orgs_by_id.get(a.get("organizationId", ""), "?"), a["name"]): a
for a in apps
}
def create_app(self, spec: AppSpec, org_id: str) -> dict:
return self._post("/applications", {
"name": spec.name,
"organizationId": org_id,
"description": spec.description,
"email": spec.email,
"phone": spec.phone,
"enabled": spec.enabled,
"state": "approved",
})
# ── APIs front-end ────────────────────────────────────────────────────────
def list_frontend_apis(self) -> list[dict]:
return self._get_all("/proxies")
def find_frontend_api(self, api_name: str, api_version: str = "") -> Optional[dict]:
for api in self.list_frontend_apis():
if api.get("name", "") == api_name:
if not api_version or api.get("version", "") == api_version:
return api
return None
# ── Souscriptions ─────────────────────────────────────────────────────────
def list_app_api_access(self, app_id: str) -> list[dict]:
try:
return self._get_all(f"/applications/{app_id}/apis")
except requests.HTTPError:
return []
def grant_api_access(self, app_id: str, api_id: str) -> dict:
return self._post(f"/applications/{app_id}/apis/{api_id}", {})