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.
Files changed (109) hide show
  1. coding_client/__init__.py +1 -0
  2. coding_client/api/__init__.py +19 -0
  3. coding_client/api/exception_handlers.py +158 -0
  4. coding_client/api/logging_setup.py +44 -0
  5. coding_client/api/metrics.py +40 -0
  6. coding_client/api/openapi_config.py +124 -0
  7. coding_client/api/request_logging.py +60 -0
  8. coding_client/api/validation.py +150 -0
  9. coding_client/auth/__init__.py +78 -0
  10. coding_client/auth/_default_tenant.py +132 -0
  11. coding_client/auth/_header_trust.py +98 -0
  12. coding_client/auth/_onbehalf.py +315 -0
  13. coding_client/auth/_trusted_networks.py +94 -0
  14. coding_client/auth/dependencies.py +61 -0
  15. coding_client/auth/middleware.py +445 -0
  16. coding_client/auth/models.py +83 -0
  17. coding_client/auth/outbound.py +137 -0
  18. coding_client/auth/rbac.py +21 -0
  19. coding_client/auth/rbac_coding.py +5 -0
  20. coding_client/auth/tenant.py +650 -0
  21. coding_client/auth/websocket.py +148 -0
  22. coding_client/clients/__init__.py +24 -0
  23. coding_client/clients/base.py +131 -0
  24. coding_client/clients/coding_ai.py +33 -0
  25. coding_client/clients/coding_analysis.py +36 -0
  26. coding_client/clients/coding_containers.py +45 -0
  27. coding_client/clients/coding_github.py +127 -0
  28. coding_client/clients/coding_realtime.py +127 -0
  29. coding_client/clients/coding_security.py +51 -0
  30. coding_client/clients/coding_testing.py +31 -0
  31. coding_client/clients/coding_workspaces.py +265 -0
  32. coding_client/config/__init__.py +12 -0
  33. coding_client/config/base_settings.py +61 -0
  34. coding_client/config/yaml_config_loader.py +172 -0
  35. coding_client/db/__init__.py +42 -0
  36. coding_client/db/db_types.py +122 -0
  37. coding_client/db/exceptions.py +59 -0
  38. coding_client/db/postgres_connection.py +572 -0
  39. coding_client/db/tenant_query_helper.py +417 -0
  40. coding_client/db_init.py +49 -0
  41. coding_client/exceptions/__init__.py +67 -0
  42. coding_client/exceptions/api_exceptions.py +395 -0
  43. coding_client/exceptions/pydantic_ai_exceptions.py +533 -0
  44. coding_client/interfaces/__init__.py +22 -0
  45. coding_client/interfaces/cache_interface.py +179 -0
  46. coding_client/interfaces/container_interface.py +49 -0
  47. coding_client/interfaces/docker_interface.py +283 -0
  48. coding_client/interfaces/graph_interface.py +28 -0
  49. coding_client/interfaces/security_interface.py +72 -0
  50. coding_client/interfaces/webhook_interface.py +67 -0
  51. coding_client/llm/__init__.py +54 -0
  52. coding_client/llm/base_agent.py +195 -0
  53. coding_client/llm/context.py +121 -0
  54. coding_client/llm/responses.py +102 -0
  55. coding_client/llm/routing.py +82 -0
  56. coding_client/llm/settings.py +102 -0
  57. coding_client/models/__init__.py +62 -0
  58. coding_client/models/agent.py +38 -0
  59. coding_client/models/ai_test_failure.py +745 -0
  60. coding_client/models/analysis.py +330 -0
  61. coding_client/models/architecture.py +685 -0
  62. coding_client/models/bug.py +128 -0
  63. coding_client/models/code_graph.py +317 -0
  64. coding_client/models/container.py +241 -0
  65. coding_client/models/core.py +878 -0
  66. coding_client/models/docker_container.py +104 -0
  67. coding_client/models/github.py +293 -0
  68. coding_client/models/logging.py +29 -0
  69. coding_client/models/package_manager.py +257 -0
  70. coding_client/models/pr_validation.py +440 -0
  71. coding_client/models/pydantic_ai.py +395 -0
  72. coding_client/models/repo_scanner.py +249 -0
  73. coding_client/models/responses.py +431 -0
  74. coding_client/models/security.py +185 -0
  75. coding_client/models/static_analysis.py +508 -0
  76. coding_client/models/system.py +48 -0
  77. coding_client/models/test_executor.py +204 -0
  78. coding_client/models/testing.py +673 -0
  79. coding_client/models/testing_config.py +200 -0
  80. coding_client/models/validation.py +127 -0
  81. coding_client/models_init.py +272 -0
  82. coding_client/telemetry.py +195 -0
  83. coding_client/testing.py +180 -0
  84. coding_client/types/__init__.py +130 -0
  85. coding_client/types/constraint_types.py +276 -0
  86. coding_client/types/core_types.py +112 -0
  87. coding_client/types/state_machine.py +443 -0
  88. coding_client/utils/__init__.py +86 -0
  89. coding_client/utils/archive_utils.py +387 -0
  90. coding_client/utils/ast_debug.py +45 -0
  91. coding_client/utils/async_timeout.py +364 -0
  92. coding_client/utils/constants.py +518 -0
  93. coding_client/utils/content_processing.py +326 -0
  94. coding_client/utils/database.py +916 -0
  95. coding_client/utils/encoding_helper.py +222 -0
  96. coding_client/utils/enum_helpers.py +166 -0
  97. coding_client/utils/file_operations.py +177 -0
  98. coding_client/utils/filesystem_ops.py +528 -0
  99. coding_client/utils/http_client.py +562 -0
  100. coding_client/utils/path_utils.py +471 -0
  101. coding_client/utils/profiling.py +421 -0
  102. coding_client/utils/response_builder.py +240 -0
  103. coding_client/utils/retry_helper.py +712 -0
  104. coding_client/utils/security_validation.py +423 -0
  105. coding_client-1.21.2.dist-info/METADATA +234 -0
  106. coding_client-1.21.2.dist-info/RECORD +109 -0
  107. coding_client-1.21.2.dist-info/WHEEL +5 -0
  108. coding_client-1.21.2.dist-info/licenses/LICENSE +201 -0
  109. coding_client-1.21.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,673 @@
