feat(api): standardize REST contracts and auth
This commit is contained in:
+23
-59
@@ -17,8 +17,6 @@ SCHEMA_VERSION = "tjwater-cli/v1"
|
||||
CLI_NAME = "tjwater-cli"
|
||||
DEFAULT_TIMEOUT = 180
|
||||
DEFAULT_SERVER = "http://192.168.1.114:8000"
|
||||
|
||||
|
||||
class CLIError(Exception):
|
||||
def __init__(
|
||||
self,
|
||||
@@ -46,8 +44,6 @@ class AuthContext:
|
||||
server: str | None = None
|
||||
access_token: str | None = None
|
||||
project_id: str | None = None
|
||||
username: str | None = None
|
||||
network: str | None = None
|
||||
headers: dict[str, str] = field(default_factory=dict)
|
||||
|
||||
|
||||
@@ -97,8 +93,6 @@ def load_auth_context(auth_stdin: bool = False) -> AuthContext:
|
||||
"server": os.getenv("TJWATER_SERVER"),
|
||||
"access_token": os.getenv("TJWATER_ACCESS_TOKEN"),
|
||||
"project_id": os.getenv("TJWATER_PROJECT_ID"),
|
||||
"username": os.getenv("TJWATER_USERNAME"),
|
||||
"network": os.getenv("TJWATER_NETWORK"),
|
||||
"headers": json.loads(extra_headers) if extra_headers else {},
|
||||
}
|
||||
|
||||
@@ -115,8 +109,6 @@ def load_auth_context(auth_stdin: bool = False) -> AuthContext:
|
||||
server=_pick(raw, "server", "base_url"),
|
||||
access_token=_pick(raw, "access_token", "token", "accessToken"),
|
||||
project_id=_pick(raw, "project_id", "projectId", "x_project_id"),
|
||||
username=_pick(raw, "username", "preferred_username"),
|
||||
network=_pick(raw, "network", "project_code", "projectCode", "project"),
|
||||
headers={str(key): str(value) for key, value in headers.items()},
|
||||
)
|
||||
|
||||
@@ -175,30 +167,6 @@ def require_project_id(ctx: RuntimeContext) -> str:
|
||||
)
|
||||
|
||||
|
||||
def require_network(ctx: RuntimeContext) -> str:
|
||||
if ctx.auth.network:
|
||||
return ctx.auth.network
|
||||
raise CLIError(
|
||||
"认证失败",
|
||||
code="NETWORK_CONTEXT_REQUIRED",
|
||||
message="missing network in auth context for legacy network-based endpoints",
|
||||
exit_code=3,
|
||||
next_commands=["add network to auth context"],
|
||||
)
|
||||
|
||||
|
||||
def require_username(ctx: RuntimeContext) -> str:
|
||||
if ctx.auth.username:
|
||||
return ctx.auth.username
|
||||
raise CLIError(
|
||||
"认证失败",
|
||||
code="USERNAME_CONTEXT_REQUIRED",
|
||||
message="missing username in auth context",
|
||||
exit_code=3,
|
||||
next_commands=["add username to auth context"],
|
||||
)
|
||||
|
||||
|
||||
def resolve_scheme(ctx: RuntimeContext, explicit_scheme: str | None, *, required: bool = False) -> str | None:
|
||||
scheme = explicit_scheme or ctx.scheme
|
||||
if required and not scheme:
|
||||
@@ -254,14 +222,14 @@ def parse_burst_file(path: Path) -> tuple[list[str], list[float]]:
|
||||
raw = read_json_input(path, label="burst")
|
||||
if isinstance(raw, dict) and "bursts" in raw:
|
||||
raw = raw["bursts"]
|
||||
if isinstance(raw, dict) and "burst_ID" in raw and "burst_size" in raw:
|
||||
ids = [str(item) for item in raw["burst_ID"]]
|
||||
if isinstance(raw, dict) and "burst_id" in raw and "burst_size" in raw:
|
||||
ids = [str(item) for item in raw["burst_id"]]
|
||||
sizes = [float(item) for item in raw["burst_size"]]
|
||||
if len(ids) != len(sizes):
|
||||
raise CLIError(
|
||||
"CLI 参数错误",
|
||||
code="BURST_FILE_INVALID",
|
||||
message="burst file burst_ID and burst_size must have the same length",
|
||||
message="burst file burst_id and burst_size must have the same length",
|
||||
exit_code=2,
|
||||
)
|
||||
return ids, sizes
|
||||
@@ -282,7 +250,7 @@ def parse_burst_file(path: Path) -> tuple[list[str], list[float]]:
|
||||
raise CLIError(
|
||||
"CLI 参数错误",
|
||||
code="BURST_FILE_INVALID",
|
||||
message="burst file must be a JSON array or object with burst_ID/burst_size",
|
||||
message="burst file must be a JSON array or object with burst_id/burst_size",
|
||||
exit_code=2,
|
||||
)
|
||||
|
||||
@@ -404,12 +372,13 @@ def _parse_response_body(response: requests.Response) -> Any:
|
||||
return {}
|
||||
|
||||
|
||||
def _with_network_param(params: dict[str, Any] | None, network: str) -> dict[str, Any]:
|
||||
params = dict(params or {})
|
||||
if "network" in params or "network_name" in params or "name" in params:
|
||||
return params
|
||||
params["network"] = network
|
||||
return params
|
||||
def _prepare_public_request(
|
||||
method: str,
|
||||
path: str,
|
||||
params: dict[str, Any] | None,
|
||||
json_body: Any,
|
||||
) -> tuple[str, str, dict[str, Any] | None, Any]:
|
||||
return method.upper(), path.rstrip("/") or "/", params or None, json_body
|
||||
|
||||
|
||||
def request_json(
|
||||
@@ -421,18 +390,14 @@ def request_json(
|
||||
json_body: Any = None,
|
||||
require_auth: bool = True,
|
||||
require_project: bool = False,
|
||||
require_network_ctx: bool = False,
|
||||
require_username_ctx: bool = False,
|
||||
) -> tuple[Any, int]:
|
||||
require_server(ctx)
|
||||
network = None
|
||||
if require_network_ctx:
|
||||
network = require_network(ctx)
|
||||
if require_username_ctx:
|
||||
require_username(ctx)
|
||||
if network and (params is not None or json_body is None):
|
||||
params = _with_network_param(params, network)
|
||||
|
||||
method, path, params, json_body = _prepare_public_request(
|
||||
method,
|
||||
path,
|
||||
params,
|
||||
json_body,
|
||||
)
|
||||
url = f"{require_server(ctx)}/api/v1{path}"
|
||||
headers = build_headers(ctx, require_auth=require_auth, require_project=require_project)
|
||||
started = time.monotonic()
|
||||
@@ -482,15 +447,14 @@ def request_bytes(
|
||||
params: dict[str, Any] | None = None,
|
||||
require_auth: bool = True,
|
||||
require_project: bool = False,
|
||||
require_network_ctx: bool = False,
|
||||
) -> tuple[bytes, int]:
|
||||
require_server(ctx)
|
||||
network = None
|
||||
if require_network_ctx:
|
||||
network = require_network(ctx)
|
||||
if network:
|
||||
params = _with_network_param(params, network)
|
||||
|
||||
method, path, params, _ = _prepare_public_request(
|
||||
method,
|
||||
path,
|
||||
params,
|
||||
None,
|
||||
)
|
||||
url = f"{require_server(ctx)}/api/v1{path}"
|
||||
headers = build_headers(ctx, require_auth=require_auth, require_project=require_project)
|
||||
started = time.monotonic()
|
||||
|
||||
Reference in New Issue
Block a user