code-analysis-client 1.0.6__tar.gz → 1.6.39__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.6 → code_analysis_client-1.6.39}/PKG-INFO +4 -4
- {code_analysis_client-1.0.6 → code_analysis_client-1.6.39}/README.md +2 -2
- {code_analysis_client-1.0.6 → code_analysis_client-1.6.39}/code_analysis_client/__init__.py +3 -0
- {code_analysis_client-1.0.6 → code_analysis_client-1.6.39}/code_analysis_client/client.py +3 -0
- {code_analysis_client-1.0.6 → code_analysis_client-1.6.39}/code_analysis_client/commands_proxy.py +3 -0
- {code_analysis_client-1.0.6 → code_analysis_client-1.6.39}/code_analysis_client/config.py +3 -0
- {code_analysis_client-1.0.6 → code_analysis_client-1.6.39}/code_analysis_client/exceptions.py +1 -0
- {code_analysis_client-1.0.6 → code_analysis_client-1.6.39}/code_analysis_client/file_session.py +146 -53
- code_analysis_client-1.6.39/code_analysis_client/server_api.py +285 -0
- {code_analysis_client-1.0.6 → code_analysis_client-1.6.39}/code_analysis_client/universal_file.py +17 -4
- {code_analysis_client-1.0.6 → code_analysis_client-1.6.39}/code_analysis_client/validation.py +3 -6
- code_analysis_client-1.6.39/code_analysis_client/version.txt +1 -0
- {code_analysis_client-1.0.6 → code_analysis_client-1.6.39}/code_analysis_client.egg-info/PKG-INFO +4 -4
- {code_analysis_client-1.0.6 → code_analysis_client-1.6.39}/code_analysis_client.egg-info/requires.txt +1 -1
- {code_analysis_client-1.0.6 → code_analysis_client-1.6.39}/pyproject.toml +1 -1
- code_analysis_client-1.0.6/code_analysis_client/server_api.py +0 -154
- code_analysis_client-1.0.6/code_analysis_client/version.txt +0 -1
- {code_analysis_client-1.0.6 → code_analysis_client-1.6.39}/code_analysis_client/py.typed +0 -0
- {code_analysis_client-1.0.6 → code_analysis_client-1.6.39}/code_analysis_client/responses.py +0 -0
- {code_analysis_client-1.0.6 → code_analysis_client-1.6.39}/code_analysis_client/server_schema.py +0 -0
- {code_analysis_client-1.0.6 → code_analysis_client-1.6.39}/code_analysis_client.egg-info/SOURCES.txt +0 -0
- {code_analysis_client-1.0.6 → code_analysis_client-1.6.39}/code_analysis_client.egg-info/dependency_links.txt +0 -0
- {code_analysis_client-1.0.6 → code_analysis_client-1.6.39}/code_analysis_client.egg-info/top_level.txt +0 -0
- {code_analysis_client-1.0.6 → code_analysis_client-1.6.39}/setup.cfg +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code-analysis-client
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.6.39
|
|
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
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
|
-
Requires-Dist: mcp-proxy-adapter>=8.10.
|
|
8
|
+
Requires-Dist: mcp-proxy-adapter>=8.10.15
|
|
9
9
|
Provides-Extra: dev
|
|
10
10
|
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
11
11
|
Requires-Dist: pytest-asyncio>=0.25.0; extra == "dev"
|
|
@@ -97,8 +97,8 @@ Sync checks (in-process registry):
|
|
|
97
97
|
pytest tests/test_client_server_api_sync.py tests/test_code_analysis_client.py -k session
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
-
Package version is in ``
|
|
101
|
-
|
|
100
|
+
Package version is in the root ``pyproject.toml``; before a client wheel build run
|
|
101
|
+
``python scripts/sync_code_analysis_client_version.py`` (also done by ``release_build.sh``).
|
|
102
102
|
|
|
103
103
|
## Examples (this repository)
|
|
104
104
|
|
|
@@ -85,8 +85,8 @@ Sync checks (in-process registry):
|
|
|
85
85
|
pytest tests/test_client_server_api_sync.py tests/test_code_analysis_client.py -k session
|
|
86
86
|
```
|
|
87
87
|
|
|
88
|
-
Package version is in ``
|
|
89
|
-
|
|
88
|
+
Package version is in the root ``pyproject.toml``; before a client wheel build run
|
|
89
|
+
``python scripts/sync_code_analysis_client_version.py`` (also done by ``release_build.sh``).
|
|
90
90
|
|
|
91
91
|
## Examples (this repository)
|
|
92
92
|
|
|
@@ -25,6 +25,7 @@ from code_analysis_client.server_api import (
|
|
|
25
25
|
FILE_SESSION_FACADE_METHODS,
|
|
26
26
|
LEGACY_REMOVED_COMMANDS,
|
|
27
27
|
REMOVED_COMMANDS,
|
|
28
|
+
TRANSFER_FACADE_METHODS,
|
|
28
29
|
UNIVERSAL_FILE_COMMANDS,
|
|
29
30
|
)
|
|
30
31
|
from code_analysis_client.universal_file import UniversalFileClient
|
|
@@ -48,6 +49,7 @@ __all__ = [
|
|
|
48
49
|
"LEGACY_REMOVED_COMMANDS",
|
|
49
50
|
"REMOVED_COMMANDS",
|
|
50
51
|
"SessionNotFoundError",
|
|
52
|
+
"TRANSFER_FACADE_METHODS",
|
|
51
53
|
"UNIVERSAL_FILE_COMMANDS",
|
|
52
54
|
"UniversalFileClient",
|
|
53
55
|
"ValidatedCommandsProxy",
|
|
@@ -62,6 +64,7 @@ __all__ = [
|
|
|
62
64
|
|
|
63
65
|
|
|
64
66
|
def _read_package_version() -> str:
|
|
67
|
+
"""Read installed client version from version.txt or package metadata."""
|
|
65
68
|
vf = Path(__file__).resolve().parent / "version.txt"
|
|
66
69
|
if vf.is_file():
|
|
67
70
|
return vf.read_text(encoding="utf-8").strip()
|
|
@@ -228,10 +228,13 @@ class CodeAnalysisAsyncClient:
|
|
|
228
228
|
)
|
|
229
229
|
|
|
230
230
|
async def close(self) -> None:
|
|
231
|
+
"""Close the underlying JSON-RPC transport."""
|
|
231
232
|
await self._rpc.close()
|
|
232
233
|
|
|
233
234
|
async def __aenter__(self) -> CodeAnalysisAsyncClient:
|
|
235
|
+
"""Enter async context manager and return this client."""
|
|
234
236
|
return self
|
|
235
237
|
|
|
236
238
|
async def __aexit__(self, exc_type: Any, exc: Any, tb: Any) -> None:
|
|
239
|
+
"""Close the client when leaving an async context manager block."""
|
|
237
240
|
await self.close()
|
{code_analysis_client-1.0.6 → code_analysis_client-1.6.39}/code_analysis_client/commands_proxy.py
RENAMED
|
@@ -19,6 +19,7 @@ class ValidatedCommandsProxy:
|
|
|
19
19
|
__slots__ = ("_client",)
|
|
20
20
|
|
|
21
21
|
def __init__(self, client: CodeAnalysisAsyncClient) -> None:
|
|
22
|
+
"""Store the client used for schema lookup and validated command calls."""
|
|
22
23
|
object.__setattr__(self, "_client", client)
|
|
23
24
|
|
|
24
25
|
def clear_schema_cache(self) -> None:
|
|
@@ -47,10 +48,12 @@ class ValidatedCommandsProxy:
|
|
|
47
48
|
return cast(Dict[str, Any], await self._client.call_validated(command, merged))
|
|
48
49
|
|
|
49
50
|
def __getattr__(self, name: str) -> Any:
|
|
51
|
+
"""Return an async validated-call wrapper for an arbitrary command name."""
|
|
50
52
|
if name.startswith("_"):
|
|
51
53
|
raise AttributeError(name)
|
|
52
54
|
|
|
53
55
|
async def _bound(**kw: Any) -> Dict[str, Any]:
|
|
56
|
+
"""Execute the dynamically selected command with validated keyword params."""
|
|
54
57
|
return cast(Dict[str, Any], await self._client.call_validated(name, kw))
|
|
55
58
|
|
|
56
59
|
_bound.__name__ = name
|
|
@@ -13,12 +13,14 @@ from typing import Any, Mapping
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
def _expand_path(value: Any) -> str | None:
|
|
16
|
+
"""Expand a non-empty filesystem path value to an absolute string."""
|
|
16
17
|
if value is None or value == "":
|
|
17
18
|
return None
|
|
18
19
|
return str(Path(str(value)).expanduser().resolve())
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
def _ssl_paths_from_section(ssl_section: Any) -> dict[str, str]:
|
|
23
|
+
"""Extract and normalize TLS path fields from a config SSL section."""
|
|
22
24
|
if not isinstance(ssl_section, dict):
|
|
23
25
|
return {}
|
|
24
26
|
out: dict[str, str] = {}
|
|
@@ -38,6 +40,7 @@ def _ssl_paths_from_section(ssl_section: Any) -> dict[str, str]:
|
|
|
38
40
|
|
|
39
41
|
|
|
40
42
|
def _network_from_server_config(config: Mapping[str, Any]) -> dict[str, Any]:
|
|
43
|
+
"""Return host, port, and protocol settings from a server config dict."""
|
|
41
44
|
server = config.get("server", {})
|
|
42
45
|
if not isinstance(server, dict):
|
|
43
46
|
server = {}
|
{code_analysis_client-1.0.6 → code_analysis_client-1.6.39}/code_analysis_client/exceptions.py
RENAMED
|
@@ -15,6 +15,7 @@ class ClientValidationError(ValueError):
|
|
|
15
15
|
field: Optional[str] = None,
|
|
16
16
|
details: Optional[Dict[str, Any]] = None,
|
|
17
17
|
) -> None:
|
|
18
|
+
"""Initialize validation error with optional field and details payload."""
|
|
18
19
|
super().__init__(message)
|
|
19
20
|
self.field = field
|
|
20
21
|
self.details = details or {}
|
{code_analysis_client-1.0.6 → code_analysis_client-1.6.39}/code_analysis_client/file_session.py
RENAMED
|
@@ -4,6 +4,21 @@ High-level file transfer and session workflow on top of CodeAnalysisAsyncClient.
|
|
|
4
4
|
Wraps ``session_*`` and ``subordinate_session_*`` MCP commands plus transfer and
|
|
5
5
|
advisory-lock commands that accept ``session_id``.
|
|
6
6
|
|
|
7
|
+
Transfer mapping (client façade → server command):
|
|
8
|
+
|
|
9
|
+
* **Download** — ``download`` → ``project_file_transfer_download_begin`` (``file_id``
|
|
10
|
+
required; optional ``project_id`` scopes the lookup). Chunk streaming uses the
|
|
11
|
+
adapter ``download_file`` helper via ``download_to_path``.
|
|
12
|
+
* **Upload** — two façade methods share one save command
|
|
13
|
+
``project_file_transfer_upload_save``:
|
|
14
|
+
|
|
15
|
+
- ``upload`` — update mode: ``file_id`` only (overwrite an indexed file).
|
|
16
|
+
- ``upload_new`` — create mode: ``project_id`` + ``file_path`` (path must not
|
|
17
|
+
be indexed yet).
|
|
18
|
+
|
|
19
|
+
Both methods upload bytes through the adapter, then call the same save command
|
|
20
|
+
with the completed ``transfer_id``.
|
|
21
|
+
|
|
7
22
|
Author: Vasiliy Zdanovskiy
|
|
8
23
|
email: vasilyvz@gmail.com
|
|
9
24
|
"""
|
|
@@ -25,6 +40,7 @@ class SessionNotFoundError(ClientValidationError):
|
|
|
25
40
|
|
|
26
41
|
|
|
27
42
|
def _unwrap(data: Dict[str, Any]) -> Dict[str, Any]:
|
|
43
|
+
"""Unwrap command result payloads and map missing sessions to SessionNotFoundError."""
|
|
28
44
|
return unwrap_command_result(
|
|
29
45
|
data,
|
|
30
46
|
session_not_found_type=SessionNotFoundError,
|
|
@@ -32,6 +48,7 @@ def _unwrap(data: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
32
48
|
|
|
33
49
|
|
|
34
50
|
def _require_non_empty(value: str, *, field: str) -> str:
|
|
51
|
+
"""Return stripped text or raise ClientValidationError for an empty field."""
|
|
35
52
|
text = str(value or "").strip()
|
|
36
53
|
if not text:
|
|
37
54
|
raise ClientValidationError(f"{field} is required", field=field)
|
|
@@ -39,13 +56,43 @@ def _require_non_empty(value: str, *, field: str) -> str:
|
|
|
39
56
|
|
|
40
57
|
|
|
41
58
|
def _validate_download_target(*, file_id: Optional[str]) -> None:
|
|
42
|
-
"""Download requires ``file_id
|
|
59
|
+
"""Download requires ``file_id`` (server rejects ``file_path``)."""
|
|
43
60
|
fid = str(file_id or "").strip()
|
|
44
61
|
if not fid:
|
|
45
62
|
raise ClientValidationError("file_id is required", field="file_id")
|
|
46
63
|
|
|
47
64
|
|
|
65
|
+
def _validate_upload_selector(
|
|
66
|
+
*,
|
|
67
|
+
file_id: Optional[str] = None,
|
|
68
|
+
file_path: Optional[str] = None,
|
|
69
|
+
project_id: Optional[str] = None,
|
|
70
|
+
) -> None:
|
|
71
|
+
"""Mirror server ``_validate_upload_selector_params`` for save payloads."""
|
|
72
|
+
fid = str(file_id or "").strip()
|
|
73
|
+
fp = str(file_path or "").strip()
|
|
74
|
+
pid = str(project_id or "").strip()
|
|
75
|
+
if fid and fp:
|
|
76
|
+
raise ClientValidationError(
|
|
77
|
+
"Specify exactly one of file_id or file_path, not both",
|
|
78
|
+
field="file_id",
|
|
79
|
+
)
|
|
80
|
+
if fid:
|
|
81
|
+
return
|
|
82
|
+
if not pid:
|
|
83
|
+
raise ClientValidationError(
|
|
84
|
+
"project_id is required when file_id is omitted",
|
|
85
|
+
field="project_id",
|
|
86
|
+
)
|
|
87
|
+
if not fp:
|
|
88
|
+
raise ClientValidationError(
|
|
89
|
+
"file_path is required when file_id is omitted",
|
|
90
|
+
field="file_path",
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
48
94
|
def _lock_mode_from_flag(lock: bool) -> str:
|
|
95
|
+
"""Convert a boolean lock flag into the server lock_mode value."""
|
|
49
96
|
return "full" if lock else "none"
|
|
50
97
|
|
|
51
98
|
|
|
@@ -67,6 +114,7 @@ class FileSessionClient:
|
|
|
67
114
|
__slots__ = ("_client",)
|
|
68
115
|
|
|
69
116
|
def __init__(self, client: CodeAnalysisAsyncClient) -> None:
|
|
117
|
+
"""Store the underlying async client used to call session commands."""
|
|
70
118
|
self._client = client
|
|
71
119
|
|
|
72
120
|
async def create_session(
|
|
@@ -89,17 +137,19 @@ class FileSessionClient:
|
|
|
89
137
|
)
|
|
90
138
|
return sid
|
|
91
139
|
|
|
140
|
+
async def validate_session(
|
|
141
|
+
self, session_id: str, *, touch: bool = False
|
|
142
|
+
) -> Dict[str, Any]:
|
|
143
|
+
"""Confirm ``session_id`` exists (``session_validate``)."""
|
|
144
|
+
sid = _require_non_empty(session_id, field="session_id")
|
|
145
|
+
params: Dict[str, Any] = {"session_id": sid}
|
|
146
|
+
if touch:
|
|
147
|
+
params["touch"] = True
|
|
148
|
+
return _unwrap(await self._client.call_validated("session_validate", params))
|
|
149
|
+
|
|
92
150
|
async def assert_session_exists(self, session_id: str) -> None:
|
|
93
|
-
"""Verify ``session_id`` is registered (
|
|
94
|
-
|
|
95
|
-
if not sid:
|
|
96
|
-
raise ClientValidationError("session_id is required", field="session_id")
|
|
97
|
-
_unwrap(
|
|
98
|
-
await self._client.call_validated(
|
|
99
|
-
"session_list_file_locks",
|
|
100
|
-
{"session_id": sid},
|
|
101
|
-
)
|
|
102
|
-
)
|
|
151
|
+
"""Verify ``session_id`` is registered (``session_validate``)."""
|
|
152
|
+
await self.validate_session(session_id, touch=False)
|
|
103
153
|
|
|
104
154
|
async def delete_session(
|
|
105
155
|
self, session_id: str, *, force: bool = False
|
|
@@ -107,12 +157,14 @@ class FileSessionClient:
|
|
|
107
157
|
"""Delete a client session (``session_delete``).
|
|
108
158
|
|
|
109
159
|
``force`` defaults to false (omit on the wire when false). Safe delete
|
|
110
|
-
requires no ``session_file_locks
|
|
111
|
-
where this session is ``parent_session_id
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
160
|
+
requires no ``session_file_locks``, no ``subordinate_sessions`` rows
|
|
161
|
+
where this session is ``parent_session_id``, and no advisory file-lock
|
|
162
|
+
leases. When ``force`` is true, subordinate link rows, DB file locks, and
|
|
163
|
+
advisory leases for this session are released before the session row is
|
|
164
|
+
deleted.
|
|
165
|
+
|
|
166
|
+
Returns ``session_id``, ``deleted``, ``released_lock_count``,
|
|
167
|
+
``released_subordinate_count``, and ``released_advisory_lease_count``.
|
|
116
168
|
"""
|
|
117
169
|
params: Dict[str, Any] = {"session_id": session_id}
|
|
118
170
|
if force:
|
|
@@ -129,8 +181,8 @@ class FileSessionClient:
|
|
|
129
181
|
|
|
130
182
|
Returns ``locked_files_by_project`` (file_id, file_path,
|
|
131
183
|
project_presentation per project) and ``subordinate_sessions``
|
|
132
|
-
(
|
|
133
|
-
|
|
184
|
+
(session_id, server_uuid, session_presentation, server_presentation,
|
|
185
|
+
link_comment).
|
|
134
186
|
"""
|
|
135
187
|
sid = _require_non_empty(session_id, field="session_id")
|
|
136
188
|
return _unwrap(
|
|
@@ -143,13 +195,13 @@ class FileSessionClient:
|
|
|
143
195
|
async def create_subordinate_session(
|
|
144
196
|
self,
|
|
145
197
|
parent_session_id: str,
|
|
146
|
-
subordinate_session_id: str,
|
|
147
198
|
comment: str,
|
|
148
199
|
*,
|
|
149
200
|
server_uuid: Optional[str] = None,
|
|
150
201
|
) -> Dict[str, Any]:
|
|
151
|
-
"""
|
|
202
|
+
"""Register leading session on a subordinate server (``subordinate_session_create``).
|
|
152
203
|
|
|
204
|
+
Subordinate servers use ``parent_session_id`` as ``session_id``.
|
|
153
205
|
``server_uuid`` defaults to the server's ``registration.instance_uuid``
|
|
154
206
|
when omitted.
|
|
155
207
|
"""
|
|
@@ -157,9 +209,6 @@ class FileSessionClient:
|
|
|
157
209
|
"parent_session_id": _require_non_empty(
|
|
158
210
|
parent_session_id, field="parent_session_id"
|
|
159
211
|
),
|
|
160
|
-
"subordinate_session_id": _require_non_empty(
|
|
161
|
-
subordinate_session_id, field="subordinate_session_id"
|
|
162
|
-
),
|
|
163
212
|
"comment": comment,
|
|
164
213
|
}
|
|
165
214
|
if server_uuid is not None:
|
|
@@ -171,10 +220,9 @@ class FileSessionClient:
|
|
|
171
220
|
async def get_subordinate_session(
|
|
172
221
|
self,
|
|
173
222
|
parent_session_id: str,
|
|
174
|
-
subordinate_session_id: str,
|
|
175
223
|
server_uuid: str,
|
|
176
224
|
) -> Dict[str, Any]:
|
|
177
|
-
"""Fetch one subordinate link (``subordinate_session_get``)."""
|
|
225
|
+
"""Fetch one subordinate server link (``subordinate_session_get``)."""
|
|
178
226
|
return _unwrap(
|
|
179
227
|
await self._client.call_validated(
|
|
180
228
|
"subordinate_session_get",
|
|
@@ -182,9 +230,6 @@ class FileSessionClient:
|
|
|
182
230
|
"parent_session_id": _require_non_empty(
|
|
183
231
|
parent_session_id, field="parent_session_id"
|
|
184
232
|
),
|
|
185
|
-
"subordinate_session_id": _require_non_empty(
|
|
186
|
-
subordinate_session_id, field="subordinate_session_id"
|
|
187
|
-
),
|
|
188
233
|
"server_uuid": _require_non_empty(server_uuid, field="server_uuid"),
|
|
189
234
|
},
|
|
190
235
|
)
|
|
@@ -193,7 +238,6 @@ class FileSessionClient:
|
|
|
193
238
|
async def update_subordinate_session(
|
|
194
239
|
self,
|
|
195
240
|
parent_session_id: str,
|
|
196
|
-
subordinate_session_id: str,
|
|
197
241
|
server_uuid: str,
|
|
198
242
|
comment: str,
|
|
199
243
|
) -> Dict[str, Any]:
|
|
@@ -205,9 +249,6 @@ class FileSessionClient:
|
|
|
205
249
|
"parent_session_id": _require_non_empty(
|
|
206
250
|
parent_session_id, field="parent_session_id"
|
|
207
251
|
),
|
|
208
|
-
"subordinate_session_id": _require_non_empty(
|
|
209
|
-
subordinate_session_id, field="subordinate_session_id"
|
|
210
|
-
),
|
|
211
252
|
"server_uuid": _require_non_empty(server_uuid, field="server_uuid"),
|
|
212
253
|
"comment": comment,
|
|
213
254
|
},
|
|
@@ -217,12 +258,11 @@ class FileSessionClient:
|
|
|
217
258
|
async def delete_subordinate_session(
|
|
218
259
|
self,
|
|
219
260
|
parent_session_id: str,
|
|
220
|
-
subordinate_session_id: str,
|
|
221
261
|
server_uuid: str,
|
|
222
262
|
) -> Dict[str, Any]:
|
|
223
|
-
"""Remove subordinate link
|
|
263
|
+
"""Remove subordinate server link (``subordinate_session_delete``).
|
|
224
264
|
|
|
225
|
-
Does not delete ``client_sessions``
|
|
265
|
+
Does not delete the leading ``client_sessions`` row; use :meth:`delete_session`.
|
|
226
266
|
"""
|
|
227
267
|
return _unwrap(
|
|
228
268
|
await self._client.call_validated(
|
|
@@ -231,9 +271,6 @@ class FileSessionClient:
|
|
|
231
271
|
"parent_session_id": _require_non_empty(
|
|
232
272
|
parent_session_id, field="parent_session_id"
|
|
233
273
|
),
|
|
234
|
-
"subordinate_session_id": _require_non_empty(
|
|
235
|
-
subordinate_session_id, field="subordinate_session_id"
|
|
236
|
-
),
|
|
237
274
|
"server_uuid": _require_non_empty(server_uuid, field="server_uuid"),
|
|
238
275
|
},
|
|
239
276
|
)
|
|
@@ -243,18 +280,15 @@ class FileSessionClient:
|
|
|
243
280
|
self,
|
|
244
281
|
*,
|
|
245
282
|
parent_session_id: Optional[str] = None,
|
|
246
|
-
subordinate_session_id: Optional[str] = None,
|
|
247
283
|
server_uuid: Optional[str] = None,
|
|
248
284
|
) -> Dict[str, Any]:
|
|
249
|
-
"""List subordinate links (``subordinate_session_list``).
|
|
285
|
+
"""List subordinate server links (``subordinate_session_list``).
|
|
250
286
|
|
|
251
287
|
Returns ``links`` and ``count``. All filter parameters are optional.
|
|
252
288
|
"""
|
|
253
289
|
params: Dict[str, Any] = {}
|
|
254
290
|
if parent_session_id is not None:
|
|
255
291
|
params["parent_session_id"] = parent_session_id
|
|
256
|
-
if subordinate_session_id is not None:
|
|
257
|
-
params["subordinate_session_id"] = subordinate_session_id
|
|
258
292
|
if server_uuid is not None:
|
|
259
293
|
params["server_uuid"] = server_uuid
|
|
260
294
|
return _unwrap(
|
|
@@ -391,9 +425,10 @@ class FileSessionClient:
|
|
|
391
425
|
compression: str = "identity",
|
|
392
426
|
lock: bool = True,
|
|
393
427
|
chunk_size: Optional[int] = None,
|
|
394
|
-
include_backup_history: bool =
|
|
428
|
+
include_backup_history: bool = True,
|
|
429
|
+
project_id: Optional[str] = None,
|
|
395
430
|
) -> Dict[str, Any]:
|
|
396
|
-
"""
|
|
431
|
+
"""Internal: call ``project_file_transfer_download_begin``."""
|
|
397
432
|
fid = _require_non_empty(file_id, field="file_id")
|
|
398
433
|
_validate_download_target(file_id=fid)
|
|
399
434
|
await self.assert_session_exists(session_id)
|
|
@@ -406,6 +441,8 @@ class FileSessionClient:
|
|
|
406
441
|
}
|
|
407
442
|
if chunk_size is not None:
|
|
408
443
|
params["chunk_size"] = chunk_size
|
|
444
|
+
if project_id is not None:
|
|
445
|
+
params["project_id"] = _require_non_empty(project_id, field="project_id")
|
|
409
446
|
payload = _unwrap(
|
|
410
447
|
await self._client.call_validated(
|
|
411
448
|
"project_file_transfer_download_begin",
|
|
@@ -423,9 +460,14 @@ class FileSessionClient:
|
|
|
423
460
|
*,
|
|
424
461
|
compression: str = "identity",
|
|
425
462
|
lock: bool = True,
|
|
426
|
-
include_backup_history: bool =
|
|
463
|
+
include_backup_history: bool = True,
|
|
464
|
+
project_id: Optional[str] = None,
|
|
427
465
|
) -> Tuple[Dict[str, Any], Any]:
|
|
428
|
-
"""Download an indexed file by ``file_id
|
|
466
|
+
"""Download an indexed file by ``file_id`` (``project_file_transfer_download_begin``).
|
|
467
|
+
|
|
468
|
+
Returns ``(begin_payload, adapter_receipt)``. ``file_path`` is not accepted —
|
|
469
|
+
resolve ``file_id`` from ``list_project_files`` or ``session_view`` first.
|
|
470
|
+
"""
|
|
429
471
|
fid = _require_non_empty(file_id, field="file_id")
|
|
430
472
|
begin = await self._begin_download(
|
|
431
473
|
session_id,
|
|
@@ -433,6 +475,7 @@ class FileSessionClient:
|
|
|
433
475
|
compression=compression,
|
|
434
476
|
lock=lock,
|
|
435
477
|
include_backup_history=include_backup_history,
|
|
478
|
+
project_id=project_id,
|
|
436
479
|
)
|
|
437
480
|
transfer_id = str(begin.get("transfer_id") or "").strip()
|
|
438
481
|
if not transfer_id:
|
|
@@ -486,8 +529,19 @@ class FileSessionClient:
|
|
|
486
529
|
unlock_after_write: bool = True,
|
|
487
530
|
dry_run: bool = False,
|
|
488
531
|
backup: bool = True,
|
|
532
|
+
diff: bool = False,
|
|
533
|
+
diff_context_lines: Optional[int] = None,
|
|
534
|
+
commit_message: Optional[str] = None,
|
|
535
|
+
validate_syntax_only: bool = False,
|
|
536
|
+
tree_id: Optional[str] = None,
|
|
537
|
+
lock_mode: Optional[str] = None,
|
|
489
538
|
) -> Dict[str, Any]:
|
|
490
539
|
"""Persist a completed adapter upload (``project_file_transfer_upload_save``)."""
|
|
540
|
+
_validate_upload_selector(
|
|
541
|
+
file_id=file_id,
|
|
542
|
+
file_path=file_path,
|
|
543
|
+
project_id=project_id,
|
|
544
|
+
)
|
|
491
545
|
await self.assert_session_exists(session_id)
|
|
492
546
|
params: Dict[str, Any] = {
|
|
493
547
|
"session_id": session_id,
|
|
@@ -502,6 +556,18 @@ class FileSessionClient:
|
|
|
502
556
|
params["file_id"] = file_id
|
|
503
557
|
if file_path is not None:
|
|
504
558
|
params["file_path"] = file_path
|
|
559
|
+
if diff:
|
|
560
|
+
params["diff"] = True
|
|
561
|
+
if diff_context_lines is not None:
|
|
562
|
+
params["diff_context_lines"] = diff_context_lines
|
|
563
|
+
if commit_message is not None:
|
|
564
|
+
params["commit_message"] = commit_message
|
|
565
|
+
if validate_syntax_only:
|
|
566
|
+
params["validate_syntax_only"] = True
|
|
567
|
+
if tree_id is not None:
|
|
568
|
+
params["tree_id"] = tree_id
|
|
569
|
+
if lock_mode is not None:
|
|
570
|
+
params["lock_mode"] = lock_mode
|
|
505
571
|
return _unwrap(
|
|
506
572
|
await self._client.call_validated(
|
|
507
573
|
"project_file_transfer_upload_save",
|
|
@@ -521,11 +587,18 @@ class FileSessionClient:
|
|
|
521
587
|
unlock: bool = True,
|
|
522
588
|
dry_run: bool = False,
|
|
523
589
|
backup: bool = True,
|
|
590
|
+
diff: bool = False,
|
|
591
|
+
diff_context_lines: Optional[int] = None,
|
|
592
|
+
commit_message: Optional[str] = None,
|
|
593
|
+
validate_syntax_only: bool = False,
|
|
594
|
+
tree_id: Optional[str] = None,
|
|
595
|
+
lock_mode: Optional[str] = None,
|
|
524
596
|
) -> str:
|
|
525
|
-
"""Create a new project file
|
|
597
|
+
"""Create a new project file (``project_file_transfer_upload_save`` create mode).
|
|
526
598
|
|
|
527
|
-
``project_id`` and project-relative ``file_path``
|
|
528
|
-
not already be indexed
|
|
599
|
+
Pass ``project_id`` and project-relative ``file_path`` only — do not pass
|
|
600
|
+
``file_id``. The path must not already be indexed (``FILE_ALREADY_INDEXED``
|
|
601
|
+
otherwise). Returns the new ``file_id`` (may be empty on ``dry_run``).
|
|
529
602
|
"""
|
|
530
603
|
pid = _require_non_empty(project_id, field="project_id")
|
|
531
604
|
fp = _require_non_empty(file_path, field="file_path")
|
|
@@ -549,6 +622,12 @@ class FileSessionClient:
|
|
|
549
622
|
unlock_after_write=unlock,
|
|
550
623
|
backup=backup,
|
|
551
624
|
dry_run=dry_run,
|
|
625
|
+
diff=diff,
|
|
626
|
+
diff_context_lines=diff_context_lines,
|
|
627
|
+
commit_message=commit_message,
|
|
628
|
+
validate_syntax_only=validate_syntax_only,
|
|
629
|
+
tree_id=tree_id,
|
|
630
|
+
lock_mode=lock_mode,
|
|
552
631
|
)
|
|
553
632
|
if dry_run:
|
|
554
633
|
fid = str(saved.get("file_id") or "").strip()
|
|
@@ -561,16 +640,23 @@ class FileSessionClient:
|
|
|
561
640
|
payload: bytes,
|
|
562
641
|
file_id: str,
|
|
563
642
|
*,
|
|
643
|
+
project_id: Optional[str] = None,
|
|
564
644
|
filename: Optional[str] = None,
|
|
565
645
|
compression: str = "identity",
|
|
566
646
|
unlock: bool = True,
|
|
567
647
|
backup: bool = True,
|
|
568
648
|
dry_run: bool = False,
|
|
649
|
+
diff: bool = False,
|
|
650
|
+
diff_context_lines: Optional[int] = None,
|
|
651
|
+
commit_message: Optional[str] = None,
|
|
652
|
+
validate_syntax_only: bool = False,
|
|
653
|
+
tree_id: Optional[str] = None,
|
|
654
|
+
lock_mode: Optional[str] = None,
|
|
569
655
|
) -> Dict[str, Any]:
|
|
570
|
-
"""Overwrite an existing indexed file
|
|
656
|
+
"""Overwrite an existing indexed file (``project_file_transfer_upload_save`` update mode).
|
|
571
657
|
|
|
572
|
-
|
|
573
|
-
|
|
658
|
+
Pass ``file_id`` only. Optional ``project_id`` must match the row if provided.
|
|
659
|
+
Do not pass ``file_path``. Returns the server save payload.
|
|
574
660
|
"""
|
|
575
661
|
fid = _require_non_empty(file_id, field="file_id")
|
|
576
662
|
name = filename or "payload.bin"
|
|
@@ -589,9 +675,16 @@ class FileSessionClient:
|
|
|
589
675
|
session_id,
|
|
590
676
|
str(receipt.transfer_id),
|
|
591
677
|
file_id=fid,
|
|
678
|
+
project_id=project_id,
|
|
592
679
|
unlock_after_write=unlock,
|
|
593
680
|
backup=backup,
|
|
594
681
|
dry_run=dry_run,
|
|
682
|
+
diff=diff,
|
|
683
|
+
diff_context_lines=diff_context_lines,
|
|
684
|
+
commit_message=commit_message,
|
|
685
|
+
validate_syntax_only=validate_syntax_only,
|
|
686
|
+
tree_id=tree_id,
|
|
687
|
+
lock_mode=lock_mode,
|
|
595
688
|
)
|
|
596
689
|
if not dry_run:
|
|
597
690
|
_extract_file_id(saved)
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Canonical command names for the high-level client API vs the live server registry.
|
|
3
|
+
|
|
4
|
+
**code-analysis-server** owns project trees, the index database, sessions, locks,
|
|
5
|
+
and editor ingress (chunked transfer). It does **not** expose in-server content
|
|
6
|
+
editing (``universal_file_open/edit/write/close``, CST/JSON tree modify/save,
|
|
7
|
+
``format_code``, legacy line writers).
|
|
8
|
+
|
|
9
|
+
**File content on CA** — read-only surface only:
|
|
10
|
+
|
|
11
|
+
- :data:`FILE_CONTENT_READ_COMMANDS` — preview and project search (``search`` /
|
|
12
|
+
``search_get_page``); on-disk grep is internal to ``search`` when enabled.
|
|
13
|
+
- :data:`FS_COMMANDS` — filesystem operations (copy/move/remove/list/grep); not
|
|
14
|
+
the structured edit workflow.
|
|
15
|
+
|
|
16
|
+
Editor clients use :data:`CLIENT_FACADE_COMMANDS` (sessions, transfer, locks,
|
|
17
|
+
``universal_file_preview``).
|
|
18
|
+
|
|
19
|
+
Author: Vasiliy Zdanovskiy
|
|
20
|
+
email: vasilyvz@gmail.com
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
from typing import Callable, Dict, FrozenSet, Tuple
|
|
26
|
+
|
|
27
|
+
# Removed from the public server surface — do not use in client facades or docs.
|
|
28
|
+
LEGACY_REMOVED_COMMANDS: FrozenSet[str] = frozenset(
|
|
29
|
+
{
|
|
30
|
+
"universal_file_read",
|
|
31
|
+
"universal_file_save",
|
|
32
|
+
"universal_file_replace",
|
|
33
|
+
"universal_file_delete",
|
|
34
|
+
"read_project_text_file",
|
|
35
|
+
"write_project_text_lines",
|
|
36
|
+
"create_text_file",
|
|
37
|
+
"replace_file_lines",
|
|
38
|
+
"universal_file_search",
|
|
39
|
+
}
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# CST / JSON tree MCP workflow — modules may exist in the repo but are not registered.
|
|
43
|
+
CST_REMOVED_COMMANDS: FrozenSet[str] = frozenset(
|
|
44
|
+
{
|
|
45
|
+
"cst_load_file",
|
|
46
|
+
"cst_save_tree",
|
|
47
|
+
"cst_modify_tree",
|
|
48
|
+
"cst_create_file",
|
|
49
|
+
"cst_find_node",
|
|
50
|
+
"cst_get_node_info",
|
|
51
|
+
"cst_get_node_by_range",
|
|
52
|
+
"cst_get_node_at_line",
|
|
53
|
+
"cst_reload_tree",
|
|
54
|
+
"cst_convert_and_save",
|
|
55
|
+
"cst_apply_buffer",
|
|
56
|
+
"cst_list_trees",
|
|
57
|
+
"cst_unload_tree",
|
|
58
|
+
"list_cst_blocks",
|
|
59
|
+
"query_cst",
|
|
60
|
+
"json_load_file",
|
|
61
|
+
"json_find_node",
|
|
62
|
+
"json_get_node_info",
|
|
63
|
+
"json_modify_tree",
|
|
64
|
+
"json_save_tree",
|
|
65
|
+
"json_reload_tree",
|
|
66
|
+
}
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Content editing on ai-editor-server only (not on code-analysis-server).
|
|
70
|
+
EDITING_REMOVED_COMMANDS: FrozenSet[str] = frozenset(
|
|
71
|
+
{
|
|
72
|
+
"universal_file_open",
|
|
73
|
+
"universal_file_edit",
|
|
74
|
+
"universal_file_write",
|
|
75
|
+
"universal_file_close",
|
|
76
|
+
"universal_file_move_nodes",
|
|
77
|
+
"session_git_log",
|
|
78
|
+
"session_git_diff",
|
|
79
|
+
"session_git_show",
|
|
80
|
+
"session_git_status",
|
|
81
|
+
"session_git_revert",
|
|
82
|
+
"session_undo",
|
|
83
|
+
"session_redo",
|
|
84
|
+
"session_write",
|
|
85
|
+
"delete_file",
|
|
86
|
+
"delete_files_by_mask",
|
|
87
|
+
"restore_deleted_files",
|
|
88
|
+
"unmark_deleted_file",
|
|
89
|
+
"cleanup_deleted_files",
|
|
90
|
+
"collapse_versions",
|
|
91
|
+
"restore_backup_file",
|
|
92
|
+
"delete_backup",
|
|
93
|
+
"clear_all_backups",
|
|
94
|
+
"split_class",
|
|
95
|
+
"extract_superclass",
|
|
96
|
+
"split_file_to_package",
|
|
97
|
+
"format_code",
|
|
98
|
+
}
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
REMOVED_COMMANDS: FrozenSet[str] = (
|
|
102
|
+
LEGACY_REMOVED_COMMANDS | CST_REMOVED_COMMANDS | EDITING_REMOVED_COMMANDS
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# Read-only file **content** on CA (no mutate/edit workflow). Includes on-disk search
|
|
106
|
+
# (``fs_grep``, paginated ``search_*``) for paths not yet in the index.
|
|
107
|
+
FILE_CONTENT_READ_COMMANDS: FrozenSet[str] = frozenset(
|
|
108
|
+
{
|
|
109
|
+
"universal_file_preview",
|
|
110
|
+
"get_file_lines",
|
|
111
|
+
"search",
|
|
112
|
+
"search_get_page",
|
|
113
|
+
"search_get_status",
|
|
114
|
+
"search_cancel",
|
|
115
|
+
"search_close",
|
|
116
|
+
}
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# Filesystem helpers — separate from structured content editing; may touch paths/bytes
|
|
120
|
+
# via copy/move/remove but are not universal_file_edit / CST / format_code.
|
|
121
|
+
FS_COMMANDS: FrozenSet[str] = frozenset(
|
|
122
|
+
{
|
|
123
|
+
"fs_copy",
|
|
124
|
+
"fs_move",
|
|
125
|
+
"fs_remove",
|
|
126
|
+
}
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
# Client sessions + DB file locks — persisted on this server for editor/terminal clients.
|
|
130
|
+
FILE_SESSION_COMMANDS: FrozenSet[str] = frozenset(
|
|
131
|
+
{
|
|
132
|
+
"session_create",
|
|
133
|
+
"session_validate",
|
|
134
|
+
"session_delete",
|
|
135
|
+
"session_list",
|
|
136
|
+
"session_view",
|
|
137
|
+
"session_open_file",
|
|
138
|
+
"session_close_file",
|
|
139
|
+
"session_list_file_locks",
|
|
140
|
+
"subordinate_session_create",
|
|
141
|
+
"subordinate_session_get",
|
|
142
|
+
"subordinate_session_update",
|
|
143
|
+
"subordinate_session_delete",
|
|
144
|
+
"subordinate_session_list",
|
|
145
|
+
}
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
TRANSFER_AND_LOCK_COMMANDS: FrozenSet[str] = frozenset(
|
|
149
|
+
{
|
|
150
|
+
"project_file_transfer_download_begin",
|
|
151
|
+
"project_file_transfer_upload_save",
|
|
152
|
+
"project_file_advisory_lock_batch",
|
|
153
|
+
}
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
UNIVERSAL_FILE_COMMANDS: FrozenSet[str] = frozenset(
|
|
157
|
+
{
|
|
158
|
+
"universal_file_preview",
|
|
159
|
+
}
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
CLIENT_FACADE_COMMANDS: FrozenSet[str] = (
|
|
163
|
+
FILE_SESSION_COMMANDS | TRANSFER_AND_LOCK_COMMANDS | UNIVERSAL_FILE_COMMANDS
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# ``FileSessionClient`` method names for each ``FILE_SESSION_COMMANDS`` entry.
|
|
167
|
+
# ``session_list_file_locks`` is also used by ``assert_session_exists``.
|
|
168
|
+
FILE_SESSION_FACADE_METHODS: Dict[str, str] = {
|
|
169
|
+
"session_create": "create_session",
|
|
170
|
+
"session_validate": "validate_session",
|
|
171
|
+
"session_delete": "delete_session",
|
|
172
|
+
"session_list": "list_sessions",
|
|
173
|
+
"session_view": "view_session",
|
|
174
|
+
"session_open_file": "lock_file",
|
|
175
|
+
"session_close_file": "unlock_file",
|
|
176
|
+
"session_list_file_locks": "list_file_locks",
|
|
177
|
+
"subordinate_session_create": "create_subordinate_session",
|
|
178
|
+
"subordinate_session_get": "get_subordinate_session",
|
|
179
|
+
"subordinate_session_update": "update_subordinate_session",
|
|
180
|
+
"subordinate_session_delete": "delete_subordinate_session",
|
|
181
|
+
"subordinate_session_list": "list_subordinate_sessions",
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
# Transfer / advisory-lock commands may map to more than one façade method.
|
|
185
|
+
TRANSFER_FACADE_METHODS: Dict[str, Tuple[str, ...]] = {
|
|
186
|
+
"project_file_transfer_download_begin": ("download",),
|
|
187
|
+
"project_file_transfer_upload_save": ("upload", "upload_new"),
|
|
188
|
+
"project_file_advisory_lock_batch": (
|
|
189
|
+
"lock_files_advisory",
|
|
190
|
+
"unlock_files_advisory",
|
|
191
|
+
),
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def assert_file_session_facade_complete() -> None:
|
|
196
|
+
"""Raise ``AssertionError`` if ``FILE_SESSION_FACADE_METHODS`` is incomplete."""
|
|
197
|
+
missing_cmds = FILE_SESSION_COMMANDS - set(FILE_SESSION_FACADE_METHODS)
|
|
198
|
+
extra_cmds = set(FILE_SESSION_FACADE_METHODS) - FILE_SESSION_COMMANDS
|
|
199
|
+
if missing_cmds or extra_cmds:
|
|
200
|
+
raise AssertionError(
|
|
201
|
+
"FILE_SESSION_FACADE_METHODS out of sync with FILE_SESSION_COMMANDS: "
|
|
202
|
+
f"missing={sorted(missing_cmds)!r} extra={sorted(extra_cmds)!r}"
|
|
203
|
+
)
|
|
204
|
+
from code_analysis_client.file_session import FileSessionClient
|
|
205
|
+
|
|
206
|
+
for command, method_name in FILE_SESSION_FACADE_METHODS.items():
|
|
207
|
+
if not hasattr(FileSessionClient, method_name):
|
|
208
|
+
raise AssertionError(
|
|
209
|
+
f"FileSessionClient missing method {method_name!r} for command {command!r}"
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def assert_transfer_facade_complete() -> None:
|
|
214
|
+
"""Raise ``AssertionError`` if ``TRANSFER_FACADE_METHODS`` is incomplete."""
|
|
215
|
+
missing_cmds = TRANSFER_AND_LOCK_COMMANDS - set(TRANSFER_FACADE_METHODS)
|
|
216
|
+
extra_cmds = set(TRANSFER_FACADE_METHODS) - TRANSFER_AND_LOCK_COMMANDS
|
|
217
|
+
if missing_cmds or extra_cmds:
|
|
218
|
+
raise AssertionError(
|
|
219
|
+
"TRANSFER_FACADE_METHODS out of sync with TRANSFER_AND_LOCK_COMMANDS: "
|
|
220
|
+
f"missing={sorted(missing_cmds)!r} extra={sorted(extra_cmds)!r}"
|
|
221
|
+
)
|
|
222
|
+
from code_analysis_client.file_session import FileSessionClient
|
|
223
|
+
|
|
224
|
+
for command, method_names in TRANSFER_FACADE_METHODS.items():
|
|
225
|
+
for method_name in method_names:
|
|
226
|
+
if not hasattr(FileSessionClient, method_name):
|
|
227
|
+
raise AssertionError(
|
|
228
|
+
f"FileSessionClient missing method {method_name!r} "
|
|
229
|
+
f"for command {command!r}"
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def _command_registered(get_command: Callable[[str], object], name: str) -> bool:
|
|
234
|
+
"""Return True when ``get_command`` can resolve a command name."""
|
|
235
|
+
try:
|
|
236
|
+
get_command(name)
|
|
237
|
+
return True
|
|
238
|
+
except KeyError:
|
|
239
|
+
return False
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def assert_facade_commands_registered(get_command: Callable[[str], object]) -> None:
|
|
243
|
+
"""Raise ``KeyError`` if any facade command is missing from the server registry."""
|
|
244
|
+
missing = [
|
|
245
|
+
name
|
|
246
|
+
for name in sorted(CLIENT_FACADE_COMMANDS)
|
|
247
|
+
if not _command_registered(get_command, name)
|
|
248
|
+
]
|
|
249
|
+
if missing:
|
|
250
|
+
raise KeyError(f"Client facade commands not registered on server: {missing}")
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def assert_removed_commands_absent(get_command: Callable[[str], object]) -> None:
|
|
254
|
+
"""Raise ``AssertionError`` if a removed command is still registered."""
|
|
255
|
+
present = [
|
|
256
|
+
name
|
|
257
|
+
for name in sorted(REMOVED_COMMANDS)
|
|
258
|
+
if _command_registered(get_command, name)
|
|
259
|
+
]
|
|
260
|
+
if present:
|
|
261
|
+
raise AssertionError(f"Removed commands still registered: {present}")
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def assert_file_content_read_commands_registered(
|
|
265
|
+
get_command: Callable[[str], object],
|
|
266
|
+
) -> None:
|
|
267
|
+
"""Raise ``KeyError`` if a read-only content command is missing from the registry."""
|
|
268
|
+
missing = [
|
|
269
|
+
name
|
|
270
|
+
for name in sorted(FILE_CONTENT_READ_COMMANDS)
|
|
271
|
+
if not _command_registered(get_command, name)
|
|
272
|
+
]
|
|
273
|
+
if missing:
|
|
274
|
+
raise KeyError(f"File content read commands not registered: {missing}")
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def assert_fs_commands_registered(get_command: Callable[[str], object]) -> None:
|
|
278
|
+
"""Raise ``KeyError`` if a filesystem helper command is missing from the registry."""
|
|
279
|
+
missing = [
|
|
280
|
+
name
|
|
281
|
+
for name in sorted(FS_COMMANDS)
|
|
282
|
+
if not _command_registered(get_command, name)
|
|
283
|
+
]
|
|
284
|
+
if missing:
|
|
285
|
+
raise KeyError(f"Filesystem commands not registered: {missing}")
|
{code_analysis_client-1.0.6 → code_analysis_client-1.6.39}/code_analysis_client/universal_file.py
RENAMED
|
@@ -20,11 +20,16 @@ if TYPE_CHECKING:
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
class UniversalFileClient:
|
|
23
|
-
"""Edit-session workflow for project files (open → edit → write → close).
|
|
23
|
+
"""Edit-session workflow for project files (open → edit → write → close).
|
|
24
|
+
|
|
25
|
+
Uses ``session_id`` from ``universal_file_open`` only. Not related to
|
|
26
|
+
client ``session_*`` commands (``session_create``, ``session_open_file``, …).
|
|
27
|
+
"""
|
|
24
28
|
|
|
25
29
|
__slots__ = ("_client",)
|
|
26
30
|
|
|
27
31
|
def __init__(self, client: CodeAnalysisAsyncClient) -> None:
|
|
32
|
+
"""Store the async client used for universal file workflow commands."""
|
|
28
33
|
self._client = client
|
|
29
34
|
|
|
30
35
|
async def open(
|
|
@@ -35,7 +40,10 @@ class UniversalFileClient:
|
|
|
35
40
|
create: bool = False,
|
|
36
41
|
initial_content: Optional[str] = None,
|
|
37
42
|
) -> Dict[str, Any]:
|
|
38
|
-
"""Start an edit session (``universal_file_open``).
|
|
43
|
+
"""Start an edit session (``universal_file_open``).
|
|
44
|
+
|
|
45
|
+
Identifies the file by ``project_id`` + ``file_path`` (not ``file_id``).
|
|
46
|
+
"""
|
|
39
47
|
params: Dict[str, Any] = {
|
|
40
48
|
"project_id": project_id,
|
|
41
49
|
"file_path": file_path,
|
|
@@ -72,7 +80,12 @@ class UniversalFileClient:
|
|
|
72
80
|
*,
|
|
73
81
|
write_mode: str = "commit",
|
|
74
82
|
) -> Dict[str, Any]:
|
|
75
|
-
"""Persist or preview draft (``universal_file_write``).
|
|
83
|
+
"""Persist or preview draft (``universal_file_write``).
|
|
84
|
+
|
|
85
|
+
Default ``write_mode`` is ``commit`` in this client wrapper. The server
|
|
86
|
+
default when the parameter is omitted is ``preview``. Pass
|
|
87
|
+
``write_mode='preview'`` first to inspect the diff, then ``commit``.
|
|
88
|
+
"""
|
|
76
89
|
return unwrap_command_result(
|
|
77
90
|
await self._client.call_validated(
|
|
78
91
|
"universal_file_write",
|
|
@@ -89,7 +102,7 @@ class UniversalFileClient:
|
|
|
89
102
|
project_id: str,
|
|
90
103
|
session_id: str,
|
|
91
104
|
) -> Dict[str, Any]:
|
|
92
|
-
"""End edit session (``
|
|
105
|
+
"""End edit session (``universal_file_open`` session_id, not client ``session_*``)."""
|
|
93
106
|
return unwrap_command_result(
|
|
94
107
|
await self._client.call_validated(
|
|
95
108
|
"universal_file_close",
|
{code_analysis_client-1.0.6 → code_analysis_client-1.6.39}/code_analysis_client/validation.py
RENAMED
|
@@ -13,12 +13,9 @@ from code_analysis_client.exceptions import ClientValidationError
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
def prepare_params_for_schema(
|
|
16
|
-
params: Dict[str, Any], schema: Dict[str, Any]
|
|
16
|
+
params: Dict[str, Any], schema: Dict[str, Any] # noqa: ARG001
|
|
17
17
|
) -> Dict[str, Any]:
|
|
18
|
-
"""
|
|
19
|
-
props = schema.get("properties") or {}
|
|
20
|
-
if not schema.get("additionalProperties", True):
|
|
21
|
-
return {k: v for k, v in params.items() if k in props}
|
|
18
|
+
"""Return a shallow copy of params for validation and dispatch."""
|
|
22
19
|
return dict(params)
|
|
23
20
|
|
|
24
21
|
|
|
@@ -34,7 +31,7 @@ def validate_params_against_schema(
|
|
|
34
31
|
field="params",
|
|
35
32
|
)
|
|
36
33
|
props = schema.get("properties") or {}
|
|
37
|
-
additional_ok = schema.get("additionalProperties",
|
|
34
|
+
additional_ok = schema.get("additionalProperties", False)
|
|
38
35
|
required_set = set(schema.get("required") or [])
|
|
39
36
|
for key, value in params.items():
|
|
40
37
|
if key not in props:
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1.6.39
|
{code_analysis_client-1.0.6 → code_analysis_client-1.6.39}/code_analysis_client.egg-info/PKG-INFO
RENAMED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code-analysis-client
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.6.39
|
|
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
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
|
-
Requires-Dist: mcp-proxy-adapter>=8.10.
|
|
8
|
+
Requires-Dist: mcp-proxy-adapter>=8.10.15
|
|
9
9
|
Provides-Extra: dev
|
|
10
10
|
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
11
11
|
Requires-Dist: pytest-asyncio>=0.25.0; extra == "dev"
|
|
@@ -97,8 +97,8 @@ Sync checks (in-process registry):
|
|
|
97
97
|
pytest tests/test_client_server_api_sync.py tests/test_code_analysis_client.py -k session
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
-
Package version is in ``
|
|
101
|
-
|
|
100
|
+
Package version is in the root ``pyproject.toml``; before a client wheel build run
|
|
101
|
+
``python scripts/sync_code_analysis_client_version.py`` (also done by ``release_build.sh``).
|
|
102
102
|
|
|
103
103
|
## Examples (this repository)
|
|
104
104
|
|
|
@@ -1,154 +0,0 @@
|
|
|
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, Dict, 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_view",
|
|
58
|
-
"session_open_file",
|
|
59
|
-
"session_close_file",
|
|
60
|
-
"session_list_file_locks",
|
|
61
|
-
"subordinate_session_create",
|
|
62
|
-
"subordinate_session_get",
|
|
63
|
-
"subordinate_session_update",
|
|
64
|
-
"subordinate_session_delete",
|
|
65
|
-
"subordinate_session_list",
|
|
66
|
-
}
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
TRANSFER_AND_LOCK_COMMANDS: FrozenSet[str] = frozenset(
|
|
70
|
-
{
|
|
71
|
-
"project_file_transfer_download_begin",
|
|
72
|
-
"project_file_transfer_upload_save",
|
|
73
|
-
"project_file_advisory_lock_batch",
|
|
74
|
-
}
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
UNIVERSAL_FILE_COMMANDS: FrozenSet[str] = frozenset(
|
|
78
|
-
{
|
|
79
|
-
"universal_file_open",
|
|
80
|
-
"universal_file_edit",
|
|
81
|
-
"universal_file_write",
|
|
82
|
-
"universal_file_close",
|
|
83
|
-
"universal_file_preview",
|
|
84
|
-
}
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
CLIENT_FACADE_COMMANDS: FrozenSet[str] = (
|
|
88
|
-
FILE_SESSION_COMMANDS | TRANSFER_AND_LOCK_COMMANDS | UNIVERSAL_FILE_COMMANDS
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
# ``FileSessionClient`` method names for each ``FILE_SESSION_COMMANDS`` entry.
|
|
92
|
-
# ``session_list_file_locks`` is also used by ``assert_session_exists``.
|
|
93
|
-
FILE_SESSION_FACADE_METHODS: Dict[str, str] = {
|
|
94
|
-
"session_create": "create_session",
|
|
95
|
-
"session_delete": "delete_session",
|
|
96
|
-
"session_list": "list_sessions",
|
|
97
|
-
"session_view": "view_session",
|
|
98
|
-
"session_open_file": "lock_file",
|
|
99
|
-
"session_close_file": "unlock_file",
|
|
100
|
-
"session_list_file_locks": "list_file_locks",
|
|
101
|
-
"subordinate_session_create": "create_subordinate_session",
|
|
102
|
-
"subordinate_session_get": "get_subordinate_session",
|
|
103
|
-
"subordinate_session_update": "update_subordinate_session",
|
|
104
|
-
"subordinate_session_delete": "delete_subordinate_session",
|
|
105
|
-
"subordinate_session_list": "list_subordinate_sessions",
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
def assert_file_session_facade_complete() -> None:
|
|
110
|
-
"""Raise ``AssertionError`` if ``FILE_SESSION_FACADE_METHODS`` is incomplete."""
|
|
111
|
-
missing_cmds = FILE_SESSION_COMMANDS - set(FILE_SESSION_FACADE_METHODS)
|
|
112
|
-
extra_cmds = set(FILE_SESSION_FACADE_METHODS) - FILE_SESSION_COMMANDS
|
|
113
|
-
if missing_cmds or extra_cmds:
|
|
114
|
-
raise AssertionError(
|
|
115
|
-
"FILE_SESSION_FACADE_METHODS out of sync with FILE_SESSION_COMMANDS: "
|
|
116
|
-
f"missing={sorted(missing_cmds)!r} extra={sorted(extra_cmds)!r}"
|
|
117
|
-
)
|
|
118
|
-
from code_analysis_client.file_session import FileSessionClient
|
|
119
|
-
|
|
120
|
-
for command, method_name in FILE_SESSION_FACADE_METHODS.items():
|
|
121
|
-
if not hasattr(FileSessionClient, method_name):
|
|
122
|
-
raise AssertionError(
|
|
123
|
-
f"FileSessionClient missing method {method_name!r} for command {command!r}"
|
|
124
|
-
)
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
def _command_registered(get_command: Callable[[str], object], name: str) -> bool:
|
|
128
|
-
try:
|
|
129
|
-
get_command(name)
|
|
130
|
-
return True
|
|
131
|
-
except KeyError:
|
|
132
|
-
return False
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
def assert_facade_commands_registered(get_command: Callable[[str], object]) -> None:
|
|
136
|
-
"""Raise ``KeyError`` if any facade command is missing from the server registry."""
|
|
137
|
-
missing = [
|
|
138
|
-
name
|
|
139
|
-
for name in sorted(CLIENT_FACADE_COMMANDS)
|
|
140
|
-
if not _command_registered(get_command, name)
|
|
141
|
-
]
|
|
142
|
-
if missing:
|
|
143
|
-
raise KeyError(f"Client facade commands not registered on server: {missing}")
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
def assert_removed_commands_absent(get_command: Callable[[str], object]) -> None:
|
|
147
|
-
"""Raise ``AssertionError`` if a removed command is still registered."""
|
|
148
|
-
present = [
|
|
149
|
-
name
|
|
150
|
-
for name in sorted(REMOVED_COMMANDS)
|
|
151
|
-
if _command_registered(get_command, name)
|
|
152
|
-
]
|
|
153
|
-
if present:
|
|
154
|
-
raise AssertionError(f"Removed commands still registered: {present}")
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
1.0.6
|
|
File without changes
|
{code_analysis_client-1.0.6 → code_analysis_client-1.6.39}/code_analysis_client/responses.py
RENAMED
|
File without changes
|
{code_analysis_client-1.0.6 → code_analysis_client-1.6.39}/code_analysis_client/server_schema.py
RENAMED
|
File without changes
|
{code_analysis_client-1.0.6 → code_analysis_client-1.6.39}/code_analysis_client.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|