1
+ """Testing models for container-based test execution and quality analysis.
2
+
3
+ This module provides comprehensive Pydantic models for the testing system that supports
4
+ multi-language test execution in containerized environments. It includes models for
5
+ test requests, results, quality analysis, and container-based execution with security
6
+ controls and resource management.
7
+
8
+ The testing system supports:
9
+ - Multi-language test execution (Python, JavaScript, TypeScript, Go, Rust, Java, C#, C++)
10
+ - Quality analysis with automated tool integration
11
+ - Resource limits and security controls
12
+ - Container-based isolation and execution
13
+ - Comprehensive result reporting with metadata
14
+
15
+ Key Model Categories:
16
+ Request Models: TestRequest, QualityRequest, QualityScanRequest
17
+ Response Models: TestResult, QualityResult, ContainerTestResult
18
+ Configuration Models: ResourceLimits, TestSecurityLevel, TestType
19
+ Factory Functions: Helper functions for creating model instances
20
+
21
+ Example:
22
+ ```python
23
+ from testing_models import TestRequest, TestResult, TestSecurityLevel, TestType
24
+
25
+ # Create a test request
26
+ request = TestRequest(
27
+ code='def add(a, b): return a + b',
28
+ test_code='def test_add(): assert add(2, 3) == 5',
29
+ language="python",
30
+ test_type=TestType.UNIT,
31
+ security_level=TestSecurityLevel.HIGH,
32
+ run_quality_check=True,
33
+ timeout=30
34
+ )
35
+
36
+ # Process test result
37
+ result = TestResult(
38
+ test_id="test-123",
39
+ success=True,
40
+ stdout="Test passed",
41
+ stderr="",
42
+ exit_code=0,
43
+ execution_time=2.5,
44
+ created_at=datetime.now(timezone.utc),
45
+ language="python",
46
+ security_level=TestSecurityLevel.HIGH
47
+ )
48
+ ```
49
+ """
50
+
51
+ from __future__ import annotations
52
+
53
+ from datetime import UTC, datetime
54
+ from enum import StrEnum
55
+ from typing import Any
56
+ from uuid import uuid7
57
+
58
+ from pydantic import (
59
+ BaseModel,
60
+ ConfigDict,
61
+ Field,
62
+ computed_field,
63
+ field_serializer,
64
+ field_validator,
65
+ model_validator,
66
+ )
67
+
68
+
69
+ class TestType(StrEnum):
70
+ """Test execution types."""
71
+
72
+ UNIT = "unit"
73
+ INTEGRATION = "integration"
74
+ BENCHMARK = "benchmark"
75
+ COVERAGE = "coverage"
76
+ SECURITY = "security"
77
+ DOC = "doc"
78
+ CLIPPY = "clippy"
79
+ FMT = "fmt"
80
+ VET = "vet"
81
+ RACE = "race"
82
+
83
+
84
+ class ResourceLimits(BaseModel):
85
+ """Resource limits for test execution."""
86
+
87
+ memory_mb: int | None = Field(None, ge=128, le=4096, description="Memory limit in MB")
88
+ cpu_cores: float | None = Field(None, ge=0.1, le=4.0, description="CPU cores limit")
89
+ timeout_seconds: int | None = Field(None, ge=1, le=3600, description="Timeout in seconds")
90
+
91
+
92
+ class TestSecurityLevel(StrEnum):
93
+ """Security levels for test execution."""
94
+
95
+ LOW = "low" # Minimal security, maximum resources
96
+ MEDIUM = "medium" # Balanced security and performance
97
+ HIGH = "high" # Maximum security, minimal resources
98
+ CRITICAL = "critical" # Critical security level
99
+
100
+
101
+ # Backward compatibility alias
102
+ SecurityLevel = TestSecurityLevel
103
+
104
+
105
+ class TestRequest(BaseModel):
106
+ """Request model for test execution."""
107
+
108
+ __test__ = False # Not a pytest test class
109
+
110
+ model_config = ConfigDict(
111
+ validate_assignment=True,
112
+ extra="forbid",
113
+ str_strip_whitespace=True,
114
+ ignored_types=(property,), # Ignore computed fields in serialization
115
+ json_schema_extra={
116
+ "example": {
117
+ "code": "def add(a, b):\n return a + b",
118
+ "test_code": "def test_add():\n assert add(2, 3) == 5\n assert add(-1, 1) == 0",
119
+ "language": "python",
120
+ "container_name": "my-project-tests",
121
+ "test_type": "unit",
122
+ "security_level": "high",
123
+ "timeout": 30,
124
+ "run_quality_check": True,
125
+ "dependencies": {"pytest": "^7.0.0"},
126
+ "resource_limits": {"memory_mb": 512, "cpu_cores": 1.0},
127
+ }
128
+ },
129
+ )
130
+
131
+ # Core test data
132
+ code: str | None = Field(None, description="Code to test")
133
+ test_code: str | None = Field(None, description="Test code to execute")
134
+ language: str = Field(description="Programming language (python, javascript, typescript)")
135
+
136
+ # Execution parameters
137
+ timeout: int | None = Field(None, ge=1, le=300, description="Test timeout in seconds")
138
+ security_level: TestSecurityLevel = Field(
139
+ default=TestSecurityLevel.HIGH,
140
+ description="Security level (defaults to HIGH for security)",
141
+ )
142
+ with_hypothesis: bool = Field(default=False, description="Enable hypothesis testing (Python)")
143
+
144
+ # File-based testing
145
+ code_path: str | None = Field(None, description="Path to code file")
146
+ code_file_path: str | None = Field(None, description="Alias for code file path")
147
+ test_path: str | None = Field(None, description="Path to test file")
148
+ test_file_path: str | None = Field(None, description="Alias for test file path")
149
+
150
+ # Container preferences
151
+ container_name: str | None = Field(None, description="Container name for test execution")
152
+
153
+ # New fields for unification
154
+ test_type: TestType | None = Field(TestType.UNIT, description="Type of test to run")
155
+ dependencies: dict[str, str] = Field(default_factory=dict, description="Package dependencies")
156
+ resource_limits: ResourceLimits | None = Field(None, description="Resource limits")
157
+
158
+ # Quality integration
159
+ run_quality_check: bool = Field(False, description="Run quality analysis after test")
160
+ quality_tools: list[str] | None = Field(None, description="Specific quality tools to run")
161
+
162
+ @field_validator("language")
163
+ @classmethod
164
+ def validate_language(cls, v: str) -> str:
165
+ """Validate supported languages."""
166
+ # Handle empty string
167
+ if not v or v.strip() == "":
168
+ raise ValueError("Language cannot be empty")
169
+
170
+ # Extended to support all 9 languages
171
+ supported = [
172
+ "python",
173
+ "javascript",
174
+ "typescript",
175
+ "nodejs",
176
+ "go",
177
+ "rust",
178
+ "java",
179
+ "csharp",
180
+ "cpp",
181
+ ]
182
+ if v.lower() not in supported:
183
+ raise ValueError(f"Language must be one of: {supported}")
184
+ return v.lower()
185
+
186
+ @model_validator(mode="after")
187
+ def validate_test_request(self) -> TestRequest:
188
+ """Validate cross-field dependencies."""
189
+ if not self.code and not self.code_path and not self.code_file_path:
190
+ raise ValueError("Either code or code_path/code_file_path must be provided")
191
+ if not self.test_code and not self.test_path and not self.test_file_path:
192
+ raise ValueError("Either test_code or test_path/test_file_path must be provided")
193
+ return self
194
+
195
+ # Computed fields using Pydantic v2
196
+ @computed_field
197
+ def code_file_path_resolved(self) -> str | None:
198
+ """Get code file path from either field."""
199
+ return self.code_file_path or self.code_path
200
+
201
+ @computed_field
202
+ def test_file_path_resolved(self) -> str | None:
203
+ """Get test file path from either field."""
204
+ return self.test_file_path or self.test_path
205
+
206
+
207
+ class TestResult(BaseModel):
208
+ """Result model for test execution."""
209
+
210
+ __test__ = False # Not a pytest test class
211
+
212
+ model_config = ConfigDict(
213
+ validate_assignment=True,
214
+ extra="forbid",
215
+ json_schema_serialization_defaults_required=True,
216
+ )
217
+
218
+ # Execution metadata
219
+ test_id: str = Field(description="Unique test execution ID")
220
+ success: bool = Field(description="Whether tests passed")
221
+
222
+ # Output data
223
+ stdout: str = Field(default="", description="Standard output")
224
+ stderr: str = Field(default="", description="Standard error output")
225
+ exit_code: int = Field(description="Process exit code")
226
+
227
+ # Timing and context
228
+ execution_time: float = Field(description="Execution time in seconds")
229
+ created_at: datetime = Field(description="When test was executed")
230
+
231
+ # Request context
232
+ language: str = Field(description="Programming language used")
233
+ security_level: TestSecurityLevel = Field(description="Security level used")
234
+
235
+ # Optional additional data
236
+ coverage_data: dict[str, Any] | None = Field(None, description="Code coverage data")
237
+ test_metadata: dict[str, Any] | None = Field(None, description="Additional test metadata")
238
+
239
+ # New fields for unification
240
+ test_type: str | None = Field(None, description="Type of test executed")
241
+ container_name: str | None = Field(None, description="Container used for execution")
242
+ quality_report: QualityResult | None = Field(None, description="Quality analysis report")
243
+ error: str | None = Field(None, description="Error message if test failed")
244
+
245
+ @field_serializer("created_at")
246
+ def serialize_datetime(self, v: datetime | None) -> str | None:
247
+ if v is None:
248
+ return None
249
+ if v.tzinfo:
250
+ return v.isoformat()
251
+ return v.replace(tzinfo=UTC).isoformat()
252
+
253
+
254
+ class QualityRequest(BaseModel):
255
+ """Request model for quality analysis."""
256
+
257
+ model_config = ConfigDict(
258
+ validate_assignment=True,
259
+ extra="forbid",
260
+ str_strip_whitespace=True,
261
+ ignored_types=(property,), # Ignore computed fields in serialization
262
+ json_schema_extra={
263
+ "example": {
264
+ "code": "import os\nimport sys\n\ndef process_data(data):\n # TODO: implement processing\n pass",
265
+ "container_name": "my-python-project",
266
+ "language": "python",
267
+ "tools": ["ruff", "ty"],
268
+ "fix_issues": True,
269
+ "fail_on_issues": False,
270
+ "custom_rules": {"max_line_length": 100, "allow_todos": False},
271
+ }
272
+ },
273
+ )
274
+
275
+ # Container scoping - REQUIRED for container-based operations
276
+ container_name: str = Field(description="Container name for scoped quality analysis")
277
+
278
+ # Code to analyze
279
+ code: str | None = Field(None, description="Code content to analyze")
280
+ code_content: str | None = Field(None, description="Alias for code content")
281
+ code_path: str | None = Field(None, description="Path to code file")
282
+ language: str | None = Field(None, description="Programming language")
283
+
284
+ # Tool selection
285
+ tools: list[str] | None = Field(None, description="Specific tools to run")
286
+
287
+ # Analysis options
288
+ fix_issues: bool = Field(default=False, description="Automatically apply resolves")
289
+ auto_resolve: bool = Field(default=False, description="Automatically apply resolves (alias)")
290
+ fail_on_issues: bool = Field(default=False, description="Fail if issues found")
291
+ custom_rules: dict[str, Any] | None = Field(None, description="Custom rules configuration")
292
+
293
+ @field_validator("language")
294
+ @classmethod
295
+ def validate_language(cls, v: str | None) -> str | None:
296
+ """Validate supported languages."""
297
+ if v is None:
298
+ return v
299
+ # Handle empty string
300
+ if v.strip() == "":
301
+ raise ValueError("Language cannot be empty")
302
+ supported = ["python", "javascript", "typescript", "nodejs"]
303
+ if v.lower() not in supported:
304
+ raise ValueError(f"Language must be one of: {supported}")
305
+ return v.lower()
306
+
307
+ @model_validator(mode="after")
308
+ def validate_quality_request(self) -> QualityRequest:
309
+ """Validate that we have code to analyze."""
310
+ if not self.code and not self.code_content and not self.code_path:
311
+ raise ValueError("Either code, code_content, or code_path must be provided")
312
+ return self
313
+
314
+ # Computed field using Pydantic v2
315
+ @computed_field
316
+ def code_content_resolved(self) -> str | None:
317
+ """Get code content from either field."""
318
+ return self.code_content or self.code
319
+
320
+
321
+ class QualityScanRequest(BaseModel):
322
+ """Request model for container-wide quality scanning."""
323
+
324
+ model_config = ConfigDict(
325
+ validate_assignment=True,
326
+ extra="forbid",
327
+ str_strip_whitespace=True,
328
+ json_schema_extra={
329
+ "example": {
330
+ "container_name": "my-fastapi-backend",
331
+ "scan_path": "/app/data",
332
+ "recursive": True,
333
+ "include_patterns": ["*.py", "*.js", "*.ts"],
334
+ "exclude_patterns": [
335
+ "__pycache__",
336
+ "node_modules",
337
+ "*.min.js",
338
+ ],
339
+ "max_files": 500,
340
+ "language": "python",
341
+ "tools": ["ruff", "ty", "bandit"],
342
+ "fix_issues": False,
343
+ "parallel_analysis": True,
344
+ "deep_analysis": True,
345
+ "fail_on_critical": True,
346
+ "min_quality_score": 80.0,
347
+ "timeout": 1800,
348
+ }
349
+ },
350
+ )
351
+
352
+ # Container to scan
353
+ container_name: str = Field(description="Container name to scan")
354
+
355
+ # Scan parameters
356
+ scan_path: str | None = Field(
357
+ default="/app/data", description="Base path to scan within container"
358
+ )
359
+ recursive: bool = Field(default=True, description="Scan directories recursively")
360
+
361
+ # File filtering
362
+ include_patterns: list[str] = Field(
363
+ default_factory=lambda: ["*.py", "*.js", "*.ts", "*.jsx", "*.tsx"],
364
+ description="File patterns to include in scan",
365
+ )
366
+ exclude_patterns: list[str] = Field(
367
+ default_factory=lambda: [
368
+ "__pycache__",
369
+ "*.pyc",
370
+ ".git",
371
+ "node_modules",
372
+ "*.min.js",
373
+ ],
374
+ description="Patterns to exclude from scan",
375
+ )
376
+ max_files: int = Field(default=500, ge=1, le=2000, description="Maximum files to scan")
377
+
378
+ # Language and tool selection
379
+ language: str | None = Field(None, description="Primary language (auto-detect if None)")
380
+ tools: list[str] | None = Field(
381
+ None, description="Specific tools to run (all available if None)"
382
+ )
383
+
384
+ # Analysis options
385
+ fix_issues: bool = Field(default=False, description="Attempt to apply resolves automatically")
386
+ parallel_analysis: bool = Field(
387
+ default=True, description="Run tools in parallel for performance"
388
+ )
389
+ deep_analysis: bool = Field(default=False, description="Enable comprehensive analysis mode")
390
+
391
+ # Quality thresholds
392
+ fail_on_critical: bool = Field(default=False, description="Fail scan if critical issues found")
393
+ min_quality_score: float | None = Field(
394
+ None, ge=0.0, le=100.0, description="Minimum acceptable quality score"
395
+ )
396
+
397
+ # Custom configuration
398
+ custom_rules: dict[str, Any] | None = Field(None, description="Custom rules configuration")
399
+ timeout: int | None = Field(default=3600, ge=30, le=7200, description="Scan timeout in seconds")
400
+
401
+ @field_validator("language")
402
+ @classmethod
403
+ def validate_language(cls, v: str | None) -> str | None:
404
+ """Validate supported languages."""
405
+ if v is None:
406
+ return v
407
+ # Handle empty string
408
+ if v.strip() == "":
409
+ raise ValueError("Language cannot be empty")
410
+ supported = ["python", "javascript", "typescript", "nodejs"]
411
+ if v.lower() not in supported:
412
+ raise ValueError(f"Language must be one of: {supported}")
413
+ return v.lower()
414
+
415
+
416
+ class QualityIssue(BaseModel):
417
+ """Individual quality issue found by analysis tools."""
418
+
419
+ model_config = ConfigDict(validate_assignment=True, extra="forbid", str_strip_whitespace=True)
420
+
421
+ tool: str = Field(description="Tool that found the issue")
422
+ severity: str = Field(description="Issue severity (critical, error, warning, info)")
423
+ message: str = Field(description="Issue description")
424
+ file_path: str = Field(description="File path where issue was found")
425
+ line_number: int | None = Field(None, description="Line number (if applicable)")
426
+ column_number: int | None = Field(None, description="Column number (if applicable)")
427
+ rule_id: str | None = Field(None, description="Rule ID that triggered issue")
428
+
429
+ # Optional resolve suggestions
430
+ suggested_fix: str | None = Field(None, description="Suggested resolve for the issue")
431
+ fix_confidence: float | None = Field(None, ge=0.0, le=1.0, description="Confidence in resolve")
432
+
433
+
434
+ class QualityResult(BaseModel):
435
+ """Result model for quality analysis."""
436
+
437
+ model_config = ConfigDict(
438
+ validate_assignment=True,
439
+ extra="forbid",
440
+ )
441
+
442
+ # Analysis metadata
443
+ analysis_id: str = Field(description="Unique analysis ID")
444
+ success: bool = Field(default=True, description="Whether analysis completed successfully")
445
+ overall_score: float = Field(ge=0.0, le=100.0, description="Overall quality score (0-100)")
446
+ total_issues: int = Field(ge=0, description="Total number of issues found")
447
+
448
+ # Detailed results
449
+ issues: list[QualityIssue] = Field(default=[], description="List of issues found")
450
+ tools_results: dict[str, dict[str, Any]] = Field(default={}, description="Results by tool")
451
+
452
+ # Execution context
453
+ execution_time: float = Field(description="Analysis time in seconds")
454
+ created_at: datetime = Field(description="When analysis was performed")
455
+ language: str = Field(description="Programming language analyzed")
456
+
457
+ # Error handling
458
+ error: str | None = Field(None, description="Error message if analysis failed")
459
+
460
+ @field_serializer("created_at")
461
+ def serialize_datetime(self, v: datetime | None) -> str | None:
462
+ if v is None:
463
+ return None
464
+ if v.tzinfo:
465
+ return v.isoformat()
466
+ return v.replace(tzinfo=UTC).isoformat()
467
+
468
+ @model_validator(mode="after")
469
+ def validate_quality_result(self) -> QualityResult:
470
+ """Validate quality result consistency."""
471
+ # Auto-correct missing error message for failed analysis
472
+ if not self.success and (not self.error or self.error.strip() == ""):
473
+ self.error = "Analysis failed without specific error details"
474
+
475
+ if self.total_issues != len(self.issues):
476
+ # Auto-correct for consistency
477
+ self.total_issues = len(self.issues)
478
+ return self
479
+
480
+ @classmethod
481
+ def create_failed_result(
482
+ cls,
483
+ analysis_id: str | None = None,
484
+ error_message: str | None = None,
485
+ language: str = "unknown",
486
+ execution_time: float = 0.0,
487
+ ) -> QualityResult:
488
+ """Factory method to create failed QualityResult with safe error handling."""
489
+ # Ensure analysis_id is provided
490
+ if not analysis_id:
491
+ analysis_id = str(uuid7())
492
+
493
+ # Ensure error message is never None or empty
494
+ if not error_message or error_message.strip() == "":
495
+ error_message = "Quality analysis failed - no specific error details available"
496
+
497
+ return cls(
498
+ analysis_id=analysis_id,
499
+ success=False,
500
+ overall_score=0.0,
501
+ total_issues=0,
502
+ issues=[],
503
+ tools_results={},
504
+ execution_time=execution_time,
505
+ created_at=datetime.now(UTC),
506
+ language=language,
507
+ error=error_message,
508
+ )
509
+
510
+
511
+ class CommandResult(BaseModel):
512
+ """Result of command execution in container."""
513
+
514
+ model_config = ConfigDict(validate_assignment=True, extra="forbid")
515
+
516
+ exit_code: int = Field(description="Command exit code")
517
+ stdout: str = Field(default="", description="Standard output")
518
+ stderr: str = Field(default="", description="Standard error output")
519
+ execution_time: float = Field(description="Execution time in seconds")
520
+ timeout: bool = Field(default=False, description="Whether command timed out")
521
+
522
+
523
+ class ContainerTestResult(BaseModel):
524
+ """Extended test result with container-specific information."""
525
+
526
+ model_config = ConfigDict(validate_assignment=True, extra="forbid")
527
+
528
+ # Base test result
529
+ test_result: TestResult = Field(description="Core test execution result")
530
+
531
+ # Container context
532
+ container_name: str = Field(description="Container name used for execution")
533
+ container_image: str = Field(description="Container image used")
534
+
535
+ # Resource usage
536
+ memory_used: int | None = Field(None, description="Peak memory usage in MB")
537
+ cpu_time: float | None = Field(None, description="CPU time used in seconds")
538
+
539
+ # Security context
540
+ security_violations: list[str] = Field(default=[], description="Security violations detected")
541
+ sandbox_effective: bool = Field(default=True, description="Whether sandbox was effective")
542
+
543
+
544
+ class TestExecutionStatus(StrEnum):
545
+ """Status of test execution."""
546
+
547
+ PENDING = "pending"
548
+ RUNNING = "running"
549
+ COMPLETED = "completed"
550
+ FAILED = "failed"
551
+ TIMEOUT = "timeout"
552
+ CANCELLED = "cancelled"
553
+
554
+
555
+ class TestExecutionInfo(BaseModel):
556
+ """Information about a test execution in progress."""
557
+
558
+ model_config = ConfigDict(validate_assignment=True, extra="forbid")
559
+
560
+ execution_id: str = Field(description="Unique execution ID")
561
+ status: TestExecutionStatus = Field(description="Current execution status")
562
+ started_at: datetime = Field(description="When execution started")
563
+ updated_at: datetime = Field(description="Last status update")
564
+
565
+ # Progress information
566
+ progress_percentage: float | None = Field(
567
+ None, ge=0.0, le=100.0, description="Execution progress"
568
+ )
569
+ current_step: str | None = Field(None, description="Current execution step")
570
+
571
+ # Results (available when completed)
572
+ result: TestResult | None = Field(None, description="Test result when completed")
573
+ error_message: str | None = Field(None, description="Error message if failed")
574
+
575
+
576
+ # Factory functions for easy model creation
577
+ def create_test_request(
578
+ code: str,
579
+ language: str,
580
+ container_name: str,
581
+ test_code: str | None = None,
582
+ security_level: TestSecurityLevel = TestSecurityLevel.MEDIUM,
583
+ timeout: int | None = None,
584
+ code_path: str | None = None,
585
+ code_file_path: str | None = None,
586
+ test_path: str | None = None,
587
+ test_file_path: str | None = None,
588
+ test_type: TestType | None = None,
589
+ dependencies: dict[str, str] | None = None,
590
+ resource_limits: ResourceLimits | None = None,
591
+ run_quality_check: bool = False,
592
+ quality_tools: list[str] | None = None,
593
+ ) -> TestRequest:
594
+ """Factory function to create test request."""
595
+ return TestRequest(
596
+ code=code,
597
+ test_code=test_code,
598
+ language=language,
599
+ security_level=security_level,
600
+ timeout=timeout,
601
+ code_path=code_path,
602
+ code_file_path=code_file_path,
603
+ test_path=test_path,
604
+ test_file_path=test_file_path,
605
+ container_name=container_name,
606
+ test_type=test_type,
607
+ dependencies=dependencies or {},
608
+ resource_limits=resource_limits,
609
+ run_quality_check=run_quality_check,
610
+ quality_tools=quality_tools,
611
+ )
612
+
613
+
614
+ def create_quality_request(
615
+ container_name: str,
616
+ code: str | None = None,
617
+ code_path: str | None = None,
618
+ language: str | None = None,
619
+ tools: list[str] | None = None,
620
+ ) -> QualityRequest:
621
+ """Factory function to create quality request.
622
+
623
+ Note: This creates a QualityRequest (for single file analysis),
624
+ not a QualityScanRequest (for container-wide scanning).
625
+ """
626
+ return QualityRequest(
627
+ container_name=container_name,
628
+ code=code,
629
+ code_path=code_path,
630
+ language=language,
631
+ tools=tools,
632
+ )
633
+
634
+
635
+ def create_test_result(
636
+ success: bool,
637
+ container_name: str,
638
+ stdout: str = "",
639
+ stderr: str = "",
640
+ exit_code: int = 0,
641
+ execution_time: float = 0.0,
642
+ language: str = "python",
643
+ security_level: TestSecurityLevel = TestSecurityLevel.MEDIUM,
644
+ test_id: str | None = None,
645
+ created_at: datetime | None = None,
646
+ coverage_data: dict[str, Any] | None = None,
647
+ test_metadata: dict[str, Any] | None = None,
648
+ test_type: str | None = None,
649
+ quality_report: QualityResult | None = None,
650
+ error: str | None = None,
651
+ ) -> TestResult:
652
+ """Factory function to create test result."""
653
+ return TestResult(
654
+ test_id=test_id or str(uuid7()),
655
+ success=success,
656
+ stdout=stdout,
657
+ stderr=stderr,
658
+ exit_code=exit_code,
659
+ execution_time=execution_time,
660
+ created_at=created_at or datetime.now(UTC),
661
+ coverage_data=coverage_data,
662
+ test_metadata=test_metadata,
663
+ language=language,
664
+ security_level=security_level,
665
+ test_type=test_type,
666
+ container_name=container_name,
667
+ quality_report=quality_report,
668
+ error=error,
669
+ )
670
+
671
+
672
+ # Rebuild model to ensure proper forward reference resolution
673
+ QualityResult.model_rebuild()