code-analysis-client 1.6.66__tar.gz → 1.6.67__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.
Files changed (24) hide show
  1. {code_analysis_client-1.6.66 → code_analysis_client-1.6.67}/PKG-INFO +28 -3
  2. {code_analysis_client-1.6.66 → code_analysis_client-1.6.67}/README.md +27 -2
  3. {code_analysis_client-1.6.66 → code_analysis_client-1.6.67}/code_analysis_client/__init__.py +12 -0
  4. {code_analysis_client-1.6.66 → code_analysis_client-1.6.67}/code_analysis_client/client.py +241 -21
  5. {code_analysis_client-1.6.66 → code_analysis_client-1.6.67}/code_analysis_client/queue_wait.py +52 -0
  6. code_analysis_client-1.6.67/code_analysis_client/version.txt +1 -0
  7. {code_analysis_client-1.6.66 → code_analysis_client-1.6.67}/code_analysis_client.egg-info/PKG-INFO +28 -3
  8. code_analysis_client-1.6.66/code_analysis_client/version.txt +0 -1
  9. {code_analysis_client-1.6.66 → code_analysis_client-1.6.67}/code_analysis_client/commands_proxy.py +0 -0
  10. {code_analysis_client-1.6.66 → code_analysis_client-1.6.67}/code_analysis_client/config.py +0 -0
  11. {code_analysis_client-1.6.66 → code_analysis_client-1.6.67}/code_analysis_client/exceptions.py +0 -0
  12. {code_analysis_client-1.6.66 → code_analysis_client-1.6.67}/code_analysis_client/file_session.py +0 -0
  13. {code_analysis_client-1.6.66 → code_analysis_client-1.6.67}/code_analysis_client/py.typed +0 -0
  14. {code_analysis_client-1.6.66 → code_analysis_client-1.6.67}/code_analysis_client/responses.py +0 -0
  15. {code_analysis_client-1.6.66 → code_analysis_client-1.6.67}/code_analysis_client/server_api.py +0 -0
  16. {code_analysis_client-1.6.66 → code_analysis_client-1.6.67}/code_analysis_client/server_schema.py +0 -0
  17. {code_analysis_client-1.6.66 → code_analysis_client-1.6.67}/code_analysis_client/universal_file.py +0 -0
  18. {code_analysis_client-1.6.66 → code_analysis_client-1.6.67}/code_analysis_client/validation.py +0 -0
  19. {code_analysis_client-1.6.66 → code_analysis_client-1.6.67}/code_analysis_client.egg-info/SOURCES.txt +0 -0
  20. {code_analysis_client-1.6.66 → code_analysis_client-1.6.67}/code_analysis_client.egg-info/dependency_links.txt +0 -0
  21. {code_analysis_client-1.6.66 → code_analysis_client-1.6.67}/code_analysis_client.egg-info/requires.txt +0 -0
  22. {code_analysis_client-1.6.66 → code_analysis_client-1.6.67}/code_analysis_client.egg-info/top_level.txt +0 -0
  23. {code_analysis_client-1.6.66 → code_analysis_client-1.6.67}/pyproject.toml +0 -0
  24. {code_analysis_client-1.6.66 → code_analysis_client-1.6.67}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-analysis-client
