Spaces:
Running
Running
| # SPDX-License-Identifier: Apache-2.0 | |
| # © 2026 Lutar, Stephen P. — SZL Holdings · Doctrine v11 · Perplexity Computer Agent. | |
| """SAP S/4HANA connector — OData (public API Business Hub sandbox read). | |
| Targets the publicly documented OData entity sets (SZL's own original client): | |
| GET {base}/sap/opu/odata/sap/API_BUSINESS_PARTNER/A_BusinessPartner?$top= | |
| GET .../API_SALES_ORDER_SRV/A_SalesOrder | |
| Public sandbox: https://sandbox.api.sap.com/s4hanacloud/... (free APIKey header). | |
| Docs: https://api.sap.com / https://community.sap.com (S/4HANA Cloud sandbox) | |
| Default state: READY → CONNECTED (read-only) vs the public sandbox once | |
| SZL_SAP_S4_API_KEY is set. Writes stay READY until a customer tenant + OAuth. | |
| """ | |
| from __future__ import annotations | |
| import os | |
| from typing import Any | |
| from ..base import Connector, Records, State, http_json | |
| from ..registry import register | |
| _SANDBOX = "https://sandbox.api.sap.com/s4hanacloud" | |
| class SapS4Connector(Connector): | |
| id = "sap_s4" | |
| label = "SAP S/4HANA (OData)" | |
| category = "erp" | |
| auth_kind = "api_key" | |
| free_tier = True # public read-only sandbox (free APIKey) | |
| env_vars = ["SZL_SAP_S4_API_KEY"] | |
| provider_base = "https://sandbox.api.sap.com/s4hanacloud/sap/opu/odata/sap/API_BUSINESS_PARTNER" | |
| docs_url = "https://api.sap.com/api/API_BUSINESS_PARTNER/overview" | |
| schema_preview = ["BusinessPartner", "BusinessPartnerName", "BusinessPartnerCategory", "OrganizationBPName1"] | |
| def _headers(self): | |
| key = os.environ.get("SZL_SAP_S4_API_KEY") | |
| h = {"Accept": "application/json"} | |
| if key: | |
| h["APIKey"] = key | |
| return h | |
| def _probe(self): | |
| key = os.environ.get("SZL_SAP_S4_API_KEY") | |
| if not key: | |
| return None, "no key" | |
| st, _ = http_json(self.provider_base + "/A_BusinessPartner?$top=1&$format=json", | |
| headers=self._headers()) | |
| return (st == 200), f"SAP sandbox HTTP {st}" | |
| def read(self, query: dict | None = None) -> Records: | |
| key = os.environ.get("SZL_SAP_S4_API_KEY") | |
| if not key: | |
| return self._ready_records( | |
| "provide credentials to activate — set SZL_SAP_S4_API_KEY " | |
| "(free read-only APIKey from the SAP Business Accelerator Hub: https://api.sap.com). " | |
| "Reads A_BusinessPartner / A_SalesOrder OData entity sets.") | |
| entity = (query or {}).get("entity", "A_BusinessPartner") | |
| top = max(1, min(int((query or {}).get("limit", 12)), 50)) | |
| st, raw = http_json(f"{self.provider_base}/{entity}?$top={top}&$format=json", | |
| headers=self._headers()) | |
| if st == 200 and isinstance(raw, dict): | |
| results = raw.get("d", {}).get("results") if isinstance(raw.get("d"), dict) else raw.get("value", []) | |
| rows = [] | |
| for r in (results or [])[:top]: | |
| rows.append({k: r.get(k) for k in self.schema_preview if k in r} or | |
| {k: v for k, v in list(r.items())[:6] if not isinstance(v, dict)}) | |
| return Records(connector_id=self.id, category=self.category, state=State.CONNECTED, | |
| records=rows, source=f"SAP S/4HANA sandbox OData {entity}", live=True, | |
| note=f"live (read-only sandbox) · {entity}", schema_preview=self.schema_preview) | |
| return Records(connector_id=self.id, category=self.category, state=State.ERROR, | |
| records=[], source=self.provider_base, live=False, | |
| note=f"credentials present but SAP sandbox HTTP {st}", schema_preview=self.schema_preview) | |
| __all__ = ["SapS4Connector"] | |