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,395 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Custom API Exceptions
|
|
3
|
+
|
|
4
|
+
These exceptions automatically convert to standardized error responses
|
|
5
|
+
via the global exception handlers.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from fastapi import HTTPException, status
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class BaseAPIException(HTTPException):
|
|
16
|
+
"""
|
|
17
|
+
Base class for all API exceptions.
|
|
18
|
+
|
|
19
|
+
Provides consistent structure for error details.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
status_code: int,
|
|
25
|
+
message: str,
|
|
26
|
+
error_code: str,
|
|
27
|
+
details: dict[str, Any] | None = None,
|
|
28
|
+
headers: dict[str, str] | None = None,
|
|
29
|
+
):
|
|
30
|
+
# Create structured detail dict
|
|
31
|
+
detail: dict[str, Any] = {"message": message, "error_code": error_code}
|
|
32
|
+
if details:
|
|
33
|
+
detail["details"] = details
|
|
34
|
+
|
|
35
|
+
super().__init__(status_code=status_code, detail=detail, headers=headers)
|
|
36
|
+
self.error_code = error_code
|
|
37
|
+
self.details = details
|
|
38
|
+
# For compatibility with global exception handler
|
|
39
|
+
self.error_details = details
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class AgentConfigurationError(BaseAPIException):
|
|
43
|
+
"""
|
|
44
|
+
Exception raised when AI agent configuration is invalid.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(
|
|
48
|
+
self,
|
|
49
|
+
message: str = "Agent configuration error",
|
|
50
|
+
error_code: str = "AGENT_CONFIG_ERROR",
|
|
51
|
+
details: dict[str, Any] | None = None,
|
|
52
|
+
):
|
|
53
|
+
super().__init__(
|
|
54
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
55
|
+
message=message,
|
|
56
|
+
error_code=error_code,
|
|
57
|
+
details=details,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class AITimeoutError(BaseAPIException):
|
|
62
|
+
"""
|
|
63
|
+
Exception raised when AI service request times out.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
def __init__(
|
|
67
|
+
self,
|
|
68
|
+
message: str = "AI service timeout",
|
|
69
|
+
error_code: str = "AI_TIMEOUT",
|
|
70
|
+
details: dict[str, Any] | None = None,
|
|
71
|
+
):
|
|
72
|
+
super().__init__(
|
|
73
|
+
status_code=status.HTTP_504_GATEWAY_TIMEOUT,
|
|
74
|
+
message=message,
|
|
75
|
+
error_code=error_code,
|
|
76
|
+
details=details,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class AIValidationError(BaseAPIException):
|
|
81
|
+
"""
|
|
82
|
+
Exception raised when AI service input validation fails.
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
def __init__(
|
|
86
|
+
self,
|
|
87
|
+
message: str = "AI input validation error",
|
|
88
|
+
error_code: str = "AI_VALIDATION_ERROR",
|
|
89
|
+
details: dict[str, Any] | None = None,
|
|
90
|
+
):
|
|
91
|
+
super().__init__(
|
|
92
|
+
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
|
|
93
|
+
message=message,
|
|
94
|
+
error_code=error_code,
|
|
95
|
+
details=details,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class NotFoundException(BaseAPIException):
|
|
100
|
+
"""
|
|
101
|
+
Exception for resource not found errors (404).
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
def __init__(
|
|
105
|
+
self,
|
|
106
|
+
message: str = "Resource not found",
|
|
107
|
+
error_code: str = "NOT_FOUND",
|
|
108
|
+
resource_type: str | None = None,
|
|
109
|
+
resource_id: str | None = None,
|
|
110
|
+
details: dict[str, Any] | None = None,
|
|
111
|
+
):
|
|
112
|
+
# Add resource info to details
|
|
113
|
+
if resource_type or resource_id:
|
|
114
|
+
if details is None:
|
|
115
|
+
details = {}
|
|
116
|
+
if resource_type:
|
|
117
|
+
details["resource_type"] = resource_type
|
|
118
|
+
if resource_id:
|
|
119
|
+
details["resource_id"] = resource_id
|
|
120
|
+
|
|
121
|
+
super().__init__(
|
|
122
|
+
status_code=status.HTTP_404_NOT_FOUND,
|
|
123
|
+
message=message,
|
|
124
|
+
error_code=error_code,
|
|
125
|
+
details=details,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class ValidationException(BaseAPIException):
|
|
130
|
+
"""
|
|
131
|
+
Exception for validation errors (400).
|
|
132
|
+
|
|
133
|
+
Use this for business logic validation, not request validation.
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
def __init__(
|
|
137
|
+
self,
|
|
138
|
+
message: str = "Validation failed",
|
|
139
|
+
error_code: str = "VALIDATION_FAILED",
|
|
140
|
+
field: str | None = None,
|
|
141
|
+
details: dict[str, Any] | None = None,
|
|
142
|
+
):
|
|
143
|
+
# Add field info to details
|
|
144
|
+
if field:
|
|
145
|
+
if details is None:
|
|
146
|
+
details = {}
|
|
147
|
+
details["field"] = field
|
|
148
|
+
|
|
149
|
+
super().__init__(
|
|
150
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
151
|
+
message=message,
|
|
152
|
+
error_code=error_code,
|
|
153
|
+
details=details,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class AuthenticationException(BaseAPIException):
|
|
158
|
+
"""
|
|
159
|
+
Exception for authentication failures (401).
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
def __init__(
|
|
163
|
+
self,
|
|
164
|
+
message: str = "Authentication required",
|
|
165
|
+
error_code: str = "UNAUTHORIZED",
|
|
166
|
+
details: dict[str, Any] | None = None,
|
|
167
|
+
headers: dict[str, str] | None = None,
|
|
168
|
+
):
|
|
169
|
+
# Add WWW-Authenticate header if not present
|
|
170
|
+
if headers is None:
|
|
171
|
+
headers = {}
|
|
172
|
+
if "WWW-Authenticate" not in headers:
|
|
173
|
+
headers["WWW-Authenticate"] = "Bearer"
|
|
174
|
+
|
|
175
|
+
super().__init__(
|
|
176
|
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
177
|
+
message=message,
|
|
178
|
+
error_code=error_code,
|
|
179
|
+
details=details,
|
|
180
|
+
headers=headers,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class PermissionException(BaseAPIException):
|
|
185
|
+
"""
|
|
186
|
+
Exception for permission/authorization failures (403).
|
|
187
|
+
"""
|
|
188
|
+
|
|
189
|
+
def __init__(
|
|
190
|
+
self,
|
|
191
|
+
message: str = "Insufficient permissions",
|
|
192
|
+
error_code: str = "FORBIDDEN",
|
|
193
|
+
required_permission: str | None = None,
|
|
194
|
+
details: dict[str, Any] | None = None,
|
|
195
|
+
):
|
|
196
|
+
# Add permission info to details
|
|
197
|
+
if required_permission:
|
|
198
|
+
if details is None:
|
|
199
|
+
details = {}
|
|
200
|
+
details["required_permission"] = required_permission
|
|
201
|
+
|
|
202
|
+
super().__init__(
|
|
203
|
+
status_code=status.HTTP_403_FORBIDDEN,
|
|
204
|
+
message=message,
|
|
205
|
+
error_code=error_code,
|
|
206
|
+
details=details,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
class ConflictException(BaseAPIException):
|
|
211
|
+
"""
|
|
212
|
+
Exception for resource conflicts (409).
|
|
213
|
+
"""
|
|
214
|
+
|
|
215
|
+
def __init__(
|
|
216
|
+
self,
|
|
217
|
+
message: str = "Resource conflict",
|
|
218
|
+
error_code: str = "CONFLICT",
|
|
219
|
+
conflicting_resource: str | None = None,
|
|
220
|
+
details: dict[str, Any] | None = None,
|
|
221
|
+
):
|
|
222
|
+
# Add conflict info to details
|
|
223
|
+
if conflicting_resource:
|
|
224
|
+
if details is None:
|
|
225
|
+
details = {}
|
|
226
|
+
details["conflicting_resource"] = conflicting_resource
|
|
227
|
+
|
|
228
|
+
super().__init__(
|
|
229
|
+
status_code=status.HTTP_409_CONFLICT,
|
|
230
|
+
message=message,
|
|
231
|
+
error_code=error_code,
|
|
232
|
+
details=details,
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
class ServiceUnavailableException(BaseAPIException):
|
|
237
|
+
"""
|
|
238
|
+
Exception for service unavailable errors (503).
|
|
239
|
+
"""
|
|
240
|
+
|
|
241
|
+
def __init__(
|
|
242
|
+
self,
|
|
243
|
+
message: str = "Service temporarily unavailable",
|
|
244
|
+
error_code: str = "SERVICE_UNAVAILABLE",
|
|
245
|
+
retry_after: int | None = None,
|
|
246
|
+
details: dict[str, Any] | None = None,
|
|
247
|
+
headers: dict[str, str] | None = None,
|
|
248
|
+
):
|
|
249
|
+
# Add Retry-After header if specified
|
|
250
|
+
if retry_after:
|
|
251
|
+
if headers is None:
|
|
252
|
+
headers = {}
|
|
253
|
+
headers["Retry-After"] = str(retry_after)
|
|
254
|
+
if details is None:
|
|
255
|
+
details = {}
|
|
256
|
+
details["retry_after_seconds"] = retry_after
|
|
257
|
+
|
|
258
|
+
super().__init__(
|
|
259
|
+
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
|
260
|
+
message=message,
|
|
261
|
+
error_code=error_code,
|
|
262
|
+
details=details,
|
|
263
|
+
headers=headers,
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
class RateLimitException(BaseAPIException):
|
|
268
|
+
"""
|
|
269
|
+
Exception for rate limit errors (429).
|
|
270
|
+
"""
|
|
271
|
+
|
|
272
|
+
def __init__(
|
|
273
|
+
self,
|
|
274
|
+
message: str = "Rate limit exceeded",
|
|
275
|
+
error_code: str = "RATE_LIMIT_EXCEEDED",
|
|
276
|
+
retry_after: int | None = None,
|
|
277
|
+
limit: int | None = None,
|
|
278
|
+
remaining: int | None = None,
|
|
279
|
+
reset: int | None = None,
|
|
280
|
+
details: dict[str, Any] | None = None,
|
|
281
|
+
headers: dict[str, str] | None = None,
|
|
282
|
+
):
|
|
283
|
+
if details is None:
|
|
284
|
+
details = {}
|
|
285
|
+
|
|
286
|
+
# Add rate limit info to details
|
|
287
|
+
if limit is not None:
|
|
288
|
+
details["limit"] = limit
|
|
289
|
+
if remaining is not None:
|
|
290
|
+
details["remaining"] = remaining
|
|
291
|
+
if reset is not None:
|
|
292
|
+
details["reset"] = reset
|
|
293
|
+
|
|
294
|
+
# Add rate limit headers
|
|
295
|
+
if headers is None:
|
|
296
|
+
headers = {}
|
|
297
|
+
if retry_after:
|
|
298
|
+
headers["Retry-After"] = str(retry_after)
|
|
299
|
+
if limit is not None:
|
|
300
|
+
headers["X-RateLimit-Limit"] = str(limit)
|
|
301
|
+
if remaining is not None:
|
|
302
|
+
headers["X-RateLimit-Remaining"] = str(remaining)
|
|
303
|
+
if reset is not None:
|
|
304
|
+
headers["X-RateLimit-Reset"] = str(reset)
|
|
305
|
+
|
|
306
|
+
super().__init__(
|
|
307
|
+
status_code=status.HTTP_429_TOO_MANY_REQUESTS,
|
|
308
|
+
message=message,
|
|
309
|
+
error_code=error_code,
|
|
310
|
+
details=details,
|
|
311
|
+
headers=headers,
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
class ProcessingException(BaseAPIException):
|
|
316
|
+
"""
|
|
317
|
+
Exception for processing errors (422).
|
|
318
|
+
|
|
319
|
+
Use this when the request is valid but cannot be processed.
|
|
320
|
+
"""
|
|
321
|
+
|
|
322
|
+
def __init__(
|
|
323
|
+
self,
|
|
324
|
+
message: str = "Unable to process request",
|
|
325
|
+
error_code: str = "UNPROCESSABLE_ENTITY",
|
|
326
|
+
reason: str | None = None,
|
|
327
|
+
details: dict[str, Any] | None = None,
|
|
328
|
+
):
|
|
329
|
+
# Add reason to details
|
|
330
|
+
if reason:
|
|
331
|
+
if details is None:
|
|
332
|
+
details = {}
|
|
333
|
+
details["reason"] = reason
|
|
334
|
+
|
|
335
|
+
super().__init__(
|
|
336
|
+
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
|
|
337
|
+
message=message,
|
|
338
|
+
error_code=error_code,
|
|
339
|
+
details=details,
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
class InternalServerException(BaseAPIException):
|
|
344
|
+
"""
|
|
345
|
+
Exception for internal server errors (500).
|
|
346
|
+
|
|
347
|
+
Use sparingly - most errors should have more specific types.
|
|
348
|
+
"""
|
|
349
|
+
|
|
350
|
+
def __init__(
|
|
351
|
+
self,
|
|
352
|
+
message: str = "Internal server error",
|
|
353
|
+
error_code: str = "INTERNAL_ERROR",
|
|
354
|
+
details: dict[str, Any] | None = None,
|
|
355
|
+
):
|
|
356
|
+
super().__init__(
|
|
357
|
+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
358
|
+
message=message,
|
|
359
|
+
error_code=error_code,
|
|
360
|
+
details=details,
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
class ContainerNotFoundException(BaseAPIException):
|
|
365
|
+
"""
|
|
366
|
+
Exception for when a container is not found.
|
|
367
|
+
"""
|
|
368
|
+
|
|
369
|
+
def __init__(
|
|
370
|
+
self,
|
|
371
|
+
container_name: str,
|
|
372
|
+
message: str | None = None,
|
|
373
|
+
error_code: str = "CONTAINER_NOT_FOUND",
|
|
374
|
+
details: dict[str, Any] | None = None,
|
|
375
|
+
):
|
|
376
|
+
if message is None:
|
|
377
|
+
message = f"Container '{container_name}' not found"
|
|
378
|
+
|
|
379
|
+
if details is None:
|
|
380
|
+
details = {}
|
|
381
|
+
|
|
382
|
+
# Add container info to details
|
|
383
|
+
details.update(
|
|
384
|
+
{
|
|
385
|
+
"resource_type": "container",
|
|
386
|
+
"resource_id": container_name,
|
|
387
|
+
}
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
super().__init__(
|
|
391
|
+
status_code=status.HTTP_400_BAD_REQUEST,
|
|
392
|
+
message=message,
|
|
393
|
+
error_code=error_code,
|
|
394
|
+
details=details,
|
|
395
|
+
)
|