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,142 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Public async client for code-analysis-server (JSON-RPC via mcp-proxy-adapter).
|
|
3
|
+
|
|
4
|
+
Author: Vasiliy Zdanovskiy
|
|
5
|
+
email: vasilyvz@gmail.com
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from code_analysis_client.client import CodeAnalysisAsyncClient
|
|
13
|
+
from code_analysis_client.symbol_tree import SymbolTreeNode
|
|
14
|
+
from code_analysis_client.commands_proxy import ValidatedCommandsProxy
|
|
15
|
+
from code_analysis_client.config import (
|
|
16
|
+
DEFAULT_CLIENT_ADAPTER_CONFIG,
|
|
17
|
+
adapter_settings_from_server_config,
|
|
18
|
+
adapter_settings_to_jsonrpc_kwargs,
|
|
19
|
+
load_server_config,
|
|
20
|
+
)
|
|
21
|
+
from code_analysis_client.exceptions import (
|
|
22
|
+
ClientValidationError,
|
|
23
|
+
CommandFailedError,
|
|
24
|
+
JobFailedError,
|
|
25
|
+
JobTimeoutError,
|
|
26
|
+
QueueJobError,
|
|
27
|
+
VectorizerUnavailableError,
|
|
28
|
+
)
|
|
29
|
+
from code_analysis_client.file_session import FileSessionClient, SessionNotFoundError
|
|
30
|
+
from code_analysis_client.check_chain import (
|
|
31
|
+
CheckChainClient,
|
|
32
|
+
CheckChainConfigInvalidError,
|
|
33
|
+
CheckChainError,
|
|
34
|
+
CheckChainGetResult,
|
|
35
|
+
CheckChainListResult,
|
|
36
|
+
ChainElement,
|
|
37
|
+
Degraded,
|
|
38
|
+
Diagnostic,
|
|
39
|
+
ExtensionChain,
|
|
40
|
+
FileCheckAndPromoteResult,
|
|
41
|
+
)
|
|
42
|
+
from code_analysis_client.worktree import WorktreeClient, WorktreeConflictError
|
|
43
|
+
from code_analysis_client.project import ProjectClient
|
|
44
|
+
from code_analysis_client.queue_wait import QueuedJob
|
|
45
|
+
from code_analysis_client.server_api import (
|
|
46
|
+
CLIENT_FACADE_COMMANDS,
|
|
47
|
+
CST_REMOVED_COMMANDS,
|
|
48
|
+
FILE_SESSION_COMMANDS,
|
|
49
|
+
FILE_SESSION_FACADE_METHODS,
|
|
50
|
+
LEGACY_REMOVED_COMMANDS,
|
|
51
|
+
REMOVED_COMMANDS,
|
|
52
|
+
TRANSFER_FACADE_METHODS,
|
|
53
|
+
UNIVERSAL_FILE_COMMANDS,
|
|
54
|
+
)
|
|
55
|
+
from code_analysis_client.universal_file import UniversalFileClient
|
|
56
|
+
from code_analysis_client.server_schema import (
|
|
57
|
+
fetch_command_schema_from_server,
|
|
58
|
+
parse_schema_from_help_payload,
|
|
59
|
+
)
|
|
60
|
+
from code_analysis_client.validation import (
|
|
61
|
+
prepare_params_for_schema,
|
|
62
|
+
validate_params_against_schema,
|
|
63
|
+
)
|
|
64
|
+
from code_analysis_client.vectorizer_state import (
|
|
65
|
+
ServiceState,
|
|
66
|
+
VectorizerState,
|
|
67
|
+
VectorizerWarning,
|
|
68
|
+
VectorizationWorkerBlock,
|
|
69
|
+
extract_vectorizer_state_from_response,
|
|
70
|
+
safe_get_vectorizer_state,
|
|
71
|
+
safe_get_warnings,
|
|
72
|
+
safe_get_vectorization_worker,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
__all__ = [
|
|
76
|
+
"DEFAULT_CLIENT_ADAPTER_CONFIG",
|
|
77
|
+
"CLIENT_FACADE_COMMANDS",
|
|
78
|
+
"CST_REMOVED_COMMANDS",
|
|
79
|
+
"ClientValidationError",
|
|
80
|
+
"CodeAnalysisAsyncClient",
|
|
81
|
+
"SymbolTreeNode",
|
|
82
|
+
"CommandFailedError",
|
|
83
|
+
"FILE_SESSION_COMMANDS",
|
|
84
|
+
"FILE_SESSION_FACADE_METHODS",
|
|
85
|
+
"FileSessionClient",
|
|
86
|
+
"JobFailedError",
|
|
87
|
+
"JobTimeoutError",
|
|
88
|
+
"LEGACY_REMOVED_COMMANDS",
|
|
89
|
+
"QueueJobError",
|
|
90
|
+
"QueuedJob",
|
|
91
|
+
"REMOVED_COMMANDS",
|
|
92
|
+
"SessionNotFoundError",
|
|
93
|
+
"TRANSFER_FACADE_METHODS",
|
|
94
|
+
"UNIVERSAL_FILE_COMMANDS",
|
|
95
|
+
"UniversalFileClient",
|
|
96
|
+
"ValidatedCommandsProxy",
|
|
97
|
+
"VectorizerUnavailableError",
|
|
98
|
+
"ServiceState",
|
|
99
|
+
"VectorizerState",
|
|
100
|
+
"VectorizerWarning",
|
|
101
|
+
"VectorizationWorkerBlock",
|
|
102
|
+
"CheckChainClient",
|
|
103
|
+
"CheckChainConfigInvalidError",
|
|
104
|
+
"CheckChainError",
|
|
105
|
+
"CheckChainGetResult",
|
|
106
|
+
"CheckChainListResult",
|
|
107
|
+
"ChainElement",
|
|
108
|
+
"Degraded",
|
|
109
|
+
"Diagnostic",
|
|
110
|
+
"ExtensionChain",
|
|
111
|
+
"FileCheckAndPromoteResult",
|
|
112
|
+
"ProjectClient",
|
|
113
|
+
"WorktreeClient",
|
|
114
|
+
"WorktreeConflictError",
|
|
115
|
+
"adapter_settings_from_server_config",
|
|
116
|
+
"adapter_settings_to_jsonrpc_kwargs",
|
|
117
|
+
"extract_vectorizer_state_from_response",
|
|
118
|
+
"fetch_command_schema_from_server",
|
|
119
|
+
"load_server_config",
|
|
120
|
+
"parse_schema_from_help_payload",
|
|
121
|
+
"prepare_params_for_schema",
|
|
122
|
+
"safe_get_vectorizer_state",
|
|
123
|
+
"safe_get_warnings",
|
|
124
|
+
"safe_get_vectorization_worker",
|
|
125
|
+
"validate_params_against_schema",
|
|
126
|
+
]
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _read_package_version() -> str:
|
|
130
|
+
"""Read installed client version from version.txt or package metadata."""
|
|
131
|
+
vf = Path(__file__).resolve().parent / "version.txt"
|
|
132
|
+
if vf.is_file():
|
|
133
|
+
return vf.read_text(encoding="utf-8").strip()
|
|
134
|
+
try:
|
|
135
|
+
import importlib.metadata as _imd
|
|
136
|
+
|
|
137
|
+
return _imd.version("code-analysis-client")
|
|
138
|
+
except Exception:
|
|
139
|
+
return "0.0.0"
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
__version__ = _read_package_version()
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Check-chain commands on top of CodeAnalysisAsyncClient.
|
|
3
|
+
|
|
4
|
+
Typed wrappers for ``check_chain_list``, ``check_chain_get`` and
|
|
5
|
+
``file_check_and_promote`` with full parameter mapping and result/error mapping:
|
|
6
|
+
diagnostics, degraded outcomes, sync conflicts and the closed error enum of
|
|
7
|
+
``file_check_and_promote``.
|
|
8
|
+
|
|
9
|
+
Author: Vasiliy Zdanovskiy
|
|
10
|
+
email: vasilyvz@gmail.com
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import re
|
|
16
|
+
from dataclasses import dataclass, field
|
|
17
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
|
18
|
+
|
|
19
|
+
from code_analysis_client.exceptions import ClientValidationError
|
|
20
|
+
from code_analysis_client.responses import command_error_code, FacadeCaller
|
|
21
|
+
from code_analysis_client.worktree import WorktreeConflictError
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from code_analysis_client.client import CodeAnalysisAsyncClient
|
|
25
|
+
|
|
26
|
+
EXTENSION_PATTERN = r"^\.[A-Za-z0-9]+(\.[A-Za-z0-9]+)*$"
|
|
27
|
+
_EXTENSION_RE = re.compile(EXTENSION_PATTERN)
|
|
28
|
+
|
|
29
|
+
FCP_ERROR_CODES = frozenset(
|
|
30
|
+
{
|
|
31
|
+
"VALIDATION_ERROR",
|
|
32
|
+
"PROJECT_NOT_FOUND",
|
|
33
|
+
"PROJECT_LOCKED",
|
|
34
|
+
"SESSION_NOT_FOUND",
|
|
35
|
+
"WORKTREE_CREATE_FAILED",
|
|
36
|
+
"WORKTREE_CREATE_TIMEOUT",
|
|
37
|
+
"WORKTREE_SYNC_CONFLICT",
|
|
38
|
+
"WORKTREE_MERGE_IN_PROGRESS",
|
|
39
|
+
"WORKTREE_REBASE_IN_PROGRESS",
|
|
40
|
+
"FCP_WRITE_FAILED",
|
|
41
|
+
"CHECK_CHAIN_CONFIG_INVALID",
|
|
42
|
+
"FCP_CHECK_TOOL_CRASHED",
|
|
43
|
+
"FCP_CHECK_TOOL_TIMEOUT",
|
|
44
|
+
"FCP_CHECK_TOOL_INSTALL_FAILED",
|
|
45
|
+
"WORKTREE_COMMIT_FAILED",
|
|
46
|
+
"FCP_PROMOTE_RACE",
|
|
47
|
+
"FCP_CANCELLED_BY_CLEANUP",
|
|
48
|
+
"GIT_NOT_AVAILABLE",
|
|
49
|
+
"GIT_NOT_A_REPO",
|
|
50
|
+
"FILE_LOCKED",
|
|
51
|
+
"LOCK_ACQUIRE_FAILED",
|
|
52
|
+
"LOCK_FILE_PATH_FORBIDDEN",
|
|
53
|
+
}
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class CheckChainError(ClientValidationError):
|
|
58
|
+
"""A check-chain command answered with a server error.
|
|
59
|
+
|
|
60
|
+
Attributes:
|
|
61
|
+
code: the server error code (e.g. ``FCP_PROMOTE_RACE``).
|
|
62
|
+
details: the server error details/data object.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
def __init__(
|
|
66
|
+
self,
|
|
67
|
+
message: str,
|
|
68
|
+
*,
|
|
69
|
+
code: str,
|
|
70
|
+
field: Optional[str] = None,
|
|
71
|
+
details: Optional[Dict[str, Any]] = None,
|
|
72
|
+
) -> None:
|
|
73
|
+
"""Store the server error code with the message and details."""
|
|
74
|
+
super().__init__(message, field=field or "command", details=details)
|
|
75
|
+
self.code = code
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class CheckChainConfigInvalidError(CheckChainError):
|
|
79
|
+
"""CHECK_CHAIN_CONFIG_INVALID with its section, extension and reason."""
|
|
80
|
+
|
|
81
|
+
def __init__(self, message: str, *, details: Optional[Dict[str, Any]] = None) -> None:
|
|
82
|
+
"""Expose details.section/extension/reason as attributes."""
|
|
83
|
+
super().__init__(
|
|
84
|
+
message, code="CHECK_CHAIN_CONFIG_INVALID", field="check_chains", details=details
|
|
85
|
+
)
|
|
86
|
+
self.section = self.details.get("section")
|
|
87
|
+
self.extension = self.details.get("extension")
|
|
88
|
+
self.reason = self.details.get("reason")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@dataclass(frozen=True)
|
|
92
|
+
class ChainElement:
|
|
93
|
+
"""One check-chain element."""
|
|
94
|
+
|
|
95
|
+
tool: str
|
|
96
|
+
config_source: str
|
|
97
|
+
blocking: bool
|
|
98
|
+
args: List[str] = field(default_factory=list)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@dataclass(frozen=True)
|
|
102
|
+
class ExtensionChain:
|
|
103
|
+
"""Resolved chain of one extension."""
|
|
104
|
+
|
|
105
|
+
extension: str
|
|
106
|
+
source: str
|
|
107
|
+
chain: List[ChainElement]
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
@dataclass(frozen=True)
|
|
111
|
+
class CheckChainListResult:
|
|
112
|
+
"""Result of ``check_chain_list``."""
|
|
113
|
+
|
|
114
|
+
project_id: str
|
|
115
|
+
chains: List[ExtensionChain]
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@dataclass(frozen=True)
|
|
119
|
+
class CheckChainGetResult:
|
|
120
|
+
"""Result of ``check_chain_get``."""
|
|
121
|
+
|
|
122
|
+
project_id: str
|
|
123
|
+
extension: str
|
|
124
|
+
source: str
|
|
125
|
+
chain: List[ChainElement]
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@dataclass(frozen=True)
|
|
129
|
+
class Diagnostic:
|
|
130
|
+
"""One finding of a chain element."""
|
|
131
|
+
|
|
132
|
+
tool: str
|
|
133
|
+
path: str
|
|
134
|
+
line: Optional[int]
|
|
135
|
+
column: Optional[int]
|
|
136
|
+
code: Optional[str]
|
|
137
|
+
severity: Optional[str]
|
|
138
|
+
message: str
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@dataclass(frozen=True)
|
|
142
|
+
class Degraded:
|
|
143
|
+
"""Why a call degraded (nothing promoted)."""
|
|
144
|
+
|
|
145
|
+
reason: str
|
|
146
|
+
tool: Optional[str] = None
|
|
147
|
+
skipped_tools: List[str] = field(default_factory=list)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@dataclass(frozen=True)
|
|
151
|
+
class FileCheckAndPromoteResult:
|
|
152
|
+
"""Result of ``file_check_and_promote`` (outcome-discriminated).
|
|
153
|
+
|
|
154
|
+
``outcome`` is ``promoted``, ``diagnostics`` or ``degraded``. ``promoted`` is
|
|
155
|
+
True only for ``promoted``. ``degraded`` may also be set on a promoted outcome
|
|
156
|
+
when only non-blocking tools were skipped (``skipped_tools``).
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
project_id: str
|
|
160
|
+
editor_session_id: str
|
|
161
|
+
path: str
|
|
162
|
+
outcome: str
|
|
163
|
+
promoted: bool
|
|
164
|
+
commit: Optional[str]
|
|
165
|
+
index_pending: bool
|
|
166
|
+
index_job_id: Optional[str]
|
|
167
|
+
index_trigger_error: Optional[str]
|
|
168
|
+
diagnostics: List[Diagnostic]
|
|
169
|
+
degraded: Optional[Degraded]
|
|
170
|
+
raw: Dict[str, Any] = field(default_factory=dict, compare=False, repr=False)
|
|
171
|
+
# Delivered write-back of a promoted documentation copy: {document_id, file_path,
|
|
172
|
+
# status "transferred"}; None for an ordinary file. A failed write-back is not a result:
|
|
173
|
+
# the server answers an error (CheckChainError, e.g. DOCUMENTATION_SERVER_UNAVAILABLE)
|
|
174
|
+
# whose details carry promoted, commit, edit_kept and holder_session_id.
|
|
175
|
+
documentation_transfer: Optional[Dict[str, Any]] = None
|
|
176
|
+
# Seconds spent waiting for the path lock and the project main lock
|
|
177
|
+
# ({path_waited_seconds, main_waited_seconds}); None from an older server.
|
|
178
|
+
lock_wait: Optional[Dict[str, float]] = None
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _require_non_empty(value: Any, *, field_name: str) -> str:
|
|
182
|
+
"""Return stripped text or raise ClientValidationError."""
|
|
183
|
+
text = str(value or "").strip()
|
|
184
|
+
if not text:
|
|
185
|
+
raise ClientValidationError(f"{field_name} is required", field=field_name)
|
|
186
|
+
return text
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _error_parts(data: Dict[str, Any]) -> tuple[str, str, Dict[str, Any]]:
|
|
190
|
+
"""Return (code, message, details) of a failed response."""
|
|
191
|
+
code = command_error_code(data)
|
|
192
|
+
err = data.get("error")
|
|
193
|
+
err_dict: Dict[str, Any] = err if isinstance(err, dict) else {}
|
|
194
|
+
message = data.get("message") or err_dict.get("message") or code or str(data)
|
|
195
|
+
details = (
|
|
196
|
+
err_dict.get("details")
|
|
197
|
+
or err_dict.get("data")
|
|
198
|
+
or data.get("details")
|
|
199
|
+
or data.get("data")
|
|
200
|
+
or {}
|
|
201
|
+
)
|
|
202
|
+
return code, str(message), details if isinstance(details, dict) else {}
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _success_payload(data: Dict[str, Any], *, command: str) -> Dict[str, Any]:
|
|
206
|
+
"""Return the success data dict, or raise the mapped error for a failure."""
|
|
207
|
+
if data.get("success"):
|
|
208
|
+
inner = data.get("data")
|
|
209
|
+
return inner if isinstance(inner, dict) else data
|
|
210
|
+
code, message, details = _error_parts(data)
|
|
211
|
+
if code == "WORKTREE_SYNC_CONFLICT":
|
|
212
|
+
raise WorktreeConflictError(
|
|
213
|
+
message,
|
|
214
|
+
conflicts=details.get("conflicts"),
|
|
215
|
+
preserved_locked_files=details.get("preserved_locked_files"),
|
|
216
|
+
)
|
|
217
|
+
if code == "CHECK_CHAIN_CONFIG_INVALID":
|
|
218
|
+
raise CheckChainConfigInvalidError(message, details=details)
|
|
219
|
+
raise CheckChainError(
|
|
220
|
+
message, code=code or "UNKNOWN", field=details.get("field") or command, details=details
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _element(raw: Any) -> ChainElement:
|
|
225
|
+
"""Map one raw chain element."""
|
|
226
|
+
item = raw if isinstance(raw, dict) else {}
|
|
227
|
+
return ChainElement(
|
|
228
|
+
tool=str(item.get("tool", "")),
|
|
229
|
+
config_source=str(item.get("config_source", "auto")),
|
|
230
|
+
blocking=bool(item.get("blocking", False)),
|
|
231
|
+
args=[str(a) for a in (item.get("args") or [])],
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _diagnostic(raw: Any) -> Diagnostic:
|
|
236
|
+
"""Map one raw diagnostic."""
|
|
237
|
+
item = raw if isinstance(raw, dict) else {}
|
|
238
|
+
return Diagnostic(
|
|
239
|
+
tool=str(item.get("tool", "")),
|
|
240
|
+
path=str(item.get("path", "")),
|
|
241
|
+
line=item.get("line"),
|
|
242
|
+
column=item.get("column"),
|
|
243
|
+
code=item.get("code"),
|
|
244
|
+
severity=item.get("severity"),
|
|
245
|
+
message=str(item.get("message", "")),
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def _degraded(raw: Any) -> Optional[Degraded]:
|
|
250
|
+
"""Map the degraded object (None when absent)."""
|
|
251
|
+
if not isinstance(raw, dict):
|
|
252
|
+
return None
|
|
253
|
+
return Degraded(
|
|
254
|
+
reason=str(raw.get("reason", "")),
|
|
255
|
+
tool=raw.get("tool"),
|
|
256
|
+
skipped_tools=[str(t) for t in (raw.get("skipped_tools") or [])],
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
class CheckChainClient:
|
|
261
|
+
"""check_chain_list / check_chain_get / file_check_and_promote."""
|
|
262
|
+
|
|
263
|
+
__slots__ = ("_client",)
|
|
264
|
+
|
|
265
|
+
def __init__(self, client: CodeAnalysisAsyncClient) -> None:
|
|
266
|
+
"""Store the underlying async client."""
|
|
267
|
+
self._client = FacadeCaller(client) # type: ignore[assignment]
|
|
268
|
+
|
|
269
|
+
async def list(self, project_id: str) -> CheckChainListResult:
|
|
270
|
+
"""Return the resolved chain of every configured extension.
|
|
271
|
+
|
|
272
|
+
Calls ``check_chain_list``. Raises CheckChainConfigInvalidError for a
|
|
273
|
+
malformed registry entry and CheckChainError for other server errors.
|
|
274
|
+
"""
|
|
275
|
+
project_id = _require_non_empty(project_id, field_name="project_id")
|
|
276
|
+
data = await self._client.call_validated(
|
|
277
|
+
"check_chain_list", {"project_id": project_id}
|
|
278
|
+
)
|
|
279
|
+
payload = _success_payload(data, command="check_chain_list")
|
|
280
|
+
return CheckChainListResult(
|
|
281
|
+
project_id=str(payload.get("project_id", project_id)),
|
|
282
|
+
chains=[
|
|
283
|
+
ExtensionChain(
|
|
284
|
+
extension=str(entry.get("extension", "")),
|
|
285
|
+
source=str(entry.get("source", "empty")),
|
|
286
|
+
chain=[_element(e) for e in (entry.get("chain") or [])],
|
|
287
|
+
)
|
|
288
|
+
for entry in (payload.get("chains") or [])
|
|
289
|
+
if isinstance(entry, dict)
|
|
290
|
+
],
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
async def get(self, project_id: str, extension: str) -> CheckChainGetResult:
|
|
294
|
+
"""Return the resolved chain of one extension (leading dot required).
|
|
295
|
+
|
|
296
|
+
Calls ``check_chain_get``. An unconfigured extension is source ``empty``
|
|
297
|
+
with an empty chain.
|
|
298
|
+
"""
|
|
299
|
+
project_id = _require_non_empty(project_id, field_name="project_id")
|
|
300
|
+
extension = _require_non_empty(extension, field_name="extension")
|
|
301
|
+
if not _EXTENSION_RE.match(extension):
|
|
302
|
+
raise ClientValidationError(
|
|
303
|
+
f"extension must match {EXTENSION_PATTERN}", field="extension"
|
|
304
|
+
)
|
|
305
|
+
data = await self._client.call_validated(
|
|
306
|
+
"check_chain_get", {"project_id": project_id, "extension": extension}
|
|
307
|
+
)
|
|
308
|
+
payload = _success_payload(data, command="check_chain_get")
|
|
309
|
+
return CheckChainGetResult(
|
|
310
|
+
project_id=str(payload.get("project_id", project_id)),
|
|
311
|
+
extension=str(payload.get("extension", extension)),
|
|
312
|
+
source=str(payload.get("source", "empty")),
|
|
313
|
+
chain=[_element(e) for e in (payload.get("chain") or [])],
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
async def file_check_and_promote(
|
|
317
|
+
self,
|
|
318
|
+
project_id: str,
|
|
319
|
+
editor_session_id: str,
|
|
320
|
+
path: str,
|
|
321
|
+
content: str,
|
|
322
|
+
*,
|
|
323
|
+
check_only: bool = False,
|
|
324
|
+
) -> FileCheckAndPromoteResult:
|
|
325
|
+
"""Write, check and promote one file through the editor's worktree.
|
|
326
|
+
|
|
327
|
+
Calls ``file_check_and_promote``. Diagnostics and degraded outcomes are
|
|
328
|
+
successful results (``outcome`` / ``promoted``), never exceptions.
|
|
329
|
+
``check_only=True`` checks the file in a temporary worktree and promotes
|
|
330
|
+
nothing (``promoted`` False, ``commit`` None).
|
|
331
|
+
Raises WorktreeConflictError on WORKTREE_SYNC_CONFLICT (with
|
|
332
|
+
``conflicts`` and ``preserved_locked_files``), CheckChainConfigInvalidError
|
|
333
|
+
for a malformed registry entry, and CheckChainError (``code`` from the
|
|
334
|
+
closed enum, e.g. FCP_PROMOTE_RACE) for every other server error. A
|
|
335
|
+
documentation copy whose single write-back failed raises CheckChainError
|
|
336
|
+
too (DOCUMENTATION_SERVER_UNAVAILABLE, NOT_FOUND or INTERNAL_ERROR) although
|
|
337
|
+
the commit is promoted; the edit and the lock stay with the session.
|
|
338
|
+
"""
|
|
339
|
+
project_id = _require_non_empty(project_id, field_name="project_id")
|
|
340
|
+
editor_session_id = _require_non_empty(
|
|
341
|
+
editor_session_id, field_name="editor_session_id"
|
|
342
|
+
)
|
|
343
|
+
path = _require_non_empty(path, field_name="path")
|
|
344
|
+
if not isinstance(content, str):
|
|
345
|
+
raise ClientValidationError("content must be a string", field="content")
|
|
346
|
+
if not isinstance(check_only, bool):
|
|
347
|
+
raise ClientValidationError("check_only must be a bool", field="check_only")
|
|
348
|
+
params: Dict[str, Any] = {
|
|
349
|
+
"project_id": project_id,
|
|
350
|
+
"editor_session_id": editor_session_id,
|
|
351
|
+
"path": path,
|
|
352
|
+
"content": content,
|
|
353
|
+
}
|
|
354
|
+
if check_only:
|
|
355
|
+
params["check_only"] = True
|
|
356
|
+
data = await self._client.call_validated("file_check_and_promote", params)
|
|
357
|
+
payload = _success_payload(data, command="file_check_and_promote")
|
|
358
|
+
outcome = str(payload.get("outcome", ""))
|
|
359
|
+
return FileCheckAndPromoteResult(
|
|
360
|
+
project_id=str(payload.get("project_id", project_id)),
|
|
361
|
+
editor_session_id=str(payload.get("editor_session_id", editor_session_id)),
|
|
362
|
+
path=str(payload.get("path", path)),
|
|
363
|
+
outcome=outcome,
|
|
364
|
+
promoted=bool(payload.get("promoted", outcome == "promoted")),
|
|
365
|
+
commit=payload.get("commit"),
|
|
366
|
+
index_pending=bool(payload.get("index_pending", False)),
|
|
367
|
+
index_job_id=payload.get("index_job_id"),
|
|
368
|
+
index_trigger_error=payload.get("index_trigger_error"),
|
|
369
|
+
diagnostics=[_diagnostic(d) for d in (payload.get("diagnostics") or [])],
|
|
370
|
+
degraded=_degraded(payload.get("degraded")),
|
|
371
|
+
raw=payload,
|
|
372
|
+
documentation_transfer=payload.get("documentation_transfer"),
|
|
373
|
+
lock_wait=payload.get("lock_wait"),
|
|
374
|
+
)
|