code-analysis-client-dev 1.6.341__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.
- code_analysis_client/__init__.py +142 -0
- code_analysis_client/check_chain.py +374 -0
- code_analysis_client/client.py +728 -0
- code_analysis_client/commands_proxy.py +63 -0
- code_analysis_client/config.py +198 -0
- code_analysis_client/exceptions.py +188 -0
- code_analysis_client/file_session.py +823 -0
- code_analysis_client/project.py +59 -0
- code_analysis_client/py.typed +0 -0
- code_analysis_client/queue_wait.py +341 -0
- code_analysis_client/responses.py +126 -0
- code_analysis_client/schema_disk_cache.py +207 -0
- code_analysis_client/server_api.py +291 -0
- code_analysis_client/server_schema.py +57 -0
- code_analysis_client/symbol_tree.py +13 -0
- code_analysis_client/universal_file.py +50 -0
- code_analysis_client/validation.py +98 -0
- code_analysis_client/vectorizer_state.py +159 -0
- code_analysis_client/version.txt +1 -0
- code_analysis_client/worktree.py +533 -0
- code_analysis_client_dev-1.6.341.dist-info/METADATA +251 -0
- code_analysis_client_dev-1.6.341.dist-info/RECORD +24 -0
- code_analysis_client_dev-1.6.341.dist-info/WHEEL +5 -0
- code_analysis_client_dev-1.6.341.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,728 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Async façade over mcp-proxy-adapter JsonRpcClient for code-analysis-server.
|
|
3
|
+
|
|
4
|
+
Author: Vasiliy Zdanovskiy
|
|
5
|
+
email: vasilyvz@gmail.com
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import asyncio
|
|
11
|
+
import warnings
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any, Dict, Literal, Mapping, Optional, Union, overload
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
from mcp_proxy_adapter.client.jsonrpc_client.client import JsonRpcClient
|
|
17
|
+
except Exception: # pragma: no cover - compatibility with older layouts
|
|
18
|
+
from mcp_proxy_adapter.client.jsonrpc_client import JsonRpcClient
|
|
19
|
+
from mcp_proxy_adapter.client.jsonrpc_client.exceptions import (
|
|
20
|
+
ClientRemoteResponseError,
|
|
21
|
+
CommandInnerFailureError,
|
|
22
|
+
CommandSessionFailedError,
|
|
23
|
+
CommandSessionTimeoutError,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
from code_analysis_client.commands_proxy import ValidatedCommandsProxy
|
|
27
|
+
from code_analysis_client.config import (
|
|
28
|
+
adapter_settings_from_server_config,
|
|
29
|
+
adapter_settings_to_jsonrpc_kwargs,
|
|
30
|
+
load_server_config,
|
|
31
|
+
merged_client_ws_config,
|
|
32
|
+
)
|
|
33
|
+
from code_analysis_client.file_session import FileSessionClient
|
|
34
|
+
from code_analysis_client.check_chain import CheckChainClient
|
|
35
|
+
from code_analysis_client.project import ProjectClient
|
|
36
|
+
from code_analysis_client.worktree import WorktreeClient
|
|
37
|
+
from code_analysis_client.queue_wait import (
|
|
38
|
+
QueuedJob,
|
|
39
|
+
StatusHook,
|
|
40
|
+
check_wait_arguments,
|
|
41
|
+
invalid_params_error,
|
|
42
|
+
map_adapter_error,
|
|
43
|
+
result_from_unified,
|
|
44
|
+
validation_error_code_for_command,
|
|
45
|
+
)
|
|
46
|
+
from code_analysis_client.universal_file import UniversalFileClient
|
|
47
|
+
from code_analysis_client.schema_disk_cache import (
|
|
48
|
+
clear_cached_schemas,
|
|
49
|
+
load_cached_schema,
|
|
50
|
+
store_cached_schema,
|
|
51
|
+
)
|
|
52
|
+
from code_analysis_client.server_schema import fetch_command_schema_from_server
|
|
53
|
+
from code_analysis_client.exceptions import ClientValidationError
|
|
54
|
+
from code_analysis_client.symbol_tree import SymbolTreeNode
|
|
55
|
+
from code_analysis_client.validation import (
|
|
56
|
+
prepare_params_for_schema,
|
|
57
|
+
validate_params_against_schema,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
_SYMBOL_TREE_OMITTED: Any = object()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _remote_invalid_params_as_client_validation(
|
|
64
|
+
error: ClientRemoteResponseError, command: str,
|
|
65
|
+
) -> ClientValidationError | None:
|
|
66
|
+
"""Convert the adapter's JSON-RPC invalid-params response for client callers.
|
|
67
|
+
|
|
68
|
+
``JsonRpcClient.execute_command()`` raises ``ClientRemoteResponseError``
|
|
69
|
+
for every top-level JSON-RPC error. Code-analysis validation errors use
|
|
70
|
+
the standard ``-32602`` code, however, and callers of this package already
|
|
71
|
+
handle local schema failures as ``ClientValidationError``. Preserve the
|
|
72
|
+
adapter's complete message and the server's validation payload while
|
|
73
|
+
presenting both local and remote invalid-parameter failures through that
|
|
74
|
+
one client contract.
|
|
75
|
+
"""
|
|
76
|
+
details = error.details
|
|
77
|
+
if details.get("code") != -32602:
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
# Generic commands retain the long-standing client API code. symbol_tree
|
|
81
|
+
# is the mapped exception: its public contract exposes the JSON-RPC code.
|
|
82
|
+
code = validation_error_code_for_command(command)
|
|
83
|
+
return invalid_params_error(str(error), details.get("data"), code=code)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class CodeAnalysisAsyncClient:
|
|
87
|
+
"""Async client: domain commands via :meth:`call` / :meth:`call_unified`; full adapter API on :attr:`rpc`."""
|
|
88
|
+
|
|
89
|
+
__slots__ = ("_rpc", "_command_schema_cache", "_commands_proxy")
|
|
90
|
+
|
|
91
|
+
def __init__(self, **jsonrpc_kwargs: Any) -> None:
|
|
92
|
+
"""Same keyword arguments as ``JsonRpcClient`` (``protocol``, ``host``, ``port``, ``cert``, …).
|
|
93
|
+
|
|
94
|
+
``ws_config`` (``{"websocket": {...}}``) is merged over
|
|
95
|
+
:data:`code_analysis_client.config.DEFAULT_CLIENT_ADAPTER_CONFIG` key by key, so the
|
|
96
|
+
adapter's wait timeouts and ``on_timeout`` policy are always explicit (never the adapter's
|
|
97
|
+
60 s / cancel constants).
|
|
98
|
+
"""
|
|
99
|
+
jsonrpc_kwargs["ws_config"] = merged_client_ws_config(
|
|
100
|
+
jsonrpc_kwargs.get("ws_config")
|
|
101
|
+
)
|
|
102
|
+
self._rpc = JsonRpcClient(**jsonrpc_kwargs)
|
|
103
|
+
self._command_schema_cache: Dict[str, Dict[str, Any]] = {}
|
|
104
|
+
self._commands_proxy: Optional[ValidatedCommandsProxy] = None
|
|
105
|
+
|
|
106
|
+
@classmethod
|
|
107
|
+
def from_jsonrpc_kwargs(cls, **kwargs: Any) -> CodeAnalysisAsyncClient:
|
|
108
|
+
"""Alias for ``CodeAnalysisAsyncClient(**kwargs)``."""
|
|
109
|
+
return cls(**kwargs)
|
|
110
|
+
|
|
111
|
+
@classmethod
|
|
112
|
+
def from_adapter_settings(
|
|
113
|
+
cls,
|
|
114
|
+
settings: Mapping[str, Any],
|
|
115
|
+
*,
|
|
116
|
+
timeout: float | None = 60.0,
|
|
117
|
+
check_hostname: bool = False,
|
|
118
|
+
token_header: str | None = None,
|
|
119
|
+
token: str | None = None,
|
|
120
|
+
) -> CodeAnalysisAsyncClient:
|
|
121
|
+
"""Build from a dict like ``PipelineConfig.generate_adapter_client_settings()``."""
|
|
122
|
+
kwargs = adapter_settings_to_jsonrpc_kwargs(
|
|
123
|
+
settings,
|
|
124
|
+
timeout=timeout,
|
|
125
|
+
check_hostname=check_hostname,
|
|
126
|
+
token_header=token_header,
|
|
127
|
+
token=token,
|
|
128
|
+
)
|
|
129
|
+
return cls(**kwargs)
|
|
130
|
+
|
|
131
|
+
@classmethod
|
|
132
|
+
def from_server_config(
|
|
133
|
+
cls,
|
|
134
|
+
config: Mapping[str, Any],
|
|
135
|
+
*,
|
|
136
|
+
timeout: float | None = 60.0,
|
|
137
|
+
check_hostname: bool = False,
|
|
138
|
+
token_header: str | None = None,
|
|
139
|
+
token: str | None = None,
|
|
140
|
+
) -> CodeAnalysisAsyncClient:
|
|
141
|
+
"""Derive connection settings from a code-analysis server config object."""
|
|
142
|
+
settings = adapter_settings_from_server_config(config)
|
|
143
|
+
return cls.from_adapter_settings(
|
|
144
|
+
settings,
|
|
145
|
+
timeout=timeout,
|
|
146
|
+
check_hostname=check_hostname,
|
|
147
|
+
token_header=token_header,
|
|
148
|
+
token=token,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
@classmethod
|
|
152
|
+
def from_server_config_path(
|
|
153
|
+
cls,
|
|
154
|
+
path: str | Path,
|
|
155
|
+
*,
|
|
156
|
+
timeout: float | None = 60.0,
|
|
157
|
+
check_hostname: bool = False,
|
|
158
|
+
token_header: str | None = None,
|
|
159
|
+
token: str | None = None,
|
|
160
|
+
) -> CodeAnalysisAsyncClient:
|
|
161
|
+
"""Load ``config.json`` and connect."""
|
|
162
|
+
return cls.from_server_config(
|
|
163
|
+
load_server_config(path),
|
|
164
|
+
timeout=timeout,
|
|
165
|
+
check_hostname=check_hostname,
|
|
166
|
+
token_header=token_header,
|
|
167
|
+
token=token,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
@property
|
|
171
|
+
def rpc(self) -> JsonRpcClient:
|
|
172
|
+
"""Underlying mcp-proxy-adapter client (queue, transfer, ``help``, ``execute_command``, …)."""
|
|
173
|
+
return self._rpc
|
|
174
|
+
|
|
175
|
+
@property
|
|
176
|
+
def commands(self) -> ValidatedCommandsProxy:
|
|
177
|
+
"""Dynamic validated wrappers: schema is loaded from the server via ``help`` (cached)."""
|
|
178
|
+
if self._commands_proxy is None:
|
|
179
|
+
self._commands_proxy = ValidatedCommandsProxy(self)
|
|
180
|
+
return self._commands_proxy
|
|
181
|
+
|
|
182
|
+
def clear_command_schema_cache(self) -> None:
|
|
183
|
+
"""Drop cached command schemas (after ``reload`` or when server definitions change).
|
|
184
|
+
|
|
185
|
+
Clears both the in-memory cache and this server's on-disk schema
|
|
186
|
+
cache (:mod:`code_analysis_client.schema_disk_cache`, bug 8e6acb34) —
|
|
187
|
+
otherwise the very next call would just repopulate the in-memory
|
|
188
|
+
cache from a now-stale disk entry, defeating the point of clearing it.
|
|
189
|
+
"""
|
|
190
|
+
self._command_schema_cache.clear()
|
|
191
|
+
clear_cached_schemas(self._rpc.base_url)
|
|
192
|
+
|
|
193
|
+
async def symbol_tree(
|
|
194
|
+
self,
|
|
195
|
+
*,
|
|
196
|
+
project_id: str,
|
|
197
|
+
file_path: str | None = _SYMBOL_TREE_OMITTED,
|
|
198
|
+
source: str | None = _SYMBOL_TREE_OMITTED,
|
|
199
|
+
source_name: str | None = _SYMBOL_TREE_OMITTED,
|
|
200
|
+
worktree_name: str | None = _SYMBOL_TREE_OMITTED,
|
|
201
|
+
options: dict[str, object] | None = _SYMBOL_TREE_OMITTED,
|
|
202
|
+
) -> list[SymbolTreeNode]:
|
|
203
|
+
"""Read declarations without saving raw source or changing project state.
|
|
204
|
+
|
|
205
|
+
Select exactly one of ``file_path`` and ``source``. Raw source requires
|
|
206
|
+
``source_name``; its extension selects the plugin. Domain failures retain
|
|
207
|
+
the normal ``CommandFailedError`` result; validation uses
|
|
208
|
+
``ClientValidationError``.
|
|
209
|
+
"""
|
|
210
|
+
params: dict[str, Any] = {"project_id": project_id}
|
|
211
|
+
for key, value in (
|
|
212
|
+
("file_path", file_path),
|
|
213
|
+
("source", source),
|
|
214
|
+
("source_name", source_name),
|
|
215
|
+
("worktree_name", worktree_name),
|
|
216
|
+
("options", options),
|
|
217
|
+
):
|
|
218
|
+
if value is not _SYMBOL_TREE_OMITTED:
|
|
219
|
+
params[key] = value
|
|
220
|
+
result = await self.call("symbol_tree", params)
|
|
221
|
+
data = result.get("data")
|
|
222
|
+
if not isinstance(data, list):
|
|
223
|
+
raise RuntimeError("symbol_tree returned a non-list result")
|
|
224
|
+
return data
|
|
225
|
+
|
|
226
|
+
@property
|
|
227
|
+
def file_sessions(self) -> FileSessionClient:
|
|
228
|
+
"""Session workflow: ``session_*``, ``subordinate_session_*``, transfer, advisory locks."""
|
|
229
|
+
return FileSessionClient(self)
|
|
230
|
+
|
|
231
|
+
@property
|
|
232
|
+
def projects(self) -> ProjectClient:
|
|
233
|
+
"""Project commands outside the session workflow (documentation-server override)."""
|
|
234
|
+
return ProjectClient(self)
|
|
235
|
+
|
|
236
|
+
@property
|
|
237
|
+
def worktrees(self) -> WorktreeClient:
|
|
238
|
+
"""Worktree management: ``worktree_*`` commands."""
|
|
239
|
+
return WorktreeClient(self)
|
|
240
|
+
|
|
241
|
+
@property
|
|
242
|
+
def check_chains(self) -> CheckChainClient:
|
|
243
|
+
"""Check-chain registry and promote: ``check_chain_*``, ``file_check_and_promote``."""
|
|
244
|
+
return CheckChainClient(self)
|
|
245
|
+
|
|
246
|
+
@property
|
|
247
|
+
def universal_files(self) -> UniversalFileClient:
|
|
248
|
+
"""Read-only structured preview (``universal_file_preview`` command)."""
|
|
249
|
+
return UniversalFileClient(self)
|
|
250
|
+
|
|
251
|
+
async def get_command_schema(
|
|
252
|
+
self, command: str, *, refresh: bool = False
|
|
253
|
+
) -> Dict[str, Any]:
|
|
254
|
+
"""Fetch input JSON schema for ``command`` using server ``help``.
|
|
255
|
+
|
|
256
|
+
Checked in order (bug 8e6acb34, Fix 1): the in-memory per-instance
|
|
257
|
+
cache, then a bounded-TTL on-disk cache shared across process
|
|
258
|
+
invocations against the same server
|
|
259
|
+
(:mod:`code_analysis_client.schema_disk_cache`) — this is what
|
|
260
|
+
removes the ~2.7ms ``help`` round trip that a fresh client instance
|
|
261
|
+
would otherwise pay on every first use of a given command, since most
|
|
262
|
+
real callers are short-lived processes that never benefit from an
|
|
263
|
+
in-memory-only cache. ``refresh=True`` skips both caches and always
|
|
264
|
+
re-fetches from the server, repopulating them afterward.
|
|
265
|
+
"""
|
|
266
|
+
if not refresh and command in self._command_schema_cache:
|
|
267
|
+
return self._command_schema_cache[command]
|
|
268
|
+
if not refresh:
|
|
269
|
+
cached = load_cached_schema(self._rpc.base_url, command)
|
|
270
|
+
if cached is not None:
|
|
271
|
+
self._command_schema_cache[command] = cached
|
|
272
|
+
return cached
|
|
273
|
+
schema = await fetch_command_schema_from_server(self._rpc, command)
|
|
274
|
+
self._command_schema_cache[command] = schema
|
|
275
|
+
store_cached_schema(self._rpc.base_url, command, schema)
|
|
276
|
+
return schema
|
|
277
|
+
|
|
278
|
+
async def _execute(
|
|
279
|
+
self,
|
|
280
|
+
command: str,
|
|
281
|
+
params: Optional[Dict[str, Any]] = None,
|
|
282
|
+
*,
|
|
283
|
+
use_cmd_endpoint: bool = False,
|
|
284
|
+
auto_poll: bool = True,
|
|
285
|
+
timeout: Optional[float] = None,
|
|
286
|
+
poll_interval: Optional[float] = None,
|
|
287
|
+
status_hook: Optional[StatusHook] = None,
|
|
288
|
+
) -> Union[Dict[str, Any], QueuedJob]:
|
|
289
|
+
"""Single core every public entry point routes through: the adapter's standard call.
|
|
290
|
+
|
|
291
|
+
Runs ``command`` through ``JsonRpcClient.execute_command_unified`` (WebSocket command
|
|
292
|
+
session, event-driven wait inside the call, adapter RPC-polling fallback with a logged
|
|
293
|
+
WARNING when ``/ws`` is unreachable). Nothing here polls.
|
|
294
|
+
|
|
295
|
+
* ``auto_poll=True`` (default) - waits for the command and returns its inner result
|
|
296
|
+
envelope. ``timeout=None`` resolves to the client config
|
|
297
|
+
``websocket.job_wait_timeout`` (shipped ``0`` = no bound); a positive ``timeout`` raises
|
|
298
|
+
:class:`~code_analysis_client.exceptions.JobTimeoutError` and detaches (the job keeps
|
|
299
|
+
running, ``websocket.on_timeout`` = ``detach``). ``poll_interval=None`` resolves to
|
|
300
|
+
``websocket.terminal_poll_interval``. ``status_hook`` receives the adapter's WebSocket
|
|
301
|
+
events (accepted ... terminal); an exception raised by the hook propagates.
|
|
302
|
+
* ``auto_poll=False`` - detaches as soon as the job id is known and returns a
|
|
303
|
+
:class:`~code_analysis_client.queue_wait.QueuedJob` for EVERY command (also a formerly
|
|
304
|
+
immediate one); ``timeout``, ``poll_interval`` and ``status_hook`` are ignored.
|
|
305
|
+
|
|
306
|
+
An inner ``success=false`` raises
|
|
307
|
+
:class:`~code_analysis_client.exceptions.CommandFailedError` (never returned as a dict);
|
|
308
|
+
a queued job that ended failed raises
|
|
309
|
+
:class:`~code_analysis_client.exceptions.CommandFailedError` with the server error code,
|
|
310
|
+
message and data (same as an immediate error); a job that was stopped or cancelled raises
|
|
311
|
+
:class:`~code_analysis_client.exceptions.JobFailedError`; a server -32602 refusal raises
|
|
312
|
+
:class:`~code_analysis_client.exceptions.ClientValidationError`. A negative ``timeout`` or
|
|
313
|
+
a non-positive ``poll_interval`` raises ``ValueError`` before anything is sent.
|
|
314
|
+
|
|
315
|
+
``use_cmd_endpoint`` remains accepted-and-ignored (documented no-op, kept only for
|
|
316
|
+
signature compatibility; the legacy ``/cmd`` route was removed in mcp-proxy-adapter 8.10.32).
|
|
317
|
+
"""
|
|
318
|
+
check_wait_arguments(timeout, poll_interval)
|
|
319
|
+
waiting = bool(auto_poll)
|
|
320
|
+
try:
|
|
321
|
+
envelope = await self._rpc.execute_command_unified(
|
|
322
|
+
command,
|
|
323
|
+
params or {},
|
|
324
|
+
auto_poll=waiting,
|
|
325
|
+
timeout=timeout if waiting else None,
|
|
326
|
+
terminal_poll_interval=poll_interval if waiting else None,
|
|
327
|
+
status_hook=status_hook if waiting else None,
|
|
328
|
+
raise_on_inner_failure=True,
|
|
329
|
+
)
|
|
330
|
+
except (
|
|
331
|
+
ClientRemoteResponseError,
|
|
332
|
+
CommandSessionTimeoutError,
|
|
333
|
+
CommandSessionFailedError,
|
|
334
|
+
CommandInnerFailureError,
|
|
335
|
+
asyncio.TimeoutError,
|
|
336
|
+
) as exc:
|
|
337
|
+
mapped = map_adapter_error(exc, command, timeout)
|
|
338
|
+
error: BaseException = exc if mapped is None else mapped
|
|
339
|
+
if isinstance(error, ClientRemoteResponseError):
|
|
340
|
+
validation_error = _remote_invalid_params_as_client_validation(
|
|
341
|
+
error, command
|
|
342
|
+
)
|
|
343
|
+
if validation_error is not None:
|
|
344
|
+
raise validation_error from exc
|
|
345
|
+
if error is exc:
|
|
346
|
+
raise
|
|
347
|
+
raise error from exc
|
|
348
|
+
if not waiting:
|
|
349
|
+
job_id = envelope.get("job_id")
|
|
350
|
+
if job_id:
|
|
351
|
+
return QueuedJob(
|
|
352
|
+
job_id=str(job_id),
|
|
353
|
+
envelope=envelope,
|
|
354
|
+
rpc=self._rpc,
|
|
355
|
+
command=command,
|
|
356
|
+
)
|
|
357
|
+
# /ws unreachable and the command ran without a job: it already executed.
|
|
358
|
+
return QueuedJob(
|
|
359
|
+
job_id="",
|
|
360
|
+
envelope=envelope,
|
|
361
|
+
rpc=self._rpc,
|
|
362
|
+
command=command,
|
|
363
|
+
resolved=result_from_unified(command, envelope),
|
|
364
|
+
)
|
|
365
|
+
return result_from_unified(command, envelope)
|
|
366
|
+
|
|
367
|
+
async def _execute_validated(
|
|
368
|
+
self,
|
|
369
|
+
command: str,
|
|
370
|
+
params: Dict[str, Any],
|
|
371
|
+
*,
|
|
372
|
+
use_cmd_endpoint: bool,
|
|
373
|
+
auto_poll: bool,
|
|
374
|
+
timeout: Optional[float],
|
|
375
|
+
poll_interval: Optional[float],
|
|
376
|
+
status_hook: Optional[StatusHook],
|
|
377
|
+
) -> Union[Dict[str, Any], QueuedJob]:
|
|
378
|
+
"""Execute after schema validation and reconcile a stale command catalog.
|
|
379
|
+
|
|
380
|
+
A cached schema can outlive a deployment that removes its command. If
|
|
381
|
+
execution reports JSON-RPC ``-32601``, discard the cached catalog view
|
|
382
|
+
and ask live ``help`` once. A command still present keeps the original
|
|
383
|
+
remote error; an absent command surfaces ``help``'s
|
|
384
|
+
:class:`ClientValidationError` with the remote response as its cause.
|
|
385
|
+
"""
|
|
386
|
+
try:
|
|
387
|
+
return await self._execute(
|
|
388
|
+
command,
|
|
389
|
+
params,
|
|
390
|
+
use_cmd_endpoint=use_cmd_endpoint,
|
|
391
|
+
auto_poll=auto_poll,
|
|
392
|
+
timeout=timeout,
|
|
393
|
+
poll_interval=poll_interval,
|
|
394
|
+
status_hook=status_hook,
|
|
395
|
+
)
|
|
396
|
+
except ClientRemoteResponseError as exc:
|
|
397
|
+
if exc.details.get("code") != -32601:
|
|
398
|
+
raise
|
|
399
|
+
self.clear_command_schema_cache()
|
|
400
|
+
try:
|
|
401
|
+
await self.get_command_schema(command, refresh=True)
|
|
402
|
+
except ClientValidationError as schema_error:
|
|
403
|
+
raise schema_error from exc
|
|
404
|
+
raise
|
|
405
|
+
|
|
406
|
+
@overload
|
|
407
|
+
async def call_validated(
|
|
408
|
+
self,
|
|
409
|
+
command: str,
|
|
410
|
+
params: Optional[Dict[str, Any]] = None,
|
|
411
|
+
*,
|
|
412
|
+
use_cmd_endpoint: bool = False,
|
|
413
|
+
refresh_schema: bool = False,
|
|
414
|
+
auto_poll: Literal[True] = True,
|
|
415
|
+
timeout: Optional[float] = None,
|
|
416
|
+
poll_interval: Optional[float] = None,
|
|
417
|
+
status_hook: Optional[StatusHook] = None,
|
|
418
|
+
) -> Dict[str, Any]: ...
|
|
419
|
+
|
|
420
|
+
@overload
|
|
421
|
+
async def call_validated(
|
|
422
|
+
self,
|
|
423
|
+
command: str,
|
|
424
|
+
params: Optional[Dict[str, Any]] = None,
|
|
425
|
+
*,
|
|
426
|
+
use_cmd_endpoint: bool = False,
|
|
427
|
+
refresh_schema: bool = False,
|
|
428
|
+
auto_poll: Literal[False],
|
|
429
|
+
timeout: Optional[float] = None,
|
|
430
|
+
poll_interval: Optional[float] = None,
|
|
431
|
+
status_hook: Optional[StatusHook] = None,
|
|
432
|
+
) -> QueuedJob: ...
|
|
433
|
+
|
|
434
|
+
@overload
|
|
435
|
+
async def call_validated(
|
|
436
|
+
self,
|
|
437
|
+
command: str,
|
|
438
|
+
params: Optional[Dict[str, Any]] = None,
|
|
439
|
+
*,
|
|
440
|
+
use_cmd_endpoint: bool = False,
|
|
441
|
+
refresh_schema: bool = False,
|
|
442
|
+
auto_poll: bool,
|
|
443
|
+
timeout: Optional[float] = None,
|
|
444
|
+
poll_interval: Optional[float] = None,
|
|
445
|
+
status_hook: Optional[StatusHook] = None,
|
|
446
|
+
) -> Union[Dict[str, Any], QueuedJob]: ...
|
|
447
|
+
|
|
448
|
+
async def call_validated(
|
|
449
|
+
self,
|
|
450
|
+
command: str,
|
|
451
|
+
params: Optional[Dict[str, Any]] = None,
|
|
452
|
+
*,
|
|
453
|
+
use_cmd_endpoint: bool = False,
|
|
454
|
+
refresh_schema: bool = False,
|
|
455
|
+
auto_poll: bool = True,
|
|
456
|
+
timeout: Optional[float] = None,
|
|
457
|
+
poll_interval: Optional[float] = None,
|
|
458
|
+
status_hook: Optional[StatusHook] = None,
|
|
459
|
+
) -> Union[Dict[str, Any], QueuedJob]:
|
|
460
|
+
"""``help`` → schema on server, shallow local validation, then the queue-aware core.
|
|
461
|
+
|
|
462
|
+
``auto_poll`` (default ``True``) forwards to :meth:`_execute`: set it
|
|
463
|
+
to ``False`` to get a :class:`~code_analysis_client.queue_wait.QueuedJob`
|
|
464
|
+
handle back instead of blocking until a queued job completes.
|
|
465
|
+
"""
|
|
466
|
+
schema = await self.get_command_schema(command, refresh=refresh_schema)
|
|
467
|
+
merged = dict(params or {})
|
|
468
|
+
prepared = prepare_params_for_schema(merged, schema)
|
|
469
|
+
validate_params_against_schema(prepared, schema, command_name=command)
|
|
470
|
+
return await self._execute_validated(
|
|
471
|
+
command,
|
|
472
|
+
prepared,
|
|
473
|
+
use_cmd_endpoint=use_cmd_endpoint,
|
|
474
|
+
auto_poll=auto_poll,
|
|
475
|
+
timeout=timeout,
|
|
476
|
+
poll_interval=poll_interval,
|
|
477
|
+
status_hook=status_hook,
|
|
478
|
+
)
|
|
479
|
+
|
|
480
|
+
@overload
|
|
481
|
+
async def call_unified_validated(
|
|
482
|
+
self,
|
|
483
|
+
command: str,
|
|
484
|
+
params: Optional[Dict[str, Any]] = None,
|
|
485
|
+
*,
|
|
486
|
+
refresh_schema: bool = False,
|
|
487
|
+
use_cmd_endpoint: bool = False,
|
|
488
|
+
expect_queue: Optional[bool] = None,
|
|
489
|
+
auto_poll: Literal[True] = True,
|
|
490
|
+
poll_interval: Optional[float] = None,
|
|
491
|
+
timeout: Optional[float] = None,
|
|
492
|
+
status_hook: Optional[StatusHook] = None,
|
|
493
|
+
) -> Dict[str, Any]: ...
|
|
494
|
+
|
|
495
|
+
@overload
|
|
496
|
+
async def call_unified_validated(
|
|
497
|
+
self,
|
|
498
|
+
command: str,
|
|
499
|
+
params: Optional[Dict[str, Any]] = None,
|
|
500
|
+
*,
|
|
501
|
+
refresh_schema: bool = False,
|
|
502
|
+
use_cmd_endpoint: bool = False,
|
|
503
|
+
expect_queue: Optional[bool] = None,
|
|
504
|
+
auto_poll: Literal[False],
|
|
505
|
+
poll_interval: Optional[float] = None,
|
|
506
|
+
timeout: Optional[float] = None,
|
|
507
|
+
status_hook: Optional[StatusHook] = None,
|
|
508
|
+
) -> QueuedJob: ...
|
|
509
|
+
|
|
510
|
+
@overload
|
|
511
|
+
async def call_unified_validated(
|
|
512
|
+
self,
|
|
513
|
+
command: str,
|
|
514
|
+
params: Optional[Dict[str, Any]] = None,
|
|
515
|
+
*,
|
|
516
|
+
refresh_schema: bool = False,
|
|
517
|
+
use_cmd_endpoint: bool = False,
|
|
518
|
+
expect_queue: Optional[bool] = None,
|
|
519
|
+
auto_poll: bool,
|
|
520
|
+
poll_interval: Optional[float] = None,
|
|
521
|
+
timeout: Optional[float] = None,
|
|
522
|
+
status_hook: Optional[StatusHook] = None,
|
|
523
|
+
) -> Union[Dict[str, Any], QueuedJob]: ...
|
|
524
|
+
|
|
525
|
+
async def call_unified_validated(
|
|
526
|
+
self,
|
|
527
|
+
command: str,
|
|
528
|
+
params: Optional[Dict[str, Any]] = None,
|
|
529
|
+
*,
|
|
530
|
+
refresh_schema: bool = False,
|
|
531
|
+
use_cmd_endpoint: bool = False,
|
|
532
|
+
expect_queue: Optional[bool] = None,
|
|
533
|
+
auto_poll: bool = True,
|
|
534
|
+
poll_interval: Optional[float] = None,
|
|
535
|
+
timeout: Optional[float] = None,
|
|
536
|
+
status_hook: Optional[StatusHook] = None,
|
|
537
|
+
) -> Union[Dict[str, Any], QueuedJob]:
|
|
538
|
+
"""Deprecated alias for :meth:`call_validated`.
|
|
539
|
+
|
|
540
|
+
``expect_queue`` remains accepted-and-ignored (documented no-op, kept
|
|
541
|
+
only for signature compatibility). ``auto_poll`` is now LIVE again and
|
|
542
|
+
forwards straight to the shared :meth:`_execute` core: ``auto_poll=False``
|
|
543
|
+
returns a :class:`~code_analysis_client.queue_wait.QueuedJob` handle
|
|
544
|
+
instead of a plain dict when the response is a queued envelope — a
|
|
545
|
+
return-type change versus the previous era where this parameter was
|
|
546
|
+
silently ignored and every path always blocked until completion.
|
|
547
|
+
Prefer :meth:`call_validated` directly; this alias emits
|
|
548
|
+
``DeprecationWarning`` on every call.
|
|
549
|
+
"""
|
|
550
|
+
warnings.warn(
|
|
551
|
+
"call_unified_validated is deprecated; use call_validated() "
|
|
552
|
+
"directly (its 'auto_poll' keyword works the same way).",
|
|
553
|
+
DeprecationWarning,
|
|
554
|
+
stacklevel=2,
|
|
555
|
+
)
|
|
556
|
+
schema = await self.get_command_schema(command, refresh=refresh_schema)
|
|
557
|
+
merged = dict(params or {})
|
|
558
|
+
prepared = prepare_params_for_schema(merged, schema)
|
|
559
|
+
validate_params_against_schema(prepared, schema, command_name=command)
|
|
560
|
+
return await self._execute_validated(
|
|
561
|
+
command,
|
|
562
|
+
prepared,
|
|
563
|
+
use_cmd_endpoint=use_cmd_endpoint,
|
|
564
|
+
auto_poll=auto_poll,
|
|
565
|
+
timeout=timeout,
|
|
566
|
+
poll_interval=poll_interval,
|
|
567
|
+
status_hook=status_hook,
|
|
568
|
+
)
|
|
569
|
+
|
|
570
|
+
@overload
|
|
571
|
+
async def call(
|
|
572
|
+
self,
|
|
573
|
+
command: str,
|
|
574
|
+
params: Optional[Dict[str, Any]] = None,
|
|
575
|
+
*,
|
|
576
|
+
use_cmd_endpoint: bool = False,
|
|
577
|
+
auto_poll: Literal[True] = True,
|
|
578
|
+
timeout: Optional[float] = None,
|
|
579
|
+
poll_interval: Optional[float] = None,
|
|
580
|
+
status_hook: Optional[StatusHook] = None,
|
|
581
|
+
) -> Dict[str, Any]: ...
|
|
582
|
+
|
|
583
|
+
@overload
|
|
584
|
+
async def call(
|
|
585
|
+
self,
|
|
586
|
+
command: str,
|
|
587
|
+
params: Optional[Dict[str, Any]] = None,
|
|
588
|
+
*,
|
|
589
|
+
use_cmd_endpoint: bool = False,
|
|
590
|
+
auto_poll: Literal[False],
|
|
591
|
+
timeout: Optional[float] = None,
|
|
592
|
+
poll_interval: Optional[float] = None,
|
|
593
|
+
status_hook: Optional[StatusHook] = None,
|
|
594
|
+
) -> QueuedJob: ...
|
|
595
|
+
|
|
596
|
+
@overload
|
|
597
|
+
async def call(
|
|
598
|
+
self,
|
|
599
|
+
command: str,
|
|
600
|
+
params: Optional[Dict[str, Any]] = None,
|
|
601
|
+
*,
|
|
602
|
+
use_cmd_endpoint: bool = False,
|
|
603
|
+
auto_poll: bool,
|
|
604
|
+
timeout: Optional[float] = None,
|
|
605
|
+
poll_interval: Optional[float] = None,
|
|
606
|
+
status_hook: Optional[StatusHook] = None,
|
|
607
|
+
) -> Union[Dict[str, Any], QueuedJob]: ...
|
|
608
|
+
|
|
609
|
+
async def call(
|
|
610
|
+
self,
|
|
611
|
+
command: str,
|
|
612
|
+
params: Optional[Dict[str, Any]] = None,
|
|
613
|
+
*,
|
|
614
|
+
use_cmd_endpoint: bool = False,
|
|
615
|
+
auto_poll: bool = True,
|
|
616
|
+
timeout: Optional[float] = None,
|
|
617
|
+
poll_interval: Optional[float] = None,
|
|
618
|
+
status_hook: Optional[StatusHook] = None,
|
|
619
|
+
) -> Union[Dict[str, Any], QueuedJob]:
|
|
620
|
+
"""Run any registered server command; the adapter's standard call waits for it by default.
|
|
621
|
+
|
|
622
|
+
Pass ``auto_poll=False`` to get a
|
|
623
|
+
:class:`~code_analysis_client.queue_wait.QueuedJob` handle back
|
|
624
|
+
immediately for any command (call ``await handle.wait(...)`` when
|
|
625
|
+
you're ready to block on it). See :meth:`_execute` for the full contract.
|
|
626
|
+
"""
|
|
627
|
+
return await self._execute(
|
|
628
|
+
command,
|
|
629
|
+
params,
|
|
630
|
+
use_cmd_endpoint=use_cmd_endpoint,
|
|
631
|
+
auto_poll=auto_poll,
|
|
632
|
+
timeout=timeout,
|
|
633
|
+
poll_interval=poll_interval,
|
|
634
|
+
status_hook=status_hook,
|
|
635
|
+
)
|
|
636
|
+
|
|
637
|
+
@overload
|
|
638
|
+
async def call_unified(
|
|
639
|
+
self,
|
|
640
|
+
command: str,
|
|
641
|
+
params: Optional[Dict[str, Any]] = None,
|
|
642
|
+
*,
|
|
643
|
+
use_cmd_endpoint: bool = False,
|
|
644
|
+
expect_queue: Optional[bool] = None,
|
|
645
|
+
auto_poll: Literal[True] = True,
|
|
646
|
+
poll_interval: Optional[float] = None,
|
|
647
|
+
timeout: Optional[float] = None,
|
|
648
|
+
status_hook: Optional[StatusHook] = None,
|
|
649
|
+
) -> Dict[str, Any]: ...
|
|
650
|
+
|
|
651
|
+
@overload
|
|
652
|
+
async def call_unified(
|
|
653
|
+
self,
|
|
654
|
+
command: str,
|
|
655
|
+
params: Optional[Dict[str, Any]] = None,
|
|
656
|
+
*,
|
|
657
|
+
use_cmd_endpoint: bool = False,
|
|
658
|
+
expect_queue: Optional[bool] = None,
|
|
659
|
+
auto_poll: Literal[False],
|
|
660
|
+
poll_interval: Optional[float] = None,
|
|
661
|
+
timeout: Optional[float] = None,
|
|
662
|
+
status_hook: Optional[StatusHook] = None,
|
|
663
|
+
) -> QueuedJob: ...
|
|
664
|
+
|
|
665
|
+
@overload
|
|
666
|
+
async def call_unified(
|
|
667
|
+
self,
|
|
668
|
+
command: str,
|
|
669
|
+
params: Optional[Dict[str, Any]] = None,
|
|
670
|
+
*,
|
|
671
|
+
use_cmd_endpoint: bool = False,
|
|
672
|
+
expect_queue: Optional[bool] = None,
|
|
673
|
+
auto_poll: bool,
|
|
674
|
+
poll_interval: Optional[float] = None,
|
|
675
|
+
timeout: Optional[float] = None,
|
|
676
|
+
status_hook: Optional[StatusHook] = None,
|
|
677
|
+
) -> Union[Dict[str, Any], QueuedJob]: ...
|
|
678
|
+
|
|
679
|
+
async def call_unified(
|
|
680
|
+
self,
|
|
681
|
+
command: str,
|
|
682
|
+
params: Optional[Dict[str, Any]] = None,
|
|
683
|
+
*,
|
|
684
|
+
use_cmd_endpoint: bool = False,
|
|
685
|
+
expect_queue: Optional[bool] = None,
|
|
686
|
+
auto_poll: bool = True,
|
|
687
|
+
poll_interval: Optional[float] = None,
|
|
688
|
+
timeout: Optional[float] = None,
|
|
689
|
+
status_hook: Optional[StatusHook] = None,
|
|
690
|
+
) -> Union[Dict[str, Any], QueuedJob]:
|
|
691
|
+
"""Deprecated alias for :meth:`call`.
|
|
692
|
+
|
|
693
|
+
``expect_queue`` remains accepted-and-ignored (documented no-op).
|
|
694
|
+
``auto_poll`` is now LIVE again and forwards straight to the shared
|
|
695
|
+
:meth:`_execute` core: ``auto_poll=False`` returns a
|
|
696
|
+
:class:`~code_analysis_client.queue_wait.QueuedJob` handle instead of
|
|
697
|
+
a plain dict when the response is a queued envelope — a return-type
|
|
698
|
+
change versus the previous era where this parameter was silently
|
|
699
|
+
ignored. Prefer :meth:`call` directly; this alias emits
|
|
700
|
+
``DeprecationWarning`` on every call.
|
|
701
|
+
"""
|
|
702
|
+
warnings.warn(
|
|
703
|
+
"call_unified is deprecated; use call() directly (its "
|
|
704
|
+
"'auto_poll' keyword works the same way).",
|
|
705
|
+
DeprecationWarning,
|
|
706
|
+
stacklevel=2,
|
|
707
|
+
)
|
|
708
|
+
return await self._execute(
|
|
709
|
+
command,
|
|
710
|
+
params,
|
|
711
|
+
use_cmd_endpoint=use_cmd_endpoint,
|
|
712
|
+
auto_poll=auto_poll,
|
|
713
|
+
timeout=timeout,
|
|
714
|
+
poll_interval=poll_interval,
|
|
715
|
+
status_hook=status_hook,
|
|
716
|
+
)
|
|
717
|
+
|
|
718
|
+
async def close(self) -> None:
|
|
719
|
+
"""Close the underlying JSON-RPC transport."""
|
|
720
|
+
await self._rpc.close()
|
|
721
|
+
|
|
722
|
+
async def __aenter__(self) -> CodeAnalysisAsyncClient:
|
|
723
|
+
"""Enter async context manager and return this client."""
|
|
724
|
+
return self
|
|
725
|
+
|
|
726
|
+
async def __aexit__(self, exc_type: Any, exc: Any, tb: Any) -> None:
|
|
727
|
+
"""Close the client when leaving an async context manager block."""
|
|
728
|
+
await self.close()
|