Files
wurangyu 1e39b6da88 feat: sap-cli skill v2.1.0 — self-contained distributable package
- assets/: sap-cli source (v2.1.0, 26 commands, 16 object types)
- references/: tool constraints + error handling (self-contained)
- scripts/setup.py: one-click install/config/verify
- SKILL.md: full command reference + dual-platform install guide
- VERSION: 2.1.0

Built from D:/Codespace/sap-cli via scripts/pack_skill.py
2026-06-13 20:52:24 +08:00

89 lines
3.0 KiB
Python

"""ADTClient base — connection, authentication, and generic request helpers."""
from __future__ import annotations
import logging
import requests
from requests.auth import HTTPBasicAuth
from sapcli.exceptions import LoginError
logger = logging.getLogger("sapcli.client")
class ADTClientBase:
"""Connection / authentication foundation shared by all mixins."""
def __init__(
self,
host: str,
client: str,
user: str,
password: str,
verify_ssl: bool = False,
) -> None:
self.host: str = host.rstrip("/")
self.csrf_token: str = "fetch"
self.session: requests.Session = requests.Session()
self.session.auth = HTTPBasicAuth(user, password)
self.session.verify = verify_ssl
self._stateful: bool = False
self.sap_client: str = client
# ------------------------------------------------------------------
# Context-manager protocol
# ------------------------------------------------------------------
def __enter__(self): # type: ignore[override]
self.login()
return self
def __exit__(self, exc_type, exc_val, exc_tb) -> None:
self.session.close()
# ------------------------------------------------------------------
# Headers / auth helpers
# ------------------------------------------------------------------
def _headers(self, content_type: str = "application/xml") -> dict[str, str]:
return {
"Accept": "*/*",
"Cache-Control": "no-cache",
"x-csrf-token": self.csrf_token,
"X-sap-adt-sessiontype": "stateful" if self._stateful else "stateless",
"content-type": content_type,
"sap-client": self.sap_client,
}
def login(self) -> bool:
url = f"{self.host}/sap/bc/adt/compatibility/graph"
hdrs = self._headers()
logger.info("LOGIN: GET %s", url)
resp = self.session.get(url, headers=hdrs)
logger.info("LOGIN RESPONSE: HTTP %s", resp.status_code)
if resp.status_code == 200:
self.csrf_token = resp.headers.get("x-csrf-token", "")
if self.csrf_token:
logger.info(
"CSRF Token: %s...(%d chars)",
self.csrf_token[:8],
len(self.csrf_token),
)
return True
raise LoginError(f"登录失败: HTTP {resp.status_code}")
def object_exists(self, obj_uri: str) -> bool:
url = f"{self.host}{obj_uri}"
hdrs = self._headers()
hdrs["Accept"] = "*/*"
logger.info("CHECK EXISTS: GET %s", url)
resp = self.session.get(url, headers=hdrs)
logger.info("CHECK EXISTS RESPONSE: HTTP %s", resp.status_code)
if resp.status_code == 200:
return True
if resp.status_code == 404:
return False
logger.warning("Unexpected status %d for existence check", resp.status_code)
return resp.status_code < 400