code-analysis-client 1.6.188__tar.gz → 1.6.191__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 (28) hide show
  1. {code_analysis_client-1.6.188 → code_analysis_client-1.6.191}/PKG-INFO +1 -1
  2. {code_analysis_client-1.6.188 → code_analysis_client-1.6.191}/code_analysis_client/__init__.py +20 -0
  3. {code_analysis_client-1.6.188 → code_analysis_client-1.6.191}/code_analysis_client/exceptions.py +56 -0
  4. {code_analysis_client-1.6.188 → code_analysis_client-1.6.191}/code_analysis_client/responses.py +30 -1
  5. code_analysis_client-1.6.191/code_analysis_client/vectorizer_state.py +159 -0
  6. code_analysis_client-1.6.191/code_analysis_client/version.txt +1 -0
  7. {code_analysis_client-1.6.188 → code_analysis_client-1.6.191}/code_analysis_client.egg-info/PKG-INFO +1 -1
  8. {code_analysis_client-1.6.188 → code_analysis_client-1.6.191}/code_analysis_client.egg-info/SOURCES.txt +1 -0
  9. code_analysis_client-1.6.188/code_analysis_client/version.txt +0 -1
  10. {code_analysis_client-1.6.188 → code_analysis_client-1.6.191}/README.md +0 -0
  11. {code_analysis_client-1.6.188 → code_analysis_client-1.6.191}/code_analysis_client/check_chain.py +0 -0
  12. {code_analysis_client-1.6.188 → code_analysis_client-1.6.191}/code_analysis_client/client.py +0 -0
  13. {code_analysis_client-1.6.188 → code_analysis_client-1.6.191}/code_analysis_client/commands_proxy.py +0 -0
  14. {code_analysis_client-1.6.188 → code_analysis_client-1.6.191}/code_analysis_client/config.py +0 -0
  15. {code_analysis_client-1.6.188 → code_analysis_client-1.6.191}/code_analysis_client/file_session.py +0 -0
  16. {code_analysis_client-1.6.188 → code_analysis_client-1.6.191}/code_analysis_client/py.typed +0 -0
  17. {code_analysis_client-1.6.188 → code_analysis_client-1.6.191}/code_analysis_client/queue_wait.py +0 -0
  18. {code_analysis_client-1.6.188 → code_analysis_client-1.6.191}/code_analysis_client/schema_disk_cache.py +0 -0
  19. {code_analysis_client-1.6.188 → code_analysis_client-1.6.191}/code_analysis_client/server_api.py +0 -0
  20. {code_analysis_client-1.6.188 → code_analysis_client-1.6.191}/code_analysis_client/server_schema.py +0 -0
  21. {code_analysis_client-1.6.188 → code_analysis_client-1.6.191}/code_analysis_client/universal_file.py +0 -0
  22. {code_analysis_client-1.6.188 → code_analysis_client-1.6.191}/code_analysis_client/validation.py +0 -0
  23. {code_analysis_client-1.6.188 → code_analysis_client-1.6.191}/code_analysis_client/worktree.py +0 -0
  24. {code_analysis_client-1.6.188 → code_analysis_client-1.6.191}/code_analysis_client.egg-info/dependency_links.txt +0 -0
  25. {code_analysis_client-1.6.188 → code_analysis_client-1.6.191}/code_analysis_client.egg-info/requires.txt +0 -0
  26. {code_analysis_client-1.6.188 → code_analysis_client-1.6.191}/code_analysis_client.egg-info/top_level.txt +0 -0
  27. {code_analysis_client-1.6.188 → code_analysis_client-1.6.191}/pyproject.toml +0 -0
  28. {code_analysis_client-1.6.188 → code_analysis_client-1.6.191}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-analysis-client
3
- Version: 1.6.188
3
+ Version: 1.6.191
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
@@ -22,6 +22,7 @@ from code_analysis_client.exceptions import (
22
22
  JobFailedError,
23
23
  JobTimeoutError,
24
24
  QueueJobError,
25
+ VectorizerUnavailableError,
25
26
  )
26
27
  from code_analysis_client.file_session import FileSessionClient, SessionNotFoundError