3
- Version: 1.6.66
3
+ Version: 1.6.67
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
@@ -85,9 +85,34 @@ counterparts): `timeout` (seconds, default `None` = wait until terminal),
85
85
  `poll_interval` (seconds between polls, default `1.0`), `status_hook` (sync or
86
86
  async callable invoked with each poll's status dict).
87
87
 
88
+ `call` / `call_validated` also take `auto_poll` (default `True`, matching the
89
+ behavior above exactly). Pass `auto_poll=False` to opt out of automatic
90
+ polling: a non-queued response still comes back as the plain domain result,
91
+ but a queued-job response comes back immediately as a `QueuedJob` handle
92
+ instead of blocking until the job finishes.
93
+
94
+ ```python
95
+ from code_analysis_client import QueuedJob
96
+
97
+ result = await client.call("some_long_running_command", {...}, auto_poll=False)
98
+ if isinstance(result, QueuedJob):
99
+ # do other work here, then block on it whenever you're ready
100
+ result = await result.wait()
101
+ # `result` is now the same dict shape a default (auto_poll=True) call returns
102
+ ```
103
+
104
+ `QueuedJob` exposes `.job_id`, `.envelope` (the raw queue-service response),
105
+ and two async methods: `.wait(timeout=None, poll_interval=1.0, status_hook=None)`
106
+ — polls to completion and returns/raises exactly like the default path — and
107
+ `.status()` — a single `queue_get_job_status` fetch without polling to
108
+ completion.
109
+
88
110
  `call_unified` / `call_unified_validated` are kept as **deprecated aliases** of
89
- `call` / `call_validated` for backward compatibility `expect_queue` and
90
- `auto_poll` are accepted but ignored, since queue handling is now always on.
111
+ `call` / `call_validated` for backward compatibility and emit
112
+ `DeprecationWarning` on every call. `expect_queue` remains accepted-and-ignored
113
+ (documented no-op). `auto_poll` is the one canonical switch and is forwarded
114
+ straight through to `call` / `call_validated` — `auto_poll=False` on an alias
115
+ returns a `QueuedJob` the same way it does on the non-deprecated method.
91
116
  Prefer `call` / `call_validated` directly.
92
117
 
93
118
  ## Validation using the server schema
@@ -73,9 +73,34 @@ counterparts): `timeout` (seconds, default `None` = wait until terminal),
73
73
  `poll_interval` (seconds between polls, default `1.0`), `status_hook` (sync or
74
74
  async callable invoked with each poll's status dict).
75
75
 
76
+ `call` / `call_validated` also take `auto_poll` (default `True`, matching the
77
+ behavior above exactly). Pass `auto_poll=False` to opt out of automatic
78
+ polling: a non-queued response still comes back as the plain domain result,
79
+ but a queued-job response comes back immediately as a `QueuedJob` handle
80
+ instead of blocking until the job finishes.
81
+
82
+ ```python
83
+ from code_analysis_client import QueuedJob
84
+
85
+ result = await client.call("some_long_running_command", {...}, auto_poll=False)
86
+ if isinstance(result, QueuedJob):
87
+ # do other work here, then block on it whenever you're ready
88
+ result = await result.wait()
89
+ # `result` is now the same dict shape a default (auto_poll=True) call returns
90
+ ```
91
+
92
+ `QueuedJob` exposes `.job_id`, `.envelope` (the raw queue-service response),
93
+ and two async methods: `.wait(timeout=None, poll_interval=1.0, status_hook=None)`
94
+ — polls to completion and returns/raises exactly like the default path — and
95
+ `.status()` — a single `queue_get_job_status` fetch without polling to
96
+ completion.
97
+
76
98
  `call_unified` / `call_unified_validated` are kept as **deprecated aliases** of
77
- `call` / `call_validated` for backward compatibility `expect_queue` and
78
- `auto_poll` are accepted but ignored, since queue handling is now always on.
99
+ `call` / `call_validated` for backward compatibility and emit
100
+ `DeprecationWarning` on every call. `expect_queue` remains accepted-and-ignored
101
+ (documented no-op). `auto_poll` is the one canonical switch and is forwarded
102
+ straight through to `call` / `call_validated` — `auto_poll=False` on an alias
103
+ returns a `QueuedJob` the same way it does on the non-deprecated method.
79
104
  Prefer `call` / `call_validated` directly.
80
105
 
81
106
  ## Validation using the server schema
@@ -24,6 +24,13 @@ from code_analysis_client.exceptions import (
24
24
  QueueJobError,
25
25
  )
26
26
  from code_analysis_client.file_session import FileSessionClient, SessionNotFoundError
27
+ from code_analysis_client.queue_wait import (
28
+ QueuedJob,
29
+ extract_job_id,
30
+ is_queued_envelope,
31
+ unwrap_job_result,
32
+ wait_for_job,
33
+ )
27
34
  from code_analysis_client.server_api import (
28
35
  CLIENT_FACADE_COMMANDS,
29
36
  CST_REMOVED_COMMANDS,
@@ -57,6 +64,7 @@ __all__ = [
57
64
  "JobTimeoutError",
58
65
  "LEGACY_REMOVED_COMMANDS",
59
66
  "QueueJobError",
67
+ "QueuedJob",
60
68
  "REMOVED_COMMANDS",
61
69
  "SessionNotFoundError",
62
70
  "TRANSFER_FACADE_METHODS",
@@ -65,11 +73,15 @@ __all__ = [
65
73
  "ValidatedCommandsProxy",
66
74
  "adapter_settings_from_server_config",
67
75
  "adapter_settings_to_jsonrpc_kwargs",
76
+ "extract_job_id",
68
77
  "fetch_command_schema_from_server",
78
+ "is_queued_envelope",
69
79
  "load_server_config",
70
80
  "parse_schema_from_help_payload",
71
81
  "prepare_params_for_schema",
82
+ "unwrap_job_result",
72
83
  "validate_params_against_schema",
84
+ "wait_for_job",
73
85
  ]
74
86
 
75
87
 
@@ -7,8 +7,9 @@ email: vasilyvz@gmail.com
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
+ import warnings
10
11
  from pathlib import Path
11
- from typing import Any, Dict, Mapping, Optional
12
+ from typing import Any, Dict, Literal, Mapping, Optional, Union, overload
12
13
 
13
14
  try:
14
15
  from mcp_proxy_adapter.client.jsonrpc_client.client import JsonRpcClient
@@ -23,6 +24,7 @@ from code_analysis_client.config import (
23
24
  )
24
25
  from code_analysis_client.file_session import FileSessionClient
25
26
  from code_analysis_client.queue_wait import (
27
+ QueuedJob,
26
28
  StatusHook,
27
29
  extract_job_id,
28
30
  is_queued_envelope,
@@ -154,19 +156,28 @@ class CodeAnalysisAsyncClient:
154
156
  params: Optional[Dict[str, Any]] = None,
155
157
  *,
156
158
  use_cmd_endpoint: bool = False,
159
+ auto_poll: bool = True,
157
160
  timeout: Optional[float] = None,
158
161
  poll_interval: float = 1.0,
159
162
  status_hook: Optional[StatusHook] = None,
160
- ) -> Dict[str, Any]:
163
+ ) -> Union[Dict[str, Any], QueuedJob]:
161
164
  """Single queue-aware core every public entry point routes through.
162
165
 
163
166
  Runs ``command`` via the raw adapter ``execute_command``. If the
164
167
  immediate response is a queue-service envelope
165
- (:func:`code_analysis_client.queue_wait.is_queued_envelope`), polls the
166
- job to completion (:func:`~code_analysis_client.queue_wait.wait_for_job`)
167
- and returns the unwrapped inner result
168
- (:func:`~code_analysis_client.queue_wait.unwrap_job_result`), raising on
169
- failure. A non-queued response is returned unchanged.
168
+ (:func:`code_analysis_client.queue_wait.is_queued_envelope`):
169
+
170
+ * ``auto_poll=True`` (default, unchanged behavior) — polls the job to
171
+ completion (:func:`~code_analysis_client.queue_wait.wait_for_job`)
172
+ and returns the unwrapped inner result
173
+ (:func:`~code_analysis_client.queue_wait.unwrap_job_result`), raising
174
+ on failure.
175
+ * ``auto_poll=False`` — returns a
176
+ :class:`~code_analysis_client.queue_wait.QueuedJob` handle
177
+ immediately instead of polling; call ``await handle.wait(...)`` to
178
+ get the same result the default path would have returned.
179
+
180
+ A non-queued response is returned unchanged regardless of ``auto_poll``.
170
181
  """
171
182
  resp = await self._rpc.execute_command(
172
183
  command,
@@ -177,6 +188,9 @@ class CodeAnalysisAsyncClient:
177
188
  return resp
178
189
 
179
190
  job_id = extract_job_id(resp)
191
+ if not auto_poll:
192
+ return QueuedJob(job_id=job_id, envelope=resp, rpc=self._rpc)
193
+
180
194
  status = await wait_for_job(
181
195
  self._rpc,
182
196
  job_id,
@@ -186,6 +200,7 @@ class CodeAnalysisAsyncClient:
186
200
  )
187
201
  return await unwrap_job_result(status, rpc=self._rpc)
188
202
 
203
+ @overload
189
204
  async def call_validated(
190
205
  self,
191
206
  command: str,
@@ -193,11 +208,58 @@ class CodeAnalysisAsyncClient:
193
208
  *,
194
209
  use_cmd_endpoint: bool = False,
195
210
  refresh_schema: bool = False,
211
+ auto_poll: Literal[True] = True,
196
212
  timeout: Optional[float] = None,
197
213
  poll_interval: float = 1.0,
198
214
  status_hook: Optional[StatusHook] = None,
199
- ) -> Dict[str, Any]:
200
- """``help`` → schema on server, shallow local validation, then the queue-aware core."""
215
+ ) -> Dict[str, Any]: ...
216
+
217
+ @overload
218
+ async def call_validated(
219
+ self,
220
+ command: str,
221
+ params: Optional[Dict[str, Any]] = None,
222
+ *,
223
+ use_cmd_endpoint: bool = False,
224
+ refresh_schema: bool = False,
225
+ auto_poll: Literal[False],
226
+ timeout: Optional[float] = None,
227
+ poll_interval: float = 1.0,
228
+ status_hook: Optional[StatusHook] = None,
229
+ ) -> QueuedJob: ...
230
+
231
+ @overload
232
+ async def call_validated(
233
+ self,
234
+ command: str,
235
+ params: Optional[Dict[str, Any]] = None,
236
+ *,
237
+ use_cmd_endpoint: bool = False,
238
+ refresh_schema: bool = False,
239
+ auto_poll: bool,
240
+ timeout: Optional[float] = None,
241
+ poll_interval: float = 1.0,
242
+ status_hook: Optional[StatusHook] = None,
243
+ ) -> Union[Dict[str, Any], QueuedJob]: ...
244
+
245
+ async def call_validated(
246
+ self,
247
+ command: str,
248
+ params: Optional[Dict[str, Any]] = None,
249
+ *,
250
+ use_cmd_endpoint: bool = False,
251
+ refresh_schema: bool = False,
252
+ auto_poll: bool = True,
253
+ timeout: Optional[float] = None,
254
+ poll_interval: float = 1.0,
255
+ status_hook: Optional[StatusHook] = None,
256
+ ) -> Union[Dict[str, Any], QueuedJob]:
257
+ """``help`` → schema on server, shallow local validation, then the queue-aware core.
258
+
259
+ ``auto_poll`` (default ``True``) forwards to :meth:`_execute`: set it
260
+ to ``False`` to get a :class:`~code_analysis_client.queue_wait.QueuedJob`
261
+ handle back instead of blocking until a queued job completes.
262
+ """
201
263
  schema = await self.get_command_schema(command, refresh=refresh_schema)
202
264
  merged = dict(params or {})
203
265
  prepared = prepare_params_for_schema(merged, schema)
@@ -206,11 +268,57 @@ class CodeAnalysisAsyncClient:
206
268
  command,
207
269
  prepared,
208
270
  use_cmd_endpoint=use_cmd_endpoint,
271
+ auto_poll=auto_poll,
209
272
  timeout=timeout,
210
273
  poll_interval=poll_interval,
211
274
  status_hook=status_hook,
212
275
  )
213
276
 
277
+ @overload
278
+ async def call_unified_validated(
279
+ self,
280
+ command: str,
281
+ params: Optional[Dict[str, Any]] = None,
282
+ *,
283
+ refresh_schema: bool = False,
284
+ use_cmd_endpoint: bool = False,
285
+ expect_queue: Optional[bool] = None,
286
+ auto_poll: Literal[True] = True,
287
+ poll_interval: float = 1.0,
288
+ timeout: Optional[float] = None,
289
+ status_hook: Optional[StatusHook] = None,
290
+ ) -> Dict[str, Any]: ...
291
+
292
+ @overload
293
+ async def call_unified_validated(
294
+ self,
295
+ command: str,
296
+ params: Optional[Dict[str, Any]] = None,
297
+ *,
298
+ refresh_schema: bool = False,
299
+ use_cmd_endpoint: bool = False,
300
+ expect_queue: Optional[bool] = None,
301
+ auto_poll: Literal[False],
302
+ poll_interval: float = 1.0,
303
+ timeout: Optional[float] = None,
304
+ status_hook: Optional[StatusHook] = None,
305
+ ) -> QueuedJob: ...
306
+
307
+ @overload
308
+ async def call_unified_validated(
309
+ self,
310
+ command: str,
311
+ params: Optional[Dict[str, Any]] = None,
312
+ *,
313
+ refresh_schema: bool = False,
314
+ use_cmd_endpoint: bool = False,
315
+ expect_queue: Optional[bool] = None,
316
+ auto_poll: bool,
317
+ poll_interval: float = 1.0,
318
+ timeout: Optional[float] = None,
319
+ status_hook: Optional[StatusHook] = None,
320
+ ) -> Union[Dict[str, Any], QueuedJob]: ...
321
+
214
322
  async def call_unified_validated(
215
323
  self,
216
324
  command: str,
@@ -223,14 +331,25 @@ class CodeAnalysisAsyncClient:
223
331
  poll_interval: float = 1.0,
224
332
  timeout: Optional[float] = None,
225
333
  status_hook: Optional[StatusHook] = None,
226
- ) -> Dict[str, Any]:
334
+ ) -> Union[Dict[str, Any], QueuedJob]:
227
335
  """Deprecated alias for :meth:`call_validated`.
228
336
 
229
- ``expect_queue`` and ``auto_poll`` are accepted only for backward
230
- compatibility and are ignored: every path is now queue-aware
231
- unconditionally through the shared :meth:`_execute` core (no more
232
- adapter ``execute_command_unified``). Prefer :meth:`call_validated`.
337
+ ``expect_queue`` remains accepted-and-ignored (documented no-op, kept
338
+ only for signature compatibility). ``auto_poll`` is now LIVE again and
339
+ forwards straight to the shared :meth:`_execute` core: ``auto_poll=False``
340
+ returns a :class:`~code_analysis_client.queue_wait.QueuedJob` handle
341
+ instead of a plain dict when the response is a queued envelope — a
342
+ return-type change versus the previous era where this parameter was
343
+ silently ignored and every path always blocked until completion.
344
+ Prefer :meth:`call_validated` directly; this alias emits
345
+ ``DeprecationWarning`` on every call.
233
346
  """
