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.
@@ -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())
@@ -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,6 @@
1
+ Metadata-Version: 2.4
2
+ Name: macore-client
3
+ Version: 0.1.4
4
+ Summary: Client and pipeline acceptance runner for the Model Access Core (MAC) server
5
+ Requires-Python: >=3.11
6
+ Requires-Dist: mcp-proxy-adapter>=8.10.15
@@ -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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ macore-client = macore_client.cli:main
3
+ pipeline = macore_client.pipeline:main