27
28
  from code_analysis_client.check_chain import (
@@ -63,6 +64,16 @@ from code_analysis_client.validation import (
63
64
  prepare_params_for_schema,
64
65
  validate_params_against_schema,
65
66
  )
67
+ from code_analysis_client.vectorizer_state import (
68
+ ServiceState,
69
+ VectorizerState,
70
+ VectorizerWarning,
71
+ VectorizationWorkerBlock,
72
+ extract_vectorizer_state_from_response,
73
+ safe_get_vectorizer_state,
74
+ safe_get_warnings,
75
+ safe_get_vectorization_worker,
76
+ )
66
77
 
67
78
  __all__ = [
68
79
  "CLIENT_FACADE_COMMANDS",
@@ -84,6 +95,11 @@ __all__ = [
84
95
  "UNIVERSAL_FILE_COMMANDS",
85
96
  "UniversalFileClient",
86
97
  "ValidatedCommandsProxy",
98
+ "VectorizerUnavailableError",
99
+ "ServiceState",
100
+ "VectorizerState",
101
+ "VectorizerWarning",
102
+ "VectorizationWorkerBlock",
87
103
  "CheckChainClient",
88
104
  "CheckChainConfigInvalidError",
89
105
  "CheckChainError",
@@ -99,11 +115,15 @@ __all__ = [
99
115
  "adapter_settings_from_server_config",
100
116
  "adapter_settings_to_jsonrpc_kwargs",
101
117
  "extract_job_id",
118
+ "extract_vectorizer_state_from_response",
102
119
  "fetch_command_schema_from_server",
103
120
  "is_queued_envelope",
104
121
  "load_server_config",
105
122
  "parse_schema_from_help_payload",
106
123
  "prepare_params_for_schema",
124
+ "safe_get_vectorizer_state",
125
+ "safe_get_warnings",
126
+ "safe_get_vectorization_worker",
107
127
  "unwrap_job_result",
108
128
  "validate_params_against_schema",
109
129
  "wait_for_job",
@@ -108,3 +108,59 @@ class CommandFailedError(QueueJobError):
108
108
  )
109
109
  self.command = command
110
110
  self.error = error
111
+
112
+
113
+ class VectorizerUnavailableError(RuntimeError):
114
+ """The vectorizer (embedding or chunker service) is not available.
115
+
116
+ Raised by vectorizer-dependent commands (semantic_search, revectorize,
117
+ rebuild_faiss) when the embedding or chunker service is known unavailable.
118
+ The pre-V1 contract EMBEDDING_SERVICE_ERROR is reserved for a failure of
119
+ the call itself, not for unavailability of a known-down service.
120
+ """
121
+
122
+ code = "VECTORIZER_UNAVAILABLE"
123
+
124
+ def __init__(
125
+ self,
126
+ message: str,
127
+ *,
128
+ service: Optional[str] = None,
129
+ services: Optional[list[str]] = None,
130
+ reason: Optional[str] = None,
131
+ since: Optional[str] = None,
132
+ last_probe_at: Optional[str] = None,
133
+ retry_after_seconds: Optional[int] = None,
134
+ details: Optional[Dict[str, Any]] = None,
135
+ ) -> None:
136
+ """Initialize with message and vectorizer availability details.
137
+
138
+ Args:
139
+ message: Human-readable error message
140
+ service: The first unavailable required service (embedding or chunker)
141
+ services: List of all unavailable required services
142
+ reason: Reason for unavailability (e.g., timeout, connection_refused)
143
+ since: ISO-8601 UTC timestamp of when the service became unavailable
144
+ last_probe_at: ISO-8601 UTC timestamp of the last probe attempt
145
+ retry_after_seconds: Suggested wait time before retrying
146
+ details: Additional details dict (overrides individual fields if provided)
147
+ """
148
+ super().__init__(message)
149
+ if details is None:
150
+ details = {}
151
+ self.service = service or details.get("service")
152
+ self.services = services or details.get("services", [])
153
+ self.reason = reason or details.get("reason")
154
+ self.since = since or details.get("since")
155
+ self.last_probe_at = last_probe_at or details.get("last_probe_at")
156
+ self.retry_after_seconds = retry_after_seconds or details.get(
157
+ "retry_after_seconds"
158
+ )
159
+ self.details = {
160
+ "service": self.service,
161
+ "services": self.services,
162
+ "reason": self.reason,
163
+ "since": self.since,
164
+ "last_probe_at": self.last_probe_at,
165
+ "retry_after_seconds": self.retry_after_seconds,
166
+ }
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  from typing import Any, Dict
6
6
 
7
- from code_analysis_client.exceptions import ClientValidationError
7
+ from code_analysis_client.exceptions import ClientValidationError, VectorizerUnavailableError
8
8
 
9
9
 
10
10
  def command_error_code(data: Dict[str, Any]) -> str:
@@ -24,6 +24,23 @@ def command_error_code(data: Dict[str, Any]) -> str:
24
24
  return ""
25
25
 
26
26
 
27
+ def vectorizer_error_details(data: Dict[str, Any]) -> Dict[str, Any]:
28
+ """Return the ``details`` object of a failed command response.
29
+
30
+ The server (mcp-proxy-adapter ``ErrorResult``) puts the details at ``error.data``; a flat
31
+ envelope may carry ``details`` at the top level or ``error.details``. All three are read, in
32
+ the order ``error.data``, ``error.details``, top-level ``details``.
33
+ """
34
+ err = data.get("error")
35
+ if isinstance(err, dict):
36
+ for key in ("data", "details"):
37
+ nested = err.get(key)
38
+ if isinstance(nested, dict):
39
+ return nested
40
+ top = data.get("details")
41
+ return top if isinstance(top, dict) else {}
42
+
43
+
27
44
  def unwrap_command_result(
28
45
  data: Dict[str, Any],
29
46
  *,
@@ -42,4 +59,16 @@ def unwrap_command_result(
42
59
  message = str(data)
43
60
  if code == "SESSION_NOT_FOUND" and session_not_found_type is not None:
44
61
  raise session_not_found_type(message, field="session_id", details=data)
62
+ if code == "VECTORIZER_UNAVAILABLE":
63
+ details = vectorizer_error_details(data)
64
+ raise VectorizerUnavailableError(
65
+ message,
66
+ service=details.get("service"),
67
+ services=details.get("services", []),
68
+ reason=details.get("reason"),
69
+ since=details.get("since"),
70
+ last_probe_at=details.get("last_probe_at"),
71
+ retry_after_seconds=details.get("retry_after_seconds"),
72
+ details=details,
73
+ )
45
74
  raise ClientValidationError(message, field="command", details=data)
@@ -0,0 +1,159 @@
1
+ """Typed models for vectorizer availability state from health and get_worker_status.
2
+
3
+ The vectorizer_availability section of pipeline/matrix.yaml defines the contract.
4
+ Clients must tolerate absence of these fields (deployed 1.6.190 server does not emit
5
+ them yet) without raising.
6
+
7
+ Author: Vasiliy Zdanovskiy
8
+ email: vasilyvz@gmail.com
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from typing import Any, Dict, List, Optional, TypedDict
14
+
15
+
16
+ class ServiceState(TypedDict, total=False):
17
+ """State of one vectorizer service (embedding or chunker).
18
+
19
+ All fields are optional to tolerate absence in pre-V1 deployments.
20
+ """
21
+
22
+ state: str # "available", "unavailable", or "unknown"
23
+ since: str # ISO-8601 UTC timestamp
24
+ last_probe_at: Optional[str] # ISO-8601 UTC timestamp or null
25
+ last_ok_at: Optional[str] # ISO-8601 UTC timestamp or null
26
+ reason: Optional[str] # One of reason_enum or null
27
+ consecutive_failures: int
28
+
29
+
30
+ class VectorizerState(TypedDict, total=False):
31
+ """Shared vectorizer availability state read by health and get_worker_status.
32
+
33
+ All fields are optional to tolerate absence in pre-V1 deployments.
34
+ Fields are presented as TypedDict with total=False; callers use .get()
35
+ to safely access optional fields.
36
+ """
37
+
38
+ state: str # "available", "unavailable", or "unknown"
39
+ paused: bool # True when state == unavailable
40
+ paused_reason: Optional[str] # Reason from the earliest-failing unavailable service
41
+ paused_since: Optional[str] # ISO-8601 UTC, from the earliest-failing service
42
+ services: Dict[str, ServiceState] # {embedding: ..., chunker: ...}
43
+ probe_interval_seconds: int
44
+ probe_timeout_seconds: int
45
+ staleness_bound_seconds: int
46
+
47
+
48
+ class VectorizerWarning(TypedDict, total=False):
49
+ """A warning from health.data.warnings or get_worker_status.data.warnings.
50
+
51
+ Only populated for VECTORIZER_UNAVAILABLE warnings.
52
+ All fields are optional to tolerate absence.
53
+ """
54
+
55
+ code: str # "VECTORIZER_UNAVAILABLE"
56
+ service: str # "embedding" or "chunker"
57
+ since: str # ISO-8601 UTC timestamp
58
+ reason: Optional[str] # Reason enum value
59
+ message: str # Human-readable text
60
+
61
+
62
+ class VectorizationWorkerBlock(TypedDict, total=False):
63
+ """The vectorization worker block from get_worker_status.
64
+
65
+ Present when worker_type in {vectorization, all} or omitted (defaults to all).
66
+ All fields are optional to tolerate absence in pre-V1 deployments.
67
+ """
68
+
69
+ paused: bool # True when vectorizer state == unavailable
70
+ paused_reason: Optional[str] # Reason from the unavailable service
71
+ paused_since: Optional[str] # ISO-8601 UTC
72
+ last_heartbeat_at: str # ISO-8601 UTC
73
+ heartbeat_age_seconds: float # Age of the last heartbeat
74
+ rpc_in_flight: Optional[Dict[str, Any]] # {service, started_at, elapsed_seconds}
75
+ dead_letter: Dict[str, Any] # {total, by_reason}
76
+
77
+
78
+ def safe_get_vectorizer_state(data: Any) -> Optional[VectorizerState]:
79
+ """Extract vectorizer state from a response dict, tolerating absence.
80
+
81
+ Supports both response formats:
82
+ - health: data.components.vectorizer
83
+ - get_worker_status: data.vectorizer
84
+
85
+ Args:
86
+ data: Response dict (e.g., from health or get_worker_status)
87
+
88
+ Returns:
89
+ VectorizerState dict if present, None if absent
90
+ """
91
+ if not isinstance(data, dict):
92
+ return None
93
+
94
+ # Try direct path first (get_worker_status)
95
+ vectorizer = data.get("vectorizer")
96
+ if isinstance(vectorizer, dict):
97
+ return vectorizer # type: ignore
98
+
99
+ # Try components path (health)
100
+ components = data.get("components")
101
+ if isinstance(components, dict):
102
+ vectorizer = components.get("vectorizer")
103
+ if isinstance(vectorizer, dict):
104
+ return vectorizer # type: ignore
105
+
106
+ return None
107
+
108
+
109
+ def safe_get_warnings(data: Any) -> List[VectorizerWarning]:
110
+ """Extract warnings list from a response dict, tolerating absence.
111
+
112
+ Args:
113
+ data: Response dict (e.g., from health or get_worker_status)
114
+
115
+ Returns:
116
+ List of VectorizerWarning dicts, empty list if absent
117
+ """
118
+ if not isinstance(data, dict):
119
+ return []
120
+ warnings = data.get("warnings")
121
+ if not isinstance(warnings, list):
122
+ return []
123
+ return warnings # type: ignore
124
+
125
+
126
+ def safe_get_vectorization_worker(data: Any) -> Optional[VectorizationWorkerBlock]:
127
+ """Extract vectorization worker block from response data, tolerating absence.
128
+
129
+ Args:
130
+ data: Worker status data dict (e.g., from get_worker_status)
131
+
132
+ Returns:
133
+ VectorizationWorkerBlock dict if present, None if absent
134
+ """
135
+ if not isinstance(data, dict):
136
+ return None
137
+ # The worker block is in the response at the same level as other worker types
138
+ # when worker_type is vectorization or all (or omitted which defaults to all)
139
+ worker = data.get("vectorization")
140
+ if not isinstance(worker, dict):
141
+ return None
142
+ return worker # type: ignore
143
+
144
+
145
+ def extract_vectorizer_state_from_response(response: Dict[str, Any]) -> Optional[VectorizerState]:
146
+ """Extract vectorizer state from any response that may contain it.
147
+
148
+ Checks both data.components.vectorizer (health/get_worker_status) paths.
149
+
150
+ Args:
151
+ response: Response dict from a command
152
+
153
+ Returns:
154
+ VectorizerState dict if found, None otherwise
155
+ """
156
+ if not isinstance(response, dict):
157
+ return None
158
+ data = response.get("data")
159
+ return safe_get_vectorizer_state(data)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-analysis-client
3
- Version: 1.6.188
3
+ Version: 1.6.191
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
@@ -15,6 +15,7 @@ code_analysis_client/server_api.py
15
15
  code_analysis_client/server_schema.py
16
16
  code_analysis_client/universal_file.py
17
17
  code_analysis_client/validation.py
18
+ code_analysis_client/vectorizer_state.py
18
19
  code_analysis_client/version.txt
19
20
  code_analysis_client/worktree.py
20
21
  code_analysis_client.egg-info/PKG-INFO