347
+ warnings.warn(
348
+ "call_unified_validated is deprecated; use call_validated() "
349
+ "directly (its 'auto_poll' keyword works the same way).",
350
+ DeprecationWarning,
351
+ stacklevel=2,
352
+ )
234
353
  schema = await self.get_command_schema(command, refresh=refresh_schema)
235
354
  merged = dict(params or {})
236
355
  prepared = prepare_params_for_schema(merged, schema)
@@ -239,31 +358,121 @@ class CodeAnalysisAsyncClient:
239
358
  command,
240
359
  prepared,
241
360
  use_cmd_endpoint=use_cmd_endpoint,
361
+ auto_poll=auto_poll,
242
362
  timeout=timeout,
243
363
  poll_interval=poll_interval,
244
364
  status_hook=status_hook,
245
365
  )
246
366
 
367
+ @overload
247
368
  async def call(
248
369
  self,
249
370
  command: str,
250
371
  params: Optional[Dict[str, Any]] = None,
251
372
  *,
252
373
  use_cmd_endpoint: bool = False,
374
+ auto_poll: Literal[True] = True,
253
375
  timeout: Optional[float] = None,
254
376
  poll_interval: float = 1.0,
255
377
  status_hook: Optional[StatusHook] = None,
256
- ) -> Dict[str, Any]:
257
- """Run any registered server command; queued jobs are polled to completion."""
378
+ ) -> Dict[str, Any]: ...
379
+
380
+ @overload
381
+ async def call(
382
+ self,
383
+ command: str,
384
+ params: Optional[Dict[str, Any]] = None,
385
+ *,
386
+ use_cmd_endpoint: bool = False,
387
+ auto_poll: Literal[False],
388
+ timeout: Optional[float] = None,
389
+ poll_interval: float = 1.0,
390
+ status_hook: Optional[StatusHook] = None,
391
+ ) -> QueuedJob: ...
392
+
393
+ @overload
394
+ async def call(
395
+ self,
396
+ command: str,
397
+ params: Optional[Dict[str, Any]] = None,
398
+ *,
399
+ use_cmd_endpoint: bool = False,
400
+ auto_poll: bool,
401
+ timeout: Optional[float] = None,
402
+ poll_interval: float = 1.0,
403
+ status_hook: Optional[StatusHook] = None,
404
+ ) -> Union[Dict[str, Any], QueuedJob]: ...
405
+
406
+ async def call(
407
+ self,
408
+ command: str,
409
+ params: Optional[Dict[str, Any]] = None,
410
+ *,
411
+ use_cmd_endpoint: bool = False,
412
+ auto_poll: bool = True,
413
+ timeout: Optional[float] = None,
414
+ poll_interval: float = 1.0,
415
+ status_hook: Optional[StatusHook] = None,
416
+ ) -> Union[Dict[str, Any], QueuedJob]:
417
+ """Run any registered server command; queued jobs are polled to completion by default.
418
+
419
+ Pass ``auto_poll=False`` to get a
420
+ :class:`~code_analysis_client.queue_wait.QueuedJob` handle back
421
+ immediately instead (call ``await handle.wait(...)`` when you're
422
+ ready to block on it).
423
+ """
258
424
  return await self._execute(
259
425
  command,
260
426
  params,
261
427
  use_cmd_endpoint=use_cmd_endpoint,
428
+ auto_poll=auto_poll,
262
429
  timeout=timeout,
263
430
  poll_interval=poll_interval,
264
431
  status_hook=status_hook,
265
432
  )
