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
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user