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,364 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Async timeout utilities for handling long-running operations.
|
|
3
|
+
|
|
4
|
+
This module provides comprehensive timeout management for async operations,
|
|
5
|
+
including decorators, context managers, and task management utilities.
|
|
6
|
+
It helps prevent operations from hanging indefinitely and provides
|
|
7
|
+
proactive timeout handling with detailed error reporting.
|
|
8
|
+
|
|
9
|
+
Key Components:
|
|
10
|
+
- AsyncTimeoutError: Custom HTTP timeout exception for API operations
|
|
11
|
+
- run_with_timeout: Utility function to run coroutines with timeout
|
|
12
|
+
- with_timeout: Decorator for adding timeout to async functions
|
|
13
|
+
- AsyncTaskManager: Manager for handling background async tasks
|
|
14
|
+
|
|
15
|
+
Timeout Features:
|
|
16
|
+
- Automatic cleanup of timed-out operations
|
|
17
|
+
- Detailed error messages with timeout duration
|
|
18
|
+
- Background task management with status tracking
|
|
19
|
+
- Task cancellation and result retrieval
|
|
20
|
+
- HTTP-compatible timeout exceptions
|
|
21
|
+
|
|
22
|
+
Usage:
|
|
23
|
+
>>> @with_timeout(30.0, "Analysis timed out")
|
|
24
|
+
>>> async def analyze_code(repo_path):
|
|
25
|
+
>>> # Long-running analysis code
|
|
26
|
+
>>> pass
|
|
27
|
+
>>>
|
|
28
|
+
>>> # Or use directly
|
|
29
|
+
>>> result = await run_with_timeout(some_coro(), 60.0)
|
|
30
|
+
>>>
|
|
31
|
+
>>> # Task management
|
|
32
|
+
>>> task_id = task_manager.submit_task("analysis", analyze_code(path))
|
|
33
|
+
>>> status = task_manager.get_task_status(task_id)
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
from __future__ import annotations
|
|
37
|
+
|
|
38
|
+
import asyncio
|
|
39
|
+
import functools
|
|
40
|
+
import logging
|
|
41
|
+
from collections.abc import Awaitable, Callable
|
|
42
|
+
from typing import Any, TypeVar
|
|
43
|
+
|
|
44
|
+
from fastapi import HTTPException, status
|
|
45
|
+
|
|
46
|
+
logger = logging.getLogger(__name__)
|
|
47
|
+
|
|
48
|
+
T = TypeVar("T")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class AsyncTimeoutError(HTTPException):
|
|
52
|
+
"""Custom timeout exception for API operations.
|
|
53
|
+
|
|
54
|
+
This exception is raised when async operations exceed their configured
|
|
55
|
+
timeout duration. It extends HTTPException to provide HTTP-compatible
|
|
56
|
+
error responses with 408 Request Timeout status code.
|
|
57
|
+
|
|
58
|
+
Attributes:
|
|
59
|
+
status_code: HTTP status code (408 Request Timeout)
|
|
60
|
+
detail: Error message including timeout duration
|
|
61
|
+
timeout_seconds: The timeout duration that was exceeded
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
def __init__(self, detail: str, timeout_seconds: float):
|
|
65
|
+
"""
|
|
66
|
+
Initialize a timeout error with context information.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
detail: Human-readable error detail message describing the operation
|
|
70
|
+
timeout_seconds: The timeout duration in seconds that was exceeded
|
|
71
|
+
|
|
72
|
+
Example:
|
|
73
|
+
>>> raise AsyncTimeoutError("Database query timed out", 30.0)
|
|
74
|
+
# Results in HTTP 408 with message: "Database query timed out (timeout: 30.0s)"
|
|
75
|
+
"""
|
|
76
|
+
super().__init__(
|
|
77
|
+
status_code=status.HTTP_408_REQUEST_TIMEOUT,
|
|
78
|
+
detail=f"{detail} (timeout: {timeout_seconds}s)",
|
|
79
|
+
)
|
|
80
|
+
self.timeout_seconds = timeout_seconds
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
async def run_with_timeout[T](
|
|
84
|
+
coro: Awaitable[T],
|
|
85
|
+
timeout_seconds: float,
|
|
86
|
+
timeout_message: str = "Operation timed out",
|
|
87
|
+
) -> T:
|
|
88
|
+
"""
|
|
89
|
+
Run an async coroutine with a timeout.
|
|
90
|
+
|
|
91
|
+
Wraps any awaitable with a timeout mechanism, automatically canceling
|
|
92
|
+
the operation if it exceeds the specified duration. This prevents
|
|
93
|
+
operations from hanging indefinitely and provides predictable behavior.
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
coro: The coroutine (awaitable) to run with timeout protection
|
|
97
|
+
timeout_seconds: Maximum time to wait in seconds (can be fractional)
|
|
98
|
+
timeout_message: Custom error message if timeout occurs
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
The result of the coroutine if completed within timeout
|
|
102
|
+
|
|
103
|
+
Raises:
|
|
104
|
+
AsyncTimeoutError: If the operation times out, with HTTP 408 status
|
|
105
|
+
Any other exception: Propagated from the original coroutine
|
|
106
|
+
|
|
107
|
+
Example:
|
|
108
|
+
>>> result = await run_with_timeout(
|
|
109
|
+
... expensive_operation(),
|
|
110
|
+
... 30.0,
|
|
111
|
+
... "Code analysis timed out"
|
|
112
|
+
... )
|
|
113
|
+
|
|
114
|
+
Note:
|
|
115
|
+
The timeout is enforced using asyncio.wait_for, which will
|
|
116
|
+
cancel the underlying coroutine if it doesn't complete in time.
|
|
117
|
+
This ensures proper cleanup of resources.
|
|
118
|
+
"""
|
|
119
|
+
try:
|
|
120
|
+
return await asyncio.wait_for(coro, timeout=timeout_seconds)
|
|
121
|
+
except TimeoutError:
|
|
122
|
+
logger.warning(f"Operation timed out after {timeout_seconds}s: {timeout_message}")
|
|
123
|
+
raise AsyncTimeoutError(timeout_message, timeout_seconds)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def with_timeout(timeout_seconds: float, timeout_message: str | None = None):
|
|
127
|
+
"""
|
|
128
|
+
Decorator to add timeout to async functions.
|
|
129
|
+
|
|
130
|
+
This decorator wraps async functions with automatic timeout handling,
|
|
131
|
+
preventing them from running indefinitely. It preserves the original
|
|
132
|
+
function signature and docstring while adding timeout protection.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
timeout_seconds: Maximum time to wait in seconds (can be fractional)
|
|
136
|
+
timeout_message: Optional custom timeout message (defaults to function name)
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
Decorator function that wraps async functions with timeout protection
|
|
140
|
+
|
|
141
|
+
Usage:
|
|
142
|
+
>>> @with_timeout(30.0, "Graph initialization timed out")
|
|
143
|
+
>>> async def initialize_graph(repo_path: str) -> GraphResult:
|
|
144
|
+
>>> # Long-running graph initialization
|
|
145
|
+
>>> return await build_graph(repo_path)
|
|
146
|
+
>>>
|
|
147
|
+
>>> # Function automatically times out after 30 seconds
|
|
148
|
+
>>> result = await initialize_graph("/path/to/repo")
|
|
149
|
+
|
|
150
|
+
Note:
|
|
151
|
+
The decorator preserves the original function's type hints and
|
|
152
|
+
metadata using functools.wraps. The timeout is applied to each
|
|
153
|
+
individual function call, not globally.
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
def decorator(
|
|
157
|
+
func: Callable[..., Awaitable[T]],
|
|
158
|
+
) -> Callable[..., Awaitable[T]]:
|
|
159
|
+
func_name = getattr(func, "__name__", repr(func))
|
|
160
|
+
|
|
161
|
+
@functools.wraps(func)
|
|
162
|
+
async def wrapper(*args: Any, **kwargs: Any) -> T:
|
|
163
|
+
message = timeout_message or f"{func_name} timed out"
|
|
164
|
+
return await run_with_timeout(func(*args, **kwargs), timeout_seconds, message)
|
|
165
|
+
|
|
166
|
+
return wrapper
|
|
167
|
+
|
|
168
|
+
return decorator
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class AsyncTaskManager:
|
|
172
|
+
"""Manager for handling long-running async tasks.
|
|
173
|
+
|
|
174
|
+
This class provides comprehensive task management capabilities for
|
|
175
|
+
async operations, including task submission, status tracking, result
|
|
176
|
+
retrieval, and cancellation. It's designed for scenarios where you
|
|
177
|
+
need to run async operations in the background and check their
|
|
178
|
+
status later.
|
|
179
|
+
|
|
180
|
+
Features:
|
|
181
|
+
- Background task execution with unique task IDs
|
|
182
|
+
- Status tracking (running, completed, failed, not_found)
|
|
183
|
+
- Result storage and retrieval
|
|
184
|
+
- Error handling and logging
|
|
185
|
+
- Task cancellation support
|
|
186
|
+
- Automatic cleanup of completed tasks
|
|
187
|
+
|
|
188
|
+
Attributes:
|
|
189
|
+
tasks: Dictionary mapping task IDs to active asyncio.Task objects
|
|
190
|
+
results: Dictionary storing results of completed tasks
|
|
191
|
+
errors: Dictionary storing error messages from failed tasks
|
|
192
|
+
|
|
193
|
+
Usage:
|
|
194
|
+
>>> manager = AsyncTaskManager()
|
|
195
|
+
>>> task_id = manager.submit_task("analysis", analyze_code(path))
|
|
196
|
+
>>> # Check status later
|
|
197
|
+
>>> status = manager.get_task_status(task_id)
|
|
198
|
+
>>> if status["status"] == "completed":
|
|
199
|
+
>>> result = status["result"]
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
def __init__(self):
|
|
203
|
+
"""
|
|
204
|
+
Initialize the task manager with empty task tracking dictionaries.
|
|
205
|
+
|
|
206
|
+
Creates empty dictionaries for tracking active tasks, completed
|
|
207
|
+
results, and error messages. All task state is maintained in
|
|
208
|
+
memory and will be lost when the manager is destroyed.
|
|
209
|
+
"""
|
|
210
|
+
self.tasks: dict[str, asyncio.Task] = {}
|
|
211
|
+
self.results: dict[str, Any] = {}
|
|
212
|
+
self.errors: dict[str, str] = {}
|
|
213
|
+
|
|
214
|
+
def submit_task(self, task_id: str, coro: Awaitable[Any]) -> str:
|
|
215
|
+
"""
|
|
216
|
+
Submit a task for background execution.
|
|
217
|
+
|
|
218
|
+
Creates a new asyncio task for the given coroutine and tracks it
|
|
219
|
+
under the specified task ID. The task runs in the background and
|
|
220
|
+
its result or error will be stored for later retrieval.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
task_id: Unique identifier for the task (must not already exist)
|
|
224
|
+
coro: The coroutine to execute in the background
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
The task ID (same as input, for convenience)
|
|
228
|
+
|
|
229
|
+
Raises:
|
|
230
|
+
ValueError: If a task with the given ID already exists
|
|
231
|
+
|
|
232
|
+
Example:
|
|
233
|
+
>>> manager = AsyncTaskManager()
|
|
234
|
+
>>> task_id = manager.submit_task("scan_123", scan_repository(repo_path))
|
|
235
|
+
>>> # Task is now running in background
|
|
236
|
+
>>> print(f"Submitted task: {task_id}")
|
|
237
|
+
"""
|
|
238
|
+
if task_id in self.tasks:
|
|
239
|
+
raise ValueError(f"Task {task_id} already exists")
|
|
240
|
+
|
|
241
|
+
task = asyncio.create_task(self._run_task(task_id, coro))
|
|
242
|
+
self.tasks[task_id] = task
|
|
243
|
+
return task_id
|
|
244
|
+
|
|
245
|
+
async def _run_task(self, task_id: str, coro: Awaitable[Any]) -> None:
|
|
246
|
+
"""
|
|
247
|
+
Run a task and store its result or error.
|
|
248
|
+
|
|
249
|
+
Internal method that executes the task coroutine and handles
|
|
250
|
+
both successful completion and error cases. Results are stored
|
|
251
|
+
in the appropriate dictionaries and the task is cleaned up
|
|
252
|
+
from the active tasks list.
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
task_id: Unique identifier for the task being executed
|
|
256
|
+
coro: The coroutine to execute
|
|
257
|
+
|
|
258
|
+
Note:
|
|
259
|
+
This method is called internally by submit_task() and should
|
|
260
|
+
not be called directly. It ensures proper error handling,
|
|
261
|
+
logging, and cleanup regardless of task outcome.
|
|
262
|
+
"""
|
|
263
|
+
try:
|
|
264
|
+
result = await coro
|
|
265
|
+
self.results[task_id] = result
|
|
266
|
+
except Exception as e:
|
|
267
|
+
logger.error(f"Task {task_id} failed: {e}")
|
|
268
|
+
self.errors[task_id] = str(e)
|
|
269
|
+
finally:
|
|
270
|
+
# Clean up completed task
|
|
271
|
+
if task_id in self.tasks:
|
|
272
|
+
del self.tasks[task_id]
|
|
273
|
+
|
|
274
|
+
def get_task_status(self, task_id: str) -> dict[str, Any]:
|
|
275
|
+
"""
|
|
276
|
+
Get the status of a task.
|
|
277
|
+
|
|
278
|
+
Retrieves the current status of a task, including its result
|
|
279
|
+
if completed or error information if failed. This method can
|
|
280
|
+
be called repeatedly to poll task progress.
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
task_id: Unique identifier for the task to check
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
Dict with status information containing:
|
|
287
|
+
- "status": One of "running", "completed", "failed", "not_found"
|
|
288
|
+
- "task_id": The task identifier
|
|
289
|
+
- "result": Task result (only if status is "completed")
|
|
290
|
+
- "error": Error message (only if status is "failed")
|
|
291
|
+
|
|
292
|
+
Example:
|
|
293
|
+
>>> status = manager.get_task_status("scan_123")
|
|
294
|
+
>>> if status["status"] == "completed":
|
|
295
|
+
>>> print(f"Task completed: {status['result']}")
|
|
296
|
+
>>> elif status["status"] == "failed":
|
|
297
|
+
>>> print(f"Task failed: {status['error']}")
|
|
298
|
+
>>> elif status["status"] == "running":
|
|
299
|
+
>>> print("Task is still running...")
|
|
300
|
+
"""
|
|
301
|
+
if task_id in self.tasks:
|
|
302
|
+
return {"status": "running", "task_id": task_id}
|
|
303
|
+
elif task_id in self.results:
|
|
304
|
+
return {
|
|
305
|
+
"status": "completed",
|
|
306
|
+
"task_id": task_id,
|
|
307
|
+
"result": self.results[task_id],
|
|
308
|
+
}
|
|
309
|
+
elif task_id in self.errors:
|
|
310
|
+
return {
|
|
311
|
+
"status": "failed",
|
|
312
|
+
"task_id": task_id,
|
|
313
|
+
"error": self.errors[task_id],
|
|
314
|
+
}
|
|
315
|
+
else:
|
|
316
|
+
return {"status": "not_found", "task_id": task_id}
|
|
317
|
+
|
|
318
|
+
def cancel_task(self, task_id: str) -> bool:
|
|
319
|
+
"""
|
|
320
|
+
Cancel a running task.
|
|
321
|
+
|
|
322
|
+
Attempts to cancel a task that is currently running. Only active
|
|
323
|
+
tasks can be cancelled; completed or failed tasks cannot be cancelled.
|
|
324
|
+
The task will be removed from the active tasks list.
|
|
325
|
+
|
|
326
|
+
Args:
|
|
327
|
+
task_id: Unique identifier for the task to cancel
|
|
328
|
+
|
|
329
|
+
Returns:
|
|
330
|
+
True if task was found and cancelled, False if task was not found
|
|
331
|
+
or was not in a cancellable state
|
|
332
|
+
|
|
333
|
+
Example:
|
|
334
|
+
>>> success = manager.cancel_task("long_running_task")
|
|
335
|
+
>>> if success:
|
|
336
|
+
>>> print("Task cancelled successfully")
|
|
337
|
+
>>> else:
|
|
338
|
+
>>> print("Task not found or already completed")
|
|
339
|
+
|
|
340
|
+
Note:
|
|
341
|
+
Cancelled tasks will not appear in results or errors dictionaries.
|
|
342
|
+
The task coroutine will receive a CancelledError exception.
|
|
343
|
+
"""
|
|
344
|
+
if task_id in self.tasks:
|
|
345
|
+
self.tasks[task_id].cancel()
|
|
346
|
+
del self.tasks[task_id]
|
|
347
|
+
return True
|
|
348
|
+
return False
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
# Global task manager instance
|
|
352
|
+
task_manager = AsyncTaskManager()
|
|
353
|
+
"""Global instance of AsyncTaskManager for application-wide task management.
|
|
354
|
+
|
|
355
|
+
This singleton instance can be used throughout the application to manage
|
|
356
|
+
long-running async tasks. It provides a centralized way to submit, track,
|
|
357
|
+
and cancel background operations.
|
|
358
|
+
|
|
359
|
+
Example:
|
|
360
|
+
>>> from utils.async_timeout import task_manager
|
|
361
|
+
>>> task_id = task_manager.submit_task("analysis", analyze_code(path))
|
|
362
|
+
>>> # Later, check status
|
|
363
|
+
>>> status = task_manager.get_task_status(task_id)
|
|
364
|
+
"""
|