macore-client 0.1.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- macore_client/__init__.py +9 -0
- macore_client/cli.py +77 -0
- macore_client/client.py +146 -0
- macore_client/live_check.py +109 -0
- macore_client/pipeline.py +171 -0
- macore_client-0.1.4.dist-info/METADATA +6 -0
- macore_client-0.1.4.dist-info/RECORD +9 -0
- macore_client-0.1.4.dist-info/WHEEL +4 -0
- macore_client-0.1.4.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# client/macore_client/__init__.py
|
|
2
|
+
# Author: Vasiliy Zdanovskiy <vasilyvz@gmail.com>
|
|
3
|
+
# Project: model_access_core (71b1e006-b777-4edb-854e-f0a47120f793)
|
|
4
|
+
"""Client package for the Model Access Core (MAC) server."""
|
|
5
|
+
|
|
6
|
+
from macore_client.client import MacClient
|
|
7
|
+
|
|
8
|
+
__version__ = "0.1.4"
|
|
9
|
+
__all__ = ["MacClient", "__version__"]
|
macore_client/cli.py
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# client/macore_client/cli.py
|
|
2
|
+
# Author: Vasiliy Zdanovskiy <vasilyvz@gmail.com>
|
|
3
|
+
# Project: model_access_core (71b1e006-b777-4edb-854e-f0a47120f793)
|
|
4
|
+
"""``macore-client`` console script: one subcommand per MAC command."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import argparse
|
|
9
|
+
import asyncio
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
import sys
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
from macore_client.client import COMMANDS, MacClient
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def default_base_url() -> str:
|
|
19
|
+
"""Resolve the server base URL from the environment."""
|
|
20
|
+
return os.environ.get("MAC_VERIFY_BASE_URL", "http://127.0.0.1:8010")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
24
|
+
"""Build the CLI parser with one subcommand per client command method."""
|
|
25
|
+
parser = argparse.ArgumentParser(
|
|
26
|
+
prog="macore-client",
|
|
27
|
+
description="Call one Model Access Core server command over JSON-RPC.",
|
|
28
|
+
)
|
|
29
|
+
parser.add_argument(
|
|
30
|
+
"--base-url",
|
|
31
|
+
default=None,
|
|
32
|
+
help="Adapter base URL (default: env MAC_VERIFY_BASE_URL or http://127.0.0.1:8010)",
|
|
33
|
+
)
|
|
34
|
+
parser.add_argument("--timeout", type=float, default=None, help="HTTP timeout seconds")
|
|
35
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
36
|
+
for command in COMMANDS:
|
|
37
|
+
sub = subparsers.add_parser(command.replace("_", "-"), help=f"Run {command}")
|
|
38
|
+
sub.add_argument(
|
|
39
|
+
"--params",
|
|
40
|
+
default="{}",
|
|
41
|
+
help="Command parameters as a JSON object string",
|
|
42
|
+
)
|
|
43
|
+
return parser
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
async def _run(base_url: str, timeout: float | None, command: str, params: dict[str, Any]) -> Any:
|
|
47
|
+
client = MacClient(base_url, timeout=timeout)
|
|
48
|
+
try:
|
|
49
|
+
return await client.call(command, params)
|
|
50
|
+
finally:
|
|
51
|
+
await client.close()
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def main(argv: list[str] | None = None) -> int:
|
|
55
|
+
"""Entry point of the ``macore-client`` command."""
|
|
56
|
+
args = build_parser().parse_args(argv)
|
|
57
|
+
command = str(args.command).replace("-", "_")
|
|
58
|
+
try:
|
|
59
|
+
params = json.loads(args.params)
|
|
60
|
+
except json.JSONDecodeError as exc:
|
|
61
|
+
print(f"macore-client: --params is not valid JSON: {exc}", file=sys.stderr)
|
|
62
|
+
return 2
|
|
63
|
+
if not isinstance(params, dict):
|
|
64
|
+
print("macore-client: --params must be a JSON object", file=sys.stderr)
|
|
65
|
+
return 2
|
|
66
|
+
base_url = args.base_url or default_base_url()
|
|
67
|
+
try:
|
|
68
|
+
result = asyncio.run(_run(base_url, args.timeout, command, params))
|
|
69
|
+
except Exception as exc: # surface transport/command failure as text, not traceback
|
|
70
|
+
print(f"macore-client: {command} failed: {exc}", file=sys.stderr)
|
|
71
|
+
return 1
|
|
72
|
+
print(json.dumps(result, ensure_ascii=False, indent=2, default=str))
|
|
73
|
+
return 0
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
if __name__ == "__main__":
|
|
77
|
+
raise SystemExit(main())
|
macore_client/client.py
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# client/macore_client/client.py
|
|
2
|
+
# Author: Vasiliy Zdanovskiy <vasilyvz@gmail.com>
|
|
3
|
+
# Project: model_access_core (71b1e006-b777-4edb-854e-f0a47120f793)
|
|
4
|
+
"""The MAC client: one method per public MAC command.
|
|
5
|
+
|
|
6
|
+
Wraps the ``mcp_proxy_adapter`` framework JSON-RPC client. No method builds a
|
|
7
|
+
URL, a JSON-RPC envelope or a raw transport payload; transport, protocol
|
|
8
|
+
selection, serialization and queued-job handling all belong to the framework
|
|
9
|
+
client. Every method mirrors a command the MAC server registers, with the same
|
|
10
|
+
name, so client and server read against each other without a translation table.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import contextlib
|
|
16
|
+
import sys
|
|
17
|
+
from typing import Any
|
|
18
|
+
from urllib.parse import urlparse
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _import_jsonrpc_client() -> Any:
|
|
22
|
+
"""Import the framework client with its import-time banner kept off stdout."""
|
|
23
|
+
with contextlib.redirect_stdout(sys.stderr):
|
|
24
|
+
from mcp_proxy_adapter.client.jsonrpc_client.client import JsonRpcClient
|
|
25
|
+
return JsonRpcClient
|
|
26
|
+
|
|
27
|
+
# Public MAC commands, one client method each. model_selftest is queued on the
|
|
28
|
+
# server; the rest answer inline.
|
|
29
|
+
COMMANDS: tuple[str, ...] = (
|
|
30
|
+
"health",
|
|
31
|
+
"model_health",
|
|
32
|
+
"model_providers",
|
|
33
|
+
"model_provider_status",
|
|
34
|
+
"model_vram_status",
|
|
35
|
+
"model_queue_status",
|
|
36
|
+
"model_session_create",
|
|
37
|
+
"model_session_get",
|
|
38
|
+
"model_session_delete",
|
|
39
|
+
"model_chat",
|
|
40
|
+
"model_count_tokens",
|
|
41
|
+
"model_estimate_request_capacity",
|
|
42
|
+
"model_request_status",
|
|
43
|
+
"model_request_cancel",
|
|
44
|
+
"model_cache_create",
|
|
45
|
+
"model_cache_set_active",
|
|
46
|
+
"model_cache_list",
|
|
47
|
+
"model_cache_delete",
|
|
48
|
+
"model_selftest",
|
|
49
|
+
"hardware_calibration",
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
_QUEUED_COMMANDS: frozenset[str] = frozenset({"model_selftest"})
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def command_methods() -> tuple[str, ...]:
|
|
56
|
+
"""Return the client's public command-method names (for surface checks)."""
|
|
57
|
+
return COMMANDS
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _unwrap(result: Any) -> Any:
|
|
61
|
+
"""Unwrap the unified-execution envelope down to the command result."""
|
|
62
|
+
seen = 0
|
|
63
|
+
while isinstance(result, dict) and seen < 8:
|
|
64
|
+
seen += 1
|
|
65
|
+
if result.get("mode") in {"immediate", "queued"} and "result" in result:
|
|
66
|
+
result = result["result"]
|
|
67
|
+
continue
|
|
68
|
+
if "result" in result and set(result.keys()) <= {"result", "job_id", "status", "queued", "command", "raw_status", "mode"}:
|
|
69
|
+
result = result["result"]
|
|
70
|
+
continue
|
|
71
|
+
break
|
|
72
|
+
return result
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class MacClient:
|
|
76
|
+
"""Full-surface client for one Model Access Core server.
|
|
77
|
+
|
|
78
|
+
Attributes:
|
|
79
|
+
transport: The framework JSON-RPC client carrying every request.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
def __init__(
|
|
83
|
+
self,
|
|
84
|
+
base_url: str = "http://127.0.0.1:8010",
|
|
85
|
+
*,
|
|
86
|
+
token: str | None = None,
|
|
87
|
+
token_header: str | None = None,
|
|
88
|
+
cert: str | None = None,
|
|
89
|
+
key: str | None = None,
|
|
90
|
+
ca: str | None = None,
|
|
91
|
+
check_hostname: bool = False,
|
|
92
|
+
timeout: float | None = None,
|
|
93
|
+
) -> None:
|
|
94
|
+
parsed = urlparse(base_url)
|
|
95
|
+
protocol = parsed.scheme or "http"
|
|
96
|
+
host = parsed.hostname or "127.0.0.1"
|
|
97
|
+
port = parsed.port or (443 if protocol == "https" else 80)
|
|
98
|
+
json_rpc_client = _import_jsonrpc_client()
|
|
99
|
+
self.transport = json_rpc_client(
|
|
100
|
+
protocol=protocol,
|
|
101
|
+
host=host,
|
|
102
|
+
port=port,
|
|
103
|
+
token=token,
|
|
104
|
+
token_header=token_header,
|
|
105
|
+
cert=cert,
|
|
106
|
+
key=key,
|
|
107
|
+
ca=ca,
|
|
108
|
+
check_hostname=check_hostname,
|
|
109
|
+
timeout=timeout,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
async def call(self, command: str, params: dict[str, Any] | None = None) -> Any:
|
|
113
|
+
"""Execute one MAC command and return its unwrapped result."""
|
|
114
|
+
if command in _QUEUED_COMMANDS:
|
|
115
|
+
raw = await self.transport.execute_command_unified(
|
|
116
|
+
command,
|
|
117
|
+
params or {},
|
|
118
|
+
auto_poll=True,
|
|
119
|
+
)
|
|
120
|
+
else:
|
|
121
|
+
raw = await self.transport.execute_command_unified(
|
|
122
|
+
command,
|
|
123
|
+
params or {},
|
|
124
|
+
expect_queue=False,
|
|
125
|
+
)
|
|
126
|
+
return _unwrap(raw)
|
|
127
|
+
|
|
128
|
+
async def close(self) -> None:
|
|
129
|
+
"""Close the underlying transport."""
|
|
130
|
+
close = getattr(self.transport, "close", None)
|
|
131
|
+
if close is not None:
|
|
132
|
+
await close()
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _make_method(command: str) -> Any:
|
|
136
|
+
async def method(self: MacClient, params: dict[str, Any] | None = None) -> Any:
|
|
137
|
+
return await self.call(command, params)
|
|
138
|
+
|
|
139
|
+
method.__name__ = command
|
|
140
|
+
method.__qualname__ = f"MacClient.{command}"
|
|
141
|
+
method.__doc__ = f"Execute the MAC ``{command}`` command."
|
|
142
|
+
return method
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
for _command in COMMANDS:
|
|
146
|
+
setattr(MacClient, _command, _make_method(_command))
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# client/macore_client/live_check.py
|
|
2
|
+
# Author: Vasiliy Zdanovskiy <vasilyvz@gmail.com>
|
|
3
|
+
# Project: model_access_core (71b1e006-b777-4edb-854e-f0a47120f793)
|
|
4
|
+
"""Live acceptance checks against a deployed MAC server.
|
|
5
|
+
|
|
6
|
+
Shared verbatim between the installed client's ``pipeline`` console script and
|
|
7
|
+
the server repository, so there is exactly one runner for every context the
|
|
8
|
+
pipeline contract names: local verification, post-deploy acceptance, and the
|
|
9
|
+
post-start smoke run.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import asyncio
|
|
15
|
+
import os
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
from macore_client.client import MacClient
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _base_url() -> str:
|
|
22
|
+
return os.environ.get("MAC_VERIFY_BASE_URL", "http://127.0.0.1:8010")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _timeout() -> float:
|
|
26
|
+
raw = os.environ.get("MAC_VERIFY_TIMEOUT", "300")
|
|
27
|
+
try:
|
|
28
|
+
return float(raw)
|
|
29
|
+
except ValueError:
|
|
30
|
+
return 300.0
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
async def _with_client(coro: Any) -> int:
|
|
34
|
+
client = MacClient(_base_url(), timeout=_timeout())
|
|
35
|
+
try:
|
|
36
|
+
return await coro(client)
|
|
37
|
+
finally:
|
|
38
|
+
await client.close()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def run_health_live() -> int:
|
|
42
|
+
"""The deployed server answers health and model_health."""
|
|
43
|
+
|
|
44
|
+
async def check(client: MacClient) -> int:
|
|
45
|
+
health = await client.call("health")
|
|
46
|
+
model_health = await client.call("model_health")
|
|
47
|
+
data = model_health.get("data") if isinstance(model_health, dict) else None
|
|
48
|
+
adapter = (data or {}).get("adapter") if isinstance(data, dict) else None
|
|
49
|
+
registry = (data or {}).get("registry") if isinstance(data, dict) else None
|
|
50
|
+
providers = (registry or {}).get("providers") if isinstance(registry, dict) else None
|
|
51
|
+
if not isinstance(health, dict):
|
|
52
|
+
print("FAIL health-live: health returned no object")
|
|
53
|
+
return 1
|
|
54
|
+
if adapter != "ok" or not isinstance(providers, int) or providers < 1:
|
|
55
|
+
print(f"FAIL health-live: adapter={adapter!r} providers={providers!r}")
|
|
56
|
+
return 1
|
|
57
|
+
print(f"PASS health-live: adapter=ok providers={providers}")
|
|
58
|
+
return 0
|
|
59
|
+
|
|
60
|
+
return asyncio.run(_with_client(check))
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def run_providers_live() -> int:
|
|
64
|
+
"""The deployed server lists at least one provider with models."""
|
|
65
|
+
|
|
66
|
+
async def check(client: MacClient) -> int:
|
|
67
|
+
result = await client.call("model_providers")
|
|
68
|
+
data = result.get("data") if isinstance(result, dict) else None
|
|
69
|
+
providers = data if isinstance(data, list) else None
|
|
70
|
+
if providers is None and isinstance(data, dict):
|
|
71
|
+
providers = data.get("providers") if isinstance(data.get("providers"), list) else None
|
|
72
|
+
if not isinstance(providers, list) or not providers:
|
|
73
|
+
print(f"FAIL providers-live: no providers in {str(result)[:200]}")
|
|
74
|
+
return 1
|
|
75
|
+
names = [p.get("provider_id") for p in providers if isinstance(p, dict)]
|
|
76
|
+
print(f"PASS providers-live: {len(providers)} providers: {', '.join(map(str, names))}")
|
|
77
|
+
return 0
|
|
78
|
+
|
|
79
|
+
return asyncio.run(_with_client(check))
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def run_selftest_live() -> int:
|
|
83
|
+
"""Drive the server's own full live regression (model_selftest)."""
|
|
84
|
+
|
|
85
|
+
async def check(client: MacClient) -> int:
|
|
86
|
+
result = await client.call("model_selftest", {})
|
|
87
|
+
node: Any = result
|
|
88
|
+
for _ in range(6):
|
|
89
|
+
if isinstance(node, dict) and isinstance(node.get("data"), dict):
|
|
90
|
+
node = node["data"]
|
|
91
|
+
continue
|
|
92
|
+
break
|
|
93
|
+
if not isinstance(node, dict) or "exit_code" not in node:
|
|
94
|
+
print(f"FAIL selftest-live: unrecognized selftest result: {str(result)[:300]}")
|
|
95
|
+
return 1
|
|
96
|
+
ok = bool(node.get("ok"))
|
|
97
|
+
exit_code = int(node.get("exit_code") or 0)
|
|
98
|
+
passed = node.get("passed")
|
|
99
|
+
failed = node.get("failed")
|
|
100
|
+
n_pass = len(passed) if isinstance(passed, list) else 0
|
|
101
|
+
n_fail = len(failed) if isinstance(failed, list) else 0
|
|
102
|
+
line = f"selftest-live: ok={ok} exit_code={exit_code} passed={n_pass} failed={n_fail}"
|
|
103
|
+
if not ok or exit_code != 0 or n_pass == 0:
|
|
104
|
+
print(f"FAIL {line}")
|
|
105
|
+
return 1
|
|
106
|
+
print(f"PASS {line}")
|
|
107
|
+
return 0
|
|
108
|
+
|
|
109
|
+
return asyncio.run(_with_client(check))
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# client/macore_client/pipeline.py
|
|
2
|
+
# Author: Vasiliy Zdanovskiy <vasilyvz@gmail.com>
|
|
3
|
+
# Project: model_access_core (71b1e006-b777-4edb-854e-f0a47120f793)
|
|
4
|
+
"""The ``pipeline`` console script: fleet acceptance contract for MAC.
|
|
5
|
+
|
|
6
|
+
``pipeline`` with no arguments runs every check, ``pipeline <check>`` runs one
|
|
7
|
+
named check, ``pipeline --list`` enumerates them. Repository-only checks
|
|
8
|
+
(unit tests, config validation) detect the MAC repository from the current
|
|
9
|
+
working directory and self-skip cleanly when the pipeline runs from an
|
|
10
|
+
installed client elsewhere; live checks target ``MAC_VERIFY_BASE_URL``
|
|
11
|
+
(default ``http://127.0.0.1:8010``).
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import argparse
|
|
17
|
+
import subprocess
|
|
18
|
+
import sys
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import Callable
|
|
22
|
+
|
|
23
|
+
from macore_client.live_check import run_health_live, run_providers_live, run_selftest_live
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass(frozen=True)
|
|
27
|
+
class Check:
|
|
28
|
+
"""One named verification check.
|
|
29
|
+
|
|
30
|
+
Attributes:
|
|
31
|
+
name: Stable check name used on the command line.
|
|
32
|
+
description: One-line description shown by ``pipeline --list``.
|
|
33
|
+
run: Zero-argument callable returning an integer exit status, 0 for pass.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
name: str
|
|
37
|
+
description: str
|
|
38
|
+
run: Callable[[], int]
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _repo_root() -> Path | None:
|
|
42
|
+
"""Return the MAC repository root when the cwd is inside it."""
|
|
43
|
+
cur = Path.cwd().resolve()
|
|
44
|
+
for candidate in (cur, *cur.parents):
|
|
45
|
+
marker = candidate / "pyproject.toml"
|
|
46
|
+
if marker.is_file() and (candidate / "model_access_core").is_dir():
|
|
47
|
+
try:
|
|
48
|
+
if 'name = "model_access_core"' in marker.read_text(encoding="utf-8"):
|
|
49
|
+
return candidate
|
|
50
|
+
except OSError:
|
|
51
|
+
return None
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def run_unit() -> int:
|
|
56
|
+
"""Repository unit tests (pytest). Self-skips outside the repository."""
|
|
57
|
+
repo = _repo_root()
|
|
58
|
+
if repo is None:
|
|
59
|
+
print("SKIP unit: not inside the model_access_core repository")
|
|
60
|
+
return 0
|
|
61
|
+
proc = subprocess.run(
|
|
62
|
+
[sys.executable, "-m", "pytest", "-q"],
|
|
63
|
+
cwd=repo,
|
|
64
|
+
check=False,
|
|
65
|
+
)
|
|
66
|
+
print(f"{'PASS' if proc.returncode == 0 else 'FAIL'} unit: pytest exit={proc.returncode}")
|
|
67
|
+
return proc.returncode
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def run_config() -> int:
|
|
71
|
+
"""Repository config placeholder validation. Self-skips outside the repository."""
|
|
72
|
+
repo = _repo_root()
|
|
73
|
+
if repo is None:
|
|
74
|
+
print("SKIP config: not inside the model_access_core repository")
|
|
75
|
+
return 0
|
|
76
|
+
config_path = repo / "configs" / "config.json"
|
|
77
|
+
if not config_path.is_file():
|
|
78
|
+
print(f"FAIL config: missing {config_path}")
|
|
79
|
+
return 1
|
|
80
|
+
try:
|
|
81
|
+
import json
|
|
82
|
+
|
|
83
|
+
from model_access_core.config.placeholders import find_unresolved_placeholders
|
|
84
|
+
except ImportError as exc:
|
|
85
|
+
print(f"FAIL config: model_access_core not importable: {exc}")
|
|
86
|
+
return 1
|
|
87
|
+
config = json.loads(config_path.read_text(encoding="utf-8"))
|
|
88
|
+
unresolved = find_unresolved_placeholders(config)
|
|
89
|
+
if unresolved:
|
|
90
|
+
print(f"FAIL config: unresolved placeholders: {unresolved}")
|
|
91
|
+
return 1
|
|
92
|
+
print("PASS config: no unresolved placeholders")
|
|
93
|
+
return 0
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def run_cli_surface() -> int:
|
|
97
|
+
"""Verify the CLI covers the client surface exactly. Runs offline."""
|
|
98
|
+
from macore_client.cli import build_parser
|
|
99
|
+
from macore_client.client import command_methods
|
|
100
|
+
|
|
101
|
+
parser = build_parser()
|
|
102
|
+
subparsers = next(
|
|
103
|
+
action
|
|
104
|
+
for action in parser._actions # noqa: SLF001 - argparse offers no public accessor
|
|
105
|
+
if isinstance(action, argparse._SubParsersAction) # noqa: SLF001
|
|
106
|
+
)
|
|
107
|
+
exposed = {name.replace("-", "_") for name in subparsers.choices}
|
|
108
|
+
methods = set(command_methods())
|
|
109
|
+
if exposed != methods:
|
|
110
|
+
print(f"FAIL cli-surface: CLI vs client mismatch: {sorted(exposed ^ methods)}")
|
|
111
|
+
return 1
|
|
112
|
+
print(f"PASS cli-surface: {len(methods)} commands exposed")
|
|
113
|
+
return 0
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
CHECKS: tuple[Check, ...] = (
|
|
117
|
+
Check("unit", "Repository: run the unit test suite (self-skips outside the repo).", run_unit),
|
|
118
|
+
Check("config", "Repository: config placeholder validation (self-skips outside the repo).", run_config),
|
|
119
|
+
Check("cli-surface", "Offline: the CLI exposes exactly the client's command surface.", run_cli_surface),
|
|
120
|
+
Check("health-live", "The deployed server answers health and model_health.", run_health_live),
|
|
121
|
+
Check("providers-live", "The deployed server lists at least one provider.", run_providers_live),
|
|
122
|
+
Check("selftest-live", "Drive the server's full live regression (model_selftest).", run_selftest_live),
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _run_all() -> int:
|
|
127
|
+
"""Run every check in order without early exit."""
|
|
128
|
+
failed = 0
|
|
129
|
+
for check in CHECKS:
|
|
130
|
+
print(f"--- {check.name}", flush=True)
|
|
131
|
+
status = check.run()
|
|
132
|
+
print(f"{'PASS' if status == 0 else 'FAIL'} {check.name}", flush=True)
|
|
133
|
+
if status != 0:
|
|
134
|
+
failed += 1
|
|
135
|
+
print(f"pipeline summary: {len(CHECKS) - failed} passed, {failed} failed", flush=True)
|
|
136
|
+
return 0 if failed == 0 else 1
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def main(argv: list[str] | None = None) -> int:
|
|
140
|
+
"""Entry point of the ``pipeline`` command."""
|
|
141
|
+
parser = argparse.ArgumentParser(
|
|
142
|
+
prog="pipeline",
|
|
143
|
+
description="Run the MAC acceptance suite or a single named check.",
|
|
144
|
+
)
|
|
145
|
+
parser.add_argument("check", nargs="?", help="name of a single check to run")
|
|
146
|
+
parser.add_argument(
|
|
147
|
+
"--list",
|
|
148
|
+
action="store_true",
|
|
149
|
+
dest="list_checks",
|
|
150
|
+
help="list the available check names",
|
|
151
|
+
)
|
|
152
|
+
args = parser.parse_args(argv)
|
|
153
|
+
|
|
154
|
+
if args.list_checks:
|
|
155
|
+
for check in CHECKS:
|
|
156
|
+
print(f"{check.name}: {check.description}")
|
|
157
|
+
return 0
|
|
158
|
+
|
|
159
|
+
if args.check is not None:
|
|
160
|
+
for check in CHECKS:
|
|
161
|
+
if check.name == args.check:
|
|
162
|
+
return check.run()
|
|
163
|
+
print(f"unknown check: {args.check}")
|
|
164
|
+
print("available checks: " + ", ".join(check.name for check in CHECKS))
|
|
165
|
+
return 2
|
|
166
|
+
|
|
167
|
+
return _run_all()
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
if __name__ == "__main__":
|
|
171
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
macore_client/__init__.py,sha256=tBZ3_aagtOD4QW3b1rS8Dt1jEXbSKqww1ZMSkEBJ0ss,320
|
|
2
|
+
macore_client/cli.py,sha256=77uk0JK3PoXrcWS-EXLpBsxcZSLJYj2XMiZMcLi2ApU,2694
|
|
3
|
+
macore_client/client.py,sha256=9aJdr5LnmBvxHB7wk83T9i9FnznB-W6jDeSSHV2jafo,4708
|
|
4
|
+
macore_client/live_check.py,sha256=rrfG03Bomx9ILiM5lE2ZGtknvcctSr_u9eZaVNsN6LU,4154
|
|
5
|
+
macore_client/pipeline.py,sha256=-63x4grFLDfCtZ66EuArOlQW6CjBph6PYeTf-A6BUqc,6067
|
|
6
|
+
macore_client-0.1.4.dist-info/METADATA,sha256=7kvHeGf4uWOFa7kq_Yuq4X4Aa7KD10GrBb9PWZdkBKQ,209
|
|
7
|
+
macore_client-0.1.4.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
8
|
+
macore_client-0.1.4.dist-info/entry_points.txt,sha256=Wz1Y2YAr79QX41nO-QK-IENtURFDhS4itel2OrFmcIw,96
|
|
9
|
+
macore_client-0.1.4.dist-info/RECORD,,
|