266
433
 
434
+ @overload
435
+ async def call_unified(
436
+ self,
437
+ command: str,
438
+ params: Optional[Dict[str, Any]] = None,
439
+ *,
440
+ use_cmd_endpoint: bool = False,
441
+ expect_queue: Optional[bool] = None,
442
+ auto_poll: Literal[True] = True,
443
+ poll_interval: float = 1.0,
444
+ timeout: Optional[float] = None,
445
+ status_hook: Optional[StatusHook] = None,
446
+ ) -> Dict[str, Any]: ...
447
+
448
+ @overload
449
+ async def call_unified(
450
+ self,
451
+ command: str,
452
+ params: Optional[Dict[str, Any]] = None,
453
+ *,
454
+ use_cmd_endpoint: bool = False,
455
+ expect_queue: Optional[bool] = None,
456
+ auto_poll: Literal[False],
457
+ poll_interval: float = 1.0,
458
+ timeout: Optional[float] = None,
459
+ status_hook: Optional[StatusHook] = None,
460
+ ) -> QueuedJob: ...
461
+
462
+ @overload
463
+ async def call_unified(
464
+ self,
465
+ command: str,
466
+ params: Optional[Dict[str, Any]] = None,
467
+ *,
468
+ use_cmd_endpoint: bool = False,
469
+ expect_queue: Optional[bool] = None,
470
+ auto_poll: bool,
471
+ poll_interval: float = 1.0,
472
+ timeout: Optional[float] = None,
473
+ status_hook: Optional[StatusHook] = None,
474
+ ) -> Union[Dict[str, Any], QueuedJob]: ...
475
+
267
476
  async def call_unified(
268
477
  self,
269
478
  command: str,
@@ -275,18 +484,29 @@ class CodeAnalysisAsyncClient:
275
484
  poll_interval: float = 1.0,
276
485
  timeout: Optional[float] = None,
277
486
  status_hook: Optional[StatusHook] = None,
278
- ) -> Dict[str, Any]:
487
+ ) -> Union[Dict[str, Any], QueuedJob]:
279
488
  """Deprecated alias for :meth:`call`.
280
489
 
281
- ``expect_queue`` and ``auto_poll`` are accepted only for backward
282
- compatibility and are ignored: every path is now queue-aware
283
- unconditionally through the shared :meth:`_execute` core (no more
284
- adapter ``execute_command_unified``). Prefer :meth:`call`.
490
+ ``expect_queue`` remains accepted-and-ignored (documented no-op).
491
+ ``auto_poll`` is now LIVE again and forwards straight to the shared
492
+ :meth:`_execute` core: ``auto_poll=False`` returns a
493
+ :class:`~code_analysis_client.queue_wait.QueuedJob` handle instead of
494
+ a plain dict when the response is a queued envelope — a return-type
495
+ change versus the previous era where this parameter was silently
496
+ ignored. Prefer :meth:`call` directly; this alias emits
497
+ ``DeprecationWarning`` on every call.
285
498
  """
