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