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.
@@ -0,0 +1,98 @@
1
+ """
2
+ Shallow JSON-schema validation aligned with BaseMCPCommand.validate_params_against_schema.
3
+
4
+ Author: Vasiliy Zdanovskiy
5
+ email: vasilyvz@gmail.com
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import Any, Dict
11
+
12
+ from code_analysis_client.exceptions import ClientValidationError
13
+
14
+
15
+ def prepare_params_for_schema(
16
+ params: Dict[str, Any], schema: Dict[str, Any] # noqa: ARG001
17
+ ) -> Dict[str, Any]:
18
+ """Return a shallow copy of params for validation and dispatch."""
19
+ return dict(params)
20
+
21
+
22
+ def validate_params_against_schema(
23
+ params: Dict[str, Any],
24
+ schema: Dict[str, Any],
25
+ command_name: str = "command",
26
+ ) -> None:
27
+ """Validate params against a command ``get_schema()``-style object (subset)."""
28
+ if not isinstance(params, dict):
29
+ raise ClientValidationError(
30
+ f"{command_name}: params must be a dict, got {type(params).__name__}",
31
+ field="params",
32
+ )
33
+ props = schema.get("properties") or {}
34
+ additional_ok = schema.get("additionalProperties", False)
35
+ required_set = set(schema.get("required") or [])
36
+ for key, value in params.items():
37
+ if key not in props:
38
+ if not additional_ok:
39
+ raise ClientValidationError(
40
+ f"{command_name}: unknown parameter {key!r}. "
41
+ "Only schema-defined properties are allowed.",
42
+ field=key,
43
+ details={"allowed": list(props.keys())},
44
+ )
45
+ continue
46
+ if value is None:
47
+ continue
48
+ prop = props[key]
49
+ expected_type = prop.get("type")
50
+ if expected_type == "string":
51
+ if not isinstance(value, str):
52
+ raise ClientValidationError(
53
+ f"{command_name}: parameter {key!r} must be string, got {type(value).__name__}",
54
+ field=key,
55
+ )
56
+ elif expected_type == "integer":
57
+ if not isinstance(value, int) or isinstance(value, bool):
58
+ raise ClientValidationError(
59
+ f"{command_name}: parameter {key!r} must be integer, got {type(value).__name__}",
60
+ field=key,
61
+ )
62
+ elif expected_type == "number":
63
+ if not isinstance(value, (int, float)) or isinstance(value, bool):
64
+ raise ClientValidationError(
65
+ f"{command_name}: parameter {key!r} must be number, got {type(value).__name__}",
66
+ field=key,
67
+ )
68
+ elif expected_type == "boolean":
69
+ if not isinstance(value, bool):
70
+ raise ClientValidationError(
71
+ f"{command_name}: parameter {key!r} must be boolean, got {type(value).__name__}",
72
+ field=key,
73
+ )
74
+ elif expected_type == "array":
75
+ if not isinstance(value, list):
76
+ raise ClientValidationError(
77
+ f"{command_name}: parameter {key!r} must be array, got {type(value).__name__}",
78
+ field=key,
79
+ )
80
+ elif expected_type == "object":
81
+ if not isinstance(value, dict):
82
+ raise ClientValidationError(
83
+ f"{command_name}: parameter {key!r} must be object, got {type(value).__name__}",
84
+ field=key,
85
+ )
86
+ if "enum" in prop and value is not None:
87
+ if value not in prop["enum"]:
88
+ raise ClientValidationError(
89
+ f"{command_name}: parameter {key!r} must be one of {prop['enum']!r}, got {value!r}",
90
+ field=key,
91
+ details={"enum": prop["enum"]},
92
+ )
93
+ for key in required_set:
94
+ if key not in params or params[key] is None:
95
+ raise ClientValidationError(
96
+ f"{command_name}: required parameter {key!r} is missing",
97
+ field=key,
98
+ )
@@ -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)
@@ -0,0 +1 @@
1
+ 1.6.341