499
+ warnings.warn(
500
+ "call_unified is deprecated; use call() directly (its "
501
+ "'auto_poll' keyword works the same way).",
502
+ DeprecationWarning,
503
+ stacklevel=2,
504
+ )
286
505
  return await self._execute(
287
506
  command,
288
507
  params,
289
508
  use_cmd_endpoint=use_cmd_endpoint,
509
+ auto_poll=auto_poll,
290
510
  timeout=timeout,
291
511
  poll_interval=poll_interval,
292
512
  status_hook=status_hook,
@@ -11,6 +11,7 @@ from __future__ import annotations
11
11
  import asyncio
12
12
  import inspect
13
13
  import time
14
+ from dataclasses import dataclass, field
14
15
  from typing import Any, Awaitable, Callable, Dict, Optional, Union
15
16
 
16
17
  from code_analysis_client.exceptions import (
@@ -254,3 +255,54 @@ async def unwrap_job_result(
254
255
  raise CommandFailedError(command, job_id, inner)
255
256
 
256
257
  return inner if isinstance(inner, dict) else {"result": inner}
258
+
259
+
260
+ @dataclass
261
+ class QueuedJob:
262
+ """Handle for a queued job returned when ``auto_poll=False`` opts out of automatic polling.
263
+
264
+ Returned by the client's queue-aware core (``CodeAnalysisAsyncClient._execute``)
265
+ in place of polling to completion when the immediate server response is a
266
+ queued-job envelope (:func:`is_queued_envelope`) and the caller passed
267
+ ``auto_poll=False``. Encapsulates all envelope-shape quirks (the job id may
268
+ live top-level or nested under ``data``, see :func:`extract_job_id`) behind
269
+ :meth:`wait` / :meth:`status` so callers never need to touch the raw envelope.
270
+ """
271
+
272
+ job_id: str
273
+ envelope: Dict[str, Any]
274
+ rpc: Any = field(repr=False)
275
+
276
+ async def wait(
277
+ self,
278
+ *,
279
+ timeout: Optional[float] = None,
280
+ poll_interval: float = 1.0,
281
+ status_hook: Optional[StatusHook] = None,
282
+ ) -> Dict[str, Any]:
283
+ """Poll the job to completion and return what ``_execute`` would have returned.
284
+
285
+ Delegates to the same :func:`wait_for_job` + :func:`unwrap_job_result`
286
+ pair the auto-polling path uses, so failures raise the identical
287
+ :class:`~code_analysis_client.exceptions.JobFailedError` /
288
+ :class:`~code_analysis_client.exceptions.CommandFailedError` /
289
+ :class:`~code_analysis_client.exceptions.JobTimeoutError`.
290
+ """
291
+ status = await wait_for_job(
292
+ self.rpc,
293
+ self.job_id,
294
+ timeout=timeout,
295
+ poll_interval=poll_interval,
296
+ status_hook=status_hook,
297
+ )
298
+ return await unwrap_job_result(status, rpc=self.rpc)
299
+
300
+ async def status(self) -> Dict[str, Any]:
301
+ """One-shot ``queue_get_job_status`` fetch (no polling to completion)."""
302
+ resp = await self.rpc.execute_command(
303
+ "queue_get_job_status", {"job_id": self.job_id}
304
+ )
305
+ data = resp.get("data") if isinstance(resp, dict) else None
306
+ if not isinstance(data, dict):
307
+ data = resp if isinstance(resp, dict) else {}
308
+ return data
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-analysis-client
3
- Version: 1.6.66
3
+ Version: 1.6.67
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
@@ -85,9 +85,34 @@ counterparts): `timeout` (seconds, default `None` = wait until terminal),
85
85
  `poll_interval` (seconds between polls, default `1.0`), `status_hook` (sync or
86
86
  async callable invoked with each poll's status dict).
87
87
 
88
+ `call` / `call_validated` also take `auto_poll` (default `True`, matching the
89
+ behavior above exactly). Pass `auto_poll=False` to opt out of automatic
90
+ polling: a non-queued response still comes back as the plain domain result,
91
+ but a queued-job response comes back immediately as a `QueuedJob` handle
92
+ instead of blocking until the job finishes.
93
+
94
+ ```python
95
+ from code_analysis_client import QueuedJob
96
+
97
+ result = await client.call("some_long_running_command", {...}, auto_poll=False)
98
+ if isinstance(result, QueuedJob):
99
+ # do other work here, then block on it whenever you're ready
100
+ result = await result.wait()
101
+ # `result` is now the same dict shape a default (auto_poll=True) call returns
102
+ ```
103
+
104
+ `QueuedJob` exposes `.job_id`, `.envelope` (the raw queue-service response),
105
+ and two async methods: `.wait(timeout=None, poll_interval=1.0, status_hook=None)`
106
+ — polls to completion and returns/raises exactly like the default path — and
107
+ `.status()` — a single `queue_get_job_status` fetch without polling to
108
+ completion.
109
+
88
110
  `call_unified` / `call_unified_validated` are kept as **deprecated aliases** of
89
- `call` / `call_validated` for backward compatibility `expect_queue` and
90
- `auto_poll` are accepted but ignored, since queue handling is now always on.
111
+ `call` / `call_validated` for backward compatibility and emit
112
+ `DeprecationWarning` on every call. `expect_queue` remains accepted-and-ignored
113
+ (documented no-op). `auto_poll` is the one canonical switch and is forwarded
114
+ straight through to `call` / `call_validated` — `auto_poll=False` on an alias
115
+ returns a `QueuedJob` the same way it does on the non-deprecated method.
91
116
  Prefer `call` / `call_validated` directly.
92
117
 
93
118
  ## Validation using the server schema