code-analysis-client 1.0.3__tar.gz → 1.0.5__tar.gz
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.
- {code_analysis_client-1.0.3 → code_analysis_client-1.0.5}/PKG-INFO +24 -1
- {code_analysis_client-1.0.3 → code_analysis_client-1.0.5}/README.md +23 -0
- {code_analysis_client-1.0.3 → code_analysis_client-1.0.5}/code_analysis_client/__init__.py +14 -0
- {code_analysis_client-1.0.3 → code_analysis_client-1.0.5}/code_analysis_client/client.py +7 -1
- {code_analysis_client-1.0.3 → code_analysis_client-1.0.5}/code_analysis_client/file_session.py +28 -24
- code_analysis_client-1.0.5/code_analysis_client/responses.py +45 -0
- code_analysis_client-1.0.5/code_analysis_client/server_api.py +113 -0
- code_analysis_client-1.0.5/code_analysis_client/universal_file.py +117 -0
- code_analysis_client-1.0.5/code_analysis_client/version.txt +1 -0
- {code_analysis_client-1.0.3 → code_analysis_client-1.0.5}/code_analysis_client.egg-info/PKG-INFO +24 -1
- {code_analysis_client-1.0.3 → code_analysis_client-1.0.5}/code_analysis_client.egg-info/SOURCES.txt +3 -0
- code_analysis_client-1.0.3/code_analysis_client/version.txt +0 -1
- {code_analysis_client-1.0.3 → code_analysis_client-1.0.5}/code_analysis_client/commands_proxy.py +0 -0
- {code_analysis_client-1.0.3 → code_analysis_client-1.0.5}/code_analysis_client/config.py +0 -0
- {code_analysis_client-1.0.3 → code_analysis_client-1.0.5}/code_analysis_client/exceptions.py +0 -0
- {code_analysis_client-1.0.3 → code_analysis_client-1.0.5}/code_analysis_client/py.typed +0 -0
- {code_analysis_client-1.0.3 → code_analysis_client-1.0.5}/code_analysis_client/server_schema.py +0 -0
- {code_analysis_client-1.0.3 → code_analysis_client-1.0.5}/code_analysis_client/validation.py +0 -0
- {code_analysis_client-1.0.3 → code_analysis_client-1.0.5}/code_analysis_client.egg-info/dependency_links.txt +0 -0
- {code_analysis_client-1.0.3 → code_analysis_client-1.0.5}/code_analysis_client.egg-info/requires.txt +0 -0
- {code_analysis_client-1.0.3 → code_analysis_client-1.0.5}/code_analysis_client.egg-info/top_level.txt +0 -0
- {code_analysis_client-1.0.3 → code_analysis_client-1.0.5}/pyproject.toml +0 -0
- {code_analysis_client-1.0.3 → code_analysis_client-1.0.5}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code-analysis-client
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.5
|
|
4
4
|
Summary: Async JSON-RPC client for the code-analysis MCP server (mcp-proxy-adapter JsonRpcClient)
|
|
5
5
|
Author-email: Vasiliy Zdanovskiy <vasilyvz@gmail.com>
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -75,6 +75,29 @@ async with CodeAnalysisAsyncClient(host="127.0.0.1", port=15001) as client:
|
|
|
75
75
|
|
|
76
76
|
Use `call_unified_validated` when you need queue polling. Pass `refresh_schema=True` on a single call to bypass the in-memory schema cache.
|
|
77
77
|
|
|
78
|
+
## High-level facades (aligned with server 1.0.3)
|
|
79
|
+
|
|
80
|
+
The client does **not** wrap CST commands (`cst_load_file`, …) or legacy file I/O
|
|
81
|
+
(`universal_file_read`, `read_project_text_file`, …). Those commands are removed
|
|
82
|
+
from the server registry. Use the facades below or generic `call` / `commands.*`.
|
|
83
|
+
|
|
84
|
+
| Facade | Property | Server commands |
|
|
85
|
+
|--------|----------|-----------------|
|
|
86
|
+
| Client DB sessions + transfer | `client.file_sessions` | `session_*`, `project_file_transfer_*`, `project_file_advisory_lock_batch` |
|
|
87
|
+
| Universal edit sessions | `client.universal_files` | `universal_file_open`, `edit`, `write`, `close`, `preview` |
|
|
88
|
+
| Any registered command | `client.call` / `client.commands.<name>` | schema from live `help()` |
|
|
89
|
+
|
|
90
|
+
Canonical command lists: `code_analysis_client.server_api` (also exported as
|
|
91
|
+
`REMOVED_COMMANDS`, `CLIENT_FACADE_COMMANDS`).
|
|
92
|
+
|
|
93
|
+
Sync check (in-process registry):
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
pytest tests/test_client_server_api_sync.py
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Package version matches the **code-analysis** server project (currently **1.0.4**).
|
|
100
|
+
|
|
78
101
|
## Examples (this repository)
|
|
79
102
|
|
|
80
103
|
Runnable scripts live under `client/examples/`. **Long-form “man page” style
|
|
@@ -63,6 +63,29 @@ async with CodeAnalysisAsyncClient(host="127.0.0.1", port=15001) as client:
|
|
|
63
63
|
|
|
64
64
|
Use `call_unified_validated` when you need queue polling. Pass `refresh_schema=True` on a single call to bypass the in-memory schema cache.
|
|
65
65
|
|
|
66
|
+
## High-level facades (aligned with server 1.0.3)
|
|
67
|
+
|
|
68
|
+
The client does **not** wrap CST commands (`cst_load_file`, …) or legacy file I/O
|
|
69
|
+
(`universal_file_read`, `read_project_text_file`, …). Those commands are removed
|
|
70
|
+
from the server registry. Use the facades below or generic `call` / `commands.*`.
|
|
71
|
+
|
|
72
|
+
| Facade | Property | Server commands |
|
|
73
|
+
|--------|----------|-----------------|
|
|
74
|
+
| Client DB sessions + transfer | `client.file_sessions` | `session_*`, `project_file_transfer_*`, `project_file_advisory_lock_batch` |
|
|
75
|
+
| Universal edit sessions | `client.universal_files` | `universal_file_open`, `edit`, `write`, `close`, `preview` |
|
|
76
|
+
| Any registered command | `client.call` / `client.commands.<name>` | schema from live `help()` |
|
|
77
|
+
|
|
78
|
+
Canonical command lists: `code_analysis_client.server_api` (also exported as
|
|
79
|
+
`REMOVED_COMMANDS`, `CLIENT_FACADE_COMMANDS`).
|
|
80
|
+
|
|
81
|
+
Sync check (in-process registry):
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
pytest tests/test_client_server_api_sync.py
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Package version matches the **code-analysis** server project (currently **1.0.4**).
|
|
88
|
+
|
|
66
89
|
## Examples (this repository)
|
|
67
90
|
|
|
68
91
|
Runnable scripts live under `client/examples/`. **Long-form “man page” style
|
|
@@ -18,6 +18,14 @@ from code_analysis_client.config import (
|
|
|
18
18
|
)
|
|
19
19
|
from code_analysis_client.exceptions import ClientValidationError
|
|
20
20
|
from code_analysis_client.file_session import FileSessionClient, SessionNotFoundError
|
|
21
|
+
from code_analysis_client.server_api import (
|
|
22
|
+
CLIENT_FACADE_COMMANDS,
|
|
23
|
+
CST_REMOVED_COMMANDS,
|
|
24
|
+
LEGACY_REMOVED_COMMANDS,
|
|
25
|
+
REMOVED_COMMANDS,
|
|
26
|
+
UNIVERSAL_FILE_COMMANDS,
|
|
27
|
+
)
|
|
28
|
+
from code_analysis_client.universal_file import UniversalFileClient
|
|
21
29
|
from code_analysis_client.server_schema import (
|
|
22
30
|
fetch_command_schema_from_server,
|
|
23
31
|
parse_schema_from_help_payload,
|
|
@@ -28,10 +36,16 @@ from code_analysis_client.validation import (
|
|
|
28
36
|
)
|
|
29
37
|
|
|
30
38
|
__all__ = [
|
|
39
|
+
"CLIENT_FACADE_COMMANDS",
|
|
40
|
+
"CST_REMOVED_COMMANDS",
|
|
31
41
|
"ClientValidationError",
|
|
32
42
|
"CodeAnalysisAsyncClient",
|
|
33
43
|
"FileSessionClient",
|
|
44
|
+
"LEGACY_REMOVED_COMMANDS",
|
|
45
|
+
"REMOVED_COMMANDS",
|
|
34
46
|
"SessionNotFoundError",
|
|
47
|
+
"UNIVERSAL_FILE_COMMANDS",
|
|
48
|
+
"UniversalFileClient",
|
|
35
49
|
"ValidatedCommandsProxy",
|
|
36
50
|
"adapter_settings_from_server_config",
|
|
37
51
|
"adapter_settings_to_jsonrpc_kwargs",
|
|
@@ -22,6 +22,7 @@ from code_analysis_client.config import (
|
|
|
22
22
|
load_server_config,
|
|
23
23
|
)
|
|
24
24
|
from code_analysis_client.file_session import FileSessionClient
|
|
25
|
+
from code_analysis_client.universal_file import UniversalFileClient
|
|
25
26
|
from code_analysis_client.server_schema import fetch_command_schema_from_server
|
|
26
27
|
from code_analysis_client.validation import (
|
|
27
28
|
prepare_params_for_schema,
|
|
@@ -122,9 +123,14 @@ class CodeAnalysisAsyncClient:
|
|
|
122
123
|
|
|
123
124
|
@property
|
|
124
125
|
def file_sessions(self) -> FileSessionClient:
|
|
125
|
-
"""Session-scoped download/upload and lock workflow (
|
|
126
|
+
"""Session-scoped download/upload and lock workflow (``session_*``, transfer)."""
|
|
126
127
|
return FileSessionClient(self)
|
|
127
128
|
|
|
129
|
+
@property
|
|
130
|
+
def universal_files(self) -> UniversalFileClient:
|
|
131
|
+
"""Universal edit-session workflow (``universal_file_*`` commands)."""
|
|
132
|
+
return UniversalFileClient(self)
|
|
133
|
+
|
|
128
134
|
async def get_command_schema(
|
|
129
135
|
self, command: str, *, refresh: bool = False
|
|
130
136
|
) -> Dict[str, Any]:
|
{code_analysis_client-1.0.3 → code_analysis_client-1.0.5}/code_analysis_client/file_session.py
RENAMED
|
@@ -15,6 +15,7 @@ from pathlib import Path
|
|
|
15
15
|
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union
|
|
16
16
|
|
|
17
17
|
from code_analysis_client.exceptions import ClientValidationError
|
|
18
|
+
from code_analysis_client.responses import unwrap_command_result
|
|
18
19
|
|
|
19
20
|
if TYPE_CHECKING:
|
|
20
21
|
from code_analysis_client.client import CodeAnalysisAsyncClient
|
|
@@ -25,24 +26,22 @@ class SessionNotFoundError(ClientValidationError):
|
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
def _unwrap(data: Dict[str, Any]) -> Dict[str, Any]:
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
raise SessionNotFoundError(message, field="session_id", details=data)
|
|
45
|
-
raise ClientValidationError(message, field="command", details=data)
|
|
29
|
+
return unwrap_command_result(
|
|
30
|
+
data,
|
|
31
|
+
session_not_found_type=SessionNotFoundError,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _extract_file_id(payload: Dict[str, Any], *, field: str = "file_id") -> str:
|
|
36
|
+
"""Return non-empty ``file_id`` from a command payload or raise."""
|
|
37
|
+
fid = str(payload.get(field) or "").strip()
|
|
38
|
+
if not fid:
|
|
39
|
+
raise ClientValidationError(
|
|
40
|
+
f"{field} missing from server response",
|
|
41
|
+
field=field,
|
|
42
|
+
details=payload,
|
|
43
|
+
)
|
|
44
|
+
return fid
|
|
46
45
|
|
|
47
46
|
|
|
48
47
|
class FileSessionClient:
|
|
@@ -230,7 +229,7 @@ class FileSessionClient:
|
|
|
230
229
|
chunk_size: Optional[int] = None,
|
|
231
230
|
include_backup_history: bool = False,
|
|
232
231
|
) -> Dict[str, Any]:
|
|
233
|
-
"""
|
|
232
|
+
"""Begin chunked download; returns payload including ``file_id`` and ``transfer_id``."""
|
|
234
233
|
await self.assert_session_exists(session_id)
|
|
235
234
|
params: Dict[str, Any] = {
|
|
236
235
|
"session_id": session_id,
|
|
@@ -246,12 +245,14 @@ class FileSessionClient:
|
|
|
246
245
|
params["file_path"] = file_path
|
|
247
246
|
if chunk_size is not None:
|
|
248
247
|
params["chunk_size"] = chunk_size
|
|
249
|
-
|
|
248
|
+
payload = _unwrap(
|
|
250
249
|
await self._client.call_validated(
|
|
251
250
|
"project_file_transfer_download_begin",
|
|
252
251
|
params,
|
|
253
252
|
)
|
|
254
253
|
)
|
|
254
|
+
_extract_file_id(payload)
|
|
255
|
+
return payload
|
|
255
256
|
|
|
256
257
|
async def download_to_path(
|
|
257
258
|
self,
|
|
@@ -275,7 +276,7 @@ class FileSessionClient:
|
|
|
275
276
|
compression: str = "identity",
|
|
276
277
|
lock_mode: str = "full",
|
|
277
278
|
) -> Tuple[Dict[str, Any], Any]:
|
|
278
|
-
"""Download + lock
|
|
279
|
+
"""Download + lock; returns (begin_payload with ``file_id``, download_receipt)."""
|
|
279
280
|
begin = await self.begin_download_locked(
|
|
280
281
|
session_id,
|
|
281
282
|
project_id=project_id,
|
|
@@ -326,7 +327,7 @@ class FileSessionClient:
|
|
|
326
327
|
dry_run: bool = False,
|
|
327
328
|
backup: bool = True,
|
|
328
329
|
) -> Dict[str, Any]:
|
|
329
|
-
"""Persist uploaded buffer and release locks
|
|
330
|
+
"""Persist uploaded buffer and release locks; response includes ``file_id``."""
|
|
330
331
|
await self.assert_session_exists(session_id)
|
|
331
332
|
params: Dict[str, Any] = {
|
|
332
333
|
"session_id": session_id,
|
|
@@ -341,12 +342,15 @@ class FileSessionClient:
|
|
|
341
342
|
params["file_id"] = file_id
|
|
342
343
|
if file_path is not None:
|
|
343
344
|
params["file_path"] = file_path
|
|
344
|
-
|
|
345
|
+
payload = _unwrap(
|
|
345
346
|
await self._client.call_validated(
|
|
346
347
|
"project_file_transfer_upload_save",
|
|
347
348
|
params,
|
|
348
349
|
)
|
|
349
350
|
)
|
|
351
|
+
if not dry_run:
|
|
352
|
+
_extract_file_id(payload)
|
|
353
|
+
return payload
|
|
350
354
|
|
|
351
355
|
async def upload_file_and_unlock(
|
|
352
356
|
self,
|
|
@@ -361,7 +365,7 @@ class FileSessionClient:
|
|
|
361
365
|
unlock_after_write: bool = True,
|
|
362
366
|
backup: bool = True,
|
|
363
367
|
) -> Dict[str, Any]:
|
|
364
|
-
"""Upload bytes
|
|
368
|
+
"""Upload bytes, save to project file, unlock; returns save payload with ``file_id``."""
|
|
365
369
|
name = filename or (Path(file_path).name if file_path else "payload.bin")
|
|
366
370
|
receipt = await self.upload_bytes(
|
|
367
371
|
payload,
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Parse JSON-RPC command envelopes from code-analysis-server."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Dict
|
|
6
|
+
|
|
7
|
+
from code_analysis_client.exceptions import ClientValidationError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def command_error_code(data: Dict[str, Any]) -> str:
|
|
11
|
+
"""Return domain or JSON-RPC error code from a failed command response."""
|
|
12
|
+
if data.get("success") is True:
|
|
13
|
+
return ""
|
|
14
|
+
top = data.get("code")
|
|
15
|
+
if top is not None and str(top).strip():
|
|
16
|
+
return str(top).strip()
|
|
17
|
+
err = data.get("error")
|
|
18
|
+
if isinstance(err, dict):
|
|
19
|
+
nested = err.get("code")
|
|
20
|
+
if nested is not None:
|
|
21
|
+
return str(nested).strip()
|
|
22
|
+
if isinstance(err, str) and err.strip():
|
|
23
|
+
return err.strip()
|
|
24
|
+
return ""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def unwrap_command_result(
|
|
28
|
+
data: Dict[str, Any],
|
|
29
|
+
*,
|
|
30
|
+
session_not_found_type: type[ClientValidationError] | None = None,
|
|
31
|
+
) -> Dict[str, Any]:
|
|
32
|
+
"""Return success ``data`` dict or raise :class:`ClientValidationError`."""
|
|
33
|
+
if data.get("success"):
|
|
34
|
+
inner = data.get("data")
|
|
35
|
+
return inner if isinstance(inner, dict) else data
|
|
36
|
+
code = command_error_code(data)
|
|
37
|
+
message = data.get("message")
|
|
38
|
+
err = data.get("error")
|
|
39
|
+
if message is None and isinstance(err, dict):
|
|
40
|
+
message = err.get("message")
|
|
41
|
+
if message is None:
|
|
42
|
+
message = str(data)
|
|
43
|
+
if code == "SESSION_NOT_FOUND" and session_not_found_type is not None:
|
|
44
|
+
raise session_not_found_type(message, field="session_id", details=data)
|
|
45
|
+
raise ClientValidationError(message, field="command", details=data)
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Canonical command names for the high-level client API vs the live server registry.
|
|
3
|
+
|
|
4
|
+
The generic client (:class:`CodeAnalysisAsyncClient.call`) can invoke any command
|
|
5
|
+
returned by server ``help``. Facade modules (:mod:`file_session`, :mod:`universal_file`)
|
|
6
|
+
must only reference commands listed here — and every name in
|
|
7
|
+
:data:`CLIENT_FACADE_COMMANDS` must be registered on the server.
|
|
8
|
+
|
|
9
|
+
Author: Vasiliy Zdanovskiy
|
|
10
|
+
email: vasilyvz@gmail.com
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from typing import Callable, FrozenSet
|
|
16
|
+
|
|
17
|
+
# Removed from the public server surface — do not use in client facades or docs.
|
|
18
|
+
LEGACY_REMOVED_COMMANDS: FrozenSet[str] = frozenset(
|
|
19
|
+
{
|
|
20
|
+
"universal_file_read",
|
|
21
|
+
"universal_file_save",
|
|
22
|
+
"universal_file_replace",
|
|
23
|
+
"universal_file_delete",
|
|
24
|
+
"read_project_text_file",
|
|
25
|
+
"write_project_text_lines",
|
|
26
|
+
"create_text_file",
|
|
27
|
+
"replace_file_lines",
|
|
28
|
+
}
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# CST tree MCP workflow — modules may exist in the repo but commands are not registered.
|
|
32
|
+
CST_REMOVED_COMMANDS: FrozenSet[str] = frozenset(
|
|
33
|
+
{
|
|
34
|
+
"cst_load_file",
|
|
35
|
+
"cst_save_tree",
|
|
36
|
+
"cst_modify_tree",
|
|
37
|
+
"cst_create_file",
|
|
38
|
+
"cst_find_node",
|
|
39
|
+
"cst_get_node_info",
|
|
40
|
+
"cst_get_node_by_range",
|
|
41
|
+
"cst_get_node_at_line",
|
|
42
|
+
"cst_reload_tree",
|
|
43
|
+
"cst_convert_and_save",
|
|
44
|
+
"list_cst_blocks",
|
|
45
|
+
"query_cst",
|
|
46
|
+
"get_file_lines",
|
|
47
|
+
}
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
REMOVED_COMMANDS: FrozenSet[str] = LEGACY_REMOVED_COMMANDS | CST_REMOVED_COMMANDS
|
|
51
|
+
|
|
52
|
+
FILE_SESSION_COMMANDS: FrozenSet[str] = frozenset(
|
|
53
|
+
{
|
|
54
|
+
"session_create",
|
|
55
|
+
"session_delete",
|
|
56
|
+
"session_list",
|
|
57
|
+
"session_open_file",
|
|
58
|
+
"session_close_file",
|
|
59
|
+
"session_list_file_locks",
|
|
60
|
+
}
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
TRANSFER_AND_LOCK_COMMANDS: FrozenSet[str] = frozenset(
|
|
64
|
+
{
|
|
65
|
+
"project_file_transfer_download_begin",
|
|
66
|
+
"project_file_transfer_upload_save",
|
|
67
|
+
"project_file_advisory_lock_batch",
|
|
68
|
+
}
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
UNIVERSAL_FILE_COMMANDS: FrozenSet[str] = frozenset(
|
|
72
|
+
{
|
|
73
|
+
"universal_file_open",
|
|
74
|
+
"universal_file_edit",
|
|
75
|
+
"universal_file_write",
|
|
76
|
+
"universal_file_close",
|
|
77
|
+
"universal_file_preview",
|
|
78
|
+
}
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
CLIENT_FACADE_COMMANDS: FrozenSet[str] = (
|
|
82
|
+
FILE_SESSION_COMMANDS | TRANSFER_AND_LOCK_COMMANDS | UNIVERSAL_FILE_COMMANDS
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _command_registered(get_command: Callable[[str], object], name: str) -> bool:
|
|
87
|
+
try:
|
|
88
|
+
get_command(name)
|
|
89
|
+
return True
|
|
90
|
+
except KeyError:
|
|
91
|
+
return False
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def assert_facade_commands_registered(get_command: Callable[[str], object]) -> None:
|
|
95
|
+
"""Raise ``KeyError`` if any facade command is missing from the server registry."""
|
|
96
|
+
missing = [
|
|
97
|
+
name
|
|
98
|
+
for name in sorted(CLIENT_FACADE_COMMANDS)
|
|
99
|
+
if not _command_registered(get_command, name)
|
|
100
|
+
]
|
|
101
|
+
if missing:
|
|
102
|
+
raise KeyError(f"Client facade commands not registered on server: {missing}")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def assert_removed_commands_absent(get_command: Callable[[str], object]) -> None:
|
|
106
|
+
"""Raise ``AssertionError`` if a removed command is still registered."""
|
|
107
|
+
present = [
|
|
108
|
+
name
|
|
109
|
+
for name in sorted(REMOVED_COMMANDS)
|
|
110
|
+
if _command_registered(get_command, name)
|
|
111
|
+
]
|
|
112
|
+
if present:
|
|
113
|
+
raise AssertionError(f"Removed commands still registered: {present}")
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Universal file edit-session workflow on top of :class:`CodeAnalysisAsyncClient`.
|
|
3
|
+
|
|
4
|
+
Wraps ``universal_file_open`` / ``edit`` / ``write`` / ``close`` / ``preview`` only.
|
|
5
|
+
Legacy ``universal_file_read`` / ``universal_file_save`` and CST commands are not
|
|
6
|
+
part of this API (see :mod:`server_api`).
|
|
7
|
+
|
|
8
|
+
Author: Vasiliy Zdanovskiy
|
|
9
|
+
email: vasilyvz@gmail.com
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
|
15
|
+
|
|
16
|
+
from code_analysis_client.responses import unwrap_command_result
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from code_analysis_client.client import CodeAnalysisAsyncClient
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class UniversalFileClient:
|
|
23
|
+
"""Edit-session workflow for project files (open → edit → write → close)."""
|
|
24
|
+
|
|
25
|
+
__slots__ = ("_client",)
|
|
26
|
+
|
|
27
|
+
def __init__(self, client: CodeAnalysisAsyncClient) -> None:
|
|
28
|
+
self._client = client
|
|
29
|
+
|
|
30
|
+
async def open(
|
|
31
|
+
self,
|
|
32
|
+
project_id: str,
|
|
33
|
+
file_path: str,
|
|
34
|
+
*,
|
|
35
|
+
create: bool = False,
|
|
36
|
+
initial_content: Optional[str] = None,
|
|
37
|
+
) -> Dict[str, Any]:
|
|
38
|
+
"""Start an edit session (``universal_file_open``)."""
|
|
39
|
+
params: Dict[str, Any] = {
|
|
40
|
+
"project_id": project_id,
|
|
41
|
+
"file_path": file_path,
|
|
42
|
+
"create": create,
|
|
43
|
+
}
|
|
44
|
+
if initial_content is not None:
|
|
45
|
+
params["initial_content"] = initial_content
|
|
46
|
+
return unwrap_command_result(
|
|
47
|
+
await self._client.call_validated("universal_file_open", params)
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
async def edit(
|
|
51
|
+
self,
|
|
52
|
+
project_id: str,
|
|
53
|
+
session_id: str,
|
|
54
|
+
operations: List[Dict[str, Any]],
|
|
55
|
+
) -> Dict[str, Any]:
|
|
56
|
+
"""Apply draft mutations (``universal_file_edit``)."""
|
|
57
|
+
return unwrap_command_result(
|
|
58
|
+
await self._client.call_validated(
|
|
59
|
+
"universal_file_edit",
|
|
60
|
+
{
|
|
61
|
+
"project_id": project_id,
|
|
62
|
+
"session_id": session_id,
|
|
63
|
+
"operations": operations,
|
|
64
|
+
},
|
|
65
|
+
)
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
async def write(
|
|
69
|
+
self,
|
|
70
|
+
project_id: str,
|
|
71
|
+
session_id: str,
|
|
72
|
+
*,
|
|
73
|
+
write_mode: str = "commit",
|
|
74
|
+
) -> Dict[str, Any]:
|
|
75
|
+
"""Persist or preview draft (``universal_file_write``)."""
|
|
76
|
+
return unwrap_command_result(
|
|
77
|
+
await self._client.call_validated(
|
|
78
|
+
"universal_file_write",
|
|
79
|
+
{
|
|
80
|
+
"project_id": project_id,
|
|
81
|
+
"session_id": session_id,
|
|
82
|
+
"write_mode": write_mode,
|
|
83
|
+
},
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
async def close(
|
|
88
|
+
self,
|
|
89
|
+
project_id: str,
|
|
90
|
+
session_id: str,
|
|
91
|
+
) -> Dict[str, Any]:
|
|
92
|
+
"""End edit session (``universal_file_close``)."""
|
|
93
|
+
return unwrap_command_result(
|
|
94
|
+
await self._client.call_validated(
|
|
95
|
+
"universal_file_close",
|
|
96
|
+
{
|
|
97
|
+
"project_id": project_id,
|
|
98
|
+
"session_id": session_id,
|
|
99
|
+
},
|
|
100
|
+
)
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
async def preview(
|
|
104
|
+
self,
|
|
105
|
+
project_id: str,
|
|
106
|
+
file_path: str,
|
|
107
|
+
**kwargs: Any,
|
|
108
|
+
) -> Dict[str, Any]:
|
|
109
|
+
"""Structured read-only preview (``universal_file_preview``)."""
|
|
110
|
+
params: Dict[str, Any] = {
|
|
111
|
+
"project_id": project_id,
|
|
112
|
+
"file_path": file_path,
|
|
113
|
+
}
|
|
114
|
+
params.update(kwargs)
|
|
115
|
+
return unwrap_command_result(
|
|
116
|
+
await self._client.call_validated("universal_file_preview", params)
|
|
117
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1.0.5
|
{code_analysis_client-1.0.3 → code_analysis_client-1.0.5}/code_analysis_client.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code-analysis-client
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.5
|
|
4
4
|
Summary: Async JSON-RPC client for the code-analysis MCP server (mcp-proxy-adapter JsonRpcClient)
|
|
5
5
|
Author-email: Vasiliy Zdanovskiy <vasilyvz@gmail.com>
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -75,6 +75,29 @@ async with CodeAnalysisAsyncClient(host="127.0.0.1", port=15001) as client:
|
|
|
75
75
|
|
|
76
76
|
Use `call_unified_validated` when you need queue polling. Pass `refresh_schema=True` on a single call to bypass the in-memory schema cache.
|
|
77
77
|
|
|
78
|
+
## High-level facades (aligned with server 1.0.3)
|
|
79
|
+
|
|
80
|
+
The client does **not** wrap CST commands (`cst_load_file`, …) or legacy file I/O
|
|
81
|
+
(`universal_file_read`, `read_project_text_file`, …). Those commands are removed
|
|
82
|
+
from the server registry. Use the facades below or generic `call` / `commands.*`.
|
|
83
|
+
|
|
84
|
+
| Facade | Property | Server commands |
|
|
85
|
+
|--------|----------|-----------------|
|
|
86
|
+
| Client DB sessions + transfer | `client.file_sessions` | `session_*`, `project_file_transfer_*`, `project_file_advisory_lock_batch` |
|
|
87
|
+
| Universal edit sessions | `client.universal_files` | `universal_file_open`, `edit`, `write`, `close`, `preview` |
|
|
88
|
+
| Any registered command | `client.call` / `client.commands.<name>` | schema from live `help()` |
|
|
89
|
+
|
|
90
|
+
Canonical command lists: `code_analysis_client.server_api` (also exported as
|
|
91
|
+
`REMOVED_COMMANDS`, `CLIENT_FACADE_COMMANDS`).
|
|
92
|
+
|
|
93
|
+
Sync check (in-process registry):
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
pytest tests/test_client_server_api_sync.py
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Package version matches the **code-analysis** server project (currently **1.0.4**).
|
|
100
|
+
|
|
78
101
|
## Examples (this repository)
|
|
79
102
|
|
|
80
103
|
Runnable scripts live under `client/examples/`. **Long-form “man page” style
|
{code_analysis_client-1.0.3 → code_analysis_client-1.0.5}/code_analysis_client.egg-info/SOURCES.txt
RENAMED
|
@@ -7,7 +7,10 @@ code_analysis_client/config.py
|
|
|
7
7
|
code_analysis_client/exceptions.py
|
|
8
8
|
code_analysis_client/file_session.py
|
|
9
9
|
code_analysis_client/py.typed
|
|
10
|
+
code_analysis_client/responses.py
|
|
11
|
+
code_analysis_client/server_api.py
|
|
10
12
|
code_analysis_client/server_schema.py
|
|
13
|
+
code_analysis_client/universal_file.py
|
|
11
14
|
code_analysis_client/validation.py
|
|
12
15
|
code_analysis_client/version.txt
|
|
13
16
|
code_analysis_client.egg-info/PKG-INFO
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
1.0.3
|
{code_analysis_client-1.0.3 → code_analysis_client-1.0.5}/code_analysis_client/commands_proxy.py
RENAMED
|
File without changes
|
|
File without changes
|
{code_analysis_client-1.0.3 → code_analysis_client-1.0.5}/code_analysis_client/exceptions.py
RENAMED
|
File without changes
|
|
File without changes
|
{code_analysis_client-1.0.3 → code_analysis_client-1.0.5}/code_analysis_client/server_schema.py
RENAMED
|
File without changes
|
{code_analysis_client-1.0.3 → code_analysis_client-1.0.5}/code_analysis_client/validation.py
RENAMED
|
File without changes
|
|
File without changes
|
{code_analysis_client-1.0.3 → code_analysis_client-1.0.5}/code_analysis_client.egg-info/requires.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|