coding-client 1.21.2__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.
- coding_client/__init__.py +1 -0
- coding_client/api/__init__.py +19 -0
- coding_client/api/exception_handlers.py +158 -0
- coding_client/api/logging_setup.py +44 -0
- coding_client/api/metrics.py +40 -0
- coding_client/api/openapi_config.py +124 -0
- coding_client/api/request_logging.py +60 -0
- coding_client/api/validation.py +150 -0
- coding_client/auth/__init__.py +78 -0
- coding_client/auth/_default_tenant.py +132 -0
- coding_client/auth/_header_trust.py +98 -0
- coding_client/auth/_onbehalf.py +315 -0
- coding_client/auth/_trusted_networks.py +94 -0
- coding_client/auth/dependencies.py +61 -0
- coding_client/auth/middleware.py +445 -0
- coding_client/auth/models.py +83 -0
- coding_client/auth/outbound.py +137 -0
- coding_client/auth/rbac.py +21 -0
- coding_client/auth/rbac_coding.py +5 -0
- coding_client/auth/tenant.py +650 -0
- coding_client/auth/websocket.py +148 -0
- coding_client/clients/__init__.py +24 -0
- coding_client/clients/base.py +131 -0
- coding_client/clients/coding_ai.py +33 -0
- coding_client/clients/coding_analysis.py +36 -0
- coding_client/clients/coding_containers.py +45 -0
- coding_client/clients/coding_github.py +127 -0
- coding_client/clients/coding_realtime.py +127 -0
- coding_client/clients/coding_security.py +51 -0
- coding_client/clients/coding_testing.py +31 -0
- coding_client/clients/coding_workspaces.py +265 -0
- coding_client/config/__init__.py +12 -0
- coding_client/config/base_settings.py +61 -0
- coding_client/config/yaml_config_loader.py +172 -0
- coding_client/db/__init__.py +42 -0
- coding_client/db/db_types.py +122 -0
- coding_client/db/exceptions.py +59 -0
- coding_client/db/postgres_connection.py +572 -0
- coding_client/db/tenant_query_helper.py +417 -0
- coding_client/db_init.py +49 -0
- coding_client/exceptions/__init__.py +67 -0
- coding_client/exceptions/api_exceptions.py +395 -0
- coding_client/exceptions/pydantic_ai_exceptions.py +533 -0
- coding_client/interfaces/__init__.py +22 -0
- coding_client/interfaces/cache_interface.py +179 -0
- coding_client/interfaces/container_interface.py +49 -0
- coding_client/interfaces/docker_interface.py +283 -0
- coding_client/interfaces/graph_interface.py +28 -0
- coding_client/interfaces/security_interface.py +72 -0
- coding_client/interfaces/webhook_interface.py +67 -0
- coding_client/llm/__init__.py +54 -0
- coding_client/llm/base_agent.py +195 -0
- coding_client/llm/context.py +121 -0
- coding_client/llm/responses.py +102 -0
- coding_client/llm/routing.py +82 -0
- coding_client/llm/settings.py +102 -0
- coding_client/models/__init__.py +62 -0
- coding_client/models/agent.py +38 -0
- coding_client/models/ai_test_failure.py +745 -0
- coding_client/models/analysis.py +330 -0
- coding_client/models/architecture.py +685 -0
- coding_client/models/bug.py +128 -0
- coding_client/models/code_graph.py +317 -0
- coding_client/models/container.py +241 -0
- coding_client/models/core.py +878 -0
- coding_client/models/docker_container.py +104 -0
- coding_client/models/github.py +293 -0
- coding_client/models/logging.py +29 -0
- coding_client/models/package_manager.py +257 -0
- coding_client/models/pr_validation.py +440 -0
- coding_client/models/pydantic_ai.py +395 -0
- coding_client/models/repo_scanner.py +249 -0
- coding_client/models/responses.py +431 -0
- coding_client/models/security.py +185 -0
- coding_client/models/static_analysis.py +508 -0
- coding_client/models/system.py +48 -0
- coding_client/models/test_executor.py +204 -0
- coding_client/models/testing.py +673 -0
- coding_client/models/testing_config.py +200 -0
- coding_client/models/validation.py +127 -0
- coding_client/models_init.py +272 -0
- coding_client/telemetry.py +195 -0
- coding_client/testing.py +180 -0
- coding_client/types/__init__.py +130 -0
- coding_client/types/constraint_types.py +276 -0
- coding_client/types/core_types.py +112 -0
- coding_client/types/state_machine.py +443 -0
- coding_client/utils/__init__.py +86 -0
- coding_client/utils/archive_utils.py +387 -0
- coding_client/utils/ast_debug.py +45 -0
- coding_client/utils/async_timeout.py +364 -0
- coding_client/utils/constants.py +518 -0
- coding_client/utils/content_processing.py +326 -0
- coding_client/utils/database.py +916 -0
- coding_client/utils/encoding_helper.py +222 -0
- coding_client/utils/enum_helpers.py +166 -0
- coding_client/utils/file_operations.py +177 -0
- coding_client/utils/filesystem_ops.py +528 -0
- coding_client/utils/http_client.py +562 -0
- coding_client/utils/path_utils.py +471 -0
- coding_client/utils/profiling.py +421 -0
- coding_client/utils/response_builder.py +240 -0
- coding_client/utils/retry_helper.py +712 -0
- coding_client/utils/security_validation.py +423 -0
- coding_client-1.21.2.dist-info/METADATA +234 -0
- coding_client-1.21.2.dist-info/RECORD +109 -0
- coding_client-1.21.2.dist-info/WHEEL +5 -0
- coding_client-1.21.2.dist-info/licenses/LICENSE +201 -0
- coding_client-1.21.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
"""Type-safe state machine for container and execution lifecycle management.
|
|
2
|
+
|
|
3
|
+
This module provides a generic state machine implementation with type safety for managing
|
|
4
|
+
the lifecycle of containers, analyses, and other stateful components in the system.
|
|
5
|
+
|
|
6
|
+
The state machine enforces valid transitions between states and maintains a history of
|
|
7
|
+
all state changes for auditing and debugging purposes.
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
from ..types.state_machine import ContainerStateMachine, AnalysisStateMachine
|
|
11
|
+
|
|
12
|
+
# Create a container state machine
|
|
13
|
+
container_sm = ContainerStateMachine()
|
|
14
|
+
|
|
15
|
+
# Check if transition is valid
|
|
16
|
+
if container_sm.can_transition_to("running"):
|
|
17
|
+
container_sm.transition_to("running")
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import logging
|
|
23
|
+
from collections.abc import Callable
|
|
24
|
+
from dataclasses import dataclass, field
|
|
25
|
+
from datetime import UTC, datetime
|
|
26
|
+
from typing import Any, TypeVar
|
|
27
|
+
|
|
28
|
+
from .core_types import AnalysisState, ContainerState, HealthState, ResponseState
|
|
29
|
+
|
|
30
|
+
logger = logging.getLogger(__name__)
|
|
31
|
+
|
|
32
|
+
StateType = TypeVar("StateType", bound=str)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass(frozen=True)
|
|
36
|
+
class StateTransition[StateType: str]:
|
|
37
|
+
"""Type-safe state transition record.
|
|
38
|
+
|
|
39
|
+
Represents a single state transition with metadata for auditing and debugging.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
from_state: StateType
|
|
43
|
+
to_state: StateType
|
|
44
|
+
timestamp: datetime = field(default_factory=lambda: datetime.now(UTC))
|
|
45
|
+
trigger: str | None = None
|
|
46
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
47
|
+
|
|
48
|
+
def __str__(self) -> str:
|
|
49
|
+
return f"{self.from_state} -> {self.to_state}"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass
|
|
53
|
+
class StateTransitionRule[StateType: str]:
|
|
54
|
+
"""Rule for state transition validation.
|
|
55
|
+
|
|
56
|
+
Defines conditions and actions for valid state transitions.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
from_state: StateType
|
|
60
|
+
to_state: StateType
|
|
61
|
+
condition: Callable[[Any], bool] | None = None
|
|
62
|
+
action: Callable[[Any], None] | None = None
|
|
63
|
+
description: str | None = None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class StateTransitionError(Exception):
|
|
67
|
+
"""Exception raised when an invalid state transition is attempted."""
|
|
68
|
+
|
|
69
|
+
def __init__(self, from_state: str, to_state: str, message: str = ""):
|
|
70
|
+
self.from_state = from_state
|
|
71
|
+
self.to_state = to_state
|
|
72
|
+
super().__init__(f"Invalid transition from {from_state} to {to_state}: {message}")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class StateMachine[StateType: str]:
|
|
76
|
+
"""Generic type-safe state machine.
|
|
77
|
+
|
|
78
|
+
Provides a framework for managing state transitions with validation,
|
|
79
|
+
history tracking, and extensible transition rules.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
def __init__(
|
|
83
|
+
self,
|
|
84
|
+
initial_state: StateType,
|
|
85
|
+
valid_transitions: dict[StateType, frozenset[StateType]],
|
|
86
|
+
transition_rules: list[StateTransitionRule[StateType]] | None = None,
|
|
87
|
+
max_history: int = 100,
|
|
88
|
+
):
|
|
89
|
+
"""Initialize the state machine.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
initial_state: Starting state for the machine
|
|
93
|
+
valid_transitions: Map of valid state transitions
|
|
94
|
+
transition_rules: Optional list of transition rules with conditions
|
|
95
|
+
max_history: Maximum number of transitions to keep in history
|
|
96
|
+
"""
|
|
97
|
+
self._current_state = initial_state
|
|
98
|
+
self._valid_transitions = valid_transitions
|
|
99
|
+
self._transition_rules = transition_rules or []
|
|
100
|
+
self._max_history = max_history
|
|
101
|
+
self._transition_history: list[StateTransition[StateType]] = []
|
|
102
|
+
self._state_change_callbacks: dict[
|
|
103
|
+
StateType, list[Callable[[StateType, StateType], None]]
|
|
104
|
+
] = {}
|
|
105
|
+
|
|
106
|
+
logger.debug(f"State machine initialized with state: {initial_state}")
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def current_state(self) -> StateType:
|
|
110
|
+
"""Get the current state."""
|
|
111
|
+
return self._current_state
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def transition_history(self) -> list[StateTransition[StateType]]:
|
|
115
|
+
"""Get the transition history."""
|
|
116
|
+
return self._transition_history.copy()
|
|
117
|
+
|
|
118
|
+
def can_transition_to(self, target_state: StateType) -> bool:
|
|
119
|
+
"""Check if transition to target state is valid.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
target_state: State to transition to
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
True if transition is valid, False otherwise
|
|
126
|
+
"""
|
|
127
|
+
# Check basic transition validity
|
|
128
|
+
if target_state not in self._valid_transitions.get(self._current_state, frozenset()):
|
|
129
|
+
return False
|
|
130
|
+
|
|
131
|
+
# Check transition rules
|
|
132
|
+
for rule in self._transition_rules:
|
|
133
|
+
if rule.from_state == self._current_state and rule.to_state == target_state:
|
|
134
|
+
if rule.condition and not rule.condition(self):
|
|
135
|
+
return False
|
|
136
|
+
|
|
137
|
+
return True
|
|
138
|
+
|
|
139
|
+
def transition_to(
|
|
140
|
+
self,
|
|
141
|
+
target_state: StateType,
|
|
142
|
+
trigger: str | None = None,
|
|
143
|
+
metadata: dict[str, Any] | None = None,
|
|
144
|
+
) -> bool:
|
|
145
|
+
"""Attempt to transition to target state.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
target_state: State to transition to
|
|
149
|
+
trigger: Optional trigger description
|
|
150
|
+
metadata: Optional metadata for the transition
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
True if transition successful, False otherwise
|
|
154
|
+
|
|
155
|
+
Raises:
|
|
156
|
+
StateTransitionError: If transition is invalid
|
|
157
|
+
"""
|
|
158
|
+
if not self.can_transition_to(target_state):
|
|
159
|
+
raise StateTransitionError(
|
|
160
|
+
self._current_state,
|
|
161
|
+
target_state,
|
|
162
|
+
f"No valid transition from {self._current_state} to {target_state}",
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
previous_state = self._current_state
|
|
166
|
+
|
|
167
|
+
# Execute transition actions
|
|
168
|
+
for rule in self._transition_rules:
|
|
169
|
+
if rule.from_state == previous_state and rule.to_state == target_state:
|
|
170
|
+
if rule.action:
|
|
171
|
+
try:
|
|
172
|
+
rule.action(self)
|
|
173
|
+
except Exception as e:
|
|
174
|
+
logger.error(f"Transition action failed: {e}")
|
|
175
|
+
return False
|
|
176
|
+
|
|
177
|
+
# Update state
|
|
178
|
+
self._current_state = target_state
|
|
179
|
+
|
|
180
|
+
# Record transition
|
|
181
|
+
transition = StateTransition(
|
|
182
|
+
from_state=previous_state,
|
|
183
|
+
to_state=target_state,
|
|
184
|
+
trigger=trigger,
|
|
185
|
+
metadata=metadata or {},
|
|
186
|
+
)
|
|
187
|
+
self._transition_history.append(transition)
|
|
188
|
+
|
|
189
|
+
# Trim history if needed
|
|
190
|
+
if len(self._transition_history) > self._max_history:
|
|
191
|
+
self._transition_history = self._transition_history[-self._max_history :]
|
|
192
|
+
|
|
193
|
+
# Execute callbacks
|
|
194
|
+
self._execute_callbacks(previous_state, target_state)
|
|
195
|
+
|
|
196
|
+
logger.debug(f"State transition: {previous_state} -> {target_state}")
|
|
197
|
+
return True
|
|
198
|
+
|
|
199
|
+
def force_transition_to(self, target_state: StateType, reason: str = "Forced") -> None:
|
|
200
|
+
"""Force a state transition without validation.
|
|
201
|
+
|
|
202
|
+
Use with caution - this bypasses all validation rules.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
target_state: State to transition to
|
|
206
|
+
reason: Reason for forcing the transition
|
|
207
|
+
"""
|
|
208
|
+
logger.warning(
|
|
209
|
+
f"Forced state transition from {self._current_state} to {target_state}: {reason}"
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
previous_state = self._current_state
|
|
213
|
+
self._current_state = target_state
|
|
214
|
+
|
|
215
|
+
# Record forced transition
|
|
216
|
+
transition = StateTransition(
|
|
217
|
+
from_state=previous_state,
|
|
218
|
+
to_state=target_state,
|
|
219
|
+
trigger=f"FORCED: {reason}",
|
|
220
|
+
metadata={"forced": True},
|
|
221
|
+
)
|
|
222
|
+
self._transition_history.append(transition)
|
|
223
|
+
|
|
224
|
+
# Execute callbacks
|
|
225
|
+
self._execute_callbacks(previous_state, target_state)
|
|
226
|
+
|
|
227
|
+
def add_state_change_callback(
|
|
228
|
+
self, state: StateType, callback: Callable[[StateType, StateType], None]
|
|
229
|
+
) -> None:
|
|
230
|
+
"""Add a callback to be executed when transitioning to a specific state.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
state: State to trigger callback on
|
|
234
|
+
callback: Function to call with (from_state, to_state) arguments
|
|
235
|
+
"""
|
|
236
|
+
if state not in self._state_change_callbacks:
|
|
237
|
+
self._state_change_callbacks[state] = []
|
|
238
|
+
self._state_change_callbacks[state].append(callback)
|
|
239
|
+
|
|
240
|
+
def _execute_callbacks(self, from_state: StateType, to_state: StateType) -> None:
|
|
241
|
+
"""Execute callbacks for state transitions."""
|
|
242
|
+
for callback in self._state_change_callbacks.get(to_state, []):
|
|
243
|
+
try:
|
|
244
|
+
callback(from_state, to_state)
|
|
245
|
+
except Exception as e:
|
|
246
|
+
logger.error(f"State change callback failed: {e}")
|
|
247
|
+
|
|
248
|
+
def get_valid_transitions(self) -> frozenset[StateType]:
|
|
249
|
+
"""Get all valid transitions from current state."""
|
|
250
|
+
return self._valid_transitions.get(self._current_state, frozenset())
|
|
251
|
+
|
|
252
|
+
def is_terminal_state(self) -> bool:
|
|
253
|
+
"""Check if current state is terminal (no outgoing transitions)."""
|
|
254
|
+
return len(self.get_valid_transitions()) == 0
|
|
255
|
+
|
|
256
|
+
def get_state_summary(self) -> dict[str, Any]:
|
|
257
|
+
"""Get a summary of the current state machine state."""
|
|
258
|
+
return {
|
|
259
|
+
"current_state": self._current_state,
|
|
260
|
+
"valid_transitions": list(self.get_valid_transitions()),
|
|
261
|
+
"is_terminal": self.is_terminal_state(),
|
|
262
|
+
"transition_count": len(self._transition_history),
|
|
263
|
+
"last_transition": (self._transition_history[-1] if self._transition_history else None),
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
# Container State Machine Configuration
|
|
268
|
+
CONTAINER_TRANSITIONS: dict[ContainerState, frozenset[ContainerState]] = {
|
|
269
|
+
"creating": frozenset(["running", "error"]),
|
|
270
|
+
"running": frozenset(["stopped", "error", "paused"]),
|
|
271
|
+
"stopped": frozenset(["running", "removing"]),
|
|
272
|
+
"error": frozenset(["removing", "creating"]),
|
|
273
|
+
"paused": frozenset(["running", "stopped"]),
|
|
274
|
+
"removing": frozenset(), # Terminal state
|
|
275
|
+
"restarting": frozenset(["running", "error"]),
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
# Analysis State Machine Configuration
|
|
279
|
+
ANALYSIS_TRANSITIONS: dict[AnalysisState, frozenset[AnalysisState]] = {
|
|
280
|
+
"queued": frozenset(["processing", "cancelled"]),
|
|
281
|
+
"processing": frozenset(["completed", "failed", "cancelled", "timeout"]),
|
|
282
|
+
"completed": frozenset(), # Terminal state
|
|
283
|
+
"failed": frozenset(["queued"]), # Can retry
|
|
284
|
+
"cancelled": frozenset(), # Terminal state
|
|
285
|
+
"timeout": frozenset(["queued"]), # Can retry
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
# Response State Machine Configuration
|
|
289
|
+
RESPONSE_TRANSITIONS: dict[ResponseState, frozenset[ResponseState]] = {
|
|
290
|
+
"pending": frozenset(["success", "error", "timeout", "unauthorized", "forbidden"]),
|
|
291
|
+
"success": frozenset(), # Terminal state
|
|
292
|
+
"error": frozenset(["pending"]), # Can retry
|
|
293
|
+
"timeout": frozenset(["pending"]), # Can retry
|
|
294
|
+
"unauthorized": frozenset(["pending"]), # Can retry after authentication
|
|
295
|
+
"forbidden": frozenset(), # Terminal state - requires permission changes
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
# Health State Machine Configuration
|
|
299
|
+
HEALTH_TRANSITIONS: dict[HealthState, frozenset[HealthState]] = {
|
|
300
|
+
"healthy": frozenset(["degraded", "unhealthy", "unknown", "critical", "maintenance"]),
|
|
301
|
+
"degraded": frozenset(["healthy", "unhealthy", "unknown", "critical"]),
|
|
302
|
+
"unhealthy": frozenset(["healthy", "degraded", "unknown", "critical"]),
|
|
303
|
+
"unknown": frozenset(["healthy", "degraded", "unhealthy", "critical"]),
|
|
304
|
+
"critical": frozenset(["healthy", "degraded", "unhealthy", "unknown", "maintenance"]),
|
|
305
|
+
"maintenance": frozenset(
|
|
306
|
+
["healthy", "degraded", "unknown"]
|
|
307
|
+
), # Usually returns to good states after maintenance
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
class ContainerStateMachine(StateMachine[ContainerState]):
|
|
312
|
+
"""Type-safe container state machine.
|
|
313
|
+
|
|
314
|
+
Manages the lifecycle of Docker containers with proper state validation.
|
|
315
|
+
"""
|
|
316
|
+
|
|
317
|
+
def __init__(self, initial_state: ContainerState = "creating"):
|
|
318
|
+
super().__init__(initial_state, CONTAINER_TRANSITIONS)
|
|
319
|
+
|
|
320
|
+
# Add container-specific transition rules
|
|
321
|
+
self._add_container_rules()
|
|
322
|
+
|
|
323
|
+
def _add_container_rules(self) -> None:
|
|
324
|
+
"""Add container-specific transition rules."""
|
|
325
|
+
|
|
326
|
+
# Example: Only allow removing if container is stopped or error
|
|
327
|
+
def can_remove(sm: Any) -> bool:
|
|
328
|
+
return sm.current_state in ["stopped", "error"]
|
|
329
|
+
|
|
330
|
+
remove_rule: StateTransitionRule[ContainerState] = StateTransitionRule(
|
|
331
|
+
from_state="stopped",
|
|
332
|
+
to_state="removing",
|
|
333
|
+
condition=can_remove,
|
|
334
|
+
description="Can only remove stopped or error containers",
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
self._transition_rules.append(remove_rule)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
class AnalysisStateMachine(StateMachine[AnalysisState]):
|
|
341
|
+
"""Type-safe analysis state machine.
|
|
342
|
+
|
|
343
|
+
Manages the lifecycle of code analysis operations.
|
|
344
|
+
"""
|
|
345
|
+
|
|
346
|
+
def __init__(self, initial_state: AnalysisState = "queued"):
|
|
347
|
+
super().__init__(initial_state, ANALYSIS_TRANSITIONS)
|
|
348
|
+
|
|
349
|
+
# Add analysis-specific transition rules
|
|
350
|
+
self._add_analysis_rules()
|
|
351
|
+
|
|
352
|
+
def _add_analysis_rules(self) -> None:
|
|
353
|
+
"""Add analysis-specific transition rules."""
|
|
354
|
+
|
|
355
|
+
# Example: Track processing start time
|
|
356
|
+
def start_processing(sm: Any) -> None:
|
|
357
|
+
if hasattr(sm, "_processing_start_time"):
|
|
358
|
+
sm._processing_start_time = datetime.now(UTC)
|
|
359
|
+
|
|
360
|
+
processing_rule: StateTransitionRule[AnalysisState] = StateTransitionRule(
|
|
361
|
+
from_state="queued",
|
|
362
|
+
to_state="processing",
|
|
363
|
+
action=start_processing,
|
|
364
|
+
description="Record processing start time",
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
self._transition_rules.append(processing_rule)
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
class ResponseStateMachine(StateMachine[ResponseState]):
|
|
371
|
+
"""Type-safe response state machine.
|
|
372
|
+
|
|
373
|
+
Manages the lifecycle of API responses and async operations.
|
|
374
|
+
"""
|
|
375
|
+
|
|
376
|
+
def __init__(self, initial_state: ResponseState = "pending"):
|
|
377
|
+
super().__init__(initial_state, RESPONSE_TRANSITIONS)
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
class HealthStateMachine(StateMachine[HealthState]):
|
|
381
|
+
"""Type-safe health state machine.
|
|
382
|
+
|
|
383
|
+
Manages the health status of system components.
|
|
384
|
+
"""
|
|
385
|
+
|
|
386
|
+
def __init__(self, initial_state: HealthState = "unknown"):
|
|
387
|
+
super().__init__(initial_state, HEALTH_TRANSITIONS)
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
# Utility functions for state machine management
|
|
391
|
+
def create_container_state_machine(
|
|
392
|
+
initial_state: ContainerState = "creating",
|
|
393
|
+
) -> ContainerStateMachine:
|
|
394
|
+
"""Create a new container state machine."""
|
|
395
|
+
return ContainerStateMachine(initial_state)
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def create_analysis_state_machine(
|
|
399
|
+
initial_state: AnalysisState = "queued",
|
|
400
|
+
) -> AnalysisStateMachine:
|
|
401
|
+
"""Create a new analysis state machine."""
|
|
402
|
+
return AnalysisStateMachine(initial_state)
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def create_response_state_machine(
|
|
406
|
+
initial_state: ResponseState = "pending",
|
|
407
|
+
) -> ResponseStateMachine:
|
|
408
|
+
"""Create a new response state machine."""
|
|
409
|
+
return ResponseStateMachine(initial_state)
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
def create_health_state_machine(
|
|
413
|
+
initial_state: HealthState = "unknown",
|
|
414
|
+
) -> HealthStateMachine:
|
|
415
|
+
"""Create a new health state machine."""
|
|
416
|
+
return HealthStateMachine(initial_state)
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
# State machine factory
|
|
420
|
+
def create_state_machine(state_type: str, initial_state: str | None = None) -> StateMachine:
|
|
421
|
+
"""Factory function to create state machines based on type.
|
|
422
|
+
|
|
423
|
+
Args:
|
|
424
|
+
state_type: Type of state machine ('container', 'analysis', 'response', 'health')
|
|
425
|
+
initial_state: Optional initial state
|
|
426
|
+
|
|
427
|
+
Returns:
|
|
428
|
+
Appropriate state machine instance
|
|
429
|
+
|
|
430
|
+
Raises:
|
|
431
|
+
ValueError: If state_type is not recognized
|
|
432
|
+
"""
|
|
433
|
+
factories = {
|
|
434
|
+
"container": lambda s: create_container_state_machine(s or "creating"),
|
|
435
|
+
"analysis": lambda s: create_analysis_state_machine(s or "queued"),
|
|
436
|
+
"response": lambda s: create_response_state_machine(s or "pending"),
|
|
437
|
+
"health": lambda s: create_health_state_machine(s or "unknown"),
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
if state_type not in factories:
|
|
441
|
+
raise ValueError(f"Unknown state machine type: {state_type}")
|
|
442
|
+
|
|
443
|
+
return factories[state_type](initial_state)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Shared utility modules for coding-client.
|
|
2
|
+
|
|
3
|
+
Provides convenience re-exports from specialized utility modules:
|
|
4
|
+
- archive_utils: Backup, temp files, atomic operations
|
|
5
|
+
- async_timeout: Async timeout management
|
|
6
|
+
- constants: Shared constants
|
|
7
|
+
- content_processing: JSON/YAML processing
|
|
8
|
+
- database: Database operation utilities
|
|
9
|
+
- encoding_helper: Encoding detection and conversion
|
|
10
|
+
- enum_helpers: Safe enum value access
|
|
11
|
+
- file_operations: Compatibility layer for all file operations
|
|
12
|
+
- filesystem_ops: Core filesystem operations
|
|
13
|
+
- http_client: HTTP client with retry support
|
|
14
|
+
- path_utils: Path validation and manipulation
|
|
15
|
+
- profiling: Performance profiling decorators
|
|
16
|
+
- response_builder: Standardized API response building
|
|
17
|
+
- retry_helper: Retry decorators for docker/network
|
|
18
|
+
- security_validation: File security validation
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
# Submodule re-exports
|
|
22
|
+
from . import (
|
|
23
|
+
archive_utils,
|
|
24
|
+
async_timeout,
|
|
25
|
+
constants,
|
|
26
|
+
content_processing,
|
|
27
|
+
database,
|
|
28
|
+
encoding_helper,
|
|
29
|
+
enum_helpers,
|
|
30
|
+
file_operations,
|
|
31
|
+
filesystem_ops,
|
|
32
|
+
http_client,
|
|
33
|
+
path_utils,
|
|
34
|
+
profiling,
|
|
35
|
+
response_builder,
|
|
36
|
+
retry_helper,
|
|
37
|
+
security_validation,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# Key items from commonly used modules
|
|
41
|
+
from .async_timeout import AsyncTaskManager, AsyncTimeoutError, run_with_timeout, with_timeout
|
|
42
|
+
from .http_client import HTTPClient, HTTPClientError
|
|
43
|
+
from .response_builder import ResponseBuilder, build_error_response, build_success_response
|
|
44
|
+
from .retry_helper import (
|
|
45
|
+
DATABASE_RETRYABLE_EXCEPTIONS,
|
|
46
|
+
RetryConfig,
|
|
47
|
+
RetryHelper,
|
|
48
|
+
retry_database,
|
|
49
|
+
retry_docker,
|
|
50
|
+
retry_network,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
__all__ = [
|
|
54
|
+
# Submodules
|
|
55
|
+
"archive_utils",
|
|
56
|
+
"async_timeout",
|
|
57
|
+
"constants",
|
|
58
|
+
"content_processing",
|
|
59
|
+
"database",
|
|
60
|
+
"encoding_helper",
|
|
61
|
+
"enum_helpers",
|
|
62
|
+
"file_operations",
|
|
63
|
+
"filesystem_ops",
|
|
64
|
+
"http_client",
|
|
65
|
+
"path_utils",
|
|
66
|
+
"profiling",
|
|
67
|
+
"response_builder",
|
|
68
|
+
"retry_helper",
|
|
69
|
+
"security_validation",
|
|
70
|
+
# Key items
|
|
71
|
+
"DATABASE_RETRYABLE_EXCEPTIONS",
|
|
72
|
+
"AsyncTaskManager",
|
|
73
|
+
"AsyncTimeoutError",
|
|
74
|
+
"build_error_response",
|
|
75
|
+
"build_success_response",
|
|
76
|
+
"HTTPClient",
|
|
77
|
+
"HTTPClientError",
|
|
78
|
+
"ResponseBuilder",
|
|
79
|
+
"RetryConfig",
|
|
80
|
+
"RetryHelper",
|
|
81
|
+
"retry_database",
|
|
82
|
+
"retry_docker",
|
|
83
|
+
"retry_network",
|
|
84
|
+
"run_with_timeout",
|
|
85
|
+
"with_timeout",
|
|
86
|
+
]
|